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