Up to this point, the graphics drawn by our applets have been relatively simple. With more complex graphics however, whether in animations or interactive programs, flicker can become a problem. (You may have already noticed subtle flickering in some of the previous applets.)
This example demonstrate the problem. It uses a pseudo-random number generator to produce a big, hairy tangle of lines. The lines follow the mouse cursor.
import java.applet.*; import java.awt.*; import java.awt.event.*; import java.lang.Math; public class NoBackbuffer1 extends Applet implements MouseMotionListener { int width, height; int mx, my; // the mouse coordinates Point[] points; int N = 300; public void init() { width = getSize().width; height = getSize().height; setBackground( Color.black ); mx = width/2; my = height/2; points = new Point[ N ]; for ( int i = 0; i < N; ++i ) { int x = (int)(( Math.random() - 0.5 ) * width / 1.5); int y = (int)(( Math.random() - 0.5 ) * height / 1.5); points[i] = new Point( x, y ); } addMouseMotionListener( this ); } public void mouseMoved( MouseEvent e ) { mx = e.getX(); my = e.getY(); showStatus( "Mouse at (" + mx + "," + my + ")" ); repaint(); e.consume(); } public void mouseDragged( MouseEvent e ) { } public void paint( Graphics g ) { g.setColor( Color.white ); for ( int j = 1; j < N; ++j ) { Point A = points[j-1]; Point B = points[j]; g.drawLine( mx+A.x, my+A.y, mx+B.x, my+B.y ); } } }
The output:
You probably see flickering when you move the mouse over the applet. The lines take a significant amount of time to draw, and since the canvas is cleared before each redraw, the image on the canvas is actually incomplete most of the time.
This second example makes the problem even more pronounced by rendering a bitmap image in the background.
import java.applet.*; import java.awt.*; import java.awt.event.*; import java.lang.Math; public class NoBackbuffer2 extends Applet implements MouseMotionListener { int width, height; int mx, my; // the mouse coordinates Point[] points; int N = 300; Image img; public void init() { width = getSize().width; height = getSize().height; setBackground( Color.black ); mx = width/2; my = height/2; points = new Point[ N ]; for ( int i = 0; i < N; ++i ) { int x = (int)(( Math.random() - 0.5 ) * width / 1.5); int y = (int)(( Math.random() - 0.5 ) * height / 1.5); points[i] = new Point( x, y ); } // Download the image "fractal.gif" from the // same directory that the applet resides in. img = getImage( getDocumentBase(), "fractal.gif" ); addMouseMotionListener( this ); } public void mouseMoved( MouseEvent e ) { mx = e.getX(); my = e.getY(); showStatus( "Mouse at (" + mx + "," + my + ")" ); repaint(); e.consume(); } public void mouseDragged( MouseEvent e ) { } public void paint( Graphics g ) { g.drawImage( img, 0, 0, this ); g.setColor( Color.white ); for ( int j = 1; j < N; ++j ) { Point A = points[j-1]; Point B = points[j]; g.drawLine( mx+A.x, my+A.y, mx+B.x, my+B.y ); } } }
The output:
The flickering you see now should be especially bad.
The solution is to use double-buffering : rather than perform drawing operations directly to screen, we draw onto an image buffer (the "backbuffer") in memory, and only after completing this image do we copy it onto the screen. There is no need to erase or clear the contents of the screen before copying (or "swapping", as it's called) the backbuffer onto the screen. During the swap, we simply overwrite the image on the screen. Hence the screen never displays a partial image: even in the middle of swapping, the screen will contain 50 % of the old image and 50 % of the new image. As long as the swap is not too slow, the eye is fooled into seeing a continuous, smooth flow of images.
This example uses a backbuffer.
import java.applet.*; import java.awt.*; import java.awt.event.*; import java.lang.Math; public class Backbuffer1 extends Applet implements MouseMotionListener { int width, height; int mx, my; // the mouse coordinates Point[] points; int N = 300; Image img; Image backbuffer; Graphics backg; public void init() { width = getSize().width; height = getSize().height; setBackground( Color.black ); mx = width/2; my = height/2; points = new Point[ N ]; for ( int i = 0; i < N; ++i ) { int x = (int)(( Math.random() - 0.5 ) * width / 1.5); int y = (int)(( Math.random() - 0.5 ) * height / 1.5); points[i] = new Point( x, y ); } img = getImage(getDocumentBase(), "fractal.gif"); backbuffer = createImage( width, height ); backg = backbuffer.getGraphics(); backg.setColor( Color.white ); addMouseMotionListener( this ); } public void mouseMoved( MouseEvent e ) { mx = e.getX(); my = e.getY(); showStatus( "Mouse at (" + mx + "," + my + ")" ); backg.drawImage( img, 0, 0, this ); for ( int j = 1; j < N; ++j ) { Point A = points[j-1]; Point B = points[j]; backg.drawLine( mx+A.x, my+A.y, mx+B.x, my+B.y ); } repaint(); e.consume(); } public void mouseDragged( MouseEvent e ) { } public void paint( Graphics g ) { g.drawImage( backbuffer, 0, 0, this ); } }
The output:
Why do we still see flicker ? Whenever the applet is supposed to redraw itself, the applet's update() function gets called. The java.awt.Component class (which is a base class of Applet) defines a default version of update() which does the following: (1) clears the applet by filling it with the background color, (2) sets the color of the graphics context to be the applet's foreground color, (3) calls the applet's paint() function. We see flickering because the canvas is still cleared before each redraw. To prevent this, we need to define our own update() function, to override the base class' behavior.
import java.applet.*; import java.awt.*; import java.awt.event.*; import java.lang.Math; public class Backbuffer2 extends Applet implements MouseMotionListener { int width, height; int mx, my; // the mouse coordinates Point[] points; int N = 300; Image img; Image backbuffer; Graphics backg; public void init() { width = getSize().width; height = getSize().height; mx = width/2; my = height/2; points = new Point[ N ]; for ( int i = 0; i < N; ++i ) { int x = (int)(( Math.random() - 0.5 ) * width / 1.5); int y = (int)(( Math.random() - 0.5 ) * height / 1.5); points[i] = new Point( x, y ); } img = getImage(getDocumentBase(), "fractal.gif"); backbuffer = createImage( width, height ); backg = backbuffer.getGraphics(); backg.setColor( Color.white ); addMouseMotionListener( this ); } public void mouseMoved( MouseEvent e ) { mx = e.getX(); my = e.getY(); showStatus( "Mouse at (" + mx + "," + my + ")" ); backg.drawImage( img, 0, 0, this ); for ( int j = 1; j < N; ++j ) { Point A = points[j-1]; Point B = points[j]; backg.drawLine( mx+A.x, my+A.y, mx+B.x, my+B.y ); } repaint(); e.consume(); } public void mouseDragged( MouseEvent e ) { } public void update( Graphics g ) { g.drawImage( backbuffer, 0, 0, this ); } public void paint( Graphics g ) { update( g ); } }
Now there should be no apparent flicker:
Update (January 2008): I received the following email message that might be useful to some readers.
Hello, I recently read your Java Applet Tutorial, it really helped me to understand basics of java applets, especially backbuffers. Although i had a problem with smooth animating - quickly moving objects weren't rendered smoothly. I thought I've made a mistake implementing backbuffer stuff, but (after a few hours) i realized that it's everything fine with this applet when it's ran on Windows. The strange behaviour was that when I was moving mouse cursor over the applet, the animation became smooth. After another few hours i found getToolkit.sync() method which forces refreshing applet in window system. The tricky part is that you can't see any difference using Windows, but the difference is clear in Xorg window system. maybe you could consider adding a line in your tutorial, maybe someone will save some time, next time :) public void update( Graphics g ) { g.drawImage( backbuffer, 0, 0, this ); >>>>>>getToolkit().sync(); } anyways thank you for your work Artur Wielogórski (student @Poznan University of Technology)