Introduction
If you’ve ever written an applet that does a lot of graphics, you might have noticed a flickering whenever the applet is redrawn. You were probably annoyed by it, too. This article tells you how to get rid of it.
Problem Definition
We’re talking here about custom-drawn graphics — that is, lines, points, rectangles, and images, rather than GUI components like buttons and text fields. These custom-drawn graphics are carried out by making calls on the Graphics object passed into
Applet.paint() |
Just before
paint() |
Just after the applet is cleared, but before your stuff is redrawn, the applet is an empty square. After your stuff is redrawn, the applet is full of stuff. If this sequence of events happens often enough and/or if the delay between clearing and drawing is long enough, you’ll see a flickering.
The
paint()
Method
paint()
Applet.paint() |
Your applet will also cause itself to be repainted, even if it hasn’t just become visible. This can happen in response to user input, or it can happen on its own.
The tricky thing about
paint() |
The applet can decide for itself that it wants to be redrawn. It tells “the system” this by calling
repaint() |
paint() |
When the system does decide, for whatever reason, that it’s time to redraw, it creates a Graphics object for the applet’s visible area. This object is specific to just this area of the window, and it contains all the drawing methods you might want to use. This Graphics object is passed to
paint() |
Making It Invisible
The basic problem here is this Graphics object. Whenever one of its drawing methods (such as
Graphics.drawLine() |
paint() |
The solution is to draw these things somewhere where they can’t be seen, and then copy everything to the screen at once.
This is called double buffering. The name comes from the old days, back when our code drew directly to the screen instead of to something called a “window”. Back then, if you wanted to avoid flicker, you would draw to a buffer — an area of memory — that wasn’t on-screen, and then point the screen hardware at that buffer when you were done. While the screen hardware was pointed there, you could draw the next frame to the first buffer. By using two buffers instead of one, you hid the process of drawing.
We use a somewhat different metaphor in Java, since we’re talking about windows rather than screens. In this case, we’re going to have two Graphics objects. One of the them is the original we mentioned earlier — the one where, if you draw to it, you immediately see the results right on the screen. We’ll call this our on-screen buffer. The other Graphics object is our off-screen buffer. When we draw here, the process won’t be observed by the user; the drawing will be invisible until we explicitly transfer the results to our on-screen buffer.
paint()
and
update()
paint()
update()
I mentioned before that the applet window is cleared for you before you draw, and that this is something that’s bothering us. Luckily, however, Java is object-oriented, which means that we can often change the behavior of the system by overriding a method.
In this case, we can take charge of the whole applet-clearing situation by overriding the
update() |
update() |
paint() |
paint() |
update() |
paint() |
paint() |
paint() |
paint() |
update() |
Here’s how the two work together, normally:
- Applet code calls
repaint()
-
tells the system to eventually cause a redrawrepaint()
- After a while, the system calls
Applet.update()
-
clears the applet squareApplet.update()
-
callsApplet.udpate()
Applet.paint()
-
draws a bunch of stuffApplet.paint()
Drawing to an Image
We don’t like this because steps 4 and 5 happen right on-screen, and so we get flickering.
Instead, we want these steps to be done off-screen. We’re going to create an Image object that is the same size as the applet. This Image is a visual component, but it’s not going to displayed directly anywhere — that is, it’s not a sub-component in any on-screen component. It’s just going to be an off-screen object that we can draw into.
Note that an Image object is different than a Graphics object. An Image is something that you can display; a Graphics is something that you can draw into. But the key here is that an Image object has a Graphics object. When you want to draw to an Image, you ask the Image for its graphics object, using
Image.getGraphics() |
- Applet code calls
repaint()
-
tells the system to eventually cause a redrawrepaint()
- After a while, the system calls
Applet.update()
-
grabs an offscreen ImageApplet.update()
-
gets a Graphics object from this ImageApplet.update()
-
clears this offscreent bufferApplet.update()
-
callsApplet.update()
on the offscreen bufferApplet.paint()
-
copies the Image to the screenApplet.update()
Implementation
This stuff is kind of tricky to figure out, because it involves understanding the various methods in the AWT display system, and what each one is for.
One you’ve figured this out, however, the code changes are fairly small. All we really have to do is change
update() |
paint() |
Here’s the code for the default version of
update() |
Listing 1: The default version of update().
if ((! (peer instanceof java.awt.peer.LightweightPeer)) &&
(! (this instanceof Label)) && (! (this instanceof TextField))) {// We’re drawing the background, so use the background colo
g.setColor(getBackground());// Cover the entire area, to clear it
g.fillRect(0, 0, width, height);// Restore the foreground color
g.setColor(getForeground());
}
// Let paint() do its stuff
paint(g);
Ignoring all the junk about peers, you can see that this is pretty simple. We set the current color to be the background color, because the call to
fillRect() |
paint() |
In our double-buffered version, it’s a little different:
Listing 2: The double-buffered version of update().
// offscreen is our off-screen buffer — an Image object
// Get its graphics object, for drawing
Graphics gg = offscreen.getGraphics();// Clear it, as before; but remember, this is off-screen!
gg.setColor( getBackground() );
gg.fillRect( 0, 0, width, height );
gg.setColor( getForeground() );// Let paint() draw to this, again not visibly on-screen
paint( gg );// We don’t need this Graphics object anymore
gg.dispose();// Copy the contents of the Image to the on-screen area
g.drawImage( offscreen, 0, 0, null );
The Abstraction
In the sample code, we’ve created a class called DoubleBufferApplet. This class does double buffering “automatically” — that is, it contains all of the above logic in its
update() |
It’s easy for an applet programmer to make use of this. Normally, you’d create an object that subclasses from Applet. Instead, you subclass from
DoubleBufferApplet |
Take a Look
To see this in action, take a look here. This shows two instances of the same applet; one is using double buffering, and one is not.
Problems/Ideas
The major problem with the framework outlined above is that we’re subclassing our applet from a special applet base class that gives us the double-buffering functionality. However, it’s not ideal to get this by subclassing; after all, we might have another applet base class that we want or need to subclass from, and you can’t subclass from both.
A better framework might be to create a
DoubleBufferCanvas |
DoubleBufferApplet |
Source Code
About the Author
Greg Travis is a freelance programmer living in New York City. His interest in computers can probably be traced back to the episode of "The Bionic Woman" where Jamie runs around trying to escape a building, whose lights and doors are controlled by an evil artificial intelligence. He’s a devout believer in the idea that when a computer program works, it’s a complete coincidence. He can be reached at mito@panix.com.