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