~ Java for Web Page Production ~

Session 7 - Multimedia 

<<  Back to Course Outline

Contents

Introduction Playing sound & music  
Session 6 Summary Concurrency & Timers Scrolling Banner Example
Session 7 Overview Animation Techniques Summary & Further Resources
Using Pictures +[(-:]  BREAK  [:-)]+=  

Introduction

We've done a lot about the mechanics of how to get things into your applet, and how to interact with it. Next week, we'll be taking a look at how you might approach a program of your own - the example given is a 'snake' style game.

There's a few last things you need to know about - the icing on the cake, really - in order to produce well-presented applets.

These can be broadly categorized as multimedia features - namely: Sound, Pictures, and Animation.

You may be surprised at how easily some of these facilities can be incorporated into your applet.

Before we start, though, lets quickly summarize some of what we learned in the last session.

Session 6 Summary

In Session 6, we covered:

Session 7 Overview

In session 7, we will be looking at:-

Using Pictures

There are a number of different ways you can work with images in Java. At the most sophisticated level, you can load images, transform them using predefined (or your own) filters, and write them to the screen.

Alternatively, you can easily make use of icons within your components.

However, we will be looking at the simplest way of using images, which is to load them directly into our applet, and paint them to the screen.

Furthermore, we will make use of the way of working we employed in the previous session, when drawing shapes to the screen - by utilising the paint() method of the JApplet.

Images can be held in a class called Image.

So to define an object where an image is to be held, we would type something like:-

        Image greenBall;

... assuming we were going to create a new image, and call it greenBall - you can guess what the image is going to be!

In order to be able to access  this image throughout our class, we would define the attribute before defining the init() method, but after we have declared the class. E.g:-

public class ImageEg1 extends JApplet {
  Image greenBall;

  public void init() {
  
   ...
  }
}

To load the image from your web site, you need to place the image into the same directory as the Java source code (ImageEg1.java file in this case) and use the following command:-

        greenBall = getImage(getCodeBase(),"green_ball.gif");

It is important that you place this in the init() method, as this ensures the Applet has been properly loaded before the image is retrieved.

The getImage method is part of the JApplet class. It takes two parameters - the first is the directory in which the image can be found - getCodeBase() will return the directory in which the Java source file is held. The second parameter gives the name of the image in this directory.

A new image object is created, and this is returned from the getImage method and placed in the greenBall object.

Note that if you wanted to place the image in the directory where the calling HTML document resides, you would use the method getDocumentBase() instead of getCodeBase().

That was simple. However, we need to paint the image (as many times as we like) on the screen.

The object was created outside of any methods, so it will be available to all methods, for the life of the Applet. This means, we can use it whenever we like in the Applet.

So, in order to paint the image on the screen, we will place the command to do this within the paint() method of the applet, as we did in the previous session to draw shapes on the screen:-

public void paint (Graphics g) {
  Graphics2D g2 = (Graphics2D) g; // Cast parameter g from Graphics to Graphics2D
  int appletWidth = getSize().width;
  int appletHeight = getSize().height;

  // Draw border and background colour
  g2.setPaint(Color.lightGray);
  g2.draw3DRect(0, 0, appletWidth-1, appletHeight-1, true); //raised
  g2.draw3DRect(5, 5, appletWidth-11, appletHeight-11, false); //lowered

  int halfWidth=(int)((appletWidth-greenBall.getWidth(this))/2.0);
  int halfHeight=(int)((appletHeight-greenBall.getHeight(this))/2.0);

  g2.drawImage(greenBall,halfWidth,halfHeight,this);
  g2.drawImage(greenBall,halfWidth-greenBall.getWidth(this),halfHeight,this);
  g2.drawImage(greenBall,halfWidth,halfHeight-greenBall.getHeight(this),this);
  g2.drawImage(greenBall,halfWidth+greenBall.getWidth(this),halfHeight,this);
  g2.drawImage(greenBall,halfWidth,halfHeight+greenBall.getHeight(this),this);
}

So what are we doing? At the beginning, we are casting the Applet's Canvas from a Graphics class to a Graphics2D class, and assigning it to the g2 attribute.

Then, as before, a border is drawn around the applet.

Note that this time, we've assigned the applet's width and height to an attribute (variable) called appletWidth and appletHeight. This is to save having to repeatedly type getSize.Width() and getSize.Height() and it also runs a little faster if the value is having to be accessed many times.

Similarly, halfWidth and halfHeight have been set to the width/height of the applet minus the width/height of the image, divided by two. This gives the position where the green ball would sit if it were to be placed exactly in the middle of the applet.

Note that to find the width of the ball, we use the following method:-

            greenBall.getWidth(this)

Thus, we are passing the current JApplet object to the Image's getWidth method. The reasons for this are beyond the scope of this course. Briefly (if you want to know), the JApplet implements an the ImageObserver interface which means that whenever any changes occur to an image, the repaint() method is called. In this case, this would mean that if the image had not yet been loaded fully before the getWidth() method had been called, then when it has been fully loaded, the repaint() method will be called. Otherwise, a value of -1 is returned.

So, to draw the image directly in the middle of the screen, we use the following command:-

        g2.drawImage(greenBall,halfWidth,halfHeight,this);

This draws an image on the g2 canvas - i.e. on the Applet. The image that gets drawn is the one loaded into the greenBall object. The x position (i.e. horizontal position) is given by halfWidth and the y position (i.e. vertical position) is given by halfHeight. Finally this refers to an object whose class implements the ImageObserver interface. In otherwise, ignore this, and just put this in!

The lines following this place the image to the left, above, to the right, and below the image in the middle, by moving the x,y position by the width of the image.

Easy peasy!

Exercise

public void paint (Graphics g) {
  Graphics2D g2 = (Graphics2D) g; // Cast parameter g from Graphics to Graphics2D
  int appletWidth = getSize().width;
  int appletHeight = getSize().height;

  // Draw border and background colour
  g2.setPaint(Color.lightGray);
  g2.draw3DRect(0, 0, appletWidth-1, appletHeight-1, true); //raised
  g2.draw3DRect(5, 5, appletWidth-11, appletHeight-11, false); //lowered

  int sqWidth = greySquare.getWidth(this);
  int sqHeight = greySquare.getHeight(this);
  int halfSqWidth = (int)(sqWidth / 2.0);
  int halfSqHeight = (int)(sqHeight / 2.0);

  int halfWidth=(int)(appletWidth / 2.0);
  int halfHeight=(int)(appletHeight / 2.0);

  int ballWidth = greenBall.getWidth(this);
  int ballHeight = greenBall.getHeight(this);
  int halfBallWidth = (int)(ballWidth / 2.0);
  int halfBallHeight = (int)(ballHeight / 2.0);

  g2.drawImage(greySquare,halfWidth-halfSqWidth,halfHeight-halfSqHeight,this);
  g2.drawImage(greenBall,halfWidth-halfSqWidth-ballWidth,halfHeight-halfBallHeight,this);
  g2.drawImage(greenBall,halfWidth-halfBallWidth,halfHeight-halfSqHeight-ballHeight,this);
  g2.drawImage(greenBall,halfWidth+halfSqWidth,halfHeight-halfBallHeight,this);
  g2.drawImage(greenBall,halfWidth-halfBallWidth,halfHeight+halfSqHeight,this);
}

If you do not get time to complete this exercise, the results can be seen in project img_eg2.qjp.

Playing Sound & Music

Since v1.2, Java has had a very simple mechanism for playing sound and music. Let's take a look at our Pairs game again, and spice it up a bit with some dramatic music in the background, and a sound effect when a pair is matched.

Open the pairs.qjp project from week 7's directory.

The first thing to note is that if you wish to make use of the AudioClip interface, which is implemented by the applet, you need to import it as follows (see line 34 of Pairs.java file):-

            import java.applet.*

We also need to declare a new variable / attribute (line 55) to play the bleep noise for when a pair is matched. We do not need to declare a variable for the music - we will see why later:-

            AudioClip keyBleep;

A file is loaded into the AudioClip object as follows (at the end of the init() method, when the java is first displayed - line 115) :-

            keyBleep = getAudioClip(getCodeBase(),"blip.wav");

Note that we use the getAudioClip method of the JApplet, state where the sound is held (same directory as the java class file), and finally give the name of the sound file. This is then placed in the object name keyBleep.

Note that on the following line, we do a similar thing, except we are getting a MIDI music file.You may think that as this needs to be played continuously and in a loop, there's not need to store it to a variable, as it won't be referenced again. Thus, the line would read:-

        getAudioClip(getCodeBase(),"vivaldi.mid").loop();

However, as the object gets called within the init() method, it falls out of the scope when the code gets to the end of the init() method, and may be disposed of by Java's garbage collector at any time after that. Thus, you may find that the tune stops playing at some random time once you start playing the game.

To avoid this, we need to set up a variable at the scope of the class, so that the variable is available for the life of the applet. Therefore, line 55 now reads:-

        AudioClip keyBleep,vivaldi;

to set up an object called vivaldi to hold the tune (if you excuse the pun), and line 116 now reads:-

        vivaldi=getAudioClip(getCodeBase(),"vivaldi.mid"); vivaldi.loop();

Thus the vivaldi object is created inside the init() method, but remains for the life of the applet as it is declared outside of the init() method. Once created, a method called loop() is used (part of the AudioClip class), which simply plays the music over and over again, until the stop() method is called. Since we are happy to play the music until the Applet is closed, we haven't used this method anywhere!

If you look on line 193, you can see that the keyBleep sound effect gets played after the two buttons get set to "-MATCH-" - i.e. when two buttons have been matched:-

        keyBleep.play();

So the play method of the AudioClip class will play the sound loaded into the object once only.

Exercise

There is another sound effect called tada.wav in the same directory as the other sounds.

Add a new AudioClip variable / attribute called tada, and load tada.wav into the object at the end of the init() method.

When the game is completed, and just before the dialog box is displayed (i.e. after the line starting if (pairsRemaining==0)), insert two instructions to stop the vivaldi music and then play the tada AudioClip sound effect.

If the user chooses to play another game, start the music once again - put the instruction to do this just before the call to the randomOrdering() method.

Try compiling, and if there are no errors, running the program with the AppletViewer program to test it work OK. See the results in pairs_tada.qjp

Concurrency and Timers

Java is what is known as a multi-threaded programming language. This means that you can run more than one task at the same time from the same program, and synchronize the order and priority that these tasks (or processes, or threads) are executed.

Writing threaded applications takes quite a lot of practice, and for this reason will not be covered in this course.

However, there is an easier-to-use framework that you can use if you wish to run a task at regular intervals. The class we use to perform this trick is called the Timer. It allows you to run a particular method (actionPerformed) once every so many milliseconds. This is what's known as a tick - a bit like the regular tick you get if you listen to a clock - in this case, a tick lasts 1000ms or 1 second. So with each tick an action is performed.

This can be used if you wish to animate an object on the screen. With each tick, you can (for example) move an object slightly in the applet, and repaint it.

The general structure that you need to implement in your program to make use of a Timer is as follows:-
import java.awt.event.*;
import javax.swing.*;
import java.awt.*;

public class TimerExample extends JApplet implements ActionListener {

  Timer timer;

  public void init() {

    timer = new Timer(50, this); // Define a timer so actionPerformed gets called

  }

  /*
  ** Defines what should happen when the applet starts - e.g. after return to page
  */
  public void start() {
    if (!timer.isRunning()) timer.start(); // start the timer running
  }

  /*
  **Defines what should happen when the applet stops - e.g. when leave the page
  */
  public void stop() {
    if (timer.isRunning()) timer.stop(); // and stop it again
  }

  /*
  ** ActionListener Interface method called by the Timer every tick
  */
  public void actionPerformed(ActionEvent e) {
    ...
  }

}

Briefly, we are creating a new object for the Timer class (the object instance in this example is called timer - note the distinction - the object is in lower case and so is recognized as different to the Timer class).

When we create the Timer, we specify the amount of time between each tick - ie. 50ms (milliseconds) in this case. Note that there are 1000ms in a second, so this gives 20 ticks per second.

The this parameter refers to the class that implements an ActionListener interface - i.e. has a actionPerformed method in it. This is the method to be called each time a tick comes around. Thus, by specifying this we are saying that the current class (TimerExample) has a method called actionPerformed that will be called every 50ms.

Thus, the code (shown as ...) inside the actionPerformed will be executed every 50ms in this scenario.

The start() and stop() methods of the applet specify what should happen when focus move away and back to the applet after it has been created - e.g. by minimizing the browser window, and restoring it again.

Thus, this says that if the timer is not running, it will be restarted, and if it is running, it will be stopped, respectively.

Animation Techniques

We can make use of this ability to execute a method periodically to perform animation.

For example, to give the impression of a shape moving across the screen, we could change the co-ordinate position by a set amount every time the actionPerformed method is called, and then repaint the screen. Here's some sample code to demonstrate this:-
import java.awt.event.*;
import javax.swing.*;
import java.awt.*;
import java.awt.geom.*;

public class Animate1 extends JApplet implements ActionListener {

  Image greenBall,greySquare;
  Timer timer;
  int xPos=6,step=1;

  public void init() {
    greenBall = getImage(getCodeBase(),"green_ball.gif");
    greySquare = getImage(getCodeBase(),"grey_square.gif");

    setBackground(Color.lightGray);
    setForeground(Color.black);


    String stepString = getParameter("step");
    if (stepString != null)
      step = Integer.parseInt(stepString);

    String speedString = getParameter("speed");
    int speed=50;
    if (speedString != null)
      speed = Integer.parseInt(speedString);

    timer = new Timer(speed, this);
  }

  public void start() {
    if (!timer.isRunning()) timer.start();
  }

  public void stop() {
    if (timer.isRunning()) timer.stop();
  }

  public void actionPerformed(ActionEvent e) {
    xPos+=step; repaint();
  }

  public void paint (Graphics g) {
    Graphics2D g2 = (Graphics2D) g;
    int appletWidth = getSize().width;
    int appletHeight = getSize().height;

    g2.setPaint(Color.lightGray);
    g2.fill(new Rectangle2D.Double(6,6,appletWidth-13,appletHeight-13));
    g2.draw3DRect(0, 0, appletWidth-1, appletHeight-1, true);
    g2.draw3DRect(5, 5, appletWidth-11, appletHeight-11, false);

    int sqWidth = greySquare.getWidth(this);
    int sqHeight = greySquare.getHeight(this);
    int halfSqWidth = (int)(sqWidth / 2.0);
    int halfSqHeight = (int)(sqHeight / 2.0);

    int halfHeight=(int)(appletHeight / 2.0);

    int ballWidth = greenBall.getWidth(this);
    int ballHeight = greenBall.getHeight(this);
    int halfBallWidth = (int)(ballWidth / 2.0);
    int halfBallHeight = (int)(ballHeight / 2.0);

    int imageRHpos = xPos+sqWidth+2*ballWidth;
    if(imageRHpos > (appletWidth-6)) xPos=7;

    g2.drawImage(greySquare,xPos+ballWidth,halfHeight-halfSqHeight,this);
    g2.drawImage(greenBall,xPos,halfHeight-halfBallHeight,this);
    g2.drawImage(greenBall,xPos+halfSqWidth+halfBallWidth,
                             halfHeight-halfSqHeight-ballHeight,this);
    g2.drawImage(greenBall,xPos+ballWidth+sqWidth,halfHeight-halfBallHeight,this);
    g2.drawImage(greenBall,xPos+halfSqWidth+halfBallWidth,halfHeight+halfSqHeight,this);
  }

  public String getAppletInfo() {
    return "Title: Animate1\nAuthor: Simon Huggins\n"+
               "Example to display graphics on the screen and animate them";
  }

}

Open the anim1.qjp QuickCup project from the week07 directory and take a look at the Animate1.htm tab - you can see that two parameters are passed from the web page to the Java Applet:-
<PARAM NAME="speed" VALUE="100">;
<PARAM NAME="step" VALUE="1">;

These two parameters relate to the animation of the images. The speed is the time between each update or tick - in this case, the image will be moved and redisplayed every 100ms, or 10 times a second.

The step is the number if pixels - or distance horizontally - that the image will be moved with each tick. In this case, it will be moved the smallest possible distance, 1 pixel, with each tick.

Try running the applet as it stands. The animation is relatively smooth, but slow. Try running it at 50ms and then 10ms speed. You may notice that the speed at 10ms is not 10 times the speed at 100ms. Why is this? The reason will be that the time it takes to draw the images exceeds 10ms, so it isn't possible to animate at this rate.

Another method of making the animation appear to move faster without increasing the actual number of ticks, is to move the images a greater distance with each tick.

For example, if we set the step parameter to 2, and the speed to 50, then this would be approximately the same in speed as setting  step to 1 and speed to 100. Notice that the animation isn't quite as smooth, though.

Similarly, if we really did want the appearance of 10ms intervals, we might compromise and set step to 5 and speed to 50ms. The animation isn't as smooth, but the speed definitely looks much faster than setting the speed to 10ms and step to 1.

Notice that on line 82 (part of the paint() method), that a light-grey rectangle is filled between the borders. This wipes out the previous image. Try taking this line out by placing a comment before it (two forward slashes - //) - notice that the images leave a 'trail', as the previously drawn images remain where they were. This is why the previously drawn images are drawn over by the background prior to re-drawing them at their new location.

Note, that for greater efficiency, you may choose to fill a rectangle over just the image itself. Note that to do this, you may need to create variables to remember the size and position of that rectangle between calls to the paint() method, as the value of x changes outside of the method. N.b. see project anim2.qjp for an example of this.

On line 99, just before the images are re-drawn, you will find the following line of code:-

            if(imageRHpos > (appletWidth-6)) xPos=7;

where wholeWidth is calculated as being:-

            int imageRHpos = xPos+sqWidth+2*ballWidth;

Thus, imageRHpos is the width of the whole image - 2 balls plus one square, added to the position of the left-hand side of the image. This gives the horizontal position of the right-hand side of the image.

Thus, if this position is after the width of the applet (minus 6 pixels to take into account the border), then it's just about to go off the end applet, so reset the left-hand side of the image to 7, which is just to the right of the border.

The only other point of note is the actionPerformed method, which gets called whenever the timer ticks:-
public void actionPerformed(ActionEvent e) {
  xPos+=step; repaint();
}

All that happens here, is that the xPos variable is incremented (added to) by the contents of the step variable, which is the step value taken from the HTML page's parameter. For example, is xPos started at 6, and the step value was 5 pixels, then the result would be 6+5=11 - xPos would then contain the value 11.

Then, the repaint() method is called, which sends a signal to Java to call the paint() method when it is next convenient to do so. Avoid calling the paint() method directly, as this can lead to garbled-looking images, as you'd be bypassing Java's synchronization mechanism by doing this.

Scrolling Banner Example

As an example of combining these techniques with the text-drawing techniques we learned last week, take a look at the banner.qjp project in the week07 directory.

This is very much based on the previous applet, which scrolled a graphic from the left hand side to the right-hand-side of the screen.

This time, we will create some text, and store its outline as a shape, so that it can be drawn on the screen. In order to give the illusion of movement, the text will be drawn starting at the right hand margin of the applet, and then moving it a specific amount to the left with each tick - thus, the horizontal X position is decreasing, thus if you look on line 81 of the Banner.java tab, you can see the that the text graphic gets moved as follows:-

        xPos-=step; repaint();

 This means that the value of step is subtracted from the value of xPos.

What happens when xPos reaches 0. If we carry on, it will be starting from a position that is off the left hand side of the applet. This is actually useful, because if we can hide anything that is not visible from the applet (a technique known as clipping), this would give the appearance of the text scrolling across the applet. And when the right hand side of the text reaches the left hand side of the applet - i.e. all of the text is hidden to the left of the clipped area of the applet, the text can be displayed from the right hand side of the applet again.

You can think of this as being a bit like having a piece of card with a small window cut into it. You feed in a piece of ticker tape with a message on from the right hand side so that it moves left, with only a small portion visible in the window. When the ticker tape is pulled to the left so far that it disappears from the window, it can be fed back around from the right again.

The following data is read in from the HTML page, so that if these values change on the HTML page, the Java program does not need to be recompiled:-
<PARAM NAME="speed" VALUE="150">;
<PARAM NAME="step" VALUE="10">;
<PARAM NAME="fontSize" VALUE="46">;
<PARAM NAME="bannerText" VALUE="Created using the QuickCup Java Learning Environment...">;

The speed value represents the time between ticks for the Timer object, the step value describes how many pixels the text should move with each tick, the fontSize describes the size (in points) of the font to be scrolled across the page, and the bannerText is the text to be scrolled. 

On lines 40 and 41, you can see that two variables have been created at class-level - i.e. they will exist for as long as the class' object exists:-
TextLayout bannerLayout;
Shape bannerShape;

These are initialised as part of the init() method of the applet, on lines 63-66 as follows:-
bannerLayout = new TextLayout( bannerText,
   new Font("Helvetica", Font.BOLD, fontSize),
   new FontRenderContext(null,false,false) );
bannerShape = bannerLayout.getOutline(null);

Therefore, the text is placed into a TextLayout object, and then converted to a shape (which can be written onto a canvas using either the draw() method for the outline or fill() method for the inside), which can then be reused with each tick. Thus, by placing these instructions here, the text does not have to be recreated with each tick, as it would do if we placed these instructions in the paint() method.

Line 68 is as follows:-

        xPos = getSize().width-6;

This describes the starting position for the text, which is the right-hand side of the applet minus six pixels to take into account the border.

As already described, the actionPerformed() method which gets called with each tick of the Timer, simply change the X (horizontal) position and repaints the canvas as follows:-
public void actionPerformed(ActionEvent e) {
  xPos-=step; repaint();
}

Next, to the paint() method, which draws the text:-

In order to blank any previous text drawn on the screen, line 96 writes a rectangle over the area of the canvas between the borders as follows:-

        g2.fill(new Rectangle2D.Double(6,6,appletWidth-13,appletHeight-13));

In order to avoid painting over the borders, the area that can be drawn in can be set using the setClip method on the Graphics2D object (g2):-

        g2.setClip(new Rectangle2D.Double(6,6,appletWidth-13,appletHeight-13));

 You can see that this corresponds to the area that was blanked - i.e. the area between the borders. Thus, if we try to write over the border now, it will be left intact. Only the area inside the border can be written to.

The next command checks to see if the right-hand side of the applet has reached the left-hand side of the border (taking into account the step amount). If it has, the position of the text is moved to the right of the border to start again:-

        if ( (xPos+bannerShape.getBounds().width+step) < 6 ) xPos = appletWidth-6;

Finally, the text colour is set to red, the starting position of the text is translated (i.e. moved) to the correct place on the screen, and the inside shape of the text (held in bannerShape) is filled into the applet's canvas g2.

Finally, the starting position of any further draws to the canvas are set back to what they were before (ie. 0,0) for neatness, and the clip area is cleared, so the whole of the canvas can be drawn on again:-
g2.setPaint(Color.red);
g2.translate(xPos,yPos);
g2.fill(bannerShape);
g2.translate(-xPos,-yPos);
g2.setClip(null);

A lot of processing power goes into drawing text in this manner. You may notice that there is some flickering, especially at higher speeds. This is due to the time delay between drawing over the previous text with a grey rectangle, and drawing the new text, which is quite labour-intensive for the computer.

Buffering Updates

One way around this is to buffer the updates. This means that you do you drawing on a different canvas, and only transfer the image to the applet's canvas at the last moment. This effectively means that the blanking of the letters, and the re-drawing of the letters takes place off-screen, so that you only ever see the changed image, never the blank rectangle. This is good for complex animation, as it means you can make any drawing changes off-screen to make the animation look as smooth and flicker-free as possible.

This technique is known as double-buffering, as the image is buffered (held in memory) once for the applet, and once off-screen for any intermediate updates.

Take a look at the banner2.qjp QuickCup project in the week07 directory. This works the same as the banner.qjp project, except it utilises a double-buffering technique to help smoothen the animation.

Look in the Banner2.java tab.

On line 33, you can see that we have imported another library - this is so we can use the image buffering classes:-

            import java.awt.image.*;

On lines 45 and 46, you can see that two objects are created at class-level, so that they can be accessed throughout the life of the object - i.e. between ticks of the Timer:-
BufferedImage bufImage;
Graphics2D bufGraphics;

These BufferedImage object holds the image to work on, and the Graphics2D object makes that image available as a canvas that can be painted on.

To create a buffered image that is the size and has the attributes of the applet, a separate method has been created called setUpImage() which is called from the init() method on line 73. Why have we done this? The canvas to be drawn on could change if the user resizes the window, so we may need to recreate the two objects under these circumstances. Thus, this code will be used in more than one place in the program. Thus, make it into a method, so that it can be called from different places:-  
public void setUpImage() {
  bufImage = (BufferedImage)createImage(getSize().width,getSize().height);
  bufGraphics = bufImage.createGraphics();
}

Your applet (inheriting from the JApplet class) has a method called createImage available, which takes a picture of the applet (given a width and height to copy - in this case, we are taking the whole applet), and returns an Image object. This is cast to a BufferedImage and assigned to the bufImage object. We now have a copy of a picture of the applet.

However, in order to draw on this buffered image, we need to create a new Graphics2D object by calling the createGraphics() method on the buffered image, and calling this in bufGraphics. This object can then be used to draw shapes etc. on it.

Note that we could have recreated these every time a tick occurs, but this would be time-consuming, as Java would continually need to garbage collect as older ticks are discarded. This causes a characteristic jerkiness as Java garbage collects.

Look at line 100, the first line in the paint() method. Instead of creating the g2 attribute/variable as a casting of the Graphics g parameter, we have assigned the bufGraphics object to it, so we can paint to the buffered image:-

        Graphics2D g2=bufGraphics;

All painting is then done to the buffered image, in exactly the same way as it was previously done to the applet. Thus, the painting is occurring off-screen.

Finally, on line 123, the buffered image is drawn onto the canvas, in the same way as any other image would be - in this case, it is placed at position 0,0 - i.e. at the top-left hand corner of the Applet's canvas, as it is the same size as the Applet's canvas:-

        g.drawImage(bufImage,0,0,null);

So, the update of the screen only takes place on this line - it is updated ONCE per tick, rather than the several times it was updated earlier when the border was redrawn, the text blanked with a grey rectangle, and the text redrawn into a clipped area.

The result looks much smoother - less flicker, and less jerkiness.

Summary and Further Sources

Complete (rather technical) course on Java from Bruce Eckel. Well-recommended Java seminar CDs also available for a very reasonable price. Also courses on C++ available. http://www.mindview.net