1 #!/usr/bin/env python 2 3 from array import array 4 from itertools import combinations 5 import EXIF 6 import PIL.Image 7 import sys 8 9 def scale(v): 10 return (v + 43) / 85 11 12 def point(rgb): 13 return tuple(map(scale, rgb)) 14 15 def index(p): 16 return p[0] * 16 + p[1] * 4 + p[2] 17 18 def colour(i): 19 return (255 * (i % 2), 255 * ((i / 2) % 2), 255 * ((i / 4) % 2)) 20 21 def add(d, v): 22 d[v] = (d.has_key(v) and d[v] or 0) + 1 23 24 def by_frequency(d): 25 l = [(f, t) for (t, f) in d.items()] 26 l.sort(reverse=True) 27 return [i[1] for i in l] 28 29 def match(b, bases): 30 return b in bases and b 31 32 def fallback(bases): 33 for b in by_frequency(bases): 34 if b not in ["_", "W"]: 35 return b 36 return by_frequency(bases)[0] 37 38 tones = [ 39 "___", "_BB", "_BB", "BBB", # 00x 40 "_GG", "__C", "_BC", "BCC", # 01x 41 "_GG", "GGC", "BCW", "CCC", # 02x 42 "GGG", "GGC", "GCW", "CCW", # 03x 43 "_RR", "_MM", "BMM", "MBB", # 10x 44 "_YY", "_**", "**B", "BBC", # 11x 45 "_GY", "GGC", "*CC", "BCW", # 12x 46 "BGY", "GGC", "CCW", "CCW", # 13x 47 "_RR", "_RM", "*MM", "RMM", # 20x 48 "RYY", "*RY", "RMM", "MMM", # 21x 49 "YYY", "YYW", "*WW", "*WW", # 22x 50 "YYY", "GYY", "GWW", "CWW", # 23x 51 "RRR", "RRM", "BMR", "BMR", # 30x 52 "RRY", "RRY", "RMW", "RMW", # 31x 53 "YYY", "YYW", "YYW", "WWW", # 32x 54 "YYY", "YYW", "YYW", "WWW", # 33x 55 ] 56 57 colours = ["_", "R", "G", "Y", "B", "M", "C", "W"] 58 59 if __name__ == "__main__": 60 width = 320 61 height = 256 62 63 input_filename, output_filename = sys.argv[1:3] 64 rotate = "-r" in sys.argv[3:] 65 66 x = EXIF.process_file(open(input_filename)) 67 im = PIL.Image.open(input_filename) 68 69 if rotate or x and x["Image Orientation"].values == [6L]: 70 im = im.rotate(270) 71 72 w, h = im.size 73 if w > h: 74 height = (width * h) / w 75 else: 76 width = (height * w) / h 77 78 im = im.resize((width, height)) 79 80 usage = [] 81 base_usage = [] 82 toned = [] 83 84 for row in range(0, height): 85 u = {} 86 usage.append(u) 87 bu = {} 88 base_usage.append(bu) 89 tr = [] 90 toned.append(tr) 91 for column in range(0, width): 92 rgb = im.getpixel((column, row)) 93 p = point(rgb) 94 i = index(p) 95 t = tones[i] 96 add(u, t) 97 if t[0] != "*": 98 add(bu, t[0]) 99 if t[1] != "*": 100 add(bu, t[1]) 101 if t[2] != "*": 102 add(bu, t[2]) 103 tr.append(t) 104 105 chosen = [] 106 107 for row, (u, bu) in enumerate(zip(usage, base_usage)): 108 light = row % 2 109 best = 0 110 best_bases = None 111 best_missing = None 112 best_map = None 113 114 for bases in combinations(bu, min(len(bu), 4)): 115 bases = dict([(base, bu[base]) for base in bases]) 116 count = 0 117 missing = [] 118 tone_map = {} 119 for tone, freq in u.items(): 120 base = match(tone[1], bases) 121 if base: 122 tone_map[tone] = base 123 count += freq 124 else: 125 base = light and match(tone[2], bases) or match(tone[0], bases) or match(tone[2], bases) 126 if base: 127 tone_map[tone] = base 128 count += freq / 2 129 else: 130 missing.append(tone) 131 if count > best: 132 best_bases = bases 133 best_missing = missing 134 best_map = tone_map 135 best = count 136 137 chosen.append((best, best_bases or bases, best_map or tone_map, best_missing or missing)) 138 139 output = [] 140 141 for row, (tr, ch) in enumerate(zip(toned, chosen)): 142 o = [] 143 for column, t in enumerate(tr): 144 best, bases, tone_map, missing = ch 145 base = tone_map.get(t) or fallback(bases) 146 o.append(base) 147 i = colours.index(base) 148 im.putpixel((column, row), colour(i)) 149 150 output.append("".join(o)) 151 152 im.save(output_filename) 153 154 # vim: tabstop=4 expandtab shiftwidth=4