Skip to content

Commit 34f548e

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 34f548e

File tree

2 files changed

+85
-0
lines changed

2 files changed

+85
-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: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
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", file.read(8))
44+
if chunk == b'IHDR':
45+
(width, height, depth, mode, compression, filters,
46+
interlaced) = struct.unpack(">IIBBBBB", file.read(13))
47+
if interlaced:
48+
raise NotImplementedError("Interlaced images unsupported")
49+
# compression and filters must be 0 with current spec
50+
assert compression == 0
51+
assert filters == 0
52+
elif chunk == b'PLTE':
53+
if palette is None:
54+
file.seek(size, 1)
55+
else:
56+
if mode != 3:
57+
raise NotImplementedError("Palette in non-indexed image")
58+
pal_size = size // 3
59+
pal = palette(pal_size)
60+
for i in range(pal_size):
61+
pal[i] = file.read(3)
62+
elif chunk == b'IDAT':
63+
data.extend(file.read(size))
64+
elif chunk == b'IEND':
65+
break
66+
else:
67+
file.seek(size, 1) # skip unknown chunks
68+
file.seek(4, 1) # skip CRC
69+
data = zlib.decompress(data)
70+
bmp = bitmap(width, height, 1 << depth)
71+
scanline = (width * depth + 7) // 8
72+
mem = memoryview(bmp)
73+
for y in range(height):
74+
dst = y * scanline
75+
src = y * (scanline + 1) + 1
76+
filter_ = data[src - 1]
77+
if filter_ == 0:
78+
mem[dst:dst + scanline] = data[src:src + scanline]
79+
else:
80+
raise NotImplementedError("Filters not supported")
81+
return bmp, pal

0 commit comments

Comments
 (0)