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:
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.