Skip to content

Commit 3c6f522

Browse files
committed
Add PNG support
This is a first stab at adding PNG support. For now there are no filters, so only indexed images will work.
1 parent 69636ff commit 3c6f522

File tree

2 files changed

+83
-0
lines changed

2 files changed

+83
-0
lines changed

adafruit_imageload/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,4 +80,8 @@ def load(
8080
from . import gif
8181

8282
return gif.load(file, bitmap=bitmap, palette=palette)
83+
if header.startswith(b'\x89PN'):
84+
from . import png
85+
86+
return png.load(file, bitmap=bitmap, palette=palette)
8387
raise RuntimeError("Unsupported image format")

adafruit_imageload/png.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
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

Comments
 (0)