27 January 2011

Making Images Byte-by-Byte in Javascript

This post explains how to generate bitmap images byte-by-byte in JavaScript. It’s mostly just a coding exercise since the canvas element actually has this functionality built-in. I used this technique to generate images in my low res paint app and favicon creator. If you hate prose, then check out the bitmap generation source code directly.

1. Bytes

A byte is 8 bits, 2^8 = 256 possible values, as you all know that's conveniently represented as two hex characters—0x00 is 0 and 0xff is 255.

In JavaScript you can write that with a string as such: '\x00' or '\xff' or even '\xff\x00\x00' which can represent the RGB value for red.

'\x00'  //  0
'\xff'  // 255
'\xff\x00\x00' // the RGB value for red

Note:

'\x' + '00' != '\x00'  // false
'\x' + '00' === 'x00'  // true - we can’t represent bytes this way

To create byte strings dynamically, you can use String.fromCharCode()

  • official definition: the fromCharCode() method converts Unicode values to characters.
  • my usage definition: you give it a number from 0-255 and it returns that as a hex string

String.fromCharCode(255) === '\xff' // true - this is the way to do it

Note: going above 255 stops producing characters that you can use as bytes, but it still produces some fun stuff, like the snowman character:

String.fromCharCode(9731) === '☃'

So to get bytes for numbers that are larger than 255, you have to break it up into chunks and do each chunk:

String.fromCharCode(value & 255); // get the last byte using a mask
value >>= 8;                      // then shift value to the next byte

2. Bitmaps

Bitmap seemed to be the simplest image format (specifically 24-bit bitmaps—24-bit, i.e., 3-bytes, i.e., RGB), there’s no compression, it’s mostly just a map of bits—I found everything I needed to know on this wikipedia page.

  • Bitmaps consist of a (52 byte) header that describes stuff about the file and then rows of pixel data as RGB values

  • Integer values are represented as little-endian (least significant byte first)

    value     big-endian      little-endian
    266       '\x01\x0a'      '\x0a\x01'
    red       '\xff\x00\x00'  '\x00\x00\xff'
    
  • Bitmap data starts at the lower left of the image (and reads across rows)

  • Each row of the bitmap needs to be padded to be a multiple of 4 bytes (since RGB is 3 bytes, this is very common)

3. Images

The data url scheme allows you to represent pretty much any media as a string.

// data:[<mediatype>][;base64],<base64_encoded_data>
var src =  'data:image/bmp;base64,' + myBase64EncodedData;

The final piece of the puzzle—btoa()—this magically converts stuff to base64, et voilà, we have an image.

var src = 'data:image/bmp;base64,' + btoa(myData);

See this code in action in the low res paint app and also in the favicon creator. You can also check out the actual bitmap generation source code to see how the header and the data are handled.

Comments (5)

1. hazır beton wrote:

The data url scheme allows you to represent pretty much any media as a string.

Posted on 31 March 2011 at 10:03 AM  |  permalink

2. steven wrote:

Hi, great stuff, days i've looked for something like your generator, since i dont know how to make one on my own. A question what about the 32 pix option how come it doesn't work ? Anyway a good starting point to learn about it! Super well done.

Posted on 20 May 2011 at 12:05 AM  |  permalink

3. peter wrote:

Sorry steven, but I don’t understand your question—what do you mean about a 32px option and it not working? If you’re referring to wanting to generate 32px favicons instead of 16px favicons, then the answer is that I only wrote it to create 16px favicons. It’d be pretty easy to change to support 32px, but I don’t have any plans on doing that. If you want to generate a large icon, you could do it in a paint/photoshop-like app and then find a website that will convert regular images to favicons.

Posted on 20 May 2011 at 11:05 AM  |  permalink

4. Pablo Almunia wrote:

I have had problems with certain line sizes, for example 7px. I've had to change this line of code:

row_padding = 4 - ((width * 3) % 4), // pad each row to a multiple of 4 bytes

If the lines have 7px, we have (7 * 3)=21 bytes, but we need 24 = 21 + (4 - ((7*3) % 4)) . Width the original code result 22 = 21 + ((7 * 3) % 4).

Posted on 31 December 2011 at 11:12 AM  |  permalink

5. peter wrote:

Thanks for pointing out this bug Pablo! Looks like I only ever tested it on even width images (which happened to work out with the original code).

However, your solution doesn’t consider the case when there should be no extra padding (e.g., 8px -> 4 - (8*3) % 4 = 4, instead of 0). There’s probably a more elegant way to code this solution, but I added one more "%4" to your example to handle that case:

row_padding = (4 - (width * 3) % 4) % 4;

Posted on 3 January 2012 at 11:01 AM  |  permalink




Did you find this helpful or fun? paypal.me/mrcoles

Peter Coles

Peter Coles

is a software engineer living in NYC who is building Superset 💪 and also created GoFullPage 📸
more »

github · soundcloud · rss