PaletteOptimiser

Change of optimiser.py

1:3e68ec96af01
optimiser.py
     1.1 --- a/optimiser.py	Tue Sep 08 14:42:05 2015 +0200
     1.2 +++ b/optimiser.py	Tue Sep 08 19:52:24 2015 +0200
     1.3 @@ -1,6 +1,8 @@
     1.4  #!/usr/bin/env python
     1.5  
     1.6  from array import array
     1.7 +from itertools import combinations
     1.8 +from random import randint
     1.9  import PIL.Image
    1.10  import sys
    1.11  
    1.12 @@ -16,37 +18,147 @@
    1.13  def colour(i):
    1.14      return (255 * (i % 2), 255 * ((i / 2) % 2), 255 * ((i / 4) % 2))
    1.15  
    1.16 +def add(d, v):
    1.17 +    d[v] = (d.has_key(v) and d[v] or 0) + 1
    1.18 +
    1.19 +def by_frequency(d):
    1.20 +    l = [(f, t) for (t, f) in d.items()]
    1.21 +    l.sort(reverse=True)
    1.22 +    return [i[1] for i in l]
    1.23 +
    1.24 +def match(b, bases):
    1.25 +    return b in bases and b
    1.26 +
    1.27 +def match_lighter(b, bases):
    1.28 +    return upward[b] in bases and upward[b]
    1.29 +
    1.30 +def match_darker(b, bases):
    1.31 +    return downward[b] in bases and downward[b]
    1.32 +
    1.33 +def match_tone(t, bases):
    1.34 +    return match(t[1], bases)
    1.35 +
    1.36 +def match_variant(t, bases, fn):
    1.37 +    return fn(t[0], bases)
    1.38 +
    1.39 +def neutral(bases, light):
    1.40 +    l = ["W", "C", "Y", "M", "G", "R", "B", "_"]
    1.41 +    if not light:
    1.42 +        l.reverse()
    1.43 +    for b in l:
    1.44 +        if b in bases:
    1.45 +            return b
    1.46 +    return bases[randint(0, 3)]
    1.47 +
    1.48  tones = [
    1.49 -    "__", "B_", "BB", # 00x
    1.50 -    "G_", "C_", "CB", # 01x
    1.51 -    "GG", "CG", "CC", # 02x
    1.52 -    "R_", "M_", "MB", # 10x
    1.53 -    "Y_", "W_", "WB", # 11x
    1.54 -    "YG", "WG", "WC", # 12x
    1.55 -    "RR", "MR", "MM", # 20x
    1.56 -    "YR", "WR", "WM", # 21x
    1.57 +    "__", "_B", "BB", # 00x
    1.58 +    "_G", "_C", "BC", # 01x
    1.59 +    "GG", "GC", "CC", # 02x
    1.60 +    "_R", "_M", "BM", # 10x
    1.61 +    "_Y", "**", "WB", # 11x
    1.62 +    "GY", "WG", "WC", # 12x
    1.63 +    "RR", "RM", "MM", # 20x
    1.64 +    "RY", "WR", "WM", # 21x
    1.65      "YY", "WY", "WW", # 22x
    1.66      ]
    1.67  
    1.68  colours = ["_", "R", "G", "Y", "B", "M", "C", "W"]
    1.69  
    1.70 +upward = {
    1.71 +    "_" : ["R", "G", "B"],
    1.72 +    "R" : ["Y", "M"],
    1.73 +    "G" : ["Y", "C"],
    1.74 +    "B" : ["C", "M"],
    1.75 +    "Y" : ["W"],
    1.76 +    "C" : ["W"],
    1.77 +    "M" : ["W"],
    1.78 +    "W" : ["W"],
    1.79 +    "*" : ["W", "Y", "C", "M"],
    1.80 +    }
    1.81 +
    1.82 +downward = {
    1.83 +    "_" : ["_"],
    1.84 +    "R" : ["_"],
    1.85 +    "G" : ["_"],
    1.86 +    "B" : ["_"],
    1.87 +    "Y" : ["R", "G"],
    1.88 +    "C" : ["G", "B"],
    1.89 +    "M" : ["R", "B"],
    1.90 +    "W" : ["Y", "C", "M"],
    1.91 +    "*" : ["R", "G", "B", "_"],
    1.92 +    }
    1.93 +
    1.94  if __name__ == "__main__":
    1.95 -
    1.96 +    width = 320
    1.97 +    height = 192
    1.98      input_filename, output_filename = sys.argv[1:3]
    1.99  
   1.100      im = PIL.Image.open(input_filename)
   1.101 -    im = im.resize((320, 256))
   1.102 +    im = im.resize((width, height))
   1.103 +
   1.104 +    usage = []
   1.105 +    base_usage = []
   1.106 +    toned = []
   1.107  
   1.108 -    for row in range(0, 256):
   1.109 -        for column in range(0, 320):
   1.110 +    for row in range(0, height):
   1.111 +        u = {}
   1.112 +        usage.append(u)
   1.113 +        bu = {}
   1.114 +        base_usage.append(bu)
   1.115 +        tr = []
   1.116 +        toned.append(tr)
   1.117 +        for column in range(0, width):
   1.118              rgb = im.getpixel((column, row))
   1.119              p = point(rgb)
   1.120              i = index(p)
   1.121              t = tones[i]
   1.122 -            c = t[row % 2]
   1.123 -            i = colours.index(c)
   1.124 +            add(u, t)
   1.125 +            if t[0] != "*":
   1.126 +                add(bu, t[0])
   1.127 +            if t[1] != "*":
   1.128 +                add(bu, t[1])
   1.129 +            tr.append(t)
   1.130 +
   1.131 +    chosen = []
   1.132 +
   1.133 +    light = True
   1.134 +    for u, bu in zip(usage, base_usage):
   1.135 +        best = 0
   1.136 +        best_bases = None
   1.137 +        best_missing = None
   1.138 +        best_map = None
   1.139 +        for bases in combinations(bu, 4):
   1.140 +            count = 0
   1.141 +            missing = []
   1.142 +            tone_map = {}
   1.143 +            for tone, freq in u.items():
   1.144 +                base = match(light and tone[1] or tone[0], bases)
   1.145 +                if base:
   1.146 +                    tone_map[tone] = base
   1.147 +                    count += freq
   1.148 +                else:
   1.149 +                    missing.append(tone)
   1.150 +            if count > best:
   1.151 +                best_bases = bases
   1.152 +                best_missing = missing
   1.153 +                best_map = tone_map
   1.154 +                best = count
   1.155 +        chosen.append((best, best_bases or bases, best_map or tone_map, best_missing or missing))
   1.156 +        light = not light
   1.157 +
   1.158 +    output = []
   1.159 +
   1.160 +    for row, (tr, ch) in enumerate(zip(toned, chosen)):
   1.161 +        o = []
   1.162 +        for column, t in enumerate(tr):
   1.163 +            best, bases, tone_map, missing = ch
   1.164 +            base = tone_map.get(t) or neutral(bases, light)
   1.165 +            o.append(base)
   1.166 +            i = colours.index(base or "_")
   1.167              im.putpixel((column, row), colour(i))
   1.168  
   1.169 +        output.append("".join(o))
   1.170 +
   1.171      im.save(output_filename)
   1.172  
   1.173  # vim: tabstop=4 expandtab shiftwidth=4