start page | rating of books | rating of authors | reviews | copyrights

Java AWT

Previous Chapter 12
Image Processing
Next
 

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.

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 ints; 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) (New)

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() (New)

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() (New)

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() (New)
public synchronized int status () (Deprecated)

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() (New)

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() (New)

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() (New)

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() (New)

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(); 
    } 
} 

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.


Previous Home Next
ImageProducer Book Index ImageFilter

Java in a Nutshell Java Language Reference Java AWT Java Fundamental Classes Exploring Java