Thursday, 14 May 2015

Animation in Java

One of the keys to the success of Java is its capability to add animation and action to Web pages without requiring you to download huge video files. Even though Java is an interpreted language (assuming that your users' browsers don't have a JIT-Just In Time compiler-installed), it's more than fast enough for most animation tasks. Listing 17.12 shows a simple animation that moves a yellow square over a red and blue checkerboard pattern.

Listing 17.12. A simple animation applet.
import java.awt.*;
import java.applet.Applet;

public class simplest_animation extends Applet implements Runnable 
{
    int x_pos,y_pos;
    int n_squares;
    int dh,dw;
    Color color_1,color_2;
    Color square_color;
    int square_size;
    int square_step;
    Thread the_thread;


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

    public void run() {
        while (true) { 

            repaint(); 
            try {
                the_thread.sleep(50); 
            } catch(InterruptedException e) {};
        }
    }


    public void init()
    {
        int w_height, w_width;
        //set the starting point of the moving square
        x_pos = 0;
        y_pos = 0;
        //set the size of the moving square
        square_size = 40;
        //set how much the square moves between screen redraws
        square_step = 2;
        //specifies the number of squares on each side of the checkerboard
        n_squares = 10; 
        //get the size of the applet's drawing area
        w_height = size().height; 
        w_width = size().width; 
        //determine the size of the squares in the checkerboard
        dh = w_height/n_squares; 
        dw = w_width/n_squares; 
        //set the colors for the checkerboard. 
        //Could have used Color.blue and 
        //Color.red instead of creating new color instances
        color_1 = new Color(0,0,255);
        color_2 = new Color(255,0,0);
        //set the color for the moving square
        square_color = new Color(255,255,0);
    }

    public void draw_check(Graphics g) {
        int i,j,offset; 
        int x,y;
        //this draws a checkerboard
        offset = 0;
        for(i=0;i<n_squares;i++) {
            y = i * dh;
            offset++; 
            for(j=0;j<n_squares;j++) {
                x = j * dw;
                if (((j + offset)% 2) > 0) {
                    g.setColor(color_1); 
                } else {
                    g.setColor(color_2); 
                } 
                g.fillRect(x,y,dw,dh); 
            } 
        }
    }
    public void paint(Graphics g)
    {
        //draw the blue and red checkerboard background
        draw_check(g); 
        //increment the position of the moving square
        x_pos += square_step; 
        y_pos += square_step; 
        //set the drawing color to the square color
        g.setColor(square_color); 
        //draw a filled rectangle that moves
        g.fillRect(x_pos,y_pos,square_size, square_size);
    }
}

What you can't see unless you run the applet is that the whole image is flickering; you can see the background through the yellow square. All in all, the word that comes to mind is ugly-hardly the sort of thing that will help sell a Web page. You can reduce this flicker in several ways. A large part of the flicker occurs when repaint invokes the update method. You saw earlier in this chapter that the update method erases the applet by drawing a background colored rectangle. This erasing, followed by the drawing of the background rectangle, causes a lot of the flicker. You can avoid this by overriding the update method with this version:
public void update(Graphics g) {
        paint(g);
    }
This eliminates the flicker in most of the image, but you'll still see flicker in the region of the rectangle. That's because the background is drawn and then the square is drawn over it. Your eye sees the background and then the yellow square every time the screen is redrawn. One way to reduce the amount of drawing that has to be done is to tell the AWT to redraw only the part of the screen that has changed.
The version of the paint method shown in Listing 17.13 does just that. It creates two rectangles: one for where the square is and one for where it will be after it's moved in this call to paint. The union method on Rectangle then is used to get the smallest rectangle that contains both those rectangles. That rectangle is the one that contains all the changes between the two frames. Next, clipRect is used to tell the AWT to repaint only this rectangle.

Note
Instead of creating two new rectangles each time paint is called, you could save the second rectangle in an instance variable and then use it as the old rectangle in the next call to paint.


Listing 17.13. Repainting the minimal area.
    public void paint(Graphics g)
    {
        Rectangle old_r, new_r,to_repaint;

         draw_check(getGraphics()); 
         //Figure out where the square is now
        old_r = new Rectangle(x_pos,y_pos,square_size,square_size); 
        //Figure out where the square will be after this method executes
        new_r = new Rectangle((x_pos + square_step),(y_pos + 
                        square_step),square_size, square_size);
        //Find the smallest rectangle that contains the old and new positions
        to_repaint = new_r.union(old_r); 
        //Tell Java to only repaint the areas that have been
       // affected by the moving square
        g.clipRect(to_repaint.x,to_repaint.y, 
                              to_repaint.width,to_repaint.height);
        x_pos += square_step; 
        y_pos += square_step; 
        g.setColor(square_color); 
        g.fillRect(x_pos,y_pos,square_size, square_size);
    }

Although using clipRect reduces the amount of work the AWT has to do, it's still not fast enough to fool your eye. To do that, you need to use double buffering.
Double buffering involves doing all your drawing to an invisible, off-screen bitmap-an image, actually-and then copying that off-screen image to the applet. This is called bitblitting on the Mac and results in much faster drawing. Listing 17.14 shows the commented changes you need to make in the animation applet to do double buffering.

Listing 17.14. Using double buffering.
public class simple_animation extends Applet implements Runnable
{
    ....
    //Define an image to use for offscreen drawing
    Image offscreen;
    Graphics offscreen_graphics;

    ....
    public void init()
    {
        ....
        //create the offscreen image and get its Graphics instance
        offscreen = createImage(size().width,size().height); 
        offscreen_graphics = offscreen.getGraphics();
        //draw the background checkerboard
        draw_check(); 
    }
....
    public void draw_check() {
        int i,j,offset; 
        int x,y;

        offset = 0;
        for(i=0;i<n_squares;i++) {
            y = i * dh;
            offset++; 
            for(j=0;j<n_squares;j++) {
                x = j * dw;
                if (((j + offset)% 2) > 0) {
                    offscreen_graphics.setColor(color_1);
                } else {
                    offscreen_graphics.setColor(color_2);
                } 
                offscreen_graphics.fillRect(x,y,dw,dh); 
            } 
        }
    }
    public void paint(Graphics g)
    {
        Rectangle old_r, new_r,to_repaint;

        old_r = new Rectangle(x_pos,y_pos,square_size,square_size); 
        new_r = new Rectangle((x_pos + square_step),(y_pos +
                     square_step),square_size, square_size);
        to_repaint = new_r.union(old_r); 
        draw_check(); 
        //just draw what's needed except for the first time
        if (x_pos < 1) {
            g.clipRect(to_repaint.x,to_repaint.y, 
                        to_repaint.width,to_repaint.height);
        }
        x_pos += square_step; 
        y_pos += square_step; 
        //same as before but now the square is drawn to the offscreen image
        offscreen_graphics.setColor(square_color); 
        offscreen_graphics.fillRect(x_pos,y_pos,square_size, 
                     square_size); 
        //now that the offscreen image is all done draw the whole thing 
        //to the screen 
        g.drawImage(offscreen,0,0,this); 
    }
}

No comments:

Post a Comment