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
ConstantsThere 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 dynamicImageFilter
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 dynamicImageFilter
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 tosetPixels()
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 bothCOMPLETESCANLINES
andRANDOMPIXELORDER
, theImageConsumer
should assume pixels will arrive inRANDOMPIXELORDER
. - 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.
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 theImageProducer
knows thewidth
andheight
of the image. This is the actualwidth
andheight
, 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 theImageProducer
if the image has any properties that should be stored for later retrieval with thegetProperty()
method ofImage
. Every image format has its own property set. One property that tends to be common is the "comment" property.properties
represents theHashtable
of properties for the image; the name of each property is used as theHashtable
key. - void setColorModel (ColorModel model)
- The
setColorModel()
method gives theImageProducer
the opportunity to tell theImageConsumer
that theColorModel
model
will be used for the majority of pixels in the image. TheImageConsumer
may use this information for optimization. However, each call tosetPixels()
contains its ownColorModel
, 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 thesetHints()
method prior to anysetPixels()
calls. Thehints
are formed by ORing the constantsCOMPLETESCANLINES
,RANDOMPIXELORDER
,SINGLEFRAME
,SINGLEPASS
, andTOPDOWNLEFTRIGHT
. These hints give the image consumer information about the order in which the producer will deliver pixels. When theImageConsumer
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 theImageConsumer
. The bytes are delivered a rectangle at a time. (x
,y
) represents the top left corner of the rectangle; its dimensions arewidth
xheight
.model
is theColorModel
used for this set of pixels; different calls tosetPixels()
may use different color models. The pixels themselves are taken from the byte arraypixels
.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 equalwidth
. 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 awidth
that is smaller thanscansize
.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 arraypixels[]
. Figure 12.4 shows how the pixels delivered bysetPixels()
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 ofint
s; this is necessary when you have more than eight bits of data per pixel. - void imageComplete (int status)
- The
ImageProducer
callsimageComplete()
to tell anImageConsumer
that it has transferred a complete image. The status argument is a flag that describes exactly why theImageProducer
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); orSTATICIMAGEDONE
(if all pixels have been delivered). WhenimageComplete()
gets called, theImageConsumer
should call the image producer'sremoveConsumer()
method, unless it wants to receive additional frames (status ofSINGLEFRAMEDONE
).
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 Anonymous). 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 newPixelGrabber
instance. ThePixelGrabber
usesImageProducer
ip
to store the unscaled cropped rectangle at position (x
,y
) of sizewidth
xheight
into thepixels
array, starting atoffset
withinpixels
, and each row starting at increments ofscansize
from that.As shown in Figure 12.5, the position (
x1
,y1
) would be stored inpixels[]
at position(y1 - y) * scansize + (x1 - x) + offset.
CallinggrabPixels()
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 theImageProducer
of theImage
image
throughgetSource()
; it then calls the previous constructor to create thePixelGrabber
. - 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
istrue
, the pixels ofimage
are converted to the default RGB model when grabbed. IfforceRGB
isfalse
and all the pixels of image use oneColorModel
, the original color model ofimage
is preserved.As with the other constructors, the
x
,y
,width
, andheight
values define the bounding box to grab. However, there's one special case to consider. Settingwidth
orheight
to -1 tells thePixelGrabber
to take thewidth
andheight
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 theColorModel
, see thegetColorModel()
method.
- public void setDimensions (int width, int height)
- In Java 1.0, the
setDimensions()
method ofPixelGrabber
ignores thewidth
andheight
, 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 thePixelGrabber
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 thehints
. - public void setProperties (Hashtable properties)
- The
setProperties()
method ignores theproperties
. - public void setColorModel (ColorModel model)
- The
setColorModel()
method ignores themodel
. - 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 theImageProducer
to deliver pixel data for some image. If the pixels fall within the portion of the image that thePixelGrabber
is interested in, they are stored within the array passed to thePixelGrabber
constructor. If necessary, theColorModel
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 anint
. - public synchronized void imageComplete (int status)
- The
imageComplete()
method usesstatus
to determine if the pixels were successfully delivered. ThePixelGrabber
then notifies anyone waiting for the pixels from agrabPixels()
call.
- 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 untilms
milliseconds have passed. The return value istrue
if all pixels were successfully acquired. Otherwise, it returnsfalse
for the abort, error, or timeout condition encountered. The exceptionInterruptedException
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 istrue
if all pixels were successfully acquired. It returnsfalse
if it encountered an abort or error condition. The exceptionInterruptedException
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 thegrabPixels()
methods described previously. To find out when thePixelGrabber
has finished, callgetStatus()
. - 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 agrabPixels()
call, it is interrupted andgrabPixels()
throws anInterruptedException
.
- public synchronized int getStatus()
public synchronized int status () - Call the
getStatus()
method to find out whether aPixelGrabber
succeeded in grabbing the pixels you want. The return value is a set ofImageObserver
flags ORed together.ALLBITS
andFRAMEBITS
indicate success; which of the two you get depends on how the image was created.ABORT
andERROR
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 thePixelGrabber
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 atx
=50 and the original image width is 100, the width of the resulting image is 50. Ifx
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 thePixelGrabber
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 aty
=50 and the original image height is 100, the height of the resulting image is 50. Ify
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 returnsnull
. If thePixelGrabber
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 eitherint[]
orbyte[]
, depending on the color model of the image. To find out if thePixelGrabber
has finished, callgetStatus()
. - public synchronized ColorModel getColorModel()
- The
getColorModel()
method returns the color model of the image. This could be the default RGBColorModel
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 thegetPixels()
array. It is best to wait until grabbing has finished before you ask for theColorModel
; to find out, callgetStatus()
.
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.