1 #!/usr/bin/env python 2 3 from array import array 4 from itertools import combinations 5 from random import randint 6 import PIL.Image 7 import sys 8 9 def scale(v): 10 return (v + 64) / 128 11 12 def point(rgb): 13 return tuple(map(scale, rgb)) 14 15 def index(p): 16 return p[0] * 9 + p[1] * 3 + 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 match_lighter(b, bases): 33 return upward[b] in bases and upward[b] 34 35 def match_darker(b, bases): 36 return downward[b] in bases and downward[b] 37 38 def match_tone(t, bases): 39 return match(t[1], bases) 40 41 def match_variant(t, bases, fn): 42 return fn(t[0], bases) 43 44 def neutral(bases, light): 45 l = ["W", "C", "Y", "M", "G", "R", "B", "_"] 46 if not light: 47 l.reverse() 48 for b in l: 49 if b in bases: 50 return b 51 return bases[randint(0, 3)] 52 53 tones = [ 54 "__", "_B", "BB", # 00x 55 "_G", "_C", "BC", # 01x 56 "GG", "GC", "CC", # 02x 57 "_R", "_M", "BM", # 10x 58 "_Y", "**", "WB", # 11x 59 "GY", "WG", "WC", # 12x 60 "RR", "RM", "MM", # 20x 61 "RY", "WR", "WM", # 21x 62 "YY", "WY", "WW", # 22x 63 ] 64 65 colours = ["_", "R", "G", "Y", "B", "M", "C", "W"] 66 67 upward = { 68 "_" : ["R", "G", "B"], 69 "R" : ["Y", "M"], 70 "G" : ["Y", "C"], 71 "B" : ["C", "M"], 72 "Y" : ["W"], 73 "C" : ["W"], 74 "M" : ["W"], 75 "W" : ["W"], 76 "*" : ["W", "Y", "C", "M"], 77 } 78 79 downward = { 80 "_" : ["_"], 81 "R" : ["_"], 82 "G" : ["_"], 83 "B" : ["_"], 84 "Y" : ["R", "G"], 85 "C" : ["G", "B"], 86 "M" : ["R", "B"], 87 "W" : ["Y", "C", "M"], 88 "*" : ["R", "G", "B", "_"], 89 } 90 91 if __name__ == "__main__": 92 width = 320 93 height = 192 94 input_filename, output_filename = sys.argv[1:3] 95 96 im = PIL.Image.open(input_filename) 97 im = im.resize((width, height)) 98 99 usage = [] 100 base_usage = [] 101 toned = [] 102 103 for row in range(0, height): 104 u = {} 105 usage.append(u) 106 bu = {} 107 base_usage.append(bu) 108 tr = [] 109 toned.append(tr) 110 for column in range(0, width): 111 rgb = im.getpixel((column, row)) 112 p = point(rgb) 113 i = index(p) 114 t = tones[i] 115 add(u, t) 116 if t[0] != "*": 117 add(bu, t[0]) 118 if t[1] != "*": 119 add(bu, t[1]) 120 tr.append(t) 121 122 chosen = [] 123 124 light = True 125 for u, bu in zip(usage, base_usage): 126 best = 0 127 best_bases = None 128 best_missing = None 129 best_map = None 130 for bases in combinations(bu, 4): 131 count = 0 132 missing = [] 133 tone_map = {} 134 for tone, freq in u.items(): 135 base = match(light and tone[1] or tone[0], bases) 136 if base: 137 tone_map[tone] = base 138 count += freq 139 else: 140 missing.append(tone) 141 if count > best: 142 best_bases = bases 143 best_missing = missing 144 best_map = tone_map 145 best = count 146 chosen.append((best, best_bases or bases, best_map or tone_map, best_missing or missing)) 147 light = not light 148 149 output = [] 150 151 for row, (tr, ch) in enumerate(zip(toned, chosen)): 152 o = [] 153 for column, t in enumerate(tr): 154 best, bases, tone_map, missing = ch 155 base = tone_map.get(t) or neutral(bases, light) 156 o.append(base) 157 i = colours.index(base or "_") 158 im.putpixel((column, row), colour(i)) 159 160 output.append("".join(o)) 161 162 im.save(output_filename) 163 164 # vim: tabstop=4 expandtab shiftwidth=4