Monday, May 5th, 2008

Compression using Canvas and PNG

Category: Canvas, JavaScript

<>p>

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.

Related Content:

Posted by Dion Almaer at 10:28 am
20 Comments

++++-
4.6 rating from 40 votes

20 Comments »

Comments feed TrackBack URI

He’s a madman, a madman I tell you! ;) Interesting ideas, and fun stuff.

Comment by Schill — May 5, 2008

So, how does this compare to standard GZIP compression?

Comment by SteveBrewer — May 5, 2008

I guess if you have really huge XHR requests and wanted them compressed you could just use images instead…
Still kind of insane.

Comment by JeromeLapointe — May 5, 2008

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

Comment by jdalton — May 5, 2008

very cool idea though :)

Comment by jdalton — May 5, 2008

Actually, if your server still doesn’t support GZIP compression (hint hint: Domino), this is a pretty neat idea.

Comment by mdmadph — May 5, 2008

And our computers come one step closer to experiencing a snow crash. :)

Comment by Andrew Clarke — May 5, 2008

mind…blown…

Comment by tj111 — May 5, 2008

Now I can finally use flickr as a code repository!

Comment by rhymeswithseven — May 5, 2008

I like this kind of abuse of one technique/format to do something it was not intended for.

Comment by p01 — May 5, 2008

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

Comment by alexeiwhite — May 5, 2008

I actually did this last week. Instead, I stored 3 ascii values per-pixel. Too bad this won’t work cross-domain.

Comment by antimatter15 — May 5, 2008

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!

Comment by Kit — May 5, 2008

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…

Comment by Anne van Kesteren — May 6, 2008

try to just print-scr the code and OCR it back clientside ;)

Comment by locdev — May 6, 2008

Anne: that’s why Jacob Seidelin sets both the internal resolution and the “CSS” resolution of the Canvas ;)

Comment by Mathieu \'p01\' Henri — May 6, 2008

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?

Comment by Laurian — May 7, 2008

Laurian: there is no EXIF library, since there is no cross browser way to read binary files.

Comment by Mathieu \'p01\' Henri — May 7, 2008

A clever technique but couldn’t you achieve the same result just depending on web/app-server compression by mime type?

Comment by Avataristic — May 8, 2008

p01, you can’t control the resolution of the datastore, it’s determined by the browser and is orthogonal to canvas or CSS pixels.

Comment by Anne van Kesteren — May 10, 2008

Leave a comment

You must be logged in to post a comment.