Monday, May 5th, 2008
Compression using Canvas and PNG

The image above is the 124 kilobyte Prototype library embedded in a 30 kilobyte 8 bit PNG image file.
Jacob Seidelin had some fun this weekend it appears and created a script that can read in JavaScript code from images. To do this, he used the canvas getImageData() method.
Here are the detailed steps:
The first step was to find the best image format for the job, that means the one that gives the best compression while still being lossless. Here on the intertubes, we don’t get a lot of image format choices and since JPEG is lossy, we’re down to GIF and PNG.
For PNG we have two options, 24 bit and 8 bit. Using 24 bit RGB colors, we can store 3 bytes of data per pixel while 8 bit indexed colors only gives us 1 byte per pixel.
A quick test in Photoshop tells us that a 100×100 image with random 24 bit colored noise compresses down to about 20 KB while a 300×100 image with random 8 bit monochromatic noise compressed down to just 5 KB. A regular 8 bit GIF comes in a bit heavier than the 8 bit PNG, so we go with the PNG option.Now we need to convert our Javascript file into color data and stuff it in a PNG file. For this purpose, I crafted this quick and dirty PHP script, which reads the Javascript file, creates a PNG image file and simply lets each pixel have a value 0-255 corresponding to the ascii value of the character in the script.
I ran into a problem here, since the image is created as a truecolor image and we need it to be 8 bit indexed and PHP won’t make an exact conversion. I guess there are ways to create a palletted image from scratch in PHP/GD, but I haven’t looked into that yet. The solution for now is to simply run the generated image through something like Photoshop and convert it to 8 bit there.
So now we have the Javascript all nice and packed up in a compressed PNG file and now we need to get it out again in the client. Using the canvas element, we simply paint the picture using drawImage() and then read all the pixel data using getImageData(). This data is given to us as a large array of values, where each pixels takes up 4 elements (RGBA), so we just take every 4 value and tack them all together into an eval()-ready string. And we’re done.
And the reading function is here.
NOTE: This is for fun, and isn’t meant to be used in the real world. That being said, see it at work in the mario game.












He’s a madman, a madman I tell you! ;) Interesting ideas, and fun stuff.
So, how does this compare to standard GZIP compression?
I guess if you have really huge XHR requests and wanted them compressed you could just use images instead…
Still kind of insane.
Standard gzip of Prototype.js (1.6.0.2) is 28.2kb;
If you remove whitespace and shrink variables and gzip the size is 20.7kb.
-JDD
very cool idea though :)
Actually, if your server still doesn’t support GZIP compression (hint hint: Domino), this is a pretty neat idea.
And our computers come one step closer to experiencing a snow crash. :)
mind…blown…
Now I can finally use flickr as a code repository!
I like this kind of abuse of one technique/format to do something it was not intended for.
I did some work with this a while back. I was exploring using LZ77 to do the same thing. I was doing it in JS and also using Flash. Here is my post:
http://blogs.nitobi.com/alexei/?p=166
and my demo:
http://blogs.nitobi.com/alexei/demos/compression/index.htm
I actually did this last week. Instead, I stored 3 ascii values per-pixel. Too bad this won’t work cross-domain.
To say the least, this idea is genius.
Once browser compatibility improves, I can see this method being pretty widespread; it’s gloriously obfuscated and has a massive compression advantage.
Genius!
FWIW, in theory this could break if the canvas backing store uses 200×200 for 100×100 canvas pixels for instance as getImageData returns the backing store data…
try to just print-scr the code and OCR it back clientside ;)
Anne: that’s why Jacob Seidelin sets both the internal resolution and the “CSS” resolution of the Canvas ;)
Great, imagine that you can embed metadata and handling code in your sprites.
OTOH, is there any js library for reading EXIF metadata client-side?
Laurian: there is no EXIF library, since there is no cross browser way to read binary files.
A clever technique but couldn’t you achieve the same result just depending on web/app-server compression by mime type?
p01, you can’t control the resolution of the datastore, it’s determined by the browser and is orthogonal to canvas or CSS pixels.