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 # Image operations. 30 31 def test(): 32 33 "Generate slices of the colour cube." 34 35 size = 512 36 for r in (0, 63, 127, 191, 255): 37 im = PIL.Image.new("RGB", (size, size)) 38 for g in range(0, size): 39 for b in range(0, size): 40 value = get_value((r, (g * 256) / size, (b * 256 / size))) 41 im.putpixel((g, b), value) 42 im.save("rgb%d.png" % r) 43 44 def test_flat(rgb): 45 46 "Generate a flat image for the colour 'rgb'." 47 48 size = 64 49 im = PIL.Image.new("RGB", (size, size)) 50 for y in range(0, size): 51 for x in range(0, size): 52 im.putpixel((x, y), get_value(rgb)) 53 im.save("rgb%02d%02d%02d.png" % rgb) 54 55 def rotate_and_scale(exif, im, width, height, rotate): 56 57 """ 58 Using the given 'exif' information, rotate and scale image 'im' given the 59 indicated 'width' and 'height' constraints and any explicit 'rotate' 60 indication. The returned image will be within the given 'width' and 61 'height', filling either or both, and preserve its original aspect ratio. 62 """ 63 64 if rotate or exif and exif["Image Orientation"].values == [6L]: 65 im = im.rotate(270) 66 67 w, h = im.size 68 if w > h: 69 height = (width * h) / w 70 else: 71 width = (height * w) / h 72 73 return im.resize((width, height)) 74 75 def get_float(options, flag): 76 try: 77 i = options.index(flag) 78 try: 79 return float(options[i+1]) 80 except (IndexError, ValueError): 81 return 1.0 82 except ValueError: 83 return 0.0 84 85 # Main program. 86 87 if __name__ == "__main__": 88 89 # Test options. 90 91 if "--test" in sys.argv: 92 test() 93 sys.exit(0) 94 elif "--test-flat" in sys.argv: 95 test_flat((120, 40, 60)) 96 sys.exit(0) 97 elif "--help" in sys.argv: 98 print >>sys.stderr, """\ 99 Usage: %s <input filename> <output filename> [ <options> ] 100 101 Options are... 102 103 -s - Saturate the input image (can be followed by a float, default 1.0) 104 -d - Desaturate the input image (can be followed by a float, default 1.0) 105 -D - Darken the input image (can be followed by a float, default 1.0) 106 -B - Brighten the input image (can be followed by a float, default 1.0) 107 108 -r - Rotate the input image clockwise 109 -p - Generate a separate preview image 110 -h - Make the preview image with half horizontal resolution (MODE 2) 111 -v - Verify the output image (loaded if -n is given) 112 -n - Generate no output image 113 """ % split(sys.argv[0])[1] 114 sys.exit(1) 115 116 width = 320 117 height = 256 118 119 input_filename, output_filename = sys.argv[1:3] 120 basename, ext = splitext(output_filename) 121 preview_filename = "".join([basename + "_preview", ext]) 122 123 options = sys.argv[3:] 124 125 # Preprocessing options that can be repeated for extra effect. 126 127 saturate = get_float(options, "-s") 128 desaturate = get_float(options, "-d") 129 darken = get_float(options, "-D") 130 brighten = get_float(options, "-B") 131 132 # General output options. 133 134 rotate = "-r" in options 135 preview = "-p" in options 136 half_resolution_preview = "-h" in options 137 verify = "-v" in options 138 no_normal_output = "-n" in options 139 make_image = not no_normal_output 140 141 # Load the input image if requested. 142 143 if make_image or preview: 144 exif = EXIF.process_file(open(input_filename)) 145 im = PIL.Image.open(input_filename).convert("RGB") 146 im = rotate_and_scale(exif, im, width, height, rotate) 147 148 sim = SimpleImage(list(im.getdata()), im.size) 149 process_image(sim, saturate, desaturate, darken, brighten) 150 im.putdata(sim.getdata()) 151 152 # Generate a preview if requested. 153 154 if preview: 155 imp = im.copy() 156 sim = SimpleImage(list(im.getdata()), im.size) 157 simp = preview_image(sim, half_resolution_preview) 158 imp.putdata(simp.getdata()) 159 imp.save(preview_filename) 160 161 # Generate an output image if requested. 162 163 if make_image: 164 sim = SimpleImage(list(im.getdata()), im.size) 165 convert_image(sim) 166 im.putdata(sim.getdata()) 167 im.save(output_filename) 168 169 # Verify the output image (which may be loaded) if requested. 170 171 if verify: 172 if no_normal_output: 173 im = PIL.Image.open(output_filename).convert("RGB") 174 175 im = SimpleImage(list(im.getdata()), im.size) 176 result = count_colours(im, 4) 177 if result is not None: 178 y, colours = result 179 print "Image %s: row %d has the following colours: %s" % (output_filename, y, "; ".join([repr(c) for c in colours])) 180 181 # vim: tabstop=4 expandtab shiftwidth=4