Fork me on GitHub

PNGStore by iamcal

Store JS/CSS in compressed PNGs

Stop! Read the original article first!

Further to previously posted results, I have managed to generate PNGs in nearly all formats. Like many technologies, PNGs seem to be barely understood; there are 5 types and 14 subtypes, not just PNG8 and PNG24! This round of tests tries to cover them all, aside from the 16-bit variants (since canvas only returns 8-bit data).

Type Channels Bits per Channel Bit Depth
0Greyscale11/2/4/81/2/4/8
2Truecolor38/1624/48
3Indexed11/2/4/81/2/4/8
4Greyscale w/ Alpha28/1616/32
6Truecolor w/ Alpha48/1632/64

These work to varying degrees - be sure to read the notes after the main results.

Additionally, I have run all images though pngcrush, OptiPNG and PNGOUT (all with max settings) to see it it makes much of a difference. It does. There are some significant savings to be had.

All of the images were created with the perl bake.pl script, using ImageMagick 6.6.3-9 and libpng 1.4.3.

Full Results

File Dimensions Type ImageMagick pngcrush OptiPNG PNGOUT Savings over GZip
jquery_ascii_t0_1b_square.png 757 x 759 Type 0, 1 bit 46,024 46,024 45,203 44,789 -
jquery_ascii_t0_1b_tall.png 9 x 63830 Type 0, 1 bit 60,517 58,626 58,698 56,385 -
jquery_ascii_t0_1b_wide.png 63830 x 9 Type 0, 1 bit 31,915 31,815 30,616 28,898 -
jquery_ascii_t0_2b_square.png 535 x 537 Type 0, 2 bit 39,225 39,225 38,739 38,169 -
jquery_ascii_t0_2b_tall.png 5 x 57447 Type 0, 2 bit 51,587 50,011 50,119 47,868 -
jquery_ascii_t0_2b_wide.png 57447 x 5 Type 0, 2 bit 30,756 30,756 28,898 27,823 -
jquery_ascii_t0_4b_square.png 378 x 380 Type 0, 4 bit 25,464 25,353 25,464 24,680 -
jquery_ascii_t0_4b_tall.png 3 x 47872 Type 0, 4 bit 41,558 41,236 41,351 39,585 -
jquery_ascii_t0_4b_wide.png 47872 x 3 Type 0, 4 bit 24,575 24,470 24,575 23,780 501
jquery_ascii_t0_8b_square.png 267 x 269 Type 0, 8 bit 25,156 25,052 25,156 24,584 -
jquery_ascii_t0_8b_tall.png 2 x 35904 Type 0, 8 bit 32,438 32,331 32,438 31,361 -
jquery_ascii_t0_8b_wide.png 35904 x 2 Type 0, 8 bit 24,540 24,437 24,540 23,975 306
jquery_ascii_t2_8b_square.png 154 x 156 Type 2, 8 bit 64,541 24,799 24,977 24,074 207
jquery_ascii_t2_8b_tall.png 1 x 23936 Type 2, 8 bit 56,410 34,592 34,704 36,321 -
jquery_ascii_t2_8b_wide.png 23936 x 1 Type 2, 8 bit 42,674 24,435 24,619 0 -
jquery_ascii_t3_1b_square.png 757 x 759 Type 3, 8 bit 56,860 40,781 45,220 44,789 -
jquery_ascii_t3_1b_tall.png 9 x 63830 Type 3, 8 bit 86,450 66,646 58,715 56,385 -
jquery_ascii_t3_1b_wide.png 63830 x 9 Type 3, 8 bit 55,528 39,133 30,633 28,898 -
jquery_ascii_t3_2b_square.png 535 x 537 Type 3, 8 bit 37,424 34,624 37,412 37,424 -
jquery_ascii_t3_2b_tall.png 5 x 57447 Type 3, 8 bit 59,089 53,692 50,142 47,868 -
jquery_ascii_t3_2b_wide.png 57447 x 5 Type 3, 8 bit 36,114 33,364 28,921 27,823 -
jquery_ascii_t3_4b_square.png 378 x 380 Type 3, 8 bit 30,061 29,559 25,615 24,680 -
jquery_ascii_t3_4b_tall.png 3 x 47872 Type 3, 8 bit 41,996 41,343 41,410 39,585 -
jquery_ascii_t3_4b_wide.png 47872 x 3 Type 3, 8 bit 29,176 28,703 24,722 23,780 501
jquery_ascii_t3_8b_square.png 267 x 269 Type 3, 8 bit 25,467 25,363 25,467 24,584 -
jquery_ascii_t3_8b_tall.png 2 x 35904 Type 3, 8 bit 32,750 32,642 32,750 31,361 -
jquery_ascii_t3_8b_wide.png 35904 x 2 Type 3, 8 bit 24,851 24,747 24,851 23,975 306
jquery_ascii_t4_8b_square.png 189 x 190 Type 4, 8 bit 60,045 24,920 25,100 24,172 109
jquery_ascii_t4_8b_tall.png 1 x 35904 Type 4, 8 bit 48,570 32,345 32,518 31,849 -
jquery_ascii_t4_8b_wide.png 35904 x 1 Type 4, 8 bit 38,146 24,449 24,633 0 -
jquery_ascii_t6_8b_square.png 133 x 135 Type 6, 8 bit 64,716 24,790 24,971 24,039 242
jquery_ascii_t6_8b_tall.png 1 x 17952 Type 6, 8 bit 59,998 35,745 35,883 39,674 -
jquery_ascii_t6_8b_wide.png 17952 x 1 Type 6, 8 bit 45,754 24,453 24,637 0 -
jquery_seq8_t0_1b_square.png 708 x 710 Type 0, 1 bit 50,796 50,796 50,257 49,956 -
jquery_seq8_t0_1b_tall.png 8 x 62832 Type 0, 1 bit 51,831 51,705 51,798 49,646 -
jquery_seq8_t0_1b_wide.png 62832 x 8 Type 0, 1 bit 42,276 42,276 41,689 41,377 -
jquery_seq8_t0_2b_square.png 501 x 502 Type 0, 2 bit 54,306 54,306 53,851 53,586 -
jquery_seq8_t0_2b_tall.png 4 x 62832 Type 0, 2 bit 51,833 51,701 51,792 49,653 -
jquery_seq8_t0_2b_wide.png 62832 x 4 Type 0, 2 bit 42,265 42,265 41,681 41,375 -
jquery_seq8_t0_4b_square.png 354 x 355 Type 0, 4 bit 43,085 43,085 42,453 42,189 -
jquery_seq8_t0_4b_tall.png 2 x 62832 Type 0, 4 bit 51,833 51,699 51,792 49,689 -
jquery_seq8_t0_4b_wide.png 62832 x 2 Type 0, 4 bit 42,257 42,257 41,674 41,403 -
jquery_seq8_t0_8b_square.png 250 x 252 Type 0, 8 bit 42,822 42,822 42,214 42,216 -
jquery_seq8_t0_8b_tall.png 1 x 62832 Type 0, 8 bit 51,818 51,691 51,783 49,630 -
jquery_seq8_t0_8b_wide.png 62832 x 1 Type 0, 8 bit 42,237 42,126 41,654 41,336 -
jquery_seq8_t2_8b_square.png 144 x 146 Type 2, 8 bit 61,105 42,480 41,983 41,668 -
jquery_seq8_t2_8b_tall.png 1 x 20944 Type 2, 8 bit 67,724 55,164 55,087 55,610 -
jquery_seq8_t2_8b_wide.png 20944 x 1 Type 2, 8 bit 55,951 42,126 41,654 41,336 -
jquery_seq8_t3_1b_square.png 708 x 710 Type 3, 8 bit 72,447 59,971 50,274 49,956 -
jquery_seq8_t3_1b_tall.png 8 x 62832 Type 3, 8 bit 81,742 65,926 51,815 49,646 -
jquery_seq8_t3_1b_wide.png 62832 x 8 Type 3, 8 bit 72,053 59,099 41,706 41,377 -
jquery_seq8_t3_2b_square.png 501 x 502 Type 3, 8 bit 58,473 54,970 53,874 53,586 -
jquery_seq8_t3_2b_tall.png 4 x 62832 Type 3, 8 bit 65,771 61,186 51,815 49,653 -
jquery_seq8_t3_2b_wide.png 62832 x 4 Type 3, 8 bit 57,768 54,264 41,704 41,375 -
jquery_seq8_t3_4b_square.png 354 x 355 Type 3, 8 bit 49,710 45,993 42,512 42,189 -
jquery_seq8_t3_4b_tall.png 2 x 62832 Type 3, 8 bit 55,719 54,318 51,851 49,689 -
jquery_seq8_t3_4b_wide.png 62832 x 2 Type 3, 8 bit 49,036 45,339 41,733 41,403 -
jquery_seq8_t3_8b_square.png 250 x 252 Type 3, 8 bit 43,602 43,602 42,991 42,616 -
jquery_seq8_t3_8b_tall.png 1 x 62832 Type 3, 8 bit 52,597 52,468 52,560 50,354 -
jquery_seq8_t3_8b_wide.png 62832 x 1 Type 3, 8 bit 43,015 42,904 42,431 42,052 -
jquery_seq8_t4_8b_square.png 177 x 178 Type 4, 8 bit 62,559 42,568 42,055 41,723 -
jquery_seq8_t4_8b_tall.png 1 x 31416 Type 4, 8 bit 66,116 53,834 53,744 53,549 -
jquery_seq8_t4_8b_wide.png 31416 x 1 Type 4, 8 bit 53,807 42,140 41,668 41,336 -
jquery_seq8_t6_8b_square.png 125 x 126 Type 6, 8 bit 61,915 42,457 41,969 41,633 -
jquery_seq8_t6_8b_tall.png 1 x 15708 Type 6, 8 bit 68,251 55,420 55,051 56,079 -
jquery_seq8_t6_8b_wide.png 15708 x 1 Type 6, 8 bit 57,585 42,144 41,672 41,336 -

Notes

The 1, 2 & 4 bit Type-3 (indexed color) images are all actually 8-bit. If anyone can convince ImageMagick (version 6.6.3-9) to produce them correctly, please let me know!

The tall/wide images are limited to 65,000 pixels on a side on purpose, so that they don't silently fail creation. However, these images seem to only load intermittently in Firefox 3.6.8 - reloading works about half the time, for no obvious reason. Again, patches welcome.

The images with alpha channels (Type-4 & Type-8) do not load correctly in Firefox, with all pixel values being slightly off. They look correctly stored in the PNGs, so this could be a canvas bug or a compositing issue. Patches or insight welcome!

Analysis

The numbers look even better this time, although the instability with the wide/tall images and the random offsets in the alpha channeled images make those pretty useless. With those bugs fixed, it might be possible to actually save some space :)

The largest (by image dimensions) version of the jQuery library is the ASCII 1-bit grayscale version. You can see it here. If you look really carefully, you can totally see John Resig's face.

Author

Cal Henderson (cal@iamcal.com)

Source Code

The source code for these experiments can be found on GitHub: iamcal/PNGStore

You can clone the project with Git by running:

$ git clone git://github.com/iamcal/PNGStore