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 -r - Rotate the input image clockwise 118 -p - Generate a separate preview image 119 -h - Make the preview image with half horizontal resolution (MODE 2) 120 -v - Verify the output image (loaded if -n is given) 121 -n - Generate no output image 122 """ % split(sys.argv[0])[1] 123 sys.exit(1) 124 125 base_width = 320 126 height = 256 127 128 input_filename, output_filename = sys.argv[1:3] 129 basename, ext = splitext(output_filename) 130 preview_filename = "".join([basename + "_preview", ext]) 131 132 options = sys.argv[3:] 133 134 # Basic image properties. 135 136 width = get_parameter(options, "-W", int, base_width, base_width) 137 number_of_colours = get_parameter(options, "-C", int, 4, 4) 138 139 # Preprocessing options that employ parameters. 140 141 saturate = get_parameter(options, "-s", float, 1.0, 0.0) 142 desaturate = get_parameter(options, "-d", float, 1.0, 0.0) 143 darken = get_parameter(options, "-D", float, 1.0, 0.0) 144 brighten = get_parameter(options, "-B", float, 1.0, 0.0) 145 146 # General output options. 147 148 rotate = "-r" in options 149 preview = "-p" in options 150 half_resolution_preview = "-h" in options 151 verify = "-v" in options 152 no_normal_output = "-n" in options 153 make_image = not no_normal_output 154 155 # Load the input image if requested. 156 157 if make_image or preview: 158 exif = EXIF.process_file(open(input_filename)) 159 im = PIL.Image.open(input_filename).convert("RGB") 160 im = rotate_and_scale(exif, im, base_width, height, rotate) 161 162 # Scale images to the appropriate width. 163 164 if width != base_width: 165 im = im.resize((width, height)) 166 167 sim = SimpleImage(list(im.getdata()), im.size) 168 process_image(sim, saturate, desaturate, darken, brighten) 169 im.putdata(sim.getdata()) 170 171 # Generate a preview if requested. 172 173 if preview: 174 imp = im.copy() 175 if half_resolution_preview: 176 imp = imp.resize((width / 2, height)) 177 sim = SimpleImage(list(imp.getdata()), imp.size) 178 convert_image(sim, 8) 179 imp.putdata(sim.getdata()) 180 if half_resolution_preview: 181 imp = imp.resize((width, height)) 182 imp.save(preview_filename) 183 184 # Generate an output image if requested. 185 186 if make_image: 187 sim = SimpleImage(list(im.getdata()), im.size) 188 convert_image(sim, number_of_colours) 189 im.putdata(sim.getdata()) 190 im.save(output_filename) 191 192 # Verify the output image (which may be loaded) if requested. 193 194 if verify: 195 if no_normal_output: 196 im = PIL.Image.open(output_filename).convert("RGB") 197 198 im = SimpleImage(list(im.getdata()), im.size) 199 result = count_colours(im, number_of_colours) 200 if result is not None: 201 y, colours = result 202 print "Image %s: row %d has the following colours: %s" % (output_filename, y, "; ".join([repr(c) for c in colours])) 203 204 # vim: tabstop=4 expandtab shiftwidth=4