1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/tools/makeimage.py Thu May 18 01:58:26 2017 +0200
1.3 @@ -0,0 +1,232 @@
1.4 +#!/usr/bin/env python
1.5 +
1.6 +"""
1.7 +Convert images for display in an Acorn Electron MODE 2 variant with a pixel
1.8 +layout of R0RGGGBB, giving 128 colours instead of the usual 8 colours.
1.9 +
1.10 +Copyright (C) 2015, 2017 Paul Boddie <paul@boddie.org.uk>
1.11 +
1.12 +This program is free software; you can redistribute it and/or modify it under
1.13 +the terms of the GNU General Public License as published by the Free Software
1.14 +Foundation; either version 3 of the License, or (at your option) any later
1.15 +version.
1.16 +
1.17 +This program is distributed in the hope that it will be useful, but WITHOUT ANY
1.18 +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
1.19 +PARTICULAR PURPOSE. See the GNU General Public License for more details.
1.20 +
1.21 +You should have received a copy of the GNU General Public License along
1.22 +with this program. If not, see <http://www.gnu.org/licenses/>.
1.23 +
1.24 +----
1.25 +
1.26 +ImageMagick can be used to dither images to a 232 palette before conversion:
1.27 +
1.28 +convert in.png -ordered-dither threshold,4,8,4 out.png
1.29 +"""
1.30 +
1.31 +from os.path import split, splitext
1.32 +import EXIF
1.33 +import PIL.Image
1.34 +import sys
1.35 +
1.36 +def convert_image(im, output_filename, width, height):
1.37 +
1.38 + "Convert 'im' and write pixel values to 'output_filename'."
1.39 +
1.40 + w, h = im.size
1.41 +
1.42 + hpad = (width - w) / 2
1.43 + leftpad = hpad; rightpad = width - w - hpad
1.44 + vpad = (height - h) / 2
1.45 + toppad = vpad; bottompad = height - h - vpad
1.46 +
1.47 + data = iter(im.getdata())
1.48 +
1.49 + f = open(output_filename, "w")
1.50 + try:
1.51 + word = []
1.52 + y = 0
1.53 +
1.54 + while y < height:
1.55 + x = 0
1.56 +
1.57 + # Top and bottom padding.
1.58 +
1.59 + if y < toppad or y >= height - bottompad:
1.60 +
1.61 + while x < width:
1.62 + word.append(0)
1.63 + flush_word(f, word)
1.64 + x += 1
1.65 +
1.66 + flush_last_word(f, word)
1.67 +
1.68 + # Lines with data.
1.69 +
1.70 + else:
1.71 + while x < width:
1.72 +
1.73 + # Left and right padding.
1.74 +
1.75 + if x < leftpad or x >= width - rightpad:
1.76 + word.append(0)
1.77 +
1.78 + # Data regions.
1.79 +
1.80 + else:
1.81 + r, g, b = data.next()
1.82 + word.insert(0,
1.83 + # R<7> -> D<7>
1.84 + (r & 0x80) |
1.85 + # R<6> -> D<5>
1.86 + ((r & 0x40) >> 1) |
1.87 + # G<7:5> -> D<4:2>
1.88 + ((g >> 5) << 2) |
1.89 + # B<7:6> -> D<1:0>
1.90 + (b >> 6))
1.91 +
1.92 + flush_word(f, word)
1.93 + x += 1
1.94 +
1.95 + flush_last_word(f, word)
1.96 +
1.97 + y += 1
1.98 +
1.99 + finally:
1.100 + f.close()
1.101 +
1.102 +def make_preview(im):
1.103 + imp = PIL.Image.new("RGB", im.size)
1.104 + data = []
1.105 + for r, g, b in im.getdata():
1.106 + data.append((((r >> 6) << 6), ((g >> 5) << 5), (b >> 6) << 6))
1.107 + imp.putdata(data)
1.108 + return imp
1.109 +
1.110 +def flush_last_word(f, word):
1.111 + if word:
1.112 + pad_word(word)
1.113 + write_word(f, word)
1.114 + del word[:]
1.115 +
1.116 +def flush_word(f, word):
1.117 + if len(word) == 4:
1.118 + write_word(f, word)
1.119 + del word[:]
1.120 +
1.121 +def pad_word(word):
1.122 + while len(word) < 4:
1.123 + word.insert(0, 0)
1.124 +
1.125 +def write_word(f, word):
1.126 + print >>f, ".word 0x%02x%02x%02x%02x" % tuple(word)
1.127 +
1.128 +def rotate_and_scale(exif, im, width, height, rotate, scale_factor):
1.129 +
1.130 + """
1.131 + Using the given 'exif' information, rotate and scale image 'im' given the
1.132 + indicated 'width' and 'height' constraints and any explicit 'rotate'
1.133 + indication. The returned image will be within the given 'width' and
1.134 + 'height', filling either or both, and preserve its original aspect ratio.
1.135 + """
1.136 +
1.137 + if rotate or exif and exif["Image Orientation"].values == [6L]:
1.138 + im = im.rotate(270)
1.139 +
1.140 + w, h = im.size
1.141 +
1.142 + # Get the relationship between the base width and the image width.
1.143 +
1.144 + width_scale_factor = (width / scale_factor) / w
1.145 + height_scale_factor = float(height) / h
1.146 + min_scale_factor = min(width_scale_factor, height_scale_factor)
1.147 +
1.148 + if min_scale_factor < 1:
1.149 + width = int(min_scale_factor * w * scale_factor)
1.150 + height = int(min_scale_factor * h)
1.151 + return im.resize((width, height))
1.152 + elif scale_factor != 1:
1.153 + width = int(w * scale_factor)
1.154 + return im.resize((width, h))
1.155 + else:
1.156 + return im
1.157 +
1.158 +def get_parameter(options, flag, conversion, default, missing):
1.159 +
1.160 + """
1.161 + From 'options', return any parameter following the given 'flag', applying
1.162 + the 'conversion' which has the given 'default' if no valid parameter is
1.163 + found, or returning the given 'missing' value if the flag does not appear at
1.164 + all.
1.165 + """
1.166 +
1.167 + try:
1.168 + i = options.index(flag)
1.169 + try:
1.170 + return conversion(options[i+1])
1.171 + except (IndexError, ValueError):
1.172 + return default
1.173 + except ValueError:
1.174 + return missing
1.175 +
1.176 +# Main program.
1.177 +
1.178 +if __name__ == "__main__":
1.179 +
1.180 + # Test options.
1.181 +
1.182 + if "--help" in sys.argv or len(sys.argv) < 3:
1.183 + basename = split(sys.argv[0])[1]
1.184 + print >>sys.stderr, """\
1.185 +Usage:
1.186 +
1.187 +%s <input filename> <output filename> [ <options> ]
1.188 +
1.189 +Options are...
1.190 +
1.191 +-W - Indicate the output width (default is 160)
1.192 +
1.193 +-p - Generate a preview with a filename based on the output filename
1.194 +
1.195 +-r - Rotate the input image clockwise explicitly
1.196 + (EXIF information is used otherwise)
1.197 +""" % basename
1.198 + sys.exit(1)
1.199 +
1.200 + base_width = 320; width = 160
1.201 + base_height = height = 256
1.202 +
1.203 + input_filename, output_filename = sys.argv[1:3]
1.204 + options = sys.argv[3:]
1.205 +
1.206 + # Basic image properties.
1.207 +
1.208 + width = get_parameter(options, "-W", int, width, width)
1.209 + rotate = "-r" in options
1.210 + preview = "-p" in options
1.211 +
1.212 + # Determine any differing horizontal scale factor.
1.213 +
1.214 + scale_factor = float(width) / base_width
1.215 +
1.216 + # Load the input image.
1.217 +
1.218 + exif = EXIF.process_file(open(input_filename))
1.219 + im = PIL.Image.open(input_filename).convert("RGB")
1.220 + im = rotate_and_scale(exif, im, width, height, rotate, scale_factor)
1.221 +
1.222 + # Generate an output image.
1.223 +
1.224 + convert_image(im, output_filename, width, height)
1.225 +
1.226 + # Generate a preview image if requested.
1.227 +
1.228 + if preview:
1.229 + _basename, ext = splitext(input_filename)
1.230 + basename, _ext = splitext(output_filename)
1.231 + preview_filename = "%s_preview%s" % (basename, ext)
1.232 + imp = make_preview(im)
1.233 + imp.save(preview_filename)
1.234 +
1.235 +# vim: tabstop=4 expandtab shiftwidth=4