[Chapter 2] Simple Graphics

#

| Chapter 2 |

# 2. Simple Graphics

Contents:
Graphics
Point
Dimension
Shape
Rectangle
Polygon
Image
MediaTracker

This chapter digs into the meat of the AWT classes. After completing this chapter, you will be able to draw strings, images, and shapes via the `Graphics` class in your Java programs. We discuss geometry-related classes--`Polygon`, `Rectangle`, `Point`, and `Dimension`, and the `Shape` interface--you will see these throughout the remaining AWT objects. You will also learn several ways to do smooth animation by using double buffering and the `MediaTracker`.

After reading this chapter, you should be able to do simple animation and image manipulation with AWT. For most applications, this should be sufficient. If you want to look at AWT's more advanced graphics capabilities, be sure to take a look at Chapter 12, Image Processing.

## 2.1 Graphics

The `Graphics` class is an abstract class that provides the means to access different graphics devices. It is the class that lets you draw on the screen, display images, and so forth. `Graphics` is an abstract class because working with graphics requires detailed knowledge of the platform on which the program runs. The actual work is done by concrete classes that are closely tied to a particular platform. Your Java Virtual Machine vendor provides the necessary concrete classes for your environment. You never need to worry about the platform-specific classes; once you have a `Graphics` object, you can call all the methods of the `Graphics` class, confident that the platform-specific classes will work correctly wherever your program runs.

You rarely need to create a `Graphics` object yourself; its constructor is protected and is only called by the subclasses that extend `Graphics`. How then do you get a `Graphics` object to work with? The sole parameter of the `Component.paint()` and `Component.update()` methods is the current graphics context. Therefore, a `Graphics` object is always available when you override a component's `paint()` and `update()` methods. You can ask for the graphics context of a `Component` by calling `Component.getGraphics()`. However, many components do not have a drawable graphics context. `Canvas` and `Container` objects return a valid `Graphics` object; whether or not any other component has a drawable graphics context depends on the run-time environment. (The latest versions of Netscape Navigator provide a drawable graphics context for any component, but you shouldn't get used to writing platform-specific code.) This restriction isn't as harsh as it sounds. For most components, a drawable graphics context doesn't make much sense; for example, why would you want to draw on a `List`? If you want to draw on a component, you probably can't. The notable exception is `Button`, and that may be fixed in future versions of AWT.

### Graphics Methods

Constructors

protected Graphics ()

  Because `Graphics` is an abstract class, it doesn't have a visible constructor. The way to get a `Graphics` object is to ask for one by calling `getGraphics()` or to use the one given to you by the `Component.paint()` or `Component.update()` method.

The abstract methods of the `Graphics` class are implemented by some windowing system-specific class. You rarely need to know which subclass of `Graphics` you are using, but the classes you actually get (if you are using the JDK) are `sun.awt.win32.Win32Graphics` ( JDK1.0), `sun.awt.window.WGraphics` ( JDK1.1), `sun.awt.motif.X11Graphics`, or `sun.awt.macos.MacGraphics`. Pseudo-constructors

In addition to using the graphics contexts given to you by `getGraphics()` or in `Component.paint()`, you can get a `Graphics` object by creating a copy of another `Graphics` object. Creating new graphics contexts has resource implications. Certain platforms have a limited number of graphics contexts that can be active. For instance, on Windows 95 you cannot have more than four in use at one time. Therefore, it's a good idea to call `dispose()` as soon as you are done with a `Graphics` object. Do not rely on the garbage collector to clean up for you.

public abstract Graphics create ()

  This method creates a second reference to the graphics context. It is useful for clipping (reducing the drawable area).

public Graphics create (int x, int y, int width, int height)

  This method creates a second reference to a subset of the drawing area of the graphics context. The new `Graphics` object covers the rectangle from (`x`, `y`) through (`x+width-1`, `y+height-1`) in the original object. The coordinate space of the new `Graphics` context is translated so that the upper left corner is (0, 0) and the lower right corner is (`width`, `height`). Shifting the coordinate system of the new object makes it easier to work within a portion of the drawing area without using offsets.

Drawing strings

These methods let you draw text strings on the screen. The coordinates refer to the left end of the text's baseline.

public abstract void drawString (String text, int x, int y)

  The `drawString()` method draws `text` on the screen in the current font and color, starting at position (`x`, `y`). The starting coordinates specify the left end of the `String`'s baseline.

public void drawChars (char text[], int offset, int length, int x, int y)

  The `drawChars()` method creates a `String` from the char array `text` starting at `text[offset]` and continuing for `length` characters. The newly created `String` is then drawn on the screen in the current font and color, starting at position (`x`, `y`). The starting coordinates specify the left end of the `String`'s baseline.

public void drawBytes (byte text[], int offset, int length, int x, int y)

  The `drawBytes()` method creates a `String` from the byte array `text` starting at `text[offset]` and continuing for `length` characters. This `String` is then drawn on the screen in the current font and color, starting at position (`x`, `y`). The starting coordinates specify the left end of the `String`'s baseline.

public abstract Font getFont ()

  The `getFont()` method returns the current `Font` of the graphics context. See Chapter 3, Fonts and Colors, for more on what you can do with fonts. You cannot get meaningful results with `getFont()` until the applet or application is displayed on the screen (generally, not in `init()` of an applet or `main()` of an application).

public abstract void setFont (Font font)

  The `setFont()` method changes the current `Font` to `font`. If `font` is not available on the current platform, the system chooses a default. To change the current font to 12 point bold TimesRoman:

      setFont (new Font ("TimesRoman", Font.BOLD, 12));

public FontMetrics getFontMetrics ()

  The `getFontMetrics()` method returns the current `FontMetrics` object of the graphics context. You use `FontMetrics` to reveal sizing properties of the current `Font`--for example, how wide the "Hello World" string will be in pixels when displayed on the screen.

public abstract FontMetrics getFontMetrics (Font font)

  This version of `getFontMetrics()` returns the `FontMetrics` for the `Font` font instead of the current font. You might use this method to see how much space a new font requires to draw text.

For more information about `Font` and `FontMetrics`, see Chapter 3, Fonts and Colors. Painting

public abstract Color getColor ()

  The `getColor()` method returns the current foreground `Color` of the `Graphics` object. All future drawing operations will use this color. Chapter 3, Fonts and Colors describes the `Color` class.

public abstract void setColor (Color color)

  The `setColor()` method changes the current drawing color to `color`. As you will see in the next chapter, the `Color` class defines some common colors for you. If you can't use one of the predefined colors, you can create a color from its RGB values. To change the current color to red, use any of the following:

      setColor (Color.red);
      setColor (new Color (255, 0, 0));
      setColor (new Color (0xff0000));

public abstract void clearRect (int x, int y, int width, int height)

  The `clearRect()` method sets the rectangular drawing area from (`x`, `y`) to (`x+width-1`, `y+height-1`) to the current background color. Keep in mind that the second pair of parameters is not the opposite corner of the rectangle, but the width and height of the area to clear.

public abstract void clipRect (int x, int y, int width, int height)

  The `clipRect()` method reduces the drawing area to the intersection of the current drawing area and the rectangular area from (`x`, `y`) to (`x+width-1`, `y+height-1`). Any future drawing operations outside this clipped area will have no effect. Once you clip a drawing area, you cannot increase its size with `clipRect()`; the drawing area can only get smaller. (However, if the `clipRect()` call is in `paint()`, the size of the drawing area will be reset to its original size on subsequent calls to `paint()`.) If you want the ability to draw to the entire area, you must create a second `Graphics` object that contains a copy of the drawing area before calling `clipRect()` or use `setClip()`. The following code is a simple applet that demonstrates clipping; Figure 2.1 shows the result.

      import java.awt.*;
      public class clipping extends java.applet.Applet {
          public void paint (Graphics g) {
              g.setColor (Color.red);
              Graphics clippedGraphics = g.create();
              clippedGraphics.drawRect (0,0,100,100);
              clippedGraphics.clipRect (25, 25, 50, 50);
              clippedGraphics.drawLine (0,0,100,100);
              clippedGraphics.dispose();
              clippedGraphics=null;
              g.drawLine (0,100,100,0);
          }
      }

  #### Figure 2.1: Clipping restricts the drawing area

  The `paint()` method for this applet starts by setting the foreground color to red. It then creates a copy of the `Graphics` context for clipping, saving the original object so it can draw on the entire screen later. The applet then draws a rectangle, sets the clipping area to a smaller region, and draws a diagonal line across the rectangle from upper left to lower right. Because clipping is in effect, only part of the line is displayed. The applet then discards the clipped `Graphics` object and draws an unclipped line from lower left to upper right using the original object `g`.

public abstract void setClip(int x, int y, int width, int height)

  This `setClip()` method allows you to change the current clipping area based on the parameters provided. `setClip()` is similar to `clipRect()`, except that it is not limited to shrinking the clipping area. The current drawing area becomes the rectangular area from (`x`, `y`) to (`x+width-1`, `y+height-1`); this area may be larger than the previous drawing area.

public abstract void setClip(Shape clip)

  This `setClip()` method allows you to change the current clipping area based on the `clip` parameter, which may be any object that implements the `Shape` interface. Unfortunately, practice is not as good as theory, and in practice, `clip` must be a `Rectangle`; if you pass `setClip()` a `Polygon`, it throws an `IllegalArgumentException`.[1] (The `Shape` interface is discussed later in this chapter.)

  > [1] It should be simple for Sun to fix this bug; one would expect clipping to a `Polygon` to be the same as clipping to the `Polygon`'s bounding rectangle.

public abstract Rectangle getClipBounds ()
public abstract Rectangle getClipRect ()

  The `getClipBounds()` methods returns a `Rectangle` that describes the clipping area of a `Graphics` object. The `Rectangle` gives you the (`x`, `y`) coordinates of the top left corner of the clipping area along with its width and height. (`Rectangle` objects are discussed later in this chapter.)

  `getClipRect()` is the Java 1.0 name for this method.

public abstract Shape getClip ()

  The `getClip()` method returns a `Shape` that describes the clipping area of a `Graphics` object. That is, it returns the same thing as `getClipBounds()` but as a `Shape`, instead of as a `Rectangle`. By calling `Shape.getBounds()`, you can get the (`x`, `y`) coordinates of the top left corner of the clipping area along with its width and height. In the near future, it is hard to imagine the actual object that `getClip()` returns being anything other than a `Rectangle`.

public abstract void copyArea (int x, int y, int width, int height, int delta_x, int delta_y)

  The `copyArea()` method copies the rectangular area from (`x`, `y`) to (`x+width`, `y+height`) to the area with an upper left corner of (`x+delta_x`, `y+delta_y`). The `delta_x` and `delta_y` parameters are not the coordinates of the second point but an offset from the first coordinate pair (`x`, `y`). The area copied may fall outside of the clipping region. This method is often used to tile an area of the graphics context. `copyArea()` does not save the contents of the area copied.

Painting mode

There are two painting or drawing modes for the `Graphics` class: paint (the default) and XOR mode. In paint mode, anything you draw replaces whatever is already on the screen. If you draw a red square, you get a red square, no matter what was underneath; this is what most programmers have learned to expect.

The behavior of XOR mode is rather strange, at least to people accustomed to modern programming environments. XOR mode is short for eXclusive-OR mode. The idea behind XOR mode is that drawing the same object twice returns the screen to its original state. This technique was commonly used for simple animations prior to the development of more sophisticated methods and cheaper hardware.

The side effect of XOR mode is that painting operations don't necessarily get the color you request. Instead of replacing the original pixel with the new value, XOR mode merges the original color, the painting color, and an XOR color (usually the background color) to form a new color. The new color is chosen so that if you repaint the pixel with the same color, you get the original pixel back. For example, if you paint a red square in XOR mode, you get a square of some other color on the screen. Painting the same red square again returns the screen to its original state.

public abstract void setXORMode (Color xorColor)

  The `setXORMode()` method changes the drawing mode to XOR mode. In XOR mode, the system uses the `xorColor` color to determine an alternate color for anything drawn such that drawing the same item twice restores the screen to its original condition. The `xorColor` is usually the current background color but can be any color. For each pixel, the new color is determined by an exclusive-or of the old pixel color, the painting color, and the `xorColor`.

  For example, if the old pixel is red, the XOR color is blue, and the drawing color is green, the end result would be white. To see why, it is necessary to look at the RGB values of the three colors. Red is (255, 0, 0). Blue is (0, 0, 255). Green is (0, 255, 0). The exclusive-or of these three values is (255, 255, 255), which is white. Drawing another green pixel with a blue XOR color yields red, the pixel's original color, since (255, 255, 255) `^` (0, 0, 255) `^` (0, 255, 0) yields (255, 0, 0).[2] The following code generates the display shown in Figure 2.2.

  > [2] `^` is the Java XOR operator.

    import java.awt.*;
    public class xor extends java.applet.Applet {
        public void init () {
            setBackground (Color.red);
        }
        public void paint (Graphics g) {
            g.setColor (Color.green);
            g.setXORMode (Color.blue);
            g.fillRect (10, 10, 100, 100);
            g.fillRect (10, 60, 100, 100);
        }
    }

#### Figure 2.2: Drawing in XOR mode

Although it's hard to visualize what color XOR mode will pick, there is one important special case. Let's say that there are only two colors: a background color (the XOR color) and a foreground color (the painting color). Each pixel must be in one color or the other. Painting "flips" each pixel to the other color. Foreground pixels become background, and vice versa.

public abstract void setPaintMode ()

  The `setPaintMode()` method puts the system into paint mode. When in paint mode, any drawing operation replaces whatever is underneath it. Call `setPaintMode()` to return to normal painting when finished with XOR mode.

Drawing shapes

Most of the drawing methods require you to specify a bounding rectangle for the object you want to draw: the location of the object's upper left corner, plus its width and height. The two exceptions are lines and polygons. For lines, you supply two endpoints; for polygons, you provide a set of points.

Versions 1.0.2 and 1.1 of AWT always draw solid lines that are one pixel wide; there is no support for line width or fill patterns. A future version should support lines with variable widths and patterns.

public abstract void drawLine (int x1, int y1, int x2, int y2)

  The `drawLine()` method draws a line on the graphics context in the current color from (`x1`, `y1`) to (`x2`, `y2`). If (`x1`, `y1`) and (`x2`, `y2`) are the same point, you will draw a point. There is no method specific to drawing a point. The following code generates the display shown in Figure 2.3.

      g.drawLine (5, 5, 50, 75);   // line
      g.drawLine (5, 75, 5, 75);   // point
      g.drawLine (50, 5, 50, 5);   // point

  #### Figure 2.3: Drawing lines and points with drawLine()

public void drawRect (int x, int y, int width, int height)

  The `drawRect()` method draws a rectangle on the drawing area in the current color from (`x`, `y`) to (`x+width`, `y+height`). If `width` or `height` is negative, nothing is drawn.

public abstract void fillRect (int x, int y, int width, int height)

  The `fillRect()` method draws a filled rectangle on the drawing area in the current color from (`x`, `y`) to (`x+width-1`, `y+height-1`). Notice that the filled rectangle is one pixel smaller to the right and bottom than requested. If `width` or `height` is negative, nothing is drawn.

public abstract void drawRoundRect (int x, int y, int width, int height, int arcWidth, int arcHeight)

  The `drawRoundRect()` method draws a rectangle on the drawing area in the current color from (`x`, `y`) to (`x+width`, `y+height`). However, instead of perpendicular corners, the corners are rounded with a horizontal diameter of `arcWidth` and a vertical diameter of `arcHeight`. If `width` or `height` is a negative number, nothing is drawn. If `width`, `height`, `arcWidth`, and `arcHeight` are all equal, you get a circle.

  To help you visualize the `arcWidth` and `arcHeight` of a rounded rectangle, Figure 2.4 shows one corner of a rectangle drawn with an `arcWidth` of 20 and a `arcHeight` of 40.

  #### Figure 2.4: Drawing rounded corners

public abstract void fillRoundRect (int x, int y, int width, int height, int arcWidth, int arcHeight)

  The `fillRoundRect()` method draws a filled rectangle on the drawing area in the current color from (`x`, `y`) to (`x+width-1`, `y+height-1`). However, instead of having perpendicular corners, the corners are rounded with a horizontal diameter of `arcWidth` and a vertical diameter of `arcHeight` for the four corners. Notice that the filled rectangle is one pixel smaller to the right and bottom than requested. If `width` or `height` is a negative number, nothing is filled. If `width`, `height`, `arcWidth`, and `arcHeight` are all equal, you get a filled circle.

  Figure 2.4 shows how AWT generates rounded corners. Figure 2.5 shows the collection of rectangles created by the following code. The rectangles in Figure 2.5 are filled and unfilled, with rounded and square corners.

      g.drawRect (25, 10, 50, 75);
      g.fillRect (25, 110, 50, 75);
      g.drawRoundRect (100, 10, 50, 75, 60, 50);
      g.fillRoundRect (100, 110, 50, 75, 60, 50);

  #### Figure 2.5: Varieties of rectangles

public void draw3DRect (int x, int y, int width, int height, boolean raised)

  The `draw3DRect()` method draws a rectangle in the current color from (`x`, `y`) to (`x+width`, `y+height`); a shadow effect makes the rectangle appear to float slightly above or below the screen. The `raised` parameter has an effect only if the current color is not black. If `raised` is `true`, the rectangle looks like a button waiting to be pushed. If `raised` is `false`, the rectangle looks like a depressed button. If `width` or `height` is negative, the shadow appears from another direction.

public void fill3DRect (int x, int y, int width, int height, boolean raised)

  The `fill3DRect()` method draws a filled rectangle in the current color from (`x`, `y`) to (`x+width`, `y+height`); a shadow effect makes the rectangle appear to float slightly above or below the screen. The `raised` parameter has an effect only if the current color is not black. If `raised` is `true`, the rectangle looks like a button waiting to be pushed. If `raised` is `false`, the rectangle looks like a depressed button. To enhance the shadow effect, the depressed area is given a slightly deeper shade of the drawing color. If `width` or `height` is negative, the shadow appears from another direction, and the rectangle isn't filled. (Different platforms could deal with this differently. Try to ensure the parameters have positive values.)

  Figure 2.6 shows the collection of three-dimensional rectangles created by the following code. The rectangles in the figure are raised and depressed, filled and unfilled.

      g.setColor (Color.gray);
      g.draw3DRect (25, 10, 50, 75, true);
      g.draw3DRect (25, 110, 50, 75, false);
      g.fill3DRect (100, 10, 50, 75, true);
      g.fill3DRect (100, 110, 50, 75, false);

  #### Figure 2.6: Filled and unfilled 3D rectangles

public abstract void drawOval (int x, int y, int width, int height)

  The `drawOval()` method draws an oval in the current color within an invisible bounding rectangle from (`x`, `y`) to (`x+width`, `y+height`). You cannot specify the oval's center point and radii. If `width` and `height` are equal, you get a circle. If `width` or `height` is negative, nothing is drawn.

public abstract void fillOval (int x, int y, int width, int height)

  The `fillOval()` method draws a filled oval in the current color within an invisible bounding rectangle from (`x`, `y`) to (`x+width-1`, `y+height-1`). You cannot specify the oval's center point and radii. Notice that the filled oval is one pixel smaller to the right and bottom than requested. If `width` or `height` is negative, nothing is drawn.

  Figure 2.7 shows the collection of ovals, filled and unfilled, that were generated by the following code:

      g.drawOval (25, 10, 50, 75);
      g.fillOval (25, 110, 50, 75);
      g.drawOval (100, 10, 50, 50);
      g.fillOval (100, 110, 50, 50);

#### Figure 2.7: Filled and unfilled ovals

public abstract void drawArc (int x, int y, int width, int height, int startAngle, int arcAngle)

  The `drawArc()` method draws an arc in the current color within an invisible bounding rectangle from (`x`, `y`) to (`x+width`, `y+height`). The arc starts at `startAngle` degrees and goes to `startAngle` + `arcAngle` degrees. An angle of 0 degrees is at the 3 o'clock position; angles increase counter-clockwise. If `arcAngle` is negative, drawing is in a clockwise direction. If `width` and `height` are equal and `arcAngle` is 360 degrees, `drawArc()` draws a circle. If `width` or `height` is negative, nothing is drawn.

public abstract void fillArc (int x, int y, int width, int height, int startAngle, int arcAngle)

  The `fillArc()` method draws a filled arc in the current color within an invisible bounding rectangle from (`x`, `y`) to (`x+width-1`, `y+height-1`). The arc starts at `startAngle` degrees and goes to `startAngle` + `arcAngle` degrees. An angle of 0 degrees is at the 3 o'clock position; angles increase counter-clockwise. If `arcAngle` is negative, drawing is in a clockwise direction. The arc fills like a pie (to the origin), not from arc endpoint to arc endpoint. This makes creating pie charts easier. If `width` and `height` are equal and `arcAngle` is 360 degrees, `fillArc()` draws a filled circle. If `width` or `height` is negative, nothing is drawn.

  Figure 2.8 shows a collection of filled and unfilled arcs that were generated by the following code:

      g.drawArc (25, 10, 50, 75, 0, 360);
      g.fillArc (25, 110, 50, 75, 0, 360);
      g.drawArc (100, 10, 50, 75, 45, 215);
      g.fillArc (100, 110, 50, 75, 45, 215);

  #### Figure 2.8: Filled and unfilled arcs

public void drawPolygon (Polygon p)

  The `drawPolygon()` method draws a path for the points in polygon `p` in the current color. Polygon discusses the `Polygon` class in detail.

  The behavior of `drawPolygon()` changes slightly between Java 1.0.2 and 1.1. With version 1.0.2, if the first and last points of a `Polygon` are not the same, a call to `drawPolygon()` results in an open polygon, since the endpoints are not connected for you. Starting with version 1.1, if the first and last points are not the same, the endpoints are connected for you.

public abstract void drawPolygon (int xPoints[], int yPoints[], int numPoints)

  The `drawPolygon()` method draws a path of `numPoints` nodes by plucking one element at a time out of `xPoints` and `yPoints` to make each point. The path is drawn in the current color. If either `xPoints` or `yPoints` does not have `numPoints` elements, `drawPolygon()` throws a run-time exception. In 1.0.2, this exception is an `IllegalArgumentException`; in 1.1, it is an `ArrayIndexOutOfBoundsException`. This change shouldn't break older programs, since you are not required to catch run-time exceptions.

public abstract void drawPolyline (int xPoints[], int yPoints[], int numPoints)

  The `drawPolyline()` method functions like the 1.0 version of `drawPolygon()`. It plays connect the dots with the points in the `xPoints` and `yPoints` arrays and does not connect the endpoints. If either `xPoints` or `yPoints` does not have `numPoints` elements, `drawPolygon()` throws the run-time exception, `ArrayIndexOutOfBoundsException`.

Filling polygons is a complex topic. It is not as easy as filling rectangles or ovals because a polygon may not be closed and its edges may cross. AWT uses an even-odd rule to fill polygons. This algorithm works by counting the number of times each scan line crosses an edge of the polygon. If the total number of crossings to the left of the current point is odd, the point is colored. If it is even, the point is left alone. Figure 2.9 demonstrates this algorithm for a single scan line that intersects the polygon at x values of 25, 75, 125, 175, 225, and 275.

#### Figure 2.9: Polygon fill algorithm

The scan line starts at the left edge of the screen; at this point there haven't been any crossings, so the pixels are left untouched. The scan line reaches the first crossing when x equals 25. Here, the total number of crossings to the left is one, so the scan line is inside the polygon, and the pixels are colored. At 75, the scan line crosses again; the total number of crossings is two, so coloring stops.

public void fillPolygon (Polygon p)

  The `fillPolygon()` method draws a filled polygon for the points in `Polygon p` in the current color. If the polygon is not closed, `fillPolygon()` adds a segment connecting the endpoints. Polygon discusses the `Polygon` class in detail.

public abstract void fillPolygon (int xPoints[], int yPoints[], int nPoints)

  The `fillPolygon()` method draws a polygon of `numPoints` nodes by plucking one element at a time out of `xPoints` and `yPoints` to make each point. The polygon is drawn in the current color. If either `xPoints` or `yPoints` does not have `numPoints` elements, `fillPolygon()` throws the run-time exception `IllegalArgumentException`. If the polygon is not closed, `fillPolygon()` adds a segment connecting the endpoints.[3]

  > [3] In Java 1.1, this method throws `ArrayIndexOutOfBoundsException`, not `IllegalArgumentException`.

Figure 2.10 shows several polygons created by the following code, containing different versions of `drawPolygon()` and `fillPolygon()`:

    int[] xPoints[] = {{50, 25, 25, 75, 75},
                       {50, 25, 25, 75, 75},
                       {100, 100, 150, 100, 150, 150, 125, 100, 150},
                       {100, 100, 150, 100, 150, 150, 125, 100, 150}};
    int[] yPoints[] = {{10, 35, 85, 85, 35, 10},
                       {110, 135, 185, 185, 135},
                       {85, 35, 35, 85, 85, 35, 10, 35, 85},
                       {185, 135, 135, 185, 185, 135, 110, 135, 185}};
    int   nPoints[] = {5, 5, 9, 9};
    g.drawPolygon (xPoints[0], yPoints[0], nPoints[0]);
    g.fillPolygon (xPoints[1], yPoints[1], nPoints[1]);
    g.drawPolygon (new Polygon(xPoints[2], yPoints[2], nPoints[2]));
    g.fillPolygon (new Polygon(xPoints[3], yPoints[3], nPoints[3]));

#### Figure 2.10: Filled and unfilled polygons

Drawing images

An `Image` is a displayable object maintained in memory. To get an image on the screen, you must draw it onto a graphics context, using the `drawImage()` method of the `Graphics` class. For example, within a `paint()` method, you would call `g.drawImage(image, ... , this)` to display some image on the screen. In other situations, you might use the `createImage()` method to generate an offscreen `Graphics` object, then use `drawImage()` to draw an image onto this object, for display later.

This begs the question: where do images come from? We will have more to say about the `Image` class later in this chapter. For now, it's enough to say that you can call `getImage()` to load an image from disk or across the Net. There are versions of `getImage()` in the `Applet` and `Toolkit` classes; the latter is for use in applications. You can also call `createImage()`, a method of the `Component` class, to generate an image in memory.

What about the last argument to `drawImage()`? What is `this` for? The last argument of `drawImage()` is always an image observer--that is, an object that implements the `ImageObserver` interface. This interface is discussed in detail in Chapter 12, Image Processing. For the time being, it's enough to say that the call to `drawImage()` starts a new thread that loads the requested image. An image observer monitors the process of loading an image; the thread that is loading the image notifies the image observer whenever new data has arrived. The `Component` class implements the `ImageObserver` interface; when you're writing a `paint()` method, you're almost certainly overriding some component's `paint()` method; therefore, it's safe to use `this` as the image observer in a call to `drawImage()`. More simply, we could say that any component can serve as an image observer for images that are drawn on it.

public abstract boolean drawImage (Image image, int x, int y, ImageObserver observer)

  The `drawImage()` method draws `image` onto the screen with its upper left corner at (`x`, `y`), using `observer` as its `ImageObserver`. Returns `true` if the object is fully drawn, `false` otherwise.

public abstract boolean drawImage (Image image, int x, int y, int width, int height, ImageObserver observer)

  The `drawImage()` method draws `image` onto the screen with its upper left corner at (`x`, `y`), using `observer` as its `ImageObserver`. The system scales `image` to fit into a `width` x `height` area. The scaling may take time. This method returns `true` if the object is fully drawn, `false` otherwise.

  With Java 1.1, you don't need to use `drawImage()` for scaling; you can prescale the image with the `Image.getScaledInstance()` method, then use the previous version of `drawImage()`.

public abstract boolean drawImage (Image image, int x, int y, Color backgroundColor, ImageObserver observer)

  The `drawImage()` method draws `image` onto the screen with its upper left corner at (`x`, `y`), using `observer` as its `ImageObserver`. `backgroundColor` is the color of the background seen through the transparent parts of the image. If no part of the image is transparent, you will not see `backgroundColor`. Returns `true` if the object is fully drawn, `false` otherwise.

public abstract boolean drawImage (Image image, int x, int y, int width, int height, Color backgroundColor, ImageObserver observer)

  The `drawImage()` method draws `image` onto the screen with its upper left corner at (`x`, `y`), using `observer` as its `ImageObserver`. `backgroundColor` is the color of the background seen through the transparent parts of the image. The system scales `image` to fit into a `width` x `height` area. The scaling may take time. This method returns `true` if the image is fully drawn, `false` otherwise.

  With Java 1.1, you can prescale the image with the `AreaAveragingScaleFilter` or `ReplicateScaleFilter` described in Chapter 12, Image Processing, then use the previous version of `drawImage()` to display it.

The following code generated the images in Figure 2.11. The images on the left come from a standard JPEG file. The images on the right come from a file in GIF89a format, in which the white pixel is "transparent." Therefore, the gray background shows through this pair of images.

    import java.awt.*;
    import java.applet.*;
    public class drawingImages extends Applet {
        Image i, j;
        public void init () {
            i = getImage (getDocumentBase(), "rosey.jpg");
            j = getImage (getDocumentBase(), "rosey.gif");
        }
        public void paint (Graphics g) {
            g.drawImage (i, 10, 10, this);
            g.drawImage (i, 10, 85, 150, 200, this);
            g.drawImage (j, 270, 10, Color.lightGray, this);
            g.drawImage (j, 270, 85, 150, 200, Color.lightGray, this);
        }
    }

#### Figure 2.11: Scaled and unscaled images

public abstract boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, ImageObserver observer)

  The `drawImage()` method draws a portion of `image` onto the screen. It takes the part of the image with corners at (`sx1`, `sy1`) and (`sx2`, `sy2`); it places this rectangular snippet on the screen with one corner at (`dx1`, `dy1`) and another at (`dx2`, `dy2`), using `observer` as its `ImageObserver`. (Think of `d` for destination location and `s` for source image.) This method returns `true` if the object is fully drawn, `false` otherwise.

  `drawImage()` flips the image if source and destination endpoints are not the same corners, crops the image if the destination is smaller than the original size, and scales the image if the destination is larger than the original size. It does not do rotations, only flips (i.e., it can produce a mirror image or an image rotated 180 degrees but not an image rotated 90 or 270 degrees).

public abstract boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, Color backgroundColor, ImageObserver observer)

  The `drawImage()` method draws a portion of `image` onto the screen. It takes the part of the image with corners at (`sx1`, `sy1`) and (`sx2`, `sy2`); it places this rectangular snippet on the screen with one corner at (`dx1`, `dy1`) and another at (`dx2`, `dy2`), using `observer` as its `ImageObserver`. (Think of `d` for destination location and `s` for source image.) `backgroundColor` is the color of the background seen through the transparent parts of the image. If no part of the image is transparent, you will not see `backgroundColor`. This method returns `true` if the object is fully drawn, `false` otherwise.

  Like the previous version of `drawImage()`, this method flips the image if source and destination endpoints are not the same corners, crops the image if the destination is smaller than the original size, and scales the image if the destination is larger than the original size. It does not do rotations, only flips (i.e., it can produce a mirror image or an image rotated 180 degrees but not an image rotated 90 or 270 degrees).

The following code demonstrates the new `drawImage()` methods in Java 1.1. They allow you to scale, flip, and crop images without the use of image filters. The results are shown in Figure 2.12.

    // Java 1.1 only
    import java.awt.*;
    import java.applet.*;
    public class drawingImages11 extends Applet {
        Image i, j;
        public void init () {
            i = getImage (getDocumentBase(), "rosey.gif");
        }
        public void paint (Graphics g) {
            g.drawImage (i, 10, 10, this);
            g.drawImage (i, 10, 85,
                         i.getWidth(this)+10, i.getHeight(this)+85,
                         i.getWidth(this), i.getHeight(this), 0, 0, this);
            g.drawImage (i, 270, 10,
                         i.getWidth(this)+270, i.getHeight(this)*2+10, 0, 0,
                         i.getWidth(this), i.getHeight(this), Color.gray, this);
            g.drawImage (i, 10, 170,
                         i.getWidth(this)*2+10, i.getHeight(this)+170, 0,
                         i.getHeight(this)/2, i.getWidth(this)/2, 0, this);
        }
    }

#### Figure 2.12: Flipped, mirrored, and cropped images

Miscellaneous methods

public abstract void translate (int x, int y)

  The `translate()` method sets how the system translates the coordinate space for you. The point at the (`x`, `y`) coordinates becomes the origin of this graphics context. Any future drawing will be relative to this location. Multiple translations are cumulative. The following code leaves the coordinate system translated by (100, 50).

      translate (100, 0);
      translate (0, 50);

  Note that each call to `paint()` provides an entirely new `Graphics` context with its origin in the upper left corner. Therefore, don't expect translations to persist from one call to `paint()` to the next.

public abstract void dispose ()

  The `dispose()` method frees any system resources used by the `Graphics` context. It's a good idea to call `dispose()` whenever you are finished with a `Graphics` object, rather than waiting for the garbage collector to call it automatically (through `finalize()`). Disposing of the `Graphics` object yourself will help your programs on systems with limited resources. However, you should not dispose the `Graphics` parameter to `Component.paint()` or `Component.update()`.

public void finalize ()

  The garbage collector calls `finalize()` when it determines that the `Graphics` object is no longer needed. `finalize()` calls `dispose()`, which frees any resources that the `Graphics` object has used.

public String toString ()

  The `toString()` method of `Graphics` returns a string showing the current font and color. However, `Graphics` is an abstract class, and classes that extend `Graphics` usually override `toString()`. For example, on a Windows 95 machine, `sun.awt.win32.Win32Graphics` is the concrete class that extends `Graphics`. The class's `toString()` method displays the current origin of the `Graphics` object, relative to the original coordinate system:

      sun.awt.win32.Win32Graphics[0,0]

Summary |  | Point