All Categories :
Java
Chapter 19
Java Graphics Fundamentals
by Michael Morrison
CONTENTS
Few Java applets would be interesting without at least some degree
of graphics. Knowing this, it's important for you to understand
the fundamentals of Java graphics so that you can make the most
of graphics in your applets. This chapter focuses on some of the
basic Java graphics techniques that will be important as you start
building your own applets. Although some of the graphics techniques
you learn about in this chapter may seem fairly simple, keep in
mind that they form the basis for more advanced graphics.
The chapter begins by explaining the Java graphics coordinate
system and the class used as the basis for most of the Java graphics
operations. The chapter then moves on to text and how it is drawn
using the standard Java graphics features. You finish up the chapter
by learning how images are used in the context of a Java applet.
By the end of the chapter, you will have a solid understanding
of graphics and how they are handled in Java applets.
All graphical computing systems use some sort of coordinate system
to specify the nature of points in the system. Coordinate systems
typically spell out the origin (0,0) of a graphical system, as
well as the axes and directions of increasing value for each of
the axes. The traditional mathematical coordinate system familiar
to most of us is shown in Figure 19.1.
Figure 19.1: The traditional coordinate system.
The graphical system in Java uses a coordinate system of its own
to specify how and where drawing operations take place. Because
all drawing in Java takes place within the confines of an applet
window, the Java coordinate system is realized by the applet window.
The coordinate system in Java has an origin located in the upper-left
corner of the window; positive X values increase to the right
and positive Y values increase down. All values in the Java coordinate
system are positive integers. Figure 19.2 shows how this coordinate
system looks.
Figure 19.2: The Java graphics coordinate system.
A topic that impacts almost every area of Java graphics is color.
Therefore, it's important to understand the underlying nature
of color and how it is modeled in Java and in computer systems
in general. Most computer systems take a similar approach to representing
color. The main function of color in a computer system is to accurately
reflect the physical nature of color within the confines of a
graphical system. This physical nature isn't hard to figure out;
anyone who has experienced the joy of Play-Doh can tell you that
colors react in different ways when they are combined with each
other. Like Play-Doh, a computer color system must be able to
mix colors with accurate, predictable results.
Color computer monitors provide possibly the most useful insight
into how software systems implement color. A color monitor has
three electron guns: red, green, and blue. The output from these
three guns converge on each pixel of the screen, exciting phosphors
to produce the appropriate color (see Figure 19.3). The combined
intensities of the guns determine the resulting pixel color. This
convergence of different colors from the monitor guns is very
similar to the convergence of different colored Play-Doh.
Figure 19.3: Electron guns in a color monitor converging to create a unique color.
Note |
Technically speaking, the result of combining colors on a monitor is different than that of combining similarly colored Play-Doh. Color combinations on a monitor are additive, meaning that mixed colors are emitted by the monitor, whereas Play-Doh color combinations are subtractive, meaning that mixed colors are absorbed. The additive or subtractive nature of a color combination depends on the physical properties of the particular medium involved.
|
The Java color system is very similar to the physical system used
by color monitors; it forms unique colors by using varying intensities
of the colors red, green, and blue. Therefore, Java colors are
represented by the combinations of the numeric intensities of
the primary colors (red, green, and blue). This color system is
known as RGB (Red Green Blue) and is standard across most graphical
computer systems.
Note |
Although RGB is the most popular computer color system in use, there are others. Another popular color system is HSB, which stands for Hue Saturation Brightness. In this system, colors are defined by varying degrees of hue, saturation, and brightness. The HSB color system is also supported by Java.
|
Table 19.1 shows the numeric values for the red, green, and blue
components of some basic colors. Notice that the intensities of
each color component range from 0 to 255 in value.
Table 19.1. RGB component values for some basic colors.
Color | Red
| Green | Blue
|
White | 255 |
255 | 255
|
Black | 0 |
0 | 0
|
Light Gray | 192
| 192 | 192
|
Dark Gray | 128
| 128 | 128
|
Red | 255 |
0 | 0
|
Green | 0 |
255 | 0
|
Blue | 0 |
0 | 255
|
Yellow | 255
| 255 | 0
|
Purple | 255
| 0 | 255
|
Java provides a class, Color,
for modeling colors. The Color
class represents an RGB color and provides methods for extracting
and manipulating the primary color components. The Color
class also includes constant members representing many popular
colors. You typically use the Color
class to specify the color when you are using many of Java's graphical
functions, which you learn about next.
Most of Java's graphics functions are accessible through a single
class, Graphics, found in
the Java AWT (Abstract Windowing Toolkit) package. The Graphics
class models a graphics context. A graphics context is
an abstract representation of a graphical surface that can be
drawn on. A graphics context is basically a way to allow you
to draw in a generic manner, without worrying about where the
drawing is physically taking place.
Graphics contexts are necessary so that the same graphics routines
can be used regardless of whether you are drawing to the screen,
to memory, or to a printer. The Graphics
class provides you with a graphics context to which you perform
all graphics functions. As you learn about the functionality provided
by the Graphics class, keep
in mind that its output is largely independent of the ultimate
destination, thanks to graphics contexts.
Graphical output code in a Java applet is usually implemented
in the applet's paint() method.
A Graphics object is passed
into the paint() method,
which is then used to perform graphical output to the applet window
(output surface). Because the Graphics
object is provided by paint(),
you never explicitly create a Graphics
object. Actually, you couldn't explicitly create a Graphics
object even if you wanted to because it is an abstract class.
Even though graphics operations often take place within the context
of an applet window, the output of the Graphics
object is really tied to a component. A component is a
generic graphical window and forms the basis for all other graphical
elements in the Java system. Java components are modeled at the
highest level by the Component
class, which is defined in the AWT package.
An applet window is just a specific type of component. Thinking
of graphics in terms of the Component
class rather than an applet window shows you that graphics can
be output to any object derived from the Component
class. As a matter of fact, every Component
object contains a corresponding Graphics
object that is used to render graphics on its surface.
Java graphics contexts (Graphics
objects) have a few attributes that determine how different graphical
operations are carried out. The most important of these attributes
is the color attribute, which determines the color used in graphics
operations such as drawing lines. You set this attribute using
the setColor() method defined
in the Graphics class. setColor()
takes a Color object as its
only parameter. Similar to setColor()
is setBackground(), which
is a method in the Component
class that determines the color of the component's background.
Graphics objects also have
a font attribute that determines the size and appearance of text.
This attribute is set using the setFont()
method, which takes a Font
object as its only parameter. You learn more about drawing text
and using the Font object
in "Drawing Text," later in this chapter.
Most of the graphics operations provided by the Graphics
class fall into one of the following categories:
- Drawing graphics primitives
- Drawing text
- Drawing images
Drawing Graphics Primitives
Graphics primitives consist of lines, rectangles, circles,
polygons, ovals, and arcs. You can create pretty impressive graphics
by mixing these primitives together; the Graphics
class provides methods for drawing these primitives. There are
also methods that act on primitives that form closed regions.
Closed regions are graphical elements with a clearly distinctive
inside and outside. For example, circles and rectangles are closed
regions, whereas lines and points are not. You can also use the
methods defined in the Graphics
class to erase the area defined by a primitive or fill it with
a particular color.
Lines
Lines are the simplest of the graphics primitives and are therefore
the easiest to draw. The drawLine()
method handles drawing lines, and is defined as follows:
void drawLine(int x1, int y1, int x2, int y2)
The first two parameters, x1
and y1, specify the
starting point for the line; the x2
and y2 parameters
specify the ending point. To draw a line in an applet, call drawLine()
in the applet's paint() method,
as in this example:
public void paint(Graphics g) {
g.drawLine(5, 10, 15, 55);
}
The results of this code are shown in Figure 19.4.
Figure 19.4: A line drawn using the drawLine() method.
Note |
Most graphical programming environments provide a means to draw lines (and other graphics primitives) in various widths. Java doesn't currently provide a facility to vary the width of lines, which is a pretty big limitation. Release 1.1 of Java will probably alleviate this problem.
|
Rectangles
Rectangles are also very easy to draw in Java. The drawRect()
method enables you to draw rectangles by specifying the upper-left
corner and the width and height of the rectangle. The drawRect()
method is defined in Graphics
as follows:
void drawRect(int x, int y, int width, int height)
The x and y
parameters specify the location of the upper-left corner of the
rectangle; the width
and height parameters
specify their namesakes, in pixels. To draw a rectangle using
drawRect(), just call it
from the paint() method like
this:
public void paint(Graphics g) {
g.drawRect(5, 10, 15, 55);
}
The results of this code are shown in Figure 19.5.
Figure 19.5: A rectangle drawn using the drawRect() method.
There is also a drawRoundRect()
method that allows you to draw rectangles with rounded corners:
void drawRoundRect(int x, int y, int width, int height, int arcWidth,
int arcHeight)
The drawRoundRect() method
requires two additional parameters than drawRect():
arcWidth and arcHeight.
These parameters specify the width and height of the arc forming
the rounded corners of the rectangle. Following is an example
of using drawRoundRect()
to draw a rectangle with rounded corners:
public void paint(Graphics g) {
g.drawRoundRect(5, 10, 15, 55, 6, 12);
}
The results of this code are shown in Figure 19.6.
Figure 19.6: A rounded rectangle drawn using the drawRoundRect() method.
In addition to these two basic rectangle-drawing methods, there
is also a method for drawing 3D rectangles with shadow effects
along the edges, draw3DRect().
However, the current limitation of Java only supporting a line
width of one pixel makes the 3D effect pretty subtle.
The Graphics class also provides
versions of each of these rectangle-drawing methods that fill
the interior of a rectangle with the current color in addition
to drawing the rectangle itself. These methods are named fillRect(),
fillRoundRect(), and fill3DRect(),
respectively. The support for drawing both unfilled and filled
rectangles is common throughout the Graphics
class when dealing with closed regions.
Note |
To draw a perfect square using any of the rectangle drawing methods, you simply use an equivalent width and height.
|
Polygons
Polygons are shapes consisting of a group of interconnected points;
each point in a polygon is connected in a series by lines. To
draw a polygon, you provide a list of points in the order that
they are to be connected to form the polygon shape. Polygons are
not closed regions by default; to make a polygon a closed region,
you must provide the same point as the starting and ending point
for the polygon.
Java provides two approaches to drawing polygons: a method and
a class. The most straightforward approach is using the drawPolygon()
method, which is defined as follows:
void drawPolygon(int xPoints[], int yPoints[], int nPoints)
The first two parameters, xPoints
and yPoints, are arrays
containing the x and y components of each coordinate in the polygon.
For example, xPoints[0]
and yPoints[0]
form the starting point for the polygon. The last parameter to
drawPolygon(), nPoints,
is the number of points in the polygon; this value is typically
equal to the length of the xPoints
and yPoints arrays.
The following example uses the drawPolygon()
method to draw a closed polygon:
public void paint(Graphics g) {
int xPts[] = {5, 25, 50, 30, 15, 5};
int yPts[] = {10, 35, 20, 65, 40, 10};
g.drawPolygon(xPts, yPts, xPts.length);
}
The results of this code are shown in Figure 19.7.
Figure 19.7: A closed polygon drawn using the drawPolygon() method.
Note |
The polygon in Figure 19.7 is closed because the same point (5, 10) is defined both at the beginning and end of the polygon coordinate list. Without duplicating this point at the end of the list, the last point would not have been connected back to the first point and the polygon would have remained an open region.
|
The other approach to drawing polygons involves using the Polygon
class to construct a Polygon
object. Once you have a Polygon
object, you can simply pass it to drawPolygon()
instead of passing the x and y point arrays. You construct a Polygon
object using one of the Polygon
class's constructors, which are defined as follows:
Polygon()Polygon(int xpoints[], int ypoints[], int npoints)
The first constructor simply creates a default polygon with an
empty coordinate list; the second constructor is initialized with
a coordinate list much like the one taken by the drawPolygon()
method just described. Regardless of how you create a Polygon
object, you can add points to the polygon by using the addPoint()
method, which is defined as follows:
void addPoint(int x, int y)
The following example uses a Polygon
object to draw a closed, filled polygon:
public void paint(Graphics g) {
int xPts[] = {5, 25, 50, 30, 15, 5};
int yPts[] = {10, 35, 20, 65, 40, 10};
Polygon poly = new Polygon(xPts, yPts, xPts.length);
g.fillPolygon(poly);
}
The results of this code are shown in Figure 19.8.
Figure 19.8: A closed polygon drawn using a polygon object and the fillPolygon() method.
I mentioned in the previous section that many of the methods for
drawing graphics primitives in Java support both unfilled and
filled versions. Figure 19.8 is an example of using the filled
version for polygons, fillPolygon().
You may not see why there is ever a need for using a Polygon
object, considering the fact that I used basically the same technique
for representing the set of points that make up the polygon. The
truth is that Polygon objects
are very useful whenever you know you'll need to draw a particular
polygon again. Consider the asteroids floating around in a space
game; you could easily model the asteroids as polygons and use
a Polygon object to represent
each. When it comes time to draw the asteroids, all the messy
coordinates are already stored within each Polygon
object, so you only have to pass the object to the drawPolygon()
or fillPolygon() method.
Ovals
Another useful graphics primitive supported by Java is the oval,
which is a rounded, closed region. You specify an oval using a
coordinate for the top left corner of the oval, along with the
width and height of the oval. This is basically the same approach
used when drawing rectangles with square corners; the difference
of course being that ovals are round regions, not rectangular
regions. The primary method for drawing ovals is drawOval(),
which is defined as follows:
void drawOval(int x, int y, int width, int height)
The x and y
parameters specify the location of the upper-left corner of the
oval; the width and
height parameters
specify their namesakes. You draw an oval using drawOval()
like this:
public void paint(Graphics g) {
g.drawOval(5, 10, 15, 55);
}
The results of this code are shown in Figure 19.9.
Figure 19.9: An oval drawn using the drawOval() method.
Like the drawing methods you learned about for other graphics
primitives, the Graphics
class also provides a method to draw filled ovals, fillOval().
Note |
To draw a perfect circle using the drawOval() or fillOval() methods, you simply use an equivalent width and height.
|
Arcs
Unlike the graphics primitives you've learned about so far, arcs
are a little more complex to handle. An arc is basically a section
of an oval; just picture erasing part of an oval and what you
have left is an arc. Because an arc is really just part of an
oval, you draw an arc in relation to the complete oval it is a
part of. In other words, to specify an arc, you must specify a
complete oval along with what section of the oval the arc comprises.
If you still don't quite follow, maybe taking a look at the Java
method for drawing arcs will help clear things up:
void drawArc(int x, int y, int width, int height, int startAngle, int arcAngle)
As you can see, the first four parameters are the same ones used
in the drawOval() method.
In fact, these parameters define the oval of which the arc is
a part. The remaining two parameters define the arc as a section
of this oval. To understand how these parameters define an arc,
refer to Figure 19.10, which shows an arc as a section of an oval.
Figure 19.10: An area defined as a section of an oval.
As you can see in Figure 19.10, an arc can be defined within an
oval by specifying the starting and ending angles for the arc.
Alternatively, you can specify just the starting angle and a number
of degrees to sweep in a particular direction. In the case of
Java, an arc is defined in the latter manner: with a starting
angle and a sweep angle, or arc angle. The sweep direction in
Java is counterclockwise, meaning that positive arc angles are
counterclockwise and negative arc angles are clockwise. The arc
shown in Figure 19.10 has a starting angle of 95 degrees and an
arc angle of 115 degrees. The resulting ending angle is the sum
of these two angles, which is 210 degrees.
Following is an example of drawing a similar arc using the drawArc()
method:
public void paint(Graphics g) {
g.drawArc(5, 10, 150, 75, 95, 115);
}
The results of this code are shown in Figure 19.11.
Figure 19.11: An arc drawn using the drawArc() method.
Like most of the graphics primitives in Java, there is a method
for drawing filled arcs, fillArc().
The fillArc() method is very
useful because you can use it to draw pie-shaped pieces of a circle
or oval. For example, if you are writing an applet that needs
to draw a pie graph for a set of data, you can use the fillArc()
method to draw each piece of the pie.
Drawing Text
Because Java applets are entirely graphical in nature, you must
use the Graphics object even
when you want to draw text. Fortunately, drawing text is very
easy and yields very nice results. You will typically create a
font for the text and select it as the font to be used by the
graphics context before actually drawing any text. As you learned
earlier, the setFont() method
selects a font into the current graphics context. This method
is defined as follows:
void setFont(Font font)
The Font object models a
textual font and includes the name, point size, and style of the
font. The Font object supports
three different font styles, which are implemented as the following
constant members: BOLD, ITALIC,
and PLAIN. These styles are
really just constant numbers and can be added together to yield
a combined effect. The constructor for the Font
object is
defined as follows:
Font(String name, int style, int size)
As you can see, the constructor takes as parameters the name,
style, and point size of the font. If you are wondering exactly
how font names work, you simply provide the string name of the
font you want to use. The names of the most common fonts supported
by Java are TimesRoman, Courier, and Helvetica. To create a bold,
italic, Helvetica, 22-point font, you use the following code:
Font f = new Font("Helvetica",
Font.BOLD + Font.ITALIC, 22);
Caution |
Some systems may support other fonts beyond the three common fonts mentioned here (TimesRoman,
Courier, and Helvetica). Even though you are free to use other fonts, keep in mind that these
three common fonts are the only ones guaranteed to be supported across all systems. In other
words, it's much safer to stick with these fonts.
|
After you've created a font, you will often want to create a FontMetric
object to find out the details of the font's size. The FontMetric
class models very specific placement information about a font,
such as the ascent, descent, leading, and total height of the
font. Figure 19.12 shows what each of these font metric attributes
represent.
Figure 19.12: The different font metric attributes.
You can use the font metrics to precisely control the location
of text you are drawing. After you have the metrics under control,
you just need to select the original Font
object into the Graphics
object using the setFont()
method, as in the following:
g.setFont(f);
Now you're ready to draw some text using the font you've created,
sized up, and selected. The drawString()
method, defined in the Graphics
class, is exactly what you need. drawString()
is defined as follows:
void drawString(String str, int x, int y)
The drawString() method takes
a String object as its first
parameter, which determines the text to be drawn. The last two
parameters specify the location at which the string is drawn;
x specifies the left
edge of the text and y
specifies the baseline of the text. The baseline of the text is
the bottom of the text, not including the descent. Refer to Figure
19.12 if you are having trouble visualizing this.
The DrawText sample applet
(see Listing 19.1) demonstrates drawing a string centered in the
applet window. Figure 19.13 shows the DrawText
applet in action.
Figure 19.13: The DrawText sample applet.
Listing 19.1. The DrawText
sample applet.
// DrawText Class
// DrawText.java
// Imports
import java.applet.*;
import java.awt.*;
public class DrawText extends Applet {
public void paint(Graphics g) {
Font font = new Font("Helvetica", Font.BOLD +
Font.ITALIC, 22);
FontMetrics fm = g.getFontMetrics(font);
String str = new
String("The highest result of education is tolerance.");
g.setFont(font);
g.drawString(str, (size().width - fm.stringWidth(str)) / 2,
((size().height - fm.getHeight()) / 2) + fm.getAscent());
}
}
The DrawText applet uses
the font-related methods you just learned to draw a string centered
in the applet window. You may be wondering about the calls to
the size() method when the
location to draw the string is being calculated. The size()
method is a member of Component
and returns a Dimension object
specifying the width and height of the applet window.
That sums up the basics of drawing text using the Graphics
object. Now it's time to move on to one of the most important
aspects of Java graphics: images.
Drawing Images
Images are rectangular graphical objects composed of colored
pixels. Each pixel in an image describes the color at that particular
location of the image. Pixels can have unique colors that are
usually described using the RGB color system. Java provides support
for working with 32-bit images, which means that each pixel in
an image is described using 32 bits. The red, green, and blue
components of a pixel's color are stored in these 32 bits, along
with an alpha component. The alpha component of a pixel
refers to the transparency or opaqueness of the pixel.
Before getting into the details of how to draw an image, you need
to first learn how to load images. The getImage()
method, defined in the Applet
class, is used to load an image from a URL. getImage()
comes in two versions, which are defined as follows:
Image getImage(URL url)
Image getImage(URL url, String name)
These two versions essentially perform the same function; the
only difference is that the first version expects a fully qualified
URL, including the name of the image, and the second version enables
you to specify a separate URL and image name.
You probably noticed that both versions of getImage()
return an object of type Image.
The Image class represents
a graphical image, such as a GIF or JPEG file image, and provides
a few methods for determining the width and height of the image.
Image also includes a method
for retrieving a graphics context for the image, which enables
you to draw directly onto an image.
The Graphics class provides
a handful of methods for drawing images, which follow:
boolean drawImage(Image img, int x, int y,
ImageObserver observer)
boolean drawImage(Image img, int x, int y, int width, int
height,
ImageObserver observer)
boolean drawImage(Image img, int x, int y, Color bgcolor, ImageObserver observer)
boolean drawImage(Image img, int x, int y, int width, int height, Color bgcolor,
ImageObserver observer)
All these methods are variations on the same theme: they all draw
an image at a certain location as defined by the parameters x
and y. The last parameter
in each method is an object of type ImageObserver,
which is used internally by drawImage()
to get information about the image.
The first version of drawImage()
draws the image at the specified x
and y location-x
and y represent the
upper-left corner of the image. The second version draws the image
inside the rectangle formed by x,
y, width,
and height. If this
rectangle is different than the image rectangle, the image is
scaled to fit. The third version of drawImage()
draws the image with transparent areas filled in with the background
color specified in the bgcolor
parameter. The last version of drawImage()
combines the capabilities in the first three, enabling you to
draw an image within a given rectangle and with a background color.
The process of drawing an image involves calling the getImage()
method to load the image, followed by a call to drawImage(),
which actually draws the image on a graphics context. The DrawImage
sample applet (shown in Listing 19.2) shows how easy it is to
draw an image (see Figure 19.14). This applet shows how easy it
is to immortalize your friends by plastering their image on the
Web; it displays a picture of my good friend, Keith Nash, who
is clearly having a bad hair day!
Figure 19.14:The DrawImage sample applet.
Listing 19.2. The DrawImage
sample applet.
// DrawImage Class
// DrawImage.java
// Imports
import java.applet.*;
import java.awt.*;
public class DrawImage extends Applet {
public void paint(Graphics g) {
Image img = getImage(getCodeBase(), "Dude.gif");
g.drawImage(img, (size().width - img.getWidth(this)) / 2,
(size().height - img.getHeight(this)) / 2, this);
}
}
The DrawImage sample applet
loads an image in the paint()
method using getImage().
The getCodeBase() method
is used to specify the applet directory where applet resources
are usually located, while the image name itself is simply given
as a string. The image is then drawn centered in the applet window
using the drawImage() method.
It's as simple as that!
This chapter bombarded you with a lot of information about the
graphics support in Java. Most of it was centered around the Graphics
object, which is fortunately pretty straightforward to use. You
began by learning about color and what it means in the context
of Java. You moved on to drawing a variety of different graphics
primitives. You then learned how text is drawn in Java through
the use of fonts and font metrics. Finally, you concluded the
chapter by taking a look at images and how they are drawn.
This chapter showed you that Java not only provides a rich set
of graphics features, but that is also has a very clean interface
through which you can explore the use of graphics in your own
applets. If the graphics features you learned about in this chapter
seem a little tame, you may be ready to jump into the next chapter,
which covers animation programming.
Contact
reference@developer.com with questions or comments.
Copyright 1998
EarthWeb Inc., All rights reserved.
PLEASE READ THE ACCEPTABLE USAGE STATEMENT.
Copyright 1998 Macmillan Computer Publishing. All rights reserved.