png
Mojo module 🡭
png
Read and write PNG files, stdlib-only, with no zlib/libpng dependency.
PNG’s image data is a zlib stream (RFC 1950) around a DEFLATE stream
(RFC 1951). Both directions go through canvas/io/deflate.mojo.
Two checksums, from the PNG spec Appendix D (CRC-32) and RFC 1950
section 9 (Adler-32). read_png checks both on every file it reads –
chunk CRC-32s, and the decompressed data’s Adler-32 against the zlib
trailer.
Watch the two byte orders: PNG’s chunk framing (length, CRC-32) and the zlib Adler-32 trailer are big-endian, but DEFLATE’s stored-block LEN/NLEN fields are little-endian.
write_png emits color type 6 (truecolor + alpha) when the canvas
contains a pixel that is not fully opaque, and color type 2 otherwise.
Rows go out either unfiltered or Sub-filtered (spec section 9),
whichever compresses smaller, and the choice is made on a sample:
every eighth row is compressed both ways, then the whole image is
compressed once in the encoding that sample picked. PngLevel sets
how much of that search happens – FAST skips it outright on an
image flat enough for the answer not to be in doubt, SMALL
compresses both encodings in full rather than sampling.
read_png accepts color types 0/2/4/6 at 8 or 16 bits and indexed
color (type 3, PLTE with an optional tRNS) at 1/2/4/8 bits,
Adam7-interlaced or not. A 16-bit sample reduces to 8 by
round(v * 255 / 65535), and a tRNS color key is compared against
the sample before that reduction, since two 16-bit values a level
apart can share an 8-bit result. Grayscale below 8 bits is the one
combination the spec allows and this does not; it raises, as an
unknown bit depth, color type or interlace method does, rather than
misreading pixels. Alpha is preserved in both directions, so an 8-bit
file round-trips through read_png -> write_png unchanged.
Structs
PngLevel: How hardwrite_pngworks to make the file small.