Android web browser reduces my images – part 2

This is the second part of this post about how Android browser reduces 32 and 24 bits PNG images to 16 bit images, at loadtime. In a mobile device this may not mean a great visual impact. But when you are using PNGs to store data this is a big problem.

On the first part of this post I talk about how to store data in a bitmap and later using it from the web using javascript and canvas element. That technique failed with all Android device browsers I tested due to that bit depth reduction.

The solution is fairly simple. Instead of using one pixel to store one byte of data you could use two pixels per byte, using the 4 more significant bits of each pixel. A possible java generation code could be now something like this:

// Suppose 'width' and 'height' as image dimensions
// and 'string' as in data

// Create image
int type = BufferedImage.TYPE_BYTE_GRAY;
BufferedImage image = new BufferedImage(width*2, height, type);
// Get raster
WritableRaster raster = image.getRaster();
int index = 0;
for(int y=0; y<height; y++) {
  for(int x=0; x<width; x++) {
    // Get data (filling with zeros at the end)
    int current = index<string.length()? (int)string.charAt(index++): 0;
    // Get data more significant bits setting the others to zero
    // and store them
    int hi = current&0xf0;
    int[] hiArray = { hi };
    raster.setPixel(x*2, y, hiArray);
    // Get data less significant bits moving them to more significant
    // bits position to store in image
    int lo = (current&0x0f)&lh;<4;
    int[] loArray = { lo };
    raster.setPixel(x*2 + 1, y, loArray);
// Finally write the image to disk
ImageIO.write(image, "png", new File(filename));

With this piece of code the generated image has doubled in width. You may think this would cause the image to be doubled size. But due to PNG image compression the resulting image gets only a few KBs more. Compressing my AirStrike 10K game with this technique only got a few 100 more bytes. And it worked on Android browsers!

Now, in the javascript side, we have to get image pixels in pairs and create one data byte per pair. But, if you remember the last part of this post, Android browser makes some dithering  when reducing bit depth. So, we need to have this in mind when unpacking data bytes. Simply getting the first pair byte for the most significant bits and the second pair byte for the less significant bits may not work in cases where dithering effect occurs.

Another time the solution is easy. Simply add some increment to image input bytes — something centered between 1 and 15. This will avoid the dithering effect getting the right original value. Then, in the other post’s javascript code, when retrieving data from image data, just make something like this:

// ...
var hi = (data[index]+8)&0xf0;
var lo = (data[index+4]+8)>>4;
index += 8;
var value = hi + lo;
if(value>0) {
  var char = String.fromCharCode(value);
  // ... do something with char value
// ...

And that’s all! Now you can use the PNG compression method in Android browsers too.