The Mobile Information Device Profile (MIDP) defines low-level user interface classes for drawing directly on a device's display. The Using the MIDP Low-Level User Interface API tip covered how to use these classes to do basic drawing. This tip builds on that foundation and discusses how to use double buffering to draw flicker-free graphics.
The term "double buffering" refers to a common technique used in computer graphics. If you consider a device's display to be a memory buffer into which drawing primitives write (the drawing primitives are the basic drawing methods such as drawLine
and drawArc
), with double buffering you draw into a second, offscreen memory buffer and then copy the entire contents of the second buffer into the display buffer. Copying from one buffer to another is a very fast operation on most devices, so that the display changes almost instantaneously. By comparison, directly drawing to a display sometimes causes users to see a flicker, as individual parts of the display are updated. Double buffering avoids this flickering by combining multiple individual drawing operations (that is, those to the offscreen buffer) into a single copy operation to the display buffer.
It's easy to do double buffering in the context of the Mobile Information Device Profile. You can use the Image
class (all classes mentioned in this tip are in the javax.microedition.lcdui
package) to create an offscreen memory buffer. You use the Graphics
class, the same class used to draw on the display, to draw to the offscreen buffer. You also use the Graphics
class to copy the contents of the offscreen buffer onto the display. Double buffering is implemented with just a few adjustments to your painting routines.
The first thing you need to do is determine if double buffering is even necessary. On some implementations, double buffering is automatically supported by the system. In other words, when the system calls your Canvas
object's paint
method, the Graphics
object passed to the method is that of an offscreen buffer managed by the system; the object is not from the display buffer. The system then takes care of copying the offscreen buffer to the display. Checking if double buffering is supported is easy -- all you do is call the isDoubleBuffered
method, like this:
public class MyCanvas extends Canvas { private Image offscreen = null; private int height; private int width; public MyCanvas(){ height = getHeight(); width = getWidth(); if( !isDoubleBuffered() ){ offscreen = Image.createImage( width, height ); } ..... // other initialization as appropriate } ...... // other code, including paint method }
Notice how if isDoubleBuffered
returns false, the constructor creates an offscreen buffer of the same width and height as the canvas
. If the display is double buffered, isDoubleBuffered
returns true and the offscreen buffer is not created.
The offscreen buffer is created by calling one of the Image.createImage
methods. There are four such methods, each of which does the following:
The last of these createImage
methods is the one used for double buffering. The other three createImage
methods cannot be used for double buffering because they create images that are immutable, that is, images that cannot be changed. Only the last createImage
method, the one that takes width and height parameters, can be used to create mutable images. Once you have a mutable image, you can call its getGraphics
method to obtain a Graphics
object that you can use to draw into the image's buffer, just like drawing on the display.
Of course, the real work occurs in the paint
method. A simple paint routine might look like this:
protected void paint( Graphics g ){ g.setColor( 255, 255, 255 ); g.fillRect( 0, 0, width, height ); }
Most paint
routines are much more complicated, especially if animation is involved. To implement double buffering, add a few lines of code before and after the existing painting code, like this:
protected void paint( Graphics g ){ Graphics saved = g; if( offscreen != null ){ g = offscreen.getGraphics(); } g.setColor( 255, 255, 255 ); g.fillRect( 0, 0, width, height ); if( g != saved ){ saved.drawImage( offscreen, 0, 0, Graphics.LEFT | Graphics.TOP ); } }
Basically all you're doing is obtaining the Graphics
object for the offscreen buffer and using it to do the painting. At the end, the entire content of the offscreen buffer is copied to the display. Notice that this is done only if double buffering is not automatically supported. You can easily determine this by checking to see if an offscreen buffer has been allocated. If double buffering is automatically supported, you simply draw directly onto the display as usual.
Double buffering is not without its price. If you're only making small changes to the display, it might be slower to use double buffering. In addition, image copying isn't very fast on some systems; on those systems flicker can can happen even with double buffering. And there is a memory penalty to pay for double buffering: the offscreen memory buffer can consume a large amount of memory, memory that you might not be able to spare. Keep the number of offscreen buffers to a minimum. You could free the offscreen buffer whenever the canvas
is hidden, for example, and allocate it again when the canvas
is shown again. This is easy to do by overriding the canvas
' hideNotify
and showNotify
methods.