pypng
| Language | Python 3 (pure Python) |
|---|---|
| License | MIT |
| Repository | gitlab.com/drj11/pypng |
| Documentation | PyPI page, docstrings in png.py |
| Depends on | nothing (uses the standard zlib module) |
| Read / write | yes / yes, all color types and bit depths 1-16; text chunks only as raw chunks |
pypng is a single Python module, png.py, that reads and writes PNG without any C extension. It supports every color type and every bit depth, including 16-bit RGB and RGBA, which Pillow reduces to 8 bits. Pixels are plain Python sequences: one row is a list or array of channel values.
It has no image operations (resize, draw, convert to JPEG) and it’s much slower than Pillow, so use it for exact control over the PNG you write, for 16-bit data, or where you can’t install binary packages.
Installation#
pip install pypng
The module is imported as png.
Basic usage: read and write a PNG#
import png
# read: rows is an iterator of rows, each row a sequence of channel values
width, height, rows, info = png.Reader(filename="in.png").asRGBA8()
print(width, height, info["bitdepth"], info["alpha"])
# invert colors, keep alpha
out_rows = []
for row in rows:
row = list(row)
for i in range(0, len(row), 4):
row[i] = 255 - row[i]
row[i + 1] = 255 - row[i + 1]
row[i + 2] = 255 - row[i + 2]
out_rows.append(row)
writer = png.Writer(width, height, greyscale=False, alpha=True, bitdepth=8)
with open("out.png", "wb") as f:
writer.write(f, out_rows)
asRGBA8() converts any PNG to 8-bit RGBA. read() returns the pixels in the file’s own format, and asDirect() expands palettes but keeps the bit depth.
How to write a 16-bit grayscale PNG#
import png
width, height = 256, 64
rows = [[x * 257 for x in range(width)] for _ in range(height)] # 0 .. 65535
writer = png.Writer(width, height, greyscale=True, bitdepth=16)
with open("gray16.png", "wb") as f:
writer.write(f, rows)
How to write a palette PNG#
Each pixel is an index into the palette. Entries with 4 values (RGBA) add transparency through a tRNS chunk; they must come before all 3-value entries:
import png
palette = [(0, 0, 255, 128), (0, 0, 0), (255, 0, 0)] # half-transparent blue, black, red
rows = [
[0, 1, 2, 1],
[1, 2, 1, 0],
]
writer = png.Writer(4, 2, palette=palette, bitdepth=2)
with open("palette.png", "wb") as f:
writer.write(f, rows)
How to write a PNG from a 2D list in one line#
png.from_array takes rows and a mode string (L, LA, RGB, RGBA, optionally with ;16 for 16-bit):
import png
png.from_array([[0, 128, 255], [255, 128, 0]], "L").save("small.png")
How to list chunks and read text chunks#
pypng doesn’t decode text chunks, but it gives you every raw chunk:
import png
reader = png.Reader(filename="in.png")
for chunk_type, data in reader.chunks():
print(chunk_type, len(data))
if chunk_type == b"tEXt":
key, _, value = data.partition(b"\x00")
print(" ", key.decode("latin-1"), "=", value.decode("latin-1"))
Tips#
png.Writer(..., compression=9)sets the zlib level.- Rows can be any iterable, so you can write huge images by generating rows lazily.
- With NumPy, pass
image.reshape(height, -1)as rows and read withnumpy.vstack(list(rows)).