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