1 #!/usr/bin/env python 2 3 from itertools import combinations 4 from random import randint 5 import EXIF 6 import PIL.Image 7 import math 8 import sys 9 10 corners = [ 11 (0, 0, 0), (255, 0, 0), (0, 255, 0), (255, 255, 0), 12 (0, 0, 255), (255, 0, 255), (0, 255, 255), (255, 255, 255) 13 ] 14 15 def distance(rgb1, rgb2): 16 r1, g1, b1 = rgb1 17 r2, g2, b2 = rgb2 18 return math.sqrt(pow(r1 - r2, 2) + pow(g1 - g2, 2) + pow(b1 - b2, 2)) 19 20 def brightness(rgb): 21 return distance(rgb, (0, 0, 0)) 22 23 def factor(start, end, rgb): 24 return distance(start, rgb) / (distance(start, rgb) + distance(rgb, end)) 25 26 def darklight(rgb1, rgb2): 27 if brightness(rgb1) <= brightness(rgb2): 28 return rgb1, rgb2 29 else: 30 return rgb2, rgb1 31 32 def nearest(rgb, values=None): 33 l = map(lambda c: (distance(rgb, c), c), values or corners) 34 l.sort() 35 return l 36 37 def pattern(rgb, values=None): 38 l = nearest(rgb, values) 39 start, end = l[0][1], l[1][1] 40 f = factor(start, end, rgb) 41 #if f > 0.5: 42 # start, end = end, start 43 # f = 1 - f 44 return start, end, f 45 46 def choose(seq, f): 47 last = int(seq * math.sqrt(f)) 48 current = int((seq + 1) * math.sqrt(f)) 49 return last != current 50 51 def get_value(xy, rgb, width, height, values=None): 52 x, y = xy 53 rgb1, rgb2, f = pattern(rgb, values) 54 if choose(x + randint(0, width), f) and choose(y + randint(0, height), f): 55 return rgb2 56 else: 57 return rgb1 58 59 def get_best(rgb, values): 60 return nearest(rgb, values)[0][1] 61 62 def sign(x): 63 return x >= 0 and 1 or -1 64 65 def saturate_rgb(rgb, exp): 66 return tuple([saturate_value(x, exp) for x in rgb]) 67 68 def saturate_value(x, exp): 69 return int(127.5 + sign(x - 127.5) * 127.5 * pow(abs(x - 127.5) / 127.5, exp)) 70 71 def test(): 72 size = 512 73 for r in (0, 63, 127, 191, 255): 74 im = PIL.Image.new("RGB", (size, size)) 75 for g in range(0, size): 76 for b in range(0, size): 77 value = get_value((g, b), (r, (g * 256) / size, (b * 256 / size)), size, size) 78 im.putpixel((g, b), value) 79 im.save("rgb%d.png" % r) 80 81 def test_flat(rgb): 82 size = 64 83 im = PIL.Image.new("RGB", (size, size)) 84 for y in range(0, size): 85 for x in range(0, size): 86 im.putpixel((x, y), get_value((x, y), rgb, size, size)) 87 im.save("rgb%02d%02d%02d.png" % rgb) 88 89 def rotate_and_scale(im, width, height, rotate): 90 if rotate or x and x["Image Orientation"].values == [6L]: 91 im = im.rotate(270) 92 93 w, h = im.size 94 if w > h: 95 height = (width * h) / w 96 else: 97 width = (height * w) / h 98 99 return im.resize((width, height)) 100 101 if __name__ == "__main__": 102 if "--test" in sys.argv: 103 test() 104 sys.exit(0) 105 elif "--test-flat" in sys.argv: 106 test_flat((120, 40, 60)) 107 sys.exit(0) 108 109 width = 320 110 height = 256 111 112 input_filename, output_filename = sys.argv[1:3] 113 rotate = "-r" in sys.argv[3:] 114 saturate = sys.argv[3:].count("-s") 115 desaturate = sys.argv[3:].count("-d") 116 117 x = EXIF.process_file(open(input_filename)) 118 im = PIL.Image.open(input_filename) 119 im = rotate_and_scale(im, width, height, rotate) 120 121 width, height = im.size 122 123 colours = [] 124 125 for y in range(0, height): 126 c = {} 127 for x in range(0, width): 128 rgb = im.getpixel((x, y)) 129 130 # Saturate if requested. 131 132 if saturate or desaturate: 133 rgb = saturate_rgb(rgb, saturate and math.pow(0.5, saturate) or math.pow(2, desaturate)) 134 im.putpixel((x, y), rgb) 135 136 # Count the number of requested colours. 137 138 value = get_value((x, y), rgb, width, height) 139 if not c.has_key(value): 140 c[value] = 1 141 else: 142 c[value] += 1 143 144 c = [(n, value) for value, n in c.items()] 145 c.sort(reverse=True) 146 colours.append(c) 147 148 for y, c in enumerate(colours): 149 most = [value for n, value in c[:4]] 150 least = [value for n, value in c[4:]] 151 152 for x in range(0, width): 153 rgb = im.getpixel((x, y)) 154 155 # Get the requested colours and choose the closest alternative for 156 # less common colours. 157 158 value = get_value((x, y), rgb, width, height) 159 if value in least: 160 value = get_value((x, y), rgb, width, height, most) 161 im.putpixel((x, y), value) 162 163 im.save(output_filename) 164 165 # vim: tabstop=4 expandtab shiftwidth=4