This chapter covers the details of using the Canvas and Graphics classes. It also shows you how to use the image processing-related classes of the java.awt.image package. Java's support of bitmapped images is explained and the drawing methods of the Graphics class are illustrated as part of an example. When you finish this chapter, you will be able to effectively use graphics in your Java window programs.
The Canvas class provides a general GUI component for drawing images and text on the screen. It does not support any drawing methods of its own, but provides access to a Graphics object through its paint() method. The paint() method is invoked upon creation and update of the frame so that the Graphics object associated with a Canvas object can be updated. The paint() method should not be directly invoked, but it can be indirectly accessed using the repaint() method. The Canvas class is typically subclassed to provide custom drawing and event handling. If you do not want to create your own Canvas subclasses, you can use the Graphics object associated with your application's Frame subclass by overriding its paint() method. This is typically done in small applications.
The Graphics class is where all of the low-level drawing methods are implemented. These methods can be used directly to draw objects and text or can be combined to display more elaborate screen objects. The Graphics drawing methods allow a number of geometrical shapes to be drawn and filled, including lines, arcs, ovals, rectangles, rounded rectangles, and polygons. A special draw3DRect() method is provided for drawing rectangles that are shaded to give them a three-dimensional appearance. The Graphics class also provides the capability to draw bitmapped images and text on the canvas. Recall that Chapter 22, "Text and Fonts," covers the drawing of text and introduces the Font and FontMetrics classes. These classes control the specific manner in which text is displayed.
The drawImage() method of the Graphics class is used to display bitmapped images on the Graphics object associated with a canvas. It takes an object of the Image class, an object that implements the ImageObserver interface, the x,y-coordinates where the image is to be displayed, and other parameters as its arguments.
The Image class is an abstract class that provides format-independent access to graphical images. Image objects are created by invoking methods of other classes that create images. Examples of these image-creating methods are the createImage() methods of the Component and Applet classes and the getImage() methods of the Toolkit and Applet classes. The getImage() methods are the most handy methods for retrieving an image that is stored in a disk file. Java currently supports GIF and JPEG formatted images through these methods.
The ImageObserver interface is defined in the java.awt.image package. This interface provides a set of constants and methods that support the creation and loading of images. The Component class implements the ImageObserver interface, and in most cases, the ImageObserver object used as the parameter to the drawImage() method can be supplied using the this identifier to reference the current Canvas or Frame object being painted.
The DisplayImageApp program shows how bitmapped images can be drawn on the screen using the drawImage() method of the Graphics class. Its source code is shown in Listing 23.1.
Listing 23.1. The source code for the DisplayImageApp program.import java.awt.*;
import jdg.ch20.*;
public class DisplayImageApp extends Frame {
MyMenuBar menuBar;
Toolkit toolkit;
int screenWidth = 400;
int screenHeight = 400;
Image image;
public static void main(String args[]){
DisplayImageApp app = new DisplayImageApp();
}
public DisplayImageApp() {
super("DisplayImageApp");
setup();
pack();
resize(screenWidth,screenHeight);
show();
}
void setup() {
setBackground(Color.white);
setupMenuBar();
setMenuBar(menuBar);
setupImage();
}
void setupMenuBar() {
String menuItems[][] = {{"File","Exit"}};
menuBar = new MyMenuBar(menuItems);
}
void setupImage() {
toolkit = getToolkit();
image = toolkit.getImage("test.gif");
}
public void paint(Graphics g) {
g.drawImage(image,0,0,this);
}
public boolean handleEvent(Event event) {
if(event.id==Event.WINDOW_DESTROY){
System.exit(0);
return true;
}else if(event.id==Event.ACTION_EVENT){
if(event.target instanceof MenuItem){
if("Exit".equals(event.arg)){
System.exit(0);
return true;
}
}
}
return false;
}
}
Before running the DisplayImageApp program, copy the test.gif and aviris.gif images from the \jdg\ch23 directory of the CD-ROM to your jdg\ch23 directory. The DisplayImageApp program uses the test.gif file. The ImageApp program, which you'll develop later in this chapter, displays the aviris.gif image.
DisplayImageMap shows how a bitmapped image can be displayed using the Graphics class. When you run the program, it displays the bitmapped image shown in Figure 23.1.
Figure 23.1 : The DisplayImageApp opening window.
The functionality of the DisplayImageApp program isn't all that astounding. Its purpose is to illustrate the use of the methods involved in loading and displaying image files. You can easily upgrade the program to display arbitrary GIF or JPEG files by adding and implementing an Open option on the File menu.
DisplayImageApp declares several field variables. The menuBar variable is used to identify the program's menu bar. The toolkit variable is used to reference the toolkit associated with the application window. The screenWidth and screenHeight variables control the size at which the window is displayed. The image variable is used to refer to the loaded image.
The DisplayImageApp window is created, set up, and displayed using the methods covered in previous chapters. The setupImage() method uses the getToolkit() method of the Window class to get the Toolkit object associated with the application window. The getImage() method of the Toolkit class is used to load the image in the test.gif file and assign it to the image variable.
The paint() method draws the image referenced by the image variable on the default Graphics object of the application window. It accomplishes this using the drawImage() method of the Graphics class. The arguments to drawImage() are the image to be displayed, the x- and y-coordinates where the image is to be drawn, and the object implementing the ImageObserver interface associated with the image. The this identifier is used to indicate that the application window is the ImageObserver.
The handleEvent() method provides the standard WINDOW_DESTROY and Exit event handling.
Some programs, such as the Microsoft Windows Paint program, are used to construct images by painting on the screen. These paint programs create an image array of color pixels and update the array based on user paint commands. These commands may consist of pixel-level drawing operations or more general operations that draw geometrical objects such as circles, rectangles, and lines. Painting programs are characterized by the fact that the pixel array is the focus for the drawing that takes place.
Drawing programs, such as CorelDRAW!, support drawing operations using a more object-oriented approach. When you draw a circle or line with a drawing program, you do not merely update the pixels of the canvas-you add an object to the list of objects that are displayed on the canvas. Because drawing programs operate at a higher object level, you can select, move, resize, group, and perform other operations on the objects that you've drawn.
The Graphics class is oriented toward providing the methods that are needed to support higher-level drawing programs rather than lower-level painting programs, although it does support important painting operations, such as displaying bitmapped images, as you saw in the DisplayImageApp program.
When using the Graphics class to support graphical operations, you will generally maintain a list of the objects that you've drawn and use that list of objects to repaint the screen, as required.
The DrawApp program shows how the higher-level drawing operations of the Graphics class are used to display and maintain a list of the objects that are drawn on a canvas. The source code of the DrawApp program is shown in Listing 23.2.
Listing 23.2. The source code for the DrawApp program.import java.awt.*;
import java.lang.Math;
import java.util.Vector;
import jdg.ch20.*;
public class DrawApp extends Frame {
Object menuItems[][] = {
{"File","New","-","Exit"},
{"Draw","+Line","-Oval","-Rectangle"}
};
MyMenuBar menuBar = new MyMenuBar(menuItems);
MyCanvas canvas = new MyCanvas(TwoPointObject.LINE);
int screenWidth = 400;
int screenHeight = 400;
public static void main(String args[]){
DrawApp app = new DrawApp();
}
public DrawApp() {
super("DrawApp");
setup();
pack();
resize(screenWidth,screenHeight);
show();
}
void setup() {
setBackground(Color.white);
setMenuBar(menuBar);
setCursor(CROSSHAIR_CURSOR);
add("Center",canvas);
}
public boolean handleEvent(Event event) {
if(event.id==Event.WINDOW_DESTROY){
System.exit(0);
return true;
}else if(event.id==Event.GOT_FOCUS && !(event.target instanceof MyCanvas)) {
setCursor(DEFAULT_CURSOR);
return true;
}else if(event.id==Event.LOST_FOCUS) {
setCursor(CROSSHAIR_CURSOR);
return true;
}else if(event.id==Event.ACTION_EVENT){
if(event.target instanceof MenuItem){
String arg = (String) event.arg;
if(processFileMenu(arg)) return true;
if(processDrawMenu(arg)) return true;
}
}
return false;
}
public boolean processFileMenu(String s) {
if("New".equals(s)){
canvas.clear();
return true;
}else if("Exit".equals(s)){
System.exit(0);
return true;
}
return false;
}
public boolean processDrawMenu(String s) {
MyMenu menu = menuBar.getMenu("Draw");
CheckboxMenuItem lineItem = (CheckboxMenuItem) menu.getItem("Line");
CheckboxMenuItem ovalItem = (CheckboxMenuItem) menu.getItem("Oval");
CheckboxMenuItem rectangleItem =
(CheckboxMenuItem) menu.getItem("Rectangle");
if("Line".equals(s)){
canvas.setTool(TwoPointObject.LINE);
lineItem.setState(true);
ovalItem.setState(false);
rectangleItem.setState(false);
return true;
}else if("Oval".equals(s)){
canvas.setTool(TwoPointObject.OVAL);
lineItem.setState(false);
ovalItem.setState(true);
rectangleItem.setState(false);
return true;
}else if("Rectangle".equals(s)){
canvas.setTool(TwoPointObject.RECTANGLE);
lineItem.setState(false);
ovalItem.setState(false);
rectangleItem.setState(true);
return true;
}
return false;
}
}
class MyCanvas extends Canvas {
int tool = TwoPointObject.LINE;
Vector objects = new Vector();
TwoPointObject current;
boolean newObject = false;
public MyCanvas(int toolType) {
super();
tool = toolType;
}
public void setTool(int toolType) {
tool = toolType;
}
public void clear() {
objects.removeAllElements();
repaint();
}
public boolean mouseDown(Event event,int x,int y) {
current = new TwoPointObject(tool,x,y);
newObject = true;
return true;
}
public boolean mouseUp(Event event,int x,int y) {
if(newObject) {
objects.addElement(current);
newObject = false;
}
return true;
}
public boolean mouseDrag(Event event,int x,int y) {
if(newObject) {
int oldX = current.endX;
int oldY = current.endY;
if(tool != TwoPointObject.LINE) {
if(x > current.startX) current.endX = x;
if(y > current.startY) current.endY = y;
int width = Math.max(oldX,current.endX) - current.startX + 1;
int height = Math.max(oldY,current.endY) - current.startY + 1;
repaint(current.startX,current.startY,width,height);
}else{
current.endX = x;
current.endY = y;
int startX = Math.min(Math.min(current.startX,current.endX),oldX);
int startY = Math.min(Math.min(current.startY,current.endY),oldY);
int endX = Math.max(Math.max(current.startX,current.endX),oldX);
int endY = Math.max(Math.max(current.startY,current.endY),oldY);
repaint(startX,startY,endX-startX+1,endY-startY+1);
}
}
return true;
}
public void paint(Graphics g) {
int numObjects = objects.size();
for(int i=0;i<numObjects;++i) {
TwoPointObject obj = (TwoPointObject) objects.elementAt(i);
obj.draw(g);
}
if(newObject) current.draw(g);
}
}
class TwoPointObject {
public static int LINE = 0;
public static int OVAL = 1;
public static int RECTANGLE = 2;
public int type, startX, startY, endX, endY;
public TwoPointObject(int objectType,int x1,int y1,int x2,int y2) {
type = objectType;
startX = x1;
startY = y1;
endX = x2;
endY = y2;
}
public TwoPointObject(int objectType,int x,int y) {
this(objectType,x,y,x,y);
}
public TwoPointObject() {
this(LINE,0,0,0,0);
}
public void draw(Graphics g) {
if(type == LINE) g.drawLine(startX,startY,endX,endY);
else{
int w = Math.abs(endX - startX);
int l = Math.abs(endY - startY);
if(type == OVAL) g.drawOval(startX,startY,w,l);
else g.drawRect(startX,startY,w,l);
}
}
}
The DrawApp program is quite a bit more sophisticated than the DisplayImageApp program with respect to the capabilities that it provides. When you run DrawApp you will see the opening window, which is shown in Figure 23.2.
Figure 23.2 : The DrawApp opening window.
The DrawApp program is initially configured to draw lines in its window. You can draw a line by clicking the left mouse button and dragging the mouse. When you have finished drawing a line, release the left mouse button and the drawn line will be completed. The coordinate where you press the left mouse button is the beginning of the line, and the coordinate where you release the left mouse button is the end of the line. Go ahead and draw several lines, as shown in Figure 23.3.
Figure 23.3 : Drawing some lines.
The DrawApp program supports the drawing of lines, ovals, and rectangles. To draw an oval, select the Oval menu item from the Draw pull-down menu, as shown in Figure 23.4.
You draw an oval in the same way that you draw a line. When you click the left button of your mouse, you mark the upper-left corner of the oval. Drag the mouse to where you want the lower-right corner of the oval and release the left mouse button. Try drawing a few ovals, as shown in Figure 23.5.
Figure 23.5 : Drawing some ovals.
Now select the Rectangle menu item from the Draw pull-down menu. You draw rectangles in the same way that you draw ovals. Go ahead and draw a rectangle, as shown in Figure 23.6.
Figure 23.6 : Drawing some rectangles.
You can experiment with the program before going on to find out how it works. If you want to clear the drawing screen, select New from the File pull-down menu.
The DrawApp program is a little (but not much) longer than the programs you've developed so far. It consists of three classes. The DrawApp class is the main class used to implement the program. The MyCanvas class is used to implement the main canvas component of the program. The TwoPointObject class is used to implement the line, oval, and rectangle objects that are drawn on the screen. It is called TwoPointObject because it supports objects that can be characterized by a starting point (mouse down) and an ending point (mouse up).
The DrawApp program declares several field variables. The menuItems[] array is used to construct the menu bar identified by the menuBar variable. The canvas variable is used to refer to the MyCanvas object that implements the program drawing. This object is constructed by passing the TwoPointObject.LINE constant as an argument. This tells the constructed object that the line tool should be initially used to support drawing. The height and width of the DrawApp window is set to 400¥400 pixels.
The DrawApp window is constructed using the standard approach that you've been following in previous chapters. The setup() method sets the background color to white, invokes setupMenuBar() to set up the menu bar, and invokes the setCursor() method of the Frame class to set the initial cursor to the CROSSHAIR_CURSOR typically used in drawing programs. The MyCanvas object referenced by the canvas variable is then added to the center of the main application window.
That's all the setup required to support DrawApp. The rest of the program code provides the event handling required to implement the drawing operations that were previously illustrated.
The handleEvent() method handles the WINDOW_DESTROY event in the usual manner. The GOT_FOCUS and LOST_FOCUS methods check to see whether the current position of the cursor changes focus from the canvas to other parts of the window. If the cursor moves outside of the canvas, the cursor is changed to its default shape. If the cursor moves into the canvas, it is changed to a CROSSHAIR_CURSOR.
The only other type of event handled by DrawApp is the ACTION_EVENT associated with user menu selections. The processFileMenu() and processDrawMenu() methods are invoked to handle these events.
The processFileMenu() method processes the New and Exit menu items. The New menu item is handled by invoking the clear() method of the MyCanvas class to clear the canvas to a blank state. The Exit menu item is handled in the usual manner.
The processDrawMenu() method begins by assigning the CheckboxMenuItem objects in the Draw menu to the lineItem, ovalItem, and rectangleItem variables. These variables are used to determine which menu items are checked.
The processDrawMenu() method handles the Line menu item by invoking the setTool() method to set the current drawing tool to the line tool. It uses the LINE constant defined in the TwoPointObject class. The lineItem, ovalItem, and rectangleItem variables are used to update the CheckboxMenuItem objects contained in the Draw menu using the setState() method of the CheckboxMenuItem class. The Oval and Rectangle menu items are handled in a similar manner. The Oval menu item is handled by invoking the setTool() method with the OVAL constant, and the Rectangle menu item is handled by invoking the setTool() method with the RECTANGLE constant. The state of the menu items of the Draw menu are updated to reflect the selected drawing tool.
The MyCanvas class subclasses the Canvas class to provide custom drawing capabilities. The tool variable is used to identify the current drawing tool that is in effect. The objects variable is declared as a Vector. It is used to store all of the objects drawn by the user. The current variable is used to refer to the current TwoPointObject object being drawn by the user. The newObject flag is used to track whether the user has begun drawing a new object.
The MyCanvas constructor invokes the constructor of the Canvas class using the superclass constructor call statement and then sets the tool variable to the toolType argument passed to the constructor.
The setTool() method changes the tool used to draw an object.
The clear() method invokes the removeAllElements() method of the vector class to remove all drawing objects stored in the Vector referenced by the objects variable.
The mouseDown() method is used to handle the event that is generated when the user clicks the left mouse button in the canvas. The method is called by the Java runtime system with the position of the mouse click. A new TwoPointObject object is created, with the tool variable and the position of the mouse click as its arguments. The newly created object is assigned to the current variable. The newObject flag is set to true and the true value is returned to indicate that the event has been successfully handled.
The mouseUp() method is used to handle the event that is generated when the user releases the left mouse button. This action marks the completion of the drawing of an object. The event is handled by adding the object referenced by the current variable to the objects vector. The newObject flag is then set to false. The object referenced by the current variable is updated with its ending position during the processing of the mouseDrag() event-handling method. The newObject flag is checked to make sure that the mouse was not clicked outside of the current window and then released.
The mouseDrag() event performs somewhat more sophisticated event handling than the mouseDown() and mouseUp() methods perform. It checks the newObject flag to make sure that an object is currently being drawn. It then sets the oldX and oldY variables to the last ending position of the object being drawn. These variables will be used to determine what portion of the canvas needs to be repainted. Repainting of the entire canvas is not visually appealing because it causes previously drawn objects to flicker.
If the current drawing tool is not a line, an oval or rectangle object is being drawn by the user. The x- and y-coordinates provided as arguments to the mouseDrag() method are checked to determine whether the mouse was dragged below and to the right of the object being drawn. If this is the case, the ending position of the current object is updated. If the mouse is dragged to the left or above the starting point of the object, the current position of the mouse is ignored. This is to ensure that the starting position of the oval or rectangle is indeed its upper-left corner. The new width and height of the area to be repainted are calculated as the maximum area covered by the previous ending position and the current object-ending position. This is to ensure that the repaint operation will erase any previous boundaries of the object being drawn. The max() method of the java.lang.Math class is used to determine this maximum area. The repaint() method of the Component class is then used to repaint the area updated as the result of the mouse drag. This version of the repaint() method takes the x,y-coordinates of the upper-left corner of the area to be redrawn and the width and height of this area as its parameters.
Line drawing is not restricted in the same manner as oval and rectangle drawing. If it were, you would not be able to draw lines that go up and to the right or down and to the left. The else part of the if statement updates the starting position of the area to be repainted as the upper- leftmost point of the line being redrawn. It then updates the ending position of the area to be repainted as the lower rightmost point of the line. The canvas is then repainted using the starting coordinates and the updated width and height of the repaint area.
To get a better feel for the process of local screen repainting, try experimenting with the way the repaint() method is used to update the canvas display.
The paint() method is used to paint and repaint the screen. It uses the size() method of the Vector class to determine how many objects are stored in the objects vector and sets the numObjects variable to this value. It then iterates through each object stored in objects and draws it on the canvas. The elementAt() method of the Vector class is used to retrieve an object from the objects vector. The object is cast into an object of class TwoPointObject and assigned to the obj variable. The draw() method of the TwoPointObject class is invoked to draw the object on the current Graphics context.
Notice that the paint() method does not have to know how to support limited-area repainting. Only full-canvas painting needs to be implemented by paint(). Support of limited-area repainting is provided by the local AWT implementation.
The TwoPointObject class is used to keep track of the objects drawn by the user. It records the type of object and its starting and ending coordinates. It also draws the objects on a Graphics object passed as a parameter.
TwoPointObject defines the LINE, OVAL, and RECTANGLE constants, which are also used by the MyCanvas class. The type variable is used to record the type of object being drawn. The startX, startY, endX, and endY variables identify the starting and ending coordinates of the object.
Three TwoPointObject constructors are declared. The first constructor takes the type of object being drawn and its starting and ending coordinates as its parameters. The second constructor leaves out the ending coordinates and sets them to be the same as the starting coordinates. The last constructor takes no parameters and creates a line that is at the coordinate 0,0.
The draw() method checks the type variable to determine what type of object is to be drawn. If the object is a line, it uses the drawLine() method of the Graphics class to draw a line from its starting to ending coordinates. If the object is an oval or a line, the w and l variables are assigned the width and length of the object to be drawn. The drawOval() and drawRect() methods are used to draw an oval or rectangle, respectively.
The Graphics class treats text in the same way as it handles other graphics objects. To include text drawing in your graphics applications, use the drawString() method of the Graphics class, as illustrated in Chapter 22. You will need to use the Font and FontMetrics classes to determine the size of the text that is drawn. Otherwise, text and graphics objects can be easily combined in any Graphics-based application.
The java.awt.image package provides a number of classes and interfaces that support image processing. These classes are described in Chapter 15, "Window Programming with the java.awt Package." For the most part, you will not need to use these classes unless your application program is oriented toward low-level image processing.
The java.awt.image package is based on the concept of an image producer and image consumer. The image producer provides the data associated with an image. This data is used or consumed by an image consumer. The ImageProducer and ImageConsumer interfaces are used to map this producer-consumer concept to specific image-processing classes.
An image filter is used to alter data that is produced by an image producer before it is consumed by an image consumer. Image filters are similar to the I/O stream filters discussed in Chapter 12, "Portable Software and the java.lang Package." An image filter reads the data produced by an image producer, modifies it, and then passes it on to the image consumer.
The ImageApp program shows how to perform image processing using a custom-built image filter. Its source code is shown in Listing 23.3.
Listing 23.3. The source code for the ImageApp program.import java.awt.*;
import java.awt.image.*;
import jdg.ch20.*;
public class ImageApp extends Frame {
MyMenuBar menuBar;
Toolkit toolkit;
int screenWidth = 500;
int screenHeight = 475;
Image filteredImage;
public static void main(String args[]){
ImageApp app = new ImageApp();
}
public ImageApp() {
super("ImageApp");
setup();
pack();
resize(screenWidth,screenHeight);
show();
}
void setup() {
setBackground(Color.white);
setupMenuBar();
setMenuBar(menuBar);
setupImage();
}
void setupMenuBar() {
String menuItems[][] = {{"File","Exit"},{"Filter","-Red","-Green","-Blue"}};
menuBar = new MyMenuBar(menuItems);
}
void setupImage() {
toolkit = getToolkit();
filteredImage = toolkit.getImage("aviris.gif");
}
public void paint(Graphics g) {
g.drawImage(filteredImage,0,0,this);
}
public void filterImage(){
Image image = toolkit.getImage("aviris.gif");
ImageFilter filter = new MyImageFilter(getMask());
filteredImage =
createImage(new FilteredImageSource(image.getSource(),filter));
repaint();
}
public int getMask() {
int red = 0xff00ffff;
int green = 0xffff00ff;
int blue = 0xffffff00;
int mask = 0xffffffff;
MyMenu menu = menuBar.getMenu("Filter");
CheckboxMenuItem redItem = (CheckboxMenuItem) menu.getItem("Red");
CheckboxMenuItem greenItem = (CheckboxMenuItem) menu.getItem("Green");
CheckboxMenuItem blueItem = (CheckboxMenuItem) menu.getItem("Blue");
if(redItem.getState()) mask &= red;
if(greenItem.getState()) mask &= green;
if(blueItem.getState()) mask &= blue;
return mask;
}
public boolean handleEvent(Event event) {
if(event.id==Event.WINDOW_DESTROY){
System.exit(0);
return true;
}else if(event.id==Event.ACTION_EVENT){
if(event.target instanceof MenuItem){
String sel = (String) event.arg;
if("Exit".equals(sel)){
System.exit(0);
return true;
}else if("Red".equals(sel) || "Green".equals(sel) || "Blue".equals(sel)) {
filterImage();
return true;
}
}
}
return false;
}
}
class MyImageFilter extends RGBImageFilter {
int filter;
public MyImageFilter(int mask) {
canFilterIndexColorModel = true;
filter = mask;
}
public int filterRGB(int x,int y,int rgb) {
return rgb & filter;
}
}
When you first run ImageApp, it loads the image contained in the file aviris.gif. (See Figure 23.7.) This is a public-domain image provided by the NASA Jet Propulsion Laboratory. It is produced by the Airborne Visible InfraRed Imaging Spectrometer (AVIRIS). The aviris.gif file is fairly large; you might have to wait a couple seconds for it to complete its loading.
Figure 23.7 : The ImageApp opening window.
The AVIRIS image is not provided to introduce you to NASA's advanced airborne-imaging algorithms. Instead, you will use this image as an example to understand how basic image filtering works. Unfortunately, the images displayed in this book are in black and white, so you will not be able to see how the image filtering works by looking at the book.
Click on the Filter pull-down menu and select the Blue menu item, as shown in Figure 23.8.
Figure 23.8 : The Filter menu.
Selecting the blue filter causes all blue color components to be filtered out of the image. The resulting image is comprised only of green and red color components. (See Figure 23.9.)
Figure 23.9 : A filtered image.
Go ahead and try the red and green filters by selecting Red and Green from the Filter menu. Also try various filter combinations to get a better feel for how filtering works.
Although the ImageApp program may seem to perform some amazing processing, it is actually quite small. Two classes are defined: the ImageApp class, used to implement the main program window, and the MyImageFilter class, used to implement the actual image filter.
The ImageApp class declares the menuBar, toolkit, screenWidth, screenHeight, and filteredImage variables. The menuBar, screenWidth, and screenHeight variables are used in their usual manner. The toolkit variable is used to refer to the Toolkit object associated with the application window. The filteredImage variable is used to refer to the image that is being manipulated.
The setupImage() method uses the getToolkit() method of the Window class to retrieve the toolkit that is associated with the application window. It then invokes the getImage() method of the Toolkit class to load the image contained in the file into an Image object that is referenced by the filteredImage variable.
The paint() method draws the image identified by the filteredImage variable on the screen using the drawImage() method of the Graphics class.
The filterImage() method oversees the image-filtering process. It loads the aviris.gif image into an object assigned to the image variable. It creates a new object of the MyImageFilter class and assigns it to the filter variable. The MyImageFilter object is provided with a filter mask that is generated by a call to the getMask() method. You'll learn what a filter mask is shortly.
The filterImage() method uses the createImage() method of the Component class to create a new image and assign it to the filteredImage variable. The repaint() method is invoked to redisplay the new image assigned to the filteredImage variable. The actual image filtering is performed as part of the creation of the arguments supplied to the createImage() method. When createImage() is invoked, a new object of the java.awt.image.FilteredImageSource class is created. This object assigns the source (ImageProducer) of the image being created to the newly created FilteredImageSource object. This object is created using the source (ImageProducer) of the aviris.gif image assigned to the image variable. The getSource() method is invoked to get the ImageProducer of the original image. The FilteredImageSource object is filtered using the MyImageFilter object assigned to the filter variable. To complete this examination of the image-filtering process, you only need to figure out how the MyImageFilter class works and what was returned by the getMask() method.
The getMask() method returns an integer value that is used to mask out certain RGB color combinations. The red variable is assigned the 0xff00ffff hexadecimal constant. The red component of an RGB color is stored in the bits that are set to zero in this constant. So, when you logically AND this value with any color, its red bits are stripped out. The hexadecimal constants assigned to the green and blue variables are defined in an analogous fashion to strip out the green and blue bits of a color when they are logically ANDed with the color. The mask variable is used to compute the returned result. The constant assigned to the mask variable will have no effect on any color that it is ANDed with.
The getMask() method checks each of the CheckboxMenuItem objects contained in the Filter menu to determine which objects are set. If a menu item is set, the color mask associated with that item is logically ANDed with the mask variable. The resulting mask value is a value that will strip out the colors specified by the set menu items.
The handleEvent() method performs the normal WINDOW_DESTROY and Exit menu item processing. It also handles the selection of the Red, Green, and Blue menu items by invoking the filterImage() method. This causes the aviris.gif image to be filtered as specified by the mask generated according to the state of the Red, Green, and Blue checkbox menu items.
The MyImageFilter class performs the actual image filtering. It extends the RGBImageFilter class defined in the java.awt.image package and overrides the filterRGB() method.
The MyImageFilter constructor takes a mask value as its parameter and assigns it to the filter variable. It also sets the canFilterIndexColorModel variable to true. This allows filtering to take place on the color map associated with the image, rather than on the actual image.
The filterRGB() method performs the image filtering. It takes the x,y-coordinate of each pixel to be filtered and the RGB color of the pixel as its parameters. It then logically ANDs the color value with the mask stored in the filter variable and returns the resulting filtered color.
This chapter covers the details of using the Canvas and Graphics classes. It also shows you how to use the image processing-related classes of the java.awt.image package. Java's support of bitmapped images is demonstrated with the DisplayImageApp program. The DrawApp program illustrates the drawing methods of the Graphics class, and the ImageApp program shows you how to use the classes of java.awt.image. Chapter 24, "Scrollbars," shows you how to use scrollbars to scroll text and graphics drawn on the canvas.