Previous | Next | Trail Map | Creating a User Interface | Working with Graphics

How to Write an Image Filter

All image filters must be subclasses of the ImageFilter(in the Creating a User Interface trail) class. If your image filter will modify the colors or transparency of an image, then instead of creating a direct subclass of ImageFilter, you should probably create a subclass of RGBImageFilter(in the Creating a User Interface trail).

Before writing an image filter, you first should find others, studying any that are similar to what you plan to write. You should also study the ImageProducer and ImageConsumer interfaces, becoming thoroughly familiar with them.

Finding Examples

You can find examples of RGBImageFilter subclasses in the applets mentioned on the previous page (Dynamically Generated Color Bullets, Live Feedback ImageMap, and Image Test).

Later in this page, you'll see an example of a direct ImageFilter subclass, RotateFilter.

Creating an ImageFilter Subclass

As we mentioned before, image filters implement the ImageConsumer(in the API reference documentation) interface. This lets them intercept data intended for the image consumer. ImageConsumer defines the following methods:
void setDimensions(int width, int height);
void setProperties(Hashtable props);
void setColorModel(ColorModel model);
void setHints(int hintflags);
void setPixels(int x, int y, int w, int h,
               ColorModel model, byte pixels[],
               int off, int scansize);
void setPixels(int x, int y, int w, int h,
               ColorModel model, int pixels[],
               int off, int scansize);
void imageComplete(int status);
The ImageFilter class implements all the above methods so that they forward the method data to the filter's consumer. For example, ImageFilter implements the setDimensions() method as follows:
public void setDimensions(int width, int height) {
    consumer.setDimensions(width, height);
Thanks to these ImageFilter methods, your subclass probably doesn't need to implement every ImageConsumer method. You need to implement only the methods that transmit data you want to change.

For example, the CropImageFilter(in the API reference documentation) class implements four of the ImageConsumer methods: setDimensions(), setProperties(), and both varieties of setPixels(). It also implements a constructor with arguments that specify the rectangle to be cropped. As another example, the RGBImageFilter(in the API reference documentation) class implements some helper methods, defines an abstract helper method to perform the actual color modifications of each pixel, and implements the following ImageConsumer methods: setColorModel() and both varieties of setPixels().

Most, if not all, filters implement the setPixels() methods. These methods determine exactly what image data is used to construct the Image. One or both of the setPixels() methods may be called multiple times during the construction of a single image. Each call gives the ImageConsumer information about a rectangle of pixels within the image. When the ImageConsumer's imageComplete() method is called with any status except SINGLEFRAMEDONE (which implies that data for more frames will appear), then the ImageConsumer can assume that it will receive no further setPixels() calls. An imageComplete() status of STATICIMAGEDONE specifies that not only is the image data complete, but that no errors have been detected.

The following illustration and table describe the arguments to the setPixels() methods.

x, y
Specify the location within the image, relative to its upper left corner, at which this rectangle begins.
w, h
Specify the width and height, in pixels, of this rectangle.
Specifies the color model used by the data in the pixels array.
Specifies an array of pixels. The rectangle of image data is contained in this array, but the array might contain more than w*h entries, depending on the values of offset and scansize. Here's the formula for determining what entry in the pixels array contains the data for the pixel at (x+i, y+j), where (0 <= i < w) and (0 <= j < h):
    offset + (j * scansize) + i 
The above formula assumes that (m,n) is in the rectangle this setPixels() call specifies, and that (m,n) is relative to the image origin. Below is an illustration of the pixels array to make this clearer. It shows how a specific pixel (for example, (x,y)) maps to an entry in the pixels array.

Specifies the index (in the pixels array) of the first pixel in the rectangle.
Specifies the width of each row in the pixels array. Due to efficiency considerations, this might be greater than w.

The RotateFilter Image Filter

The RotateFilter class rotates an image by the specified angle. It relies on the following graphics formulas to calculate the new position of each pixel:
newX = oldX*cos(angle) - oldY*sin(angle)
newY = oldX*sin(angle) + oldY*cos(angle)
RotateFilter implements the following ImageConsumer methods:
Records the unfiltered image's width and height for use in the setPixels() and imageComplete() methods. Calculates the filtered image's final width and height, records it for use in its imageComplete() method, creates a buffer to store the image data as it comes in, and calls the consumer's setDimensions() method to set the new width and height.
Tells the consumer to expect pixels in the default RGB color model.
Tells the consumer to expect the image data in top-down-left-right order (the order in which you're reading this page), in complete scan lines, and with every pixel sent exactly once.
setPixels() (both varieties of this method)
Converts the pixels to the default RGB model (if necessary) and copies the pixels into a storage buffer. Most image filters would simply modify the pixel data and forward it to the consumer, but because the sides of a rotated rectangle are no longer horizontal and vertical (for most angles), this filter can not efficiently forward pixels from its setPixels() method. Instead, RotateFilter stores all the pixel data until it receives an imageComplete() message.
Rotates the image and then invokes consumer.setPixels() repeatedly to send each line of the image to the consumer. After sending the whole image, this method invokes consumer.imageComplete().

Previous | Next | Trail Map | Creating a User Interface | Working with Graphics