| 
 
| 
 BBS水木清华站∶精华区发信人: midi (迷笛), 信区: Java标 题: Java里的高级图象处理
 日 期: Sat Sep 7 16:09:51 1996
 
 + Chapter Project
 + Class Organization
 + How It Works
 + Fractals and the Mandelbrot Set
 + Using The Applets
 + The Mandelbrot Class
 + CalculateFilterNotify Interface
 + CalculatorProducer Interface
 + The CalculatorFilter Class
 + The CalculatorImage Class
 + The MandelApp Class
 + The MandelZoomApp Class
 + The BmpImage Class
 + Automatic Documentation with Javadoc
 + Summary
 
 Advanced Image Processing
 
 This chapter's project, one that views the Mandelbrot set, gives you
 examples of some of the more advanced concepts you have been introduced to.
 The Mandelbrot set is the most spectacular example of fractals, which
 represents one of the hot scientific topics of recent years. With the
 applets in this chapter, you can view or generate an original Mandelbrot
 image and zoom in and out of it to produce new portions of the set.
 
 Since the Mandelbrot set can take a while to generate?requiring millions of
 calculations?it gives you a chance to combine threads and image filters so
 you can view the set as it's being generated. You might also want to save
 the Mandelbrot images. The BmpClass, introduced in Part III, that converts
 a BMP formatted file into a Java image, is enhanced so you can save the
 Mandelbrot data as a BMP file. You can then view or modify it with any tool
 that can handle the BMP format. Finally, the chapter concludes by showing
 how you can auto-document the source of a Java class into a HTML file. This
 can be viewed by a browser and has links to other classes.
 
 Since you have already been introduced to most aspects of Java, this
 chapter will jump straight into the project. Topics will be introduced as
 they are appropriate.
 
 Chapter Project
 
 There are actually two applets in this chapter. The first applet,
 MandelApp, is used to generate a full Mandelbrot set. The tools and BMP
 file produced by this applet are input into the second applet, called
 MandelZoomApp. This applet displays a Mandelbrot set, then allows you to
 zoom (magnify) portions of the set so you can inspect its fractal
 qualities. You can also return to previous images and zoom into another
 area.
 
 If you want to use the file-saving capabilities of this program, you need
 to run it from something that does not prevent file saving, such as the
 appletviewer program. You can run the program in a browser like Netscape,
 though; it will be able to do everything except save the images as files.
 
 Class Organization
 
 Table 14.1 lists the classes used in this chapter's applets. Most of the
 classes are new, so their names are set in boldface type. Existing classes
 that were modified have their names italicized.
 
 Table 14.1. Mandelbrot project classes and interfaces.
 
 Class/Interface Description
 
 BmpImage For BMP-Image conversion.
 
 CalculatorFilter ImageFilter that produces updates of images as they
 are generated.
 
 CalculatorFilterNotifyInterface that defines ways an ImageFilter can
 receive data updates.
 
 CalculatorImage Used to tie a calculation object, an image, and a
 CalculatorFilter together.
 
 CalculatorProducer Interface that defines a mechanism for establishing
 how an ImageFilter can update a calculation class.
 
 MandelApp An Applet that produces a full Mandelbrot image and
 lets you save it to a file.
 
 MandelEntry Accessor class for keeping information about a
 Mandelbrot image.
 
 A Thread that produces Mandelbrot data for the
 specified parameters. It implements
 Mandelbrot Calculator-Producer to get started by a filter. It
 uses CalculatorFilterNotify to update a filter with
 new data.
 
 MandelZoomApp An Applet that displays the full Mandelbrot set and
 allows you to zoom in and out of the set.
 
 How It Works
 
 Because the Mandelbrot set can take quite a while to generate, it was
 designed by combining a calculation thread with an image filter so you can
 see the results as they are generated. However, understanding how the
 classes interrelate is a little tricky. Figure 14.1 shows the workflow
 involved in producing a Mandelbrot image. Understanding this flow is the
 key to understanding this project.
 
 Figure 14.1. Workflow of producing a Mandelbrot image.
 
 The process begins when an applet displaying Mandelbrot sets constructs a
 Mandelbrot object. (In this project, the two Applet classes are MandelApp
 and MandelZoomApp.) The Mandelbrot object, in turn, creates an instance of
 the CalculatorImage class. The Mandelbrot set passes itself as a part of
 the CalculatorImage constructor. It is referenced as a CalculatorProducer
 object, an interface that the Mandelbrot class implements. This interface
 implementation will be used to communicate with the image filter.
 
 In the next step, the applet requests a Mandelbrot image. This is initiated
 by calling the getImage() method of the Mandelbrot object, which in turn
 leads to a call to a like-named method of the CalculatorImage object. At
 this point, the CalculatorImage object first creates a color palette by
 using an instance of the ImageColorModel class, then creates a
 MemoryImageSource object. This object, which implements ImageProducer,
 produces an image initialized to all zeros (black); it's combined with an
 instance of the CalculatorFilter class to produce a FilteredImageSource.
 
 When the MemoryImageSource object produces its empty image, it is passed to
 the CalculatorFilter, which takes the opportunity to produce the calculated
 image. It does this by kicking off the thread of the image to be
 calculated. The CalculatorFilter doesn't know that it is the Mandelbrot set
 that's calculated?it just knows that some calculation needs to occur in the
 CalculatorProducer object in which it has a reference.
 
 Once the Mandelbrot thread is started, it begins the long calculations to
 produce a Mandelbrot set. Whenever it finishes a section of the set, it
 notifies the filter with new data through the CalculatorFilterNotify
 interface. The filter, in turn, lets the viewing applet know that it has
 new data to display by updating the corresponding ImageConsumer, which
 causes the applet's imageUpdate() method to be called. This causes a
 repaint, and the new image data to be displayed. This process repeats until
 the full image is created.
 
 As you have probably observed, this is a complicated process. Although the
 mechanics of image processing were introduced in Part III, it doesn't hurt
 to have another example. The Calculator classes here are meant to provide a
 generic approach toward manipulating images that need long calculations.
 You can replace the Mandelbrot class with some other calculation thread
 that implements CalculatorProducer, and everything should work. A good
 exercise would be to replace Mandelbrot with another fractal calculation or
 some other scientific imaging calculation. (I found that replacing
 Mandelbrot with a Julia fractal class calculation was very easy).
 
 Fractals and the Mandelbrot Set
 
 Before going into the internals of the classes that make up this project,
 it's worth spending a couple of moments to understand what's behind the
 images produced by the Mandelbrot class.
 
 In the 1970s, Benoit Mandelbrot at IBM was using computers to study curves
 generated by iterations of complex formulas. He found that these curves had
 unusual characteristics, one of which is called self-similarity. The curves
 have a series of patterns that repeat themselves when inspected more
 closely.
 
 One of the characteristics of the curves Mandelbrot studied was that they
 could be described as having a certain dimensional quality that Mandelbrot
 termed "fractal." One of the fractals that Mandelbrot was investigating is
 called Julia sets. By mapping the set in a certain way, Mandelbrot came
 across a set that turned out to include all the Julia sets?a kind of a
 master set that was deemed the Mandelbrot set. This set has several
 spectacular features, all of them beautiful. The most striking of these is
 its self-similarity and a extraordinary sensitivity to initial conditions.
 As you explore the Mandelbrot set, you will be amazed by both its seeming
 chaos and exquisite order.
 
 Figure 14.2 shows the famous Mandelbrot set, produced by this chapter's
 MandelApp applet. Figures shown throughout this chapter shows the kind of
 images that appear when you zoom into various places in this set. The
 Mandelbrot set is based on a seemingly simple iterated function, shown in
 Formula 14.1.
 
 Figure 14.2. The full Mandelbrot set image.
 
 Formula 14.1. Formula for calculating the Mandelbrot set.
 
 zn+1=zn2 + c
 
 In Formula 14.1, z and c are complex numbers. The Mandelbrot set is
 concerned with what happens when z0 is zero and c is set over a range of
 values. The real part of c is set to the x-axis, and the complex portion
 corresponds to the y-axis. A color is mapped to each point based on how
 quickly the corresponding value of c causes the iteration to reach
 infinity. The process of "zooming" in and out of the Mandelbrot set is
 equivalent to defining what ranges of c are going to be explored. It is
 amazing that something so simple can yield patterns so sophisticated!
 
 ---------------------------------------------------------------------------
 [Image]NOTE: If you are more interested in chaos and fractals, there are a
 lot of places to turn. Chaos by James Gleick (Penguin, 1987) is a layman's
 introduction to the ideas and discoveries that gave rise to chaos theory
 and the study of fractals. Mandelbrot's The Fractal Geometry of Nature
 (W.H. Freeman, 1983) lays out his ideas on fractals and nature. For a
 rigorous mathematical treatment of fractals, see the beautiful book
 Fractals Everywhere (Academic Press, 1988), written by one of the foremost
 figures in fractals, Michael Barnsley. Among other things, Barnsley is a
 major innovator on how to use fractal geometrics to achieve high rates of
 data compression.
 
 For a no-nonsense approach to writing programs that display fractals, see
 Fractal Programming in C by Roger T. Stevens (M&T Books, 1989). The
 algorithms for the Mandelbrot set were developed from this book. The C
 programs in this book map very easily to Java?except for the underlying
 graphics tools, which were developed for MS-DOS. However, the image
 calculation classes created in this chapter aim to fill this gap. With
 Stevens's book and these classes, you should be able to move his C code
 right over to Java and begin exploring the amazing world of fractals!
 ---------------------------------------------------------------------------
 
 Using The Applets
 
 There are two applets in this chapter. The first applet, MandelApp,
 generates the full Mandelbrot set. This will take a little while, depending
 on your computer; for example, on a 486DX2-50 PC, it takes a couple of
 minutes. When the image is complete, indicated by a message on the
 browser's status bar, you can save the image to a BMP formatted file by
 clicking anywhere on the applet's display area. The file will be called
 mandel.bmp. Remember to run this applet from a program, such as
 appletviewer, that lets applets write to disk.
 
 The other applet, MandelAppZoom, is more full-featured. It begins by
 loading the Mandelbrot bitmap specified by an HTML applet parameter tag.
 The default mandel1 corresponds to a BMP file and a data file that
 specifies x-y parameter values?included on this book's CD-ROM.
 
 Once the image is up, you can pick regions to zoom in on by clicking on a
 point in the image, then dragging the mouse to the endpoint of the region
 you want to display. Enter z or Z on the keyboard, and the applet creates
 the image representing the new region of the Mandelbrot set. The key to
 this applet is patience! The calculations can take a little while to set up
 and run. The applet tries to help your patience by updating the status bar
 to indicate what is going on. Furthermore, the image filter displays each
 column of the set as the calculations advance.
 
 You might select a region that doesn't appear to have anything interesting
 to show when you zoom on it. You can stop a calculation in the middle by
 entering a or A on the keyboard. The applet will take a moment to wrap up,
 but then you can proceed. When you are having problems finding an
 interesting region to look at, try increasing the size of the highlighted
 area. This will yield a bigger area that is generated, giving you a better
 feel for what should be inspected. You get the best results by working with
 medium-sized highlighted regions, rather than large or small ones.
 
 Figures 14.3 to 14.6 show what some of the zoomed-in regions of the
 Mandelbrot set look like. Figure 14.3 is a large area picked above the
 black "circles" of the full Mandelbrot set; Figure 14.5 explores an area
 between two of the black areas. The richest displays seem to occur at the
 boundaries of the black areas. The black color indicates that the
 particular value takes a long time to reach infinity. Consequently, these
 are also the regions that take the longest to calculate. You get what you
 pay for!
 
 Figure 14.3. Zoom in over black regions of Figure 14.2.
 
 Figure 14.4. Zoom in of Figure 14.3.
 
 Figure 14.5. Zoom in between black regions of Figure 14.2.
 
 Figure 14.6. Zoom in of Figure 14.5.
 
 The zoom applet maintains a cache of processed images so you can move back
 and forth among the processed images. Table 14.2 lists the text codes for
 using the zoom applet.
 
 Table 14.2. Codes for controlling the Mandelbrot applet.
 
 Characters Action
 
 A or a Abort current Mandelbrot calculation.
 
 B or b Go to previous image.
 
 F or f Go to next image.
 
 C or c Remove all but full image from memory.
 
 N or n Go to next image.
 
 P or p Go to previous image.
 
 S or s Save the current image to a BMP file prefixed by tempMandel.
 
 Z or z Zoom in on currently highlighted region.
 
 The Mandelbrot Class
 
 The Mandelbrot class, shown in Listing 14.1, calculates the Mandelbrot set.
 It implements the Runnable interface, so it can run as a thread, and also
 implements the CalculatorProducer interface, so it can update an image
 filter of progress made in its calculations.
 
 There are two constructors for the Mandelbrot class. The default
 constructor produces the full Mandelbrot set and takes the dimensions of
 the image to calculate. The Real and Imagine variables in the constructors
 and the run() method are used to map the x-y axis to the real and imaginary
 portions of c in Formula 14.1. The other constructor is used to zoom in on
 a user-defined mapping.
 
 A couple of the other variables are worth noting. The variable
 maxIterations represents when to stop calculating a number. If this number,
 set to 512, is reached, then the starting value of c takes a long time to
 head toward infinity. The variable maxSize is a simpler indicator of how
 quickly the current value grows. How the current calculation is related to
 these variables is mapped to a specific color; the higher the number, the
 slower the growth. If you have a fast computer, you can adjust these
 variables to get a richer or duller expression of the Mandelbrot set.
 
 Once the thread is started (by the CalculatorFilter object through the
 start() method), the run() method calculates the Mandelbrot values and
 stores a color corresponding to the growth rate of the current complex
 number into a pixel array. When a column is complete, it uses the
 CalculateFilterNotify to let the related filter know that new data has been
 produced. It also checks to see whether you want to abort the calculation.
 Note how it synchronizes the stopCalc boolean object in the run() and
 stop() methods.
 
 The calculation can take a while to complete. Still, it takes only a couple
 of minutes on a 486-based PC. This performance is quite a testament to
 Java! With other interpreted, portable languages you would probably be
 tempted to use the reset button because the calculations would take so
 long. With Java you get fast visual feedback on how the set unfolds.
 
 A good exercise is to save any partially developed Mandelbrot set; you can
 use the saveBMP() method here. You also need some kind of data file to
 indicate where the calculation was stopped.
 
 Listing 14.1. The Mandelbrot class.
 
 import java.awt.image.*;
 import java.awt.Image;
 import java.lang.*;
 // Class for producing a Mandelbrot set image...
 public class Mandelbrot implements Runnable, CalculatorProducer {
 int width; // The dimensions of the image...
 int height;
 CalculateFilterNotify filter; // Keeps track of image production...
 int pix[]; // Pixels used to construct image...
 CalculatorImage img;
 // General Mandelbrot parameters...
 int numColors = 256;
 int maxIterations = 512;
 int maxSize = 4;
 double RealMax,ImagineMax,RealMin,ImagineMin; // Define sizes to build...
 private Boolean stopCalc = new Boolean(false); // Stop calculations...
 // Create standard Mandelbrot set
 public Mandelbrot(int width,int height) {
 this.width = width;
 this.height = height;
 RealMax = 1.20; // Default starting sizes...
 RealMin = -2.0;
 ImagineMax = 1.20;
 ImagineMin = -1.20;
 }
 // Create zoom of Mandelbrot set
 public Mandelbrot(int width,int height,double RealMax,double RealMin,
 double ImagineMax,double ImagineMin) {
 this.width = width;
 this.height = height;
 this.RealMax = RealMax; // Default starting sizes...
 this.RealMin = RealMin;
 this.ImagineMax = ImagineMax;
 this.ImagineMin = ImagineMin;
 }
 // Start producing the Mandelbrot set...
 public Image getImage() {
 img = new CalculatorImage(width,height,this);
 return img.getImage();
 }
 // Start thread to produce data...
 public void start(int pix[],CalculateFilterNotify filter) {
 this.pix = pix;
 this.filter = filter;
 new Thread(this).start();
 }
 // See if user wants to stop before completion...
 public void stop() {
 synchronized (stopCalc) {
 stopCalc = Boolean.TRUE;
 }
 System.out.println("GOT STOP!");
 }
 // Create data here...
 public void run() {
 // Establish Mandelbrot parameters...
 double Q[] = new double[height];
 // Pixdata is for image filter updates...
 int pixdata[] = new int[height];
 double P,diffP,diffQ, x, y, x2, y2;
 int color, row, column,index;
 System.out.println("RealMax = " + RealMax + " RealMin = " + RealMin +
 " ImagineMax = " + ImagineMax + " ImagineMin = " + ImagineMin);
 // Setup calculation parameters...
 diffP = (RealMax - RealMin)/(width);
 diffQ = (ImagineMax - ImagineMin)/(height);
 Q[0] = ImagineMax;
 color = 0;
 // Setup delta parameters...
 for (row = 1; row < height; row++)
 Q[row] = Q[row-1] - diffQ;
 P = RealMin;
 // Start calculating!
 for (column = 0; column < width; column++) {
 for (row = 0; row < height; row++) {
 x = y = x2 = y2 = 0.0;
 color = 1;
 while ((color < maxIterations) &&
 ((x2 + y2) < maxSize)) {
 x2 = x * x;
 y2 = y * y;
 y = (2*x*y) + Q[row];
 x = x2 - y2 + P;
 ++color;
 }
 // plot...
 index = (row * width) + column;
 pix[index] = (int)(color % numColors);
 pixdata[row] = pix[index];
 } // end row
 // Update column after each iteration...
 filter.dataUpdateColumn(column,pixdata);
 P += diffP;
 // See if we were told to stop...
 synchronized (stopCalc) {
 if (stopCalc == Boolean.TRUE) {
 column = width;
 System.out.println("RUN: Got stop calc!");
 }
 } // end sync
 } // end col
 // Tell filter that we're done producing data...
 System.out.println("FILTER: Data Complete!");
 filter.setComplete();
 }
 // Save the Mandelbrot set as a BMP file...
 public void saveBMP(String filename) {
 img.saveBMP(filename,pix);
 }
 }
 
 CalculateFilterNotify Interface
 
 The CalculateFilterNotify interface defines the methods needed to update an
 image filter that works with a calculation thread. As shown in Listing
 14.2, the "data" methods are used for conveying a new batch of data to the
 filter. The setComplete() method indicates that the calculations are
 complete.
 
 Listing 14.2. The CalculateFilterNotify interface.
 
 /* Interface for defining methods for updating a
 Calulator Filter... */
 public interface CalculateFilterNotify {
 public void dataUpdate(); // Update everything...
 public void dataUpdateRow(int row); // Update one row...
 public void dataUpdateColumn(int col,int pixdata[]); // Update one
 column...
 public void setComplete();
 }
 
 CalculatorProducer Interface
 
 The CalculatorProducer interface, as shown in Listing 14.3, defines the
 method called when a calculation filter is ready to kick off a thread that
 produces the data used to generate an image. The CalculateFilterNotify
 object passed to the start() method is called by the producer whenever new
 data is yielded.
 
 Listing 14.3. The CalculatorProducer interface.
 
 // Interface for a large calculation to produce image...
 interface CalculatorProducer {
 public void start(int pix[],CalculateFilterNotify cf);
 }
 
 The CalculatorFilter Class
 
 The CalculatorFilter class in Listing 14.4 is a subclass of ImageFilter.
 Its purpose is to receive image data produced by some long calculation
 (like the Mandelbrot set) and update any consumer of the the new data's
 image. The CalculatorProducer, indicated by variable cp, is what produces
 the data.
 
 Since the ImageFilter class was explained in detail in Part III, issues
 related to this class are not repeated here. However, a couple of things
 should be pointed out. When the image is first requested, the filter gets
 the dimensions the consumer wants by a call of the setDimensions() method.
 At this point, the CalculatorFilter will allocate a large array holding the
 color values for each pixel.
 
 When the original ImageProducer is finished creating the original image,
 the filter's imageComplete() method will be called, but the filter needs to
 override this method. In this case, the CalculatorFilter will start the
 CalculatorProducer thread, passing it the pixel array to put in its
 updates. Whenever the CalculatorProducer has new data, it will call one of
 the four methods specified by the CalculateFilterNotify interface:
 dataUpdate(), dataUpdateRow(), dataUpdateColumn(), or setComplete(). (The
 dataUpdateColumn() method is called by the Mandelbrot calculation since it
 operates on a column basis). In each of these cases, the filter updates the
 appropriate consumer pixels by using the setPixels() method, then calls the
 consumer's imageComplete() method to indicate the nature of the change. For
 the three "data" methods, the updates are only partial, so a
 SINGLEFRAMEDONE flag is sent. The setComplete() method, on the other hand,
 indicates that everything is complete, so it sets a STATICIMAGEDONE flag.
 
 Listing 14.4. The CalculatorFilter class.
 
 import java.awt.image.*;
 import java.awt.Image;
 import java.awt.Toolkit;
 import java.lang.*;
 public class CalculatorFilter extends ImageFilter
 implements CalculateFilterNotify {
 private ColorModel defaultRGBModel;
 private int width, height;
 private int pix[];
 private boolean complete = false;
 private CalculatorProducer cp;
 private boolean cpStart = false;
 public CalculatorFilter(ColorModel cm,CalculatorProducer cp) {
 defaultRGBModel = cm;
 this.cp = cp;
 }
 public void setDimensions(int width, int height) {
 this.width = width;
 this.height = height;
 pix = new int[width * height];
 consumer.setDimensions(width,height);
 }
 public void setColorModel(ColorModel model) {
 consumer.setColorModel(defaultRGBModel);
 }
 public void setHints(int hints) {
 consumer.setHints(ImageConsumer.RANDOMPIXELORDER);
 }
 public void resendTopDownLeftRight(ImageProducer p) {
 }
 public void setPixels(int x, int y, int w, int h,
 ColorModel model, int pixels[],int off,int scansize) {
 }
 public void imageComplete(int status) {
 if (!cpStart) {
 cpStart = true;
 dataUpdate(); // Show empty pixels...
 cp.start(pix,this);
 } // end if
 if (complete)
 consumer.imageComplete(ImageConsumer.STATICIMAGEDONE);
 }
 // Called externally to notify that more data has been created
 // Notify consumer so they can repaint...
 public void dataUpdate() {
 consumer.setPixels(0,0,width,height,
 defaultRGBModel,pix,0,width);
 consumer.imageComplete(ImageConsumer.SINGLEFRAMEDONE);
 }
 // External call to update a specific pixel row...
 public void dataUpdateRow(int row) {
 // The key thing here is the second to last parameter (offset)
 // which states where to start getting data from the pix array...
 consumer.setPixels(0,row,width,1,
 defaultRGBModel,pix,(width * row),width);
 consumer.imageComplete(ImageConsumer.SINGLEFRAMEDONE);
 }
 // External call to update a specific pixel column...
 public void dataUpdateColumn(int col,int pixdata[]) {
 // The key thing here is the second to last parameter (offset)
 // which states where to start getting data from the pix array...
 consumer.setPixels(col,0,1,height,
 defaultRGBModel,pixdata,0,1);
 consumer.imageComplete(ImageConsumer.SINGLEFRAMEDONE);
 }
 // Called from external calculating program when data has
 // finished being calculated...
 public void setComplete() {
 complete = true;
 consumer.setPixels(0,0,width,height,
 defaultRGBModel,pix,0,width);
 consumer.imageComplete(ImageConsumer.STATICIMAGEDONE);
 }
 }
 
 The CalculatorImage Class
 
 The CalculatorImage class, shown in Listing 14.5, is the glue between the
 CalculatorProducer class that produces the image data and the
 CalculatorFilter that manages it. When an image is requested with the
 getImage() method, the CalculatorImage creates a color palette through an
 instance of the ImageColorModel class, then creates a MemoryImageSource
 object. This ImageProducer object produces an image initialized to all
 zeros (black). It is combined with an instance of the CalculatorFilter
 class to produce a FilteredImageSource. When the createImage() method of
 the Toolkit is called, production of the calculated image begins.
 
 The color palette is a randomly generated series of pixel values. Depending
 on your luck, these colors can be attractive or uninspiring. The
 createPalette() method is a good place to create a custom set of colors for
 this applet, if you want to have some control over its appearance. You
 should replace the random colors with hard-coded RGB values, and you might
 want to download a URL file that specifies a special color mapping.
 
 Listing 14.5. The CalculatorImage class.
 
 // This class takes a CalculatorProducer and sets up the
 // environment for creating a calculated image. Ties the
 // producer to the CalculatorFilter so incremental updates can
 // be made...
 public class CalculatorImage {
 int width; // The dimensions of the image...
 int height;
 CalculatorProducer cp; // What produces the image data...
 IndexColorModel palette; // The colors of the image...
 // Create Palette only once per session...
 static IndexColorModel prvPalette = null;
 int numColors = 256; // Number of colors in palette...
 // Use defines how big of an image they want...
 public CalculatorImage(int width,int height,CalculatorProducer cp) {
 this.width = width;
 this.height = height;
 this.cp = cp;
 }
 // Start producing the Calculator image...
 public synchronized Image getImage() {
 // Hook into the filter...
 createPalette();
 ImageProducer p = new FilteredImageSource(
 new MemoryImageSource(width,height,palette,
 (new int[width * height]),0,width),
 new CalculatorFilter(palette,cp));
 // Return the image...
 return Toolkit.getDefaultToolkit().createImage(p);
 }
 // Create a 256 color palette...
 // Use Default color model...
 void createPalette() {
 // Create palette only once per session...
 if (prvPalette != null) {
 palette = prvPalette;
 return;
 }
 // Create a palette out of random RGB combinations...
 byte blues[], reds[], greens[];
 reds = new byte[numColors];
 blues = new byte[numColors];
 greens = new byte[numColors];
 // First and last entries are black and white...
 blues[0] = reds[0] = greens[0] = (byte)0;
 blues[255] = reds[255] = greens[255] = (byte)255;
 // Fill in other entries...
 for ( int x = 1; x < 254; x++ ){
 reds[x] = (byte)(255 * Math.random());
 blues[x] = (byte)(255 * Math.random());
 greens[x] = (byte)(255 * Math.random());
 }
 // Create Index Color Model...
 palette = new IndexColorModel(8,256,reds,greens,blues);
 prvPalette = palette;
 }
 // Save the image set as a BMP file...
 public void saveBMP(String filename,int pix[]) {
 try {
 BmpImage.saveBitmap(filename,palette,
 pix,width,height);
 }
 catch (IOException ioe) {
 System.out.println("Error saving file!");
 }
 }
 }
 
 The MandelApp Class
 
 The MandelApp class, shown in Listing 14.6, creates and displays the full
 Mandelbrot set; the end result is shown in Figure 14.2. An instance of the
 Mandelbrot class is created in the init() method. Whenever the Mandelbrot
 calculation has produced some new data, it calls the ImageObserver-based
 method, imageUpdate(). This will probably result in the applet being
 repainted to show the new data. If the image is complete, an internal flag
 is set. After this, if you click the mouse, the image will be saved to a
 BMP formatted file called mandel.bmp.
 
 Listing 14.6. The MandelApp class.
 
 import java.awt.*;
 import java.lang.*;
 import java.applet.Applet;
 // This applet displays the Mandlebrot set through
 // use of the Mandelbrot class...
 public class MandelApp extends Applet {
 Image im; // Image that displays Mandelbrot set...
 Mandelbrot m; // Creates the Mandelbrot image...
 int NUMCOLS = 640; // Dimensions image display...
 int NUMROWS = 350;
 boolean complete = false;
 // Set up the Mandelbrot set...
 public void init() {
 m = new Mandelbrot(NUMCOLS,NUMROWS);
 im = m.getImage();
 }
 // Will get updates as set is being created.
 // Repaint when they occur...
 public boolean imageUpdate(Image im,int flags,
 int x, int y, int w, int h) {
 if ((flags & FRAMEBITS) != 0) {
 showStatus("Calculating...");
 repaint();
 return true;
 }
 if ((flags & ALLBITS) != 0) {
 showStatus("Image Complete!");
 repaint();
 complete = true;
 return false;
 }
 return true;
 }
 // Paint on update...
 public void update(Graphics g) {
 paint;
 }
 public synchronized void paint(Graphics g) {
 g.drawImage(im,0,0,this);
 }
 // Save Bitmap on mouse down when image complete...
 public boolean mouseDown(Event evt,int x, int y) {
 if (complete) {
 showStatus("Save Bitmap...");
 m.saveBMP("mandel.bmp");
 showStatus("Bitmap saved!");
 return true;
 } // end if
 return false;
 }
 }
 
 The MandelZoomApp Class
 
 Listing 14.7 shows the MandelZoomApp class, which represents this chapter's
 main applet; its function was described earlier, in the section "Using the
 Applets." See this section and Table 14.1 for how to use the applet.
 
 The most interesting features in the code are the routines for marking the
 region to be highlighted. Each pixel on the displayed Mandelbrot image maps
 an x-y value to a real-imaginary value of the c value of the Mandelbrot
 formula shown in Formula 14.1. Whenever you move the cursor, the current
 real-imaginary values are shown in the browser's status bar. When you
 highlight an area to zoom in on, you are really picking a range of c values
 to be explored. All the double variables are used for tracking this range
 of values. These values are read in at initialization by the
 loadParameters() method to match the bitmap that's displayed. You can
 specify other Mandelbrot BMP files and corresponding data files by changing
 the filename parameter of the applet's
 |  |