Images

   

Images are an exciting part of modern user interfaces and "enjoy" AWT support. This support is manifested through the AWT's facilities for image loading, drawing, animation, production, consumption, and filtering. Before exploring this support, some essential concepts need to be understood . These concepts include

  • Definition of an image?

  • The producer, consumer, and image observer model

  • Color models

An image is one or more rectangular grids of colored pixels. Each grid is known as a frame. JPEG files can only contain a single frame, whereas GIF files can contain one or more frames. A GIF file containing multiple frames is known as an animated GIF.

Each image is associated with an object created from a subclass of the AWT's abstract Image class (located in the java.awt package). This class contains a number of constants and methods of importance to images. Various Image methods will be mentioned throughout the remainder of this chapter. (Check out the SDK documentation for a complete description of Image members .)

The AWT's support rests on a producer, consumer, and image observer model. A producer is an object that produces an image. Producing this image might involve loading a GIF, JPEG, or another image format file, reading memory, or filtering pixels. Regardless, the producer sends an image's pixels to a consumer object that consumes these pixels. Consuming an image might involve saving the pixels to a file, storing the pixels in a memory buffer, storing the pixels in preparation for display, or filtering pixels. Consumers register/de-register their interest in producers by calling producer methods and producers send image information to consumers by calling consumer methods. Producers and consumers are usually (but not always) hidden deep inside the AWT. Furthermore, they work in partnership with image observers.

An image observer is a drawing surface that is notified when the producer has produced image information (such as width, height, or pixels). Pixels are never sent to an image observer: They are always sent to a consumer. As a frame becomes available, the producer calls the image observer which, in turn , "asks" the AWT to render the consumer's pixels (making up the frame) on the drawing surface.

To be an image observer, an object's class must implement the ImageObserver interface (located in the java.awt.image package). Because all drawing surfaces descend from Component and because Component implements ImageObserver, all drawing surfaces are image observers.

ImageObserver declares several flag constants and a method that is called by the producer. This method, known as imageUpdate, has the following signature:

 boolean imageUpdate (Image im, int flags, int x, int y, int width, int height) 

The im argument is a reference to an Image subclass object. (It is possible to determine the producer for this image by calling Image 's getSource method.) The flags argument is a set of ImageObserver flags that have been ORed together. These flags specify information about im that is now available. The meaning of the x, y, width, and height arguments depends on flags. After a frame has loaded, these arguments identify the upper-left corner location and dimensions of this frame. The imageUpdate method returns a Boolean true value to the producer if it needs more information. After the last frame has finished loading, false is returned.

You're probably wondering about the rationale for this model. Why not load images directly without using producers, consumers, and image observers? There is one good reason for this model's existence: applets.

A Web browser loads an applet's class and resource files (including image files) from a remote computer over a network. This can take time. In fact, back in 1995 when Java was first unveiled, slow modems were the order of the day. Loading even small images took time. If an applet was forced to wait until its images were completely loaded, users would get fed up and not want anything to do with the applet. However, because of the producer, consumer, and image observer model, an applet can get work done while loading images. In other words, it can be more responsive . (This idea carries over to JFC applications.)

Figure 13.18 illustrates the relationship between producers, consumers, and image observers.

Figure 13.18. Under the producer, consumer, and image observer model, consumers register their interest in receiving pixels and notifications from producers, and producers notify image observers to inform programs when significant image information has been sent to consumers.

graphics/13fig18.gif

An image's pixels are nothing more than colors. How these colors are represented depends on the image's color model. A color model is a mechanism that associates an image's pixel colors with their storage representation. There are two commonly used color models: direct and index.

The direct color model uses color numbers to directly store pixel color information. A color number is an integer that encodes a color's alpha, red, green, and blue components in a 0xAARRGGBB format. This requires 32 bits of storage per color number (8 bits per component). For example, a 500 500 pixel image consisting of 32-bit color numbers would require 1,000,000 bytes of storage. That's a lot of storage!

Years ago, memory was expensive and it was not practical to directly store color numbers in images consisting of many pixels. Therefore, an alternative to the direct color model was commonly used: the index color model.

The index color model uses color indexes to indirectly store pixel color information. A color index is an integer that refers to an entry in a lookup table. Each entry contains a color number. A program can obtain a color number by indexing this table via the color index. Instead of requiring 32 bits of storage per pixel, fewer bits can be used. For example, a 500 500 pixel image consisting of 8-bit color indexes would require 250,000 bytes of storage. This is a 75% storage savings over the direct color model. However, an 8-bit color index can only reference a maximum of 256 entries in the lookup table. As a result, it's impossible to simultaneously display more than 256 colors. There is always a tradeoff !

Figure 13.19 illustrates the direct and index color models.

Figure 13.19. The AWT supports the direct and index color models.

graphics/13fig19.gif

The AWT supplies an abstract ColorModel class (located in the java.awt.image package) that serves as the parent for its DirectColorModel and IndexColorModel subclasses. A producer can create objects from either subclass to represent the direct and index color models. Table 13.9 describes some of ColorModel 's methods.

Table 13.9. Inside the AWT's ColorModel Class
Method Description
ColorModel (int bits) Initializes a subclass object to the number of required bits per pixel. The maximum value for bits is 32 because bits is an int. (Normally, 32 bits would be used to represent the direct color model and anything less would be used to represent the index color model.)
getAlpha (int pixel) Returns an int containing the alpha component of pixel. Its range is between 0 and 255, where 0 means completely transparent and 255 means completely opaque .
getBlue (int pixel) Returns an int containing the blue component of pixel. Its range is between 0 and 255, where 0 means no blue and 255 means blue at its maximum intensity.
getGreen (int pixel) Returns an int containing the green component of pixel. Its range is between 0 and 255, where 0 means no green and 255 means green at its maximum intensity.
getPixelSize () Returns an int containing the number of required bits per pixel, as passed to the constructor.
getRed (int pixel) Returns an int containing the red component of pixel. Its range is between 0 and 255, where 0 means no red and 255 means red at its maximum intensity.
getRGB (int pixel) Returns an int containing the color number of pixel. If a subclass changes the ordering or size of the different color components (from the 0xAARRGGBB format), this method will still return pixel as a color number.
getRGBdefault () Returns either a direct color model object or an index color model object that directly or indirectly uses color numbers to represent pixel colors.

Loading and Drawing Images

Images are normally loaded by calling getImage methods. After it's loaded, an image can be drawn by using any one of several drawImage methods. Some of these drawImage methods can manipulate an image prior to drawing by performing scaling, flipping, and cropping operations.

Exploring the Loading and Drawing Process

Applets and applications load images in a similar manner. However, applets usually load images from remote computers on a network, whereas applications typically load images from a computer's file system. Regardless, they do share one thing in common: getImage.

The Applet class provides two overloaded getImage methods. These methods take one or two arguments that identify the location and name of an image file. When called, they create a producer and an object from a subclass of the abstract Image class. A reference to the producer is stored in this " Image " object. Each getImage method returns immediately with a reference to the " Image " object. The producer is not yet started.

Note

After getImage has been called, the applet can call one of Graphics ' drawImage methods to start the production and drawing process. An internal consumer is also created. Each drawImage method returns immediately and the producer begins to produce the image. Pixels are sent from the producer to the consumer.


To see an example of an applet that obtains and displays an image, check out the source code to ImageApplet in Listing 13.14.

Listing 13.14 The ImageApplet Applet Source Code
 // ImageApplet.java import java.awt.*; import java.applet.Applet; public class ImageApplet extends Applet {    Image im;    public void init ()    {       im = getImage (getDocumentBase (), "sojourner.jpg");    }    public void paint (Graphics g)    {       g.drawImage (im, 0, 0, this);    } } 

Tip

The image file sojourner.jpg (as with all other image files in this and other chapters) is included with this book's source code.

When an animated GIF is displayed, the AWT takes care of the animation on your behalf . For example, in ImageApplet, you could replace sojourner.jpg with the name of an animated GIF file and watch the resulting animation.


ImageApplet calls getImage in its init method. This method creates a producer. It records the location (as specified by getDocumentBase 's return value) and name of the image file ( sojourner.jpg ) in this producer. An Image subclass object is created and a reference to the producer is stored in this object.

The Image subclass object is returned from getImage and its reference is assigned to im. When the applet needs to update its drawing surface, the drawing surface's paint method is called. In turn, this method calls drawImage to start the producer and create a consumer. im is passed as an argument to drawImage so that drawImage can obtain a reference to the producer. Java's this keyword is passed as an argument to drawImage so that the newly created producer " knows " which image observer to contact. An internal consumer is also created and registered with the producer. (Of course, this activity takes place during the first call to drawImage. )

The producer starts loading the image and periodically calls the image observer's imageUpdate method when information has been obtained. The image observer "asks" the AWT to call paint when either part of the image or the entire image has been loaded, and paint calls drawImage. On this and subsequent calls, drawImage obtains the consumer's pixels and renders them onto the drawing surface. The result is shown in Figure 13.20.

Figure 13.20. ImageApplet shows an image of the Sojourner rover on Mars.

graphics/13fig20.gif

The application approach to obtaining and drawing images is similar to the applet approach. However, instead of using Applet 's getImage methods, an application calls one of Toolkit 's getImage methods. ( Toolkit will be examined in the next chapter.) These methods works in a similar manner to Applet 's getImage methods.

For an example of an application that loads and draws an image, check out the source code to ImageApp in Listing 13.15.

Listing 13.15 The ImageApp Application Source Code
 // ImageApp.java import java.awt.*; import java.awt.event.*; class ImageApp extends Frame {    ImageApp (String title)    {       super (title);       addWindowListener (new WindowAdapter ()                          {                              public void windowClosing (WindowEvent e)                              {                                 System.exit (0);                              }                          });       add (new Picture ("sojourner.jpg"));       pack ();       setResizable (false);       setVisible (true);    }    public static void main (String [] args)    {       new ImageApp ("Image Application");    } } class Picture extends Canvas {    private Image image;    Picture (String imageFileName)    {       Toolkit tk = Toolkit.getDefaultToolkit ();       image = tk.getImage (imageFileName);    }    public Dimension getPreferredSize ()    {       return new Dimension (256, 248);    }    public void paint (Graphics g)    {       g.drawImage (image, 0, 0, this);    } } 

Don't worry about not understanding the GUI aspects of this code. After you've had a chance to explore AWT GUI concepts in the next chapter, come back and review ImageApp. You'll find that the GUI code makes more sense.

Note

You might be wondering why this chapter has focused solely on applets (until now) to present AWT features. The reason is that applets require no special GUI setup before they can be used. In contrast, JFC applications require this setup ”as evidenced by ImageApp. Rather than get sidetracked into discussing their GUI setup, JFC applications have been avoided. This will change in the next chapter, where the GUI side of the AWT is discussed.


If you examine the Graphics class, you'll discover six overloaded drawImage methods. Each method handles drawing- related tasks in different ways. For example, some versions render transparent pixels in a specified color (as opposed to leaving the original pixel colors unchanged), whereas other versions provide scaling, cropping, and flipping features. Regardless of differences, each method's approach to working with producers/consumers is the same, and each method returns a Boolean true value after the image has been completely loaded.

Scaling Images

Several drawImage methods can be called to perform scaling prior to drawing an image. To demonstrate how this works, Listing 13.16 presents source code to an ImageScale applet. This applet draws an original image along with a scaled down version of the image.

Listing 13.16 The ImageScale Applet Source Code
 // ImageScale.java import java.awt.*; import java.applet.Applet; import java.awt.image.ImageObserver; public class ImageScale extends Applet {    Image im;    public void init ()    {       im = getImage (getDocumentBase (), "twain.jpg");    }   public void paint (Graphics g)    {       if (g.drawImage (im, 0, 0, this))       {           int width = im.getWidth (this);           int height = im.getHeight (this);           g.drawImage (im, width, 0, width + width / 2, height / 2,                        0, 0, width, height, this);       }    } } 

ImageScale takes advantage of drawImage returning a Boolean true value after the original image is completely loaded. After it's loaded, the width and height of this image are obtained by calling Image 's getWidth and getHeight methods. These methods take an ImageObserver argument ”an object that implements the ImageObserver interface ”and return -1 until the producer has produced the width/height information. Because they are not called until drawImage returns true, getWidth and getHeight are guaranteed to return the image's width and height. A second version of drawImage (with 10 arguments) is called to load and draw the scaled image.

Scaling is achieved by dividing the target image's lower-right corner coordinates by a specified value. ImageScale divides these coordinates by 2. The result is shown in Figure 13.21.

Figure 13.21. ImageScale shows original and scaled down images of Mark Twain (an early American author and humorist).

graphics/13fig21.gif

Tip

The Image class provides the getScaledInstance method for generating a prescaled version of an image. Instead of calling a drawImage method to scale and then draw, you can call getScaledInstance to prescale and then a drawImage method to only draw. This is useful in situations in which calling drawImage to scale and then draw results in a degraded appearance (because scaling takes time).


Cropping and Flipping Images

Several drawImage methods can be called to achieve cropping and flipping prior to drawing an image. Listing 13.17 presents source code to an ImageCropFlip applet that demonstrates drawing an original image along with a cropped and flipped version of the image.

Listing 13.17 The ImageCropFlip Applet Source Code
 // ImageCropFlip.java import java.awt.*; import java.applet.Applet; public class ImageCropFlip extends Applet {    Image im;    public void init ()    {       im = getImage (getDocumentBase (), "lions.jpg");    }    public void paint (Graphics g)    {       if (g.drawImage (im, 0, 0, this))       {           int width = im.getWidth (this);           int height = im.getHeight (this);          g.drawImage (im, width, height + 10, 0, 2 * height  40,                        0, 0, width, height - 50, this);       }    } } 

Cropping is achieved by modifying the original image coordinates. ImageCropFlip crops vertically by subtracting 50 from the height of the original image. Flipping is achieved by swapping the target image coordinates. ImageCropFlip flips the image horizontally by swapping the x-coordinates of the target image's upper-left and lower-right corners. Instead of having an upper-left corner at ( 0, height + 10 ) and a lower-right corner at ( width, 2 * height “ 40 ), the target image is given an upper-left corner at ( width, height + 10 ) and a lower-right corner at ( 0, 2 * height “ 40 ). The result is shown in Figure 13.22.

Figure 13.22. ImageCropFlip shows original and cropped/flipped images of lions.

graphics/13fig22.gif

Animation

Animation has come a long way since Walt Disney's original black and white Mickey Mouse cartoons debuted in the early part of the last century. The central idea behind animation is to draw a frame of pixels, display this frame, pause for a certain length of time, draw the next frame, and so on. (You can change this order to pause, draw a frame, display this frame, and so on ”as long as there is a pause between the display of two frames.)

The AWT makes it easy to achieve animation. To see an example, check out the TypeWriter applet source code in Listing 13.18.

Listing 13.18 The TypeWriter Applet Source Code
 // TypeWriter.java import java.awt.*; import java.applet.Applet; public class TypeWriter extends Applet implements Runnable {    Thread t;    String text = "Java makes typing fun!";    int index;    final int len = text.length ();    public void start ()    {       if (t == null)       {           t = new Thread (this);           t.start ();       }    }    public void stop ()    {      t = null;    }    public void run ()    {       Thread current = Thread.currentThread ();       while (t == current)       {          try          {             Thread.sleep (200);          }          catch (InterruptedException e) { }          repaint ();          index++;          if (index >= len)              index = 0;       }    }    public void paint (Graphics g)    {       g.setColor (Color.blue);       String s = text.substring (0, index + 1);       g.drawString (s, 10, 30);    } } 

TypeWriter mimics the effect of a typewriter typing a message, one character at a time. It follows a standard pattern that Java applets use to achieve animation. With a few modifications, this pattern also can be used by JFC applications.

A Thread variable is required to reference a Thread object that controls the animation. This object is created and the animation thread is started in the applet's start method. The Thread object is marked for garbage collection in the applet's stop method when null is assigned to the Thread variable.

The Thread constructor used to initialize the Thread object requires a single argument. This argument must be an object that implements the Runnable interface. Because this is passed as the argument, the object implementing Runnable is the current applet object. To implement Runnable, the applet must provide a run method. It is in this method that the animation loop is placed.

The animation loop runs for as long as the Web page containing the applet is the currently displayed Web page. As soon as the user switches to another Web page, it is important to stop this loop. Otherwise , the animation thread keeps running. To know when to stop, a reference to the current Thread object must be obtained. Then, for each iteration of the animation loop, the current thread reference is compared to the original Thread object reference. If they differ , the animation loop "knows" that the applet's stop method was called, and the run method terminates.

Each iteration of the animation loop does three things: First, it pauses for 200 milliseconds by calling Thread.sleep. Then, it calls the AWT's repaint method. This method asks the AWT to draw the next frame by calling paint. (More will be said about repaint in the next chapter.) Finally, an index variable is updated for determining what gets drawn in the next frame. This index can be thought of as the frame number.

Animating Graphics and Images

Earlier, you were cautioned against using large for loops in a paint method. This restriction can be a problem if you need to draw quite a bit of graphical content (such as a star field consisting of several hundred stars). Fortunately, there is a solution ”the animation process. All you need to do is restructure the original for loop idea into the animation idea of frames. Check out the StarField1 applet source code in Listing 13.19 to see how this is accomplished.

Listing 13.19 The StarField1 Applet Source Code
 // StarField1.java import java.awt.*; import java.applet.Applet; public class StarField1 extends Applet implements Runnable {    Thread t;    int counter;    Color starColors [] = {  Color.white, Color.darkGray, Color.lightGray,                             Color.blue, Color.yellow } ;    public void start ()    {       if (t == null)       {           t = new Thread (this);           counter = 0;           t.start ();       }    }    public void stop ()    {       t = null;    }    public void run ()    {       Thread current = Thread.currentThread ();       while (t == current)       {          // Draw frame          repaint (); // This method causes the update method to be called.          // Pause          try          {             Thread.sleep (50);          }          catch (InterruptedException e) { }          // Manipulate counter          if (++counter > 500)              counter = 0;       }    }    public void paint (Graphics g)    {       // The following getClipBounds method call returns       // a rectangle that contains the dimensions of the       // clip (a rectangular portion of the drawing       // surface that must be redrawn, because of       // damage).  Damage represents a "hole" in the       // drawing surface.  It occurs when a window       // partially (or completely) obscures the drawing       // surface and then is removed.  Because the       // surface's obscured pixels were replaced by the       // overlapping window, they must be redrawn.       // Rather than redraw the entire drawing surface,       // only the clip should be redrawn  which also       // improves drawing performance.       Rectangle r = g.getClipBounds ();       // Redraw the clip area by filling all pixels with       // the color black.  Any stars in this area will be       // wiped out.       g.setColor (Color.black);       g.fillRect (r.x, r.y, r.width, r.height);    }    // Return a random integer from 0 through limit  1.    int rnd (int limit)    {       return (int) (Math.random () * limit);    }    public void update (Graphics g)    {       // When there are enough stars, redraw the background       // and erase visible stars.       if (counter == 0)           paint (g);       // Set the next star's color and draw this star.       g.setColor (starColors [rnd (starColors.length)]);       int x = rnd (getSize ().width);       int y = rnd (getSize ().height);       g.drawLine (x, y, x, y);   } } 

StarField1 is an animation program that displays a constantly changing star field on a black background. To achieve animation, StarField1 uses the same multithreading technique as TypeWriter. However, StarField1 also makes use of a clipping rectangle and distributes the painting task between its paint and update methods. To understand why this is done, it's important to learn about the AWT's painting process.

Painting is initiated in one of two ways: system-controlled and program-controlled. System-controlled painting occurs when the AWT directly calls the paint method. This usually happens when a window that either completely or partially obscures the program's drawing surface is removed, or the program's drawing surface is resized. When either scenario happens, the colors of those pixels occupying the previously obscured portion of the drawing surface (or the entire drawing surface ”if resized) will be changed to a default background color (such as white). (These pixels are said to be damaged.) This situation is not desirable for StarField1, whose background must be black. As a result, this program's paint method takes care of setting background pixels to black. Before this is done, paint calls the getClipBounds method to return a clipping rectangle. The clipping rectangle's coordinates and dimensions identify the region of damage (either those drawing surface pixels that were obscured or all pixels ”if the drawing surface was resized). StarField1 's paint method uses the clipping rectangle in conjunction with the fillRect method to ensure that only damaged pixels are colored black.

Tip

Although it's possible to set all drawing surface pixels to an appropriate background color (such as black), this is rather wasteful when only a few pixels have been obscured. However, if your program's paint method is designed to modify only pixels that lie in the clipping rectangle, it will achieve a better drawing performance, than if it always modifies all drawing surface pixels.


The problem with the approach that StarField1 uses to repair damage to its background is that any stars displayed in the damaged region are erased. The technique of double buffering (discussed later in this chapter) solves this problem.

Program-controlled painting results from the program calling the repaint method. In response, the AWT calls the program's update method. Because StarField1 was designed to erase all stars after 500 stars have been displayed (otherwise the star field looks cluttered), it only calls paint to clear the star field whenever the counter variable is zero. (This variable is reset to zero when it reaches 500.) The only other task performed by update is to generate a new color and position for a star, and to draw this star.

Note

Like paint, update is called by the AWT and is used to update a drawing surface. However, the inherited version of update clears the drawing surface's background to a default color (such as white) before calling paint. This method is overridden to prevent this clearing to a default color, and to control when the background is cleared to black. As with repaint, update will be more fully discussed in the next chapter.


Figure 13.23. StarField1 shows one frame of an animated starfield.

graphics/13fig23.gif

Now that you've seen animated graphics, you're probably eager to see how images are animated. As you'll discover, there isn't a lot of difference between animating graphics and images.

Listing 13.20 presents source code to the Animator1 applet. This applet takes advantage of HTML parameters to animate images.

Listing 13.20 The Animator1 Applet Source Code
 // Animator1.java import java.awt.*; import java.net.URL; import java.applet.Applet; public class Animator1 extends Applet implements Runnable {    Thread t;    int frameNo;    Image imArray [];    public void init ()    {       String prefix = getParameter ("imageprefix");       if (prefix == null)       {           System.out.println ("image prefix parameter missing");           return;       }       String ext = getParameter ("imageext");       if (ext == null)       {           System.out.println ("image extension parameter missing");           return;       }       String nimages = getParameter ("numimages");       if (nimages == null)       {           System.out.println ("number of images parameter missing");           return;       }       int numImages = Integer.parseInt (nimages);       if (numImages < 0)       {           System.out.println ("number of images < 0");           return;       }       imArray = new Image [numImages];       URL u = getDocumentBase ();       for (int i = 0; i < numImages; i++)       {            imArray [i] = getImage (u, prefix + i + ext);            showStatus ("Loading frame " + i);       }    }    public void start ()    {       if (t == null)       {           t = new Thread (this);           frameNo = 0;           t.start ();       }    }    public void stop ()    {      t = null;    }    public void run ()    {       Thread current = Thread.currentThread ();       while (t == current)       {          repaint ();         try          {             Thread.sleep (50);          }          catch (InterruptedException e) { }         frameNo++;          if (frameNo >= imArray.length)              frameNo = 0;       }    }    public void paint (Graphics g)    {       g.drawImage (imArray [frameNo], 0, 0, this);       showStatus ("Showing frame " + frameNo);    } } 

Much of Animator1 's code is pretty straightforward. You've already seen most of these techniques in prior applets. One item that will be new to you is the showStatus method. This method, inherited from Applet, displays a string of text in a browser's (or the Applet Viewer's) status bar. It is useful for providing updates of what is happening.

As you scan through the init method, you'll discover several calls to the getParameter method. This method allows Animator1 to be more adaptable. Three parameters are used by Animator1: imageprefix provides the name of an image file, imageext provides the file's extension (such as .gif or .jpg), and numimages provides a count of image files. To create an image filename, Animator1 concatenates the extension to a number ranging from zero through one less than the numimages value, which in turn is concatenated to the prefix. For example, assuming a prefix of sts, an extension of .jpg, and a numimages value of 506, Animator1 will generate filenames ranging from sts0.jpg through sts505.jpg. Listing 13.21 provides an example of what the HTML code looks like to run this applet.

Listing 13.21 The Animator1 Applet HTML Code
 <applet code="Animator1.class" width=175 height=135> <param name="imageprefix" value="sts"> <param name="imageext" value=".jpg"> <param name="numimages" value="506"> </applet> 

The Animator1 HTML is configured to animate 506 image frames of a Space Shuttle landing. (The space shuttle is also known as the Space Transport System or STS.) When run, this applet looks like a tiny TV set. Figure 13.24 shows Animator1 displaying one of these frames.

Figure 13.24. Animator1 shows one frame of a Space Shuttle landing ani mation.

graphics/13fig24.gif

Tracking Media

When you run Animator1, you'll notice a lot of flicker. Part of the flicker has to do with the absence of an update method. Without this method, the background is cleared between each frame. This clearing causes momentary jitters that are picked up by the human eye. The rest of this flicker has to do with the animation starting before all frames are loaded.

The AWT provides the MediaTracker class (located in the java.awt package) to track media loading. Contrary to its name, MediaTracker only tracks image loading ”audio loading is not tracked.

To use MediaTracker, you create a MediaTracker object, call one of its methods to add each image file (whose loading you want to track) to MediaTracker 's internal data structure, and call a method to wait until MediaTracker has loaded all files. To see what this looks like, check out the Animator2 source code in Listing 13.22.

Listing 13.22 The Animator2 Applet Source Code
 // Animator2.java import java.awt.*; import java.net.URL; import java.applet.Applet; public class Animator2 extends Applet implements Runnable {    Thread t;    int frameNo;    Image imArray [];    public void init ()    {       String prefix = getParameter ("imageprefix");       if (prefix == null)       {           System.out.println ("image prefix parameter missing");           return;       }       String ext = getParameter ("imageext");       if (ext == null)       {           System.out.println ("image extension parameter missing");           return;       }       String nimages = getParameter ("numimages");       if (nimages == null)       {           System.out.println ("number of images parameter missing");           return;       }       int numImages = Integer.parseInt (nimages);       if (numImages < 0)       {           System.out.println ("number of images < 0");           return;       }       imArray = new Image [numImages];       URL u = getDocumentBase ();  MediaTracker mt = new MediaTracker (this);       for (int i = 0; i < numImages; i++)       {            imArray [i] = getImage (u, prefix + i + ext);            mt.addImage (imArray [i], 0);            showStatus ("Loading frame " + i);       }       showStatus ("Waiting for load completion");       try       {          mt.waitForID (0);       }       catch (InterruptedException e) { }  }    public void start ()    {       if (t == null)       {           t = new Thread (this);           frameNo = 0;           t.start ();       }    }    public void stop ()    {      t = null;    }    public void run ()    {       Thread current = Thread.currentThread ();       while (t == current)       {          repaint ();          try          {             Thread.sleep (50);          }          catch (InterruptedException e) { }          frameNo++;          if (frameNo >= imArray.length)              frameNo = 0;       }    }    public void paint (Graphics g)    {       g.drawImage (imArray [frameNo], 0, 0, this);       showStatus ("Showing frame " + frameNo);    }  public void update (Graphics g)    {       paint (g);    }  } 

A MediaTracker object is created in the init method. Its addImage method is called to add an image to its data structure. Upon examination, you'll notice that zero is passed as an argument to this method. This number identifies an image and is used for grouping purposes. (Different numbers can be assigned to different groups of images to facilitate management.) Finally, MediaTracker 's waitForID method is called. Zero is passed as an argument to this method. As a result, waitForID forces the applet thread to wait until all image files associated with group code zero have loaded.

If you don't want to use MediaTracker, there is an alternative. This alternative involves two methods inherited from Component ” prepareImage and checkImage ” and is somewhat faster than MediaTracker. ( MediaTracker internally uses these methods but is slowed down by some internal bookkeeping.)

The prepareImage method is called to start the producer associated with its Image argument, whereas checkImage checks to see if the image has loaded and returns a combination of status flags. These flags are represented by constants in the ImageObserver interface. For example, the ALLBITS constant means the image has completely loaded.

The following code fragment shows how you might modify the init method in Animator1 to use this alternative. (This code fragment was taken from the Animator3 applet, included with this book's source code.)

 for (int i = 0; i < numImages; i++) {      imArray [i] = getImage (u, prefix + i + ext);      prepareImage (imArray [i], this);      showStatus ("Loading frame " + i); } showStatus ("Waiting for load completion"); for (int i = 0; i < numImages; i++)      while ((checkImage (imArray [i], this) &             ImageObserver.ALLBITS) != ImageObserver.ALLBITS)      {         // Occasionally, yield control to other threads.         // This makes the system more responsive.         Thread.yield ();      } } 
Double Buffering

Some animations are complicated. They require multiple overlapping images to be drawn via drawImage. Furthermore, other Graphics methods might need to be called to add effects. Despite overriding update and using MediaTracker, flicker can still occur because of the time required to render all of this information, and the human eye observes part of this process. A solution to this problem was devised some years ago, and is known as double buffering.

Double buffering is the technique of creating a buffer, rendering all graphics and images to the buffer, and drawing the buffer on the drawing surface. It takes advantage of the createImage and getGraphics methods.

The Component class provides a pair of createImage methods that are inherited by every drawing surface. One of these methods takes a pair of int arguments that represents the size of the buffer (in pixels), and returns a reference to an Image subclass object. For example, the following code fragment creates a buffer of 100 horizontal by 200 vertical pixels:

 Image buffer; //  buffer = createImage (100, 200); 

The Image class provides a method called getGraphics. This method returns a graphics context associated with Image ” as long as Image represents a buffer. Otherwise, null is returned. The following code fragment shows how to extract this context from the buffer in the previous code fragment:

 Graphics bkContext = buffer.getGraphics (); 

You perform all your drawing operations using this graphics context. The following code fragment illustrates this concept by building on the previous code fragments :

 bkContext.setColor (Color.red); bkContext.drawLine (10, 10, 30, 30); 

Finally, in the paint method, you dump the contents of the buffer to the drawing surface. Bringing together all the previous code fragments results in the following logic:

 Image buffer; Graphics bkContext; public void init () {    buffer = createImage (100, 200);    bkContext = buffer.getGraphics (); } public void run () { //  setup code    bkContext.setColor (Color.red);    bkContext.drawLine (10, 10, 30, 30);    repaint (); //  additional code } public void paint (Graphics g) {    // You can manipulate the buffer from any method, including paint.    g.drawImage (buffer, 0, 0, this); } 

When you use double buffering, the idea is to prepare a single frame of animation at a time. This frame consists of a buffer's contents ”built using a wide variety of Graphics method calls. You want all this work to occur in the run method and only display the buffer in the paint method.

Note

A StarField2 applet is included with this book's source code. This program uses double-buffering to ensure that no stars are erased when the user obscures or resizes the applet's drawing surface. (If you recall, this was a problem with StarField1. )


Producing Images

A producer is responsible for passing image data to a consumer. To be a producer, an object's class must implement the ImageProducer interface. (It's good to understand this interface in the event that you want to create your own producer.) One of the color models that the producer might use to represent pixel colors is the direct color model. The producer can work with the direct color model by using the DirectColorModel class. Instead of creating your own producers, you can take advantage of the AWT's MemoryImageSource class.

The ImageProducer Interface

Any object implementing the ImageProducer interface (located in the java.awt.image package) is known as a producer. A consumer can call this interface's methods to register itself with a producer and request image data. These methods include

  • addConsumer

  • isConsumer

  • removeConsumer

  • requestTopDownLeftRight

  • startProduction

The addConsumer method registers a consumer with the producer. It has the following signature:

 void addConsumer (ImageConsumer ic) 

The consumer is represented by ic. It will be notified when image data becomes available.

The isConsumer method determines if a given consumer is currently registered with the producer. It has the following signature:

 boolean isConsumer (ImageConsumer ic) 

The consumer is represented by ic. A Boolean true value is returned if ic is registered with the producer.

The removeConsumer method de-registers a consumer from the producer. It has the following signature:

 void removeConsumer (ImageConsumer ic) 

The consumer is represented by ic. After it is de-registered, the consumer is no longer eligible to receive image data.

The requestTopDownLeftRight method requests the producer to resend image data. It has the following signature:

 void requestTopDownLeftRight (ImageConsumer ic) 

The consumer, represented by ic, requests the producer to resend image data in top/down/left/right order so that higher quality conversion algorithms (dependent on this order) can create a better rendered version of the image.

The startProduction method registers a consumer with the producer and requests that image data immediately be sent to the consumer. It has the following signature:

 void startProduction (ImageConsumer ic) 

The consumer is represented by ic.

The DirectColorModel Class

The DirectColorModel class (located in the java.awt package) is used to create objects representing direct color models. Each pixel is represented by a 32-bit color number.

DirectColorModel inherits methods from ColorModel, and provides a few methods of its own. These methods include

  • constructors

  • getAlphaMask

  • getBlueMask

  • getGreenMask

  • getRedMask

The first of DirectColorModel 's constructors has the following signature:

 DirectColorModel (int bits, int redMask, int greenMask, int blueMask,                   int alphaMask) 

This constructor initializes a DirectColorModel object to a model that uses bits to represent the total number of bits per pixel. This value is less than or equal to 32. The redMask, greenMask, blueMask, and alphaMask arguments state where in a pixel's bits each color component exists. Each of the bit masks must be contiguous (for example, green cannot occupy only the first and eighth bits) and should not exceed 8 bits in size. When joined together, the numbers of mask bits should sum to the value specified by bits.

The color number-based direct color model can be created by using the following code fragment:

 DirectColorModel dcm = new DirectColorModel (32, 0x00ff0000, 0x0000ff00,                                              0x000000ff, 0xff000000); 

An IllegalArgumentException object is thrown if the bits in a mask are not contiguous, the number of mask bits exceeds bits, or mask bits overlap (that is, the same bit appears in two or more masks).

The second DirectColorModel constructor has the following signature:

 DirectColorModel (int bits, int redMask, int greenMask, int blueMask) 

This constructor calls the previous constructor. It passes an alpha mask of 0x00000000, which means that this color model will not have a transparency component. All colors will be fully opaque with an alpha value of 255.

The getAlphaMask, getBlueMask, getGreenMask, and getRedMask methods return the alpha, blue, green, and red component masks, respectively. In all cases, the returned values ranges from 0 through 255. These methods have the following signatures:

 int getAlphaMask () int getBlueMask () int getGreenMask () int getRedMask () 
The MemoryImageSource Class

The MemoryImageSource class (located in the java.awt.image package) is used to create producers that produce images from arrays of pixels. Basically, you create an array of pixel data and pass this array along with a color model to a MemoryImageSource constructor. The resulting object becomes a producer for this image. It passes the pixel and color model information to a consumer. In addition to the ImageProducer methods, MemoryImageSource provides six constructors and several of its own methods.

MemoryImageSource 's constructors include

 MemoryImageSource (int w, int h, ColorModel cm, byte [] pixels,                    int off, int scan) MemoryImageSource (int w, int h, ColorModel cm, byte [] pixels,                    int off, int scan, Hashtable prop) MemoryImageSource (int w, int h, ColorModel cm, int [] pixels,                    int off, int scan) MemoryImageSource (int w, int h, ColorModel cm, int [] pixels,                    int off, int scan, Hashtable prop) MemoryImageSource (int w, int h, int [] pixels, int off, int scan) MemoryImageSource (int w, int h, int [] pixels, int off, int scan,                    Hashtable properties) 

The w and h arguments specify the width and height of the image (in pixels). The cm argument specifies the color model. If not present, ColorModel 's getRGBDefault method is called to supply the default color number-based direct color model. The pixels argument specifies an array of pixels. This is either a byte array (for use with the index color model) or an int array (for use with the direct color model). The off argument specifies the offset of the first pixel in the array. This is usually zero. The scan argument specifies the number of pixels per row and is usually equal to w. The prop argument specifies a Hashtable of properties (such as copyright information) to be associated with the image. If this argument isn't present, no properties are assumed.

Listing 13.23 presents source code to a ColorBlend1 applet that demonstrates using MemoryImageSource.

Listing 13.23 The ColorBlend1 Applet Source Code
 // ColorBlend1.java import java.awt.*; import java.awt.image.*; import java.applet.Applet; public class ColorBlend1 extends Applet {    Image im;    public void init ()    {       int width = getSize ().width;       int height = getSize ().height;       int [] pixels = new int [width * height];       int index = 0;       for (int y = 0; y < height; y++)       {            int numerator = y * 255;            int b = numerator / height;            int r = 255 - numerator / height;            for (int x = 0; x < width; x++)            {                 int g = x * 255 / width;                 pixels [index++] = (255 << 24)  (r << 16)  (g << 8)                                     b;            }       }      im = createImage (new MemoryImageSource (width, height, pixels,                                                0, width));    }    public void paint (Graphics g)    {       g.drawImage (im, 0, 0, this);    } } 

ColorBlend1 creates an image from a blend of the primary colors. MemoryImageSource is used to produce this image and the default color number-based direct color model is the color model.

The image is created as follows: An array of pixels is allocated. The size of this array is equal to the width and height of the applet. For each pixel, the intensity of its blue component is chosen to be weak toward the top of the image and increase toward the bottom. In contrast, the intensity of red is chosen to be strong toward the top and decrease toward the bottom. Toward the left side, the intensity of green is chosen to be weak and increase toward the right. All pixels are completely opaque. This results in a pleasant blend of colors. Figure 13.25 shows ColorBlend1 's output. (The colors are not shown in the figure. To see these colors, please run ColorBlend1. )

Figure 13.25. ColorBlend1 shows a color-blended image.

graphics/13fig25.gif

Because MemoryImageSource supports animation, it is possible to pass multiple frames to consumers using this producer. This feature mimics animated GIFs. To accomplish this task, MemoryImageSource provides the setAnimated, setFullBufferUpdates, and several overloaded newPixels methods. The idea is to call setAnimated and setFullBufferUpdates immediately after creating a MemoryImageSource object. Then, when it comes time to do the actual animation, you keep calling one or more of the newPixels methods to send partial or complete frames to the consumer. (For more information, check out the SDK documentation.)

Note

For an example of a program that uses this kind of animation, check out the source code to the ColorBlend2 applet. (This source code is included with the rest of this book's source code.)


Troubleshooting Tip

If you are having trouble trying to extract an image's properties (assuming that it has properties), see "Extracting Property Values," in the "Troubleshooting" section at the end of this chapter.


Consuming Images

Consumers complement producers. A consumer receives image data from a producer and is responsible for storing or manipulating this data. To be a consumer, an object's class must implement the ImageConsumer interface. (If you ever want to create your own consumer, you'll need to understand this interface.) One of the color models that the consumer might have to work with is the index color model. This color model is the result of the producer creating an object from the IndexColorModel class. Instead of creating your own consumers, you can take advantage of the AWT's PixelGrabber class.

The ImageConsumer Interface

Any object implementing the ImageConsumer interface (located in the java.awt.image package) is known as a consumer. A producer can call this interface's methods to send notifications and data to a consumer. These methods include

  • imageComplete

  • setColorModel

  • setDimensions

  • setHints

  • setPixels

  • setProperties

The imageComplete method is called when a complete image has been transferred to the consumer. It has the following signature:

 void imageComplete (int status) 

This method provides the consumer with status information, via the status argument. This argument tells the consumer why the producer has finished producing an image, and is represented by one of the following ImageConsumer constants:

  • IMAGEABORTED ” The producer had to abort production. A retry might succeed.

  • IMAGEERROR ” The producer had to abort production because of a serious error (such as invalid pixel data). A retry will not succeed.

  • SINGLEFRAMEDONE ” A frame apart from the last frame has completed loading. There are additional frames to produce.

  • STATICIMAGEDONE ” The final frame has completed loading. There are no more frames to produce.

The setColorModel method informs the consumer about the color model being used for the majority of the image's pixels. It has the following signature:

 void setColorModel (ColorModel model) 

The model argument can be used to optimize the image. However, because each call to setPixels is given its own ColorModel argument, model is only advisory.

The setDimensions method passes the actual dimensions of an image to the consumer. It has the following signature:

 void setDimensions (int width, int height) 

The width and height arguments contain the actual dimensions. The consumer will perform scaling and resizing as appropriate.

The setHints method provides hints for optimally rendering an image. It has the following signature:

 void setHints (int hints) 

This method is called prior to setPixels. The hints argument is formed by ORing together the following ImageConsumer constants:

  • COMPLETESCANLINES Each call to setPixels will deliver at least one complete scan line of pixels.

  • RANDOMPIXELORDER The pixels are not provided in any particular order. As a result, optimization that depends on pixel delivery order cannot be performed by the consumer.

  • SINGLEFRAME The image consists of a single frame. An example of an image that does not consist of a single frame is a GIF89a image.

  • SINGLEPASS Expect each pixel to be delivered once and only once. (Certain image formats, such as JPEG, deliver an image in several passes, with each pass yielding a much clearer image.)

  • TOPDOWNLEFTRIGHT Expect the pixels to arrive in a top/down/left/right order.

The first of two overloaded setPixels methods has the following signature:

 void setPixels (int x, int y, int width, int height, ColorModel cm,                 byte [] pixels, int off, int scan) 

This method delivers pixels to the consumer as a rectangle of bytes. The x, y, width, and height arguments specify the upper-left corner and dimensions of this rectangle. These dimensions are relative to the actual size of the image. The color model is passed via cm. The pixels are taken from the array pixels. The location of the first pixel is found at pixels [off]. The length of an array scan line is specified by scan. Usually, scan equals width. To determine the location of pixel ( a, b ), use the following formula: ((b “ y) * scan + (a “ x) + off). (This method is usually called when the index color model is used.)

The second overloaded setPixels method has the following signature:

 void setPixels (int x, int y, int width, int height, ColorModel cm,                 int [] pixels, int off, int scan) 

This method is similar to the other setPixels method except that pixels is specified as an array of int s. This is necessary when there are more than 8 bits of data per pixel. (This method is usually called when the direct color model is used.)

The setProperties method passes image properties to the consumer. It has the following signature:

 void setProperties (Hashtable properties) 

Properties might include copyright, cropping, or comment information. These properties can be retrieved at a later time by calling Image 's getProperty method. (Many images do not have properties.)

The IndexColorModel Class

The IndexColorModel class is used to create objects representing index color models. This model uses a lookup table (also known as a color map) with a maximum size of 256 entries. Each entry holds a color number. A pixel is represented by an index into the lookup table and is no more than 8 bits in size.

IndexColorModel inherits methods from ColorModel, and provides a few methods of its own. These methods include

  • constructors

  • getAlphas

  • getBlues

  • getGreens

  • getMapSize

  • getReds

  • getTransparentPixel

The first of IndexColorModel 's constructors has the following signature:

 IndexColorModel (int bits, int size, byte [] cmap, int start,                  boolean hasAlpha, int transparent) 

The number of bits used to represent each pixel is specified by bits. This number must not exceed 8. The number of elements in the internal color map is specified by size and must not be less than the number 2 raised to the power of bits. If hasAlpha is true, the color map's color numbers include alpha components. If the image will have a transparent pixel, transparent is the index of this pixel in the color map. Otherwise, transparent should be set to -1.

The color map is specified by cmap and start is the index at which this map begins ”prior elements are ignored. Each map entry consists of 3 or 4 consecutive bytes. If hasAlpha is true, this entry consists of 4 bytes and contains a 32-bit color number. Otherwise, this entry consists of 3 bytes and contains a 24-bit color number. (There is no alpha component.)

If bits is greater than 8, size is greater than 2 to the power of bits, or cmap is too small to hold the color map, an ArrayIndexOutOfBoundsException object is thrown.

Note

A transparent pixel is a pixel with an index in the color map that refers to a color number whose alpha component is ignored. Transparent pixels are commonly used with GIF files. A transparent pixel can be used by color models that either ignore transparency levels in other entries (when hasAlpha is false ) or support these levels (when hasAlpha is true ).


The second IndexColorModel constructor has the following signature:

 IndexColorModel (int bits, int size, byte [] cmap, int start, boolean hasAlpha) 

This constructor calls the previous constructor with transparent set to “1.

The third IndexColorModel constructor has the following signature:

 IndexColorModel (int bits, int size, byte [] red, byte [] green, byte [] blue, graphics/ccc.gif int transparent) 

Instead of a single color map array, this constructor uses three arrays to represent the red, green, and blue components. These arrays must each be at least size elements in length. A pixel with index i has a red component at red [i], a green component at green [i], and a blue component at blue [i].

If bits is greater than 8, size is greater than 2 to the power of bits, or the red, green, and blue arrays are too small to hold the color map, an ArrayIndexOutOfBoundsException object is thrown.

The fourth IndexColorModel constructor has the following signature:

 IndexColorModel (int bits, int size, byte [] red, byte [] green, byte [] blue) 

This constructor calls the previous constructor with transparent set to “1.

The final IndexColorModel constructor has the following signature:

 IndexColorModel (int bits, int size, byte [] red, byte [] green, byte [] blue,                  byte [] alpha) 

This constructor is similar to the previous two constructors except an alpha array is used instead of a transparent pixel. This allows you to determine the amount of transparency for each individual color number.

If bits is greater than eight or size is greater than two to the power of bits or the red, green, blue and alpha arrays are too small to hold the color map, an ArrayIndexOutOfBoundsException object is thrown.

The getAlphas method returns an array of all alpha components in the color map. It has the following signature:

 void getAlphas (byte [] alphas) 

Space must be pre-allocated for alphas, so that this array is capable of holding at least size elements.

The getBlues method returns an array of all blue components in the color map. It has the following signature:

 void getBlues (byte [] blues) 

Space must be pre-allocated for blues, so that this array is capable of holding at least size elements.

The getGreens method returns an array of all green components in the color map. It has the following signature:

 void getGreens (byte [] greens) 

Space must be pre-allocated for greens, so that this array is capable of holding at least size elements.

The getMapSize method returns the number of entries in the color map. It has the following signature:

 int getMapSize () 

The getReds method returns an array of all red components in the color map. It has the following signature:

 void getReds (byte [] reds) 

Space must be pre-allocated for reds, so that this array is capable of holding at least size elements.

The getTransparentPixel method returns the index of the color map entry that represents the transparent pixel. It has the following signature:

 int getTransparentPixel () 

If there is no transparent pixel, -1 is returned.

The PixelGrabber Class

The PixelGrabber class (located in the java.awt.image package) is used to create consumers that consume images and place the pixels in arrays. These consumers are the opposite of MemoryImageSource producers and can be used to extract image pixels for storage in files. To recreate an image, load the pixels into an array and pass this array to a MemoryImageSource producer. In addition to the ImageConsumer methods, PixelGrabber provides the following:

  • constructors

  • abortGrabbing

  • getColorModel

  • getHeight

  • getPixels

  • getStatus

  • getWidth

  • grabPixels

  • startGrabbing

The first PixelGrabber constructor has the following signature:

 PixelGrabber (ImageProducer ip, int x, int y, int width, int height,               int [] pixels, int off, int scan) 

The PixelGrabber object uses ip as its producer. This producer stores the image whose upper-left corner is ( x, y ) and dimensions are ( width, height ) in the pixels array beginning at pixels [off]. Each row starts at scan increments . The default color number-based direct color model is used.

The second PixelGrabber constructor has the following signature:

 PixelGrabber (Image im, int x, int y, int width, int height, int [] pixels,               int off, int scan) 

This constructor extracts the producer by calling im.getSource and then calls the previous constructor.

The final PixelGrabber constructor has the following signature:

 PixelGrabber (Image im, int x, int y, int width, int height, boolean forceRGB) 

No array needs to be pre-allocated. This constructor takes care of that task. The forceRGB argument determines whether the original or the default color number-based direct color model is used.

The abortGrabbing method causes pixel grabbing to stop. It has the following signature:

 void abortGrabbing () 

Any thread waiting for pixel data via grabPixels is interrupted and grabPixels throws an InterruptedException object.

The getColorModel method retrieves the color model. It has the following signature:

 ColorModel getColorModel () 

This method returns null until pixel grabbing has finished.

The getHeight method retrieves the image's height. It has the following signature:

 int getHeight () 

This method returns -1 until pixel grabbing is finished.

The getPixels method retrieves the image's pixels as an array. It has the following signature:

 Object getPixels () 

This method returns null until pixel grabbing is finished.

The getStatus method provides information on whether pixel grabbing succeeded. It has the following signature:

 int getStatus () 

The return value is a set of flags specified by ImageObserver. These flags can be ORed together. ALLBITS and FRAMEBITS indicate success. ABORT and ERROR indicate production problems.

The getWidth method retrieves the image's width. It has the following signature:

 int getWidth () 

This method returns -1 until pixel grabbing is finished.

The grabPixels method starts the pixel grabbing process. It has the following signature:

boolean grabPixels ()

This method starts storing pixel data. It doesn't return until all pixels have been stored. A Boolean true value is returned if all pixels were obtained. An InterruptedException object is thrown if the thread waiting for pixel data is interrupted.

The startGrabbing method makes it possible to asynchronously retrieve image data. It has the following signature:

 void startGrabbing () 

This method immediately returns. You should call the getStatus method to find out when grabbing is complete.

Listing 13.24 presents source code to a Negative applet that demonstrates using PixelGrabber with MemoryImageSource.

Listing 13.24 The Negative Applet Source Code
 // Negative.java import java.awt.*; import java.awt.image.*; import java.applet.Applet; public class Negative extends Applet {    Image im, imInv;    int width;    public void init ()    {       MediaTracker mt = new MediaTracker (this);       im = getImage (getCodeBase (), "tiger.jpg");       mt.addImage (im, 0);       try       {          mt.waitForID (0);       }       catch (InterruptedException e) { }       width = im.getWidth (this);       int height = im.getHeight (this);       int [] pixels = new int [width * height];       PixelGrabber pg = new PixelGrabber (im, 0, 0, width, height,                                           pixels, 0, width);       try       {          pg.grabPixels ();       }       catch (InterruptedException e) { }       int size = width * height;       for (int i = 0; i < size; i++)            pixels [i] = pixels [i] ^ 0xffffff;       imInv = createImage (new MemoryImageSource (width, height,                                                   pixels, 0, width));    }    public void paint (Graphics g)    {       g.drawImage (im, 0, 0, this);       g.drawImage (imInv, width, 0, this);    } } 

Negative displays an image of a Bengal tiger together with a negative of this image. PixelGrabber grabs all pixels of the tiger image and stores them in pixels. Each pixel's color is negated by XORing the red, green, and blue components with 0xffffff. (Any alpha information is preserved.) Figure 13.26 shows Negative 's output.

Figure 13.26 . Negative shows original and negative images.

graphics/13fig26.gif

Troubleshooting Tip

If you are having trouble using PixelGrabber to save the contents of an image to a Microsoft BMP file, see "Saving Images as Bitmap Files," in the "Troubleshooting" section at the end of this chapter.


Filtering Images

What does an image sent to Earth from a distant spacecraft and an old black and white photograph have in common? The answer is deterioration. In many cases, an image sent to Earth has its pixels corrupted by solar radiation. Also, photographs tend to fade with time (with a resulting loss of detail). In either situation, image-processing techniques can recover lost details and render a higher-quality image.

The AWT provides the FilteredImageSource and ImageFilter classes, along with a variety of ImageFilter subclasses, to support image processing. Various filters can be constructed to perform tasks, ranging from sharpening and blurring images, to modifying colors and embossing. Filters can be cascaded together to achieve some truly powerful effects (with minimal extra coding).

The FilteredImageSource and ImageFilter Classes

The FilteredImageSource class (located in the java.awt.image package) is a producer that works with an ImageFilter class (also located in the java.awt.image package) and its subclasses to filter images.

FilteredImageSource works with another producer, an intermediate ImageFilter consumer, and an ultimate consumer. The other producer generates pixel data for an original image and sends it to FilteredImageSource. FilteredImageSource takes this data and sends it to an ImageFilter consumer for modification. (The data is modified so that the resulting image appears scaled, grayed, rotated , cropped, and so on.) FilteredImageSource then takes the data and sends it to an ultimate consumer. Figure 13.27 illustrates the relationship between these entities.

Figure 13.27. A FilteredImage Source producer works with other entities to create filtered images.

graphics/13fig27.gif

The default ImageFilter is known as a "null" filter because it doesn't modify an image. To accomplish anything useful, this class must be subclassed. Assuming you have a class that subclasses ImageFilter, the following code fragment illustrates how you would create and draw a filtered image.

 Image im; Image imFiltered; public void init () {    im = getImage (getDocumentBase (), "tiger.jpg");    imFiltered = createImage (new FilteredImageSource (im.getSource (),                                                       new Filter ())); } public void paint (Graphics g) {    g.drawImage (imFiltered, 0, 0, this); } 
Filter Classes

For common filtering tasks, the AWT provides a variety of ImageFilter subclasses. These subclasses include

  • RGBImageFilter

  • CropImageFilter

  • ReplicateScaleFilter

  • AreaAveragingScaleFilter

All these classes are located in the java.awt.image package. With the exception of RGBImageFilter, the names of these classes reflect their purpose.

RGBImageFilter (located in the java.awt.image package) is an abstract subclass of

ImageFilter. This class makes it possible to create filters that independently change pixels based on their position and color.

If the filtering algorithm does not depend on a pixel's position, the AWT can use an optimization for images that use the index color model. Instead of filtering individual pixels, only the color map is filtered. To tell the AWT to use this optimization, a constructor that sets RGBImageFilter 's canFilterIndexColorModel variable to true can be provided.

When creating an RGBImageFilter subclass, the only method you need to override is the abstract filterRGB method. This method has the following signature:

 int filterRGB (int x, int y, int rgb) 

The x and y arguments identify the pixel's position. If a color table entry is being filtered instead of a pixel (by setting canFilterIndexColorModel to true ), -1 is passed in each of x and y. The rgb argument provides the existing color. The filtered rgb value is returned.

To see what an RGBImageFilter subclass looks like, check out the source code to GrayFilter in Listing 13.25.

Listing 13.25 The GrayFilter Source Code
 // GrayFilter.java import java.awt.image.*; public class GrayFilter extends RGBImageFilter {    public GrayFilter ()    {       canFilterIndexColorModel = true;    }    public int filterRGB (int x, int y, int rgb)    {       int gray = (((rgb & 0xff0000) >> 16) +                   ((rgb & 0x00ff00) >> 8) +                   (rgb & 0x0000ff)) / 3;      return (0xff000000  (gray << 16)  (gray << 8)  gray);    } } 

GrayFilter converts the RGB colors of an image to equivalent shades of gray. This filter can be used in conjunction with FilteredImageSource.

Listing 13.26 presents source code to a GrayFilterDemo applet that uses GrayFilter.

Listing 13.26 The GrayFilterDemo Applet Source Code
 // GrayFilterDemo.java import java.awt.*; import java.awt.image.*; import java.applet.Applet; public class GrayFilterDemo extends Applet {    Image im, imGray;    public void init ()    {       im = getImage (getCodeBase (), "tiger.jpg");       GrayFilter gf = new GrayFilter ();       imGray = createImage (new FilteredImageSource (im.getSource (),                                                      gf));    }    public void paint (Graphics g)    {       if (g.drawImage (im, 0, 0, this));           g.drawImage (imGray, im.getWidth (this), 0, this);    } } 

Figure 13.28 shows GrayFilterDemo 's output.

Figure 13.28. GrayFilterDemo shows original and grayed images.

graphics/13fig28.gif

Cascading Filters

Complex filtering tasks can be simplified by chaining multiple filters together. For example, suppose you wanted to perform gray filtering followed by negative filtering. In other words, you would first like to convert RGB colors to shades of gray and then generate a negative. This is a perfect situation in which to cascade filters.

To demonstrate cascading, you will need a filter for generating a negative. The source code to a filter that accomplishes this task is presented in Listing 13.27.

Listing 13.27 The NegativeFilter Source Code
 // NegativeFilter.java import java.awt.image.*; public class NegativeFilter extends RGBImageFilter {    public NegativeFilter ()    {       canFilterIndexColorModel = true;    }    public int filterRGB (int x, int y, int rgb)    {       return rgb ^ 0x00ffffff;    } } 

NegativeFilter flips the RGB components of an image to achieve a negative. As with GrayFilter, this filter can be used with FilteredImageSource.

Listing 13.28 presents source code to a CascadeFilterDemo applet that uses the previous GrayFilter to convert an image to shades of gray, followed by NegativeFilter to generate a negative.

Listing 13.28 The CascadeFilterDemo Applet Source Code
 // CascadeFilterDemo.java import java.awt.*; import java.awt.image.*; import java.applet.Applet; public class CascadeFilterDemo extends Applet {    Image im, im2;    public void init ()    {       im = getImage (getCodeBase (), "tiger.jpg");       GrayFilter gf = new GrayFilter ();       Image imTemp;       imTemp = createImage (new FilteredImageSource (im.getSource (),                                                      gf));       NegativeFilter nf = new NegativeFilter ();       im2 = createImage (new FilteredImageSource (imTemp.getSource (),                                                   nf));    }    public void paint (Graphics g)    {       if (g.drawImage (im, 0, 0, this));           g.drawImage (im2, im.getWidth (this), 0, this);    } } 

Finally, the moment of truth is at hand. Figure 13.29 shows CascadeFilterDemo 's output. As you can see, the image to the right is both grayed and negated.

Figure 13.29. CascadeFilterDemo shows original and grayed/negative images.

graphics/13fig29.gif

   


Special Edition Using Java 2 Standard Edition
Special Edition Using Java 2, Standard Edition (Special Edition Using...)
ISBN: 0789724685
EAN: 2147483647
Year: 1999
Pages: 353

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net