PaletteOptimiser

optimiser.py

126:e51a3f1dd413
2015-10-12 Paul Boddie Made the verify-only mode more usable, improving the documentation. simpleimage-shedskin
     1 #!/usr/bin/env python     2      3 """     4 Convert and optimise images for display in an Acorn Electron MODE 1 variant     5 with four colours per line but eight colours available for selection on each     6 line.     7      8 Copyright (C) 2015 Paul Boddie <paul@boddie.org.uk>     9     10 This program is free software; you can redistribute it and/or modify it under    11 the terms of the GNU General Public License as published by the Free Software    12 Foundation; either version 3 of the License, or (at your option) any later    13 version.    14     15 This program is distributed in the hope that it will be useful, but WITHOUT ANY    16 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A    17 PARTICULAR PURPOSE.  See the GNU General Public License for more details.    18     19 You should have received a copy of the GNU General Public License along    20 with this program.  If not, see <http://www.gnu.org/licenses/>.    21 """    22     23 from optimiserlib import *    24 from os.path import split, splitext    25 import EXIF    26 import PIL.Image    27 import sys    28     29 def test():    30     31     "Generate slices of the colour cube."    32     33     size = 512    34     for r in (0, 63, 127, 191, 255):    35         im = PIL.Image.new("RGB", (size, size))    36         for g in range(0, size):    37             for b in range(0, size):    38                 value = get_value((r, (g * 256) / size, (b * 256 / size)))    39                 im.putpixel((g, b), value)    40         im.save("rgb%d.png" % r)    41     42 def test_flat(rgb):    43     44     "Generate a flat image for the colour 'rgb'."    45     46     size = 64    47     im = PIL.Image.new("RGB", (size, size))    48     for y in range(0, size):    49         for x in range(0, size):    50             im.putpixel((x, y), get_value(rgb))    51     im.save("rgb%02d%02d%02d.png" % rgb)    52     53 def rotate_and_scale(exif, im, width, height, rotate):    54     55     """    56     Using the given 'exif' information, rotate and scale image 'im' given the    57     indicated 'width' and 'height' constraints and any explicit 'rotate'    58     indication. The returned image will be within the given 'width' and    59     'height', filling either or both, and preserve its original aspect ratio.    60     """    61     62     if rotate or exif and exif["Image Orientation"].values == [6L]:    63         im = im.rotate(270)    64     65     w, h = im.size    66     if w > h:    67         height = (width * h) / w    68     else:    69         width = (height * w) / h    70     71     return im.resize((width, height))    72     73 def get_parameter(options, flag, conversion, default, missing):    74     75     """    76     From 'options', return any parameter following the given 'flag', applying    77     the 'conversion' which has the given 'default' if no valid parameter is    78     found, or returning the given 'missing' value if the flag does not appear at    79     all.    80     """    81     82     try:    83         i = options.index(flag)    84         try:    85             return conversion(options[i+1])    86         except (IndexError, ValueError):    87             return default    88     except ValueError:    89         return missing    90     91 # Main program.    92     93 if __name__ == "__main__":    94     95     # Test options.    96     97     if "--test" in sys.argv:    98         test()    99         sys.exit(0)   100     elif "--test-flat" in sys.argv:   101         test_flat((120, 40, 60))   102         sys.exit(0)   103     elif "--help" in sys.argv or len(sys.argv) < 3:   104         basename = split(sys.argv[0])[1]   105         print >>sys.stderr, """\   106 Usage:   107    108 %s <input filename> <output filename> [ <options> ]   109    110 %s -v <filename> [ -C <number of colours> ]   111    112 Options are...   113    114 -W - Indicate the output width (default is 320)   115 -C - Number of colours per scanline (default is 4)   116 -A - Produce an output image with the same aspect ratio as the input   117      (useful for previewing)   118    119 -s - Saturate the input image (optional float, 1.0 if unspecified)   120 -d - Desaturate the input image (optional float, 1.0 if unspecified)   121 -D - Darken the input image (optional float, 1.0 if unspecified)   122 -B - Brighten the input image (optional float, 1.0 if unspecified)   123    124 -l - Use colours producing the least error   125      (slower but useful for fewer than 4 colours)   126    127 -r - Rotate the input image clockwise explicitly   128      (EXIF information is used otherwise)   129 -p - Generate a separate preview image   130 -h - Make the preview image with half horizontal resolution (MODE 2)   131 -v - Verify the output image (loaded if -n is given)   132 -n - Generate no output image   133    134 Specifying -v instead of input filename permits the verification of   135 previously-generated images. Doing so causes all other options except for -C   136 to be ignored.   137 """ % (basename, basename)   138         sys.exit(1)   139    140     base_width = width = 320   141     base_height = height = 256   142    143     input_filename, output_filename = sys.argv[1:3]   144     basename, ext = splitext(output_filename)   145     preview_filename = "".join([basename + "_preview", ext])   146    147     verify_only = input_filename == "-v"   148     options = sys.argv[3:]   149    150     # Basic image properties.   151    152     width = get_parameter(options, "-W", int, base_width, base_width)   153     number_of_colours = get_parameter(options, "-C", int, 4, 4)   154     preserve_aspect_ratio = "-A" in options   155    156     scale_factor = float(width) / base_width   157     height = int(base_height * scale_factor)   158    159     # Preprocessing options that employ parameters.   160    161     saturate = get_parameter(options, "-s", float, 1.0, 0.0)   162     desaturate = get_parameter(options, "-d", float, 1.0, 0.0)   163     darken = get_parameter(options, "-D", float, 1.0, 0.0)   164     brighten = get_parameter(options, "-B", float, 1.0, 0.0)   165    166     # General output options.   167    168     no_normal_output = "-n" in options or verify_only   169     verify = "-v" in options or verify_only   170    171     rotate = "-r" in options and not verify_only   172     preview = "-p" in options and not verify_only   173    174     half_resolution_preview = "-h" in options   175     least_error = "-l" in options   176    177     make_image = not no_normal_output   178    179     # Load the input image if requested.   180    181     if make_image or preview:   182         exif = EXIF.process_file(open(input_filename))   183         im = PIL.Image.open(input_filename).convert("RGB")   184         im = rotate_and_scale(exif, im, width, height, rotate)   185         image_width, image_height = im.size   186    187         # Scale images to the appropriate height.   188    189         if scale_factor != 1:   190             im = im.resize((image_width, int(image_height / scale_factor)))   191    192         sim = SimpleImage(list(im.getdata()), im.size)   193         process_image(sim, saturate, desaturate, darken, brighten)   194         im.putdata(sim.getdata())   195    196     # Generate a preview if requested.   197    198     if preview:   199         imp = im.copy()   200         if half_resolution_preview:   201             imp = imp.resize((image_width / 2, image_height))   202         sim = SimpleImage(list(imp.getdata()), imp.size)   203         convert_image(sim, 8)   204         imp.putdata(sim.getdata())   205         if half_resolution_preview:   206             imp = imp.resize((image_width, image_height))   207    208         # Scale images to a height determined by the aspect ratio.   209    210         if preserve_aspect_ratio and scale_factor != 1:   211             imp = imp.resize((image_width, image_height))   212    213         imp.save(preview_filename)   214    215     # Generate an output image if requested.   216    217     if make_image:   218         sim = SimpleImage(list(im.getdata()), im.size)   219         convert_image(sim, number_of_colours, least_error)   220         im.putdata(sim.getdata())   221    222         # Scale images to a height determined by the aspect ratio.   223    224         if preserve_aspect_ratio and scale_factor != 1:   225             im = im.resize((image_width, image_height))   226    227         im.save(output_filename)   228    229     # Verify the output image (which may be loaded) if requested.   230    231     if verify:   232         if no_normal_output:   233             im = PIL.Image.open(output_filename).convert("RGB")   234    235         im = SimpleImage(list(im.getdata()), im.size)   236         result = count_colours(im, number_of_colours)   237         if result is not None:   238             y, colours = result   239             print "Image %s: row %d has the following colours: %s" % (output_filename, y, "; ".join([repr(c) for c in colours]))   240    241 # vim: tabstop=4 expandtab shiftwidth=4