Saturday, August 7, 2010

Its all about the layers..

For an Android project I am currently working on, I wanted to take an image of the last state of my game board, and overlay another image on it. While this seems like a simple thing it wasn't terribly simple to figure out. (Though, it is surprisingly simple to implement.)

To make this easy, lets assume you have two bitmaps. We will call them bitmap1, and bitmap2. Also to make it easy, we will assume they are stored as drawables in your Eclipse project.

It is also important to note that bitmap2 must have some transparent areas on it. If bitmap2 is completely opaque, you won't see any of bitmap1, and this code will be a waste of effort. (Since you could just draw bitmap2 by itself.)

The first thing we want to do is get our drawables stored in Bitmap objects. These bitmap objects should be called bitmap1 and bitmap2. This is easily done with the following code :


BitmapDrawable draw1 = (BitmapDrawable)res.getDrawable(R.drawable.bitmap1);
Bitmap bitmap1 = draw1.getBitmap();

BitmapDrawable draw2 = (BitmapDrawable)res.getDrawable(R.drawable.bitmap2);
Bitmap bitmap2 = draw2.getBitmap();



That was easy. Now, we have to figure out how to combine the two. This is where things start to get a little less obvious. After Googling around, I found several different methods that could do it. But, the method I am going to use below seemed the most straightforward to me.

First, the code, then the explanation :



Bitmap layers = Bitmap.createBitmap(bitmap1.getWidth(), bitmap1.getHeight(), bitmap1.getConfig());
Canvas c = new Canvas(layers);
c.drawBitmap(bitmap1, new Matrix(), null);
c.drawBitmap(bitmap2, new Matrix(), null);



Believe it or not, you now have a merged bitmap in memory. (But how? It looks like all I did was draw on a canvas!?)

Simple, if you look at the Canvas class, you will find that one of the constructors allows you to pass in a Bitmap object. When you do this, anything that is drawn on the canvas is put in to the Bitmap that was passed in. So, in our example above, if we wanted to see the result, we could draw the Bitmap object 'layers' to the screen, and we would have the image of bitmap2 laid over bitmap1.

Or, to look at it another way. If you were working with a SurfaceView, and wanted to draw the bitmaps there, you would create a Canvas object, draw the two bitmaps in order, and pass the Canvas to the SurfaceView where it would be drawn. The code here does the same basic thing. However, instead of having the code drawn immediately on the SurfaceView, you have the output sent to a Bitmap.

But, lets look at some assumptions in this code real quick. When we create the 'layers' Bitmap object, we have to tell the Bitmap object a little bit about the bitmap we want to create. Specifically, we have to tell it what the size of the bitmap is, and what the configuration of the bitmap is. The size is fairly obvious. But, in the sample code above, we are assuming that either the two bitmaps are the same size, or that bitmap2 is smaller than bitmap1. If bitmap1 is actually smaller than bitmap2, then the final bitmap will be the same size as bitmap1, and bitmap2 will be clipped to fit. But what about the configuration? What is that? The configuration is a value that tells the bitmap what kind of data it will be storing. Or, another way to look at it, how big each pixel will be in memory. Without getting in to a bunch of detail, under normal circumstances, you will want to use ARGB_8888.

Why the heck would you ever do this? Well, if you needed a composite image that would be used repeatedly in your program you could either try to draw it in real-time every time it was needed, or draw it once and just reuse the drawing. If the image is more than a few pixels, recreating the overlay each time could be an expensive operation. Caching the result and using that instead can save you a fair bit of processing time that you can use for other cool effects in your program!