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