├── .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
--------------------------------------------------------------------------------