Java Technology Home Page
A-Z Index

Java Developer Connection(SM)
Books

Downloads, APIs, Documentation
Java Developer Connection
Tutorials, Tech Articles, Training
Online Support
Community Discussion
News & Events from Everywhere
Products from Everywhere
How Java Technology is Used Worldwide
Print Button
 
Book Excerpts

Chapter 3 Graphics from Graphic Java 1.2
Vol 1 - AWT

By David M. Geary

(May 1998)

This is an excerpt from Graphic Java 1.2: Mastering the JFC Volume 1 - AWT by David M. Geary, published August 1998. The first of three volumes, written for experienced programmers looking for thorough and detailed explanations of the 1.2 AWT class libraries, Volume 1 covers all aspects of the AWT. The book also covers advanced topics such as clipboard and data transfer, double buffering, custom dialogs, and sprite animation. Together with the forthcoming Volume 2 - Swing (Fall 1998) and Volume 3 - 2D API (Winter 1998), Graphic Java 1.2 provides Java developers with the tools needed to build professional, cross-platform applications that take full advantage of the Java Foundation Classes.

Graphics

The AWT provides user interface components such as buttons, lists, menus, dialogs and so on, but it does not include anything analogous to purely graphical objects. For instance, the AWT does not provide Line or Circle classes.

The AWT does provide Rectangle, Polygon, and Point classes, however those classes were added as afterthoughts to the original AWT. Since the design of the original AWT did not allow for purely graphical objects, Rectangle, Polygon and Point wound up without any graphical capabilities. In other words, Rectangle, Polygon and Point do not come equipped with a draw method. You can only set and get information about the geometric entity each represents.

Instead of providing purely graphical objects, the AWT employs a simpler--albeit less flexible and extensible--model. Every AWT component comes complete with its own java.awt.Graphics object, through which graphical operations can be performed in the associated component.

Graphics are also used to draw into various output devices, such as offscreen buffers and printers.

java.awt.Graphics

java.awt.Graphics is an abstract class that defines a veritable kitchen sink of graphical operations. Its 47 public methods can be used for rendering images and text, drawing and filling shapes, clipping graphical operations, and much more.

Nearly all applets (and applications) that use the AWT manipulate a Graphics for at least one graphical service. For example, even simple Hello World applets are quick to use a Graphics to display their clever verbiage:

public class HelloWorld extends Applet {
  public void paint(Graphics g) {
    g.drawString("Hello Graphic Java World", 75, 100);
  }
}  

In addition to performing graphical operations within a component, each Graphics also keeps track of the following graphical properties:

  • the color used for drawing and filling shapes
  • the font used for rendering text
  • a clipping rectangle
  • a graphical mode (XOR or Paint)
  • a translation origin for rendering and clipping coordinates

In order to provide an overall picture of the role that the Graphics class plays in the AWT (and the JDK), two tables are provided below. See JDK Methods that are passed a Graphics Reference lists JDK methods that are passed a reference to a Graphics.

JDK Methods that are passed a Graphics Reference 

Package Class Methods
java.awt Canvas paint(Graphics g)
  Component paint(Graphics g)
  Component paintAll(Graphics g)
  Component print(Graphics g)
  Component printAll(Graphics g)
  Component update(Graphics g)
  Container paint(Graphics g)
  Container paintComponents(Graphics g)
  Container print(Graphics g)
  Container printComponents(Graphics g)
  ScrollPane printComponents(Graphics g)
java.beans Property-Editor paintValue(Graphics g, Rectangle r)
  Property-EditorSupport paintValue(Graphics g, Rectangle r)

Nearly all of the methods that are passed a reference to a Graphics reside in the java.awt.package. Furthermore, notice that many of the methods are used to paint or print AWT components.

See JDK Methods that Return a Graphics Reference lists methods that return a Graphics reference. The most commonly used method from See JDK Methods that Return a Graphics Reference is Component.getGraphics(), which returns a reference to the Graphics associated with a java.awt.Component.

JDK Methods that Return a Graphics Reference
Package Class Methods
java.awt Component getGraphics()
  Image getGraphics()
  PrintJob getGraphics()
  Graphics create()
  Graphics create(int x, int y, int w, int h)

Images and print jobs also provide a getGraphics method. Image.getGraphics() is often used for double buffering by rendering into an offscreen buffer.

Graphics.create() clones the Graphics on whose behalf create() was called. The version of the create() that takes four arguments sets the origin and clipping rectangle for the newly created copy. The origin is specified by the x and y arguments. The clipping rectangle is the intersection of the Graphics' current clipping rectangle and the rectangle defined by the arguments passed to create().

Graphics Parameters

The Graphics class fulfills two major responsibilities:

  • set and get graphical parameters
  • perform graphical operations in an output device

The main focus of attention for the Graphics class is the second item, to which most of this chapter is devoted. This section takes a brief look at the graphical parameters that are maintained by the Graphics class:

  • color
  • font and font metrics
  • clipping rectangle
  • graphics mode

The Graphics class provides get and set methods for color, font and clipping rectangle. Graphics mode is a write-only property, while font metrics is a read-only property.3

Each Graphics maintains a single color, representing the color that will be used for the next rendering operation:

  • void setColor(Color color)
  • Color getColor()

The font used to draw text may be specified and read by the following methods:

  • void setFont(Font f)
  • Font getFont()

Font metrics are represented by the java.awt.FontMetrics class, which provides information about a particular font, such as the height of the font, its ascent, leading, etc. The Graphics class provides two methods that return a reference to a FontMetrics :

  • FontMetrics getFontMetrics()
  • FontMetrics getFontMetrics(Font f)

The no-argument version of the method returns the font metrics associated with the Graphics' current font. The second method returns the font metrics associated with the specified font. Although there is no setter method, font metrics may be specified indirectly by setting a particular font.

Each Graphics also maintains a clipping rectangle. Graphical operations are clipped to the rectangle specified by a Graphics' clipping rectangle:

  • void setClip(int x, int y, int w, int h)
  • Rectangle getClipBounds()
  • void setClip(Shape)
  • Shape getClip()
  • void clipRect(int x, int y, int w, int h)

The clipping rectangle can be specified either by four integers representing the bounding box of the rectangle, or by a java.awt.Shape. The Shape interface is part of the Java2D API, which is discussed at length in the third volume of Graphic Java. Shapes can be non-rectangular, so it is possible to define a non-rectangular clipping rectangle for an output device.

The last method listed above computes a new clipping rectangle which is the intersection of the previous clipping rectangle with the rectangle specified by the method's arguments. In early versions of the AWT, Graphics.clipRect() was the only way to modify a clipping rectangle.

Finally, graphics mode, which is discussed in detail in See Graphics Modes, determines how text and shapes are drawn over existing graphics. Two methods are provided to set the mode:

  • void setPaintMode()
  • void setXORMode()

setPaintMode() sets the graphics mode to paint, meaning subsequent rendering operations will overwrite existing graphics. setXORMode() allows for drawing and erasing without disturbing the graphics underneath. Paint mode is the default graphics mode, whereas XOR mode comes in handy in a number of situations, such as rubberbanding or layering graphics in a drawing program, for example.

The Graphics Coordinate System

The graphics coordinate system is anchored in the upper left-hand corner of a component, with coordinates increasing down and to the right, as depicted in See Graphics Coordinate System.

Coordinates lie between pixels of the output device. Operations that draw outlines of shapes, such as Graphics.drawRect(), traverse a coordinate path with a pen that hangs beneath and to the right of the path. The size of the pen is always one pixel wide and one pixel high.

Drawing Shapes

See RectTest Applet lists an applet that invokes Graphics.drawRect() to draw a small rectangle in the upper left-hand corner of the applet.

RectTest Applet

  import java.applet.Applet;
  import java.awt.*;

  public class RectTest extends Applet {
    public void paint(Graphics g) {
      g.drawRect(2,2,4,4);
    }
  }

Although the applet has a simple implementation, it illustrates an important point.

At first glance, it may appear that the arguments to drawRect(2,2,4,4) define the bounds of the rectangle to be drawn in pixel coordinates. In reality, as shown in See Drawing Shape Outlines, the arguments specify a coordinate path that the graphics' pen will traverse when drawing the rectangle. The coordinate path starts at (2,2) and is 4 coordinates wide and 4 coordinates high:

(2,2) --> (6,2) --> (6,6) --> (2,6) --> (2,2)

The graphics pen traverses the path, hanging down and to the right of the path. As the pen traverses the path, it colors pixels that it comes in contact with. The color used by the pen can be specified by invoking Graphics.setColor(Color).

Drawing a rectangle by invoking Graphics.drawRect() results in an extra row of pixels on the right and bottom sides of the rectangle. This is due to the fact that the arguments passed to Graphics.drawRect() define the coordinate path that the pen will follow, and not the size of the rectangle itself. Since the pen hangs beneath and to the right of the coordinates along the path, the statement g.drawRect(2,2,4,4) actually draws a rectangle whose width and height are 5 pixels--not 4 pixels as you might expect.


AWT Tip
Coordinates Lie Between Pixels

Graphics coordinates lie between pixels, not on them. Graphics methods that specify coordinates specify a path that the graphics' pen--always a pixel-sized square--will traverse. The pen paints pixels below and to the right of the coordinates on the path. As a result, Graphics methods that paint outlines of shapes draw an extra row of pixels on the right and bottom sides of the shape's outline. For example, the statement g.drawRect(x,y,10,10) paints a rectangle 11 pixels wide and 11 pixels high. The arguments to Graphics.drawRect() specifies the coordinate path--not the dimensions of the rectangle.


Drawing a Border Around a Component

When drawing a border around a component for the first time, newcomers to the AWT often override the component's paint method like so:4

  public void paint(Graphics g) {
    Dimension size = getSize();
    g.drawRect(0, 0, size.width, size.height);
  }

However, as we know, the rectangle will be one pixel wider and one pixel taller than the size of the component. As a result, the right and bottom edges of the border will be drawn outside of the component, and therefore will not be visible.

The solution, of course, is to subtract one pixel from both the width and the height:

  Dimension size = getSize();
  g.drawRect(0, 0, size.width-1, size.height-1);

Filling Shapes

See FillTest Applet lists another simple applet, which is identical to the one listed in See RectTest Applet, except that the rectangle is filled.

FillTest Applet

  import java.applet.Applet;

  import java.awt.*;
  
  public class FillTest extends Applet {
    public void paint(Graphics g) {
      g.fillRect(2,2,4,4);
    }
  }

The arguments passed to fillRect() specify the same coordinate path as the previous call to drawRect(). However, Graphics methods that fill shapes fill the interior of the path, and therefore the filled rectangle is 4 pixels wide and 4 pixels high, as depicted in See Filling Shapes.

The fact that shape fills are smaller than shape outlines can be confusing. Additionally, the arguments passed to drawRect() and fillRect() define a coordinate path, but are often mistaken for representing the bounds of the rectangle in pixels. All Graphics methods that draw outlines and fills exhibit a discrepancy in size. The effect for arcs can be seen in See Drawing and Filling Arcs, which draws an outline and fill of an arc.

Graphics References

There are two ways to obtain a reference to a component's Graphics: override one of the methods listed in See JDK Methods that are passed a Graphics Reference that are passed a Graphics reference, or invoke one of the methods listed in See JDK Methods that Return a Graphics Reference, all of which return a Graphics reference.

It is worth mentioning that the Graphics reference returned from getGraphics() in Component, Image and PrintJob does not, as the name might seem to imply, return a reference to a single Graphics. Instead, the methods return a brand new Graphics that is a copy of the original. As we'll see in the next section, this has some important consequences.

Graphics References Refer to Copies

It is important to emphasize that Graphics references refer to copies of the actual Graphics associated with a component. Consider the applet listed in See CopyTest Applet--an applet that merely draws a line from its upper left-hand corner to the lower right-hand corner.

CopyTest Applet

  import java.applet.Applet;

  import java.awt.*;

  public class CopyTest extends Applet {
    public void paint(Graphics g) {
      setForeground(Color.yellow);
      g.drawLine(0,
                 0,
                 getSize().width-1, 
                 getSize().height-1);
    }
  }

The applet sets its foreground color to yellow, and then draws a line from the upper left-hand corner to the lower-right hand corner of the applet. However, this applet may not perform as you expect--the line will not be yellow the first time it is drawn.

The applet is shown in See Graphics References Passed to paint(). Refer to Copies.

The picture on the left shows the applet in its initial state; the line is drawn in the default foreground color for the applet, which, for Windows95, is black. The picture on the right shows the applet after it has been resized, and therefore repainted. After the initial painting of the applet, subsequent calls to paint() result in a yellow line. Here's why:

The call to Component.setForeground() changes the current color of the component's Graphics --in this case to yellow. setForeground() affects the applet's Graphics, but not the copy of the Graphics that was passed to paint(). Therefore, when paint() is invoked for the first time the line will not be yellow; the Graphics passed to paint() is out of synch with the actual Graphics when the call to drawLine() is made.

Subsequent calls to paint() are passed new copies of the applet's Graphics, and by then the call to setForeground() has resulted in the current color of the applet's Graphics being set to yellow.

If the applet is modified as listed in See CopyTest2 Applet, the line will initially be drawn in yellow.

CopyTest2 Applet

  import java.applet.Applet;
  import java.awt.*;
  
  public class CopyTest2 extends Applet {
    public void paint(Graphics g) {
      setForeground(Color.yellow);
  
      // the next line would do just as 
      // well as the following
      // g.setColor(Color.yellow);
  
      Graphics copy = getGraphics();
      try {
        System.out.println("g=" + g.getColor() + 
                  " copy=" + copy.getColor());
  
        copy.drawLine(0,0,
                getSize().width-1, getSize().height-1);
      }
      finally {
        copy.dispose();
      }
    }
  }

The Graphics passed to paint() is ignored. Instead, getGraphics() is called to obtain a new Graphics reference which is used to draw the line. Since the Graphics is obtained after the call to setForeground(), the current color of the Graphics, and therefore the color of the line, will be yellow.

The first time paint() is called, it prints the following output:5

    g=java.awt.Color[r=0,g=0,b=0] 
    copy=java.awt.Color[r=255,g=255,b=0]

Notice that the Graphics returned by Component.getGraphics() is disposed of before the method returns by invoking Graphics.dispose(), while the Graphics passed to paint() is not. See See Disposing of a Graphics for more on when it is sometimes necessary to dispose of a Graphics.

Lifetime of a Graphics Reference

In addition to referring to a copy of the real thing, references to Graphics that are passed to methods such as paint() and update() are only valid during the execution of the method they are passed to. Once the method returns, the reference is no longer valid.

Consider the foolish applet listed in See HoldRef Applet, which tries to reuse the Graphics reference initially passed to paint(). The line will be drawn the first time paint() is invoked, but subsequent calls to paint() will result in the line being drawn into an invalid Graphics, and the line no longer shows up (the applet can be forced to repaint by resizing the window).

HoldRef Applet

import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;

public class HoldRef extends Applet {
  private Graphics oldg;
  private boolean first = true;

  public void paint(Graphics g) {
    if(first) {
      oldg = g;
      first = false;
    }
    oldg.drawLine(
                  0,
                  0,
                  getSize().width-1, 
                  getSize().height-1);
  }
}

Graphics references passed to methods have short life-spans because they are a finite resource that must be disposed of. Each Graphics represents a graphics context supplied by the native windowing system. Such graphics contexts are usually available in a finite quantity, and callers that pass references to a Graphics are careful to dispose of it when the call returns. For example, when a call to Component.paint() returns, the caller disposes of the Graphics that was passed to paint().

Although Java has garbage collection, there are a couple of places in the AWT where it is up to the developer to dispose of system resources, and disposing of Graphics is one of them. 6 There are two issues related to disposing of a Graphics: When it needs to be done, and how it is done.

Disposing of a Graphics

Here's a simple rule for when you should dispose of a Graphics: If you obtain a reference to a Graphics by invoking one of the getGraphics methods, listed in See JDK Methods that Return a Graphics Reference, or by creating a Graphics via Graphics.create(), then you are responsible for disposing of it.

If you override a method which is passed a Graphics reference, such as Component.paint() or Component.update(), then you are off the hook-- it is the caller's responsibility to dispose of the reference.

Disposing of a Graphics is accomplished by invoking Graphics.dispose(), as the code fragment below demonstrates.

  public void someMethodInAComponent() { 
  // code fragment
    Graphics g = getGraphics();
  
    if(g != null) {
      try {
        // do something with g - if an exception is 
        // thrown, the finally block will be executed
      }
      finally {
        g.dispose() // crucial
      }
    }
  }

The call to dispose() is crucial because neglecting to do so can cause the windowing system to run out of graphics contexts, which on most operating systems is not a pretty sight.

Also note that it is not merely paranoia that causes the check for g against null. getGraphics() can indeed return null, if it is invoked before the component's peer has been created.

The call to Graphics.dispose() is placed in a finally block; manipulation of the Graphics is performed within a corresponding try block. This ensures that the call to dispose() will be made in the event that an exception is thrown from within the try block.


AWT Tip

Graphics References Passed to Methods Refer to Copies

Graphics represent native graphics contexts, which are typically a finite resource. Therefore, Graphics references returned by a method must be disposed of by calling Graphics.dispose().

Graphics references passed to methods such as Component.paint(), do not need to be manually disposed of by the method--the caller of the method is responsible for disposing of the Graphics when the calls return.


More on Drawing and Filling Shapes

The Graphics class provides methods for rendering the following:

  • Lines
  • PolyLines
  • Rectangles
  • Arcs
  • Ovals
  • Polygons
  • Text
  • Images

Rectangles, arcs, ovals and polygons can also be filled. In this section, we will explore all but the last item listed above.

Drawing Lines

Lines are drawn by invoking Graphics.drawLine(int x, int y, int x2, int y2). The AWT is not capable of drawing lines of variable thickness; the graphics's pen, and therefore the lines it draws, are always one pixel thick. In addition, lines are always drawn solid--there is no provision for patterned lines such as dotted or dashed. However, the Java 2D API provides extensive support for various linestyles and pen sizes.

The applet shown in See Drawing Lines draws lines at random locations. The lines are also fitted with random lengths, directions, and colors.

The applet shown in See Drawing Lines is listed in See PickupSticks Applet. Activation of the "scatter" button causes the applet to repaint. Math.random() is used to randomize the line's parameters. random() returns a psuedo-random7 number between 0.0 and 1.0. That number is multiplied by some factor of ten to give reasonable bounds to the line's location and size.

PickupSticks Applet

import java.applet.Applet;


  import java.awt.*;
  import java.awt.event.*;
  
  public class PickupSticks extends Applet {
    private static Color[] colors = { 
      Color.white, Color.black, Color.blue, 
      Color.red, Color.yellow, Color.orange, 
      Color.cyan, Color.pink, Color.magenta, 
      Color.green };
  
    public void init() {
      Button button = new Button("scatter");  

      add(button);

      button.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent event) {
          repaint();
        }
      });
    }
    public void paint(Graphics g) {
      for(int i=0; i < 500; ++i) {
        int x = (int)(Math.random()*100);
        int y = (int)(Math.random()*100);
        int deltax = (int)(Math.random()*100);
        int deltay = (int)(Math.random()*100);

        g.setColor(colors[(int)(Math.random()*10)]);
        g.drawLine(x,y,x + deltax, y + deltay);
      }
    }
  }

The arguments to Graphics.drawLine(int x, int y, int x2, int y2) represent the endpoints of the line. Notice that the line, like all other shapes drawn by a Graphics, is drawn in the Graphics' current color. The applet listed above uses a random number between 1 and 10 to select an index into the colors array in order to randomize the color of the line.

Drawing PolyLines

A polyline is a series of connected line segments, which are drawn by drawPolyline(int[] xPoints, int[] yPoints, int numPoints). The method is passed two arrays, one specifying the x coordinate of each point, and the other representing the y coordinate. Additionally, the method takes an integer value signifying the number of points to be drawn.8

The figure drawn by drawPolyline() is not closed if the first and last points differ.

The applet shown in See Drawing Polylines randomly generates polylines. The applet contains a repaint button which causes the applet to repaint when activated. The number of points and their locations, in addition to the color of the polyline varies in a psuedo-random fashion.

The applet shown in See Drawing Polylines is listed in See Polylines Applet.

Polylines Applet

  import java.applet.Applet;

  
  import java.awt.*;
  import java.awt.event.*;

  public class Polylines extends Applet {
    private static Color[] colors = { 
      Color.white, Color.black, Color.blue, Color.red, 
      Color.yellow, Color.orange, Color.cyan, Color.pink, 
      Color.magenta, Color.green };

    public void init() {
      Button button = new Button("repaint");
      add(button);
      button.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent event) {
          Polylines.this.repaint();
        }
      });
    }
    public void paint(Graphics g) {
      int arraySize = ((int)(Math.random()*100));
      int[] xPoints = new int[arraySize];
      int[] yPoints = new int[arraySize];

      for(int i=0; i < xPoints.length; ++i) {
        xPoints[i] = ((int)(Math.random()*200)); 
        yPoints[i] = ((int)(Math.random()*200)); 
      }
      g.setColor(colors[(int)(Math.random()*10)]);
      g.drawPolyline(xPoints, yPoints, xPoints.length);

      showStatus(arraySize + " points");
    }
  }

Drawing Rectangles

In contrast to lines, the Graphics class provides a wealth of support for rectangles; three types of rectangles are supported:

  • solid
  • rounded
  • 3D

The Graphics methods for painting and filling rectangles are listed below:

  • void clearRect(int x, int y, int w, int h)
  • void drawRect(int x, int y, int w, int h)
  • void drawRoundRect(int x, int y, int w, int h, int arcWidth, in arcHeight)
  • void draw3DRect(int x, int y, int w, int h, boolean raise)
  • void fillRoundRect(int x, int y, int w, int h, int arcWidth, int arcHeight)
  • void fillRect(int x, int y, int w, int h)
  • void fill3DRect(int x, int y, int w, int h, boolean raise)

It is important to remember that the x,y,w and h arguments passed to each of the functions listed above defines a coordinate path--which is not necessarily the size of the rectangle. As a result, Graphics methods that draw outlines of shapes draw an extra row of pixels on the right and bottom sides, making them w +1 pixels wide and h +1 pixels high. Fill methods, on the other hand, fill the interior of the path making them w pixels wide and h pixels high. See Drawing Shapes.

The methods for drawing and filling 3D rectangles take an argument in addition to x,y,w and h. The boolean argument specifies whether the 3D effect is raised or inset; true equates to raised, and false equates to inset.

The methods for drawing and filling round rectangles take two additional arguments: arcWidth and arcHeight, both integer values. arcWidth specifies the arc's horizontal diameter in pixels, and arcHeight specifies the vertical diameter of the arc. See Rounded Rectangles illustrates the horizontal and vertical diameters of the arcs.

The applet shown in See Drawing Rectangles paints rectangles with random parameters for color, size and location. All three types of rectangles supported by the Graphics class are drawn.

The applet comes equipped with three buttons and a checkbox. Each button represents the type of rectangle to be drawn; activating a button results in a repaint using the type of rectangle the button represents.

A checkbox is also included for specifying whether or not the rectangles are filled.

The applet is rather lengthy, but a good percentage of it is concerned with constructing the buttons and checkbox and their corresponding event handling. Our concern, namely drawing rectangles, is encapsulated in the applet's paint method.

public class RandomRectangles extends Applet {
  ...
  private boolean fill = false, raise = false, 
          round = false, threeD = false;
  ...
  public void paint(Graphics g) {
    for(int i=0; i < numRects; i++) {
      Point lhc = randomPoint(); // left hand corner
      Dimension size = randomDimension();

      g.setColor(colors[(int)(Math.random()*10)]);

      if(round) {
        if(fill)
          g.fillRoundRect(
            lhc.x,lhc.y,size.width,size.height,
            (int)(Math.random()*250),
            (int)(Math.random()*250));
        else
          g.drawRoundRect(
            lhc.x,lhc.y,size.width,size.height,
            (int)(Math.random()*250),
            (int)(Math.random()*250));
      }
      else if(threeD) {
        g.setColor(Color.lightGray);
        if(fill)
          g.fill3DRect(
            lhc.x,lhc.y,size.width,size.height,raise);
        else
          g.draw3DRect(
            lhc.x,lhc.y,size.width,size.height,raise);
      }
      else {
        if(fill)
          g.fillRect(
          lhc.x,lhc.y,size.width,size.height);
        else
          g.drawRect(
          lhc.x,lhc.y,size.width,size.height);
      }
      raise = raise ? false : true;
    }
  }
  ...
}

Boolean class members are used to keep track of which button was last activated, and whether or not the fill option is currently checked.

All of the rectangles drawn by the applet are drawn with a one pixel square pen, because that is the only size the AWT provides.

When 3D rectangles are drawn, the Graphics' color is set to light gray before drawing the rectangle, because draw3DRect() and fill3DRect() draw rectangles that only look three dimensional if they are drawn in light gray.

The applet is listed in its entirety in See RandomRectangles Applet.

RandomRectangles Applet

import java.applet.Applet;


import java.awt.*;
import java.awt.event.*;

public class RandomRectangles extends Applet {
  private static Color[] colors = { 
    Color.white, Color.black, Color.blue, Color.red, 
    Color.yellow, Color.orange, Color.cyan, Color.pink, 
    Color.magenta, Color.green };

  private int numRects = 10;
  private boolean fill = false, raise = false, 
          round = false, threeD = false;

  public void init() {
    Button rectsButton = new Button("rectangles");  
    Button roundButton = new Button("round rectangles");  
    Button threeDButton = new Button("3D rectangles");  
    Checkbox fillCheckbox = new Checkbox("fill");

    add(rectsButton);
    add(roundButton);
    add(threeDButton);
    add(fillCheckbox);

    rectsButton.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent event) {
        round = false;
        threeD = false;
        repaint();
      }
    });
    roundButton.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent event) {
        round = true;  
        threeD = false;
        repaint();
      }
    });
    threeDButton.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent event) {
        threeD = true;
        round = false;
        repaint();
      }
    });
    fillCheckbox.addItemListener(new ItemListener() {
      public void itemStateChanged(ItemEvent event) {
        fill = ((
          Checkbox)(event.getSource())).getState();
      }
    });
  }
  public void paint(Graphics g) {
    for(int i=0; i < numRects; i++) {
      Point lhc = randomPoint(); // left hand corner
      Dimension size = randomDimension();

      g.setColor(colors[(int)(Math.random()*10)]);

      if(round) {
        if(fill)
          g.fillRoundRect(
            lhc.x,lhc.y,size.width,size.height,
            (int)(Math.random()*250),
            (int)(Math.random()*250));
        else
          g.drawRoundRect(
            lhc.x,lhc.y,size.width,size.height,
            (int)(Math.random()*250),
            (int)(Math.random()*250));
      }
      else if(threeD) {
        g.setColor(Color.lightGray);

        if(fill)
          g.fill3DRect(
            lhc.x,lhc.y,size.width,size.height,raise);
        else
          g.draw3DRect(
            lhc.x,lhc.y,size.width,size.height,raise);
      }
      else {
        if(fill)
          g.fillRect(
          lhc.x,lhc.y,size.width,size.height);
        else
          g.drawRect(
          lhc.x,lhc.y,size.width,size.height);
      }
      raise = raise ? false : true;
    }
  }
  private Dimension randomDimension() {
    return new Dimension((int)(Math.random()*250),
              (int)(Math.random()*250));
  }
  private Point randomPoint() {
    return new Point((int)(Math.random()*250),
            (int)(Math.random()*250));
  }
}

Drawing Arcs

java.awt.Graphics provides the following methods for drawing and filling arcs:

  • void drawArc(int x, int y, int w, int h, int startAngle, int endAngle)
  • void fillArc(int x, int y, int w, int h, int startAngle, int endAngle)

Both methods are passed the same list of arguments. The first four arguments specify a coordinate path for a bounding box into which the arc will be drawn (or filled). The last two arguments specify the start and end angles of the arc in degrees. For example, the applet shown in See Drawing Arcs draws an arc with a start angle of 0 degrees and an end angle of 270 degrees whose width and height are specified as 151 and 101 pixels, respectively. The applet is listed in See DrawArc Applet.

DrawArc Applet

import java.applet.Applet;


import java.awt.*;

public class DrawArc extends Applet {
  public void paint(Graphics g) {
    g.setColor(Color.black);
    g.drawArc(10,10,150,100,0,270);
  }
}

The first four arguments passed to Graphics.drawArc() specify a coordinate path for the bounding rectangle--not the size of the rectangle itself. Since the graphic's pen hangs down and to the right of the coordinate path, the actual rectangle will always be one pixel wider and one pixel taller than the width and height arguments for the coordinate path--see See Drawing Shapes.

Graphics.fillArc() fills the interior of the coordinate path specified by the arguments it is passed. The applet listed in See DrawArc Applet is modified to fill the arc in addition to drawing it, as shown in See Drawing and Filling Arcs.

Notice that the black outline of the arc is visible on the right and bottom sides of the arc, even though the calls to drawArc() and fillArc() specify the same coordinate path. This is due to the fact that Graphics methods that draw shape outlines draw an extra row of pixels on the left and bottom sides of the shape.

Arcs are the only unclosed shape that can be filled. Filled arcs are closed by drawing lines that emanate from the center of the arc to its endpoints. The applet shown See Drawing and Filling Arcs is listed in See DrawAndFillArc Applet.

DrawAndFillArc Applet

import java.applet.Applet;


import java.awt.*;

public class DrawAndFillArc extends Applet {
  public void paint(Graphics g) {
    g.setColor(Color.black);
    g.drawArc(10,10,150,100,0,270);
    g.setColor(Color.yellow);
    g.fillArc(10,10,150,100,0,270);
  }
}

Drawing Ovals

The Graphics class provides methods for drawing and filling elliptical shapes:

  • void drawOval(int x, int y, int w, int h)
  • void fillOval(int x, int y, int w, int h)

The arguments passed to both methods specify a coordinate path for a bounding rectangle for the ellipse. If the width and height of the coordinate path are equal, then a circle is drawn. The oval is centered at the center of the bounding box, and just fits inside it.

As with other Graphics methods that draw shapes, drawOval() draws an ellipse that fits into a rectangle which is w+1 pixels wide and h+1 pixels high.

Drawing Polygons

Polygons may be drawn and filled by the following Graphics methods:

  • void drawPolygon(int[] xPoints, int[] yPoints, int numPoints)
  • void drawPolygon(Polygon polygon)
  • void drawPolygon(int[] xPoints, int[] yPoints, int numPoints)
  • void drawPolygon(Polygon polygon)

A polygon may be drawn or filled by specifying either a Polygon object, or arrays of x and y values specifying the points of the polygon. Polygons are automatically closed if the first and last points are not the same.

It is interesting to note that although the AWT provides non-graphical Polygon and Rectangle classes, there is no drawRect(Rectangle) method provided by the Graphics class, although there is a drawPolygon(Polygon).

Drawing Text

The Graphics class provides three methods for rendering text:

  • void drawString(String s, int x, int y)
  • void drawChars(char[], int offset, int length, int x, int y)
  • void drawBytes(byte[], int offset, int length, int x, int y)

The text may be specified as a string, an array of characters, or an array of bytes, depending upon which method is invoked.

All three methods for rendering text are passed an x,y location at which to draw the text. The location corresponds to the baseline of the text, and not the upper left-hand corner of the text, as is the case for rectangles, as depicted in See Drawing Strings With the drawString() Method.

The offset and length arguments passed to drawChars() and drawBytes() specifies an offset into the array at which to start drawing, and the number of characters to draw, respectively.

The Graphics class does not provide the capability to rotate text.


AWT Tip

Locations for Strings and Shapes Differ

The location specified for drawing a string specifies the baseline of the text, whereas the location specified for drawing a shape refers to the upper left-hand corner of the shape's boundary. If a string and rectangle are drawn with the same location specified for each, the string will appear above the rectangle.


Translating a Coordinate System's Origin

Unless specified otherwise, the origin of a Graphics' coordinate system coincides with the upper left-hand corner of its associated component, image or print job, as illustrated in See Graphics Coordinate System.

Graphics.translate() can be used to translate the origin to a new location. The translate method is passed two integer values representing a point in the original coordinate system that will become the origin of the translated coordinate system.

Translating a coordinate system's origin is done for numerous reasons; one reason is to scroll the contents of a container without scrollbars, as our next applet illustrates.

The applet shown in See Translating a Coordinate System's Origin displays an image that can be scrolled by dragging the mouse within the applet.

The scrolling is accomplished by translating the origin of the Graphics' coordinate system whenever a mouse drag event is detected, and repainting the image.

The top left picture in See Translating a Coordinate System's Origin shows the applet in its initial state. The top right picture shows the applet after the origin has been translated and the image has been redrawn. The bottom picture illustrates that the origin may be translated to negative x and y values.

The applet's init method loads the image, and its paint method draws the image at (0,0).

  public void init() {
  image = getImage(getCodeBase(), "saint.gif");
  try {
    MediaTracker mt = new MediaTracker(this);
    mt.addImage(image, 0);
    mt.waitForID(0);
  }
  catch(InterruptedException e) {
    e.printStackTrace();
  }
  }
  public void paint(Graphics g) {
  g.drawImage(image, 0, 0, this);
  }

init() employs an instance of MediaTracker to ensure that the image is fully loaded before it is displayed, but loading images is not the focus of our discussion here.

The applet defines inner class versions of mouse and mouse motion listeners. When the mouse is pressed in the applet, the location of the mouse press is saved.

  public class TranslateTest extends Applet {
  Image image;
  Point pressed = new Point(), lastTranslate = 
    new Point();...
  public void init() {
    ...
    addMouseListener(new MouseAdapter() {
      public void mousePressed(MouseEvent e) {
        Point loc = e.getPoint();
        // adjust mouse press location for
        // translation ...
        pressed.x = loc.x - lastTranslate.x;
        pressed.y = loc.y - lastTranslate.y;
      }
    });
    ...
  }...
  }

Again, the focus of our discussion here concerns translating the origin of a coordinate system and not event handling or inner classes. The gist of the event handling code is that when a mouse press occurs in the applet, mousePressed() listed above, is invoked.

The location of the mouse press is adjusted for the last translation point. The lastTranslate point is initially (0,0), 9 so the first time the mouse is pressed, the pressed location is the same as the mouse press coordinates. lastTranslate is updated every time the coordinate system is translated as the result of a mouse dragged event.

Its important to realize that mouse press point is adjusted for the last translation because the translations only apply to the copy of the component's Graphics obtained from the call to getGraphics(). The actual Graphics associated with the applet is never translated; only the copies of the actual Graphics get translated.

The call to Graphics.translate() takes place in mouseDragged() :

public class TranslateTest extends Applet {...  
  public void init() {
    ...
    addMouseMotionListener(new MouseMotionAdapter() {
      public void mouseDragged(MouseEvent e) {
        Point loc = e.getPoint();
        Point translate = new Point(loc.x - pressed.x,
                      loc.y - pressed.y);
        Graphics g = getGraphics();
  
        try {
          g.clearRect(0,0,
                getSize().width,getSize().height);
          g.translate(translate.x, translate.y);
          showStatus("Translating Graphics:  " + 
                translate);
  
          g.drawImage(image, 0, 0, TranslateTest.this);
        }
        finally {
          g.dispose();
        }
        lastTranslate = translate;
      }
    });
  }...
  }

As the mouse is dragged, a translate point is calculated by subtracting the coordinates of the original mouse press event from the coordinates of the mouse dragged event, as shown in See Translation Coordinates.

The copy of the Graphics obtained from getGraphics() has its origin translated to the calculated value.

There are a couple of points regarding the previous example worth reiterating concerning Graphics.

The first is that a call is made to Graphics.drawImage() to redraw the image after the translation. Since the applet's paint method also draws the image at (0,0), it might be tempting to simply call repaint() after translating the origin, and let paint() take care of rendering the image. However, remember that the Graphics object obtained in mouseDragged(), like the Graphics passed to paint(), are copies of the actual graphics context associated with the component. Invoking g.translate(translate.x, translate.y) translates the origin of the copy of the Graphics returned from getGraphics() but not the copy that would be passed to paint().10

Second, notice that a call is made to dispose of the Graphics obtained from the call to Component.getGraphics(). It is not necessary to dispose of the Graphics passed to paint().

Finally, if you run the applet from the CD in the back of the book, you will notice that the image flickers as it is being dragged. This is due to the fact that the erasing and rendering of the image is done in the onscreen graphics. The applet could easily be double buffered to eliminate the flicker.

The translateTest applet is listed in its entirety in See TranslateTest Applet.

Transla int h)

The first, as we have seen, creates an exact duplicate of the Graphics on whose behalf the method is invoked.

The second method also creates a duplicate; however, the arguments specify a translation (x,y) and a new clipping rectangle (x,y,w,h). The origin of the Graphics returned from create(int,int,int,int) is translated to the (x,y) coordinates, whereas the clipping rectangle winds up being the intersection of the original clipping rectangle with the specified rectangle.

The applet shown in See Creating a Graphics displays an image twice--once using the Graphics passed to paint(), and a second time using a Graphics copy that has had it's origin translated and clipping rectangle modified by the call to create().

The applet is listed in its entirety in See CreateTest Applet.

CreateTest Applet

  import java.applet.Applet;

  
  import java.awt.*;
  
  public class CreateTest extends Applet {
    private Image image;
  
    public void init() {
      MediaTracker mt = new MediaTracker(this); 
      image = getImage(getCodeBase(), "image.gif");
  
      try {
        mt.addImage(image, 0);
        mt.waitForID(0);
      }
      catch(Exception e) {
        e.printStackTrace();
      }
    }
    public void paint(Graphics g) {
      Graphics copy = 
        g.create(image.getWidth(this),0,100,100);
  
      try {
        System.out.println(
          "g:  " + g.getClip().toString());
        System.out.println("copy: " + 
                  copy.getClip().toString());
  
        g.drawImage(image, 0, 0, this);
        copy.drawImage(image, 0, 0, this);
      }
      finally {
        copy.dispose();
      }
    }
  }

An image is loaded and the applet's paint method creates a copy of the Graphics it is passed. The copy has its origin translated to (imw,0), where imw represents the width of the image.

The copy also has its clipping rectangle set to the intersection of the original Graphics' clipping rectangle, and the rectangle specified by (image.getWidth(this),0,100,100). Since the original Graphics' clipping rectangle covers the area occupied by the applet, the intersection of the two rectangles winds up being (image.getWidth(this),0,100,100).

The tip-off that the copy's Graphics has been translated comes from the fact that both calls to drawImage() draw the image at (0,0).

Summary

Unlike some object-oriented graphical toolkits, the AWT does not provide classes for specific shapes, such as Line and Circle classes. Instead, each AWT component comes with a Graphics object that is used to perform graphical operations in the component. In addition to components, other output devices, such as printers and offscreen images, also have associated Graphics for performing graphical operations.

java.awt.Graphics provides a wealth of methods for drawing and filling shapes, drawing text and setting graphical parameters such as the color and font used for the next rendering operation. However, in some respects Graphics are quite limited--for instance, the pen used for drawing is restricted in size to a one pixel square. The Java 2D API remedies many of the shortcomings of the Graphics class; however, the 2D API is not strictly a part of the AWT, and is beyond the scope of this book (see Graphic Java Mastering the JFC Volume III: The 2D API for an in-depth look at the 2D API).

The coordinate system for graphical operations is anchored in the upper left-hand corner of the device, with x and y coordinates increasing down and to the right, respectively. Coordinates lie in between pixels instead of on them, and Graphics methods that draw outlines of shapes are passed arguments that define the shape in terms of coordinates instead of pixels. The graphics pen hangs down and to the right of the coordinate path, and therefore outlines of shapes result in an extra row of pixels on the right and bottom sides of the shapes. Shape fills, on the other hand, fill the interior of the shape, and therefore the fill is the same size as the coordinate path. This discrepancy in size between shape outlines and fills is often a stumbling block to newcomers to the AWT.

Each Graphics corresponds to a graphics context from the underlying native windowing system. As a result, Graphics represent a finite resource that must be manually disposed of. If a Graphics is obtained by invoking a method that returns a reference to a Graphics, Graphics.dispose() must be called for the Graphics in order to free system resources. On the other hand, methods that are passed a Graphics reference are generally absolved from having to dispose of the Graphics. In general it is up to the caller of such methods to dispose of the Graphics.

The Graphics class also comes with a number of handy features such as: translating the origin of the coordinate system, clipping graphical operations to a specified shape, and the ability to create a copy of an existing Graphics.


Footnotes

1. java.awt.peer methods omitted
2. java.awt.peer methods omitted
3. Font metrics are associated with a specific font, so font metrics can be changed by changing the Graphics' font.
4. getSize() is a Component method.
5. under Windows95.
6. Windows and dialogs must also be disposed of.
7. psuedo-random number generators have repeating sequences.
8. The number of points equals the number of line segments - 1.
9. The Point no-argument constructor sets the point's location to (0,0).
10. Which is also the reason we must track the last translation point, as previously noted.

The author, David M. Geary, was the lead engineer for the user interface toolkit for Sun's Java Software Division's Java Management API. David has been developing GUIs and using object-oriented technology since 1984, including C++, Smalltalk, Eiffel, Objective-C and now Java. He is currently an independent Java programming consultant.


Reader Feedback

Tell us what you think of this article.

[Duke]

 Very worth reading  Worth reading  Not worth reading

If you have other comments or ideas for future articles, please type them here:


Print Button
[ This page was updated: 27-Sep-2000 ]
Products & APIs | Developer Connection | Docs & Training | Online Support
Community Discussion | Industry News | Solutions Marketplace | Case Studies
Glossary | Feedback | A-Z Index
For more information on Java technology
and other software from Sun Microsystems, call:
(800) 786-7638
Outside the U.S. and Canada, dial your country's AT&T Direct Access Number first.
Sun Microsystems, Inc.
Copyright © 1995-2000 Sun Microsystems, Inc.
All Rights Reserved. Terms of Use. Privacy Policy.