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