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