1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/main.py Fri Oct 09 23:48:21 2015 +0200
1.3 @@ -0,0 +1,174 @@
1.4 +#!/usr/bin/env python
1.5 +
1.6 +"""
1.7 +Convert and optimise images for display in an Acorn Electron MODE 1 variant
1.8 +with four colours per line but eight colours available for selection on each
1.9 +line.
1.10 +
1.11 +Copyright (C) 2015 Paul Boddie <paul@boddie.org.uk>
1.12 +
1.13 +This program is free software; you can redistribute it and/or modify it under
1.14 +the terms of the GNU General Public License as published by the Free Software
1.15 +Foundation; either version 3 of the License, or (at your option) any later
1.16 +version.
1.17 +
1.18 +This program is distributed in the hope that it will be useful, but WITHOUT ANY
1.19 +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
1.20 +PARTICULAR PURPOSE. See the GNU General Public License for more details.
1.21 +
1.22 +You should have received a copy of the GNU General Public License along
1.23 +with this program. If not, see <http://www.gnu.org/licenses/>.
1.24 +"""
1.25 +
1.26 +from optimiser import *
1.27 +from os.path import split, splitext
1.28 +import EXIF
1.29 +import PIL.Image
1.30 +import sys
1.31 +
1.32 +def rotate_and_scale(exif, im, width, height, rotate):
1.33 +
1.34 + """
1.35 + Using the given 'exif' information, rotate and scale image 'im' given the
1.36 + indicated 'width' and 'height' constraints and any explicit 'rotate'
1.37 + indication. The returned image will be within the given 'width' and
1.38 + 'height', filling either or both, and preserve its original aspect ratio.
1.39 + """
1.40 +
1.41 + if rotate or exif and exif["Image Orientation"].values == [6L]:
1.42 + im = im.rotate(270)
1.43 +
1.44 + w, h = im.size
1.45 + if w > h:
1.46 + height = (width * h) / w
1.47 + else:
1.48 + width = (height * w) / h
1.49 +
1.50 + return im.resize((width, height))
1.51 +
1.52 +def test():
1.53 +
1.54 + "Generate slices of the colour cube."
1.55 +
1.56 + size = 64
1.57 + for r in (0, 63, 127, 191, 255):
1.58 + pim = PIL.Image.new("RGB", (size, size))
1.59 + im = SimpleImage(list(pim.getdata()), pim.size)
1.60 + test_slice(im, size, r)
1.61 + pim.putdata(im.getdata())
1.62 + pim.save("rgb%d.png" % r)
1.63 +
1.64 +def test_flat(rgb):
1.65 +
1.66 + "Generate a flat image for the colour 'rgb'."
1.67 +
1.68 + size = 64
1.69 + pim = PIL.Image.new("RGB", (size, size))
1.70 + im = SimpleImage(list(pim.getdata()), pim.size)
1.71 + test_flat_slice(im, size, rgb)
1.72 + pim.putdata(im.getdata())
1.73 + pim.save("rgb%02d%02d%02d.png" % rgb)
1.74 +
1.75 +def get_float(options, flag):
1.76 + try:
1.77 + i = options.index(flag)
1.78 + if i+1 < len(options) and options[i+1].isdigit():
1.79 + return float(options[i+1])
1.80 + else:
1.81 + return 1.0
1.82 + except ValueError:
1.83 + return 0.0
1.84 +
1.85 +# Main program.
1.86 +
1.87 +if __name__ == "__main__":
1.88 +
1.89 + # Test options.
1.90 +
1.91 + if "--test" in sys.argv:
1.92 + test()
1.93 + sys.exit(0)
1.94 + elif "--test-flat" in sys.argv:
1.95 + test_flat((120, 40, 60))
1.96 + sys.exit(0)
1.97 + elif "--help" in sys.argv:
1.98 + print >>sys.stderr, """\
1.99 +Usage: %s <input filename> <output filename> [ <options> ]
1.100 +
1.101 +Options are...
1.102 +
1.103 +-s - Saturate the input image (can be followed by a float, default 1.0)
1.104 +-d - Desaturate the input image (can be followed by a float, default 1.0)
1.105 +-D - Darken the input image (can be followed by a float, default 1.0)
1.106 +-B - Brighten the input image (can be followed by a float, default 1.0)
1.107 +
1.108 +-r - Rotate the input image clockwise
1.109 +-p - Generate a separate preview image
1.110 +-h - Make the preview image with half horizontal resolution (MODE 2)
1.111 +-v - Verify the output image (loaded if -n is given)
1.112 +-n - Generate no output image
1.113 +""" % split(sys.argv[0])[1]
1.114 + sys.exit(1)
1.115 +
1.116 + width = 320
1.117 + height = 256
1.118 +
1.119 + input_filename, output_filename = sys.argv[1:3]
1.120 + basename, ext = splitext(output_filename)
1.121 + preview_filename = "".join([basename + "_preview", ext])
1.122 +
1.123 + options = sys.argv[3:]
1.124 +
1.125 + # Preprocessing options that can be repeated for extra effect.
1.126 +
1.127 + saturate = get_float(options, "-s")
1.128 + desaturate = get_float(options, "-d")
1.129 + darken = get_float(options, "-D")
1.130 + brighten = get_float(options, "-B")
1.131 +
1.132 + # General output options.
1.133 +
1.134 + rotate = "-r" in options
1.135 + preview = "-p" in options
1.136 + half_resolution_preview = "-h" in options
1.137 + verify = "-v" in options
1.138 + no_normal_output = "-n" in options
1.139 + make_image = not no_normal_output
1.140 +
1.141 + # Load the input image if requested.
1.142 +
1.143 + if make_image or preview:
1.144 + exif = EXIF.process_file(open(input_filename))
1.145 + pim = PIL.Image.open(input_filename).convert("RGB")
1.146 + pim = rotate_and_scale(exif, pim, width, height, rotate)
1.147 + im = SimpleImage(list(pim.getdata()), pim.size)
1.148 + process_image(im, saturate, desaturate, darken, brighten)
1.149 +
1.150 + # Generate a preview if requested.
1.151 +
1.152 + if preview:
1.153 + imp = preview_image(im, half_resolution_preview)
1.154 + pimp = pim.copy()
1.155 + pimp.putdata(imp.getdata())
1.156 + pimp.save(preview_filename)
1.157 +
1.158 + # Generate an output image if requested.
1.159 +
1.160 + if make_image:
1.161 + convert_image(im)
1.162 + pim.putdata(im.getdata())
1.163 + pim.save(output_filename)
1.164 +
1.165 + # Verify the output image (which may be loaded) if requested.
1.166 +
1.167 + if verify:
1.168 + if no_normal_output:
1.169 + pim = PIL.Image.open(output_filename).convert("RGB")
1.170 + im = SimpleImage(list(pim.getdata()), pim.size)
1.171 +
1.172 + result = count_colours(im, 4)
1.173 + if result is not None:
1.174 + y, colours = result
1.175 + print "Image %s: row %d has the following colours: %s" % (output_filename, y, "; ".join([repr(c) for c in colours]))
1.176 +
1.177 +# vim: tabstop=4 expandtab shiftwidth=4
2.1 --- a/optimiser.py Fri Oct 09 22:16:49 2015 +0200
2.2 +++ b/optimiser.py Fri Oct 09 23:48:21 2015 +0200
2.3 @@ -21,9 +21,6 @@
2.4 """
2.5
2.6 from random import random, randrange
2.7 -from os.path import split, splitext
2.8 -import EXIF
2.9 -import PIL.Image
2.10 import itertools
2.11 import math
2.12 import sys
2.13 @@ -208,49 +205,16 @@
2.14 all.sort(reverse=True)
2.15 return [l for total, l in all]
2.16
2.17 -def test():
2.18 -
2.19 - "Generate slices of the colour cube."
2.20 +def test_slice(im, size, r):
2.21 + for g in range(0, size):
2.22 + for b in range(0, size):
2.23 + value = get_value((r, (g * 256) / size, (b * 256 / size)))
2.24 + im.putpixel((g, b), value)
2.25
2.26 - size = 512
2.27 - for r in (0, 63, 127, 191, 255):
2.28 - im = PIL.Image.new("RGB", (size, size))
2.29 - for g in range(0, size):
2.30 - for b in range(0, size):
2.31 - value = get_value((r, (g * 256) / size, (b * 256 / size)))
2.32 - im.putpixel((g, b), value)
2.33 - im.save("rgb%d.png" % r)
2.34 -
2.35 -def test_flat(rgb):
2.36 -
2.37 - "Generate a flat image for the colour 'rgb'."
2.38 -
2.39 - size = 64
2.40 - im = PIL.Image.new("RGB", (size, size))
2.41 +def test_flat_slice(im, size, rgb):
2.42 for y in range(0, size):
2.43 for x in range(0, size):
2.44 im.putpixel((x, y), get_value(rgb))
2.45 - im.save("rgb%02d%02d%02d.png" % rgb)
2.46 -
2.47 -def rotate_and_scale(exif, im, width, height, rotate):
2.48 -
2.49 - """
2.50 - Using the given 'exif' information, rotate and scale image 'im' given the
2.51 - indicated 'width' and 'height' constraints and any explicit 'rotate'
2.52 - indication. The returned image will be within the given 'width' and
2.53 - 'height', filling either or both, and preserve its original aspect ratio.
2.54 - """
2.55 -
2.56 - if rotate or exif and exif["Image Orientation"].values == [6L]:
2.57 - im = im.rotate(270)
2.58 -
2.59 - w, h = im.size
2.60 - if w > h:
2.61 - height = (width * h) / w
2.62 - else:
2.63 - width = (height * w) / h
2.64 -
2.65 - return im.resize((width, height))
2.66
2.67 def count_colours(im, colours):
2.68
2.69 @@ -263,9 +227,7 @@
2.70 width, height = im.size
2.71
2.72 for y in range(0, height):
2.73 - l = set()
2.74 - for x in range(0, width):
2.75 - l.add(im.getpixel((x, y)))
2.76 + l = set(im.getdata()[y * width:(y+1) * width])
2.77 if len(l) > colours:
2.78 return (y, l)
2.79 return None
2.80 @@ -344,101 +306,44 @@
2.81 rgbn = tuple(map(lambda i: clip(i[0] + (i[1] - i[2]) / 2.0), zip(rgbn, rgb, value)))
2.82 im.putpixel((x, y+1), rgbn)
2.83
2.84 -def get_float(options, flag):
2.85 - try:
2.86 - i = options.index(flag)
2.87 - if i+1 < len(options) and options[i+1].isdigit():
2.88 - return float(options[i+1])
2.89 - else:
2.90 - return 1.0
2.91 - except ValueError:
2.92 - return 0.0
2.93 +class SimpleImage:
2.94 +
2.95 + "An image behaving like PIL.Image."
2.96 +
2.97 + def __init__(self, data, size):
2.98 + self.data = data
2.99 + self.width, self.height = self.size = size
2.100 +
2.101 + def copy(self):
2.102 + return SimpleImage(self.data[:], self.size)
2.103
2.104 -# Main program.
2.105 + def getpixel(self, xy):
2.106 + x, y = xy
2.107 + return self.data[y * self.width + x]
2.108 +
2.109 + def putpixel(self, xy, value):
2.110 + x, y = xy
2.111 + self.data[y * self.width + x] = value
2.112 +
2.113 + def getdata(self):
2.114 + return self.data
2.115 +
2.116 +# Test program.
2.117
2.118 if __name__ == "__main__":
2.119 -
2.120 - # Test options.
2.121 -
2.122 - if "--test" in sys.argv:
2.123 - test()
2.124 - sys.exit(0)
2.125 - elif "--test-flat" in sys.argv:
2.126 - test_flat((120, 40, 60))
2.127 - sys.exit(0)
2.128 - elif "--help" in sys.argv:
2.129 - print >>sys.stderr, """\
2.130 -Usage: %s <input filename> <output filename> [ <options> ]
2.131 -
2.132 -Options are...
2.133 + data = [(0, 0, 0)] * 1024
2.134 + size = (32, 32)
2.135
2.136 --s - Saturate the input image (can be followed by a float, default 1.0)
2.137 --d - Desaturate the input image (can be followed by a float, default 1.0)
2.138 --D - Darken the input image (can be followed by a float, default 1.0)
2.139 --B - Brighten the input image (can be followed by a float, default 1.0)
2.140 -
2.141 --r - Rotate the input image clockwise
2.142 --p - Generate a separate preview image
2.143 --h - Make the preview image with half horizontal resolution (MODE 2)
2.144 --v - Verify the output image (loaded if -n is given)
2.145 --n - Generate no output image
2.146 -""" % split(sys.argv[0])[1]
2.147 - sys.exit(1)
2.148 -
2.149 - width = 320
2.150 - height = 256
2.151 -
2.152 - input_filename, output_filename = sys.argv[1:3]
2.153 - basename, ext = splitext(output_filename)
2.154 - preview_filename = "".join([basename + "_preview", ext])
2.155 -
2.156 - options = sys.argv[3:]
2.157 -
2.158 - # Preprocessing options that can be repeated for extra effect.
2.159 + im = SimpleImage(data, size)
2.160
2.161 - saturate = get_float(options, "-s")
2.162 - desaturate = get_float(options, "-d")
2.163 - darken = get_float(options, "-D")
2.164 - brighten = get_float(options, "-B")
2.165 -
2.166 - # General output options.
2.167 -
2.168 - rotate = "-r" in options
2.169 - preview = "-p" in options
2.170 - half_resolution_preview = "-h" in options
2.171 - verify = "-v" in options
2.172 - no_normal_output = "-n" in options
2.173 - make_image = not no_normal_output
2.174 -
2.175 - # Load the input image if requested.
2.176 -
2.177 - if make_image or preview:
2.178 - exif = EXIF.process_file(open(input_filename))
2.179 - im = PIL.Image.open(input_filename).convert("RGB")
2.180 - im = rotate_and_scale(exif, im, width, height, rotate)
2.181 + process_image(im, 1.0, 0.0, 1.0, 0.0)
2.182 + imp = preview_image(im, False)
2.183 + convert_image(im)
2.184
2.185 - process_image(im, saturate, desaturate, darken, brighten)
2.186 -
2.187 - # Generate a preview if requested.
2.188 -
2.189 - if preview:
2.190 - preview_image(im, half_resolution_preview).save(preview_filename)
2.191 -
2.192 - # Generate an output image if requested.
2.193 + test_im = SimpleImage(data, size)
2.194 + test_slice(test_im, 32, 0)
2.195
2.196 - if make_image:
2.197 - convert_image(im)
2.198 - im.save(output_filename)
2.199 -
2.200 - # Verify the output image (which may be loaded) if requested.
2.201 -
2.202 - if verify:
2.203 - if no_normal_output:
2.204 - im = PIL.Image.open(output_filename).convert("RGB")
2.205 -
2.206 - result = count_colours(im, 4)
2.207 - if result is not None:
2.208 - y, colours = result
2.209 - print "Image %s: row %d has the following colours: %s" % (output_filename, y, "; ".join([repr(c) for c in colours]))
2.210 + test_flat_im = SimpleImage(data, size)
2.211 + test_flat_slice(test_flat_im, 32, (200, 100, 50))
2.212
2.213 # vim: tabstop=4 expandtab shiftwidth=4