PaletteOptimiser

optimiser.py

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