VGAPIC32

tools/makeimage.py

23:81ee57d5c082
2017-05-18 Paul Boddie Switched to using a I0RRGGBB colour representation.
     1 #!/usr/bin/env python     2      3 """     4 Convert images for display in an Acorn Electron MODE 2 variant with a pixel     5 layout of I0RRGGBB, giving 128 colours instead of the usual 8 colours.     6      7 Copyright (C) 2015, 2017 Paul Boddie <paul@boddie.org.uk>     8      9 This program is free software; you can redistribute it and/or modify it under    10 the terms of the GNU General Public License as published by the Free Software    11 Foundation; either version 3 of the License, or (at your option) any later    12 version.    13     14 This program is distributed in the hope that it will be useful, but WITHOUT ANY    15 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A    16 PARTICULAR PURPOSE.  See the GNU General Public License for more details.    17     18 You should have received a copy of the GNU General Public License along    19 with this program.  If not, see <http://www.gnu.org/licenses/>.    20 """    21     22 from os.path import split, splitext    23 import EXIF    24 import PIL.Image    25 import sys    26     27 def convert_image(im, output_filename, width, height):    28     29     "Convert 'im' and write pixel values to 'output_filename'."    30     31     w, h = im.size    32     33     hpad = (width - w) / 2    34     leftpad = hpad; rightpad = width - w - hpad    35     vpad = (height - h) / 2    36     toppad = vpad; bottompad = height - h - vpad    37     38     data = iter(im.getdata())    39     40     f = open(output_filename, "w")    41     try:    42         word = []    43         y = 0    44     45         while y < height:    46             x = 0    47     48             # Top and bottom padding.    49     50             if y < toppad or y >= height - bottompad:    51     52                 while x < width:    53                     word.append(0)    54                     flush_word(f, word)    55                     x += 1    56     57                 flush_last_word(f, word)    58     59             # Lines with data.    60     61             else:    62                 while x < width:    63     64                     # Left and right padding.    65     66                     if x < leftpad or x >= width - rightpad:    67                         word.append(0)    68     69                     # Data regions.    70     71                     else:    72                         r, g, b = data.next()    73                         rm, gm, bm, i = get_values(r, g, b)    74     75                         # Encode the byte value: I0RRGGBB.    76     77                         word.insert(0,    78                             # I -> D<7>    79                             (i << 7) |    80                             # R<7:6> -> D<5:4>    81                             (rm >> 2) |    82                             # G<7:6> -> D<3:2>    83                             (gm >> 4) |    84                             # B<7:6> -> D<1:0>    85                             (bm >> 6))    86     87                     flush_word(f, word)    88                     x += 1    89     90                 flush_last_word(f, word)    91     92             y += 1    93     94     finally:    95         f.close()    96     97 def get_values(r, g, b):    98     99     "Return modified values for 'r', 'g' and 'b', plus an intensity bit."   100    101     rm = r & 0xc0   102     gm = g & 0xc0   103     bm = b & 0xc0   104     rd = r - rm   105     gd = g - gm   106     bd = b - bm   107     i = ((rd ** 2 + gd ** 2 + bd ** 2) ** 0.5) >= 32 and 1 or 0   108     return rm, gm, bm, i   109    110 def make_preview(im):   111     imp = PIL.Image.new("RGB", im.size)   112     data = []   113     for r, g, b in im.getdata():   114         rm, gm, bm, i = get_values(r, g, b)   115         r = rm + (i * 32)   116         g = gm + (i * 32)   117         b = bm + (i * 32)   118         data.append((r, g, b))   119     imp.putdata(data)   120     return imp   121    122 def flush_last_word(f, word):   123     if word:   124         pad_word(word)   125         write_word(f, word)   126         del word[:]   127    128 def flush_word(f, word):   129     if len(word) == 4:   130         write_word(f, word)   131         del word[:]   132    133 def pad_word(word):   134     while len(word) < 4:   135         word.insert(0, 0)   136    137 def write_word(f, word):   138     print >>f, ".word 0x%02x%02x%02x%02x" % tuple(word)   139    140 def rotate_and_scale(exif, im, width, height, rotate, scale_factor):   141    142     """   143     Using the given 'exif' information, rotate and scale image 'im' given the   144     indicated 'width' and 'height' constraints and any explicit 'rotate'   145     indication. The returned image will be within the given 'width' and   146     'height', filling either or both, and preserve its original aspect ratio.   147     """   148    149     if rotate or exif and exif["Image Orientation"].values == [6L]:   150         im = im.rotate(270)   151    152     w, h = im.size   153    154     # Get the relationship between the base width and the image width.   155    156     width_scale_factor = (width / scale_factor) / w   157     height_scale_factor = float(height) / h   158     min_scale_factor = min(width_scale_factor, height_scale_factor)   159    160     if min_scale_factor < 1:   161         width = int(min_scale_factor * w * scale_factor)   162         height = int(min_scale_factor * h)   163         return im.resize((width, height))   164     elif scale_factor != 1:   165         width = int(w * scale_factor)   166         return im.resize((width, h))   167     else:   168         return im   169    170 def get_parameter(options, flag, conversion, default, missing):   171    172     """   173     From 'options', return any parameter following the given 'flag', applying   174     the 'conversion' which has the given 'default' if no valid parameter is   175     found, or returning the given 'missing' value if the flag does not appear at   176     all.   177     """   178    179     try:   180         i = options.index(flag)   181         try:   182             return conversion(options[i+1])   183         except (IndexError, ValueError):   184             return default   185     except ValueError:   186         return missing   187    188 # Main program.   189    190 if __name__ == "__main__":   191    192     # Test options.   193    194     if "--help" in sys.argv or len(sys.argv) < 3:   195         basename = split(sys.argv[0])[1]   196         print >>sys.stderr, """\   197 Usage:   198    199 %s <input filename> <output filename> [ <options> ]   200    201 Options are...   202    203 -W - Indicate the output width (default is 160)   204    205 -p - Generate a preview with a filename based on the output filename   206    207 -r - Rotate the input image clockwise explicitly   208      (EXIF information is used otherwise)   209 """ % basename   210         sys.exit(1)   211    212     base_width = 320; width = 160   213     base_height = height = 256   214    215     input_filename, output_filename = sys.argv[1:3]   216     options = sys.argv[3:]   217    218     # Basic image properties.   219    220     width = get_parameter(options, "-W", int, width, width)   221     rotate = "-r" in options   222     preview = "-p" in options   223    224     # Determine any differing horizontal scale factor.   225    226     scale_factor = float(width) / base_width   227    228     # Load the input image.   229    230     exif = EXIF.process_file(open(input_filename))   231     im = PIL.Image.open(input_filename).convert("RGB")   232     im = rotate_and_scale(exif, im, width, height, rotate, scale_factor)   233    234     # Generate an output image.   235    236     convert_image(im, output_filename, width, height)   237    238     # Generate a preview image if requested.   239    240     if preview:   241         _basename, ext = splitext(input_filename)   242         basename, _ext = splitext(output_filename)   243         preview_filename = "%s_preview%s" % (basename, ext)   244         imp = make_preview(im)   245         imp.save(preview_filename)   246    247 # vim: tabstop=4 expandtab shiftwidth=4