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