The library provides a Bitmap class with a simple interface:
|create bitmap of specified size|
|set pixel colour, components in the range [0, 1]|
|scale down by integer factor n|
|generate URL which can be used as <img> src|
In the Bitmap class colour values are represented linearly by floats in the range [0, 1]. Linear encoding makes physically-correct compositing simpler (double the value means double the brightness) but it is not intuitive, because ordinarily we deal with gamma-encoded values. For example, the medium grey colour #808080 actually has only about 21% of the brightness of the colour white.
In the PNG image colour values are represented by one of 256 discrete values. To give more sample space to darker colours, we apply a power function with an exponent smaller than 1, which is the encoder gamma value. Any gamma value can be chosen and specified in the file. We use the default value of 1/2.2 that decoders usually assume if a value is not specified.
Pixel data is stored in the PNG image as a sequence of rows like this:
The 0 at the front of each row specifies the filter method for that row, in this case – no filtering.
This data is then zlib-compressed using the deflate algorithm. The deflate algorithm is specified in RFC 1951. We don't actually want to compress anything, so we just put it in a block with no compression. The format is:
|1 byte||bit 0||final block flag||1 = final|
|bits 1–2||block type||0 = not compressed|
|2 bytes||data length||little-endian|
|2 bytes||~(data length)||little-endian|
This deflated data goes in a zlib container, which is specified in RFC 1950. This includes a smaller header and a checksum. The format is:
|1 byte||bits 0–3||compression method||8 = deflate|
|bits 4–7||window size||7 = 32 KB|
|1 byte||bits 0–4||check bits for first 2 bytes||1|
|bit 5||preset dictionary||0 = don't use|
|bits 6–7||compression level||0 = least compression|
|4 bytes||Adler-32 checksum of uncompressed data|
The Adler-32 checksum is also specified in RFC 1950, which also contains a sample implementation.
The file format is described in the PNG specification. It consists of a signature followed by a series of chunks. The simplest PNG image is represented by three chunks: a header (IHDR), the image data (IDAT) and an end-of-file marker (IEND), so that's what we write.
|25 bytes||IHDR chunk|
|12 bytes||IEND chunk|
Each chunk is structured like this:
|4 bytes||data length||big-endian|
|4 bytes||chunk name||e.g. |
|4 bytes||CRC over chunk name and data|
The CRC uses the 32-bit IEEE 802.3 polynomial. The PNG standard contains a sample implementation.
The IHDR chunk contains information about the image format:
|1 byte||bit depth||8, 16|
|1 byte||colour type||2 = RGB, 6 = RGBA|
|1 byte||compression method||0 = default|
|1 byte||filter method||0 = default|
|1 byte||interlace method||0 = none|
The IDAT chunk contains zlib-compressed pixel data.
The IEND chunk contains no data.
The data: scheme is specified in RFC 2397. We use Base64 encoding, so the URL contains:
|Base64-encoded file data|
The current standard for Base64 is RFC 4648.
The final image URL is a data-URL-encapsulated Base64-encoded PNG file that contains CRC-verified zlib-encapsulated deflate-compressed Adler32-verified filtered rows of gamma-encoded pixel values.