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