How-to Java, by Todd Sundsted

When static images just don't make the cut

Learn how to spice up your applets and applications
with animated images

Summary
Images don't have to stand still. They can move. Unfortunately, with the basic Java language class library, you can't display images like the output of a video feed or even an animated GIF, which has become so commonplace on the Web. Not wanting you to be left in the dark ages, I have set out this month to show you how to manipulate the Java language class library to create animated Image objects. When we're done, you'll have not only a useful piece of software, but a much better understanding of two components of the Java language class library. (1,750 words)

Two months ago I showed you how to create an instance of the Image class given the URL of a file containing image data. Then, in last month's column, I showed you how to create an instance of the Image class given an instance of a class that implemented the ImageProducer interface. In both examples the rest of the application didn't know (or care, for that matter) how the Image object was created.

Let's examine the second case in a bit more detail.

True to its name, an image producer produces image data. The image producer in last month's column produced an image showing a color gradient. However, an image producer is not limited to generating (and regenerating) a single, static frame of image data; in fact, it is free to redefine the image data at any time.

Perhaps you can guess where I am going with this. After an image producer has produced one frame of image data, it is free to draw right on top of that frame to produce yet another frame of image data. Repeat this process a couple of times and, bam, you've got animation!

Whoah! Animation that easy? You bet. Let's take a closer look at how we can actually do this repetitive drawing.

The mechanics
Take a look at the illustration below, which shows the relationship between an image producer and one or more image consumers. As you know from our previous discussions, the image producer communicates information about the image data it is producing to one or more image consumers by invoking public methods on the image consumer. Image consumers then use this image information in a variety of ways -- to draw the image on the screen, for example. If you're feeling little lost about the whole concept of image producers and image consumers, take a quick detour back to last month's column before we move on.

The image producer/image consumer relationship

The interface between an image producer and an image consumer dictates the type of images that can be produced. With that in mind, let's look at two of the methods the ImageConsumer interface defines and the parameters those methods expect. The methods are the setHints() and imageComplete() methods. Let's begin at the end with the imageComplete() method, shown next:

imageComplete(int nStatus)

When an image producer calls an image consumer's imageComplete() method, it does so with the intent of informing the consumer about the status of the image reconstruction. The imageComplete() method requires an integer argument, which can be one of four values: IMAGEABORTED, IMAGEERROR, STATICIMAGEDONE, and SINGLEFRAMEDONE.

An image producer uses the arguments IMAGEABORTED and IMAGEERROR to indicate that something has gone wrong during the process of image production. It uses the argument STATICIMAGEDONE to indicate that image production is completely finished. Last of all, but central to our plan, it uses the argument SINGLEFRAMEDONE to indicate it has produced a single frame of image data but more frames may follow.

The following snippet demonstrates how an image producer would indicate to an image consumer that image production is complete:

public void startProduction(ImageConsumer ic)
{
      .
      .
   // image production code goes here...
      .
      .
   ic.imageComplete(ImageConsumer.STATICIMAGEDONE);
      .
      .
}

Now let's skip back to the beginning and examine the setHints() method, shown here:

setHints(int nHints)

Before image production begins, an image producer can tell the image consumer a little bit about how it intends to deliver the image data with "hints." The image consumer can use this information to optimize its processing of the image data. Image producers can specify five hints, which can be combined, to let an image consumer know what's coming: RANDOMPIXELORDER, COMPLETESCANLINES, TOPDOWNLEFTRIGHT, SINGLEFRAME, and SINGLEPASS.

An image producer uses the argument RANDOMPIXELORDER to indicate it will deliver the image data in an unpredictable order (this is the default). It uses the argument COMPLETESCANLINES to indicate it will deliver the image data one scan line at a time. The argument TOPDOWNLEFTRIGHT indicates the image producer will deliver the image data in a top-down and left-to-right fashion. And finally, the arguments SINGLEFRAME and SINGLEPASS indicate the image data will consist of only a single frame and it will set a particular pixel's data only one time. An image producer should not send the last two hints if it intends to produce more than one frame of image data.

The following snippet demonstrates how an image producer would indicate to an image consumer how it intends to deliver image data:

public void startProduction(ImageConsumer ic)
{
      .
      .
   ic.setHints(ImageConsumer.RANDOMPIXELORDER);
      .
      .
   // image production code goes here...
      .
      .
}

The flowchart displayed below shows the steps an image producer must take when sending multiframe image data to image consumers. Here's what happens. An image producer must first communicate hints and related information to any image consumers that are interested in that information. It then must send the image data for the first frame. As each frame is completed, the image producer invokes the image consumer's imageComplete() method with the argument SINGLEFRAMEDONE. It then sends image data for the next frame, and so on. When the image producer has finished sending all the frames, it invokes the image consumer's imageComplete() method with the argument STATICIMAGEDONE.

An image producer's flow of control

Getting down to the code
Because all of the hard work of image handling is done within the image producer, the application itself doesn't require any significant modifications. The image created from the custom image producer plugs into an application just like any other image. The code listing A framework for displaying an image below shows the framework we used last month for displaying an image, with one small change. The createImage() method now takes as a parameter an instance of the new image producer. No additional machinery is required.

public class Framework extends Applet
{
   private Image _im = null;

   public void init()
   {
      // createImage() creates an image from an
      //   image producer object...
      _im = createImage(new NewImageProducer());

      // because this is an applet, "this" refers to an
      //   instance of the Applet class... which just
      //   happens to be an image observer...
      prepareImage(_im, this);
   }

   public void paint(Graphics g)
   {
      // see the comment about "this" above... 
      g.drawImage(_im, 50, 50, this);
   }
}

Code Listing: A framework for displaying an image

Applying the techniques
The applet in this section demonstrates this technique in action. Unfortunately, I wasn't able to provide a video feed for this column (although that certainly would have been impressive), so I had to find another way to illustrate the techniques we reviewed this month. After much thought and diligent searching, I came up with the Mandelbrot generator applet, which I happen to think is really neat.

You need a Java-enabled browser to see this applet.

A Mandelbrot generator

The image producer in this example produces an image of the Mandelbrot set. If you're wondering what the heck that is, be sure to read the sidebar The Mandelbrot set for more information. The image producer first produces a rough, low-resolution image, which it enhances with each successive frame. In this way, the viewer is treated to continually "better" approximations of the image without having to wait until the full image is generated (which is, let me warn you now, a time-consuming process).

The complete source code for the Mandelbrot image producer is available here. The complete source code of the applet is available here.

Wrapping up
A framework like the one shown previously is really all that is required to display the image. I recommend you take the image producer out for a whirl and drop it into some of your code in place of a static image. Go ahead, give it a try. If you come across any serious problems, you can always contact me (todd.sundsted@javaworld.com) with whatever's holding you up.

Next month I'll be launching the first of a multipart series on 3-D graphics in Java. 3-D graphics engines are at the heart of a widely diverse set of applications from computer aided design software to games. They're also an essential element of any virtual reality environment. So stick around and I'll show you how to create your own little virtual 3-D world.

About the author
Todd Sundsted has been writing programs since computers became available in desktop models. Though originally interested in building distributed object applications in C++, Todd moved to the Java programming language when Java became the obvious choice for that sort of thing. Todd is co-author of the Java Language API SuperBible, now in bookstores everywhere. In addition to writing, Todd provides consulting, training, and mentoring services to companies learning about and working with the Internet.

Resources

Previous How-To Java articles

[(c) Copyright 1997 Web Publishing Inc., an IDG Communications company]


Original URL: http://www.javaworld.com/javaworld/jw-04-1997/jw-04-howto.html
Last modified: Tuesday, April 01, 1997

The Mandelbrot set

The Mandelbrot set is one of the best-known examples of a fractal. It's so well known that even if you haven't heard of it, you've almost certainly seen it.

All fractals share one important characteristic--they are self-similar at all levels of magnification. This means that the closer you look at a fractal, the more detail you see--and the detail seen close up looks just like the detail seen far away.

Contrast this with most things we are familiar with--my lawn, for example, looks like a hilly green carpet when viewed from my living room window. However, when I view it from an inch or two away, the hills disappear and individual blades of grass appear. I don't see smaller and smaller hills.

The Mandelbrot set is a set of complex numbers. A complex number belongs in the Mandelbrot set if the passes (or fails, depending on how you look at it) a simple test.

Take any complex number c and run it through the following equation (z is initially zero):

f(z) = z * z + c

Replace the previous value of z with the result and run c through again. Repeat this procedure. Eventually, one of two things will happen. Either the magnitude of the result will begin to grow larger and larger (at which point we quit and say the number is not in the Mandelbrot set), or, after a large number of iterations, the magnitude won't grow larger and larger (at which point we give up and say the number is in the Mandelbrot set).

We can plot the resulting set by treating the real part of the complex number as the x coordinate and the imaginary part of the complex number as the y coordinate. The complex numbers found in the Mandelbrot set are usually colored black. Numbers not in the Mandelbrot set are colored according to the number of iterations required before we decide they're not in the set.

The most interesting thing about the Mandelbrot set is the incredible complexity that results from such a simple algorithm. At first glance, the resulting plot looks like a cardioid with warts. If you look at the warts, however, you see that they have warts and creeping tendrils. Look at the tendrils and you'll find miniature versions of the original cardioid, also resplendent with warts. And on and on it goes.

Back to story