In this chapter you will learn how to augment the appearance of your Java program by using graphics. Java provides a complete set of classes for drawing lines and geometric shapes such as arcs, ovals, rectangles, and polygons. You will also learn how to display and manipulate images. You will learn about Java's classes for filtering, cropping, and modifying images. You will also learn about the ImageObserver interface that allows an applet to monitor the progress of an image that is being retrieved for display.
In this section you will learn about the Graphics class that is included in the java.awt package. You've already seen the Graphics class used in many of the examples in this book. For example, in Chapter 2 "Creating Your First Applet with Applet Wizard," the following code was used to display "Hello, World" on the screen:
public void paint(Graphics g)
{
g.drawString("Hello, World", 10, 10);
}
As you would expect of a class named Graphics, it can be used to do much more than draw strings. The Graphics class can be used to draw a variety of shapes. Figure 14.1 illustrates a very simple screen that was created with member methods of the Graphics class.
Figure 14.1 : A very simple example of drawing filled and outlined shapes.
The Graphics class includes methods for drawing both
filled and outlined shapes. An outlined shape is just the outline
of the shape; a filled shape is the outline plus the area bounded
by the outline. The bounded area is filled by painting it with
the current color.
| NOTE |
The use of color is discussed in Chapter 16, "Sprucing Things Up with Colors and Fonts. |
A variety of shapes can be drawn using the Graphics class,
as shown in Table 14.1. As you can see from this table, all shapes
except a line can be drawn both filled and outlined. Because a
line always has a width of one, there is no need to draw filled
lines.
| Shape | ||
| Arc | ||
| Line | ||
| Oval | ||
| Polygon | ||
| Rectangle | ||
| Round Rectangle | ||
| 3-D Rectangle |
When you use any of these methods to draw a shape, you pass it the coordinates at which the shape is to be drawn. As far as the Graphics class is concerned, coordinate numbering starts at 0,0 in the top left and increases down and to the right. This can be seen in Figure 14.2.
One of the simplest shapes to draw is a line. A line can be drawn using the drawLine member of the Graphics class. The signature of drawLine is as follows:
public abstract void drawLine(int x1, int y1, int x2, int y2);
This method will draw a line between the points given by (x1, y1) and (x2, y2). As an example, imagine you have been asked to display the image shown in Figure 14.3. This drawing can be created with the code shown in Listing 14.1.
Figure 14.3 : A simple figure created with drawLine.
Listing 14.1. EX14A.java.
import java.applet.*;
import java.awt.*;
public class EX14A extends Applet
{
public void init()
{
resize(320, 240);
}
public void paint(Graphics g)
{
// draw the three horizontal lines
g.drawLine(20, 20, 220, 20);
g.drawLine(20, 70, 220, 70);
g.drawLine(20, 120, 220, 120);
// draw the vertical lines at each end
g.drawLine(20, 20, 20, 120);
g.drawLine(220, 20, 220, 120);
// draw the vertical line in the top half
g.drawLine(120, 20, 120, 70);
// draw two vertical lines in the bottom half
g.drawLine( 86, 70, 86, 120);
g.drawLine(154, 70, 154, 120);
}
}
| NOTE |
Figure 14.3 was first presented to me in the form of a puzzle: Starting from a point inside or outside the figure, draw a single line that crosses each and every line segment of the figure exactly once. On a day when I must have been a particularly annoying seven-year-old, my dad drew this puzzle for me and offered me $20 if I could solve it. I couldn't, even though I occasionally returned to it for nearly 15 more years. It wasn't until college when I learned about Euler and the Bridges of Knigsberg that I discovered the puzzle was unsolvable. However, it did solve my dad's problem of keeping a seven-year-old quiet in the back of a car |
There are more methods for drawing rectangles than I have fingers on one hand. There are six. Fortunately, I could count them all on two hands and didn't need to use my toes or this book would have had an unpleasant odor. Of the six methods for drawing rectangles, three draw filled rectangles and three draw outlined rectangles. Methods whose names begin with draw are used to create outlined rectangles; methods whose names begin with fill are used to create filled rectangles. The following methods are available:
public void drawRect(int x, int y, int width, int height);
public abstract void fillRect(int x, int y, int width, int height);
public void draw3DRect(int x, int y, int width, int height,
boolean raised);
public void fill3DRect(int x, int y, int width, int height,
boolean raised);
public abstract void drawRoundRect(int x, int y, int width,
int height, int arcWidth, int arcHeight);
public abstract void fillRoundRect(int x, int y, int width,
int height, int arcWidth, int arcHeight);
A simple rectangle, as drawn by drawRect and fillRect, requires only parameters for the starting x and y coordinates and the width and height of the rectangle. A three-dimensional rectangle can be painted with draw3DRect or fill3DRect by specifying an additional parameter that indicates whether the rectangle should appear raised or lowered. The three-dimensional effect is achieved by brightening or darkening the color of the rectangle when it is drawn. Because of this, you must use setColor prior to painting the rectangle or the three-dimensional effect will not be apparent.
Finally, a rectangle with rounded corners can be painted with drawRoundRect or fillRoundRect. These methods require two additional parameters that specify the width and height of the arc that is used to draw the corners. Larger values for these parameters will create more rounded corners.
As an example of how these methods may be used, consider the code in Listing 14.2. This code will create the screen shown in Figure 14.4.
Figure 14.4 : Examples of the various rectangle methods.
Listing 14.2. EX14B.java illustrates how to draw rectangles.
import java.applet.*;
import java.awt.*;
public class EX14B extends Applet
{
public void paint(Graphics g)
{
// draw an outlined rectangle
g.drawRect(10, 10, 200, 200);
g.drawString("drawRect", 10, 225);
// draw a filled rectangle
g.fillRect(15, 15, 30, 60);
g.drawString("fillRect", 15, 90);
// set a color so that the 3D rectangles
// are displayed as raised or indented
g.setColor(Color.cyan);
// draw an outlined 3D rectangle
g.draw3DRect(60, 15, 40, 10, false);
g.drawString("draw3DRect", 60, 40);
// draw a filled 3D rectangle
g.fill3DRect(140, 15, 30, 20, true);
g.drawString("fill3DRect", 140, 50);
// draw an outlined round rectangle
g.drawRoundRect(20, 110, 40, 60, 20, 40);
g.drawString("drawRoundRect", 20, 185);
// draw a filled round rectangle
g.fillRoundRect(120, 110, 60, 60, 20, 20);
g.drawString("fillRoundRect", 120, 185);
}
public void init()
{
resize(320, 240);
}
}
You've already used arcs to create the rounded corners of a rectangle drawn with drawRoundRect or fillRoundRect. You can also create an arc on its own. Arcs are created with the drawArc or fillArc methods whose signatures are the following:
public abstract void drawArc(int x, int y, int width, int height,
int startAngle, int arcAngle);
public abstract void fillArc(int x, int y, int width, int height,
int startAngle, int arcAngle);
As with the rectangle methods, the method whose name begins with draw will paint an outlined shape, and the method whose name begins with fill will paint a filled shape. To paint an arc you specify the position and size of an imaginary rectangle that bounds the arc. You also specify the starting angle of the arc and the number of degrees in the arc. This can be seen in Figure 14.5.
Figure 14.5 : An arc is specified by describing an imaginary rectangle surrounding it.
Figure 14.5 shows an arc and also uses drawRectangle to explicitly paint the invisible rectangle that surrounds the arc. The rectangle and arc of this example were created with the following two statements:
g.drawArc(60, 30, 100, 200, 45, 180); g.drawRect(60, 30, 100, 200);
The first four parameters to drawArc indicate a rectangle that starts at (60, 30) is 100 pixels wide, and 200 pixels high. These same four values are passed to drawRect to explicitly draw the rectangle. The final two parameters to drawArc indicate that the arc should begin at 45 degrees and end 180 degrees later (at 225 degrees). As you can see in Figure 14.5, the 0 degree position is at three o'clock. Because the final parameter is a positive number, the arc will be drawn in a counter-clockwise direction. A negative number causes the arc to be drawn in a clockwise direction. This can be seen in Figure 14.6, which shows the same arc when drawn in the opposite direction with the following code:
Figure 14.6 : The same arc when drawn in a clockwise direction.
g.drawArc(60, 30, 100, 200, 45, -180);
Class EX14C, shown in Listing 14.3, demonstrates how to use drawArc and fillArc. Three rows of arcs are painted, as can be seen in Figure 14.7. The first row shows four arcs drawn with counterclockwise angles. The second row shows the same four arcs drawn in a clockwise direction. The final row shows two filled arcs with starting angles other than 0.
Figure 14.7 : Sample arcs painted with drawArc and fillArc.
Listing 14.3. EX14C.java.
import java.applet.*;
import java.awt.*;
public class EX14C extends Applet
{
public void init()
{
resize(320, 240);
}
public void paint(Graphics g)
{
// draw arcs with counterclockwise angles
g.drawArc(10, 60, 20, 50, 0, 90);
g.drawArc(60, 60, 20, 50, 0, 180);
g.drawArc(110, 60, 20, 50, 0, 270);
g.drawArc(160, 60, 20, 50, 0, 360);
// draw arcs with clockwise angles
g.drawArc(10, 120, 20, 50, 0, -90);
g.drawArc(60, 120, 20, 50, 0, -180);
g.drawArc(110, 120, 20, 50, 0, -270);
g.drawArc(160, 120, 20, 50, 0, -360);
// draw filled arcs that don't start at 0
g.fillArc(10, 180, 20, 50, 45, 180);
g.fillArc(60, 180, 20, 50, 0, 135);
}
}
Drawing an oval is very similar to drawing a regular rectangle. An oval can be painted using either of the following methods:
public abstract void drawOval(int x, int y,int width,int height); public abstract void fillOval(int x, int y,int width,int height);
As is consistent with the other member methods of Graphics, drawOval paints an outlined oval and fillOval paints a filled oval. As with arcs, the parameters passed to these methods describe an imaginary rectangle surrounding the oval. As an example of painting ovals, consider class EX14D, shown in Listing 14.4. This class paints a large outlined oval and then paints a smaller filled oval inside the larger oval. The results of executing this class can be seen in Figure 14.8.
Figure 14.8 : Sample ovals painted with drawOval and fillOval.
Listing 14.4. EX14D.java.
import java.applet.*;
import java.awt.*;
public class EX14D extends Applet
{
public void init()
{
resize(520, 400);
}
public void paint(Graphics g)
{
// draw an outlined oval
g.drawOval(10, 10, 225, 300);
g.drawString("drawOval", 102, 323);
// draw a filled oval within the outlined oval
g.fillOval(73, 120, 100, 100);
g.drawString("fillOval", 105, 233);
}
}
If what you really want to draw is a non-standard shape that can't be painted with one of the methods described so far, you may be able to use the polygon drawing methods. As usual, methods are provided for painting outlined and filled shapes. The following four methods can be used to paint polygons:
public abstract void fillPolygon(int xPoints[], int yPoints[],
int nPoints);
public abstract void drawPolygon(int xPoints[], int yPoints[],
int nPoints);
public void fillPolygon(Polygon p);
public void drawPolygon(Polygon p);
The first two of these methods are each passed two arrays that represent the x and y positions of the points on the polygon and an integer. As an example of how this works, consider the following:
int xPoints[] = new int[4]; int yPoints[] = new int[4]; xPoints[0] = 150; xPoints[1] = 150; xPoints[2] = 250; xPoints[3] = 150; yPoints[0] = 150; yPoints[1] = 250; yPoints[2] = 250; yPoints[3] = 150; g.fillPolygon(xPoints, yPoints, 4);
In this case two arrays are allocated to hold four items each. The corners of the polygon are given by the matched pairs of the arrays. For example, (xPoints[0], yPoints[0]) and (xPoints[1], yPoints[1]) identify the first two of four corners on the polygon.
The other two methods for drawing polygons are passed a polygon as their lone parameter. These methods are more convenient if you already have a Polygon object. The following code is equivalent to the prior example but passes a Polygon to fillPolygon instead of passing the arrays:
int xPoints[] = new int[4]; int yPoints[] = new int[4]; xPoints[0] = 150; xPoints[1] = 150; xPoints[2] = 250; xPoints[3] = 150; yPoints[0] = 150; yPoints[1] = 250; yPoints[2] = 250; yPoints[3] = 150; Polygon p = new Polygon(xPoints, yPoints, 4); g.fillPolygon(p);
As a further example of how the fillPolygon and drawPolygon methods may be used, consider example EX14E, which is shown in Listing 14.5. This example draws three shapes, a four-sided polygon, and two triangles, as shown in Figure 14.9.
Figure 14.9 : Sample polygons painted with drawPolygon and fillPolygon.
Listing 14.5. EX14E.java.
import java.applet.*;
import java.awt.*;
public class EX14E extends Applet
{
public void init()
{
resize(520, 400);
}
public void paint(Graphics g)
{
drawFirstPolygon(g);
drawSecondPolygon(g);
drawThirdPolygon(g);
}
private void drawFirstPolygon(Graphics g)
{
int xPoints[] = new int[5];
int yPoints[] = new int[5];
xPoints[0] = 60;
xPoints[1] = 100;
xPoints[2] = 150;
xPoints[3] = 110;
xPoints[4] = 60;
yPoints[0] = 10;
yPoints[1] = 70;
yPoints[2] = 30;
yPoints[3] = 170;
yPoints[4] = 10;
g.drawPolygon(xPoints, yPoints, 5);
}
private void drawSecondPolygon(Graphics g)
{
int xPoints[] = new int[4];
int yPoints[] = new int[4];
xPoints[0] = 150;
xPoints[1] = 150;
xPoints[2] = 250;
xPoints[3] = 150;
yPoints[0] = 150;
yPoints[1] = 250;
yPoints[2] = 250;
yPoints[3] = 150;
g.fillPolygon(xPoints, yPoints, 4);
}
private void drawThirdPolygon(Graphics g)
{
int xPoints[] = new int[4];
int yPoints[] = new int[4];
xPoints[0] = 250;
xPoints[1] = 300;
xPoints[2] = 200;
xPoints[3] = 250;
yPoints[0] = 300;
yPoints[1] = 350;
yPoints[2] = 350;
yPoints[3] = 300;
Polygon p = new Polygon(xPoints, yPoints, 4);
g.drawPolygon(p);
}
}
In addition to geometric shapes and lines, you can also display graphics images, such as JPG or GIF files. To do this you use any of the four provided drawImage methods whose signatures are as follows:
public abstract boolean drawImage(Image img, int x, int y,
ImageObserver observer);
public abstract boolean drawImage(Image img, int x, int y,
int width, int height, ImageObserver observer);
public abstract boolean drawImage(Image img, int x, int y,
Color bgcolor, ImageObserver observer);
public abstract boolean drawImage(Image img, int x, int y,
int width, int height,Color bgcolor,ImageObserver observer);
The first of these methods is passed the image itself, the coordinates of its top-left corner, and an ImageObserver. ImageObserver is an interface that is implemented by the Component class. Because Applet is a subclass of Component you can use any class you derive from Applet as an ImageObserver. ImageObservers are useful because they can be sent information about the image as it is being loaded. Because Applet implements the ImageObserver interface, you can pass this as the ImageObserver parameter. This can be seen in the following code fragment:
public class MyClass extends Applet
{
// ...other methods here
public void paint(Graphics g)
{
g.drawImage(myImage, 100, 100, this);
}
}
The second drawImage method is passed parameters for width and height. This causes the image to be scaled so that it appears in the specified rectangle. The final two drawImage methods are the same as the first two with the addition of being able to specify a background color for the image.
However, before you can use drawImage you must have an Image object to draw. As you already learned in Chapter 4, "Applet Programming Fundamentals," this can be done with the getImage method of the Applet class. Typically, this is done in the init method of the applet, as follows:
public class MyClass extends Applet
{
Image myImage;
// ...other methods here
public void init()
{
myImage = getImage(getDocumentBase(), "savannah.jpg");
}
}
As an example of displaying images, consider EX14F, as shown in Listing 14.6. Note that since the Image class is in java.awt.image, this package must be imported. In the init method of EX14F, getImage is used to create the Image object, myImage. In the paint method this image is displayed twice. The first use of drawImage specifies a square from point (0, 0) to point (150, 150). The image will be resized and displayed in this area. This can be seen in Figure 14.10. The second use of drawImage specifies only the coordinates of the top left of the image.
Figure 14.10 : The same image displayed with two different drawImage methods.
Listing 14.6. EX14F.java displays a simple image.
import java.applet.*;
import java.awt.*;
import java.awt.image.*;
public class EX14F extends Applet
{
private Image myImage;
public void init()
{
resize(620, 470);
// create the image
myImage = getImage(getDocumentBase(), "savannah.jpg");
}
public void paint(Graphics g)
{
// paint the image in the specified rectangle
g.drawImage(myImage, 0, 0, 150, 150, this);
// paint the image on the screen
g.drawImage(myImage, 160, 0, this);
}
}
It is also possible to create and use filters on images before they are displayed. The class CropImageFilter is provided in the java.awt.image package and is a useful filter that enables you to display only a portion of the image. Class EX14G, shown in Listing 14.7, illustrates how to use CropImageFilter.
Listing 14.7. EX14G.java.
import java.applet.*;
import java.awt.*;
import java.awt.image.*;
public class EX14G extends Applet
{
private Image myImage;
private Image croppedImage;
public void init()
{
resize(630, 470);
myImage = getImage(getDocumentBase(), "savannah.jpg");
// create a filter that will crop the image to the
// area starting at point (195, 0) with a width of 140
// and a height of 150
CropImageFilter myCropFilter = new CropImageFilter(195,0,
140, 150);
// create a new image source based on the original image
// and using the newly created filter
FilteredImageSource imageSource = new
FilteredImageSource(myImage.getSource(),
myCropFilter);
// create the cropped image
croppedImage = createImage(imageSource);
}
public void paint(Graphics g)
{
// paint the cropped image
g.drawImage(croppedImage, 0, 0, this);
// paint the original image to the right of the cropped one
g.drawImage(myImage, 160, 0, this);
}
}
After using getImage to create the Image object, the init method of EX14G constructs a new CropImageFilter object named myCropFilter. This constructor is passed the x and y coordinates, and the width and height of where to start cropping the image. In this case, a rectangle 140 pixels wide and 150 high will be cropped beginning at point (195, 0). Next, the constructor for FilteredImageSource is used to create a new image source. The image source is based on the original image and myCropFilter. Finally, createImage is used to return the actual Image object.
This work is put to use in the paint method. This method uses drawImage first to display the cropped image and then to display the original image for comparison. The results can be seen in Figure 14.11.
Figure 14.11 : The CropImageFilter can be used to crop images.
It is also possible to create your own filter. To do so you create a subclass of either ImageFilter or RGBImageFilter and override methods to provide the filtering you want. The RGBImageFilter class is a subclass of ImageFilter and makes it very easy to write a filter that manipulates the colors of the individual pixels in an image.
As an example of how you can create your own filter based on RGBImageFilter, imagine you needed to write a filter that will remove part of an image. This could be very useful in a litigation support system for processing documents. In this type of system it is important to be able to redact, or black out, text. Or, as you'll see in this example, a redaction filter is useful for hiding the eyes of a Mafia informant.
Listing 14.8 shows EX14H. This example illustrates the use of a redaction filter, as shown in Figure 14.12. The class RedactFilter is defined as a subclass of RGBImageFilter. Its constructor is passed values that indicate the area to be redacted.
Figure 14.12 : The RedactFilter can be used to conceal the identity of a Mafia informant.
Listing 14.8. EX14H.java illustrates the creation of a custom filter.
import java.applet.*;
import java.awt.*;
import java.awt.image.*;
class RedactFilter extends RGBImageFilter
{
int startX, startY, endX, endY;
// the constructor is passed the coordinates of the area
// to redact and stores these values
public RedactFilter(int x, int y, int width, int height)
{
startX = x;
startY = y;
endX = startX + width;
endY = startY + height;
}
public int filterRGB(int x, int y, int rgb)
{
// if the (x,y) position is in the redacted area
// return red, otherwise return the same color that
// was passed in
if (x >= startX && x <= endX && y >= startY && y <= endY)
return 0xff0000ff;
else
return rgb;
}
}
public class EX14H extends Applet
{
private Image myImage;
private Image redactedImage;
public void init()
{
resize(570, 470);
// get the original image
myImage = getImage(getDocumentBase(), "savannah.jpg");
// create a filter and specify the range to be redacted
ImageFilter filter = new RedactFilter(220, 80, 80, 15);
// create a new image source based on the original
// image and the new filter
FilteredImageSource imageSource=new FilteredImageSource(
myImage.getSource(), filter);
// create the redacted image from the image source
redactedImage = createImage(imageSource);
}
public void paint(Graphics g)
{
// paint the redacted image
g.drawImage(redactedImage, 0, 0, this);
}
}
The method filterRGB is an overridden member of RGBImageFilter. This method is called once for each pixel in the image. Its x and y parameters indicate the location of the pixel being passed. Its rgb parameter indicates the current color of the pixel. The value returned by filterRGB is the color that will be displayed for this pixel. To alter the image, return something different from the current value in rgb. In this case, RedactFilter checks to see whether the pixel is within the specified area. If so, red is returned. If not, the unchanged rgb value is returned.
The RedactFilter is used very similarly to how CropImageFilter was used in the prior example. In the init method of EX14H, getImage is used to create the image. Next, a new instance of RedactFilter is constructed and a new image source is created based on the original image and the filter. Finally, createImage is used to create the redacted image from the image source. The paint method uses drawImage as it would with any other image.
As you might recall, when the drawImage method was first introduced you needed to pass this as the final parameter to it, as in the following example:
g.drawImage(redactedImage, 0, 0, this);
This parameter represents an ImageObserver and since the Applet class implements the ImageObserver interface through the Component class, you can use the Applet this variable. So far you've been asked to take this parameter on faith. Now it's time to see what an ImageObserver can do.
The ImageObserver interface includes a single method. This method, imageUpdate, is called whenever additional information about an image becomes available. For example, it might take time to retrieve a large image across the Internet. The ImageObserver interface can monitor the progress of an image retrieval. An applet could then possibly display a progress message, an estimated time to complete, or take any other useful action. The signature of imageUpdate is as follows:
public abstract boolean imageUpdate(Image img, int infoflags,
int x, int y, int width, int height);
The first parameter represents the image being updated. The second parameter represents a combination of various flags that give information about the image. These flags are described in Table 14.2. The remaining parameters usually represent a rectangle indicating the portion of the image that has been retrieved so far. Depending on the values in the infoflags parameter, some of these parameters might be invalid. The imageUpdate method should return true if you want to continue receiving updates, or false otherwise.
| Flag | Description |
| ABORT | Retrieval of the image was aborted. |
| ALLBITS | All bits of the image have been retrieved. |
| ERROR | An error occurred while retrieving the image. |
| FRAMEBITS | A frame that is part of a multi-frame image has been completely retrieved. |
| HEIGHT | The height parameter now represents the final height of the image. |
| PROPERTIES | The properties of the image have been retrieved. |
| SOMEBITS | More bits have been retrieved. |
| WIDTH | The width parameter now represents the final width of the image. |
As an example of how imageUpdate can be used, consider EX14I, as shown in Listing 14.9. In this example, a text area is created that will be used to display status messages. The imageUpdate method compares the value in the infoflags parameter against ImageObserver.ERROR and ImageObserver.ALLBITS. When one of these flags is set, a message is appended to the text area.
Listing 14.9. EX14I.java illustrates the use of the ImageObserver interface.
import java.applet.*;
import java.awt.*;
import java.awt.image.*;
public class EX14I extends Applet
{
private Image myImage;
TextArea status;
public void init()
{
resize(520, 470);
// create the image
myImage = getImage(getDocumentBase(), "savannah.jpg");
// create a text area for displaying status information
status = new TextArea(5, 20);
add(status);
}
public void paint(Graphics g)
{
// paint the image in the specified rectangle
g.drawImage(myImage, 0, 110, 350, 350, this);
}
public synchronized boolean imageUpdate(Image img,
int infoflags, int x, int y, int width, int height)
{
// if an error occurs, display the message
if ((infoflags & ImageObserver.ERROR) != 0)
status.appendText("Error\r\n");
// once all the bits have been received display
// a message and repaint the applet
if ((infoflags & ImageObserver.ALLBITS) != 0)
{
status.appendText("Allbits\r\n");
repaint();
}
return true;
}
}
This chapter gave you an in-depth look at the Graphics class and how you can use the methods of this class to enhance the appearance of your Java programs. In this chapter you learned how to draw outlined and filled shapes such as lines, arcs, ovals, rectangles, and polygons. You also learned about the Image class. You learned how to filter, crop, and modify images prior to display. Finally, you also saw how the ImageObserver interface allows an Applet to monitor the progress of an image that is being retrieved. In the next chapter you will learn how to further improve your Java programs by using animation and sound.