Previous 199869 Revisions Next

r34306 Saturday 10th January, 2015 at 10:35:34 UTC by Miodrag Milanović
Converted png2bcd tool to python [Andrew Gardner]
[/trunk].gitignore makefile
[src/build]build.mak png.py* png2bdc.c png2bdc.py*

trunk/.gitignore
r242817r242818
1515src/regtests/chdman/temp
1616src/regtests/jedutil/output
1717/sta
18*.pyc
trunk/makefile
r242817r242818
991991   @echo Converting $<...
992992   @$(PYTHON) $(SRC)/build/file2str.py $< $@ layout_$(basename $(notdir $<))
993993
994$(OBJ)/%.fh: $(SRC)/%.png $(PNG2BDC_TARGET) $(SRC)/build/file2str.py
994$(OBJ)/%.fh: $(SRC)/%.png $(SRC)/build/png2bdc.py $(SRC)/build/file2str.py
995995   @echo Converting $<...
996   @$(PNG2BDC) $< $(OBJ)/temp.bdc
996   @$(PYTHON) $(SRC)/build/png2bdc.py $< $(OBJ)/temp.bdc
997997   @$(PYTHON) $(SRC)/build/file2str.py $(OBJ)/temp.bdc $@ font_$(basename $(notdir $<)) UINT8
998998
999999$(DRIVLISTOBJ): $(DRIVLISTSRC)
trunk/src/build/build.mak
r242817r242818
2121MAKEDEP_TARGET = $(BUILDOUT)/makedep$(BUILD_EXE)
2222MAKEMAK_TARGET = $(BUILDOUT)/makemak$(BUILD_EXE)
2323MAKELIST_TARGET = $(BUILDOUT)/makelist$(BUILD_EXE)
24PNG2BDC_TARGET = $(BUILDOUT)/png2bdc$(BUILD_EXE)
2524VERINFO_TARGET = $(BUILDOUT)/verinfo$(BUILD_EXE)
2625
2726MAKEDEP = $(MAKEDEP_TARGET)
2827MAKEMAK = $(MAKEMAK_TARGET)
2928MAKELIST = $(MAKELIST_TARGET)
30PNG2BDC = $(PNG2BDC_TARGET)
3129VERINFO = $(VERINFO_TARGET)
3230
3331ifneq ($(TERM),cygwin)
r242817r242818
3533MAKEDEP = $(subst /,\,$(MAKEDEP_TARGET))
3634MAKEMAK = $(subst /,\,$(MAKEMAK_TARGET))
3735MAKELIST = $(subst /,\,$(MAKELIST_TARGET))
38PNG2BDC = $(subst /,\,$(PNG2BDC_TARGET))
3936VERINFO = $(subst /,\,$(VERINFO_TARGET))
4037endif
4138endif
r242817r242818
4542   $(MAKEDEP_TARGET) \
4643   $(MAKEMAK_TARGET) \
4744   $(MAKELIST_TARGET) \
48   $(PNG2BDC_TARGET) \
4945   $(VERINFO_TARGET) \
5046
5147
r242817r242818
107103
108104
109105#-------------------------------------------------
110# png2bdc
111#-------------------------------------------------
112
113PNG2BDCOBJS = \
114   $(BUILDOBJ)/png2bdc.o \
115   $(OBJ)/lib/util/astring.o \
116   $(OBJ)/lib/util/corefile.o \
117   $(OBJ)/lib/util/corealloc.o \
118   $(OBJ)/lib/util/bitmap.o \
119   $(OBJ)/lib/util/png.o \
120   $(OBJ)/lib/util/palette.o \
121   $(OBJ)/lib/util/unicode.o \
122
123$(PNG2BDC_TARGET): $(PNG2BDCOBJS) $(LIBOCORE) $(ZLIB)
124   @echo Linking $@...
125   $(LD) $(LDFLAGS) $^ $(BASELIBS) -o $@
126
127
128
129#-------------------------------------------------
130106# verinfo
131107#-------------------------------------------------
132108
r242817r242818
147123$(MAKELIST_TARGET):
148124   @echo $@ should be built natively. Nothing to do.
149125
150$(PNG2BDC_TARGET):
151   @echo $@ should be built natively. Nothing to do.
152
153126$(VERINFO_TARGET):
154127   @echo $@ should be built natively. Nothing to do.
155128
trunk/src/build/png.py
r0r242818
1#!/usr/bin/env python
2
3# png.py - PNG encoder/decoder in pure Python
4#
5# Copyright (C) 2006 Johann C. Rocholl <johann@browsershots.org>
6# Portions Copyright (C) 2009 David Jones <drj@pobox.com>
7# And probably portions Copyright (C) 2006 Nicko van Someren <nicko@nicko.org>
8#
9# Original concept by Johann C. Rocholl.
10#
11# LICENCE (MIT)
12#
13# Permission is hereby granted, free of charge, to any person
14# obtaining a copy of this software and associated documentation files
15# (the "Software"), to deal in the Software without restriction,
16# including without limitation the rights to use, copy, modify, merge,
17# publish, distribute, sublicense, and/or sell copies of the Software,
18# and to permit persons to whom the Software is furnished to do so,
19# subject to the following conditions:
20#
21# The above copyright notice and this permission notice shall be
22# included in all copies or substantial portions of the Software.
23#
24# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
28# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
29# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
30# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
31# SOFTWARE.
32
33"""
34Pure Python PNG Reader/Writer
35
36This Python module implements support for PNG images (see PNG
37specification at http://www.w3.org/TR/2003/REC-PNG-20031110/ ). It reads
38and writes PNG files with all allowable bit depths
39(1/2/4/8/16/24/32/48/64 bits per pixel) and colour combinations:
40greyscale (1/2/4/8/16 bit); RGB, RGBA, LA (greyscale with alpha) with
418/16 bits per channel; colour mapped images (1/2/4/8 bit).
42Adam7 interlacing is supported for reading and
43writing.  A number of optional chunks can be specified (when writing)
44and understood (when reading): ``tRNS``, ``bKGD``, ``gAMA``.
45
46For help, type ``import png; help(png)`` in your python interpreter.
47
48A good place to start is the :class:`Reader` and :class:`Writer`
49classes.
50
51Requires Python 2.3.  Limited support is available for Python 2.2, but
52not everything works.  Best with Python 2.4 and higher.  Installation is
53trivial, but see the ``README.txt`` file (with the source distribution)
54for details.
55
56This file can also be used as a command-line utility to convert
57`Netpbm <http://netpbm.sourceforge.net/>`_ PNM files to PNG, and the
58reverse conversion from PNG to PNM. The interface is similar to that
59of the ``pnmtopng`` program from Netpbm.  Type ``python png.py --help``
60at the shell prompt for usage and a list of options.
61
62A note on spelling and terminology
63----------------------------------
64
65Generally British English spelling is used in the documentation.  So
66that's "greyscale" and "colour".  This not only matches the author's
67native language, it's also used by the PNG specification.
68
69The major colour models supported by PNG (and hence by PyPNG) are:
70greyscale, RGB, greyscale--alpha, RGB--alpha.  These are sometimes
71referred to using the abbreviations: L, RGB, LA, RGBA.  In this case
72each letter abbreviates a single channel: *L* is for Luminance or Luma
73or Lightness which is the channel used in greyscale images; *R*, *G*,
74*B* stand for Red, Green, Blue, the components of a colour image; *A*
75stands for Alpha, the opacity channel (used for transparency effects,
76but higher values are more opaque, so it makes sense to call it
77opacity).
78
79A note on formats
80-----------------
81
82When getting pixel data out of this module (reading) and presenting
83data to this module (writing) there are a number of ways the data could
84be represented as a Python value.  Generally this module uses one of
85three formats called "flat row flat pixel", "boxed row flat pixel", and
86"boxed row boxed pixel".  Basically the concern is whether each pixel
87and each row comes in its own little tuple (box), or not.
88
89Consider an image that is 3 pixels wide by 2 pixels high, and each pixel
90has RGB components:
91
92Boxed row flat pixel::
93
94  list([R,G,B, R,G,B, R,G,B],
95       [R,G,B, R,G,B, R,G,B])
96
97Each row appears as its own list, but the pixels are flattened so
98that three values for one pixel simply follow the three values for
99the previous pixel.  This is the most common format used, because it
100provides a good compromise between space and convenience.  PyPNG regards
101itself as at liberty to replace any sequence type with any sufficiently
102compatible other sequence type; in practice each row is an array (from
103the array module), and the outer list is sometimes an iterator rather
104than an explicit list (so that streaming is possible).
105
106Flat row flat pixel::
107
108  [R,G,B, R,G,B, R,G,B,
109   R,G,B, R,G,B, R,G,B]
110
111The entire image is one single giant sequence of colour values.
112Generally an array will be used (to save space), not a list.
113
114Boxed row boxed pixel::
115
116  list([ (R,G,B), (R,G,B), (R,G,B) ],
117       [ (R,G,B), (R,G,B), (R,G,B) ])
118
119Each row appears in its own list, but each pixel also appears in its own
120tuple.  A serious memory burn in Python.
121
122In all cases the top row comes first, and for each row the pixels are
123ordered from left-to-right.  Within a pixel the values appear in the
124order, R-G-B-A (or L-A for greyscale--alpha).
125
126There is a fourth format, mentioned because it is used internally,
127is close to what lies inside a PNG file itself, and has some support
128from the public API.  This format is called packed.  When packed,
129each row is a sequence of bytes (integers from 0 to 255), just as
130it is before PNG scanline filtering is applied.  When the bit depth
131is 8 this is essentially the same as boxed row flat pixel; when the
132bit depth is less than 8, several pixels are packed into each byte;
133when the bit depth is 16 (the only value more than 8 that is supported
134by the PNG image format) each pixel value is decomposed into 2 bytes
135(and `packed` is a misnomer).  This format is used by the
136:meth:`Writer.write_packed` method.  It isn't usually a convenient
137format, but may be just right if the source data for the PNG image
138comes from something that uses a similar format (for example, 1-bit
139BMPs, or another PNG file).
140
141And now, my famous members
142--------------------------
143"""
144
145# http://www.python.org/doc/2.2.3/whatsnew/node5.html
146from __future__ import generators
147
148__version__ = "0.0.17"
149
150from array import array
151try: # See :pyver:old
152    import itertools
153except ImportError:
154    pass
155import math
156# http://www.python.org/doc/2.4.4/lib/module-operator.html
157import operator
158import struct
159import sys
160import zlib
161# http://www.python.org/doc/2.4.4/lib/module-warnings.html
162import warnings
163try:
164    # `cpngfilters` is a Cython module: it must be compiled by
165    # Cython for this import to work.
166    # If this import does work, then it overrides pure-python
167    # filtering functions defined later in this file (see `class
168    # pngfilters`).
169    import cpngfilters as pngfilters
170except ImportError:
171    pass
172
173
174__all__ = ['Image', 'Reader', 'Writer', 'write_chunks', 'from_array']
175
176
177# The PNG signature.
178# http://www.w3.org/TR/PNG/#5PNG-file-signature
179_signature = struct.pack('8B', 137, 80, 78, 71, 13, 10, 26, 10)
180
181_adam7 = ((0, 0, 8, 8),
182          (4, 0, 8, 8),
183          (0, 4, 4, 8),
184          (2, 0, 4, 4),
185          (0, 2, 2, 4),
186          (1, 0, 2, 2),
187          (0, 1, 1, 2))
188
189def group(s, n):
190    # See http://www.python.org/doc/2.6/library/functions.html#zip
191    return zip(*[iter(s)]*n)
192
193def isarray(x):
194    """Same as ``isinstance(x, array)`` except on Python 2.2, where it
195    always returns ``False``.  This helps PyPNG work on Python 2.2.
196    """
197
198    try:
199        return isinstance(x, array)
200    except TypeError:
201        # Because on Python 2.2 array.array is not a type.
202        return False
203
204try:
205    array.tobytes
206except AttributeError:
207    try:  # see :pyver:old
208        array.tostring
209    except AttributeError:
210        def tostring(row):
211            l = len(row)
212            return struct.pack('%dB' % l, *row)
213    else:
214        def tostring(row):
215            """Convert row of bytes to string.  Expects `row` to be an
216            ``array``.
217            """
218            return row.tostring()
219else:
220    def tostring(row):
221        """ Python3 definition, array.tostring() is deprecated in Python3
222        """
223        return row.tobytes()
224
225# Conditionally convert to bytes.  Works on Python 2 and Python 3.
226try:
227    bytes('', 'ascii')
228    def strtobytes(x): return bytes(x, 'iso8859-1')
229    def bytestostr(x): return str(x, 'iso8859-1')
230except (NameError, TypeError):
231    # We get NameError when bytes() does not exist (most Python
232    # 2.x versions), and TypeError when bytes() exists but is on
233    # Python 2.x (when it is an alias for str() and takes at most
234    # one argument).
235    strtobytes = str
236    bytestostr = str
237
238def interleave_planes(ipixels, apixels, ipsize, apsize):
239    """
240    Interleave (colour) planes, e.g. RGB + A = RGBA.
241
242    Return an array of pixels consisting of the `ipsize` elements of
243    data from each pixel in `ipixels` followed by the `apsize` elements
244    of data from each pixel in `apixels`.  Conventionally `ipixels`
245    and `apixels` are byte arrays so the sizes are bytes, but it
246    actually works with any arrays of the same type.  The returned
247    array is the same type as the input arrays which should be the
248    same type as each other.
249    """
250
251    itotal = len(ipixels)
252    atotal = len(apixels)
253    newtotal = itotal + atotal
254    newpsize = ipsize + apsize
255    # Set up the output buffer
256    # See http://www.python.org/doc/2.4.4/lib/module-array.html#l2h-1356
257    out = array(ipixels.typecode)
258    # It's annoying that there is no cheap way to set the array size :-(
259    out.extend(ipixels)
260    out.extend(apixels)
261    # Interleave in the pixel data
262    for i in range(ipsize):
263        out[i:newtotal:newpsize] = ipixels[i:itotal:ipsize]
264    for i in range(apsize):
265        out[i+ipsize:newtotal:newpsize] = apixels[i:atotal:apsize]
266    return out
267
268def check_palette(palette):
269    """Check a palette argument (to the :class:`Writer` class)
270    for validity.  Returns the palette as a list if okay; raises an
271    exception otherwise.
272    """
273
274    # None is the default and is allowed.
275    if palette is None:
276        return None
277
278    p = list(palette)
279    if not (0 < len(p) <= 256):
280        raise ValueError("a palette must have between 1 and 256 entries")
281    seen_triple = False
282    for i,t in enumerate(p):
283        if len(t) not in (3,4):
284            raise ValueError(
285              "palette entry %d: entries must be 3- or 4-tuples." % i)
286        if len(t) == 3:
287            seen_triple = True
288        if seen_triple and len(t) == 4:
289            raise ValueError(
290              "palette entry %d: all 4-tuples must precede all 3-tuples" % i)
291        for x in t:
292            if int(x) != x or not(0 <= x <= 255):
293                raise ValueError(
294                  "palette entry %d: values must be integer: 0 <= x <= 255" % i)
295    return p
296
297def check_sizes(size, width, height):
298    """Check that these arguments, in supplied, are consistent.
299    Return a (width, height) pair.
300    """
301
302    if not size:
303        return width, height
304
305    if len(size) != 2:
306        raise ValueError(
307          "size argument should be a pair (width, height)")
308    if width is not None and width != size[0]:
309        raise ValueError(
310          "size[0] (%r) and width (%r) should match when both are used."
311            % (size[0], width))
312    if height is not None and height != size[1]:
313        raise ValueError(
314          "size[1] (%r) and height (%r) should match when both are used."
315            % (size[1], height))
316    return size
317
318def check_color(c, greyscale, which):
319    """Checks that a colour argument for transparent or
320    background options is the right form.  Returns the colour
321    (which, if it's a bar integer, is "corrected" to a 1-tuple).
322    """
323
324    if c is None:
325        return c
326    if greyscale:
327        try:
328            l = len(c)
329        except TypeError:
330            c = (c,)
331        if len(c) != 1:
332            raise ValueError("%s for greyscale must be 1-tuple" %
333                which)
334        if not isinteger(c[0]):
335            raise ValueError(
336                "%s colour for greyscale must be integer" % which)
337    else:
338        if not (len(c) == 3 and
339                isinteger(c[0]) and
340                isinteger(c[1]) and
341                isinteger(c[2])):
342            raise ValueError(
343                "%s colour must be a triple of integers" % which)
344    return c
345
346class Error(Exception):
347    def __str__(self):
348        return self.__class__.__name__ + ': ' + ' '.join(self.args)
349
350class FormatError(Error):
351    """Problem with input file format.  In other words, PNG file does
352    not conform to the specification in some way and is invalid.
353    """
354
355class ChunkError(FormatError):
356    pass
357
358
359class Writer:
360    """
361    PNG encoder in pure Python.
362    """
363
364    def __init__(self, width=None, height=None,
365                 size=None,
366                 greyscale=False,
367                 alpha=False,
368                 bitdepth=8,
369                 palette=None,
370                 transparent=None,
371                 background=None,
372                 gamma=None,
373                 compression=None,
374                 interlace=False,
375                 bytes_per_sample=None, # deprecated
376                 planes=None,
377                 colormap=None,
378                 maxval=None,
379                 chunk_limit=2**20):
380        """
381        Create a PNG encoder object.
382
383        Arguments:
384
385        width, height
386          Image size in pixels, as two separate arguments.
387        size
388          Image size (w,h) in pixels, as single argument.
389        greyscale
390          Input data is greyscale, not RGB.
391        alpha
392          Input data has alpha channel (RGBA or LA).
393        bitdepth
394          Bit depth: from 1 to 16.
395        palette
396          Create a palette for a colour mapped image (colour type 3).
397        transparent
398          Specify a transparent colour (create a ``tRNS`` chunk).
399        background
400          Specify a default background colour (create a ``bKGD`` chunk).
401        gamma
402          Specify a gamma value (create a ``gAMA`` chunk).
403        compression
404          zlib compression level: 0 (none) to 9 (more compressed);
405          default: -1 or None.
406        interlace
407          Create an interlaced image.
408        chunk_limit
409          Write multiple ``IDAT`` chunks to save memory.
410
411        The image size (in pixels) can be specified either by using the
412        `width` and `height` arguments, or with the single `size`
413        argument.  If `size` is used it should be a pair (*width*,
414        *height*).
415
416        `greyscale` and `alpha` are booleans that specify whether
417        an image is greyscale (or colour), and whether it has an
418        alpha channel (or not).
419
420        `bitdepth` specifies the bit depth of the source pixel values.
421        Each source pixel value must be an integer between 0 and
422        ``2**bitdepth-1``.  For example, 8-bit images have values
423        between 0 and 255.  PNG only stores images with bit depths of
424        1,2,4,8, or 16.  When `bitdepth` is not one of these values,
425        the next highest valid bit depth is selected, and an ``sBIT``
426        (significant bits) chunk is generated that specifies the
427        original precision of the source image.  In this case the
428        supplied pixel values will be rescaled to fit the range of
429        the selected bit depth.
430
431        The details of which bit depth / colour model combinations the
432        PNG file format supports directly, are somewhat arcane
433        (refer to the PNG specification for full details).  Briefly:
434        "small" bit depths (1,2,4) are only allowed with greyscale and
435        colour mapped images; colour mapped images cannot have bit depth
436        16.
437
438        For colour mapped images (in other words, when the `palette`
439        argument is specified) the `bitdepth` argument must match one of
440        the valid PNG bit depths: 1, 2, 4, or 8.  (It is valid to have a
441        PNG image with a palette and an ``sBIT`` chunk, but the meaning
442        is slightly different; it would be awkward to press the
443        `bitdepth` argument into service for this.)
444
445        The `palette` option, when specified, causes a colour mapped
446        image to be created: the PNG colour type is set to 3; greyscale
447        must not be set; alpha must not be set; transparent must not be
448        set; the bit depth must be 1,2,4, or 8.  When a colour mapped
449        image is created, the pixel values are palette indexes and
450        the `bitdepth` argument specifies the size of these indexes
451        (not the size of the colour values in the palette).
452
453        The palette argument value should be a sequence of 3- or
454        4-tuples.  3-tuples specify RGB palette entries; 4-tuples
455        specify RGBA palette entries.  If both 4-tuples and 3-tuples
456        appear in the sequence then all the 4-tuples must come
457        before all the 3-tuples.  A ``PLTE`` chunk is created; if there
458        are 4-tuples then a ``tRNS`` chunk is created as well.  The
459        ``PLTE`` chunk will contain all the RGB triples in the same
460        sequence; the ``tRNS`` chunk will contain the alpha channel for
461        all the 4-tuples, in the same sequence.  Palette entries
462        are always 8-bit.
463
464        If specified, the `transparent` and `background` parameters must
465        be a tuple with three integer values for red, green, blue, or
466        a simple integer (or singleton tuple) for a greyscale image.
467
468        If specified, the `gamma` parameter must be a positive number
469        (generally, a float).  A ``gAMA`` chunk will be created.
470        Note that this will not change the values of the pixels as
471        they appear in the PNG file, they are assumed to have already
472        been converted appropriately for the gamma specified.
473
474        The `compression` argument specifies the compression level to
475        be used by the ``zlib`` module.  Values from 1 to 9 specify
476        compression, with 9 being "more compressed" (usually smaller
477        and slower, but it doesn't always work out that way).  0 means
478        no compression.  -1 and ``None`` both mean that the default
479        level of compession will be picked by the ``zlib`` module
480        (which is generally acceptable).
481
482        If `interlace` is true then an interlaced image is created
483        (using PNG's so far only interace method, *Adam7*).  This does
484        not affect how the pixels should be presented to the encoder,
485        rather it changes how they are arranged into the PNG file.
486        On slow connexions interlaced images can be partially decoded
487        by the browser to give a rough view of the image that is
488        successively refined as more image data appears.
489
490        .. note ::
491
492          Enabling the `interlace` option requires the entire image
493          to be processed in working memory.
494
495        `chunk_limit` is used to limit the amount of memory used whilst
496        compressing the image.  In order to avoid using large amounts of
497        memory, multiple ``IDAT`` chunks may be created.
498        """
499
500        # At the moment the `planes` argument is ignored;
501        # its purpose is to act as a dummy so that
502        # ``Writer(x, y, **info)`` works, where `info` is a dictionary
503        # returned by Reader.read and friends.
504        # Ditto for `colormap`.
505
506        width, height = check_sizes(size, width, height)
507        del size
508
509        if width <= 0 or height <= 0:
510            raise ValueError("width and height must be greater than zero")
511        if not isinteger(width) or not isinteger(height):
512            raise ValueError("width and height must be integers")
513        # http://www.w3.org/TR/PNG/#7Integers-and-byte-order
514        if width > 2**32-1 or height > 2**32-1:
515            raise ValueError("width and height cannot exceed 2**32-1")
516
517        if alpha and transparent is not None:
518            raise ValueError(
519                "transparent colour not allowed with alpha channel")
520
521        if bytes_per_sample is not None:
522            warnings.warn('please use bitdepth instead of bytes_per_sample',
523                          DeprecationWarning)
524            if bytes_per_sample not in (0.125, 0.25, 0.5, 1, 2):
525                raise ValueError(
526                    "bytes per sample must be .125, .25, .5, 1, or 2")
527            bitdepth = int(8*bytes_per_sample)
528        del bytes_per_sample
529        if not isinteger(bitdepth) or bitdepth < 1 or 16 < bitdepth:
530            raise ValueError("bitdepth (%r) must be a postive integer <= 16" %
531              bitdepth)
532
533        self.rescale = None
534        if palette:
535            if bitdepth not in (1,2,4,8):
536                raise ValueError("with palette, bitdepth must be 1, 2, 4, or 8")
537            if transparent is not None:
538                raise ValueError("transparent and palette not compatible")
539            if alpha:
540                raise ValueError("alpha and palette not compatible")
541            if greyscale:
542                raise ValueError("greyscale and palette not compatible")
543        else:
544            # No palette, check for sBIT chunk generation.
545            if alpha or not greyscale:
546                if bitdepth not in (8,16):
547                    targetbitdepth = (8,16)[bitdepth > 8]
548                    self.rescale = (bitdepth, targetbitdepth)
549                    bitdepth = targetbitdepth
550                    del targetbitdepth
551            else:
552                assert greyscale
553                assert not alpha
554                if bitdepth not in (1,2,4,8,16):
555                    if bitdepth > 8:
556                        targetbitdepth = 16
557                    elif bitdepth == 3:
558                        targetbitdepth = 4
559                    else:
560                        assert bitdepth in (5,6,7)
561                        targetbitdepth = 8
562                    self.rescale = (bitdepth, targetbitdepth)
563                    bitdepth = targetbitdepth
564                    del targetbitdepth
565
566        if bitdepth < 8 and (alpha or not greyscale and not palette):
567            raise ValueError(
568              "bitdepth < 8 only permitted with greyscale or palette")
569        if bitdepth > 8 and palette:
570            raise ValueError(
571                "bit depth must be 8 or less for images with palette")
572
573        transparent = check_color(transparent, greyscale, 'transparent')
574        background = check_color(background, greyscale, 'background')
575
576        # It's important that the true boolean values (greyscale, alpha,
577        # colormap, interlace) are converted to bool because Iverson's
578        # convention is relied upon later on.
579        self.width = width
580        self.height = height
581        self.transparent = transparent
582        self.background = background
583        self.gamma = gamma
584        self.greyscale = bool(greyscale)
585        self.alpha = bool(alpha)
586        self.colormap = bool(palette)
587        self.bitdepth = int(bitdepth)
588        self.compression = compression
589        self.chunk_limit = chunk_limit
590        self.interlace = bool(interlace)
591        self.palette = check_palette(palette)
592
593        self.color_type = 4*self.alpha + 2*(not greyscale) + 1*self.colormap
594        assert self.color_type in (0,2,3,4,6)
595
596        self.color_planes = (3,1)[self.greyscale or self.colormap]
597        self.planes = self.color_planes + self.alpha
598        # :todo: fix for bitdepth < 8
599        self.psize = (self.bitdepth/8) * self.planes
600
601    def make_palette(self):
602        """Create the byte sequences for a ``PLTE`` and if necessary a
603        ``tRNS`` chunk.  Returned as a pair (*p*, *t*).  *t* will be
604        ``None`` if no ``tRNS`` chunk is necessary.
605        """
606
607        p = array('B')
608        t = array('B')
609
610        for x in self.palette:
611            p.extend(x[0:3])
612            if len(x) > 3:
613                t.append(x[3])
614        p = tostring(p)
615        t = tostring(t)
616        if t:
617            return p,t
618        return p,None
619
620    def write(self, outfile, rows):
621        """Write a PNG image to the output file.  `rows` should be
622        an iterable that yields each row in boxed row flat pixel
623        format.  The rows should be the rows of the original image,
624        so there should be ``self.height`` rows of ``self.width *
625        self.planes`` values.  If `interlace` is specified (when
626        creating the instance), then an interlaced PNG file will
627        be written.  Supply the rows in the normal image order;
628        the interlacing is carried out internally.
629
630        .. note ::
631
632          Interlacing will require the entire image to be in working
633          memory.
634        """
635
636        if self.interlace:
637            fmt = 'BH'[self.bitdepth > 8]
638            a = array(fmt, itertools.chain(*rows))
639            return self.write_array(outfile, a)
640        else:
641            nrows = self.write_passes(outfile, rows)
642            if nrows != self.height:
643                raise ValueError(
644                  "rows supplied (%d) does not match height (%d)" %
645                  (nrows, self.height))
646
647    def write_passes(self, outfile, rows, packed=False):
648        """
649        Write a PNG image to the output file.
650
651        Most users are expected to find the :meth:`write` or
652        :meth:`write_array` method more convenient.
653       
654        The rows should be given to this method in the order that
655        they appear in the output file.  For straightlaced images,
656        this is the usual top to bottom ordering, but for interlaced
657        images the rows should have already been interlaced before
658        passing them to this function.
659
660        `rows` should be an iterable that yields each row.  When
661        `packed` is ``False`` the rows should be in boxed row flat pixel
662        format; when `packed` is ``True`` each row should be a packed
663        sequence of bytes.
664        """
665
666        # http://www.w3.org/TR/PNG/#5PNG-file-signature
667        outfile.write(_signature)
668
669        # http://www.w3.org/TR/PNG/#11IHDR
670        write_chunk(outfile, 'IHDR',
671                    struct.pack("!2I5B", self.width, self.height,
672                                self.bitdepth, self.color_type,
673                                0, 0, self.interlace))
674
675        # See :chunk:order
676        # http://www.w3.org/TR/PNG/#11gAMA
677        if self.gamma is not None:
678            write_chunk(outfile, 'gAMA',
679                        struct.pack("!L", int(round(self.gamma*1e5))))
680
681        # See :chunk:order
682        # http://www.w3.org/TR/PNG/#11sBIT
683        if self.rescale:
684            write_chunk(outfile, 'sBIT',
685                struct.pack('%dB' % self.planes,
686                            *[self.rescale[0]]*self.planes))
687       
688        # :chunk:order: Without a palette (PLTE chunk), ordering is
689        # relatively relaxed.  With one, gAMA chunk must precede PLTE
690        # chunk which must precede tRNS and bKGD.
691        # See http://www.w3.org/TR/PNG/#5ChunkOrdering
692        if self.palette:
693            p,t = self.make_palette()
694            write_chunk(outfile, 'PLTE', p)
695            if t:
696                # tRNS chunk is optional. Only needed if palette entries
697                # have alpha.
698                write_chunk(outfile, 'tRNS', t)
699
700        # http://www.w3.org/TR/PNG/#11tRNS
701        if self.transparent is not None:
702            if self.greyscale:
703                write_chunk(outfile, 'tRNS',
704                            struct.pack("!1H", *self.transparent))
705            else:
706                write_chunk(outfile, 'tRNS',
707                            struct.pack("!3H", *self.transparent))
708
709        # http://www.w3.org/TR/PNG/#11bKGD
710        if self.background is not None:
711            if self.greyscale:
712                write_chunk(outfile, 'bKGD',
713                            struct.pack("!1H", *self.background))
714            else:
715                write_chunk(outfile, 'bKGD',
716                            struct.pack("!3H", *self.background))
717
718        # http://www.w3.org/TR/PNG/#11IDAT
719        if self.compression is not None:
720            compressor = zlib.compressobj(self.compression)
721        else:
722            compressor = zlib.compressobj()
723
724        # Choose an extend function based on the bitdepth.  The extend
725        # function packs/decomposes the pixel values into bytes and
726        # stuffs them onto the data array.
727        data = array('B')
728        if self.bitdepth == 8 or packed:
729            extend = data.extend
730        elif self.bitdepth == 16:
731            # Decompose into bytes
732            def extend(sl):
733                fmt = '!%dH' % len(sl)
734                data.extend(array('B', struct.pack(fmt, *sl)))
735        else:
736            # Pack into bytes
737            assert self.bitdepth < 8
738            # samples per byte
739            spb = int(8/self.bitdepth)
740            def extend(sl):
741                a = array('B', sl)
742                # Adding padding bytes so we can group into a whole
743                # number of spb-tuples.
744                l = float(len(a))
745                extra = math.ceil(l / float(spb))*spb - l
746                a.extend([0]*int(extra))
747                # Pack into bytes
748                l = group(a, spb)
749                l = map(lambda e: reduce(lambda x,y:
750                                           (x << self.bitdepth) + y, e), l)
751                data.extend(l)
752        if self.rescale:
753            oldextend = extend
754            factor = \
755              float(2**self.rescale[1]-1) / float(2**self.rescale[0]-1)
756            def extend(sl):
757                oldextend(map(lambda x: int(round(factor*x)), sl))
758
759        # Build the first row, testing mostly to see if we need to
760        # changed the extend function to cope with NumPy integer types
761        # (they cause our ordinary definition of extend to fail, so we
762        # wrap it).  See
763        # http://code.google.com/p/pypng/issues/detail?id=44
764        enumrows = enumerate(rows)
765        del rows
766
767        # First row's filter type.
768        data.append(0)
769        # :todo: Certain exceptions in the call to ``.next()`` or the
770        # following try would indicate no row data supplied.
771        # Should catch.
772        i,row = enumrows.next()
773        try:
774            # If this fails...
775            extend(row)
776        except:
777            # ... try a version that converts the values to int first.
778            # Not only does this work for the (slightly broken) NumPy
779            # types, there are probably lots of other, unknown, "nearly"
780            # int types it works for.
781            def wrapmapint(f):
782                return lambda sl: f(map(int, sl))
783            extend = wrapmapint(extend)
784            del wrapmapint
785            extend(row)
786
787        for i,row in enumrows:
788            # Add "None" filter type.  Currently, it's essential that
789            # this filter type be used for every scanline as we do not
790            # mark the first row of a reduced pass image; that means we
791            # could accidentally compute the wrong filtered scanline if
792            # we used "up", "average", or "paeth" on such a line.
793            data.append(0)
794            extend(row)
795            if len(data) > self.chunk_limit:
796                compressed = compressor.compress(tostring(data))
797                if len(compressed):
798                    write_chunk(outfile, 'IDAT', compressed)
799                # Because of our very witty definition of ``extend``,
800                # above, we must re-use the same ``data`` object.  Hence
801                # we use ``del`` to empty this one, rather than create a
802                # fresh one (which would be my natural FP instinct).
803                del data[:]
804        if len(data):
805            compressed = compressor.compress(tostring(data))
806        else:
807            compressed = strtobytes('')
808        flushed = compressor.flush()
809        if len(compressed) or len(flushed):
810            write_chunk(outfile, 'IDAT', compressed + flushed)
811        # http://www.w3.org/TR/PNG/#11IEND
812        write_chunk(outfile, 'IEND')
813        return i+1
814
815    def write_array(self, outfile, pixels):
816        """
817        Write an array in flat row flat pixel format as a PNG file on
818        the output file.  See also :meth:`write` method.
819        """
820
821        if self.interlace:
822            self.write_passes(outfile, self.array_scanlines_interlace(pixels))
823        else:
824            self.write_passes(outfile, self.array_scanlines(pixels))
825
826    def write_packed(self, outfile, rows):
827        """
828        Write PNG file to `outfile`.  The pixel data comes from `rows`
829        which should be in boxed row packed format.  Each row should be
830        a sequence of packed bytes.
831
832        Technically, this method does work for interlaced images but it
833        is best avoided.  For interlaced images, the rows should be
834        presented in the order that they appear in the file.
835
836        This method should not be used when the source image bit depth
837        is not one naturally supported by PNG; the bit depth should be
838        1, 2, 4, 8, or 16.
839        """
840
841        if self.rescale:
842            raise Error("write_packed method not suitable for bit depth %d" %
843              self.rescale[0])
844        return self.write_passes(outfile, rows, packed=True)
845
846    def convert_pnm(self, infile, outfile):
847        """
848        Convert a PNM file containing raw pixel data into a PNG file
849        with the parameters set in the writer object.  Works for
850        (binary) PGM, PPM, and PAM formats.
851        """
852
853        if self.interlace:
854            pixels = array('B')
855            pixels.fromfile(infile,
856                            (self.bitdepth/8) * self.color_planes *
857                            self.width * self.height)
858            self.write_passes(outfile, self.array_scanlines_interlace(pixels))
859        else:
860            self.write_passes(outfile, self.file_scanlines(infile))
861
862    def convert_ppm_and_pgm(self, ppmfile, pgmfile, outfile):
863        """
864        Convert a PPM and PGM file containing raw pixel data into a
865        PNG outfile with the parameters set in the writer object.
866        """
867        pixels = array('B')
868        pixels.fromfile(ppmfile,
869                        (self.bitdepth/8) * self.color_planes *
870                        self.width * self.height)
871        apixels = array('B')
872        apixels.fromfile(pgmfile,
873                         (self.bitdepth/8) *
874                         self.width * self.height)
875        pixels = interleave_planes(pixels, apixels,
876                                   (self.bitdepth/8) * self.color_planes,
877                                   (self.bitdepth/8))
878        if self.interlace:
879            self.write_passes(outfile, self.array_scanlines_interlace(pixels))
880        else:
881            self.write_passes(outfile, self.array_scanlines(pixels))
882
883    def file_scanlines(self, infile):
884        """
885        Generates boxed rows in flat pixel format, from the input file
886        `infile`.  It assumes that the input file is in a "Netpbm-like"
887        binary format, and is positioned at the beginning of the first
888        pixel.  The number of pixels to read is taken from the image
889        dimensions (`width`, `height`, `planes`) and the number of bytes
890        per value is implied by the image `bitdepth`.
891        """
892
893        # Values per row
894        vpr = self.width * self.planes
895        row_bytes = vpr
896        if self.bitdepth > 8:
897            assert self.bitdepth == 16
898            row_bytes *= 2
899            fmt = '>%dH' % vpr
900            def line():
901                return array('H', struct.unpack(fmt, infile.read(row_bytes)))
902        else:
903            def line():
904                scanline = array('B', infile.read(row_bytes))
905                return scanline
906        for y in range(self.height):
907            yield line()
908
909    def array_scanlines(self, pixels):
910        """
911        Generates boxed rows (flat pixels) from flat rows (flat pixels)
912        in an array.
913        """
914
915        # Values per row
916        vpr = self.width * self.planes
917        stop = 0
918        for y in range(self.height):
919            start = stop
920            stop = start + vpr
921            yield pixels[start:stop]
922
923    def array_scanlines_interlace(self, pixels):
924        """
925        Generator for interlaced scanlines from an array.  `pixels` is
926        the full source image in flat row flat pixel format.  The
927        generator yields each scanline of the reduced passes in turn, in
928        boxed row flat pixel format.
929        """
930
931        # http://www.w3.org/TR/PNG/#8InterlaceMethods
932        # Array type.
933        fmt = 'BH'[self.bitdepth > 8]
934        # Value per row
935        vpr = self.width * self.planes
936        for xstart, ystart, xstep, ystep in _adam7:
937            if xstart >= self.width:
938                continue
939            # Pixels per row (of reduced image)
940            ppr = int(math.ceil((self.width-xstart)/float(xstep)))
941            # number of values in reduced image row.
942            row_len = ppr*self.planes
943            for y in range(ystart, self.height, ystep):
944                if xstep == 1:
945                    offset = y * vpr
946                    yield pixels[offset:offset+vpr]
947                else:
948                    row = array(fmt)
949                    # There's no easier way to set the length of an array
950                    row.extend(pixels[0:row_len])
951                    offset = y * vpr + xstart * self.planes
952                    end_offset = (y+1) * vpr
953                    skip = self.planes * xstep
954                    for i in range(self.planes):
955                        row[i::self.planes] = \
956                            pixels[offset+i:end_offset:skip]
957                    yield row
958
959def write_chunk(outfile, tag, data=strtobytes('')):
960    """
961    Write a PNG chunk to the output file, including length and
962    checksum.
963    """
964
965    # http://www.w3.org/TR/PNG/#5Chunk-layout
966    outfile.write(struct.pack("!I", len(data)))
967    tag = strtobytes(tag)
968    outfile.write(tag)
969    outfile.write(data)
970    checksum = zlib.crc32(tag)
971    checksum = zlib.crc32(data, checksum)
972    checksum &= 2**32-1
973    outfile.write(struct.pack("!I", checksum))
974
975def write_chunks(out, chunks):
976    """Create a PNG file by writing out the chunks."""
977
978    out.write(_signature)
979    for chunk in chunks:
980        write_chunk(out, *chunk)
981
982def filter_scanline(type, line, fo, prev=None):
983    """Apply a scanline filter to a scanline.  `type` specifies the
984    filter type (0 to 4); `line` specifies the current (unfiltered)
985    scanline as a sequence of bytes; `prev` specifies the previous
986    (unfiltered) scanline as a sequence of bytes. `fo` specifies the
987    filter offset; normally this is size of a pixel in bytes (the number
988    of bytes per sample times the number of channels), but when this is
989    < 1 (for bit depths < 8) then the filter offset is 1.
990    """
991
992    assert 0 <= type < 5
993
994    # The output array.  Which, pathetically, we extend one-byte at a
995    # time (fortunately this is linear).
996    out = array('B', [type])
997
998    def sub():
999        ai = -fo
1000        for x in line:
1001            if ai >= 0:
1002                x = (x - line[ai]) & 0xff
1003            out.append(x)
1004            ai += 1
1005    def up():
1006        for i,x in enumerate(line):
1007            x = (x - prev[i]) & 0xff
1008            out.append(x)
1009    def average():
1010        ai = -fo
1011        for i,x in enumerate(line):
1012            if ai >= 0:
1013                x = (x - ((line[ai] + prev[i]) >> 1)) & 0xff
1014            else:
1015                x = (x - (prev[i] >> 1)) & 0xff
1016            out.append(x)
1017            ai += 1
1018    def paeth():
1019        # http://www.w3.org/TR/PNG/#9Filter-type-4-Paeth
1020        ai = -fo # also used for ci
1021        for i,x in enumerate(line):
1022            a = 0
1023            b = prev[i]
1024            c = 0
1025
1026            if ai >= 0:
1027                a = line[ai]
1028                c = prev[ai]
1029            p = a + b - c
1030            pa = abs(p - a)
1031            pb = abs(p - b)
1032            pc = abs(p - c)
1033            if pa <= pb and pa <= pc: Pr = a
1034            elif pb <= pc: Pr = b
1035            else: Pr = c
1036
1037            x = (x - Pr) & 0xff
1038            out.append(x)
1039            ai += 1
1040
1041    if not prev:
1042        # We're on the first line.  Some of the filters can be reduced
1043        # to simpler cases which makes handling the line "off the top"
1044        # of the image simpler.  "up" becomes "none"; "paeth" becomes
1045        # "left" (non-trivial, but true). "average" needs to be handled
1046        # specially.
1047        if type == 2: # "up"
1048            type = 0
1049        elif type == 3:
1050            prev = [0]*len(line)
1051        elif type == 4: # "paeth"
1052            type = 1
1053    if type == 0:
1054        out.extend(line)
1055    elif type == 1:
1056        sub()
1057    elif type == 2:
1058        up()
1059    elif type == 3:
1060        average()
1061    else: # type == 4
1062        paeth()
1063    return out
1064
1065
1066def from_array(a, mode=None, info={}):
1067    """Create a PNG :class:`Image` object from a 2- or 3-dimensional
1068    array.  One application of this function is easy PIL-style saving:
1069    ``png.from_array(pixels, 'L').save('foo.png')``.
1070
1071    .. note :
1072
1073      The use of the term *3-dimensional* is for marketing purposes
1074      only.  It doesn't actually work.  Please bear with us.  Meanwhile
1075      enjoy the complimentary snacks (on request) and please use a
1076      2-dimensional array.
1077   
1078    Unless they are specified using the *info* parameter, the PNG's
1079    height and width are taken from the array size.  For a 3 dimensional
1080    array the first axis is the height; the second axis is the width;
1081    and the third axis is the channel number.  Thus an RGB image that is
1082    16 pixels high and 8 wide will use an array that is 16x8x3.  For 2
1083    dimensional arrays the first axis is the height, but the second axis
1084    is ``width*channels``, so an RGB image that is 16 pixels high and 8
1085    wide will use a 2-dimensional array that is 16x24 (each row will be
1086    8*3==24 sample values).
1087
1088    *mode* is a string that specifies the image colour format in a
1089    PIL-style mode.  It can be:
1090
1091    ``'L'``
1092      greyscale (1 channel)
1093    ``'LA'``
1094      greyscale with alpha (2 channel)
1095    ``'RGB'``
1096      colour image (3 channel)
1097    ``'RGBA'``
1098      colour image with alpha (4 channel)
1099
1100    The mode string can also specify the bit depth (overriding how this
1101    function normally derives the bit depth, see below).  Appending
1102    ``';16'`` to the mode will cause the PNG to be 16 bits per channel;
1103    any decimal from 1 to 16 can be used to specify the bit depth.
1104
1105    When a 2-dimensional array is used *mode* determines how many
1106    channels the image has, and so allows the width to be derived from
1107    the second array dimension.
1108
1109    The array is expected to be a ``numpy`` array, but it can be any
1110    suitable Python sequence.  For example, a list of lists can be used:
1111    ``png.from_array([[0, 255, 0], [255, 0, 255]], 'L')``.  The exact
1112    rules are: ``len(a)`` gives the first dimension, height;
1113    ``len(a[0])`` gives the second dimension; ``len(a[0][0])`` gives the
1114    third dimension, unless an exception is raised in which case a
1115    2-dimensional array is assumed.  It's slightly more complicated than
1116    that because an iterator of rows can be used, and it all still
1117    works.  Using an iterator allows data to be streamed efficiently.
1118
1119    The bit depth of the PNG is normally taken from the array element's
1120    datatype (but if *mode* specifies a bitdepth then that is used
1121    instead).  The array element's datatype is determined in a way which
1122    is supposed to work both for ``numpy`` arrays and for Python
1123    ``array.array`` objects.  A 1 byte datatype will give a bit depth of
1124    8, a 2 byte datatype will give a bit depth of 16.  If the datatype
1125    does not have an implicit size, for example it is a plain Python
1126    list of lists, as above, then a default of 8 is used.
1127
1128    The *info* parameter is a dictionary that can be used to specify
1129    metadata (in the same style as the arguments to the
1130    :class:``png.Writer`` class).  For this function the keys that are
1131    useful are:
1132   
1133    height
1134      overrides the height derived from the array dimensions and allows
1135      *a* to be an iterable.
1136    width
1137      overrides the width derived from the array dimensions.
1138    bitdepth
1139      overrides the bit depth derived from the element datatype (but
1140      must match *mode* if that also specifies a bit depth).
1141
1142    Generally anything specified in the
1143    *info* dictionary will override any implicit choices that this
1144    function would otherwise make, but must match any explicit ones.
1145    For example, if the *info* dictionary has a ``greyscale`` key then
1146    this must be true when mode is ``'L'`` or ``'LA'`` and false when
1147    mode is ``'RGB'`` or ``'RGBA'``.
1148    """
1149
1150    # We abuse the *info* parameter by modifying it.  Take a copy here.
1151    # (Also typechecks *info* to some extent).
1152    info = dict(info)
1153
1154    # Syntax check mode string.
1155    bitdepth = None
1156    try:
1157        # Assign the 'L' or 'RGBA' part to `gotmode`.
1158        if mode.startswith('L'):
1159            gotmode = 'L'
1160            mode = mode[1:]
1161        elif mode.startswith('RGB'):
1162            gotmode = 'RGB'
1163            mode = mode[3:]
1164        else:
1165            raise Error()
1166        if mode.startswith('A'):
1167            gotmode += 'A'
1168            mode = mode[1:]
1169
1170        # Skip any optional ';'
1171        while mode.startswith(';'):
1172            mode = mode[1:]
1173
1174        # Parse optional bitdepth
1175        if mode:
1176            try:
1177                bitdepth = int(mode)
1178            except (TypeError, ValueError):
1179                raise Error()
1180    except Error:
1181        raise Error("mode string should be 'RGB' or 'L;16' or similar.")
1182    mode = gotmode
1183
1184    # Get bitdepth from *mode* if possible.
1185    if bitdepth:
1186        if info.get('bitdepth') and bitdepth != info['bitdepth']:
1187            raise Error("mode bitdepth (%d) should match info bitdepth (%d)." %
1188              (bitdepth, info['bitdepth']))
1189        info['bitdepth'] = bitdepth
1190
1191    # Fill in and/or check entries in *info*.
1192    # Dimensions.
1193    if 'size' in info:
1194        # Check width, height, size all match where used.
1195        for dimension,axis in [('width', 0), ('height', 1)]:
1196            if dimension in info:
1197                if info[dimension] != info['size'][axis]:
1198                    raise Error(
1199                      "info[%r] should match info['size'][%r]." %
1200                      (dimension, axis))
1201        info['width'],info['height'] = info['size']
1202    if 'height' not in info:
1203        try:
1204            l = len(a)
1205        except TypeError:
1206            raise Error(
1207              "len(a) does not work, supply info['height'] instead.")
1208        info['height'] = l
1209    # Colour format.
1210    if 'greyscale' in info:
1211        if bool(info['greyscale']) != ('L' in mode):
1212            raise Error("info['greyscale'] should match mode.")
1213    info['greyscale'] = 'L' in mode
1214    if 'alpha' in info:
1215        if bool(info['alpha']) != ('A' in mode):
1216            raise Error("info['alpha'] should match mode.")
1217    info['alpha'] = 'A' in mode
1218
1219    planes = len(mode)
1220    if 'planes' in info:
1221        if info['planes'] != planes:
1222            raise Error("info['planes'] should match mode.")
1223
1224    # In order to work out whether we the array is 2D or 3D we need its
1225    # first row, which requires that we take a copy of its iterator.
1226    # We may also need the first row to derive width and bitdepth.
1227    a,t = itertools.tee(a)
1228    row = t.next()
1229    del t
1230    try:
1231        row[0][0]
1232        threed = True
1233        testelement = row[0]
1234    except (IndexError, TypeError):
1235        threed = False
1236        testelement = row
1237    if 'width' not in info:
1238        if threed:
1239            width = len(row)
1240        else:
1241            width = len(row) // planes
1242        info['width'] = width
1243
1244    # Not implemented yet
1245    assert not threed
1246
1247    if 'bitdepth' not in info:
1248        try:
1249            dtype = testelement.dtype
1250            # goto the "else:" clause.  Sorry.
1251        except AttributeError:
1252            try:
1253                # Try a Python array.array.
1254                bitdepth = 8 * testelement.itemsize
1255            except AttributeError:
1256                # We can't determine it from the array element's
1257                # datatype, use a default of 8.
1258                bitdepth = 8
1259        else:
1260            # If we got here without exception, we now assume that
1261            # the array is a numpy array.
1262            if dtype.kind == 'b':
1263                bitdepth = 1
1264            else:
1265                bitdepth = 8 * dtype.itemsize
1266        info['bitdepth'] = bitdepth
1267
1268    for thing in 'width height bitdepth greyscale alpha'.split():
1269        assert thing in info
1270    return Image(a, info)
1271
1272# So that refugee's from PIL feel more at home.  Not documented.
1273fromarray = from_array
1274
1275class Image:
1276    """A PNG image.  You can create an :class:`Image` object from
1277    an array of pixels by calling :meth:`png.from_array`.  It can be
1278    saved to disk with the :meth:`save` method.
1279    """
1280
1281    def __init__(self, rows, info):
1282        """
1283        .. note ::
1284       
1285          The constructor is not public.  Please do not call it.
1286        """
1287       
1288        self.rows = rows
1289        self.info = info
1290
1291    def save(self, file):
1292        """Save the image to *file*.  If *file* looks like an open file
1293        descriptor then it is used, otherwise it is treated as a
1294        filename and a fresh file is opened.
1295
1296        In general, you can only call this method once; after it has
1297        been called the first time and the PNG image has been saved, the
1298        source data will have been streamed, and cannot be streamed
1299        again.
1300        """
1301
1302        w = Writer(**self.info)
1303
1304        try:
1305            file.write
1306            def close(): pass
1307        except AttributeError:
1308            file = open(file, 'wb')
1309            def close(): file.close()
1310
1311        try:
1312            w.write(file, self.rows)
1313        finally:
1314            close()
1315
1316class _readable:
1317    """
1318    A simple file-like interface for strings and arrays.
1319    """
1320
1321    def __init__(self, buf):
1322        self.buf = buf
1323        self.offset = 0
1324
1325    def read(self, n):
1326        r = self.buf[self.offset:self.offset+n]
1327        if isarray(r):
1328            r = r.tostring()
1329        self.offset += n
1330        return r
1331
1332
1333class Reader:
1334    """
1335    PNG decoder in pure Python.
1336    """
1337
1338    def __init__(self, _guess=None, **kw):
1339        """
1340        Create a PNG decoder object.
1341
1342        The constructor expects exactly one keyword argument. If you
1343        supply a positional argument instead, it will guess the input
1344        type. You can choose among the following keyword arguments:
1345
1346        filename
1347          Name of input file (a PNG file).
1348        file
1349          A file-like object (object with a read() method).
1350        bytes
1351          ``array`` or ``string`` with PNG data.
1352
1353        """
1354        if ((_guess is not None and len(kw) != 0) or
1355            (_guess is None and len(kw) != 1)):
1356            raise TypeError("Reader() takes exactly 1 argument")
1357
1358        # Will be the first 8 bytes, later on.  See validate_signature.
1359        self.signature = None
1360        self.transparent = None
1361        # A pair of (len,type) if a chunk has been read but its data and
1362        # checksum have not (in other words the file position is just
1363        # past the 4 bytes that specify the chunk type).  See preamble
1364        # method for how this is used.
1365        self.atchunk = None
1366
1367        if _guess is not None:
1368            if isarray(_guess):
1369                kw["bytes"] = _guess
1370            elif isinstance(_guess, str):
1371                kw["filename"] = _guess
1372            elif hasattr(_guess, 'read'):
1373                kw["file"] = _guess
1374
1375        if "filename" in kw:
1376            self.file = open(kw["filename"], "rb")
1377        elif "file" in kw:
1378            self.file = kw["file"]
1379        elif "bytes" in kw:
1380            self.file = _readable(kw["bytes"])
1381        else:
1382            raise TypeError("expecting filename, file or bytes array")
1383
1384
1385    def chunk(self, seek=None, lenient=False):
1386        """
1387        Read the next PNG chunk from the input file; returns a
1388        (*type*,*data*) tuple.  *type* is the chunk's type as a string
1389        (all PNG chunk types are 4 characters long).  *data* is the
1390        chunk's data content, as a string.
1391
1392        If the optional `seek` argument is
1393        specified then it will keep reading chunks until it either runs
1394        out of file or finds the type specified by the argument.  Note
1395        that in general the order of chunks in PNGs is unspecified, so
1396        using `seek` can cause you to miss chunks.
1397
1398        If the optional `lenient` argument evaluates to True,
1399        checksum failures will raise warnings rather than exceptions.
1400        """
1401
1402        self.validate_signature()
1403
1404        while True:
1405            # http://www.w3.org/TR/PNG/#5Chunk-layout
1406            if not self.atchunk:
1407                self.atchunk = self.chunklentype()
1408            length,type = self.atchunk
1409            self.atchunk = None
1410            data = self.file.read(length)
1411            if len(data) != length:
1412                raise ChunkError('Chunk %s too short for required %i octets.'
1413                  % (type, length))
1414            checksum = self.file.read(4)
1415            if len(checksum) != 4:
1416                raise ValueError('Chunk %s too short for checksum.', tag)
1417            if seek and type != seek:
1418                continue
1419            verify = zlib.crc32(strtobytes(type))
1420            verify = zlib.crc32(data, verify)
1421            # Whether the output from zlib.crc32 is signed or not varies
1422            # according to hideous implementation details, see
1423            # http://bugs.python.org/issue1202 .
1424            # We coerce it to be positive here (in a way which works on
1425            # Python 2.3 and older).
1426            verify &= 2**32 - 1
1427            verify = struct.pack('!I', verify)
1428            if checksum != verify:
1429                (a, ) = struct.unpack('!I', checksum)
1430                (b, ) = struct.unpack('!I', verify)
1431                message = "Checksum error in %s chunk: 0x%08X != 0x%08X." % (type, a, b)
1432                if lenient:
1433                    warnings.warn(message, RuntimeWarning)
1434                else:
1435                    raise ChunkError(message)
1436            return type, data
1437
1438    def chunks(self):
1439        """Return an iterator that will yield each chunk as a
1440        (*chunktype*, *content*) pair.
1441        """
1442
1443        while True:
1444            t,v = self.chunk()
1445            yield t,v
1446            if t == 'IEND':
1447                break
1448
1449    def undo_filter(self, filter_type, scanline, previous):
1450        """Undo the filter for a scanline.  `scanline` is a sequence of
1451        bytes that does not include the initial filter type byte.
1452        `previous` is decoded previous scanline (for straightlaced
1453        images this is the previous pixel row, but for interlaced
1454        images, it is the previous scanline in the reduced image, which
1455        in general is not the previous pixel row in the final image).
1456        When there is no previous scanline (the first row of a
1457        straightlaced image, or the first row in one of the passes in an
1458        interlaced image), then this argument should be ``None``.
1459
1460        The scanline will have the effects of filtering removed, and the
1461        result will be returned as a fresh sequence of bytes.
1462        """
1463
1464        # :todo: Would it be better to update scanline in place?
1465        # Yes, with the Cython extension making the undo_filter fast,
1466        # updating scanline inplace makes the code 3 times faster
1467        # (reading 50 images of 800x800 went from 40s to 16s)
1468        result = scanline
1469
1470        if filter_type == 0:
1471            return result
1472
1473        if filter_type not in (1,2,3,4):
1474            raise FormatError('Invalid PNG Filter Type.'
1475              '  See http://www.w3.org/TR/2003/REC-PNG-20031110/#9Filters .')
1476
1477        # Filter unit.  The stride from one pixel to the corresponding
1478        # byte from the previous pixel.  Normally this is the pixel
1479        # size in bytes, but when this is smaller than 1, the previous
1480        # byte is used instead.
1481        fu = max(1, self.psize)
1482
1483        # For the first line of a pass, synthesize a dummy previous
1484        # line.  An alternative approach would be to observe that on the
1485        # first line 'up' is the same as 'null', 'paeth' is the same
1486        # as 'sub', with only 'average' requiring any special case.
1487        if not previous:
1488            previous = array('B', [0]*len(scanline))
1489
1490        def sub():
1491            """Undo sub filter."""
1492
1493            ai = 0
1494            # Loop starts at index fu.  Observe that the initial part
1495            # of the result is already filled in correctly with
1496            # scanline.
1497            for i in range(fu, len(result)):
1498                x = scanline[i]
1499                a = result[ai]
1500                result[i] = (x + a) & 0xff
1501                ai += 1
1502
1503        def up():
1504            """Undo up filter."""
1505
1506            for i in range(len(result)):
1507                x = scanline[i]
1508                b = previous[i]
1509                result[i] = (x + b) & 0xff
1510
1511        def average():
1512            """Undo average filter."""
1513
1514            ai = -fu
1515            for i in range(len(result)):
1516                x = scanline[i]
1517                if ai < 0:
1518                    a = 0
1519                else:
1520                    a = result[ai]
1521                b = previous[i]
1522                result[i] = (x + ((a + b) >> 1)) & 0xff
1523                ai += 1
1524
1525        def paeth():
1526            """Undo Paeth filter."""
1527
1528            # Also used for ci.
1529            ai = -fu
1530            for i in range(len(result)):
1531                x = scanline[i]
1532                if ai < 0:
1533                    a = c = 0
1534                else:
1535                    a = result[ai]
1536                    c = previous[ai]
1537                b = previous[i]
1538                p = a + b - c
1539                pa = abs(p - a)
1540                pb = abs(p - b)
1541                pc = abs(p - c)
1542                if pa <= pb and pa <= pc:
1543                    pr = a
1544                elif pb <= pc:
1545                    pr = b
1546                else:
1547                    pr = c
1548                result[i] = (x + pr) & 0xff
1549                ai += 1
1550
1551        # Call appropriate filter algorithm.  Note that 0 has already
1552        # been dealt with.
1553        (None,
1554         pngfilters.undo_filter_sub,
1555         pngfilters.undo_filter_up,
1556         pngfilters.undo_filter_average,
1557         pngfilters.undo_filter_paeth)[filter_type](fu, scanline, previous, result)
1558        return result
1559
1560    def deinterlace(self, raw):
1561        """
1562        Read raw pixel data, undo filters, deinterlace, and flatten.
1563        Return in flat row flat pixel format.
1564        """
1565
1566        # Values per row (of the target image)
1567        vpr = self.width * self.planes
1568
1569        # Make a result array, and make it big enough.  Interleaving
1570        # writes to the output array randomly (well, not quite), so the
1571        # entire output array must be in memory.
1572        fmt = 'BH'[self.bitdepth > 8]
1573        a = array(fmt, [0]*vpr*self.height)
1574        source_offset = 0
1575
1576        for xstart, ystart, xstep, ystep in _adam7:
1577            if xstart >= self.width:
1578                continue
1579            # The previous (reconstructed) scanline.  None at the
1580            # beginning of a pass to indicate that there is no previous
1581            # line.
1582            recon = None
1583            # Pixels per row (reduced pass image)
1584            ppr = int(math.ceil((self.width-xstart)/float(xstep)))
1585            # Row size in bytes for this pass.
1586            row_size = int(math.ceil(self.psize * ppr))
1587            for y in range(ystart, self.height, ystep):
1588                filter_type = raw[source_offset]
1589                source_offset += 1
1590                scanline = raw[source_offset:source_offset+row_size]
1591                source_offset += row_size
1592                recon = self.undo_filter(filter_type, scanline, recon)
1593                # Convert so that there is one element per pixel value
1594                flat = self.serialtoflat(recon, ppr)
1595                if xstep == 1:
1596                    assert xstart == 0
1597                    offset = y * vpr
1598                    a[offset:offset+vpr] = flat
1599                else:
1600                    offset = y * vpr + xstart * self.planes
1601                    end_offset = (y+1) * vpr
1602                    skip = self.planes * xstep
1603                    for i in range(self.planes):
1604                        a[offset+i:end_offset:skip] = \
1605                            flat[i::self.planes]
1606        return a
1607
1608    def iterboxed(self, rows):
1609        """Iterator that yields each scanline in boxed row flat pixel
1610        format.  `rows` should be an iterator that yields the bytes of
1611        each row in turn.
1612        """
1613
1614        def asvalues(raw):
1615            """Convert a row of raw bytes into a flat row.  Result will
1616            be a freshly allocated object, not shared with
1617            argument.
1618            """
1619
1620            if self.bitdepth == 8:
1621                return array('B', raw)
1622            if self.bitdepth == 16:
1623                raw = tostring(raw)
1624                return array('H', struct.unpack('!%dH' % (len(raw)//2), raw))
1625            assert self.bitdepth < 8
1626            width = self.width
1627            # Samples per byte
1628            spb = 8//self.bitdepth
1629            out = array('B')
1630            mask = 2**self.bitdepth - 1
1631            shifts = map(self.bitdepth.__mul__, reversed(range(spb)))
1632            for o in raw:
1633                out.extend(map(lambda i: mask&(o>>i), shifts))
1634            return out[:width]
1635
1636        return itertools.imap(asvalues, rows)
1637
1638    def serialtoflat(self, bytes, width=None):
1639        """Convert serial format (byte stream) pixel data to flat row
1640        flat pixel.
1641        """
1642
1643        if self.bitdepth == 8:
1644            return bytes
1645        if self.bitdepth == 16:
1646            bytes = tostring(bytes)
1647            return array('H',
1648              struct.unpack('!%dH' % (len(bytes)//2), bytes))
1649        assert self.bitdepth < 8
1650        if width is None:
1651            width = self.width
1652        # Samples per byte
1653        spb = 8//self.bitdepth
1654        out = array('B')
1655        mask = 2**self.bitdepth - 1
1656        shifts = map(self.bitdepth.__mul__, reversed(range(spb)))
1657        l = width
1658        for o in bytes:
1659            out.extend([(mask&(o>>s)) for s in shifts][:l])
1660            l -= spb
1661            if l <= 0:
1662                l = width
1663        return out
1664
1665    def iterstraight(self, raw):
1666        """Iterator that undoes the effect of filtering, and yields
1667        each row in serialised format (as a sequence of bytes).
1668        Assumes input is straightlaced.  `raw` should be an iterable
1669        that yields the raw bytes in chunks of arbitrary size.
1670        """
1671
1672        # length of row, in bytes
1673        rb = self.row_bytes
1674        a = array('B')
1675        # The previous (reconstructed) scanline.  None indicates first
1676        # line of image.
1677        recon = None
1678        for some in raw:
1679            a.extend(some)
1680            while len(a) >= rb + 1:
1681                filter_type = a[0]
1682                scanline = a[1:rb+1]
1683                del a[:rb+1]
1684                recon = self.undo_filter(filter_type, scanline, recon)
1685                yield recon
1686        if len(a) != 0:
1687            # :file:format We get here with a file format error:
1688            # when the available bytes (after decompressing) do not
1689            # pack into exact rows.
1690            raise FormatError(
1691              'Wrong size for decompressed IDAT chunk.')
1692        assert len(a) == 0
1693
1694    def validate_signature(self):
1695        """If signature (header) has not been read then read and
1696        validate it; otherwise do nothing.
1697        """
1698
1699        if self.signature:
1700            return
1701        self.signature = self.file.read(8)
1702        if self.signature != _signature:
1703            raise FormatError("PNG file has invalid signature.")
1704
1705    def preamble(self, lenient=False):
1706        """
1707        Extract the image metadata by reading the initial part of
1708        the PNG file up to the start of the ``IDAT`` chunk.  All the
1709        chunks that precede the ``IDAT`` chunk are read and either
1710        processed for metadata or discarded.
1711
1712        If the optional `lenient` argument evaluates to True, checksum
1713        failures will raise warnings rather than exceptions.
1714        """
1715
1716        self.validate_signature()
1717
1718        while True:
1719            if not self.atchunk:
1720                self.atchunk = self.chunklentype()
1721                if self.atchunk is None:
1722                    raise FormatError(
1723                      'This PNG file has no IDAT chunks.')
1724            if self.atchunk[1] == 'IDAT':
1725                return
1726            self.process_chunk(lenient=lenient)
1727
1728    def chunklentype(self):
1729        """Reads just enough of the input to determine the next
1730        chunk's length and type, returned as a (*length*, *type*) pair
1731        where *type* is a string.  If there are no more chunks, ``None``
1732        is returned.
1733        """
1734
1735        x = self.file.read(8)
1736        if not x:
1737            return None
1738        if len(x) != 8:
1739            raise FormatError(
1740              'End of file whilst reading chunk length and type.')
1741        length,type = struct.unpack('!I4s', x)
1742        type = bytestostr(type)
1743        if length > 2**31-1:
1744            raise FormatError('Chunk %s is too large: %d.' % (type,length))
1745        return length,type
1746
1747    def process_chunk(self, lenient=False):
1748        """Process the next chunk and its data.  This only processes the
1749        following chunk types, all others are ignored: ``IHDR``,
1750        ``PLTE``, ``bKGD``, ``tRNS``, ``gAMA``, ``sBIT``.
1751
1752        If the optional `lenient` argument evaluates to True,
1753        checksum failures will raise warnings rather than exceptions.
1754        """
1755
1756        type, data = self.chunk(lenient=lenient)
1757        method = '_process_' + type
1758        m = getattr(self, method, None)
1759        if m:
1760            m(data)
1761
1762    def _process_IHDR(self, data):
1763        # http://www.w3.org/TR/PNG/#11IHDR
1764        if len(data) != 13:
1765            raise FormatError('IHDR chunk has incorrect length.')
1766        (self.width, self.height, self.bitdepth, self.color_type,
1767         self.compression, self.filter,
1768         self.interlace) = struct.unpack("!2I5B", data)
1769
1770        check_bitdepth_colortype(self.bitdepth, self.color_type)
1771
1772        if self.compression != 0:
1773            raise Error("unknown compression method %d" % self.compression)
1774        if self.filter != 0:
1775            raise FormatError("Unknown filter method %d,"
1776              " see http://www.w3.org/TR/2003/REC-PNG-20031110/#9Filters ."
1777              % self.filter)
1778        if self.interlace not in (0,1):
1779            raise FormatError("Unknown interlace method %d,"
1780              " see http://www.w3.org/TR/2003/REC-PNG-20031110/#8InterlaceMethods ."
1781              % self.interlace)
1782
1783        # Derived values
1784        # http://www.w3.org/TR/PNG/#6Colour-values
1785        colormap =  bool(self.color_type & 1)
1786        greyscale = not (self.color_type & 2)
1787        alpha = bool(self.color_type & 4)
1788        color_planes = (3,1)[greyscale or colormap]
1789        planes = color_planes + alpha
1790
1791        self.colormap = colormap
1792        self.greyscale = greyscale
1793        self.alpha = alpha
1794        self.color_planes = color_planes
1795        self.planes = planes
1796        self.psize = float(self.bitdepth)/float(8) * planes
1797        if int(self.psize) == self.psize:
1798            self.psize = int(self.psize)
1799        self.row_bytes = int(math.ceil(self.width * self.psize))
1800        # Stores PLTE chunk if present, and is used to check
1801        # chunk ordering constraints.
1802        self.plte = None
1803        # Stores tRNS chunk if present, and is used to check chunk
1804        # ordering constraints.
1805        self.trns = None
1806        # Stores sbit chunk if present.
1807        self.sbit = None
1808
1809    def _process_PLTE(self, data):
1810        # http://www.w3.org/TR/PNG/#11PLTE
1811        if self.plte:
1812            warnings.warn("Multiple PLTE chunks present.")
1813        self.plte = data
1814        if len(data) % 3 != 0:
1815            raise FormatError(
1816              "PLTE chunk's length should be a multiple of 3.")
1817        if len(data) > (2**self.bitdepth)*3:
1818            raise FormatError("PLTE chunk is too long.")
1819        if len(data) == 0:
1820            raise FormatError("Empty PLTE is not allowed.")
1821
1822    def _process_bKGD(self, data):
1823        try:
1824            if self.colormap:
1825                if not self.plte:
1826                    warnings.warn(
1827                      "PLTE chunk is required before bKGD chunk.")
1828                self.background = struct.unpack('B', data)
1829            else:
1830                self.background = struct.unpack("!%dH" % self.color_planes,
1831                  data)
1832        except struct.error:
1833            raise FormatError("bKGD chunk has incorrect length.")
1834
1835    def _process_tRNS(self, data):
1836        # http://www.w3.org/TR/PNG/#11tRNS
1837        self.trns = data
1838        if self.colormap:
1839            if not self.plte:
1840                warnings.warn("PLTE chunk is required before tRNS chunk.")
1841            else:
1842                if len(data) > len(self.plte)/3:
1843                    # Was warning, but promoted to Error as it
1844                    # would otherwise cause pain later on.
1845                    raise FormatError("tRNS chunk is too long.")
1846        else:
1847            if self.alpha:
1848                raise FormatError(
1849                  "tRNS chunk is not valid with colour type %d." %
1850                  self.color_type)
1851            try:
1852                self.transparent = \
1853                    struct.unpack("!%dH" % self.color_planes, data)
1854            except struct.error:
1855                raise FormatError("tRNS chunk has incorrect length.")
1856
1857    def _process_gAMA(self, data):
1858        try:
1859            self.gamma = struct.unpack("!L", data)[0] / 100000.0
1860        except struct.error:
1861            raise FormatError("gAMA chunk has incorrect length.")
1862
1863    def _process_sBIT(self, data):
1864        self.sbit = data
1865        if (self.colormap and len(data) != 3 or
1866            not self.colormap and len(data) != self.planes):
1867            raise FormatError("sBIT chunk has incorrect length.")
1868
1869    def read(self, lenient=False):
1870        """
1871        Read the PNG file and decode it.  Returns (`width`, `height`,
1872        `pixels`, `metadata`).
1873
1874        May use excessive memory.
1875
1876        `pixels` are returned in boxed row flat pixel format.
1877
1878        If the optional `lenient` argument evaluates to True,
1879        checksum failures will raise warnings rather than exceptions.
1880        """
1881
1882        def iteridat():
1883            """Iterator that yields all the ``IDAT`` chunks as strings."""
1884            while True:
1885                try:
1886                    type, data = self.chunk(lenient=lenient)
1887                except ValueError, e:
1888                    raise ChunkError(e.args[0])
1889                if type == 'IEND':
1890                    # http://www.w3.org/TR/PNG/#11IEND
1891                    break
1892                if type != 'IDAT':
1893                    continue
1894                # type == 'IDAT'
1895                # http://www.w3.org/TR/PNG/#11IDAT
1896                if self.colormap and not self.plte:
1897                    warnings.warn("PLTE chunk is required before IDAT chunk")
1898                yield data
1899
1900        def iterdecomp(idat):
1901            """Iterator that yields decompressed strings.  `idat` should
1902            be an iterator that yields the ``IDAT`` chunk data.
1903            """
1904
1905            # Currently, with no max_length paramter to decompress, this
1906            # routine will do one yield per IDAT chunk.  So not very
1907            # incremental.
1908            d = zlib.decompressobj()
1909            # Each IDAT chunk is passed to the decompressor, then any
1910            # remaining state is decompressed out.
1911            for data in idat:
1912                # :todo: add a max_length argument here to limit output
1913                # size.
1914                yield array('B', d.decompress(data))
1915            yield array('B', d.flush())
1916
1917        self.preamble(lenient=lenient)
1918        raw = iterdecomp(iteridat())
1919
1920        if self.interlace:
1921            raw = array('B', itertools.chain(*raw))
1922            arraycode = 'BH'[self.bitdepth>8]
1923            # Like :meth:`group` but producing an array.array object for
1924            # each row.
1925            pixels = itertools.imap(lambda *row: array(arraycode, row),
1926                       *[iter(self.deinterlace(raw))]*self.width*self.planes)
1927        else:
1928            pixels = self.iterboxed(self.iterstraight(raw))
1929        meta = dict()
1930        for attr in 'greyscale alpha planes bitdepth interlace'.split():
1931            meta[attr] = getattr(self, attr)
1932        meta['size'] = (self.width, self.height)
1933        for attr in 'gamma transparent background'.split():
1934            a = getattr(self, attr, None)
1935            if a is not None:
1936                meta[attr] = a
1937        if self.plte:
1938            meta['palette'] = self.palette()
1939        return self.width, self.height, pixels, meta
1940
1941
1942    def read_flat(self):
1943        """
1944        Read a PNG file and decode it into flat row flat pixel format.
1945        Returns (*width*, *height*, *pixels*, *metadata*).
1946
1947        May use excessive memory.
1948
1949        `pixels` are returned in flat row flat pixel format.
1950
1951        See also the :meth:`read` method which returns pixels in the
1952        more stream-friendly boxed row flat pixel format.
1953        """
1954
1955        x, y, pixel, meta = self.read()
1956        arraycode = 'BH'[meta['bitdepth']>8]
1957        pixel = array(arraycode, itertools.chain(*pixel))
1958        return x, y, pixel, meta
1959
1960    def palette(self, alpha='natural'):
1961        """Returns a palette that is a sequence of 3-tuples or 4-tuples,
1962        synthesizing it from the ``PLTE`` and ``tRNS`` chunks.  These
1963        chunks should have already been processed (for example, by
1964        calling the :meth:`preamble` method).  All the tuples are the
1965        same size: 3-tuples if there is no ``tRNS`` chunk, 4-tuples when
1966        there is a ``tRNS`` chunk.  Assumes that the image is colour type
1967        3 and therefore a ``PLTE`` chunk is required.
1968
1969        If the `alpha` argument is ``'force'`` then an alpha channel is
1970        always added, forcing the result to be a sequence of 4-tuples.
1971        """
1972
1973        if not self.plte:
1974            raise FormatError(
1975                "Required PLTE chunk is missing in colour type 3 image.")
1976        plte = group(array('B', self.plte), 3)
1977        if self.trns or alpha == 'force':
1978            trns = array('B', self.trns or '')
1979            trns.extend([255]*(len(plte)-len(trns)))
1980            plte = map(operator.add, plte, group(trns, 1))
1981        return plte
1982
1983    def asDirect(self):
1984        """Returns the image data as a direct representation of an
1985        ``x * y * planes`` array.  This method is intended to remove the
1986        need for callers to deal with palettes and transparency
1987        themselves.  Images with a palette (colour type 3)
1988        are converted to RGB or RGBA; images with transparency (a
1989        ``tRNS`` chunk) are converted to LA or RGBA as appropriate.
1990        When returned in this format the pixel values represent the
1991        colour value directly without needing to refer to palettes or
1992        transparency information.
1993
1994        Like the :meth:`read` method this method returns a 4-tuple:
1995
1996        (*width*, *height*, *pixels*, *meta*)
1997
1998        This method normally returns pixel values with the bit depth
1999        they have in the source image, but when the source PNG has an
2000        ``sBIT`` chunk it is inspected and can reduce the bit depth of
2001        the result pixels; pixel values will be reduced according to
2002        the bit depth specified in the ``sBIT`` chunk (PNG nerds should
2003        note a single result bit depth is used for all channels; the
2004        maximum of the ones specified in the ``sBIT`` chunk.  An RGB565
2005        image will be rescaled to 6-bit RGB666).
2006
2007        The *meta* dictionary that is returned reflects the `direct`
2008        format and not the original source image.  For example, an RGB
2009        source image with a ``tRNS`` chunk to represent a transparent
2010        colour, will have ``planes=3`` and ``alpha=False`` for the
2011        source image, but the *meta* dictionary returned by this method
2012        will have ``planes=4`` and ``alpha=True`` because an alpha
2013        channel is synthesized and added.
2014
2015        *pixels* is the pixel data in boxed row flat pixel format (just
2016        like the :meth:`read` method).
2017
2018        All the other aspects of the image data are not changed.
2019        """
2020
2021        self.preamble()
2022
2023        # Simple case, no conversion necessary.
2024        if not self.colormap and not self.trns and not self.sbit:
2025            return self.read()
2026
2027        x,y,pixels,meta = self.read()
2028
2029        if self.colormap:
2030            meta['colormap'] = False
2031            meta['alpha'] = bool(self.trns)
2032            meta['bitdepth'] = 8
2033            meta['planes'] = 3 + bool(self.trns)
2034            plte = self.palette()
2035            def iterpal(pixels):
2036                for row in pixels:
2037                    row = map(plte.__getitem__, row)
2038                    yield array('B', itertools.chain(*row))
2039            pixels = iterpal(pixels)
2040        elif self.trns:
2041            # It would be nice if there was some reasonable way
2042            # of doing this without generating a whole load of
2043            # intermediate tuples.  But tuples does seem like the
2044            # easiest way, with no other way clearly much simpler or
2045            # much faster.  (Actually, the L to LA conversion could
2046            # perhaps go faster (all those 1-tuples!), but I still
2047            # wonder whether the code proliferation is worth it)
2048            it = self.transparent
2049            maxval = 2**meta['bitdepth']-1
2050            planes = meta['planes']
2051            meta['alpha'] = True
2052            meta['planes'] += 1
2053            typecode = 'BH'[meta['bitdepth']>8]
2054            def itertrns(pixels):
2055                for row in pixels:
2056                    # For each row we group it into pixels, then form a
2057                    # characterisation vector that says whether each
2058                    # pixel is opaque or not.  Then we convert
2059                    # True/False to 0/maxval (by multiplication),
2060                    # and add it as the extra channel.
2061                    row = group(row, planes)
2062                    opa = map(it.__ne__, row)
2063                    opa = map(maxval.__mul__, opa)
2064                    opa = zip(opa) # convert to 1-tuples
2065                    yield array(typecode,
2066                      itertools.chain(*map(operator.add, row, opa)))
2067            pixels = itertrns(pixels)
2068        targetbitdepth = None
2069        if self.sbit:
2070            sbit = struct.unpack('%dB' % len(self.sbit), self.sbit)
2071            targetbitdepth = max(sbit)
2072            if targetbitdepth > meta['bitdepth']:
2073                raise Error('sBIT chunk %r exceeds bitdepth %d' %
2074                    (sbit,self.bitdepth))
2075            if min(sbit) <= 0:
2076                raise Error('sBIT chunk %r has a 0-entry' % sbit)
2077            if targetbitdepth == meta['bitdepth']:
2078                targetbitdepth = None
2079        if targetbitdepth:
2080            shift = meta['bitdepth'] - targetbitdepth
2081            meta['bitdepth'] = targetbitdepth
2082            def itershift(pixels):
2083                for row in pixels:
2084                    yield map(shift.__rrshift__, row)
2085            pixels = itershift(pixels)
2086        return x,y,pixels,meta
2087
2088    def asFloat(self, maxval=1.0):
2089        """Return image pixels as per :meth:`asDirect` method, but scale
2090        all pixel values to be floating point values between 0.0 and
2091        *maxval*.
2092        """
2093
2094        x,y,pixels,info = self.asDirect()
2095        sourcemaxval = 2**info['bitdepth']-1
2096        del info['bitdepth']
2097        info['maxval'] = float(maxval)
2098        factor = float(maxval)/float(sourcemaxval)
2099        def iterfloat():
2100            for row in pixels:
2101                yield map(factor.__mul__, row)
2102        return x,y,iterfloat(),info
2103
2104    def _as_rescale(self, get, targetbitdepth):
2105        """Helper used by :meth:`asRGB8` and :meth:`asRGBA8`."""
2106
2107        width,height,pixels,meta = get()
2108        maxval = 2**meta['bitdepth'] - 1
2109        targetmaxval = 2**targetbitdepth - 1
2110        factor = float(targetmaxval) / float(maxval)
2111        meta['bitdepth'] = targetbitdepth
2112        def iterscale():
2113            for row in pixels:
2114                yield map(lambda x: int(round(x*factor)), row)
2115        if maxval == targetmaxval:
2116            return width, height, pixels, meta
2117        else:
2118            return width, height, iterscale(), meta
2119
2120    def asRGB8(self):
2121        """Return the image data as an RGB pixels with 8-bits per
2122        sample.  This is like the :meth:`asRGB` method except that
2123        this method additionally rescales the values so that they
2124        are all between 0 and 255 (8-bit).  In the case where the
2125        source image has a bit depth < 8 the transformation preserves
2126        all the information; where the source image has bit depth
2127        > 8, then rescaling to 8-bit values loses precision.  No
2128        dithering is performed.  Like :meth:`asRGB`, an alpha channel
2129        in the source image will raise an exception.
2130
2131        This function returns a 4-tuple:
2132        (*width*, *height*, *pixels*, *metadata*).
2133        *width*, *height*, *metadata* are as per the
2134        :meth:`read` method.
2135       
2136        *pixels* is the pixel data in boxed row flat pixel format.
2137        """
2138
2139        return self._as_rescale(self.asRGB, 8)
2140
2141    def asRGBA8(self):
2142        """Return the image data as RGBA pixels with 8-bits per
2143        sample.  This method is similar to :meth:`asRGB8` and
2144        :meth:`asRGBA`:  The result pixels have an alpha channel, *and*
2145        values are rescaled to the range 0 to 255.  The alpha channel is
2146        synthesized if necessary (with a small speed penalty).
2147        """
2148
2149        return self._as_rescale(self.asRGBA, 8)
2150
2151    def asRGB(self):
2152        """Return image as RGB pixels.  RGB colour images are passed
2153        through unchanged; greyscales are expanded into RGB
2154        triplets (there is a small speed overhead for doing this).
2155
2156        An alpha channel in the source image will raise an
2157        exception.
2158
2159        The return values are as for the :meth:`read` method
2160        except that the *metadata* reflect the returned pixels, not the
2161        source image.  In particular, for this method
2162        ``metadata['greyscale']`` will be ``False``.
2163        """
2164
2165        width,height,pixels,meta = self.asDirect()
2166        if meta['alpha']:
2167            raise Error("will not convert image with alpha channel to RGB")
2168        if not meta['greyscale']:
2169            return width,height,pixels,meta
2170        meta['greyscale'] = False
2171        typecode = 'BH'[meta['bitdepth'] > 8]
2172        def iterrgb():
2173            for row in pixels:
2174                a = array(typecode, [0]) * 3 * width
2175                for i in range(3):
2176                    a[i::3] = row
2177                yield a
2178        return width,height,iterrgb(),meta
2179
2180    def asRGBA(self):
2181        """Return image as RGBA pixels.  Greyscales are expanded into
2182        RGB triplets; an alpha channel is synthesized if necessary.
2183        The return values are as for the :meth:`read` method
2184        except that the *metadata* reflect the returned pixels, not the
2185        source image.  In particular, for this method
2186        ``metadata['greyscale']`` will be ``False``, and
2187        ``metadata['alpha']`` will be ``True``.
2188        """
2189
2190        width,height,pixels,meta = self.asDirect()
2191        if meta['alpha'] and not meta['greyscale']:
2192            return width,height,pixels,meta
2193        typecode = 'BH'[meta['bitdepth'] > 8]
2194        maxval = 2**meta['bitdepth'] - 1
2195        maxbuffer = struct.pack('=' + typecode, maxval) * 4 * width
2196        def newarray():
2197            return array(typecode, maxbuffer)
2198
2199        if meta['alpha'] and meta['greyscale']:
2200            # LA to RGBA
2201            def convert():
2202                for row in pixels:
2203                    # Create a fresh target row, then copy L channel
2204                    # into first three target channels, and A channel
2205                    # into fourth channel.
2206                    a = newarray()
2207                    pngfilters.convert_la_to_rgba(row, a)
2208                    yield a
2209        elif meta['greyscale']:
2210            # L to RGBA
2211            def convert():
2212                for row in pixels:
2213                    a = newarray()
2214                    pngfilters.convert_l_to_rgba(row, a)
2215                    yield a
2216        else:
2217            assert not meta['alpha'] and not meta['greyscale']
2218            # RGB to RGBA
2219            def convert():
2220                for row in pixels:
2221                    a = newarray()
2222                    pngfilters.convert_rgb_to_rgba(row, a)
2223                    yield a
2224        meta['alpha'] = True
2225        meta['greyscale'] = False
2226        return width,height,convert(),meta
2227
2228def check_bitdepth_colortype(bitdepth, colortype):
2229    """Check that `bitdepth` and `colortype` are both valid,
2230    and specified in a valid combination. Returns if valid,
2231    raise an Exception if not valid.
2232    """
2233
2234    if bitdepth not in (1,2,4,8,16):
2235        raise FormatError("invalid bit depth %d" % bitdepth)
2236    if colortype not in (0,2,3,4,6):
2237        raise FormatError("invalid colour type %d" % colortype)
2238    # Check indexed (palettized) images have 8 or fewer bits
2239    # per pixel; check only indexed or greyscale images have
2240    # fewer than 8 bits per pixel.
2241    if colortype & 1 and bitdepth > 8:
2242        raise FormatError(
2243          "Indexed images (colour type %d) cannot"
2244          " have bitdepth > 8 (bit depth %d)."
2245          " See http://www.w3.org/TR/2003/REC-PNG-20031110/#table111 ."
2246          % (bitdepth, colortype))
2247    if bitdepth < 8 and colortype not in (0,3):
2248        raise FormatError("Illegal combination of bit depth (%d)"
2249          " and colour type (%d)."
2250          " See http://www.w3.org/TR/2003/REC-PNG-20031110/#table111 ."
2251          % (bitdepth, colortype))
2252
2253def isinteger(x):
2254    try:
2255        return int(x) == x
2256    except (TypeError, ValueError):
2257        return False
2258
2259
2260# === Legacy Version Support ===
2261
2262# :pyver:old:  PyPNG works on Python versions 2.3 and 2.2, but not
2263# without some awkward problems.  Really PyPNG works on Python 2.4 (and
2264# above); it works on Pythons 2.3 and 2.2 by virtue of fixing up
2265# problems here.  It's a bit ugly (which is why it's hidden down here).
2266#
2267# Generally the strategy is one of pretending that we're running on
2268# Python 2.4 (or above), and patching up the library support on earlier
2269# versions so that it looks enough like Python 2.4.  When it comes to
2270# Python 2.2 there is one thing we cannot patch: extended slices
2271# http://www.python.org/doc/2.3/whatsnew/section-slices.html.
2272# Instead we simply declare that features that are implemented using
2273# extended slices will not work on Python 2.2.
2274#
2275# In order to work on Python 2.3 we fix up a recurring annoyance involving
2276# the array type.  In Python 2.3 an array cannot be initialised with an
2277# array, and it cannot be extended with a list (or other sequence).
2278# Both of those are repeated issues in the code.  Whilst I would not
2279# normally tolerate this sort of behaviour, here we "shim" a replacement
2280# for array into place (and hope no-one notices).  You never read this.
2281#
2282# In an amusing case of warty hacks on top of warty hacks... the array
2283# shimming we try and do only works on Python 2.3 and above (you can't
2284# subclass array.array in Python 2.2).  So to get it working on Python
2285# 2.2 we go for something much simpler and (probably) way slower.
2286try:
2287    array('B').extend([])
2288    array('B', array('B'))
2289# :todo:(drj) Check that TypeError is correct for Python 2.3
2290except TypeError:
2291    # Expect to get here on Python 2.3
2292    try:
2293        class _array_shim(array):
2294            true_array = array
2295            def __new__(cls, typecode, init=None):
2296                super_new = super(_array_shim, cls).__new__
2297                it = super_new(cls, typecode)
2298                if init is None:
2299                    return it
2300                it.extend(init)
2301                return it
2302            def extend(self, extension):
2303                super_extend = super(_array_shim, self).extend
2304                if isinstance(extension, self.true_array):
2305                    return super_extend(extension)
2306                if not isinstance(extension, (list, str)):
2307                    # Convert to list.  Allows iterators to work.
2308                    extension = list(extension)
2309                return super_extend(self.true_array(self.typecode, extension))
2310        array = _array_shim
2311    except TypeError:
2312        # Expect to get here on Python 2.2
2313        def array(typecode, init=()):
2314            if type(init) == str:
2315                return map(ord, init)
2316            return list(init)
2317
2318# Further hacks to get it limping along on Python 2.2
2319try:
2320    enumerate
2321except NameError:
2322    def enumerate(seq):
2323        i=0
2324        for x in seq:
2325            yield i,x
2326            i += 1
2327
2328try:
2329    reversed
2330except NameError:
2331    def reversed(l):
2332        l = list(l)
2333        l.reverse()
2334        for x in l:
2335            yield x
2336
2337try:
2338    itertools
2339except NameError:
2340    class _dummy_itertools:
2341        pass
2342    itertools = _dummy_itertools()
2343    def _itertools_imap(f, seq):
2344        for x in seq:
2345            yield f(x)
2346    itertools.imap = _itertools_imap
2347    def _itertools_chain(*iterables):
2348        for it in iterables:
2349            for element in it:
2350                yield element
2351    itertools.chain = _itertools_chain
2352
2353
2354# === Support for users without Cython ===
2355
2356try:
2357    pngfilters
2358except NameError:
2359    class pngfilters(object):
2360        def undo_filter_sub(filter_unit, scanline, previous, result):
2361            """Undo sub filter."""
2362
2363            ai = 0
2364            # Loops starts at index fu.  Observe that the initial part
2365            # of the result is already filled in correctly with
2366            # scanline.
2367            for i in range(filter_unit, len(result)):
2368                x = scanline[i]
2369                a = result[ai]
2370                result[i] = (x + a) & 0xff
2371                ai += 1
2372        undo_filter_sub = staticmethod(undo_filter_sub)
2373
2374        def undo_filter_up(filter_unit, scanline, previous, result):
2375            """Undo up filter."""
2376
2377            for i in range(len(result)):
2378                x = scanline[i]
2379                b = previous[i]
2380                result[i] = (x + b) & 0xff
2381        undo_filter_up = staticmethod(undo_filter_up)
2382
2383        def undo_filter_average(filter_unit, scanline, previous, result):
2384            """Undo up filter."""
2385
2386            ai = -filter_unit
2387            for i in range(len(result)):
2388                x = scanline[i]
2389                if ai < 0:
2390                    a = 0
2391                else:
2392                    a = result[ai]
2393                b = previous[i]
2394                result[i] = (x + ((a + b) >> 1)) & 0xff
2395                ai += 1
2396        undo_filter_average = staticmethod(undo_filter_average)
2397
2398        def undo_filter_paeth(filter_unit, scanline, previous, result):
2399            """Undo Paeth filter."""
2400
2401            # Also used for ci.
2402            ai = -filter_unit
2403            for i in range(len(result)):
2404                x = scanline[i]
2405                if ai < 0:
2406                    a = c = 0
2407                else:
2408                    a = result[ai]
2409                    c = previous[ai]
2410                b = previous[i]
2411                p = a + b - c
2412                pa = abs(p - a)
2413                pb = abs(p - b)
2414                pc = abs(p - c)
2415                if pa <= pb and pa <= pc:
2416                    pr = a
2417                elif pb <= pc:
2418                    pr = b
2419                else:
2420                    pr = c
2421                result[i] = (x + pr) & 0xff
2422                ai += 1
2423        undo_filter_paeth = staticmethod(undo_filter_paeth)
2424
2425        def convert_la_to_rgba(row, result):
2426            for i in range(3):
2427                result[i::4] = row[0::2]
2428            result[3::4] = row[1::2]
2429        convert_la_to_rgba = staticmethod(convert_la_to_rgba)
2430
2431        def convert_l_to_rgba(row, result):
2432            """Convert a grayscale image to RGBA. This method assumes
2433            the alpha channel in result is already correctly
2434            initialized.
2435            """
2436            for i in range(3):
2437                result[i::4] = row
2438        convert_l_to_rgba = staticmethod(convert_l_to_rgba)
2439
2440        def convert_rgb_to_rgba(row, result):
2441            """Convert an RGB image to RGBA. This method assumes the
2442            alpha channel in result is already correctly initialized.
2443            """
2444            for i in range(3):
2445                result[i::4] = row[i::3]
2446        convert_rgb_to_rgba = staticmethod(convert_rgb_to_rgba)
2447
2448
2449# === Command Line Support ===
2450
2451def read_pam_header(infile):
2452    """
2453    Read (the rest of a) PAM header.  `infile` should be positioned
2454    immediately after the initial 'P7' line (at the beginning of the
2455    second line).  Returns are as for `read_pnm_header`.
2456    """
2457   
2458    # Unlike PBM, PGM, and PPM, we can read the header a line at a time.
2459    header = dict()
2460    while True:
2461        l = infile.readline().strip()
2462        if l == strtobytes('ENDHDR'):
2463            break
2464        if not l:
2465            raise EOFError('PAM ended prematurely')
2466        if l[0] == strtobytes('#'):
2467            continue
2468        l = l.split(None, 1)
2469        if l[0] not in header:
2470            header[l[0]] = l[1]
2471        else:
2472            header[l[0]] += strtobytes(' ') + l[1]
2473
2474    required = ['WIDTH', 'HEIGHT', 'DEPTH', 'MAXVAL']
2475    required = [strtobytes(x) for x in required]
2476    WIDTH,HEIGHT,DEPTH,MAXVAL = required
2477    present = [x for x in required if x in header]
2478    if len(present) != len(required):
2479        raise Error('PAM file must specify WIDTH, HEIGHT, DEPTH, and MAXVAL')
2480    width = int(header[WIDTH])
2481    height = int(header[HEIGHT])
2482    depth = int(header[DEPTH])
2483    maxval = int(header[MAXVAL])
2484    if (width <= 0 or
2485        height <= 0 or
2486        depth <= 0 or
2487        maxval <= 0):
2488        raise Error(
2489          'WIDTH, HEIGHT, DEPTH, MAXVAL must all be positive integers')
2490    return 'P7', width, height, depth, maxval
2491
2492def read_pnm_header(infile, supported=('P5','P6')):
2493    """
2494    Read a PNM header, returning (format,width,height,depth,maxval).
2495    `width` and `height` are in pixels.  `depth` is the number of
2496    channels in the image; for PBM and PGM it is synthesized as 1, for
2497    PPM as 3; for PAM images it is read from the header.  `maxval` is
2498    synthesized (as 1) for PBM images.
2499    """
2500
2501    # Generally, see http://netpbm.sourceforge.net/doc/ppm.html
2502    # and http://netpbm.sourceforge.net/doc/pam.html
2503
2504    supported = [strtobytes(x) for x in supported]
2505
2506    # Technically 'P7' must be followed by a newline, so by using
2507    # rstrip() we are being liberal in what we accept.  I think this
2508    # is acceptable.
2509    type = infile.read(3).rstrip()
2510    if type not in supported:
2511        raise NotImplementedError('file format %s not supported' % type)
2512    if type == strtobytes('P7'):
2513        # PAM header parsing is completely different.
2514        return read_pam_header(infile)
2515    # Expected number of tokens in header (3 for P4, 4 for P6)
2516    expected = 4
2517    pbm = ('P1', 'P4')
2518    if type in pbm:
2519        expected = 3
2520    header = [type]
2521
2522    # We have to read the rest of the header byte by byte because the
2523    # final whitespace character (immediately following the MAXVAL in
2524    # the case of P6) may not be a newline.  Of course all PNM files in
2525    # the wild use a newline at this point, so it's tempting to use
2526    # readline; but it would be wrong.
2527    def getc():
2528        c = infile.read(1)
2529        if not c:
2530            raise Error('premature EOF reading PNM header')
2531        return c
2532
2533    c = getc()
2534    while True:
2535        # Skip whitespace that precedes a token.
2536        while c.isspace():
2537            c = getc()
2538        # Skip comments.
2539        while c == '#':
2540            while c not in '\n\r':
2541                c = getc()
2542        if not c.isdigit():
2543            raise Error('unexpected character %s found in header' % c)
2544        # According to the specification it is legal to have comments
2545        # that appear in the middle of a token.
2546        # This is bonkers; I've never seen it; and it's a bit awkward to
2547        # code good lexers in Python (no goto).  So we break on such
2548        # cases.
2549        token = strtobytes('')
2550        while c.isdigit():
2551            token += c
2552            c = getc()
2553        # Slight hack.  All "tokens" are decimal integers, so convert
2554        # them here.
2555        header.append(int(token))
2556        if len(header) == expected:
2557            break
2558    # Skip comments (again)
2559    while c == '#':
2560        while c not in '\n\r':
2561            c = getc()
2562    if not c.isspace():
2563        raise Error('expected header to end with whitespace, not %s' % c)
2564
2565    if type in pbm:
2566        # synthesize a MAXVAL
2567        header.append(1)
2568    depth = (1,3)[type == strtobytes('P6')]
2569    return header[0], header[1], header[2], depth, header[3]
2570
2571def write_pnm(file, width, height, pixels, meta):
2572    """Write a Netpbm PNM/PAM file.
2573    """
2574
2575    bitdepth = meta['bitdepth']
2576    maxval = 2**bitdepth - 1
2577    # Rudely, the number of image planes can be used to determine
2578    # whether we are L (PGM), LA (PAM), RGB (PPM), or RGBA (PAM).
2579    planes = meta['planes']
2580    # Can be an assert as long as we assume that pixels and meta came
2581    # from a PNG file.
2582    assert planes in (1,2,3,4)
2583    if planes in (1,3):
2584        if 1 == planes:
2585            # PGM
2586            # Could generate PBM if maxval is 1, but we don't (for one
2587            # thing, we'd have to convert the data, not just blat it
2588            # out).
2589            fmt = 'P5'
2590        else:
2591            # PPM
2592            fmt = 'P6'
2593        file.write('%s %d %d %d\n' % (fmt, width, height, maxval))
2594    if planes in (2,4):
2595        # PAM
2596        # See http://netpbm.sourceforge.net/doc/pam.html
2597        if 2 == planes:
2598            tupltype = 'GRAYSCALE_ALPHA'
2599        else:
2600            tupltype = 'RGB_ALPHA'
2601        file.write('P7\nWIDTH %d\nHEIGHT %d\nDEPTH %d\nMAXVAL %d\n'
2602                   'TUPLTYPE %s\nENDHDR\n' %
2603                   (width, height, planes, maxval, tupltype))
2604    # Values per row
2605    vpr = planes * width
2606    # struct format
2607    fmt = '>%d' % vpr
2608    if maxval > 0xff:
2609        fmt = fmt + 'H'
2610    else:
2611        fmt = fmt + 'B'
2612    for row in pixels:
2613        file.write(struct.pack(fmt, *row))
2614    file.flush()
2615
2616def color_triple(color):
2617    """
2618    Convert a command line colour value to a RGB triple of integers.
2619    FIXME: Somewhere we need support for greyscale backgrounds etc.
2620    """
2621    if color.startswith('#') and len(color) == 4:
2622        return (int(color[1], 16),
2623                int(color[2], 16),
2624                int(color[3], 16))
2625    if color.startswith('#') and len(color) == 7:
2626        return (int(color[1:3], 16),
2627                int(color[3:5], 16),
2628                int(color[5:7], 16))
2629    elif color.startswith('#') and len(color) == 13:
2630        return (int(color[1:5], 16),
2631                int(color[5:9], 16),
2632                int(color[9:13], 16))
2633
2634def _add_common_options(parser):
2635    """Call *parser.add_option* for each of the options that are
2636    common between this PNG--PNM conversion tool and the gen
2637    tool.
2638    """
2639    parser.add_option("-i", "--interlace",
2640                      default=False, action="store_true",
2641                      help="create an interlaced PNG file (Adam7)")
2642    parser.add_option("-t", "--transparent",
2643                      action="store", type="string", metavar="#RRGGBB",
2644                      help="mark the specified colour as transparent")
2645    parser.add_option("-b", "--background",
2646                      action="store", type="string", metavar="#RRGGBB",
2647                      help="save the specified background colour")
2648    parser.add_option("-g", "--gamma",
2649                      action="store", type="float", metavar="value",
2650                      help="save the specified gamma value")
2651    parser.add_option("-c", "--compression",
2652                      action="store", type="int", metavar="level",
2653                      help="zlib compression level (0-9)")
2654    return parser
2655
2656def _main(argv):
2657    """
2658    Run the PNG encoder with options from the command line.
2659    """
2660
2661    # Parse command line arguments
2662    from optparse import OptionParser
2663    import re
2664    version = '%prog ' + __version__
2665    parser = OptionParser(version=version)
2666    parser.set_usage("%prog [options] [imagefile]")
2667    parser.add_option('-r', '--read-png', default=False,
2668                      action='store_true',
2669                      help='Read PNG, write PNM')
2670    parser.add_option("-a", "--alpha",
2671                      action="store", type="string", metavar="pgmfile",
2672                      help="alpha channel transparency (RGBA)")
2673    _add_common_options(parser)
2674
2675    (options, args) = parser.parse_args(args=argv[1:])
2676
2677    # Convert options
2678    if options.transparent is not None:
2679        options.transparent = color_triple(options.transparent)
2680    if options.background is not None:
2681        options.background = color_triple(options.background)
2682
2683    # Prepare input and output files
2684    if len(args) == 0:
2685        infilename = '-'
2686        infile = sys.stdin
2687    elif len(args) == 1:
2688        infilename = args[0]
2689        infile = open(infilename, 'rb')
2690    else:
2691        parser.error("more than one input file")
2692    outfile = sys.stdout
2693    if sys.platform == "win32":
2694        import msvcrt, os
2695        msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
2696
2697    if options.read_png:
2698        # Encode PNG to PPM
2699        png = Reader(file=infile)
2700        width,height,pixels,meta = png.asDirect()
2701        write_pnm(outfile, width, height, pixels, meta)
2702    else:
2703        # Encode PNM to PNG
2704        format, width, height, depth, maxval = \
2705          read_pnm_header(infile, ('P5','P6','P7'))
2706        # When it comes to the variety of input formats, we do something
2707        # rather rude.  Observe that L, LA, RGB, RGBA are the 4 colour
2708        # types supported by PNG and that they correspond to 1, 2, 3, 4
2709        # channels respectively.  So we use the number of channels in
2710        # the source image to determine which one we have.  We do not
2711        # care about TUPLTYPE.
2712        greyscale = depth <= 2
2713        pamalpha = depth in (2,4)
2714        supported = map(lambda x: 2**x-1, range(1,17))
2715        try:
2716            mi = supported.index(maxval)
2717        except ValueError:
2718            raise NotImplementedError(
2719              'your maxval (%s) not in supported list %s' %
2720              (maxval, str(supported)))
2721        bitdepth = mi+1
2722        writer = Writer(width, height,
2723                        greyscale=greyscale,
2724                        bitdepth=bitdepth,
2725                        interlace=options.interlace,
2726                        transparent=options.transparent,
2727                        background=options.background,
2728                        alpha=bool(pamalpha or options.alpha),
2729                        gamma=options.gamma,
2730                        compression=options.compression)
2731        if options.alpha:
2732            pgmfile = open(options.alpha, 'rb')
2733            format, awidth, aheight, adepth, amaxval = \
2734              read_pnm_header(pgmfile, 'P5')
2735            if amaxval != '255':
2736                raise NotImplementedError(
2737                  'maxval %s not supported for alpha channel' % amaxval)
2738            if (awidth, aheight) != (width, height):
2739                raise ValueError("alpha channel image size mismatch"
2740                                 " (%s has %sx%s but %s has %sx%s)"
2741                                 % (infilename, width, height,
2742                                    options.alpha, awidth, aheight))
2743            writer.convert_ppm_and_pgm(infile, pgmfile, outfile)
2744        else:
2745            writer.convert_pnm(infile, outfile)
2746
2747
2748if __name__ == '__main__':
2749    try:
2750        _main(sys.argv)
2751    except Error, e:
2752        print >>sys.stderr, e
trunk/src/build/png2bdc.c
r242817r242818
1// license:BSD-3-Clause
2// copyright-holders:Aaron Giles
3/***************************************************************************
4
5    png2bdc.c
6
7    Super-simple PNG to BDC file generator
8
9****************************************************************************
10
11    Format of PNG data:
12
13    Multiple rows of characters. A black pixel means "on". All other colors
14    mean "off". Each row looks like this:
15
16    * 8888  ***    *
17    * 4444 *   *  **
18    * 2222 *   *   *
19    * 1111 *   *   *
20    *      *   *   *
21    **      ***   ***
22    *
23    *
24
25           ****** ****
26
27    The column of pixels on the left-hand side (column 0) indicates the
28    character cell height. This column must be present on each row and
29    the height must be consistent for each row.
30
31    Protruding one pixel into column 1 is the baseline indicator. There
32    should only be one row with a pixel in column 1 for each line, and
33    that pixel row index should be consistent for each row.
34
35    In columns 2-5 are a 4-hex-digit starting character index number. This
36    is encoded as binary value. Each column is 4 pixels tall and represents
37    one binary digit. The character index number is the unicode character
38    number of the first character encoded in this row; subsequent
39    characters in the row are at increasing character indices.
40
41    Starting in column 6 and beyond are the actual character bitmaps.
42    Below them, in the second row after the last row of the character,
43    is a solid line that indicates the width of the character, and also
44    where the character bitmap begins and ends.
45
46***************************************************************************/
47
48#include <stdio.h>
49#include <stdlib.h>
50#include <string.h>
51#include <ctype.h>
52#include <new>
53#include "png.h"
54
55
56//**************************************************************************
57//  CONSTANTS & DEFINES
58//**************************************************************************
59
60#define CACHED_CHAR_SIZE        12
61#define CACHED_HEADER_SIZE      16
62
63
64
65//**************************************************************************
66//  TYPE DEFINITIONS
67//**************************************************************************
68
69// a render_font contains information about a single character in a font
70struct render_font_char
71{
72   render_font_char() : width(0), xoffs(0), yoffs(0), bmwidth(0), bmheight(0) { }
73
74   INT32               width;              // width from this character to the next
75   INT32               xoffs, yoffs;       // X and Y offset from baseline to top,left of bitmap
76   INT32               bmwidth, bmheight;  // width and height of bitmap
77   bitmap_argb32 *     bitmap;             // pointer to the bitmap containing the raw data
78};
79
80
81// a render_font contains information about a font
82struct render_font
83{
84   render_font() : height(0), yoffs(0) { }
85
86   int                 height;             // height of the font, from ascent to descent
87   int                 yoffs;              // y offset from baseline to descent
88   render_font_char    chars[65536];       // array of characters
89};
90
91
92
93//**************************************************************************
94//  INLINE FUNCTIONS
95//**************************************************************************
96
97inline int pixel_is_set(bitmap_argb32 &bitmap, int y, int x)
98{
99   return (bitmap.pix32(y, x) & 0xffffff) == 0;
100}
101
102
103
104//**************************************************************************
105//  MAIN
106//**************************************************************************
107
108//-------------------------------------------------
109//  write_data - write data to the given file and
110//  throw an exception if an error occurs
111//-------------------------------------------------
112
113static void write_data(core_file &file, UINT8 *base, UINT8 *end)
114{
115   UINT32 bytes_written = core_fwrite(&file, base, end - base);
116   if (bytes_written != end - base)
117   {
118      fprintf(stderr, "Error writing to destination file\n");
119      throw;
120   }
121}
122
123
124//-------------------------------------------------
125//  render_font_save_cached - write the cached
126//  data out to the file
127//-------------------------------------------------
128
129static bool render_font_save_cached(render_font &font, const char *filename, UINT32 hash)
130{
131   // attempt to open the file
132   core_file *file;
133   file_error filerr = core_fopen(filename, OPEN_FLAG_WRITE | OPEN_FLAG_CREATE, &file);
134   if (filerr != FILERR_NONE)
135      return true;
136
137   try
138   {
139      // determine the number of characters
140      int numchars = 0;
141      for (int chnum = 0; chnum < 65536; chnum++)
142         if (font.chars[chnum].width > 0)
143            numchars++;
144
145      // write the header
146      dynamic_buffer tempbuffer(65536);
147      UINT8 *dest = &tempbuffer[0];
148      *dest++ = 'f';
149      *dest++ = 'o';
150      *dest++ = 'n';
151      *dest++ = 't';
152      *dest++ = hash >> 24;
153      *dest++ = hash >> 16;
154      *dest++ = hash >> 8;
155      *dest++ = hash & 0xff;
156      *dest++ = font.height >> 8;
157      *dest++ = font.height & 0xff;
158      *dest++ = font.yoffs >> 8;
159      *dest++ = font.yoffs & 0xff;
160      *dest++ = numchars >> 24;
161      *dest++ = numchars >> 16;
162      *dest++ = numchars >> 8;
163      *dest++ = numchars & 0xff;
164      write_data(*file, tempbuffer, dest);
165
166      // write the empty table to the beginning of the file
167      dynamic_buffer chartable(numchars * CACHED_CHAR_SIZE + 1, 0);
168      write_data(*file, &chartable[0], &chartable[numchars * CACHED_CHAR_SIZE]);
169
170      // loop over all characters
171      int tableindex = 0;
172      for (int chnum = 0; chnum < 65536; chnum++)
173      {
174         render_font_char &ch = font.chars[chnum];
175         if (ch.width > 0)
176         {
177            // write out a bit-compressed bitmap if we have one
178            if (ch.bitmap != NULL)
179            {
180               // write the data to the tempbuffer
181               dest = tempbuffer;
182               UINT8 accum = 0;
183               UINT8 accbit = 7;
184
185               // bit-encode the character data
186               for (int y = 0; y < ch.bmheight; y++)
187               {
188                  int desty = y + font.height + font.yoffs - ch.yoffs - ch.bmheight;
189                  const UINT32 *src = (desty >= 0 && desty < font.height) ? &ch.bitmap->pix32(desty) : NULL;
190                  for (int x = 0; x < ch.bmwidth; x++)
191                  {
192                     if (src != NULL && src[x] != 0)
193                        accum |= 1 << accbit;
194                     if (accbit-- == 0)
195                     {
196                        *dest++ = accum;
197                        accum = 0;
198                        accbit = 7;
199                     }
200                  }
201               }
202
203               // flush any extra
204               if (accbit != 7)
205                  *dest++ = accum;
206
207               // write the data
208               write_data(*file, tempbuffer, dest);
209
210               // free the bitmap and texture
211               global_free(ch.bitmap);
212               ch.bitmap = NULL;
213            }
214
215            // compute the table entry
216            dest = &chartable[tableindex++ * CACHED_CHAR_SIZE];
217            *dest++ = chnum >> 8;
218            *dest++ = chnum & 0xff;
219            *dest++ = ch.width >> 8;
220            *dest++ = ch.width & 0xff;
221            *dest++ = ch.xoffs >> 8;
222            *dest++ = ch.xoffs & 0xff;
223            *dest++ = ch.yoffs >> 8;
224            *dest++ = ch.yoffs & 0xff;
225            *dest++ = ch.bmwidth >> 8;
226            *dest++ = ch.bmwidth & 0xff;
227            *dest++ = ch.bmheight >> 8;
228            *dest++ = ch.bmheight & 0xff;
229         }
230      }
231
232      // seek back to the beginning and rewrite the table
233      core_fseek(file, CACHED_HEADER_SIZE, SEEK_SET);
234      write_data(*file, &chartable[0], &chartable[numchars * CACHED_CHAR_SIZE]);
235
236      // all done
237      core_fclose(file);
238      return false;
239   }
240   catch (...)
241   {
242      core_fclose(file);
243      osd_rmfile(filename);
244      return true;
245   }
246}
247
248
249//-------------------------------------------------
250//  bitmap_to_chars - convert a bitmap to
251//  characters in the given font
252//-------------------------------------------------
253
254static bool bitmap_to_chars(bitmap_argb32 &bitmap, render_font &font)
255{
256   // loop over rows
257   int rowstart = 0;
258   while (rowstart < bitmap.height())
259   {
260      // find the top of the row
261      for ( ; rowstart < bitmap.height(); rowstart++)
262         if (pixel_is_set(bitmap, rowstart, 0))
263            break;
264      if (rowstart >= bitmap.height())
265         break;
266
267      // find the bottom of the row
268      int rowend;
269      for (rowend = rowstart + 1; rowend < bitmap.height(); rowend++)
270         if (!pixel_is_set(bitmap, rowend, 0))
271         {
272            rowend--;
273            break;
274         }
275
276      // find the baseline
277      int baseline;
278      for (baseline = rowstart; baseline <= rowend; baseline++)
279         if (pixel_is_set(bitmap, baseline, 1))
280            break;
281      if (baseline > rowend)
282      {
283         fprintf(stderr, "No baseline found between rows %d-%d\n", rowstart, rowend);
284         break;
285      }
286
287      // set or confirm the height
288      if (font.height == 0)
289      {
290         font.height = rowend - rowstart + 1;
291         font.yoffs = baseline - rowend;
292      }
293      else
294      {
295         if (font.height != rowend - rowstart + 1)
296         {
297            fprintf(stderr, "Inconsistent font height at rows %d-%d\n", rowstart, rowend);
298            break;
299         }
300         if (font.yoffs != baseline - rowend)
301         {
302            fprintf(stderr, "Inconsistent baseline at rows %d-%d\n", rowstart, rowend);
303            break;
304         }
305      }
306
307      // decode the starting character
308      int chstart = 0;
309      for (int x = 0; x < 4; x++)
310         for (int y = 0; y < 4; y++)
311            chstart = (chstart << 1) | pixel_is_set(bitmap, rowstart + y, 2 + x);
312
313      // print info
314//      printf("Row %d-%d, baseline %d, character start %X\n", rowstart, rowend, baseline, chstart);
315
316      // scan the column to find characters
317      int colstart = 0;
318      while (colstart < bitmap.width())
319      {
320         render_font_char &ch = font.chars[chstart];
321
322         // find the start of the character
323         for ( ; colstart < bitmap.width(); colstart++)
324            if (pixel_is_set(bitmap, rowend + 2, colstart))
325               break;
326         if (colstart >= bitmap.width())
327            break;
328
329         // find the end of the character
330         int colend;
331         for (colend = colstart + 1; colend < bitmap.width(); colend++)
332            if (!pixel_is_set(bitmap, rowend + 2, colend))
333            {
334               colend--;
335               break;
336            }
337
338         // skip char which code is already registered
339         if (ch.width <= 0)
340         {
341            // print info
342//              printf("  Character %X - width = %d\n", chstart, colend - colstart + 1);
343
344            // allocate a bitmap
345            ch.bitmap = global_alloc(bitmap_argb32(colend - colstart + 1, font.height));
346
347            // plot the character
348            for (int y = rowstart; y <= rowend; y++)
349               for (int x = colstart; x <= colend; x++)
350                  ch.bitmap->pix32(y - rowstart, x - colstart) = pixel_is_set(bitmap, y, x) ? 0xffffffff : 0x00000000;
351
352            // set the character parameters
353            ch.width = colend - colstart + 1;
354            ch.xoffs = 0;
355            ch.yoffs = font.yoffs;
356            ch.bmwidth = ch.bitmap->width();
357            ch.bmheight = ch.bitmap->height();
358         }
359
360         // next character
361         chstart++;
362         colstart = colend + 1;
363      }
364
365      // next row
366      rowstart = rowend + 1;
367   }
368
369   // return non-zero (TRUE) if we errored
370   return (rowstart < bitmap.height());
371}
372
373
374//-------------------------------------------------
375//  main - main entry point
376//-------------------------------------------------
377
378int main(int argc, char *argv[])
379{
380   // validate arguments
381   if (argc < 3)
382   {
383      fprintf(stderr, "Usage:\n%s <input.png> [<input2.png> [...]] <output.bdc>\n", argv[0]);
384      return 1;
385   }
386   const char *bdcname = argv[argc - 1];
387
388   // iterate over input files
389   static render_font font;
390   bool error = false;
391   for (int curarg = 1; curarg < argc - 1; curarg++)
392   {
393      // load the png file
394      const char *pngname = argv[curarg];
395      core_file *file;
396      file_error filerr = core_fopen(pngname, OPEN_FLAG_READ, &file);
397      if (filerr != FILERR_NONE)
398      {
399         fprintf(stderr, "Error %d attempting to open PNG file\n", filerr);
400         error = true;
401         break;
402      }
403
404      bitmap_argb32 bitmap;
405      png_error pngerr = png_read_bitmap(file, bitmap);
406      core_fclose(file);
407      if (pngerr != PNGERR_NONE)
408      {
409         fprintf(stderr, "Error %d reading PNG file\n", pngerr);
410         error = true;
411         break;
412      }
413
414      // parse the PNG into characters
415      error = bitmap_to_chars(bitmap, font);
416      if (error)
417         break;
418   }
419
420   // write out the resulting font
421   if (!error)
422      error = render_font_save_cached(font, bdcname, 0);
423
424   // cleanup after ourselves
425   return error ? 1 : 0;
426}
trunk/src/build/png2bdc.py
r0r242818
1#!/usr/bin/env python
2##
3## license:BSD-3-Clause
4## copyright-holders:Aaron Giles, Andrew Gardner
5## ****************************************************************************
6##
7##    png2bdc.c
8##
9##    Super-simple PNG to BDC file generator
10##
11## ****************************************************************************
12##
13##    Format of PNG data:
14##
15##    Multiple rows of characters. A black pixel means "on". All other colors
16##    mean "off". Each row looks like this:
17##
18##    * 8888  ***    *
19##    * 4444 *   *  **
20##    * 2222 *   *   *
21##    * 1111 *   *   *
22##    *      *   *   *
23##    **      ***   ***
24##    *
25##    *
26##
27##           ****** ****
28##
29##    The column of pixels on the left-hand side (column 0) indicates the
30##    character cell height. This column must be present on each row and
31##    the height must be consistent for each row.
32##
33##    Protruding one pixel into column 1 is the baseline indicator. There
34##    should only be one row with a pixel in column 1 for each line, and
35##    that pixel row index should be consistent for each row.
36##
37##    In columns 2-5 are a 4-hex-digit starting character index number. This
38##    is encoded as binary value. Each column is 4 pixels tall and represents
39##    one binary digit. The character index number is the unicode character
40##    number of the first character encoded in this row; subsequent
41##    characters in the row are at increasing character indices.
42##
43##    Starting in column 6 and beyond are the actual character bitmaps.
44##    Below them, in the second row after the last row of the character,
45##    is a solid line that indicates the width of the character, and also
46##    where the character bitmap begins and ends.
47##
48## ***************************************************************************
49
50##
51## Python note:
52##   This is a near-literal translation of the original C++ code.  As such there
53##   are some very non-pythonic things done throughout.  The conversion was done
54##   this way so as to insure compatibility as much as possible given the small
55##   number of test cases.
56##
57
58import os
59import png
60import sys
61
62
63########################################
64## Helper classes
65########################################
66class RenderFontChar:
67    """
68    Contains information about a single character in a font.
69    """
70   
71    def __init__(self):
72        """
73        """
74        self.width = 0          # width from this character to the next
75        self.xOffs = 0          # X offset from baseline to top,left of bitmap
76        self.yOffs = 0          # Y offset from baseline to top,left of bitmap
77        self.bmWidth = 0        # width of bitmap
78        self.bmHeight = 0       # height of bitmap
79        self.bitmap = None      # pointer to the bitmap containing the raw data
80
81
82class RenderFont:
83    """
84    Contains information about a font
85    """
86   
87    def __init__(self):
88        self.height = 0         # height of the font, from ascent to descent
89        self.yOffs = 0          # y offset from baseline to descent
90        self.chars = list()     # array of characters
91        for i in range(0, 65536):
92            self.chars.append(RenderFontChar())
93
94
95
96########################################
97## Helper functions
98########################################
99def pixelIsSet(value):
100    return (value & 0xffffff) == 0
101
102
103def renderFontSaveCached(font, filename, hash32):
104    """
105    """
106    fp = open(filename, "wb")
107    if not fp:
108        return 1
109
110    # Write the header
111    numChars = 0
112    for c in font.chars:
113        if c.width > 0:
114            numChars += 1
115
116    CACHED_CHAR_SIZE = 12
117    CACHED_HEADER_SIZE = 16
118   
119    try:
120        fp.write('f')
121        fp.write('o')
122        fp.write('n')
123        fp.write('t')
124        fp.write(bytearray([hash32 >> 24 & 0xff]))
125        fp.write(bytearray([hash32 >> 16 & 0xff]))
126        fp.write(bytearray([hash32 >> 8 & 0xff]))
127        fp.write(bytearray([hash32 >> 0 & 0xff]))
128        fp.write(bytearray([font.height >> 8 & 0xff]))
129        fp.write(bytearray([font.height >> 0 & 0xff]))
130        fp.write(bytearray([font.yOffs >> 8 & 0xff]))
131        fp.write(bytearray([font.yOffs >> 0 & 0xff]))
132        fp.write(bytearray([numChars >> 24 & 0xff]))
133        fp.write(bytearray([numChars >> 16 & 0xff]))
134        fp.write(bytearray([numChars >> 8 & 0xff]))
135        fp.write(bytearray([numChars >> 0 & 0xff]))
136       
137        # Write a blank table at first (?)
138        charTable = [0]*(numChars * CACHED_CHAR_SIZE)
139        fp.write(bytearray(charTable))
140       
141        # Loop over all characters
142        tableIndex = 0
143       
144        for i in range(len(font.chars)):
145            c = font.chars[i]
146            if c.width == 0:
147                continue
148           
149            if c.bitmap:
150                dBuffer = list()
151                accum = 0
152                accbit = 7
153   
154                # Bit-encode the character data
155                for y in range(0, c.bmHeight):
156                    src = None
157                    desty = y + font.height + font.yOffs - c.yOffs - c.bmHeight
158                    if desty >= 0 and desty < font.height:
159                        src = c.bitmap[desty]
160                    for x in range(0, c.bmWidth):
161                        if src is not None and src[x] != 0:
162                            accum |= 1 << accbit
163                        accbit -= 1
164                        if accbit+1 == 0:
165                            dBuffer.append(accum)
166                            accum = 0
167                            accbit = 7
168               
169                # Flush any extra
170                if accbit != 7:
171                    dBuffer.append(accum)
172               
173                # Write the data
174                fp.write(bytearray(dBuffer))
175           
176            destIndex = tableIndex * CACHED_CHAR_SIZE
177            charTable[destIndex +  0] = i >> 8 & 0xff
178            charTable[destIndex +  1] = i >> 0 & 0xff
179            charTable[destIndex +  2] = c.width >> 8 & 0xff
180            charTable[destIndex +  3] = c.width >> 0 & 0xff
181            charTable[destIndex +  4] = c.xOffs >> 8 & 0xff
182            charTable[destIndex +  5] = c.xOffs >> 0 & 0xff
183            charTable[destIndex +  6] = c.yOffs >> 8 & 0xff
184            charTable[destIndex +  7] = c.yOffs >> 0 & 0xff
185            charTable[destIndex +  8] = c.bmWidth >> 8 & 0xff
186            charTable[destIndex +  9] = c.bmWidth >> 0 & 0xff
187            charTable[destIndex + 10] = c.bmHeight >> 8 & 0xff
188            charTable[destIndex + 11] = c.bmHeight >> 0 & 0xff
189            tableIndex += 1
190   
191        # Seek back to the beginning and rewrite the table
192        fp.seek(CACHED_HEADER_SIZE, 0)
193        fp.write(bytearray(charTable))
194   
195        fp.close()
196        return 0
197
198    except:
199        return 1
200   
201
202def bitmapToChars(pngObject, font):
203    """
204    Convert a bitmap to characters in the given font
205    """
206    # Just cache the bitmap into a list of lists since random access is paramount
207    bitmap = list()
208    width = pngObject.asRGBA8()[0]
209    height = pngObject.asRGBA8()[1]
210    rowGenerator = pngObject.asRGBA8()[2]
211    for row in rowGenerator:
212        cRow = list()
213        irpd = iter(row)
214        for r,g,b,a in zip(irpd, irpd, irpd, irpd):
215            cRow.append(a << 24 | r << 16 | g << 8 | b)
216        bitmap.append(cRow)
217   
218    rowStart = 0
219    while rowStart < height:
220        # Find the top of the row
221        for i in range(rowStart, height):
222            if pixelIsSet(bitmap[rowStart][0]):
223                break
224            rowStart += 1
225        if rowStart >= height:
226            break
227
228        # Find the bottom of the row
229        rowEnd = rowStart + 1
230        for i in range(rowEnd, height):
231            if not pixelIsSet(bitmap[rowEnd][0]):
232                rowEnd -= 1
233                break
234            rowEnd += 1
235
236        # Find the baseline
237        baseline = rowStart
238        for i in range(rowStart, rowEnd+1):
239            if pixelIsSet(bitmap[baseline][1]):
240                break
241            baseline += 1
242        if baseline > rowEnd:
243            sys.stderr.write("No baseline found between rows %d-%d\n" % (rowStart, rowEnd))
244            break
245
246        # Set or confirm the height
247        if font.height == 0:
248            font.height = rowEnd - rowStart + 1
249            font.yOffs = baseline - rowEnd
250        else:
251            if font.height != (rowEnd - rowStart + 1):
252                sys.stderr.write("Inconsistent font height at rows %d-%d\n" % (rowStart, rowEnd))
253                break
254            if font.yOffs != (baseline - rowEnd):
255                sys.stderr.write("Inconsistent baseline at rows %d-%d\n" % (rowStart, rowEnd))
256                break
257
258        # decode the starting character
259        chStart = 0
260        for x in range(0, 4):
261            for y in range(0, 4):
262                chStart = (chStart << 1) | pixelIsSet(bitmap[rowStart+y][2+x])
263
264        # Print debug info
265        # print("Row %d-%d, baseline %d, character start %X" % (rowStart, rowEnd, baseline, chStart))
266
267        # scan the column to find characters
268        colStart = 0
269        while (colStart < width):
270            ch = RenderFontChar()
271
272            # Find the start of the character
273            for i in range(colStart, width):
274                if pixelIsSet(bitmap[rowEnd+2][colStart]):
275                    break
276                colStart += 1
277            if colStart >= width:
278                break
279
280            # Find the end of the character
281            colEnd = colStart + 1
282            for i in range(colEnd, width):
283                if not pixelIsSet(bitmap[rowEnd+2][colEnd]):
284                    colEnd -= 1
285                    break
286                colEnd += 1
287
288            # Skip char which code is already registered
289            if ch.width <= 0:
290                # Print debug info
291                # print "  Character %X - width = %d" % (chStart, colEnd - colStart + 1)
292
293                # Plot the character
294                ch.bitmap = list()
295                for y in range(rowStart, rowEnd+1):
296                    ch.bitmap.append(list())
297                    for x in range(colStart, colEnd+1):
298                        if pixelIsSet(bitmap[y][x]):
299                            ch.bitmap[-1].append(0xffffffff)
300                        else:
301                            ch.bitmap[-1].append(0x00000000)
302
303                # Set the character parameters
304                ch.width = colEnd - colStart + 1
305                ch.xOffs = 0
306                ch.yOffs = font.yOffs
307                ch.bmWidth = len(ch.bitmap[0])
308                ch.bmHeight = len(ch.bitmap)
309               
310                # Insert the character into the list
311                font.chars[chStart] = ch
312
313            # Next character
314            chStart += 1
315            colStart = colEnd + 1
316
317        # Next row
318        rowStart = rowEnd + 1
319   
320    # Return non-zero if we errored
321    return (rowStart < height)
322
323
324
325########################################
326## Main
327########################################
328def main():
329    if len(sys.argv) < 3:
330        sys.stderr.write("Usage:\n%s <input.png> [<input2.png> [...]] <output.bdc>\n" % sys.argv[0])
331        return 1
332    bdcName = sys.argv[-1]
333   
334    font = RenderFont()
335    for i in range(1, len(sys.argv)-1):
336        filename = sys.argv[i]
337    if not os.path.exists(filename):
338        sys.stderr.write("Error attempting to open PNG file.\n")
339        return 1
340
341    pngObject = png.Reader(filename)
342    try:
343        pngObject.validate_signature()
344    except:
345        sys.stderr.write("Error reading PNG file.\n")
346        return 1
347           
348    error = bitmapToChars(pngObject, font)
349    if error:
350        return 1
351   
352    error = renderFontSaveCached(font, bdcName, 0)
353    return error
354   
355   
356   
357########################################
358## Program entry point
359########################################
360if __name__ == "__main__":
361    sys.exit(main())


Previous 199869 Revisions Next


© 1997-2024 The MAME Team