|
| 1 | +# SPDX-FileCopyrightText: 2022 Radomir Dopieralski |
| 2 | +# |
| 3 | +# SPDX-License-Identifier: MIT |
| 4 | + |
| 5 | +""" |
| 6 | +`adafruit_imageload.png` |
| 7 | +==================================================== |
| 8 | +
|
| 9 | +Load pixel values (indices or colors) into a bitmap and colors into a palette |
| 10 | +from a PNG file. |
| 11 | +
|
| 12 | +* Author(s): Radomir Dopieralski |
| 13 | +
|
| 14 | +""" |
| 15 | + |
| 16 | +import struct |
| 17 | +import zlib |
| 18 | + |
| 19 | + |
| 20 | +__version__ = "0.0.0-auto.0" |
| 21 | +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" |
| 22 | + |
| 23 | + |
| 24 | +def load(file, *, bitmap, palette=None): # pylint: disable=too-many-locals,too-many-branches |
| 25 | + """Loads a PNG image from the open ``file``. |
| 26 | +
|
| 27 | + Returns tuple of bitmap object and palette object. |
| 28 | +
|
| 29 | + :param file: The *.png file being loaded |
| 30 | + :param object bitmap: Type to store bitmap data. Must have API similar to |
| 31 | + `displayio.Bitmap`. |
| 32 | + :param object palette: Type to store the palette. Must have API similar to |
| 33 | + `displayio.Palette`. Will be skipped if None""" |
| 34 | + header = file.read(8) |
| 35 | + if header != b'\x89PNG\r\n\x1a\n': |
| 36 | + raise ValueError("Not a PNG file") |
| 37 | + del header |
| 38 | + data = bytearray() |
| 39 | + pal = None |
| 40 | + mode = None |
| 41 | + depth = None |
| 42 | + while True: |
| 43 | + size, chunk = struct.unpack(">I4s", f.read(8)) |
| 44 | + if chunk == b'IHDR': |
| 45 | + (width, height, depth, mode, compression, filters, |
| 46 | + interlaced) = struct.unpack(">IIBBBBB", f.read(13)) # pylint: disable=unused-variable |
| 47 | + if interlaced: |
| 48 | + raise NotImplementedError("Interlaced images unsupported") |
| 49 | + # compression and filters must be 0 with current spec |
| 50 | + elif chunk == b'PLTE': |
| 51 | + if palette is None: |
| 52 | + file.seek(size, 1) |
| 53 | + else: |
| 54 | + if mode != 3: |
| 55 | + raise NotImplementedError("Palette in non-indexed image") |
| 56 | + pal_size = size // 3 |
| 57 | + pal = palette(pal_size) |
| 58 | + for i in range(pal_size): |
| 59 | + pal[i] = file.read(3) |
| 60 | + elif chunk == b'IDAT': |
| 61 | + data.extend(file.read(size)) |
| 62 | + elif chunk == b'IEND': |
| 63 | + break |
| 64 | + else: |
| 65 | + file.seek(size, 1) # skip unknown chunks |
| 66 | + file.seek(4, 1) # skip CRC |
| 67 | + data = zlib.decompress(data) |
| 68 | + bmp = bitmap(width, height, 1 << depth) |
| 69 | + scanline = (width * depth + 7) // 8 |
| 70 | + mem = memoryview(bmp) |
| 71 | + for y in range(height): |
| 72 | + dst = y * scanline |
| 73 | + src = y * (scanline + 1) + 1 |
| 74 | + filter = data[src - 1] |
| 75 | + if filter == 0: |
| 76 | + mem[dst:dst + scanline] = data[src:src + scanline] |
| 77 | + else: |
| 78 | + raise NotImplementedError("Filters not supported") |
| 79 | + return bmp, pal |
0 commit comments