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 if w > h: 71 height = (width * h) / w 72 else: 73 width = (height * w) / h 74 75 return im.resize((width, height)) 76 77 def get_parameter(options, flag, conversion, default, missing): 78 79 """ 80 From 'options', return any parameter following the given 'flag', applying 81 the 'conversion' which has the given 'default' if no valid parameter is 82 found, or returning the given 'missing' value if the flag does not appear at 83 all. 84 """ 85 86 try: 87 i = options.index(flag) 88 try: 89 return conversion(options[i+1]) 90 except (IndexError, ValueError): 91 return default 92 except ValueError: 93 return missing 94 95 # Main program. 96 97 if __name__ == "__main__": 98 99 # Test options. 100 101 if "--test" in sys.argv: 102 test() 103 sys.exit(0) 104 elif "--test-flat" in sys.argv: 105 test_flat((120, 40, 60)) 106 sys.exit(0) 107 elif "--help" in sys.argv: 108 print >>sys.stderr, """\ 109 Usage: %s <input filename> <output filename> [ <options> ] 110 111 Options are... 112 113 -W - Indicate the output width (default is 320) 114 -C - Number of colours per scanline (default is 4) 115 116 -s - Saturate the input image (optional float, 1.0 if unspecified) 117 -d - Desaturate the input image (optional float, 1.0 if unspecified) 118 -D - Darken the input image (optional float, 1.0 if unspecified) 119 -B - Brighten the input image (optional float, 1.0 if unspecified) 120 121 -l - Use colours producing the least error 122 (slower but useful for fewer than 4 colours) 123 124 -r - Rotate the input image clockwise explicitly 125 (EXIF information is used otherwise) 126 -p - Generate a separate preview image 127 -h - Make the preview image with half horizontal resolution (MODE 2) 128 -v - Verify the output image (loaded if -n is given) 129 -n - Generate no output image 130 131 An input filename of - implies the -n option, and is useful for verifying 132 previously generated images. 133 """ % split(sys.argv[0])[1] 134 sys.exit(1) 135 136 base_width = 320 137 height = 256 138 139 input_filename, output_filename = sys.argv[1:3] 140 basename, ext = splitext(output_filename) 141 preview_filename = "".join([basename + "_preview", ext]) 142 143 options = sys.argv[3:] 144 145 # Basic image properties. 146 147 width = get_parameter(options, "-W", int, base_width, base_width) 148 number_of_colours = get_parameter(options, "-C", int, 4, 4) 149 150 # Preprocessing options that employ parameters. 151 152 saturate = get_parameter(options, "-s", float, 1.0, 0.0) 153 desaturate = get_parameter(options, "-d", float, 1.0, 0.0) 154 darken = get_parameter(options, "-D", float, 1.0, 0.0) 155 brighten = get_parameter(options, "-B", float, 1.0, 0.0) 156 157 # General output options. 158 159 least_error = "-l" in options 160 rotate = "-r" in options 161 preview = "-p" in options 162 half_resolution_preview = "-h" in options 163 verify = "-v" in options 164 no_normal_output = "-n" in options or input_filename == "-" 165 make_image = not no_normal_output 166 167 # Load the input image if requested. 168 169 if make_image or preview: 170 exif = EXIF.process_file(open(input_filename)) 171 im = PIL.Image.open(input_filename).convert("RGB") 172 im = rotate_and_scale(exif, im, base_width, height, rotate) 173 174 # Scale images to the appropriate width. 175 176 if width != base_width: 177 im = im.resize((width, height)) 178 179 process_image(im, saturate, desaturate, darken, brighten) 180 181 # Generate a preview if requested. 182 183 if preview: 184 imp = im.copy() 185 if half_resolution_preview: 186 imp = imp.resize((width / 2, height)) 187 convert_image(imp, 8) 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 convert_image(im, number_of_colours, least_error) 196 im.save(output_filename) 197 198 # Verify the output image (which may be loaded) if requested. 199 200 if verify: 201 if no_normal_output: 202 im = PIL.Image.open(output_filename).convert("RGB") 203 204 result = count_colours(im, number_of_colours) 205 if result is not None: 206 y, colours = result 207 print "Image %s: row %d has the following colours: %s" % (output_filename, y, "; ".join([repr(c) for c in colours])) 208 209 # vim: tabstop=4 expandtab shiftwidth=4