From 9dd531559fc782ba52df996bd6c618edf292150c Mon Sep 17 00:00:00 2001 From: Radomir Dopieralski Date: Mon, 1 Aug 2022 14:11:03 +0200 Subject: [PATCH] Add PNG support This is a first stab at adding PNG support. For now there are no filters, so only indexed images will work. --- adafruit_imageload/__init__.py | 4 ++ adafruit_imageload/png.py | 90 ++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 adafruit_imageload/png.py diff --git a/adafruit_imageload/__init__.py b/adafruit_imageload/__init__.py index 6aec02d..1eeaed9 100644 --- a/adafruit_imageload/__init__.py +++ b/adafruit_imageload/__init__.py @@ -80,4 +80,8 @@ def load( from . import gif return gif.load(file, bitmap=bitmap, palette=palette) + if header.startswith(b"\x89PN"): + from . import png + + return png.load(file, bitmap=bitmap, palette=palette) raise RuntimeError("Unsupported image format") diff --git a/adafruit_imageload/png.py b/adafruit_imageload/png.py new file mode 100644 index 0000000..0fdc056 --- /dev/null +++ b/adafruit_imageload/png.py @@ -0,0 +1,90 @@ +# SPDX-FileCopyrightText: 2022 Radomir Dopieralski +# +# SPDX-License-Identifier: MIT + +""" +`adafruit_imageload.png` +==================================================== + +Load pixel values (indices or colors) into a bitmap and colors into a palette +from a PNG file. + +* Author(s): Radomir Dopieralski + +""" + +import struct +import zlib + + +__version__ = "0.0.0-auto.0" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" + + +def load( + file, *, bitmap, palette=None +): # pylint: disable=too-many-locals,too-many-branches + """Loads a PNG image from the open ``file``. + + Returns tuple of bitmap object and palette object. + + :param file: The *.png file being loaded + :param object bitmap: Type to store bitmap data. Must have API similar to + `displayio.Bitmap`. + :param object palette: Type to store the palette. Must have API similar to + `displayio.Palette`. Will be skipped if None""" + header = file.read(8) + if header != b"\x89PNG\r\n\x1a\n": + raise ValueError("Not a PNG file") + del header + data = bytearray() + pal = None + mode = None + depth = None + while True: + size, chunk = struct.unpack(">I4s", file.read(8)) + if chunk == b"IHDR": + ( + width, + height, + depth, + mode, + compression, + filters, + interlaced, + ) = struct.unpack(">IIBBBBB", file.read(13)) + if interlaced: + raise NotImplementedError("Interlaced images unsupported") + # compression and filters must be 0 with current spec + assert compression == 0 + assert filters == 0 + elif chunk == b"PLTE": + if palette is None: + file.seek(size, 1) + else: + if mode != 3: + raise NotImplementedError("Palette in non-indexed image") + pal_size = size // 3 + pal = palette(pal_size) + for i in range(pal_size): + pal[i] = file.read(3) + elif chunk == b"IDAT": + data.extend(file.read(size)) + elif chunk == b"IEND": + break + else: + file.seek(size, 1) # skip unknown chunks + file.seek(4, 1) # skip CRC + data = zlib.decompress(data) + bmp = bitmap(width, height, 1 << depth) + scanline = (width * depth + 7) // 8 + mem = memoryview(bmp) + for y in range(height): + dst = y * scanline + src = y * (scanline + 1) + 1 + filter_ = data[src - 1] + if filter_ == 0: + mem[dst : dst + scanline] = data[src : src + scanline] + else: + raise NotImplementedError("Filters not supported") + return bmp, pal