[ Home | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 ]

Exercise 7: Backbuffers

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 need to enable Java to see this applet. )

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:

( You need to enable Java to see this applet. )

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:

( You need to enable Java to see this applet. )

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:

( You need to enable Java to see this applet. )

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)