1 #!/usr/bin/env python 2 3 from random import random 4 from os.path import splitext 5 import EXIF 6 import PIL.Image 7 import itertools 8 import math 9 import sys 10 11 corners = [ 12 (0, 0, 0), (255, 0, 0), (0, 255, 0), (255, 255, 0), 13 (0, 0, 255), (255, 0, 255), (0, 255, 255), (255, 255, 255) 14 ] 15 16 def distance(rgb1, rgb2): 17 r1, g1, b1 = rgb1 18 r2, g2, b2 = rgb2 19 return math.sqrt(pow(r1 - r2, 2) + pow(g1 - g2, 2) + pow(b1 - b2, 2)) 20 21 def nearest(rgb, values): 22 l = [(distance(rgb, value), value) for value in values] 23 l.sort() 24 return l[0][1] 25 26 def scale(rgb): 27 return tuple(map(lambda x: x / 255.0, rgb)) 28 29 def invert(srgb): 30 return tuple(map(lambda x: 1.0 - x, srgb)) 31 32 def combination(rgb): 33 rgb = scale(rgb) 34 rgbi = invert(rgb) 35 pairs = zip(rgbi, rgb) 36 d = [] 37 for corner in corners: 38 rs, gs, bs = scale(corner) 39 d.append((pairs[0][int(rs)] * pairs[1][int(gs)] * pairs[2][int(bs)], corner)) 40 return d 41 42 def combine(d): 43 out = [0, 0, 0] 44 for v, rgb in d: 45 out[0] += v * rgb[0] 46 out[1] += v * rgb[1] 47 out[2] += v * rgb[2] 48 return out 49 50 """ 51 r(R - K) 52 r(Y - G) 53 r(M - B) 54 r(W - C) 55 g(G - K) 56 g(Y - R) 57 g(C - B) 58 g(W - M) 59 b(B - K) 60 b(M - R) 61 b(C - G) 62 b(W - Y) 63 64 b(g(r(W - C) - r(M - B)) - g(r(Y - G) - r(R - K))) 65 b(r(g(W - M) - g(C - B)) - r(g(Y - R) - g(G - K))) 66 67 b(g(rW + riC) + gi(rM + riB)) + bi(g(rY + riG) + gi(rR + riK)) 68 b(r(gW + giM) + ri(gC + giB)) + bi(r(gY + giR) + ri(gG + giK)) 69 70 W(b.g.r) + C(b.g.ri) + M(b.gi.r) + B(b.g.ri) + Y(bi.g.r) + G(bi.g.ri) + R(bi.gi.r) + K(bi.gi.ri) 71 ... 72 """ 73 74 def pattern(rgb): 75 l = combination(rgb) 76 l.sort(reverse=True) 77 return l 78 79 def get_value(rgb): 80 choose = random() 81 threshold = 0 82 for f, c in pattern(rgb): 83 threshold += f 84 if choose < threshold: 85 return c 86 return c 87 88 def sign(x): 89 return x >= 0 and 1 or -1 90 91 def saturate_rgb(rgb, exp): 92 return tuple([saturate_value(x, exp) for x in rgb]) 93 94 def saturate_value(x, exp): 95 return int(127.5 + sign(x - 127.5) * 127.5 * pow(abs(x - 127.5) / 127.5, exp)) 96 97 def replace(value, values): 98 if value not in values: 99 for i, v in list(enumerate(values))[::-1]: 100 if v != value: 101 values[i] = value 102 return 103 104 def test(): 105 size = 512 106 for r in (0, 63, 127, 191, 255): 107 im = PIL.Image.new("RGB", (size, size)) 108 for g in range(0, size): 109 for b in range(0, size): 110 value = get_value((r, (g * 256) / size, (b * 256 / size))) 111 im.putpixel((g, b), value) 112 im.save("rgb%d.png" % r) 113 114 def test_flat(rgb): 115 size = 64 116 im = PIL.Image.new("RGB", (size, size)) 117 for y in range(0, size): 118 for x in range(0, size): 119 im.putpixel((x, y), get_value(rgb)) 120 im.save("rgb%02d%02d%02d.png" % rgb) 121 122 def rotate_and_scale(im, width, height, rotate): 123 if rotate or x and x["Image Orientation"].values == [6L]: 124 im = im.rotate(270) 125 126 w, h = im.size 127 if w > h: 128 height = (width * h) / w 129 else: 130 width = (height * w) / h 131 132 return im.resize((width, height)) 133 134 if __name__ == "__main__": 135 if "--test" in sys.argv: 136 test() 137 sys.exit(0) 138 elif "--test-flat" in sys.argv: 139 test_flat((120, 40, 60)) 140 sys.exit(0) 141 142 width = 320 143 height = 256 144 145 input_filename, output_filename = sys.argv[1:3] 146 basename, ext = splitext(output_filename) 147 148 rotate = "-r" in sys.argv[3:] 149 saturate = sys.argv[3:].count("-s") 150 desaturate = sys.argv[3:].count("-d") 151 152 x = EXIF.process_file(open(input_filename)) 153 im = PIL.Image.open(input_filename).convert("RGB") 154 im = rotate_and_scale(im, width, height, rotate) 155 156 width, height = im.size 157 158 colours = [] 159 160 for y in range(0, height): 161 c = {} 162 for x in range(0, width): 163 rgb = im.getpixel((x, y)) 164 165 # Saturate if requested. 166 167 if saturate or desaturate: 168 rgb = saturate_rgb(rgb, saturate and math.pow(0.5, saturate) or math.pow(2, desaturate)) 169 im.putpixel((x, y), rgb) 170 171 # Sum the colour probabilities. 172 173 for f, value in combination(rgb): 174 if not c.has_key(value): 175 c[value] = f 176 else: 177 c[value] += f 178 179 c = [(n, value) for value, n in c.items()] 180 c.sort(reverse=True) 181 colours.append(c) 182 183 for y, c in enumerate(colours): 184 most = [value for n, value in c[:4]] 185 least = [value for n, value in c[4:]] 186 187 #if least: 188 # if (0, 0, 0) in least[:2]: 189 # replace((0, 0, 0), most) 190 # if (255, 255, 255) in least[:2]: 191 # replace((255, 255, 255), most) 192 193 for x in range(0, width): 194 rgb = im.getpixel((x, y)) 195 196 # Get the requested colours and choose the closest alternative for 197 # less common colours. 198 199 value = get_value(rgb) 200 if value in least: 201 value = nearest(value, most) 202 203 im.putpixel((x, y), value) 204 205 im.save(output_filename) 206 207 # vim: tabstop=4 expandtab shiftwidth=4