Since I do not run any server side code, I'm always playing around with new client side tricks. For example, the XSL in my RSS or the sudoku solver. But recently I was playing with the HTML Canvas and was wondering whether there was some other way I could generate images client side with javascript. And it turns out that you can.
Tool of choice for moving image data from a javascript variable into a real image is a data: URI. And it also helps that windows bitmaps are so easy to generate, after all it was designed with simplicity in mind. Here's a chunk of code which should in most ways be self explanatory.
function Bitmap(width, height, background) { this.height = height; this.width = width; this.frame = new Array(height * width); } Bitmap.prototype.setPixel = function setPixel(x,y, c) { var offset = (y * this.width) + x; /* remember that they are integers and not bytes :) */ this.frame[offset] = c; }; Bitmap.prototype.render = function render() { var s = new StringStream(); s.writeString("BM"); s.writeInt32(14 + 40 + (this.height * this.width * 3)); /* 24bpp */ s.writeInt32(0); s.writeInt32(14+40); /* 14 bytes done, now writing the 40 byte BITMAPINFOHEADER */ s.writeInt32(40); /* biSize == sizeof(BITMAPINFOHEADER) */ s.writeInt32(this.width); s.writeInt32(this.height); s.writeUInt16(1); /* biPlanes */ s.writeUInt16(24); /* bitcount 24 bpp RGB */ s.writeInt32(0); /* biCompression */ s.writeInt32(this.width * this.height * 3); /* size */ s.writeInt32(3780); /* biXPelsPerMeter */ s.writeInt32(3780); /* biYPelsPerMeter */ s.writeInt32(0); /* biClrUsed */ s.writeInt32(0); /* biClrImportant */ /* 54 bytes done and we can start writing the data */ for(var y = this.height - 1; y >=0 ; y--) { for(var x = 0; x < this.width; x++) { var offset = (y * this.width) + x; s.writePixel(this.frame[offset] ? this.frame[offset] : 0); } } return s; };
Well, that was easy. Now all you have to do is generate a base64 stream from the string and put in a data: URL. All in all it took a few hours of coding to get Javascript to churn out proper Endian binary data for int32 and uint16s. And then it takes a huge chunk of memory while running because I concatenate a large number of strings. Ideally StringStream should have just kept an array of strings and finally concatenated them all into one string to avoid the few hundred allocs the code currently does. But why optimize something when you could sleep instead.
Anyway, if you want a closer look at the complete code, here's a pretty decent demo.
--Curiousity is pointless.