This is a small JavaScript library that transforms an array of pixels (a bitmap) into a PNG image encapsulated in a data: URL. This is intended for small graphics: I made it to create graphics of stars in a space-themed browser-based game. The technique is quite old, and somewhat obsoleted by the <canvas> element, but still interesting if only because of the number of data transformations involved.
|
The library provides a Bitmap class with a simple interface:
var bmp = new Bitmap(w,
h)
| create bitmap of specified size |
bmp.pixel[x][y]
= [r, g, b, a]
| set pixel colour, components in the range [0, 1] |
bmp.subsample(n)
| scale down by integer factor n |
var url = bmp.dataURL()
| 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:
0RGBARGBARGBARGBARGBA |
0RGBARGBARGBARGBARGBA |
0RGBARGBARGBARGBARGBA |
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:
Deflated data | |||
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 | |
* | uncompressed data |
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:
zlib-compressed data | |||
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 | |
* | deflated data | ||
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.
PNG file | ||
8 bytes | signature | "\211PNG\r\n\32\n"
|
25 bytes | IHDR chunk | |
* | IDAT chunk | |
12 bytes | IEND chunk |
Each chunk is structured like this:
Chunk | ||
4 bytes | data length | big-endian |
4 bytes | chunk name | e.g. "IDHR"
|
* | chunk data | |
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:
IHDR data | ||
4 bytes | width | big-endian |
4 bytes | height | big-endian |
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:
data: URL | |
scheme | data:
|
MIME type | image/png
|
encoding extension | ;base64
|
separator | ,
|
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.