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", "BCC", "CCC", # 02x 42 "GGG", "GCC", "CCC", "CCC", # 03x 43 "_RR", "_MM", "BMM", "MBB", # 10x 44 "_YY", "_**", "**B", "BBW", # 11x 45 "_GY", "GGC", "*CC", "BCW", # 12x 46 "GGY", "GGG", "GCC", "CCW", # 13x 47 "_RR", "_RM", "*MM", "MMM", # 20x 48 "RYY", "*RY", "M*W", "MMW", # 21x 49 "YYY", "YYW", "*WW", "*WW", # 22x 50 "YYY", "YYW", "GWW", "CWW", # 23x 51 "RRR", "RRM", "RMM", "MMM", # 30x 52 "RRY", "RRY", "RMW", "MMW", # 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 input_filename, output_filename = sys.argv[1:3] 62 rotate = "-r" in sys.argv[3:] 63 64 x = EXIF.process_file(open(input_filename)) 65 im = PIL.Image.open(input_filename) 66 if rotate or x["Image Orientation"].values == [6L]: 67 im = im.rotate(270) 68 w, h = im.size 69 height = (width * h) / w 70 im = im.resize((width, height)) 71 72 usage = [] 73 base_usage = [] 74 toned = [] 75 76 for row in range(0, height): 77 u = {} 78 usage.append(u) 79 bu = {} 80 base_usage.append(bu) 81 tr = [] 82 toned.append(tr) 83 for column in range(0, width): 84 rgb = im.getpixel((column, row)) 85 p = point(rgb) 86 i = index(p) 87 t = tones[i] 88 add(u, t) 89 if t[0] != "*": 90 add(bu, t[0]) 91 if t[1] != "*": 92 add(bu, t[1]) 93 if t[2] != "*": 94 add(bu, t[2]) 95 tr.append(t) 96 97 chosen = [] 98 99 for row, (u, bu) in enumerate(zip(usage, base_usage)): 100 light = row % 2 101 best = 0 102 best_bases = None 103 best_missing = None 104 best_map = None 105 106 for bases in combinations(bu, min(len(bu), 4)): 107 bases = dict([(base, bu[base]) for base in bases]) 108 count = 0 109 missing = [] 110 tone_map = {} 111 for tone, freq in u.items(): 112 base = match(tone[1], bases) 113 if base: 114 tone_map[tone] = base 115 count += freq 116 else: 117 base = light and match(tone[2], bases) or match(tone[0], bases) or match(tone[2], bases) 118 if base: 119 tone_map[tone] = base 120 count += freq / 2 121 else: 122 missing.append(tone) 123 if count > best: 124 best_bases = bases 125 best_missing = missing 126 best_map = tone_map 127 best = count 128 129 chosen.append((best, best_bases or bases, best_map or tone_map, best_missing or missing)) 130 131 output = [] 132 133 for row, (tr, ch) in enumerate(zip(toned, chosen)): 134 o = [] 135 for column, t in enumerate(tr): 136 best, bases, tone_map, missing = ch 137 base = tone_map.get(t) or fallback(bases) 138 o.append(base) 139 i = colours.index(base) 140 im.putpixel((column, row), colour(i)) 141 142 output.append("".join(o)) 143 144 im.save(output_filename) 145 146 # vim: tabstop=4 expandtab shiftwidth=4