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.

The Graphics Coordinate System

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.

The Basics of Color

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.

The Graphics Class

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

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!

Summary

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.