Last modified: Thu Jul 23 14:45:03 UTC+0200 2026 © A. Tarpai
A PNG decoder in C
2015 A. Tarpai
Source code is in a repository on GitHub.
Thanks to Willem van Schaik's PngSuite I could test the decoder on a bunch of images.
This is a snapshot of my folder with PNG and decoded-converted BMP equivalents of supported images by this decoder:
Supported PNG types:
- all png pixel formats
- greyscale
- 1,2,4 and 8/16 bit
- 8/16 bit with alpha
- truecolor (RGB)
- 8/16 bit
- 8/16 bit with alpha
- indexed-color (1,2,4,8 bit)
- greyscale
- alpha blends on white (this is a test only)
- interlacing
- all filter types
- multiple IDAT chunks
Limitations:
- supports only critical chunks
- no transparency - client dependent anyway
- no background chunk support
- no gamma support
- no corrupted PNG error handling
The decoder converts a PNG file to BMP file in-memory:
+---------------------------------+ Decoder +-------------------+
| PNG FILE | ----------------> | |
+---------------------------------+ | BMP |
| FILE |
| |
| |
| |
+-------------------+
BMP output of this decoder
BMP is one of these 4 direct-color formats:
- BM-format RGB888 (aka RGB24)
- BM-format RGB8888 (aka RGB32)
- BM-format RGB565 (aka RGB16)
- BM-format RGB555 (aka RGB15)
Why BMP memory output?
- standard format with a small HEADER
- uncompressed pixel data
- BITMAP is ready to Blit to frame buffer
- or save the whole BMP to disk
Whatever the PNG pixel format is, it will write true-color (namely direct-color) pixels in BM-format from a given memory location.
This is set when calling PNGDecode() passing a BITPERPIXELENUM.
Note that scanlines are DWORD-padded (with possible x-padding).
This is exactly the PC video hardware frame buffer structure (I started image decoders for a GUI on PC), hence the 4 versions:
The BM pixel format:
BITPERPIXELENUM24
BM-format RGB888 (aka RGB24)
B G R
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - x x x
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - x x x
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - x x x
BITPERPIXELENUM32
BM-format RGB8888 (aka RGB32)
B G R A
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
BITPERPIXELENUM16
BM-format RGB565 (aka RGB16)
bgr
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - x x
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - x x
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - x x
BITPERPIXELENUM15
BM-format RGB555 (aka RGB15)
bgr
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - x x
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - x x
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - x x
Decoder - big picture
+---------------------------------+ Decoder +-------------------+
| PNG FILE | ----------------> | BMP HEADER |
+---------------------------------+ | |-------------------|
| | |
| | BITMAP |
+----------+ | |
| buffers | | |
| | +-------------------+
+----------+ In memory
- DEFLATE - true-color 15/16/24/32-bpp
- 2 x Image-line - DWORD-paddes scanlines
This is a per-image-line decoder
But fully inflates PNG image data before conversion. (For large PNG-s, I also experimented with a byte-by-byte inflate using call-backs, the zlib-window. It reduced memory allocations, but simply made inflate.c ugly for me.)
+-------------------+
| |
----------------+ filter decoded pixel data convert | |
inflated data | ----------------> +--------------------+ --------> |xxxxxxxxxxxxxxxxxxx|
----------------+ +--------------------+ | |
|| | |
|| | |
+--------------------+ +-------------------+
+--------------------+ BITMAP
image line
filter buffer
