Simplifying Graphics With Java and Threads (轉)
Simplifying Graphics With Java and Threads (轉)[@more@]
Simplifying Graphics With Java and Threads
By Alex Chaffee and John Papageorge
July 1997
<!-- Begin Main Body Text -->
Here's some code that Earthweb's Alex Chaffee likes to use as an example of how Java can be used to simplify algorithms by combining graphics with threading to render complex graphics.
Basically there are three threads: a Renderer, which performs a complex calculation; an Animator, which wakes up periodically and draws the current image to the screen; and of course the default User Interface thread, which responds to user events and changes the display as appropriate.
The Renderer runs at a low priority—just grinding away forever, or at least until it's done; it shouldn't interfere with the other two threads. The Animator only comes to life every 500 msec or so, to avoid the expense of drawing each pixel.
To perfoRM a specific calculation, subclass the Renderer to make a Mandelbrot function. Note the object-oriented design: this way, the Mandelbrot subclass only needs to override the Run method and doesn't have to worry about the threading implementation, which is handled by its superclass. Of course, in this example, the threading implementation is trivial, but this is a teaching exercise.
Other items of note are the use of an offscreen Graphics buffer, and the use of progressive rendering. The code makes several rough passes over the image, calculating the value in each quadrant, then gradually refines the view. Unlike .NETscape's rendering of progressive gifs, this program treats a calculated pixel as the "center" of a square of color, not as the top corner or a pixel-wide stripe, and so avoids that odd venetian-blind scrolling-up effect. With a laugh, Chaffee says, "But maybe I'm the only one in the world who thought Netscape's gif-rendering algorithm looked funny." now , see Chaffee's source code
/*
man.java
a test to do threaded animation
Copyright (c) 1996 Alex Chaffee, all rights reserved
PeRmission is granted to use this source code for
instructional purposes as long as copyright information
remains intact.
@Author: Alex Chaffee
*/
import java.awt.*;
import java.util.*;
import java.applet.*;
import java.lang.Math;
/**
* Animator
* This class implements the thread that draws the image to the screen
* every 500 msec.
* @author Alex Chaffee
**/
class Animator implements Runnable {
static int delay = 500;
private Applet applet;
private Vector images;
private Thread thread = null;
private int frame;
public Animator(Applet appletIn, Vector imagesIn) {
applet = appletIn;
images = imagesIn;
}
public void start() {
if (thread == null) {
thread = new Thread(this);
thread.start();
}
}
public void stop() {
System.out.println("Animator stopped");
thread.stop();
thread = null;
}
public void run() {
thread.setPriority(Thread.MAX_PRIORITY-1);
while (thread != null) {
applet.repaint();
try {
thread.sleep(delay);
} catch (InterruptedException e) {};
// Increment frame
// Must use synchronized on the off chance that update gets called
// between the frame++ and the frame=0
synchronized (this) {
frame++;
if (frame > images.size()) {
frame = 0;
}
}
}
}
public void paint(Graphics g) {
if (frame span = 4 / (K^mag)
* @see kZoom
*/
int mag;
/**
* point in virtual space around which we're drawing the thing
* (i.e. where in virtual space is (0,0) in the window
*/
float originX = 0;
float originY = 0;
public Mandelbrot(Image image, int mag, float originX, float originY) {
super(image);
this.mag = mag;
this.originX = originX;
this.originY = originY;
}
static float MagToSpan(int mag) {
return 4 / (float)(java.lang.Math.pow(kZoom, mag));
}
/**
* Render this image progressively.
*/
public void run() {
/**
* The graphics port of the image we're drawing to
*/
Graphics g;
g = image.getGraphics();
int width, height;
width = image.getWidth(null);
height = image.getHeight(null);
/**
* The width of the window on the virtual space
* @see mag
*/
float span = MagToSpan(mag);
/**
* current resolution (how big each pixel is)
*/
int resolution;
/**
* how far in do we go before we're rendering by pixel
*/
int resolutionMax = 10; // should calculate based on image size
/**
* current increment (based on resolution)
*/
float inc;
resolution = 1;
int widthPixel, heightPixel;
do {
// the resolution determines which power of two we're dividing
// the span by -- so it fills in by squares
float scale = 1 / (float)(java.lang.Math.pow(2, resolution));
inc = span * scale;
// pre-calculate the width and height of each "pixel" in the image
widthPixel = (int)(scale * width) + 1;
heightPixel = (int)(scale * height) + 1;
spew("resolution " + resolution + " pixel=(" + widthPixel + ", " +
heightPixel + ")");
// Mandelbrot function
Color c;
int maxiterations = 100;
int i;
float temp, r, x, y;
float minX = originX + inc/2;
float maxX = originX + span;
float minY = originY + inc/2;
float maxY = originY + span;
for (float c1= minX; c1 os rather than
// recalculating
int h = (int)(((c1 - originX)*width)/span);
int v = (int)(((c2 - originY)*height)/span);
// center on the point, not upper-left
h -= widthPixel/2;
v -= heightPixel/2;
g.setColor(c);
g.fillRect(h, v, widthPixel, heightPixel);
Thread.currentThread().yield();
}
/*
// even with yield, there's a goofy bug with Netscape 2.0b5
// and lower wherein the browser never takes time for itself
// (how selfless). So I'll try sleeping for a millisec just to
// see if it helps
Thread.currentThread().sleep(1);
*/
} // for traverse virtual space
resolution++;
} while (widthPixel > 1 || heightPixel > 1);
spew("stopped");
} // method run
void spew(String str) {
System.out.println("(" + originX + "," + originY + ")x" + mag + ":" + str);
}
public static int iterate(float c1, float c2) {
int maxiterations = 100;
// Nitty gritty, merci Benoit
float r, x, y, temp;
int i;
r = 0;
x = 0;
y = 0;
i = 0;
while ((i0)
zoom(mag-1);
return true;
default:
return false;
} // switch
} // keyDown
void zoom(int magNew) {
float spanOld = Mandelbrot.MagToSpan(mag);
float centerX = originX + spanOld/2;
float centerY = originY + spanOld/2;
mag = magNew;
float spanNew = Mandelbrot.MagToSpan(mag);
originX = centerX - spanNew/2;
originY = centerY - spanNew/2;
stopThreads();
startThreads();
}
public boolean mouseDown(Event evt, int x, int y) {
System.out.println(evt.toString());
stopThreads();
animator = null;
float span = Mandelbrot.MagToSpan(mag);
originX = x * span / size().width + originX - span/2;
originY = y * span / size().height + originY - span/2;
System.out.println("New origin: " + x + (new Dimension(x,y)).toString()
+ " -> "
+ originX + "," + originY );
start();
return true;
}
static float convert(int x, int width, int mag, float origin) {
float span = Mandelbrot.MagToSpan(mag);
float z = x * span/width + origin;
return z;
}
public boolean mouseMove(Event evt, int x, int y) {
float vx = convert(x, size().width, mag, originX);
float vy = convert(y, size().height, mag, originY);
int i = Mandelbrot.iterate(vx, vy);
String str = "i=" + (new Integer(i)).toString();
str = str + " (" + vx + ", " + vy + ")";
getAppletContext().showStatus(str);
// System.out.println((new Integer(i)).toString());
return true;
}
}