[Chapter 12] 12.4 ImageConsumer
Author: John Zukowski
Date: Thu Jul 31 14:50:57 1997

#

| Chapter 12 Image Processing |

## 12.4 ImageConsumer

The `ImageConsumer` interface specifies the methods that must be implemented to receive data from an `ImageProducer`. For the most part, that is the only context in which you need to know about the `ImageConsumer` interface. If you write an image producer, it will be handed a number of obscure objects, about which you know nothing except that they implement `ImageConsumer`, and that you can therefore call the methods discussed in this section to deliver your data. The chances that you will ever implement an image consumer are rather remote, unless you are porting Java to a new environment. It is more likely that you will want to subclass `ImageFilter`, in which case you may need to implement some of these methods. But most of the time, you will just need to know how to hand your data off to the next element in the chain.

The `java.awt.image` package includes two classes that implement `ImageConsumer:` `PixelGrabber` and `ImageFilter` (and its subclasses). These classes are unique in that they don't display anything on the screen. `PixelGrabber` takes the image data and stores it in a pixel array; you can use this array to save the image in a file, generate a new image, etc. `ImageFilter`, which is used in conjunction with `FilteredImageSource`, modifies the image data; the `FilteredImageSource` sends the modified image to another consumer, which can further modify or display the new image. When you draw an image on the screen, the JDK's `ImageRepresentation` class is probably doing the real work. This class is part of the `sun.awt.image` package. You really don't need to know anything about it, although you may see `ImageRepresentation` mentioned in a stack trace if you try to filter beyond the end of a pixel array.

### ImageConsumer Interface

Constants

There are two sets of constants for `ImageConsumer`. One set represents those that can be used for the `imageComplete()` method. The other is used with the `setHints()` method. See the descriptions of those methods on how to use them.

The first set of flags is for the `imageComplete()` method:

public static final int IMAGEABORTED

  The `IMAGEABORTED` flag signifies that the image creation process was aborted and the image is not complete. In the image production process, an abort could mean multiple things. It is possible that retrying the production would succeed.

public static final int IMAGEERROR

  The `IMAGEERROR` flag signifies that an error was encountered during the image creation process and the image is not complete. In the image production process, an error could mean multiple things. More than likely, the image file or pixel data is invalid, and retrying won't succeed.

public static final int SINGLEFRAMEDONE

  The `SINGLEFRAMEDONE` flag signifies that a frame other than the last has completed loading. There are additional frames to display, but a new frame is available and is complete. For an example of this flag in use, see the dynamic `ImageFilter` example in Example 12.8.

public static final int STATICIMAGEDONE

  The `STATICIMAGEDONE` flag signifies that the image has completed loading. If this is a multiframe image, all frames have been generated. For an example of this flag in use, see the dynamic `ImageFilter` example in Example 12.8.

The following set of flags can be ORed together to form the single parameter to the `setHints()` method. Certain flags do not make sense set together, but it is the responsibility of the concrete `ImageConsumer` to enforce this.

public static final int COMPLETESCANLINES

  The `COMPLETESCANLINES` flag signifies that each call to `setPixels()` will deliver at least one complete scan line of pixels to this consumer.

public static final int RANDOMPIXELORDER

  The `RANDOMPIXELORDER` flag tells the consumer that pixels are not provided in any particular order. Therefore, the consumer cannot perform optimization that depends on pixel delivery order. In the absence of both `COMPLETESCANLINES` and `RANDOMPIXELORDER`, the `ImageConsumer` should assume pixels will arrive in `RANDOMPIXELORDER`.

public static final int SINGLEFRAME

  The `SINGLEFRAME` flag tells the consumer that this image contains a single non-changing frame. This is the case with most image formats. An example of an image that does not contain a single frame is the multiframe GIF89a image.

public static final int SINGLEPASS

  The `SINGLEPASS` flag tells the consumer to expect each pixel once and only once. Certain image formats, like progressive JPEG images, deliver a single image several times, with each pass yielding a sharper image.

public static final int TOPDOWNLEFTRIGHT

  The final `setHints()` flag, `TOPDOWNLEFTRIGHT`, tells the consumer to expect the pixels in a top-down, left-right order. This flag will almost always be set.

Methods

The interface methods are presented in the order in which they are normally called by an `ImageProducer`.

void setDimensions (int width, int height)

  The `setDimensions()` method should be called once the `ImageProducer` knows the `width` and `height` of the image. This is the actual `width` and `height`, not necessarily the scaled size. It is the consumer's responsibility to do the scaling and resizing.

void setProperties (Hashtable properties)

  The `setProperties()` method should only be called by the `ImageProducer` if the image has any properties that should be stored for later retrieval with the `getProperty()` method of `Image`. Every image format has its own property set. One property that tends to be common is the "comment" property. `properties` represents the `Hashtable` of properties for the image; the name of each property is used as the `Hashtable` key.

void setColorModel (ColorModel model)

  The `setColorModel()` method gives the `ImageProducer` the opportunity to tell the `ImageConsumer` that the `ColorModel` `model` will be used for the majority of pixels in the image. The `ImageConsumer` may use this information for optimization. However, each call to `setPixels()` contains its own `ColorModel`, which isn't necessarily the same as the color model given here. In other words, `setColorModel()` is only advisory; it does not guarantee that all (or any) of the pixels in the image will use this model. Using different color models for different parts of an image is possible, but not recommended.

void setHints (int hints)

  An `ImageProducer` should call the `setHints()` method prior to any `setPixels()` calls. The `hints` are formed by ORing the constants `COMPLETESCANLINES`, `RANDOMPIXELORDER`, `SINGLEFRAME`, `SINGLEPASS`, and `TOPDOWNLEFTRIGHT`. These hints give the image consumer information about the order in which the producer will deliver pixels. When the `ImageConsumer` is receiving pixels, it can take advantage of these hints for optimization.

void setPixels (int x, int y, int width, int height, ColorModel model, byte pixels[], int offset, int scansize)

  An ImageProducer calls the `setPixels()` method to deliver the image pixel data to the `ImageConsumer`. The bytes are delivered a rectangle at a time. (`x`, `y`) represents the top left corner of the rectangle; its dimensions are `width` x `height`. `model` is the `ColorModel` used for this set of pixels; different calls to `setPixels()` may use different color models. The pixels themselves are taken from the byte array `pixels`. `offset` is the first element of the pixel array that will be used. `scansize` is the length of the scan lines in the array. In most cases, you want the consumer to render all the pixels on the scan line; in this case, `scansize` will equal `width`. However, there are cases in which you want the consumer to ignore part of the scan line; you may be clipping an image, and the ends of the scan line fall outside the clipping region. In this case, rather than copying the pixels you want into a new array, you can specify a `width` that is smaller than `scansize`.

  That's a lot of information, but it's easy to summarize. A pixel located at point (`x1`, `y1`) within the rectangle being delivered to the consumer is located at position (`(y1 - y) * scansize + (x1 - x) + offset`) within the array `pixels[]`. Figure 12.4 shows how the pixels delivered by `setPixels()` fit into the complete image; Figure 12.5 shows how pixels are stored within the array.

#### Figure 12.4: Delivering pixels for an image

#### Figure 12.5: Storing pixels in an array

void setPixels (int x, int y, int width, int height, ColorModel model, int pixels[], int offset, int scansize)

  The second `setPixels()` method is similar to the first. `pixels[]` is an array of `int`s; this is necessary when you have more than eight bits of data per pixel.

void imageComplete (int status)

  The `ImageProducer` calls `imageComplete()` to tell an `ImageConsumer` that it has transferred a complete image. The status argument is a flag that describes exactly why the `ImageProducer` has finished. It may have one of the following values: `IMAGEABORTED` (if the image production was aborted); `IMAGEERROR` (if an error in producing the image occurred); `SINGLEFRAMEDONE` (if a single frame of a multiframe image has been completed); or `STATICIMAGEDONE` (if all pixels have been delivered). When `imageComplete()` gets called, the `ImageConsumer` should call the image producer's `removeConsumer()` method, unless it wants to receive additional frames (status of `SINGLEFRAMEDONE`).

PPMImageDecoder

Now that we have discussed the `ImageConsumer` interface, we're finally ready to give an example of a full-fledged `ImageProducer`. This producer uses the methods of the `ImageConsumer` interface to communicate with image consumers; image consumers use the `ImageProducer` interface to register themselves with this producer.

Our image producer will interpret images in the PPM format.[1] PPM is a simple image format developed by Jef Poskanzer as part of the pbmplus image conversion package. A PPM file starts with a header consisting of the image type, the image's width and height in pixels, and the maximum value of any RGB component. The header is entirely in ASCII. The pixel data follows the header; it is either in binary (if the image type is P6) or ASCII (if the image type is P3). The pixel data is simply a series of bytes describing the color of each pixel, moving left to right and top to bottom. In binary format, each pixel is represented by three bytes: one for red, one for green, and one for blue. In ASCII format, each pixel is represented by three numeric values, separated by white space (space, tab, or newline). A comment may occur anywhere in the file, but it would be surprising to see one outside of the header. Comments start with # and continue to the end of the line. ASCII format files are obviously much larger than binary files. There is no compression on either file type.

> [1] For more information about PPM and the pbmplus package, see Encyclopedia of Graphics File Formats, by James D. Murray and William VanRyper (from O'Reilly & Associates). See also http://www.acme.com/.

The `PPMImageDecoder` source is listed in Example 12--4. The applet that uses this class is shown in Example 12.5. You can reuse a lot of the code in the `PPMImageDecoder` when you implement your own image producers.

#### Example 12.4: PPMImageDecoder Source

    import java.awt.*;
    import java.awt.image.*;
    import java.util.*;
    import java.io.*;
    public class PPMImageDecoder implements ImageProducer {
    /* Since done in-memory, only one consumer */
        private ImageConsumer consumer;
        boolean loadError = false;
        int width;
        int height;
        int store[][];
        Hashtable props = new Hashtable();
    /* Format of Ppm file is single pass/frame, w/ complete scan lines in order */
        private static int PpmHints = (ImageConsumer.TOPDOWNLEFTRIGHT |
                                       ImageConsumer.COMPLETESCANLINES |
                                       ImageConsumer.SINGLEPASS |
                                       ImageConsumer.SINGLEFRAME);

The class starts by declaring class variables and constants. We will use the variable `PpmHints` when we call `setHints()`. Here, we set this variable to a collection of "hint" constants that indicate we will produce pixel data in top-down, left-right order; we will always send complete scan lines; we will make only one pass over the pixel data (we will send each pixel once); and there is one frame per image (i.e., we aren't producing a multiframe sequence).

The next chunk of code implements the `ImageProducer` interface; consumers use it to request image data:

    /* There is only a single consumer. When it registers, produce image. */
    /* On error, notify consumer. */
        public synchronized void addConsumer (ImageConsumer ic) {
            consumer = ic;
            try {
                produce();
            }catch (Exception e) {
                if (consumer != null)
                    consumer.imageComplete (ImageConsumer.IMAGEERROR);
            }
            consumer = null;
        }
    /* If consumer passed to routine is single consumer, return true, else false. */
        public synchronized boolean isConsumer (ImageConsumer ic) {
            return (ic == consumer);
        }
    /* Disables consumer if currently consuming. */
        public synchronized void removeConsumer (ImageConsumer ic) {
            if (consumer == ic)
                consumer = null;
        }
    /* Production is done by adding consumer. */
        public void startProduction (ImageConsumer ic) {
            addConsumer (ic);
        }
        public void requestTopDownLeftRightResend (ImageConsumer ic) {
            // Not needed.  The data is always in this format.
        }

The previous group of methods implements the `ImageProducer` interface. They are quite simple, largely because of the way this `ImageProducer` generates images. It builds the image in memory before delivering it to the consumer; you must call the `readImage()` method (discussed shortly) before you can create an image with this consumer. Because the image is in memory before any consumers can register their interest, we can write an `addConsumer()` method that registers a consumer and delivers all the data to that consumer before returning. Therefore, we don't need to manage a list of consumers in a `Hashtable` or some other collection object. We can store the current consumer in an instance variable `ic` and forget about any others: only one consumer exists at a time. To make sure that only one consumer exists at a time, we synchronize the `addConsumer()`, `isConsumer()`, and `removeConsumer()` methods. Synchronization prevents another consumer from registering itself before the current consumer has finished. If you write an `ImageProducer` that builds the image in memory before delivering it, you can probably use this code verbatim.

`addConsumer()` is little more than a call to the method `produce()`, which handles "consumer relations": it delivers the pixels to the consumer using the methods in the `ImageConsumer` interface. If `produce()` throws an exception, `addConsumer()` calls `imageComplete()` with an `IMAGEERROR` status code. Here's the code for the `produce()` method:

    /* Production Process:
            Prerequisite: Image already read into store array. (readImage)
                          props / width / height already set (readImage)
            Assumes RGB Color Model - would need to filter to change.
            Sends Ppm Image data to consumer.
            Pixels sent one row at a time.
    */
        private void produce () {
            ColorModel cm = ColorModel.getRGBdefault();
            if (consumer != null) {
                if (loadError) {
                    consumer.imageComplete (ImageConsumer.IMAGEERROR);
                } else {
                    consumer.setDimensions (width, height);
                    consumer.setProperties (props);
                    consumer.setColorModel (cm);
                    consumer.setHints (PpmHints);
                    for (int j=0;j<height;j++)
                        consumer.setPixels (0, j, width, 1, cm, store[j], 0, width);
                    consumer.imageComplete (ImageConsumer.STATICIMAGEDONE);
                }
            }
        }

`produce()` just calls the `ImageConsumer` methods in order: it sets the image's dimensions, hands off an empty `Hashtable` of properties, sets the color model (the default RGB model) and the hints, and then calls `setPixels()` once for each row of pixel data. The data is in the integer array `store[][]`, which has already been loaded by the `readImage()` method (defined in the following code). When the data is delivered, the method `setPixels()` calls `imageComplete()` to indicate that the image has been finished successfully.

    /* Allows reading to be from internal byte array, in addition to disk/socket */
        public void readImage (byte b[]) {
            readImage (new ByteArrayInputStream (b));
        }
    /* readImage reads image data from Stream */
    /* parses data for PPM format             */
    /* closes inputstream when done           */
        public void readImage (InputStream is) {
            long tm = System.currentTimeMillis();
            boolean raw=false;
            DataInputStream dis = null;
            BufferedInputStream bis = null;
            try {
                bis = new BufferedInputStream (is);
                dis = new DataInputStream (bis);
                String word;
                word = readWord (dis);
                if ("P6".equals (word)) {
                    raw = true;
                } else if ("P3".equals (word)) {
                    raw = false;
                } else {
                    throw (new AWTException ("Invalid Format " + word));
                }
                width = Integer.parseInt (readWord (dis));
                height = Integer.parseInt (readWord (dis));
                // Could put comments in props - makes readWord more complex
                int maxColors = Integer.parseInt (readWord (dis));
                if ((maxColors < 0) || (maxColors > 255)) {
                    throw (new AWTException ("Invalid Colors " + maxColors));
                }
                store = new int[height][width];
                if (raw) {                   // binary format (raw) pixel data
                    byte row[] = new byte [width*3];
                    for (int i=0;i<height;i++){
                        dis.readFully (row);
                        for (int j=0,k=0;j<width;j++,k+=3) {
                            int red = row[k];
                            int green = row[k+1];
                            int blue = row[k+2];
                            if (red < 0)
                                red +=256;
                            if (green < 0)
                                green +=256;
                            if (blue < 0)
                                blue +=256;
                            store[i][j] = (0xff<< 24) | (red << 16) |
                                          (green << 8) | blue;
                        }
                    }
                } else {                     // ASCII pixel data
                    for (int i=0;i<height;i++) {
                        for (int j=0;j<width;j++) {
                            int red = Integer.parseInt (readWord (dis));
                            int green = Integer.parseInt (readWord (dis));
                            int blue = Integer.parseInt (readWord (dis));
                            store[i][j] = (0xff<< 24) | (red << 16) |
                                          (green << 8) | blue;
                        }
                    }
                }
            } catch (IOException io) {
                loadError = true;
                System.out.println ("IO Exception " + io.getMessage());
            } catch (AWTException awt) {
                loadError = true;
                System.out.println ("AWT Exception " + awt.getMessage());
            } catch (NoSuchElementException nse) {
                loadError = true;
                System.out.println ("No Such Element Exception " + nse.getMessage());
            } finally {
                try {
                    if (dis != null)
                        dis.close();
                    if (bis != null)
                        bis.close();
                    if (is != null)
                        is.close();
                } catch (IOException io) {
                    System.out.println ("IO Exception " + io.getMessage());
                }
            }
            System.out.println ("Done in " + (System.currentTimeMillis() - tm)
                                + " ms");
        }

`readImage()` reads the image data from an `InputStream` and converts it into the array of pixel data that `produce()` transfers to the consumer. Code using this class must call `readImage()` to process the data before calling `createImage()`; we'll see how this works shortly. Although there is a lot of code in `readImage()`, it's fairly simple. (It would be much more complex if we were dealing with an image format that compressed the data.) It makes heavy use of `readWord()`, a utility method that we'll discuss next; `readWord()` returns a word of ASCII text as a string.

`readImage()` starts by converting the `InputStream` into a `DataInputStream`. It uses `readWord()` to get the first word from the stream. This should be either "P6" or "P3", depending on whether the data is in binary or ASCII. It then uses `readWord()` to save the image's width and height and the maximum value of any color component. Next, it reads the color data into the `store[][]` array. The ASCII case is simple because we can use `readWord()` to read ASCII words conveniently; we read red, green, and blue words, convert them into `ints`, and pack the three into one element (one pixel) of `store[][]`. For binary data, we read an entire scan line into the byte array `row[]`, using `readFully()`; then we start a loop that packs this scan line into one row of `store[][]`. A little additional complexity is in the inner loop because we must keep track of two arrays (`row[]` and `store[][]`). We read red, green, and blue components from `row[]`, converting Java's signed bytes to unsigned data by adding 256 to any negative values; finally, we pack these components into one element of `store[][]`.

    /* readWord returns a word of text from stream          */
    /* Ignores PPM comment lines.                           */
    /* word defined to be something wrapped by whitespace   */
        private String readWord (InputStream is) throws IOException {
            StringBuffer buf = new StringBuffer();
            int b;
            do {// get rid of leading whitespace
                if ((b=is.read()) == -1)
                    throw new EOFException();
                if ((char)b == '#') { // read to end of line - ppm comment
                    DataInputStream dis = new DataInputStream (is);
                    dis.readLine();
                    b = ' ';  // ensure more reading
                }
            }while (Character.isSpace ((char)b));
            do {
                buf.append ((char)(b));
                if ((b=is.read()) == -1)
                    throw new EOFException();
            } while (!Character.isSpace ((char)b));  // reads first space
            return buf.toString();
        }
    }

`readWord()` is a utility method that reads one ASCII word from an `InputStream`. A word is a sequence of characters that aren't spaces; space characters include newlines and tabs in addition to spaces. This method also throws out any comments (anything between # and the end of the line). It collects the characters into a `StringBuffer`, converting the `StringBuffer` into a `String` when it returns.

#### Example 12.5: PPMImageDecoder Test Program

    import java.awt.Graphics;
    import java.awt.Color;
    import java.awt.image.ImageConsumer;
    import java.awt.Image;
    import java.awt.MediaTracker;
    import java.net.URL;
    import java.net.MalformedURLException;
    import java.io.InputStream;
    import java.io.IOException;
    import java.applet.Applet;
    public class ppmViewer extends Applet {
        Image image = null;
        public void init () {
            try {
                String file = getParameter ("file");
                if (file != null) {
                    URL imageurl = new URL (getDocumentBase(), file);
                    InputStream is = imageurl.openStream();
                    PPMImageDecoder ppm = new PPMImageDecoder ();
                    ppm.readImage (is);
                    image = createImage (ppm);
                    repaint();
                }
            } catch (MalformedURLException me) {
                System.out.println ("Bad URL");
             } catch (IOException io) {
                System.out.println ("Bad File");
            }
        }
        public void paint (Graphics g) {
            g.drawImage (image, 0, 0, this);
        }
    }

The applet we use to test our `ImageProducer` is very simple. It creates a URL that points to an appropriate PPM file and gets an `InputStream` from that URL. It then creates an instance of our `PPMImageDecoder`; calls `readImage()` to load the image and generate pixel data; and finally, calls `createImage()` with our `ImageProducer` as an argument to create an `Image` object, which we draw in `paint()`.

### PixelGrabber

The `PixelGrabber` class is a utility for converting an image into an array of pixels. This is useful in many situations. If you are writing a drawing utility that lets users create their own graphics, you probably want some way to save a drawing to a file. Likewise, if you're implementing a shared whiteboard, you'll want some way to transmit images across the Net. If you're doing some kind of image processing, you may want to read and alter individual pixels in an image. The `PixelGrabber` class is an `ImageConsumer` that can capture a subset of the current pixels of an `Image`. Once you have the pixels, you can easily save the image in a file, send it across the Net, or work with individual points in the array. To recreate the `Image` (or a modified version), you can pass the pixel array to a `MemoryImageSource`.

Prior to Java 1.1, `PixelGrabber` saves an array of pixels but doesn't save the image's width and height--that's your responsibility. You may want to put the width and height in the first two elements of the pixel array and use an offset of 2 when you store (or reproduce) the image.

Starting with Java 1.1, the grabbing process changes in several ways. You can ask the `PixelGrabber` for the image's size or color model. You can grab pixels asynchronously and abort the grabbing process before it is completed. Finally, you don't have to preallocate the pixel data array. Constructors

public PixelGrabber (ImageProducer ip, int x, int y, int width, int height, int pixels[], int offset, int scansize)

  The first `PixelGrabber` constructor creates a new `PixelGrabber` instance. The `PixelGrabber` uses `ImageProducer` `ip` to store the unscaled cropped rectangle at position (`x`, `y`) of size `width` x `height` into the `pixels` array, starting at `offset` within `pixels`, and each row starting at increments of `scansize` from that.

  As shown in Figure 12.5, the position (`x1`, `y1`) would be stored in `pixels[]` at position `(y1 - y) * scansize + (x1 - x) + offset.` Calling `grabPixels()` starts the process of writing pixels into the array.

  The `ColorModel` for the pixels copied into the array is always the default RGB model: that is, 32 bits per pixel, with 8 bits for alpha, red, green, and blue components.

public PixelGrabber (Image image, int x, int y, int width, int height, int pixels[], int offset, int scansize)

  This version of the `PixelGrabber` constructor gets the `ImageProducer` of the `Image` `image` through `getSource()`; it then calls the previous constructor to create the `PixelGrabber`.

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

  This version of the constructor does not require you to preallocate the pixel array and lets you preserve the color model of the original image. If `forceRGB` is `true`, the pixels of `image` are converted to the default RGB model when grabbed. If `forceRGB` is `false` and all the pixels of image use one `ColorModel`, the original color model of `image` is preserved.

  As with the other constructors, the `x`, `y`, `width`, and `height` values define the bounding box to grab. However, there's one special case to consider. Setting `width` or `height` to -1 tells the `PixelGrabber` to take the `width` and `height` from the image itself. In this case, the grabber stores all the pixels below and to the right of the point (`x`, `y`). If (`x`, `y`) is outside of the image, you get an empty array.

  Once the pixels have been grabbed, you get the pixel data via the `getPixels()` method described in "Other methods." To get the `ColorModel`, see the `getColorModel()` method.

ImageConsumer interface methods

public void setDimensions (int width, int height)

  In Java 1.0, the `setDimensions()` method of `PixelGrabber` ignores the `width` and `height`, since this was set by the constructor.

  With Java 1.1, `setDimensions()` is called by the image producer to give it the dimensions of the original image. This is how the `PixelGrabber` finds out the image's size if the constructor specified -1 for the image's width or height.

public void setHints (int hints)

  The `setHints()` method ignores the `hints`.

public void setProperties (Hashtable properties)

  The `setProperties()` method ignores the `properties`.

public void setColorModel (ColorModel model)

  The `setColorModel()` method ignores the `model`.

public void setPixels (int x, int y, int w, int h, ColorModel model, byte pixels[], int offset, int scansize)

  The `setPixels()` method is called by the `ImageProducer` to deliver pixel data for some image. If the pixels fall within the portion of the image that the `PixelGrabber` is interested in, they are stored within the array passed to the `PixelGrabber` constructor. If necessary, the `ColorModel` is used to convert each pixel from its original representation to the default RGB representation. This method is called when each pixel coming from the image producer is represented by a byte.

public void setPixels (int x, int y, int w, int h, ColorModel model, int pixels[], int offset, int scansize)

  The second `setPixels()` method is almost identical to the first; it is used when each pixel coming from the image producer is represented by an `int`.

public synchronized void imageComplete (int status)

  The `imageComplete()` method uses `status` to determine if the pixels were successfully delivered. The `PixelGrabber` then notifies anyone waiting for the pixels from a `grabPixels()` call.

Grabbing methods

public synchronized boolean grabPixels (long ms) throws InterruptedException

  The `grabPixels()` method starts storing pixel data from the image. It doesn't return until all pixels have been loaded into the pixels array or until `ms` milliseconds have passed. The return value is `true` if all pixels were successfully acquired. Otherwise, it returns `false` for the abort, error, or timeout condition encountered. The exception `InterruptedException` is thrown if another thread interrupts this one while waiting for pixel data.

public boolean grabPixels () throws InterruptedException

  This `grabPixels()` method starts storing pixel data from the image. It doesn't return until all pixels have been loaded into the pixels array. The return value is `true` if all pixels were successfully acquired. It returns `false` if it encountered an abort or error condition. The exception `InterruptedException` is thrown if another thread interrupts this one while waiting for pixel data.

public synchronized void startGrabbing()

  The `startGrabbing()` method provides an asynchronous means of grabbing the pixels. This method returns immediately; it does not block like the `grabPixels()` methods described previously. To find out when the `PixelGrabber` has finished, call `getStatus()`.

public synchronized void abortGrabbing()

  The `abortGrabbing()` method allows you to stop grabbing pixel data from the image. If a thread is waiting for pixel data from a `grabPixels()` call, it is interrupted and `grabPixels()` throws an `InterruptedException`.

Other methods

public synchronized int getStatus()
public synchronized int status ()

  Call the `getStatus()` method to find out whether a `PixelGrabber` succeeded in grabbing the pixels you want. The return value is a set of `ImageObserver` flags ORed together. `ALLBITS` and `FRAMEBITS` indicate success; which of the two you get depends on how the image was created. `ABORT` and `ERROR` indicate that problems occurred while the image was being produced.

  `status()`is the Java 1.0 name for this method.

public synchronized int getWidth()

  The `getWidth()` method reports the width of the image data stored in the destination buffer. If you set width to -1 when you called the `PixelGrabber` constructor, this information will be available only after the grabber has received the information from the image producer (`setDimensions()`). If the width is not available yet, `getWidth()` returns -1.

  The width of the resulting image depends on several factors. If you specified the width explicitly in the constructor, the resulting image has that width, no questions asked--even if the position at which you start grabbing is outside the image. If you specified -1 for the width, the resulting width will be the difference between the `x` position at which you start grabbing (set in the constructor) and the actual image width; for example, if you start grabbing at `x`=50 and the original image width is 100, the width of the resulting image is 50. If `x` falls outside the image, the resulting width is 0.

public synchronized int getHeight()

  The `getHeight()` method reports the height of the image data stored in the destination buffer. If you set height to -1 when you called the `PixelGrabber` constructor, this information will be available only after the grabber has received the information from the image producer (`setDimensions()`). If the height is not available yet, `getHeight()` returns -1.

  The height of the resulting image depends on several factors. If you specified the height explicitly in the constructor, the resulting image has that height, no questions asked--even if the position at which you start grabbing is outside the image. If you specified -1 for the height, the resulting height will be the difference between the `y` position at which you start grabbing (set in the constructor) and the actual image height; for example, if you start grabbing at `y`=50 and the original image height is 100, the height of the resulting image is 50. If `y` falls outside the image, the resulting height is 0.

public synchronized Object getPixels()

  The `getPixels()` method returns an array of pixel data. If you passed a pixel array to the constructor, you get back your original array object, with the data filled in. If, however, the array was not previously allocated, you get back a new array. The size of this array depends on the image you are grabbing and the portion of that image you want. If size and image format are not known yet, this method returns `null`. If the `PixelGrabber` is still grabbing pixels, this method returns an array that may change based upon the rest of the image. The type of the array you get is either `int[]` or `byte[]`, depending on the color model of the image. To find out if the `PixelGrabber` has finished, call `getStatus()`.

public synchronized ColorModel getColorModel()

  The `getColorModel()` method returns the color model of the image. This could be the default RGB `ColorModel` if a pixel buffer was explicitly provided, `null` if the color model is not known yet, or a varying color model until all the pixel data has been grabbed. After all the pixels have been grabbed, `getColorModel()` returns the actual color model used for the `getPixels()`array. It is best to wait until grabbing has finished before you ask for the `ColorModel`; to find out, call `getStatus()`.

Using PixelGrabber to modify an image

You can modify images by combining a `PixelGrabber` with `MemoryImageSource`. Use `getImage()` to load an image from the Net; then use `PixelGrabber` to convert the image into an array. Modify the data in the array any way you please; then use `MemoryImageSource` as an image producer to display the new image.

Example 12.6 demonstrates the use of the `PixelGrabber` and `MemoryImageSource` to rotate, flip, and mirror an image. (We could also do the rotations with a subclass of `ImageFilter`, which we will discuss next.) The output is shown in Figure 12.6. When working with an image that is loaded from a local disk or the network, remember to wait until the image is loaded before grabbing its pixels. In this example, we use a `MediaTracker` to wait for the image to load.

#### Example 12.6: Flip Source

    import java.applet.*;
    import java.awt.*;
    import java.awt.image.*;
    public class flip extends Applet {
        Image i, j, k, l;
        public void init () {
            MediaTracker mt = new MediaTracker (this);
            i = getImage (getDocumentBase(), "ora-icon.gif");
            mt.addImage (i, 0);
        try {
            mt.waitForAll();
            int width = i.getWidth(this);
            int height = i.getHeight(this);
            int pixels[] = new int [width * height];
            PixelGrabber pg = new PixelGrabber
            (i, 0, 0, width, height, pixels, 0, width);
            if (pg.grabPixels() && ((pg.status() &
                ImageObserver.ALLBITS) !=0)) {
                j = createImage (new MemoryImageSource (width, height,
                         rowFlipPixels (pixels, width, height), 0, width));
                k = createImage (new MemoryImageSource (width, height,
                     colFlipPixels (pixels, width, height), 0, width));
                l = createImage (new MemoryImageSource (height, width,
                     rot90Pixels (pixels, width, height), 0, height));
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

#### Figure 12.6: Flip output

The `try` block in Example 12.6 does all the interesting work. It uses a `PixelGrabber` to grab the entire image into the array `pixels[]`. After calling `grabPixels()`, it checks the `PixelGrabber` status to make sure that the image was stored correctly. It then generates three new images based on the first by calling `createImage()` with a `MemoryImageSource` object as an argument. Instead of using the original array, the `MemoryImageSource` objects call several utility methods to manipulate the array: `rowFlipPixels()`, `colFlipPixels()`, and `rot90Pixels()`. These methods all return integer arrays.

    public void paint (Graphics g) {
        g.drawImage (i, 10, 10, this); // regular
        if (j != null)
            g.drawImage (j, 150, 10, this); // rowFlip
        if (k != null)
            g.drawImage (k, 10, 60, this); // colFlip
        if (l != null)
            g.drawImage (l, 150, 60, this); // rot90
    }
    private int[] rowFlipPixels (int pixels[], int width, int height) {
        int newPixels[] = null;
        if ((width*height) == pixels.length) {
            newPixels = new int [width*height];
            int newIndex=0;
            for (int y=height-1;y>=0;y--)
                for (int x=width-1;x>=0;x--)
                    newPixels[newIndex++]=pixels[y*width+x];
        }
        return newPixels;
    }

`rowFlipPixels()` creates a mirror image of the original, flipped horizontally. It is nothing more than a nested loop that copies the original array into a new array.

        private int[] colFlipPixels (int pixels[], int width, int height) {
            ...
        }
    private int[] rot90Pixels (int pixels[], int width, int height) {
            ...
        }
    }

`colFlipPixels()` and `rot90Pixels()` are fundamentally similar to `rowFlipPixels()`; they just copy the original pixel array into another array, and return the result. `colFlipPixels()` generates a vertical mirror image; `rot90Pixels()` rotates the image by 90 degrees counterclockwise. Grabbing data asynchronously

To demonstrate the new methods introduced by Java 1.1 for `PixelGrabber`, the following program grabs the pixels and reports information about the original image on mouse clicks. It takes its data from the image used in Figure 12.6.

    // Java 1.1 only
    import java.applet.*;
    import java.awt.*;
    import java.awt.image.*;
    import java.awt.event.*;
    public class grab extends Applet {
        Image i;
        PixelGrabber pg;
        public void init () {
            i = getImage (getDocumentBase(), "ora-icon.gif");
            pg  = new PixelGrabber (i, 0, 0, -1, -1, false);
            pg.startGrabbing();
            enableEvents (AWTEvent.MOUSE_EVENT_MASK);
        }
        public void paint (Graphics g) {
            g.drawImage (i, 10, 10, this);
        }
        protected void processMouseEvent(MouseEvent e) {
            if (e.getID() == MouseEvent.MOUSE_CLICKED) {
                System.out.println ("Status: " + pg.getStatus());
                System.out.println ("Width:  " + pg.getWidth());
                System.out.println ("Height: " + pg.getHeight());
                System.out.println ("Pixels: " +
                   (pg.getPixels() instanceof byte[] ? "bytes" : "ints"));
                System.out.println ("Model:  " + pg.getColorModel());
            }
            super.processMouseEvent (e);
        }
    }

This applet creates a `PixelGrabber` without specifying an array, then starts grabbing pixels. The grabber allocates its own array, but we never bother to ask for it since we don't do anything with the data itself: we only report the grabber's status. (If we wanted the data, we'd call `getPixels()`.) Sample output from a single mouse click, after the image loaded, would appear something like the following:

    Status: 27
    Width:  120
    Height: 38
    Pixels: bytes
    Model:  java.awt.image.IndexColorModel@1ed34

You need to convert the status value manually to the corresponding meaning by looking up the status codes in `ImageObserver`. The value 27 indicates that the 1, 2, 8, and 16 flags are set, which translates to the `WIDTH`, `HEIGHT`, `SOMEBITS`, and `FRAMEBITS` flags, respectively.

ImageProducer |  | ImageFilter