PaletteOptimiser

Changeset

29:21c5d47536ea
2015-10-02 Paul Boddie raw files shortlog changelog graph Added experiments with PIL's dithering/quantizing, summing colour probabilities, finding the "best" combinations where more than four colours are required. Removed the preview image saving.
dither.py (file) optimiser.py (file)
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/dither.py	Fri Oct 02 19:22:17 2015 +0200
     1.3 @@ -0,0 +1,24 @@
     1.4 +#!/usr/bin/env python
     1.5 +
     1.6 +import PIL.Image
     1.7 +import sys
     1.8 +
     1.9 +def get_palette():
    1.10 +    l = []
    1.11 +    for i in range(0, 8):
    1.12 +        r = ((i / 4) % 2) * 255; g = ((i / 2) % 2) * 255; b = (i % 2) * 255
    1.13 +        for j in range(0, 32):
    1.14 +            l.extend((r, g, b))
    1.15 +
    1.16 +    imp = PIL.Image.new("P", (1, 1))
    1.17 +    imp.putpalette(l)
    1.18 +    return imp
    1.19 +
    1.20 +def dither(im, imp=None):
    1.21 +    return im.quantize(palette=imp or get_palette())
    1.22 +
    1.23 +im = PIL.Image.open(sys.argv[1])
    1.24 +im2 = dither(im)
    1.25 +im2.save(sys.argv[2])
    1.26 +
    1.27 +# vim: tabstop=4 expandtab shiftwidth=4
     2.1 --- a/optimiser.py	Fri Oct 02 13:37:29 2015 +0200
     2.2 +++ b/optimiser.py	Fri Oct 02 19:22:17 2015 +0200
     2.3 @@ -1,9 +1,11 @@
     2.4  #!/usr/bin/env python
     2.5  
     2.6 +from dither import dither
     2.7  from random import random
     2.8 -from os.path import extsep, splitext
     2.9 +from os.path import splitext
    2.10  import EXIF
    2.11  import PIL.Image
    2.12 +import itertools
    2.13  import math
    2.14  import sys
    2.15  
    2.16 @@ -17,10 +19,10 @@
    2.17      r2, g2, b2 = rgb2
    2.18      return math.sqrt(pow(r1 - r2, 2) + pow(g1 - g2, 2) + pow(b1 - b2, 2))
    2.19  
    2.20 -def distribution(rgb, points):
    2.21 +def distribution(rgb, values=None):
    2.22      l = []
    2.23      total = 0
    2.24 -    for c in points:
    2.25 +    for c in values or corners:
    2.26          d = distance(rgb, c)
    2.27          if d == 0:
    2.28              return [(1, c)]
    2.29 @@ -51,6 +53,22 @@
    2.30  def saturate_value(x, exp):
    2.31      return int(127.5 + sign(x - 127.5) * 127.5 * pow(abs(x - 127.5) / 127.5, exp))
    2.32  
    2.33 +def get_best(im, width, y, colours):
    2.34 +    best = 0
    2.35 +    best_values = None
    2.36 +
    2.37 +    for values in itertools.combinations([value for n, value in colours], 4):
    2.38 +        current = 0
    2.39 +        for x in range(0, width):
    2.40 +            rgb = im.getpixel((x, y))
    2.41 +            for f, value in distribution(rgb, values):
    2.42 +                current += f
    2.43 +        if current >= best:
    2.44 +            best_values = values
    2.45 +            best = current
    2.46 +
    2.47 +    return best_values
    2.48 +
    2.49  def test():
    2.50      size = 512
    2.51      for r in (0, 63, 127, 191, 255):
    2.52 @@ -94,21 +112,23 @@
    2.53  
    2.54      input_filename, output_filename = sys.argv[1:3]
    2.55      basename, ext = splitext(output_filename)
    2.56 -    preview_filename = extsep.join([basename + "_preview", ext])
    2.57  
    2.58 -    preview = "-p" in sys.argv[3:]
    2.59      rotate = "-r" in sys.argv[3:]
    2.60      saturate = sys.argv[3:].count("-s")
    2.61      desaturate = sys.argv[3:].count("-d")
    2.62 +    best = "-b" in sys.argv[3:]
    2.63 +    dithering = "-d" in sys.argv[3:]
    2.64  
    2.65      x = EXIF.process_file(open(input_filename))
    2.66      im = PIL.Image.open(input_filename).convert("RGB")
    2.67      im = rotate_and_scale(im, width, height, rotate)
    2.68  
    2.69 -    if preview:
    2.70 -        im_preview = im.copy()
    2.71 +    width, height = im.size
    2.72  
    2.73 -    width, height = im.size
    2.74 +    im_original = im
    2.75 +
    2.76 +    if dithering:
    2.77 +        im = dither(im).convert("RGB")
    2.78  
    2.79      colours = []
    2.80  
    2.81 @@ -123,28 +143,25 @@
    2.82                  rgb = saturate_rgb(rgb, saturate and math.pow(0.5, saturate) or math.pow(2, desaturate))
    2.83                  im.putpixel((x, y), rgb)
    2.84  
    2.85 -            # Count the number of requested colours.
    2.86 +            # Sum the colour probabilities.
    2.87  
    2.88 -            value = get_value(rgb)
    2.89 -            if not c.has_key(value):
    2.90 -                c[value] = 1
    2.91 -            else:
    2.92 -                c[value] += 1
    2.93 -
    2.94 -            if preview:
    2.95 -                im_preview.putpixel((x, y), value)
    2.96 +            for f, value in distribution(rgb):
    2.97 +                if not c.has_key(value):
    2.98 +                    c[value] = f
    2.99 +                else:
   2.100 +                    c[value] += f
   2.101  
   2.102          c = [(n, value) for value, n in c.items()]
   2.103          c.sort(reverse=True)
   2.104          colours.append(c)
   2.105  
   2.106 -    if preview:
   2.107 -        im_preview.save(preview_filename)
   2.108 -
   2.109      for y, c in enumerate(colours):
   2.110          most = [value for n, value in c[:4]]
   2.111          least = [value for n, value in c[4:]]
   2.112  
   2.113 +        if least and best:
   2.114 +            most = get_best(im_original, width, y, c)
   2.115 +
   2.116          for x in range(0, width):
   2.117              rgb = im.getpixel((x, y))
   2.118  
   2.119 @@ -153,7 +170,9 @@
   2.120  
   2.121              value = get_value(rgb)
   2.122              if value in least:
   2.123 +                rgb = im_original.getpixel((x, y))
   2.124                  value = get_value(rgb, most)
   2.125 +
   2.126              im.putpixel((x, y), value)
   2.127  
   2.128      im.save(output_filename)