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