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: 104 print >>sys.stderr, """\ 105 Usage: %s <input filename> <output filename> [ <options> ] 106 107 Options are... 108 109 -W - Indicate the output width (default is 320) 110 -C - Number of colours per scanline (default is 4) 111 112 -s - Saturate the input image (optional float, 1.0 if unspecified) 113 -d - Desaturate the input image (optional float, 1.0 if unspecified) 114 -D - Darken the input image (optional float, 1.0 if unspecified) 115 -B - Brighten the input image (optional float, 1.0 if unspecified) 116 117 -l - Use colours producing the least error 118 (slower but useful for fewer than 4 colours) 119 120 -r - Rotate the input image clockwise explicitly 121 (EXIF information is used otherwise) 122 -p - Generate a separate preview image 123 -h - Make the preview image with half horizontal resolution (MODE 2) 124 -v - Verify the output image (loaded if -n is given) 125 -n - Generate no output image 126 127 An input filename of - implies the -n option, and is useful for verifying 128 previously generated images. 129 """ % split(sys.argv[0])[1] 130 sys.exit(1) 131 132 base_width = 320 133 height = 256 134 135 input_filename, output_filename = sys.argv[1:3] 136 basename, ext = splitext(output_filename) 137 preview_filename = "".join([basename + "_preview", ext]) 138 139 options = sys.argv[3:] 140 141 # Basic image properties. 142 143 width = get_parameter(options, "-W", int, base_width, base_width) 144 number_of_colours = get_parameter(options, "-C", int, 4, 4) 145 146 # Preprocessing options that employ parameters. 147 148 saturate = get_parameter(options, "-s", float, 1.0, 0.0) 149 desaturate = get_parameter(options, "-d", float, 1.0, 0.0) 150 darken = get_parameter(options, "-D", float, 1.0, 0.0) 151 brighten = get_parameter(options, "-B", float, 1.0, 0.0) 152 153 # General output options. 154 155 least_error = "-l" in options 156 rotate = "-r" in options 157 preview = "-p" in options 158 half_resolution_preview = "-h" in options 159 verify = "-v" in options 160 no_normal_output = "-n" in options or input_filename == "-" 161 make_image = not no_normal_output 162 163 # Load the input image if requested. 164 165 if make_image or preview: 166 exif = EXIF.process_file(open(input_filename)) 167 im = PIL.Image.open(input_filename).convert("RGB") 168 im = rotate_and_scale(exif, im, base_width, height, rotate) 169 170 # Scale images to the appropriate width. 171 172 if width != base_width: 173 im = im.resize((width, height)) 174 175 sim = SimpleImage(list(im.getdata()), im.size) 176 process_image(sim, saturate, desaturate, darken, brighten) 177 im.putdata(sim.getdata()) 178 179 # Generate a preview if requested. 180 181 if preview: 182 imp = im.copy() 183 if half_resolution_preview: 184 imp = imp.resize((width / 2, height)) 185 sim = SimpleImage(list(imp.getdata()), imp.size) 186 convert_image(sim, 8) 187 imp.putdata(sim.getdata()) 188 if half_resolution_preview: 189 imp = imp.resize((width, height)) 190 imp.save(preview_filename) 191 192 # Generate an output image if requested. 193 194 if make_image: 195 sim = SimpleImage(list(im.getdata()), im.size) 196 convert_image(sim, number_of_colours, least_error) 197 im.putdata(sim.getdata()) 198 im.save(output_filename) 199 200 # Verify the output image (which may be loaded) if requested. 201 202 if verify: 203 if no_normal_output: 204 im = PIL.Image.open(output_filename).convert("RGB") 205 206 im = SimpleImage(list(im.getdata()), im.size) 207 result = count_colours(im, number_of_colours) 208 if result is not None: 209 y, colours = result 210 print "Image %s: row %d has the following colours: %s" % (output_filename, y, "; ".join([repr(c) for c in colours])) 211 212 # vim: tabstop=4 expandtab shiftwidth=4