Skip to content
io
png

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 hard write_png works to make the file small.

Functions