Chapter 10 - Advanced Graphics

Motion

Motion can be achieved by sequentially adjusting and repainting the applet. This method is most effective when responding to user input. If your applet is going to continuously change or need to compute things while the movement is occurring you need to create a thread. Threads are a little more complicated than simple movement and will be discussed in the next section.

The easiest way to create simple motion is to respond to the mouseDrag() method. The strategy is simple. When the user clicks in the applet, paint something at that location. When the user drags the mouse inside the applet, paint the object at the location of the mouseDrag event. Since mouseDrag is continuously called as the user moves the mouse around the applet (while the button is down) it will update the mouse location.

	import java.applet.*;
	import java.awt.*;
	import java.awt.Graphics;
	
	public class DragText extends Applet {
	
		int xpos;
		int ypos;
	
		public void init() {

			resize(300, 100);

		}
	
		public void paint(Graphics g) {
	
			g.drawString("Drag me...", xpos, ypos);
	
		}
	
		public boolean mouseDown(Event e, int x, int y) {
	
			xpos = x;
			ypos = y;
			repaint();
			return true;
		}		
	
		public boolean mouseDrag(Event e, int x, int y) {
	
			xpos = x;
			ypos = y;
			repaint();
			return true;
	
		}
	}

The DragText applet is a simple example showing the ease of responding to user input.

Figure 10.1

If you want to keep track of several objects you need to store them somehow and repaint all objects each time the applet is updated. The best way to store the objects is to use an array, or pair of arrays, to hold the coordinates of the objects. When the applet repaints you need to repaint all the coordinates in the array.

	import java.applet.*;
	import java.awt.*;
	import java.awt.Graphics;

	public class DragImage extends Applet {
	
		int xpos[] = new int[20];
		int ypos[] = new int[20];
		int i;
		int j=0;
		int number=0;
		Image ball;
	
		public void init() {
			resize(300, 100);
			ball = getImage(getDocumentBase(), "images/ball.gif");
			setBackground(Color.white);
		}
	
		public void paint(Graphics g) {
	
			for (i=0; i<number; i++)
				g.drawImage(ball, xpos[i], ypos[i], this);
	
		}
	
		public boolean mouseDown(Event e, int x, int y) {
	
			xpos[j] = x;
			ypos[j] = y;
			if (number==20)
				number=20;
			else number++;
			repaint();
			return true;
	
		}
		
		public boolean mouseDrag(Event e, int x, int y) {
	
			xpos[j] = x;
			ypos[j] = y;
			repaint();
			return true;
	
		}
	
		public boolean mouseUp(Event e, int x, int y) {	
		
			xpos[j] = x;
			ypos[j] = y;
			if (j==19)
				j=0;
			else j++;
			repaint();
			return true;	

		}

	}

Figure 10.2

The DragImage applet is very similar to the DragText applet but keeps track of 20 objects and draws an image instead of text. When the array in full of coordinates it starts over at the first position. This means that the applet can only have 20 objects on the screen at once. When you create the 21st ball the fist ball will disappear.

The DragImage applet suffers from flicker because it needs to draw up to 20 images every time the applet is updated. There are methods to reduce and sometimes eliminate the cause of flicker and they will be discussed later.

Motion can also be short movements inside the applet triggered by a user's input.\

	import java.applet.*;
	import java.awt.*;
	import java.awt.Graphics;
	
	public class SlideText extends Applet {
	
		int xpos = 0;
		int ypos= 0;
		int xDist= 0;
		int yDist = 0;
	
		public void init() {
			resize(300, 100);
		}
	
		public void paint(Graphics g) {
	
			for (int i=0; i <10; i++) {
				xpos = xpos + xDist;
				ypos = ypos + yDist;
				g.clearRect(0,0,300,100);
				g.drawString("Watch me...", xpos, ypos);
			}
	
		}
	
		public boolean mouseUp(Event e, int x, int y) {
	
			xDist = x -xpos;
			yDist = y -ypos;
			xDist = xDist / 10;
			yDist = yDist / 10;
			repaint();
			return true;
	
		}
	}

Figure 10.3

The SlideText applet calculates the beginning and ending position of the text and then draws the text 10 times, each a little closer to the final position. This has the effect of sliding the text towards the mouse click position. This applet is not very accurate because some distance will be lost to remainder caused by the integer division.

Introduction to threads

Threads allow an applet to do more than one thing at a certain point in time. It is similar to the idea of multitasking which was introduced on the Macintosh platform with the MultiFinder. It allowed you to use more than one application on your machine at once. Both programs would be running even though only one was up front on the screen.

This method of computing has probably become second nature to you. You may be typing a paper while downloading a file over the network. Each application is running in its own space and not interfering with the others.

Threads are similar in concept. An applets can create and destroy threads that perform certain functions. Any time your applet computes something that will take a significant amount of time or power it is better to use a thread. The thread will then continue on its task while your applet performs other functions.

Continuous applets or CPU intensive applets such as animations will monopolize the computer's time and not allow other functions to complete. If the intensive portions are written as threads they will not interfere but rather take CPU time as they can. This property also allows many applets to used on a single page.

Threads are easy to add to your applet code and should be used whenever necessary. The following five steps will implement a thread in your code.

First you need to add the words "implements runnable" to the class declaration of your applet. This adds the functionality needed to your applet to run threads.

	public class ThreadDemo extends java.awt.Applet implements Runnable {
	
	}

Second you need to create an instance of type Thread and import the correct package that contains the Thread class. This instance variable will hold the thread your applet uses.

	import java.lang.*;

	Thread myThread;

Third you need to override the start() method to simply create and start the thread. It should check to see if the thread exists and if not create and start it.

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

Fourth you need to override the run() method and implement the body of the applet there. You can call other methods which will in turn be running inside this thread.

	public void run() {

	}

Lastly, you need to override the stop() method to stop the running thread when the user leaves the page or quits. It should stop the executing thread and set the value back to null which allows it to be destroyed by the system.

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

If the user returns to your applet the start method will be called and a new thread will begin execution. These steps are usually identical for all applets that implement a thread and can be copied without incident. Threads and multithreading will be discussed in detail in Chapter 13.

Animation

Animation is a classic example of the proper use of threads. Without threads the screen would update incorrectly or the rest of the system would be unable to complete any other tasks. A simple example of animation uses the threads technique described previously and animates a bit of scrolling text.

	import java.applet.*;
	import java.awt.Graphics;
	import java.awt.*;
	import java.lang.*;
	import java.awt.Font;
	import java.awt.FontMetrics;
	
	public class TickerDemo extends Applet implements Runnable {
	
		String message;
		int x;
		int width;
		Thread ticker;
		Dimension appletDim;
	
		public void init() {
	
			appletDim = size();
			resize(appletDim.width, appletDim.height);
			message = getParameter("text");
			FontMetrics fontInfo = getFontMetrics(getFont());
			width = fontInfo.stringWidth(message);
			x = 0 - width;
	
		}
	
		public void start() {
		
			if (ticker == null) {
				ticker = new Thread(this);
				ticker.start();
			}
	
		}
	
		public void run() {
	
			while (true) {
				if (x < appletDim.width) 
					x=x+5;
				else x=0-width;
				try {
					Thread.sleep(200); }
					catch (InterruptedException e) {}
				repaint();
			}
		}	
	
		public void paint(Graphics g) {	
			
			g.drawString(message, x, 10);

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

Figure 10.4

You should feel fairly comfortable with the TickerDemo applet. The try...catch statement is a wrapper to catch any exceptions thrown by the method sleep which just causes the applet to pause for the given amount of milliseconds. Exceptions and their usage will be discussed in Chapter ??.

	import java.applet.*;
	import java.awt.Graphics;
	import java.awt.*;
	import java.lang.*;
	
	public class GolfBall extends Applet implements Runnable {
	
		Image golfBalls[] = new Image[8];
		int x;
		int width;
		Thread ticker;
		Dimension appletDim;
		int i;
	
		public void init() {
	
			appletDim = size();
			resize(appletDim.width, appletDim.height);
			for(int j=0; j<8; j++)
				golfBalls[j] = getImage(getDocumentBase(), "images/gb" 					+ (j+1) + ".gif");	
			x = -50;
			i=0;
			setBackground(Color.white);
	
		}
	
		public void start() {
		
			if (ticker == null) {
				ticker = new Thread(this);
				ticker.start();
			}
	
		}
	
		public void run() {
	
			while (true) {
				if (x < appletDim.width) 
					x=x+5;
				else x=-50;			
				try {
					Thread.sleep(100); }
				catch (InterruptedException e) {}
				repaint();
			}
			}
	
		public void paint(Graphics g) {
			
			g.drawImage(golfBalls[i], x, 10, this);
			if (i == 7)
				i=0;
			else i=i+1;
	
		}
	
		public void stop() {
	
			if (ticker!= null) {
				ticker.stop();
				ticker = null;
			}
		}
	}

Figure 10.5

The Golf Ball applet is an example of movement and animation. Eight gif files are stepped through sequentially while moved across the screen. This gives the effect that the ball is rolling from left to right across the applet. The applet loads the images into an array so they can be easily accessed and then animates them with a thread.

The images flicker in the first few seconds of the applet's life because the images are still being loaded and drawn during the first few frames. This can be avoided by using the Media Tracker class that will wait until the images are fully loaded before starting the thread.

Depending on your machine, you may have seen some flickering during the applet's animation as well. This can be removed or partially removed by using several different techniques.

Overriding update()

The easiest technique to reduce flickering in your applet is to override the update() method supplied by Java. The default update clears the applet's screen and then calls the paint method. The act of clearing the applet before painting can cause flicker, especially if the applet area is large.

The solution is to rewrite update so that it does not clear the screen before calling paint. This means that objects will be left on the screen and the paint method will simply paint on top of them. This works well in cases where an animation steps through frames in place. If the animation is moving then the objects will leave trails.

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

Clipping

The second technique allows you to define a rectangular area to repaint rather than repainting the entire applet. This is called clipping and works well in certain situations when you need to clear the screen but still want to reduce the flicker of the running applet. Java provides simple methods for adding this functionality to your applets.

The clipRect() method limits the drawing area that the update and paint methods effect. By limiting the area to only what needs to be cleared and redrawn the applet will flicker less. This means you need to keep track of the area that needs to be redrawn while your applet is running. The method takes four arguments consisting of the x position, y position, width, and height of the clipping rectangle.