├── .gitmodules ├── README.md ├── compression └── compress ├── image_processing └── pixel_sort.py ├── jpeg_scalpel ├── jpeg_ecg_extraction.py └── jpeg_header_byte_glitch.py ├── random_mashing └── todo.sh ├── raw_conversion ├── GIMP │ └── 2.10 │ │ └── scripts │ │ └── jpg_to_raw.scm ├── jpg_to_raw.py ├── jpg_to_raw.scm ├── jpg_to_raw.sh └── raw_to_jpg.sh ├── sonification ├── echo.sh ├── flanger.sh └── phaser.sh ├── wallpaper └── wallpaper_rotate ├── wordpad └── wordpad_effect.py └── world_map.jpg /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lock/glitchlock"] 2 | path = lock/glitchlock 3 | url = https://github.com/xero/glitchlock 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Corruption is lovely # 2 | 3 | This repo contains scripts and ideas about glitching images 4 | 5 | ### Further Reading ### 6 | 7 | - 8 | - 9 | - 10 | - 11 | - 12 | - 13 | - 14 | - 15 | - 16 | - 17 | - 18 | - 19 | - 20 | - 21 | - 22 | - 23 | - 24 | - 25 | - 26 | - 27 | - 28 | - 29 | - [sonification](https://en.wikipedia.org/wiki/Sonification) 30 | - [sox instead of audacity](https://maryknize.com/blog/glitch_art_with_sox_imagemagick_and_vim/) 31 | - 32 | - 33 | - 34 | - 35 | - 36 | - 37 | - 38 | - 39 | - [accidental glitches](https://www.reddit.com/r/glitchart/) 40 | - [intentional glitches](https://www.reddit.com/r/glitch_art/) 41 | - [video datamoshing](https://www.reddit.com/r/datamoshing/) 42 | - [glitchlock](https://github.com/xero/glitchlock) 43 | 44 | 45 | -------------------------------------------------------------------------------- /compression/compress: -------------------------------------------------------------------------------- 1 | convert -quality 2 ../world_map.jpg world_map_compressed.jpg 2 | -------------------------------------------------------------------------------- /image_processing/pixel_sort.py: -------------------------------------------------------------------------------- 1 | from PIL import Image 2 | import colorsys 3 | import sys 4 | 5 | im = Image.open("../world_map.jpg") 6 | 7 | lightness_threshold = 0.20 8 | 9 | def rgb_to_int(r): 10 | out = r[0]; 11 | out = (out << 8) + r[1]; 12 | out = (out << 8) + r[2]; 13 | return out 14 | 15 | def int_to_rgb(out): 16 | red = (out >> 16) & 0xFF; 17 | green = (out >> 8) & 0xFF; 18 | blue = out & 0xFF; 19 | return (red, green, blue) 20 | 21 | def get_first_not_black(im, x, y): 22 | while x < im.width-1: 23 | x += 1 24 | col = im.getpixel((x, y)) 25 | h,l,s = colorsys.rgb_to_hls(col[0]/255.0, col[1]/255.0, col[2]/255.0) 26 | if l >= lightness_threshold: 27 | return x 28 | return -1 29 | 30 | def get_next_black(im, x, y): 31 | x += 1 32 | 33 | while x < im.width-1: 34 | x += 1 35 | col = im.getpixel((x, y)) 36 | h,l,s = colorsys.rgb_to_hls(col[0]/255.0, col[1]/255.0, col[2]/255.0) 37 | if l <= lightness_threshold: 38 | return x 39 | return x-1 40 | 41 | 42 | for y in range(im.height): 43 | x = 0 44 | while x != -1 and x < im.width: 45 | col_arr = [] 46 | start = get_first_not_black(im, x, y) 47 | x = start 48 | end = get_next_black(im, x, y) 49 | x = end 50 | if start == -1 or end == -1: 51 | break 52 | else: 53 | #print "col: " + str(y) + " - start: " + str(start) + " - end: " + str(end) 54 | for i in range(start, end): 55 | col_arr.append(rgb_to_int(im.getpixel((i, y)))) 56 | col_arr.sort() 57 | for i in range(start, end): 58 | im.putpixel((i, y), int_to_rgb(col_arr.pop(0))) 59 | 60 | im.save("sorted.jpg", "JPEG") 61 | 62 | # to modify pixel 63 | 64 | -------------------------------------------------------------------------------- /jpeg_scalpel/jpeg_ecg_extraction.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from struct import * 4 | import math 5 | import os 6 | 7 | 8 | def extract_ecg(data, output_dir): 9 | 10 | index = 0 11 | lenchunk = 0 12 | data_copy = data 13 | file_count = 1; 14 | previous_index = index; 15 | 16 | # TODO also put quantization tables (DCT) and Huffman tables in separate files 17 | while(True): 18 | hdr, = unpack(">H", data[0:2]) 19 | if hdr == 0xffd8: 20 | lenchunk = 2 21 | else: 22 | # fetch the length of this chunk (LV) 23 | lenchunk, = unpack(">H", data[2:4]) 24 | # plus the size of the tag (TLV) 25 | lenchunk+=2 26 | # the actual content/V is placed after TL 27 | chunk = data[4:lenchunk] 28 | # quantization table 29 | #if hdr == 0xffdb 30 | # huffman table 31 | #elif hdr == 0xffc4: 32 | 33 | # last chunk that we process is the start of scan 34 | if hdr == 0xffda: 35 | # create the directory if not present 36 | if not os.path.exists(output_dir): 37 | os.mkdir(output_dir) 38 | 39 | # write everything until this index 40 | print("saving header") 41 | open(output_dir+'/' + str(file_count).zfill(2) +'_header.jpg', 'wb').write(data_copy[:index]) 42 | 43 | file_count += 1 44 | print("saving scan "+str(file_count).zfill(2)) 45 | open(output_dir+'/' + str(file_count).zfill(2) +'_scan.jpg', 'wb').write(data[:lenchunk]) 46 | 47 | # and continue 48 | data = data[lenchunk:] 49 | index += lenchunk 50 | break 51 | 52 | # go to next chunk 53 | data = data[lenchunk:] 54 | index += lenchunk 55 | 56 | 57 | 58 | # now put whatever was before in a header file 59 | 60 | # Now we enter a loop of: 61 | # Start of scan - ECS 62 | # Restart (or not, as set by the restart interval markers in the header) 63 | # ... 64 | # Start of scan - ECS again 65 | # ... 66 | # until end of file FFD9 67 | # if we replace one of the start of scan FFDA with eof FFD9 then 68 | # if the image will only load the first scan/drawing until that point 69 | # When in scanline/progressive vs baseline we directly draw what 70 | # we got at that point (I assume) 71 | 72 | # TODO we could possibly break FF00 (real FF) and FFD0-FFD7 (restart markers) 73 | previous_index = index 74 | lenchunk = 0 75 | while(True): 76 | # read 2 bytes 77 | hdr, = unpack(">H", data[0:2]) 78 | # we reached the end of the file 79 | if hdr == 0xffd9: 80 | break 81 | elif hdr == 0xffda: 82 | # we reached another scan layer, write what we got so far 83 | file_count += 1 84 | print("saving data "+str(file_count).zfill(2)) 85 | open(output_dir+'/' + str(file_count).zfill(2) +'_data.jpg', 'wb').write(data_copy[previous_index:index]) 86 | 87 | # write the header separately 88 | lenchunk, = unpack(">H", data[2:4]) 89 | lenchunk+=2 90 | file_count += 1 91 | print("saving scan "+str(file_count).zfill(2)) 92 | open(output_dir+'/' + str(file_count).zfill(2) +'_scan.jpg', 'wb').write(data[:lenchunk]) 93 | 94 | # and continue 95 | previous_index = index + lenchunk 96 | else: 97 | lenchunk = 2 98 | 99 | # go to next 2 bytes 100 | data = data[lenchunk:] 101 | index += lenchunk 102 | 103 | # last scan layer to save before the ffd9 EoI 104 | file_count += 1 105 | print("saving data "+str(file_count).zfill(2)) 106 | open(output_dir+'/' + str(file_count).zfill(2) +'_data.jpg', 'wb').write(data_copy[previous_index:index]) 107 | previous_index = index 108 | 109 | # finish by writing 0xffd9 to a file 110 | file_count += 1 111 | print("finished saving FFD9") 112 | open(output_dir+'/' + str(file_count).zfill(2) +'_end.jpg', 'wb').write(data[0:2]) 113 | 114 | 115 | extract_ecg(open('../world_map.jpg', 'rb').read(), 'output_folder') 116 | 117 | -------------------------------------------------------------------------------- /jpeg_scalpel/jpeg_header_byte_glitch.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from struct import * 4 | import sys 5 | import random 6 | 7 | def corrupt_byte_and_save(head, btoc, max_change, output): 8 | if len(head) < btoc: 9 | btoc = len(head) - 2 10 | corrupted, = unpack(">B", head[btoc:btoc+1]) 11 | #print(hex(corrupted)) 12 | r = random.randint(1, max_change) 13 | operation = random.choice(['add', 'sub']) 14 | if operation == 'add': 15 | corrupted = (corrupted + r) & 0xFF 16 | elif operation == 'sub': 17 | corrupted = (256 + corrupted - r) & 0xFF 18 | #print(hex(corrupted)) 19 | output.write(head[0:btoc]) 20 | ## because it's immutable 21 | output.write(pack("B", corrupted)) 22 | output.write(head[btoc+1:]) 23 | 24 | def corrupt_header(data, output): 25 | output = open(output, 'wb') 26 | index = 0 27 | lenchunk = 0 28 | parts = [] 29 | while(True): 30 | hdr, = unpack(">H", data[0:2]) 31 | if hdr == 0xffd8: 32 | lenchunk = 2 33 | else: 34 | # fetch the length of this chunk (LV) 35 | lenchunk, = unpack(">H", data[2:4]) 36 | # plus the size of the tag (TLV) 37 | lenchunk+=2 38 | # the actual content/V is placed after TL 39 | chunk = data[4:lenchunk] 40 | if hdr == 0xffda: 41 | break 42 | # go to next chunk 43 | parts.append(data[0:lenchunk]) 44 | data = data[lenchunk:] 45 | index += lenchunk 46 | 47 | # we're done with the header 48 | # now mess one of the part of it and save the rest of data 49 | 50 | mess = random.choice(['quantization', 'huffman']) 51 | count = 0 52 | 53 | # first get a count 54 | for head in parts: 55 | hdr, = unpack(">H", head[0:2]) 56 | if mess == 'quantization' and hdr == 0xFFDB: 57 | count += 1 58 | elif mess == 'huffman' and hdr == 0xFFC4: 59 | count += 1 60 | 61 | # select one to mess between these 62 | #print(mess+ " => " + str(count)) 63 | which_to_mess = random.randint(0, count-1) 64 | count = 0 65 | 66 | for head in parts: 67 | hdr, = unpack(">H", head[0:2]) 68 | if mess == 'quantization' and hdr == 0xFFDB: 69 | if count == which_to_mess: 70 | # choose byte between 5 and 8, byte to corrupt 71 | btoc = random.randint(5, 8) 72 | corrupt_byte_and_save(head, btoc, 255, output) 73 | else: 74 | output.write(head) 75 | count += 1 76 | elif mess == 'huffman' and hdr == 0xFFC4: 77 | if count == which_to_mess: 78 | # choose byte above (2+2+17)21 and lower than 26, byte to corrupt 79 | btoc = random.randint(30, 50) 80 | corrupt_byte_and_save(head, btoc, 6, output) 81 | pass 82 | else: 83 | output.write(head) 84 | count += 1 85 | else: 86 | output.write(head) 87 | 88 | output.write(data) 89 | 90 | if len(sys.argv) != 3: 91 | print(sys.argv[0] + ": image.jpg corrupt.jpg") 92 | sys.exit(1) 93 | 94 | corrupt_header(open(sys.argv[1], 'rb').read(), sys.argv[2]) 95 | 96 | -------------------------------------------------------------------------------- /random_mashing/todo.sh: -------------------------------------------------------------------------------- 1 | # randomly break or add pixels in an image 2 | -------------------------------------------------------------------------------- /raw_conversion/GIMP/2.10/scripts/jpg_to_raw.scm: -------------------------------------------------------------------------------- 1 | ../../../jpg_to_raw.scm -------------------------------------------------------------------------------- /raw_conversion/jpg_to_raw.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import numpy 4 | import PIL.Image 5 | import sys 6 | 7 | if len(sys.argv) < 3: 8 | print(sys.argv[0] + ": image.jpg image.data [interleaved | planar]") 9 | sys.exit(1) 10 | 11 | image = PIL.Image.open(sys.argv[1]) 12 | out = open(sys.argv[2], "wb") 13 | pixels = numpy.asarray(image).flatten().flatten() 14 | 15 | if len(sys.argv) < 4 or sys.argv[3] == "interleaved": 16 | out.write(pixels) 17 | elif sys.argv[3] == "planar": 18 | out.write(bytes(pixels[0::3])) 19 | out.write(bytes(pixels[1::3])) 20 | out.write(bytes(pixels[2::3])) 21 | else: 22 | print("Unknown option:", sys.argv[3]) 23 | sys.exit(1) 24 | -------------------------------------------------------------------------------- /raw_conversion/jpg_to_raw.scm: -------------------------------------------------------------------------------- 1 | ; 2 | ; script to convert jpg to raw/data image 3 | ; 4 | ; for a list of all procedure used check: help > procedure browser 5 | ; 6 | (define (jpg_to_raw in_filename out_filename interleaved?) 7 | (let* ((image (car (gimp-file-load RUN-NONINTERACTIVE in_filename in_filename))) 8 | (drawable (car (gimp-image-get-active-layer image)))) 9 | ; image-type: RAW_RGB (0), RAW_PLANAR (3) 10 | ; palette-type { RAW_PALETTE_RGB (0), RAW_PALETTE_BGR (1) } 11 | (file-raw-save2 RUN-NONINTERACTIVE 12 | image 13 | drawable 14 | out_filename 15 | out_filename 16 | (if interleaved? 0 6) 17 | 0) 18 | (gimp-item-delete drawable) 19 | (gimp-image-delete image))) 20 | -------------------------------------------------------------------------------- /raw_conversion/jpg_to_raw.sh: -------------------------------------------------------------------------------- 1 | # Example: 2 | # ./jpg_to_raw.sh world_map.jpg world_map.data 1 3 | # 1 for interleaved, 0 for planar 4 | # 5 | # Only GIMP can create planar images, even the following only gives interleaved 6 | # stream -interlace plane -map rgb -storage-type char world_map.jpg stream.planar.data 7 | # The -interlace isn't respected in imagemagick 8 | XDG_CONFIG_HOME=$PWD 9 | export XDG_CONFIG_HOME 10 | echo $XDG_CONFIG_HOME 11 | if [ $# -lt 3 ] 12 | then 13 | echo "$0: image.jpg image.data [1|0, for interleaved or planar]" 14 | exit 15 | fi 16 | if [ "XX$3XX" == "XX1XX" ] 17 | then 18 | gimp -i -b "(jpg_to_raw \"$1\" \"$2\" #t)" -b '(gimp-quit 0)' 19 | else 20 | gimp -i -b "(jpg_to_raw \"$1\" \"$2\" #f)" -b '(gimp-quit 0)' 21 | fi 22 | -------------------------------------------------------------------------------- /raw_conversion/raw_to_jpg.sh: -------------------------------------------------------------------------------- 1 | # Example: 2 | # ./raw_to_jpg.sh world_map.data world_map.jpg "2000x1479" 1 3 | # 1 for interleaved, 0 for planar 4 | # 5 | # We can do that using imagemagick but not GIMP because: 6 | # file-raw-load procedure only allows interactive mode 7 | # The run mode { RUN-INTERACTIVE (0) } 8 | if [ $# -lt 4 ] 9 | then 10 | echo "$0: image.data image.jpg widthxheight [1|0, for interleaved or planar]" 11 | exit 12 | fi 13 | 14 | if [ "XX$4XX" == "XX1XX" ] 15 | then 16 | convert -size $3 -depth 8 rgb:$1 $2 17 | else 18 | convert -size $3 -interlace plane -depth 8 rgb:$1 $2 19 | fi 20 | -------------------------------------------------------------------------------- /sonification/echo.sh: -------------------------------------------------------------------------------- 1 | sox -t ul -c 1 -r 48k "$1" -t ul "$2" echo 0.4 0.8 10 0.9 2 | -------------------------------------------------------------------------------- /sonification/flanger.sh: -------------------------------------------------------------------------------- 1 | sox -t ul -c 1 -r 48k "$1" -t ul "$2" flanger 2 | -------------------------------------------------------------------------------- /sonification/phaser.sh: -------------------------------------------------------------------------------- 1 | sox -t ul -c 1 -r 48k "$1" -t ul "$2" phaser 0.3 0.9 1 0.7 0.5 -t 2 | -------------------------------------------------------------------------------- /wallpaper/wallpaper_rotate: -------------------------------------------------------------------------------- 1 | speed=0.5 2 | while : ; do 3 | for i in ` ls images/world_map* -v ` ; do 4 | echo "$i" ; 5 | hsetroot -solid '#A9DFF9' -full "$i" 6 | sleep $speed ; 7 | done ; 8 | done 9 | -------------------------------------------------------------------------------- /wordpad/wordpad_effect.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from struct import * 4 | import sys 5 | import os 6 | 7 | 8 | def wordpad_effect(data, output_file): 9 | 10 | out = open(output_file, 'wb') 11 | print("reading len: "+ str(len(data)) + " and writing: "+output_file) 12 | index = 0 13 | previous_byte = b"\x00" 14 | while index < len(data): 15 | component, = unpack("B", data[index:index+1]) 16 | if component == 0x07: 17 | component = 0x20 18 | 19 | if component == 0x0d: 20 | if index < len(data): 21 | next_byte, = unpack("B", data[index+1:index+2]) 22 | if next_byte != 0x0a: 23 | component |= 0x0a00 24 | elif component == 0x0a and previous_byte != 0x0d: 25 | component = ((component << 8) | 0x0d) 26 | out.write(pack("H", component)) 27 | else: 28 | out.write(pack("B", component)) 29 | index += 1 30 | previous_byte = component 31 | 32 | 33 | wordpad_effect(open('world_map.interleaved.data', 'rb').read(), 'world_map.interleaved.corrupt.data') 34 | wordpad_effect(open('world_map.planar.data', 'rb').read(), 'world_map.planar.corrupt.data') 35 | 36 | -------------------------------------------------------------------------------- /world_map.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/venam/glitching_images/abcf27941cea6eecd7a3189f3a31138bdacd1ab1/world_map.jpg --------------------------------------------------------------------------------