14. Basic animation


Drawing a sinusoid
Some figures can be described by means of mathematical equations. You may, for instance, have come across a sinusoid:



This is the graph of the equation: 
y = sin x.

You can't just use this equation to draw in an applet, anyway not unchanged. The width of this graph is 2π (a little more than 6), while an average applet window will be a few hundred pixels wide.

Moreover, the maximum and minimum values of this sinusoid  are -1 and +1, whereas your average applet window is at least a hundred pixels high.

Besides, the larger values of graphs are usually at the top, while the applet window has its largest values at the bottom.

Consequently, three values will have to be converted:

  • The normal amplitude of the sine (-1 to +1) must be multiplied to obtain the height of the window, so that the full height of the window is used.
  • The sine trajectory (2π) must be multiplied by a certain factor to convert it to the width of the applet window.
  • In order to get the largest values at the top, the figure should be turned upside down. We shall subtract the values calculated from half the window height (here named halfHeight).

Of course, pixels will always be whole numbers (type int). So the sinue values must be cast with:

       y = halfHeight - (int)sine;

The program makes use of the variables lastX and lastY because we don't draw dots, but small lines between the points of the sinusoid. Hence, after drawing any of these lines, lastX and lastY will be assigned the values of x and y.


This is what the program looks like:

// A sinusoid drawn in an applet
import java.applet.*;
import java.awt.*;
public class Sinusoid extends Applet {
public void paint (Graphics g) {
    setBackground (Color.white);
    Dimension d = getSize();
    int x, y, winWidth = d.width, winHeight = d.height;
    int halfHeight = winHeight / 2;
    int lastX = 0, lastY = halfHeight;
    double trajectory = 2 * Math.PI;
    double factor = trajectory / winWidth;
    for (x = 1; x <= winWidth; x++) {
        double sine = Math.sin (x * factor) * halfHeight;
        y = halfHeight - (int)sine;
        g.drawLine (x, y, lastX, lastY);
        lastX = x; lastY = y;
    }
}
}

And here is a working applet:




More body and colour: a Dutch flag
Our sinusoid may be interesting to mathematicians. It's probably less suitable to serve for an average illustration. We may try to give the figure a little more body, for instance something like this:



Probably the best way to make this figure is by making use of the class Polygon, which we saw in chapter 13.

There are a number of ways to define the points of a polygon. In this tutorial we choose two arrays, one with horizontal and the other with vertical coordinates, which should look like this:

       g.drawPolygon (horArray, vertArray, arrayLength);

The points that will make any of the three coloured bands will have to look like this:



These points (defined by the values laid down in the two arrays) will be connected by drawPolygon / fillPoygon. Needless to say that the coordinates of the points are determined by sine values.

The program makes use of the method fillArrays, in which - as the name suggests - the two arrays are filled.

int fillArrays
       (int h[ ], int v[ ], int xStart, int wd, int yStart, int he, int step)


(The line is split into two because it may otherwise be split accidentally with certain screen resolutions.)

The method is of the int type. It returns the size of that part of the arrays that is filled with coordinates: the arrays have been declared with ample space. We normally use only a part of them.

Apart of the two arrays, this method has 5 more variables, which are:
  • xStart: the distance from the left side of the window
  • yStart: the distance from the top of the window
  • wd: the width of the coloured band (between left and right)
  • ht: the height of the coloured band (the "thickness")
  • step: distance between the two end points of any of the connecting lines

First attempt at animation
Before we present the program here, we're first going to change it in such a way that the "wave" will move. This can be done by repeatedly adding a little bit (called: extra) to the number whose sine is taken.

The method fillArrays will look like this:

int fillArrays
   (int h[ ],int v[ ],int xStart,int wd,int yStart,int ht,int step,double extra)


It's no real animation: whenever a button is clicked a new picture will be drawn, so that we get a kind of slow and primitive cartoon movie.

This is what the program looks like:

// A flag which moves by clicking a button
import java.applet.*;
import java.awt.*;
import java.awt.event.*;
public class Flag1 extends Applet implements ActionListener {
Button but = new Button ("click");
double add = 0;

public void init () {
    setBackground (Color.black);
    add (but);
    but.addActionListener (this);
}

public void paint (Graphics g) {
    Dimension d = getSize();
    int height = d.height, width = d.width;
    int x[ ] = new int[2*width], y[ ] = new int[2*width];
    for (int color=0; color<3; color++) {
        switch(color) {
            case 0: g.setColor (Color.red); break;
            case 1: g.setColor (Color.white); break;
            case 2: g.setColor (Color.blue);
        }
        // The following 2 lines are actually one:
        int size = fillArrays
        (x, y, width/8, 3*width/4, (1+color)*height/5, height/5, 5, add);
        g.fillPolygon (x, y, size);
    }
}

// The following 2 lines are actually one:
int fillArrays
   (int h[ ], int v[ ], int xStart, int wd, int yStart, int ht, int step, double extra) {
    int count = 0;
    // top line of the colour band:
     for (int i=xStart; (i<=xStart+wd); i += step) {
          h[count] = i;
          v[count++] = yStart+(int)(ht*Math.sin(i/(0.2*wd)+extra));
     } 
    int n = count -1;
    // The values of the bottom line are not calculated.
    // They're equal to to the values of the top line + ht
    for (int i=h[count-1]; i>=xStart; i-=step) {
        h[count] = i;
        v[count++] = v[n--]+ht;
    }
    return count;
}

public void actionPerformed (ActionEvent evt) {
    add += 0.7;
    repaint();
}
}

And this is what the applet looks like:




A better animation with a "thread"
Suppose we would change our program in such a way that the applet continuously draws new images without clicking a button. Then the animation would probably go too fast.

But we would run into another problem as well: the applet does exactly as we order it: it will just go on and on and will be hard to stop.

Both problems will be solved by the use of a so-called thread.

Modern computer systems (such as Windows or Linux) can do several tasks simultaneously. You can download a file from the Internet while you are using a word processor. This is sometimes referred to as multi-tasking.

In a multi-tasking system, the tasks are performed by different programs using different parts of memory.

An applet cannot do multi-tasking, but it can perform different tasks using the same memory space. Those separate tasks are called threads. This way of performing several tasks is called multi-threading

If we don't take special measures, an applet will perform just one task. When carrying out an animation, the system must be ordered to do the drawing as a separate process. This can be done by means of a thread (which is an object of the class Thread).

First, an object of the class Thread must be declared (for instance: myThread) with:

       Thread myThread;

Then myThread can be initialized. This is a done in the method start(), which we make by overriding the standard applet method start.  As we saw in part 5, this method start will be run every time the applet is started.
public void start( ) {
    if (myThread == null) {
        myThread = new Thread (this);
        myThread.start ( );
    }
}
The line myThread = new Thread (this) creates a real thread. Only the thread must start running. In order to get this done, the method run of the thread must be invoked with myThread.start ( ).

An applet, however, does not have a method run(). We can introduce the method via the interface Runnable. As we saw earlier (in part 11), methods not present in the class can be introduced with an interface.

Our method run( ) will look like this:
public void run () {
    for (; ;) {
        try {
            repaint();
            myThread.sleep (delay);
            extra += 0.2;
        }
        catch (InterruptedException e) { };
    }
}
The expression for (; ;) is a loop in which the three control expressions are missing, which means the loop will just go on and on. 

The thread may be interrupted. Hence we must use the try/catch construction (also discussed in chapter 10).

Every time again, a new image is made by repaint(), after which the execution of the thread is suspended for some time by the method sleep, which waits for 60/1000 of a second. For the duration of that period any other tasks may be carried out.

Then the incrementation extra += 0.2; will cause the sinusoid to be moved a bit to the left in the next image.

When the thread is ended, the method stop() is invoked which takes care that while the thread is not active, it won't make use of any system resources.

And here is the program:

// A waving banner
import java.applet.*;
import java.awt.*;
public class Flag2 extends Applet implements Runnable {
Thread myThread;
int delay = 60; // 60/1000 second
double extra;

public void init () {
    setBackground (Color.black);
}

public void run () {
    for (;;) {
        try {
            repaint();
            myThread.sleep (delay);
            extra +=0.2;
        }
        catch (InterruptedException e) {};
    }
}

public void start () {
    if (myThread == null) {
        myThread = new Thread (this);
        myThread.start();
    }
}

public void stop () {
    if (myThread != null) {
        myThread.stop();
        myThread = null;
    }
}

public void paint (Graphics g) {
    Dimension d = getSize();
    int height = d.height, width = d.width;
    int x[ ] = new int[2*width], y[ ] = new int[2*width];
    for (int color=0; color<3; color++) {
        switch(color) {
            case 0: g.setColor (Color.red); break;
            case 1: g.setColor (Color.white); break;
            case 2: g.setColor (Color.blue);
        }
        int size = fillArrays
           (x, y, width/8, 3*width/4, (1+color)*height/5, height/5, 5, extra);
        g.fillPolygon (x, y, size);
    }
}

int fillArrays
      (int h[], int v[], int xStart, int wd, int yStart, int ht, int step, double extra) {
    int count = 0;
    for (int i=xStart; (i<=xStart+wd); i += step) {
        h[count] = i;
        v[count++] = yStart+(int)(ht*Math.sin(i/(0.3*wd)+extra));
    }
    int n = count - 1;
    for (int i=h[count - 1]; i>=xStart; i -= step) {
        h[count] = i;
        v[count++] = v[n--]+ht;
    }
    return count;
}

}

The applet will look like this:





"Double buffering"

You may have noticed that our last animation did not look entirely perfect. The image showed some flickering.

We can construct the image in memory and then move it to the screen memory in one fast move, which gives a much steadier image.

We declare the image img in memory with:

Image img;
What we also need is a graphical context (gBuf) to draw the image in memory.
Graphics gBuf;
These were only the declarations. In paint we'll initialize both of them:
if (img == null) {
     img = createImage (width, height);
     gBuf = img.getGraphics();
}
One cause of the flickering is that repaint() completely wipes out the image. We are going to change that. As we saw in chapter 11, we can override the applet method update(), so it no longer wipes out the screen.
public void update (Graphics g) {
     paint(g);
}
We'll actually wipe out the image by covering it with a black rectangle:

       gBuf.setColor (Color.black);
       gBuf.fillRect (0, 0, width, height);


The rest will not be drawn directly to the screen either, but will first be stored in memory:

       gBuf.fillPolygon (x, y, Size);

Once the image is completed, it will be moved to the screen memory with:
if (img != null)
       g.drawImage (img, 0, 0, null);
And here is the complete program:

// a waving banner made with "double buffering"
import java.applet.*;
import java.awt.*;
public class Flag3 extends Applet implements Runnable {
private Thread thread;
int delay = 60;
double extra = 0;
Image img;
Graphics gBuf;

public void run () {
   for (;;) {
      try {
         repaint();
         thread.sleep (delay);
         extra += 0.2;
      }
      catch (InterruptedException e) { };
   }
}

public void start () {
   if (thread == null) {
      thread = new Thread (this);
      thread.start();
   }
}

public void stop () {
   if (thread != null) {
      thread.stop();
      thread = null;
  }
}

public void paint (Graphics g) {
   Dimension d = getSize();
   int width = d.width, height = d.height;
   if (img == null) {
      img = createImage (width, height);
      gBuf = img.getGraphics();
   }
   gBuf.setColor (Color.black);
   gBuf.fillRect (0,0,width,height);
   int x[ ] = new int[2*width], y[ ] = new int[2*width];
   for (int color=0; color<3; color++) {
      switch(color) {
         case 0: gBuf.setColor (Color.red); break;
         case 1: gBuf.setColor (Color.white); break;
         case 2: gBuf.setColor (Color.blue);
      }
      int size = fillArray
      (x, y, width/8, 3*width/4, (1+color)*height/5, height/5, 5, extra);
      gBuf.fillPolygon (x, y, size);
   }
   if (img != null)
      g.drawImage (img, 0, 0, null);
   }
   int fillArray
   (int h[], int v[], int xStart, int wd, int yStart, int ht, int step, double extra) {
   int count = 0;
   for (int i=xStart; (i<=xStart+wd); i += step) {
      h[count] = i;
      v[count++] = yStart+(int)(ht*Math.sin(i/(0.3*wd)+extra));
   }
   int n = count-1;
   for (int i=h[count-1]; i>=xStart; i-=step) {
      h[count] = i;
      v[count++] = v[n--]+ht;
   }
   return count;
}

public void update (Graphics g) {
   paint(g);
}
}

And here is what our final animation looks like:



Exercise 14.1
In chapter 6 we drew a circle that pulsates as long as we keep clicking a button. Please make a program with an animation (which does not flicker) of a pulsating circle.

Exercise 14.2
Make a marquee in which the text "Long live Java" (or another text of 10 to 20 characters) in big letters moves continuously from right to left on the screen.

Exercise 14.3
Add two buttons to the applet of 14.2 which will cause the text to move either to the left or to the right.


To Chapter 15

Menu Java Tutorial

HOME

ALPHABETICAL INDEX

(c) 2005, Thomas J.H. Luif