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 # Image operations. 30 31 def test(): 32 33 "Generate slices of the colour cube." 34 35 size = 512 36 for r in (0, 63, 127, 191, 255): 37 im = PIL.Image.new("RGB", (size, size)) 38 for g in range(0, size): 39 for b in range(0, size): 40 value = get_value((r, (g * 256) / size, (b * 256 / size))) 41 im.putpixel((g, b), value) 42 im.save("rgb%d.png" % r) 43 44 def test_flat(rgb): 45 46 "Generate a flat image for the colour 'rgb'." 47 48 size = 64 49 im = PIL.Image.new("RGB", (size, size)) 50 for y in range(0, size): 51 for x in range(0, size): 52 im.putpixel((x, y), get_value(rgb)) 53 im.save("rgb%02d%02d%02d.png" % rgb) 54 55 def rotate_and_scale(exif, im, width, height, rotate): 56 57 """ 58 Using the given 'exif' information, rotate and scale image 'im' given the 59 indicated 'width' and 'height' constraints and any explicit 'rotate' 60 indication. The returned image will be within the given 'width' and 61 'height', filling either or both, and preserve its original aspect ratio. 62 """ 63 64 if rotate or exif and exif["Image Orientation"].values == [6L]: 65 im = im.rotate(270) 66 67 w, h = im.size 68 if w > h: 69 height = (width * h) / w 70 else: 71 width = (height * w) / h 72 73 return im.resize((width, height)) 74 75 def get_parameter(options, flag, conversion, default, missing): 76 77 """ 78 From 'options', return any parameter following the given 'flag', applying 79 the 'conversion' which has the given 'default' if no valid parameter is 80 found, or returning the given 'missing' value if the flag does not appear at 81 all. 82 """ 83 84 try: 85 i = options.index(flag) 86 try: 87 return conversion(options[i+1]) 88 except (IndexError, ValueError): 89 return default 90 except ValueError: 91 return missing 92 93 # Main program. 94 95 if __name__ == "__main__": 96 97 # Test options. 98 99 if "--test" in sys.argv: 100 test() 101 sys.exit(0) 102 elif "--test-flat" in sys.argv: 103 test_flat((120, 40, 60)) 104 sys.exit(0) 105 elif "--help" in sys.argv: 106 print >>sys.stderr, """\ 107 Usage: %s <input filename> <output filename> [ <options> ] 108 109 Options are... 110 111 -W - Indicate the output width (default is 320) 112 -C - Number of colours per scanline (default is 4) 113 114 -s - Saturate the input image (optional float, 1.0 if unspecified) 115 -d - Desaturate the input image (optional float, 1.0 if unspecified) 116 -D - Darken the input image (optional float, 1.0 if unspecified) 117 -B - Brighten the input image (optional float, 1.0 if unspecified) 118 119 -r - Rotate the input image clockwise 120 -p - Generate a separate preview image 121 -h - Make the preview image with half horizontal resolution (MODE 2) 122 -v - Verify the output image (loaded if -n is given) 123 -n - Generate no output image 124 """ % split(sys.argv[0])[1] 125 sys.exit(1) 126 127 base_width = 320 128 height = 256 129 130 input_filename, output_filename = sys.argv[1:3] 131 basename, ext = splitext(output_filename) 132 preview_filename = "".join([basename + "_preview", ext]) 133 134 options = sys.argv[3:] 135 136 # Basic image properties. 137 138 width = get_parameter(options, "-W", int, base_width, base_width) 139 number_of_colours = get_parameter(options, "-C", int, 4, 4) 140 141 # Preprocessing options that employ parameters. 142 143 saturate = get_parameter(options, "-s", float, 1.0, 0.0) 144 desaturate = get_parameter(options, "-d", float, 1.0, 0.0) 145 darken = get_parameter(options, "-D", float, 1.0, 0.0) 146 brighten = get_parameter(options, "-B", float, 1.0, 0.0) 147 148 # General output options. 149 150 rotate = "-r" in options 151 preview = "-p" in options 152 half_resolution_preview = "-h" in options 153 verify = "-v" in options 154 no_normal_output = "-n" in options 155 make_image = not no_normal_output 156 157 # Load the input image if requested. 158 159 if make_image or preview: 160 exif = EXIF.process_file(open(input_filename)) 161 im = PIL.Image.open(input_filename).convert("RGB") 162 im = rotate_and_scale(exif, im, base_width, height, rotate) 163 164 # Scale images to the appropriate width. 165 166 if width != base_width: 167 im = im.resize((width, height)) 168 169 sim = SimpleImage(list(im.getdata()), im.size) 170 process_image(sim, saturate, desaturate, darken, brighten) 171 im.putdata(sim.getdata()) 172 173 # Generate a preview if requested. 174 175 if preview: 176 imp = im.copy() 177 if half_resolution_preview: 178 imp = imp.resize((width / 2, height)) 179 sim = SimpleImage(list(imp.getdata()), imp.size) 180 convert_image(sim, 8) 181 imp.putdata(sim.getdata()) 182 if half_resolution_preview: 183 imp = imp.resize((width, height)) 184 imp.save(preview_filename) 185 186 # Generate an output image if requested. 187 188 if make_image: 189 sim = SimpleImage(list(im.getdata()), im.size) 190 convert_image(sim, number_of_colours) 191 im.putdata(sim.getdata()) 192 im.save(output_filename) 193 194 # Verify the output image (which may be loaded) if requested. 195 196 if verify: 197 if no_normal_output: 198 im = PIL.Image.open(output_filename).convert("RGB") 199 200 im = SimpleImage(list(im.getdata()), im.size) 201 result = count_colours(im, number_of_colours) 202 if result is not None: 203 y, colours = result 204 print "Image %s: row %d has the following colours: %s" % (output_filename, y, "; ".join([repr(c) for c in colours])) 205 206 # vim: tabstop=4 expandtab shiftwidth=4