PaletteOptimiser

optimiser.py

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