On Android, there are a few different overloads for the drawBitmap method. One of these methods takes an integer color array and draws it as a bitmap:
public void drawBitmap (int[] colors, int offset, int stride, float x, float y, int width, int height, boolean hasAlpha, Paint paint)
The documentation states that "this method avoids explicitly creating a bitmap object which can be more efficient if the colors are changing often". While it's true that a bitmap object isn't created, the design of this method makes it a poor choice for drawing, as you'll see below.
First, some background. Within PixStack, I use the fantastic FreeImage library to load and save images. Once an image has been loaded, the pixel data is transformed, and is stored internally in ARGB format. Up until very recently, the pixels were then kept in an integer array. That worked great, because it's possible to draw a color array using the above method, and because I could very easily access the pixel data directly, which becomes important when the images are large.
Images were slow to draw, but I had assumed that was mostly down to the fact that I was asking Android to draw a large number of pixels with scaling. After looking at some other photo editors a few weeks back, I found that for the same image, drawing peformance was much worse in my app than in others. With a few basic tests, I confirmed that drawing a bitmap object was far faster than drawing a color array. Constantly curious, I dove into the Android source code to find out why.
Call structure
The following is what takes place when you draw a bitmap object:
- Canvas.drawBitmap
Checks that the bitmap hasn't been recycled, beore simply handing control off to native_drawBitmap. - SkCanvasGlue::drawBitmap__BitmapFFPaint
native_drawBitmap is translated into drawBitmap__BitmapFFPaint, which sets up the (native) canvas and paint objects, before calling into the Skia library. - SkCanvas::drawBitmap
Does some bounds checks, then calls an internal draw method. - SkCanvas::internalDrawBitmap
Does some minor work, before calling another internal method. - SkCanvas::commonDrawBitmap
From what I understand, this is the point at which the bitmap is actually drawn on screen by the device. How it's drawn isn't relevant here, so for all intents and purposes, this is the last call when drawing.
Here's what happens when you draw a color array:
- Canvas.drawBitmap
Performs some sanity checks on its arguments, before calling an overloaded version of native_drawBitmap. - SkCanvasGlue.drawBitmapArray
As before, native_drawBitmap is translated into another native method, in this case drawBitmapArray. Unlike the equivalent method above, this method allocates a new Skia bitmap (SkBitmap) and copies the pixels into it. The copy is done row-by-row, and it is at this point that the pixels are pre-multiplied. From here, the call structure is the same as the case above. - SkCanvas::drawBitmap
- SkCanvas::internalDrawBitmap
- SkCanvas::commonDrawBitmap
Graphical overview
Final Thoughts
The reduced performance seen when using a color array comes down to the fact that every time you draw, the system will initialise a new native SkBitmap object. This isn't a problem when drawing a (Java) bitmap object, since the SkBitmap is initialised once, in the bitmap constructor.
If you have the option, always stick with drawing a bitmap object.