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 + 64) / 128 10 11 def point(rgb): 12 return tuple(map(scale, rgb)) 13 14 def index(p): 15 return p[0] * 9 + p[1] * 3 + 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 match_lighter(b, bases): 32 return upward[b] in bases and upward[b] 33 34 def match_darker(b, bases): 35 return downward[b] in bases and downward[b] 36 37 def fallback(bases): 38 for b in by_frequency(bases): 39 if b not in ["_", "W"]: 40 return b 41 return by_frequency(bases)[0] 42 43 tones = [ 44 "___", "_BB", "BBB", # 00x 45 "_GG", "_CC", "BCC", # 01x 46 "GGG", "GCC", "CCC", # 02x 47 "_RR", "_MM", "BMM", # 10x 48 "_YY", "_*W", "BBW", # 11x 49 "GYY", "GGW", "CCW", # 12x 50 "RRR", "RMM", "MMM", # 20x 51 "RYY", "RRW", "MMW", # 21x 52 "YYY", "YYW", "WWW", # 22x 53 ] 54 55 colours = ["_", "R", "G", "Y", "B", "M", "C", "W"] 56 57 upward = { 58 "_" : ["R", "G", "B"], 59 "R" : ["Y", "M"], 60 "G" : ["Y", "C"], 61 "B" : ["C", "M"], 62 "Y" : ["W"], 63 "C" : ["W"], 64 "M" : ["W"], 65 "W" : ["W"], 66 "*" : ["W", "Y", "C", "M"], 67 } 68 69 downward = { 70 "_" : ["_"], 71 "R" : ["_"], 72 "G" : ["_"], 73 "B" : ["_"], 74 "Y" : ["R", "G"], 75 "C" : ["G", "B"], 76 "M" : ["R", "B"], 77 "W" : ["Y", "C", "M"], 78 "*" : ["R", "G", "B", "_"], 79 } 80 81 if __name__ == "__main__": 82 width = 320 83 input_filename, output_filename = sys.argv[1:3] 84 85 im = PIL.Image.open(input_filename) 86 w, h = im.size 87 height = (width * h) / w 88 im = im.resize((width, height)) 89 90 usage = [] 91 base_usage = [] 92 toned = [] 93 94 for row in range(0, height): 95 u = {} 96 usage.append(u) 97 bu = {} 98 base_usage.append(bu) 99 tr = [] 100 toned.append(tr) 101 for column in range(0, width): 102 rgb = im.getpixel((column, row)) 103 p = point(rgb) 104 i = index(p) 105 t = tones[i] 106 add(u, t) 107 if t[0] != "*": 108 add(bu, t[0]) 109 if t[1] != "*": 110 add(bu, t[1]) 111 if t[2] != "*": 112 add(bu, t[2]) 113 tr.append(t) 114 115 chosen = [] 116 117 for row, (u, bu) in enumerate(zip(usage, base_usage)): 118 light = row % 2 119 best = 0 120 best_bases = None 121 best_missing = None 122 best_map = None 123 for bases in combinations(bu, 4): 124 bases = dict([(base, bu[base]) for base in bases]) 125 count = 0 126 missing = [] 127 tone_map = {} 128 for tone, freq in u.items(): 129 base = match(tone[1], bases) or match(light and tone[2] or tone[0], bases) 130 if base: 131 tone_map[tone] = base 132 count += freq 133 else: 134 missing.append(tone) 135 if count > best: 136 best_bases = bases 137 best_missing = missing 138 best_map = tone_map 139 best = count 140 chosen.append((best, best_bases or bases, best_map or tone_map, best_missing or missing)) 141 142 output = [] 143 144 for row, (tr, ch) in enumerate(zip(toned, chosen)): 145 o = [] 146 for column, t in enumerate(tr): 147 best, bases, tone_map, missing = ch 148 base = tone_map.get(t) or fallback(bases) 149 o.append(base) 150 i = colours.index(base) 151 im.putpixel((column, row), colour(i)) 152 153 output.append("".join(o)) 154 155 im.save(output_filename) 156 157 # vim: tabstop=4 expandtab shiftwidth=4