TOC
BACK
FORWARD
HOME

Java 1.1 Unleashed

- 20 -
Java Graphics Fundamentals

by Michael Morrison

IN THIS CHAPTER

  • The Graphics Coordinate System
  • The Basics of Color
  • The Graphics Class

Few Java applets, and few Java applications for that matter, would be interesting without at least some 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 and applications.

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

Figure 20.1.

The traditional coordinate system.

The graphics 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 or application window, the Java coordinate system is realized by the 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 20.2 shows how this coordinate system looks.

Figure 20.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's 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 20.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.


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

Figure 20.3.

Electron guns in a color monitor converge to create a unique color.

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 20.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 20.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 you can draw on. A graphics context is basically a way for 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 or application is usually implemented in the paint() method. A Graphics object is passed into the paint() method, which is then used to perform graphical output to the applet or application 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 a 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 or application window is just a specific type of component. Thinking of graphics in terms of the Component class rather than a 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 the section "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 distinguishable inside and outside. For example, circles and rectangles are closed regions, but 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 20.4.

Figure 20.4.

A line drawn with the drawLine() method.


NOTE: Most graphical programming environments provide a way 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.

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

Figure 20.5.

A rectangle drawn with 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 more 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 that uses 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 20.6.

Figure 20.6.

A rounded rectangle drawn with 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, because Java has the limitation of only supporting a line width of one pixel, the 3D effect is 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 fillRect(), fillRoundRect(), and fill3DRect(). The support for drawing both filled and unfilled 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, 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 in which 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 20.7.

Figure 20.7.

A closed polygon drawn with the drawPolygon() method.


NOTE: The polygon in Figure 20.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 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 20.8.

Figure 20.8.

A closed polygon drawn with a Polygon object and the fillPolygon() method.

In the previous section, I mentioned that many of the methods for drawing graphics primitives in Java support both unfilled and filled versions. Figure 20.8 is an example of using the filled version for polygons, fillPolygon().

You may not see why there is ever a need to use a Polygon object, considering the fact that I used the same technique for representing the set of points that make up the polygon for both the drawPolygon() method and the Polygon object. The truth is that Polygon objects are very useful whenever you know you have to draw a particular polygon again. Consider the asteroids floating around in a space game; you can 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, is 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 the drawOval() method, like this:

public void paint(Graphics g) {
  g.drawOval(5, 10, 15, 55);
}

The results of this code are shown in Figure 20.9.

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, simply use equivalent width and height values.

Figure 20.9.

An oval drawn with the drawOval() method.

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 of the drawArc() method 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 20.10, which shows an arc as a section of an oval.

As you can see in Figure 20.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 20.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.

Figure 20.10.

An arc defined as a section of an oval.

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

Figure 20.11.

An arc drawn with 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 has 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 (and graphical Java applications) 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 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 its 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 in addition to 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 three fonts if you want your programs to be truly cross platform.

After you create a font, you 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 (pronounced ledding), and total height of the font. Fig- ure 20.12 shows what these font metric attributes represent.

You can use the font metrics to precisely control the location of the text you are drawing. After you have the metrics under control, you just have to select the original Font object into the Graphics object using the setFont() method, as in the following example:

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)

Figure 20.12.

The different font metric attributes.

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 20.12 if you are having trouble visualizing these pieces.

The DrawText sample applet in Listing 20.1 demonstrates how to draw a string centered in the applet window. Figure 20.13 shows the DrawText applet in action.

Listing 20.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, (getSize().width - fm.stringWidth(str)) / 2,
      ((getSize().height - fm.getHeight()) / 2) + fm.getAscent());
  }

}

The DrawText applet uses the font-related methods you just learned about 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 that specifies 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 aspect of Java graphics: images.

Figure 20.13.

The DrawText sample applet.

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 must 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)
boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1,
  int sy1, int sx2, int sy2, ImageObserver observer)
boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1,
  int sy1, int sx2, int sy2, 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 defined rectangle. The third version of drawImage() draws the image with transparent areas filled in with the background color specified in the bgcolor parameter. The fourth version of drawImage() combines the capabilities of the first three, enabling you to draw an image within a given rectangle and with a background color.

The last two versions of drawImage() are new to Java 1.1 and provide support for clipping and flipping images. Before these versions of drawImage() were available, you had to clip an image using a cropping filter or the clipping mechanism provided in the Graphics class. The clipping mechanism in the Graphics class is limited because it can be set only to a more restrictive value--which means that it isn't very versatile. Furthermore, Java 1.0 really provided no direct means of flipping images. The last two drawImage() methods support both clipping and flipping by allowing you to specify the source and destination rectangles for the draw. Flipping is accomplished by swapping the x and y extents of the destination rectangle, which results in a reverse draw. The last of the drawImage() methods allows you to specify a background color to be drawn in transparent areas of the image.

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 (presented in Listing 20.2) shows how easy it is to draw and flip an image (see Figure 20.14). This applet also shows how easy it is to immortalize your friends by plastering their images on the Web; Figure 20.14 displays a picture of my good friend, Keith Nash, and his twin brother Ned. Actually, "both" people shown in the applet are Keith--the applet inverts the original image horizontally to draw it a second time.

Listing 20.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, 0, 0, this);
    g.drawImage(img, img.getWidth(this) * 2, 0, img.getWidth(this),
      img.getHeight(this), 0, 0, img.getWidth(this), img.getHeight(this),
      this);
  }

}

Figure 20.14.

The DrawImage sample applet.

The DrawImage sample applet loads an image in the paint() method using getImage(). The getCodeBase() method is used to specify the applet directory in which applet resources are usually located, while the image name itself is simply given as a string. The image is then drawn in the applet window using the basic drawImage() method. The more versatile drawImage() method is then used to draw the same image flipped horizontally next to the first image. The flipping is carried out simply by swapping the horizontal values on the destination rectangle, resulting in a reverse draw horizontally. 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 it 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.

TOCBACKFORWARDHOME


©Copyright, Macmillan Computer Publishing. All rights reserved.