PaletteOptimiser

optimiser.py

128:93aaf33edc73
2015-10-12 Paul Boddie Made more thorough fixes to the scaling of images. simpleimage
     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     y = 0    49     while y < height:    50         x = 0    51         while x < width:    52             im.putpixel((x, y), get_value(rgb))    53             x += 1    54         y += 1    55     im.save("rgb%02d%02d%02d.png" % rgb)    56     57 def rotate_and_scale(exif, im, width, height, rotate):    58     59     """    60     Using the given 'exif' information, rotate and scale image 'im' given the    61     indicated 'width' and 'height' constraints and any explicit 'rotate'    62     indication. The returned image will be within the given 'width' and    63     'height', filling either or both, and preserve its original aspect ratio.    64     """    65     66     if rotate or exif and exif["Image Orientation"].values == [6L]:    67         im = im.rotate(270)    68     69     w, h = im.size    70     71     width_scale_factor = float(width) / w    72     height_scale_factor = float(height) / h    73     scale_factor = min(width_scale_factor, height_scale_factor)    74     75     if scale_factor < 1:    76         width = int(scale_factor * w)    77         height = int(scale_factor * h)    78         return im.resize((width, height))    79     else:    80         return im    81     82 def get_parameter(options, flag, conversion, default, missing):    83     84     """    85     From 'options', return any parameter following the given 'flag', applying    86     the 'conversion' which has the given 'default' if no valid parameter is    87     found, or returning the given 'missing' value if the flag does not appear at    88     all.    89     """    90     91     try:    92         i = options.index(flag)    93         try:    94             return conversion(options[i+1])    95         except (IndexError, ValueError):    96             return default    97     except ValueError:    98         return missing    99    100 # Main program.   101    102 if __name__ == "__main__":   103    104     # Test options.   105    106     if "--test" in sys.argv:   107         test()   108         sys.exit(0)   109     elif "--test-flat" in sys.argv:   110         test_flat((120, 40, 60))   111         sys.exit(0)   112     elif "--help" in sys.argv or len(sys.argv) < 3:   113         basename = split(sys.argv[0])[1]   114         print >>sys.stderr, """\   115 Usage:   116    117 %s <input filename> <output filename> [ <options> ]   118    119 %s -v <filename> [ -C <number of colours> ]   120    121 Options are...   122    123 -W - Indicate the output width (default is 320)   124 -C - Number of colours per scanline (default is 4)   125 -A - Produce an output image with the same aspect ratio as the input   126      (useful for previewing)   127    128 -s - Saturate the input image (optional float, 1.0 if unspecified)   129 -d - Desaturate the input image (optional float, 1.0 if unspecified)   130 -D - Darken the input image (optional float, 1.0 if unspecified)   131 -B - Brighten the input image (optional float, 1.0 if unspecified)   132    133 -l - Use colours producing the least error   134      (slower but useful for fewer than 4 colours)   135    136 -r - Rotate the input image clockwise explicitly   137      (EXIF information is used otherwise)   138 -p - Generate a separate preview image   139 -h - Make the preview image with half horizontal resolution (MODE 2)   140 -v - Verify the output image (loaded if -n is given)   141 -n - Generate no output image   142    143 Specifying -v instead of input filename permits the verification of   144 previously-generated images. Doing so causes all other options except for -C   145 to be ignored.   146 """ % (basename, basename)   147         sys.exit(1)   148    149     base_width = width = 320   150     base_height = height = 256   151    152     input_filename, output_filename = sys.argv[1:3]   153     basename, ext = splitext(output_filename)   154     preview_filename = "".join([basename + "_preview", ext])   155    156     verify_only = input_filename == "-v"   157     options = sys.argv[3:]   158    159     # Basic image properties.   160    161     width = get_parameter(options, "-W", int, base_width, base_width)   162     number_of_colours = get_parameter(options, "-C", int, 4, 4)   163     preserve_aspect_ratio = "-A" in options   164    165     # Determine whether the height will need adjusting before conversion.   166    167     scale_factor = float(width) / base_width   168     height = int(base_height * scale_factor)   169    170     # Preprocessing options that employ parameters.   171    172     saturate = get_parameter(options, "-s", float, 1.0, 0.0)   173     desaturate = get_parameter(options, "-d", float, 1.0, 0.0)   174     darken = get_parameter(options, "-D", float, 1.0, 0.0)   175     brighten = get_parameter(options, "-B", float, 1.0, 0.0)   176    177     # General output options.   178    179     no_normal_output = "-n" in options or verify_only   180     verify = "-v" in options or verify_only   181    182     rotate = "-r" in options and not verify_only   183     preview = "-p" in options and not verify_only   184    185     half_resolution_preview = "-h" in options   186     least_error = "-l" in options   187    188     make_image = not no_normal_output   189    190     # Load the input image if requested.   191    192     if make_image or preview:   193         exif = EXIF.process_file(open(input_filename))   194         im = PIL.Image.open(input_filename).convert("RGB")   195         im = rotate_and_scale(exif, im, width, height, rotate)   196         image_width, image_height = im.size   197    198         # Scale images to the appropriate height.   199    200         if scale_factor != 1:   201             im = im.resize((image_width, int(image_height / scale_factor)))   202             image_width, image_height = im.size   203    204         process_image(im, saturate, desaturate, darken, brighten)   205    206     # Generate a preview if requested.   207    208     if preview:   209         imp = im.copy()   210         if half_resolution_preview:   211             imp = imp.resize((image_width / 2, image_height))   212         convert_image(imp, 8)   213         if half_resolution_preview:   214             imp = imp.resize((image_width, image_height))   215    216         # Scale images to a height determined by the aspect ratio.   217    218         if preserve_aspect_ratio and scale_factor != 1:   219             imp = imp.resize((image_width, int(image_height * scale_factor)))   220    221         imp.save(preview_filename)   222    223     # Generate an output image if requested.   224    225     if make_image:   226         convert_image(im, number_of_colours, least_error)   227    228         # Scale images to a height determined by the aspect ratio.   229    230         if preserve_aspect_ratio and scale_factor != 1:   231             im = im.resize((image_width, int(image_height * scale_factor)))   232    233         im.save(output_filename)   234    235     # Verify the output image (which may be loaded) if requested.   236    237     if verify:   238         if no_normal_output:   239             im = PIL.Image.open(output_filename).convert("RGB")   240    241         result = count_colours(im, number_of_colours)   242         if result is not None:   243             y, colours = result   244             print "Image %s: row %d has the following colours: %s" % (output_filename, y, "; ".join([repr(c) for c in colours]))   245    246 # vim: tabstop=4 expandtab shiftwidth=4