BUY THIS BOOK
This print book is out of stock, with no immediate plans to reprint.

Safari Books Online

What is this?


Looking to Reprint this content?


Java 2D Graphics
Java 2D Graphics By Jonathan Knudsen
May 1999
Pages: 366

Cover | Table of Contents | Colophon


Table of Contents

Chapter 1: Introduction
This chapter describes Java 2D's roots, contributors, related technologies, and capabilities. I'll also explain how you can obtain a Graphics2D object in your application, and then I'll present a useful class that will be used throughout the book. Finally, the chapter concludes with a "teaser" example that shows off some of Java 2D's features.
The Java 2D Application Programming Interface (the 2D API) is a set of classes that can be used to create high quality graphics. It includes features like geometric transformation, antialiasing, alpha compositing, image processing, and bidirectional text layout, just to name a few. Don't worry if you don't know what some of these features are — I'll explain them all.
Java 2D is part of the core classes of the Java 2 platform (formerly JDK 1.2). The 2D API introduces new classes in the following packages:
  • java.awt
  • java.awt.image
In addition, the 2D API encompasses six entirely new packages:
  • java.awt.color
  • java.awt.font
  • java.awt.geom
  • java.awt.print
  • java.awt.image.renderable
  • com.sun.image.codec.jpeg
All of these packages are part of the core Java 2 platform, except com.sun.image.code.jpeg. This means that, except for the JPEG package, you can rely on the 2D API in all implementations of the Java 2 platform.
This book covers all of the new packages, with the exception of
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
What Is Java 2D?
The Java 2D Application Programming Interface (the 2D API) is a set of classes that can be used to create high quality graphics. It includes features like geometric transformation, antialiasing, alpha compositing, image processing, and bidirectional text layout, just to name a few. Don't worry if you don't know what some of these features are — I'll explain them all.
Java 2D is part of the core classes of the Java 2 platform (formerly JDK 1.2). The 2D API introduces new classes in the following packages:
  • java.awt
  • java.awt.image
In addition, the 2D API encompasses six entirely new packages:
  • java.awt.color
  • java.awt.font
  • java.awt.geom
  • java.awt.print
  • java.awt.image.renderable
  • com.sun.image.codec.jpeg
All of these packages are part of the core Java 2 platform, except com.sun.image.code.jpeg. This means that, except for the JPEG package, you can rely on the 2D API in all implementations of the Java 2 platform.
This book covers all of the new packages, with the exception of java.awt.image.renderable. This package serves as a bridge to the Java Advanced Imaging API (JAI), which is outside the scope of this book.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
What Can Java 2D Do?
Java 2D is designed to do anything you want it to do (with computer graphics, at least). Prior to Java 2D, AWT's graphics toolkit had some serious limitations:
  • All lines were drawn with a single-pixel thickness.
  • Only a handful of fonts were available.
  • AWT didn't offer much control over drawing. For example, you couldn't manipulate the individual shapes of characters.
  • If you wanted to rotate or scale anything, you had to do it yourself.
  • If you wanted special fills, like gradients or patterns, you had to make them yourself.
  • Image support was rudimentary.
  • Control of transparency was awkward.
The 2D API remedies these shortcomings and does a lot more, too. To appreciate what the 2D API can offer, you need to see it in action. Java 2 includes a sample program that demonstrates many of the features of the API. To run it, navigate to the demo/jfc/Java2D directory in the JDK installation directory. Then run the Java2Demo class. For example:
C:> cd \jdk1.2\demo\jfc\Java2D
C:> java Java2Demo
         
Figure 1.1: Sun's 2D demo
You should see a window that looks like Figure 1.1. Each of the tabs across the top displays a set of 2D's features. Spend some time with this application. Then come back and read about all the things 2D can do, including:
shapes
Arbitrary geometric shapes can be represented by combinations of straight lines and curves. The 2D API also provides a useful toolbox of standard shapes, like rectangles, arcs, and ellipses. See Chapter 3, for details.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Relatives
The Abstract Windowing Toolkit (AWT) that comes with JDK 1.0 and 1.1 is a large set of classes that encapsulate windows, controls, fonts, images, and drawing. However, the AWT lacks a number of important features, as users of more mature graphics toolkits were quick to point out. Instead of applying a quick fix, the engineers at Sun created the largest, most powerful graphics toolkit yet, the Java Foundation Classes (JFC). JFC is included with Java 2. The 2D API is part of JFC. It is a "core" API, which means that it is present in every implementation of Java 2. It cannot run in older versions of the JDK, however.
To understand how 2D fits into the larger scheme of things, it's helpful to examine how it evolved from AWT. Conceptually, AWT can be split into two pieces: a user interface (UI) toolkit and a drawing toolkit. Between JDK 1.1 and Java 2 (JDK 1.2), these two pieces evolved considerably. The UI toolkit became Swing, and the drawing toolkit became the 2D API.
In this section, I'll explain how Java 2D relates to some other APIs and buzzwords:
Java Foundation Classes (JFC)
Java 2D is one part of JFC. The other parts are AWT, Swing, the Accessibility API, and the Drag and Drop API. See http://java.sun.com/products/jfc/ for details.
AWT
In Java 2, you can use the 2D API to draw on AWT components. AWT is described in books such as John Zukowski's Java AWT Reference (published by O'Reilly & Associates, Inc.).
Swing
As with AWT components in Java 2, you can use 2D to draw on Swing components. You may want to use 2D to develop your own components or your own look and feel. For more on Swing, see Java Swing , by Robert Eckstein, Marc Loy, and Dave Wood (O'Reilly). Online information is also available at
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Genesis
The Java people at Sun have a crazy ambition to redefine all of computing. Each new version of the Java platform includes vastly expanded capabilities. Between the JDK itself and the extension APIs, Sun seems intent on making Java able to do anything you could possibly want to do with a computer. In order to create the 2D API, the good people at Sun conspired with several industry partners, including the following four companies.
Sun's most important partner for the 2D API was Adobe Systems, Inc. These are the people who developed the PostScript language as well as an impressive lineup of graphics and text applications, including Framemaker, Acrobat, Illustrator, and others. Adobe helped Sun design the 2D API. If you're familiar with PostScript, you'll probably see echoes of it in the classes and methods of the 2D API. Adobe's web site is at http://www.adobe.com/.
A small company called Ductus provided a key piece of the 2D API's implementation, called a rasterizer. The rasterizer handles the task of representing idealized mathematical shapes on output devices with pixels, like monitors and printers. You can read more about Ductus at their web site, http://www.ductus.com/.
Another important partner was Eastman Kodak (http://www.kodak.com/). Sun worked closely with Kodak to develop the imaging and color management classes in the 2D API. Some of the implementation of these classes is based on technology licensed from Kodak. Just as Adobe helped design the graphics part of the 2D API, Kodak helped with the design and implementation of the imaging and color management portions of the 2D API.
It's a funny industry we work in: Taligent, one of Sun's partners in the 2D API, no longer exists. Formerly an IBM subsidiary, Taligent has now been reabsorbed into the mother ship. During Taligent's independent existence, however, Sun licensed two technologies from them for use in the 2D API: bidirectional text layout and constructive area geometry.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Where Do I Get a Graphics2D?
Shapes, text, and images are all ultimately drawn by a Graphics2D object. But where does the Graphics2D come from? As usual, there's more than one way to do it.
Every Component that AWT shows on the screen has a paint() method. The system passes a Graphics to this method. In JDK 1.1 and earlier, you could draw on Components by overriding the paint() method and using the Graphics to draw things.
It works exactly the same way in Java 2, except that it's a Graphics2D that is passed to paint(). To take advantage of all the spiffy 2D features, you'll have to perform a cast in your paint() method, like this:
public void paint(Graphics g) {
  Graphics2D g2 = (Graphics2D)g;
  // Now we can do cool 2D stuff.
}
Note that your component may not necessarily be drawn on the screen. The Graphics2D that gets passed to paint() might actually represent a printer or any other output device.
Swing components work almost the same way. Strictly speaking, however, you should implement the paintComponent() method instead of paint(). Swing uses the paint() method to draw child components. Swing's implementation of paint() calls paintComponent() to draw the component itself. You may be able to get away with implementing paint() instead of paintComponent(), but then don't be surprised if the component is not drawn correctly.
You can use a Graphics or Graphics2D to draw on images, as well. If you have an Image that you have created yourself, you can get a corresponding Graphics2D by calling createGraphics(), as follows:
public void drawOnImage(Image i) {
  Graphics g = i.getGraphics();
  // Now draw on the image using g.
}
This works only for any Image you've created yourself, not for an Image loaded from a file.
If you have a BufferedImage (Java 2D's new image class), you can obtain a
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
File Formats
There are many, many ways to store graphics information in a file. In this section I'll briefly describe two formats, GIF and JPEG. These formats are common currencies of the Internet—any web browser that shows images knows how to show GIF and JPEG images. Similarly, the JDK can load and display GIF or JPEG images.
For more detailed information on these formats, or on other popular graphics file formats, see the Encyclopedia of Graphics File Formats , by James D. Murray and William vanRyper (O'Reilly).
GIF stands for Graphics Interchange Format. GIF images can have 2 to 256 colors and are compressed before being stored. The compression algorithm is lossless, which means that the original picture will be restored verbatim when the image is decompressed and displayed.
There are actually two common flavors of this format, GIF87a and GIF89a. GIF89a offers the option of designating one of the image colors as transparent. Applications that know how to show GIF89a images correctly will allow the background to show through the transparent areas of the image. You've probably seen these "transparent GIFs" in web pages.
GIF89a also supports simple animations, which you've probably seen in web pages. These are called animated GIFs ; they've been supported in Java since JDK 1.1. For more information on animated GIFs, see GIF Animation Studio by Richard Koman (published by Songline Studios, Inc.).
JPEG stands for Joint Photographic Experts Group. Unlike some other file formats, it was designed specifically for photographic images. JPEG images support more colors than GIF images, up to 24 bits per pixel. JPEG images are compressed before being stored using a lossy compression algorithm. This means that when the image is loaded and displayed, it will not be exactly the same as the original image. The 2D API includes support for reading and writing JPEG files in the
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Hello, 2D!
This chapter ends with a bang—an example that demonstrates the power of the 2D API. You probably won't understand much of the code at this point, but rest assured that it all will become clear as you work through the rest of the book.
In general terms, this is what the example does:
  • The example draws a background of colored circles.
  • Then the example draws an image. The image is broken into small pieces, and each piece is drawn partially transparent, allowing the circles to show through. The image is Raphael's self-portrait, taken from the Virtual Uffizi at http://www.arca.net/uffizi/.
  • Finally, the example draws some text on a color-gradient-filled background. Then the text is drawn a second time, rotated 90°.
The results are shown in Figure 15.1. This is a less than 200 lines of code (with lots of comments). It's a small subset of what can be accomplished with the 2D API.
Note that this example depends on the ApplicationFrame class presented earlier in this chapter. If you haven't entered and compiled ApplicationFrame, do it now.
import java.awt.*;
import java.awt.event.*;
import java.awt.font.*;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.Random;

import com.sun.image.codec.jpeg.*;

public class ShowOff
    extends Component {
  public static void main(String[] args) {
    try {
      // The image is loaded either from this
      //   default filename or the first command-
      //   line argument.
      // The second command-line argument specifies
      //   what string will be displayed. The third
      //   specifies at what point in the string the
      //   background color will change.
      String filename = "Raphael.jpg";
      String message = "Java2D";
      int split = 4;
      if (args.length > 0) filename = args[0];
      if (args.length > 1) message = args[1];
      if (args.length > 2) split = Integer.parseInt(args[2]);
      ApplicationFrame f = new ApplicationFrame("ShowOff v1.0");
      f.setLayout(new BorderLayout());
      ShowOff showOff = new ShowOff(filename, message, split);
      f.add(showOff, BorderLayout.CENTER);
      f.setSize(f.getPreferredSize());
      f.center();
      f.setResizable(false);
      f.setVisible(true);
    }
    catch (Exception e) {
      System.out.println(e);
      System.exit(0);
    }
  }
  
  private BufferedImage mImage;
  private Font mFont;
  private String mMessage;
  private int mSplit;
  private TextLayout mLayout;
  
  public ShowOff(String filename, String message, int split)
      throws IOException, ImageFormatException {
    // Get the specified image.
    InputStream in = getClass().getResourceAsStream(filename);
    JPEGImageDecoder decoder = JPEGCodec.createJPEGDecoder(in);
    mImage = decoder.decodeAsBufferedImage();
    in.close();
    // Create a font.
    mFont = new Font("Serif", Font.PLAIN, 116);
    // Save the message and split.
    mMessage = message;
    mSplit = split;
    // Set our size to match the image's size.
    setSize((int)mImage.getWidth(), (int)mImage.getHeight());
  }

  public void paint(Graphics g) {
    Graphics2D g2 = (Graphics2D)g;
    
    // Turn on antialiasing.
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
        RenderingHints.VALUE_ANTIALIAS_ON);

    drawBackground(g2);
    drawImageMosaic(g2);
    drawText(g2);
  }
  
  protected void drawBackground(Graphics2D g2) {
    // Draw circles of different colors.
    int side = 45;
    int width = getSize().width;
    int height = getSize().height;
    Color[] colors = { Color.yellow, Color.cyan, Color.orange,
        Color.pink, Color.magenta, Color.lightGray };
    for (int y = 0; y < height; y += side) {
      for (int x = 0; x < width; x += side) {
        Ellipse2D ellipse = new Ellipse2D.Float(x, y, side, side);
        int index = (x + y) / side % colors.length;
        g2.setPaint(colors[index]);
        g2.fill(ellipse);
      }
    }
  }

  protected void drawImageMosaic(Graphics2D g2) {
    // Break the image up into tiles. Draw each
    //   tile with its own transparency, allowing
    //   the background to show through to varying
    //   degrees.
    int side = 36;
    int width = mImage.getWidth();
    int height = mImage.getHeight();
    for (int y = 0; y < height; y += side) {
      for (int x = 0; x < width; x += side) {
        // Calculate an appropriate transparency value.
        float xBias = (float)x / (float)width;
        float yBias = (float)y / (float)height;
        float alpha = 1.0f - Math.abs(xBias - yBias);
        g2.setComposite(AlphaComposite.getInstance(
            AlphaComposite.SRC_OVER, alpha));
        // Draw the subimage.
        int w = Math.min(side, width - x);
        int h = Math.min(side, height - y);
        BufferedImage tile = mImage.getSubimage(x, y, w, h);
        g2.drawImage(tile, x, y, null);
      }
    }
    // Reset the composite.
    g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
  }
  
  protected void drawText(Graphics2D g2) {
    // Find the bounds of the entire string.
    FontRenderContext frc = g2.getFontRenderContext();
    mLayout = new TextLayout(mMessage, mFont, frc);
    // Find the dimensions of this component.
    int width = getSize().width;
    int height = getSize().height;
    // Place the first full string, horizontally centered,
    //   at the bottom of the component.
    Rectangle2D bounds = mLayout.getBounds();
    double x = (width - bounds.getWidth()) / 2;
    double y = height - bounds.getHeight();
    drawString(g2, x, y, 0);
    // Now draw a second version, anchored to the right side
    //   of the component and rotated by -PI / 2.
    drawString(g2, width - bounds.getHeight(), y, -Math.PI / 2);
  }
  
  protected void drawString(Graphics2D g2,
      double x, double y, double theta) {
    // Transform to the requested location.
    g2.translate(x, y);
    // Rotate by the requested angle.
    g2.rotate(theta);
    // Draw the first part of the string.
    String first = mMessage.substring(0, mSplit);
    float width = drawBoxedString(g2, first, Color.white, Color.red, 0);
    // Draw the second part of the string.
    String second = mMessage.substring(mSplit);
    drawBoxedString(g2, second, Color.blue, Color.white, width);
    // Undo the transformations.
    g2.rotate(-theta);
    g2.translate(-x, -y);
  }
  
  protected float drawBoxedString(Graphics2D g2,
      String s, Color c1, Color c2, double x) {
    // Calculate the width of the string.
    FontRenderContext frc = g2.getFontRenderContext();
    TextLayout subLayout = new TextLayout(s, mFont, frc);
    float advance = subLayout.getAdvance();
    // Fill the background rectangle with a gradient.
    GradientPaint gradient = new GradientPaint((float)x, 0, c1,
        (float)(x + advance), 0, c2);
    g2.setPaint(gradient);
    Rectangle2D bounds = mLayout.getBounds();
    Rectangle2D back = new Rectangle2D.Double(x, 0,
        advance, bounds.getHeight());
    g2.fill(back);
    // Draw the string over the gradient rectangle.
    g2.setPaint(Color.white);
    g2.setFont(mFont);
    g2.drawString(s, (float)x, (float)-bounds.getY());
    return advance;
  }
}
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Chapter 2: The Big Picture
The Graphics2D class is the cornerstone of Java 2D. But what is it, exactly? And how does it work? In this chapter, I'll lay the groundwork for the rest of the book by covering the fundamental topics of the 2D API. I'll talk about the Graphics2D class, compositing, and coordinate spaces.
Rendering is the process of taking a collection of shapes, text, and images and figuring out what colors the pixels should be on a screen or printer. Shapes, text, and images are called graphics primitives ; screens and printers are called output devices. If I wanted to be pompous, I'd tell you that rendering is the process of displaying graphics primitives on output devices. A rendering engine performs this work; in the 2D API, the Graphics2D class is the rendering engine. Figure 2.1 shows this process at a high level. The 2D rendering engine takes care of the details of underlying devices and can accurately reproduce the geometry and color of a drawing, regardless of the device that displays it.
Figure 2.1: Rendering, the short story
Apart from being a rendering engine, an instance of Graphics2D also represents a drawing surface, which is simply some collection of pixels, each of which holds a color. It might be the inside of a window or a page in a printer, or even an offscreen image. Each time you draw something new, the new element is added to the existing drawing represented by the Graphics2D.
Graphics2D uses its internal state to decide exactly how graphics primitives are converted to pixel colors. For example, part of Graphics2D's internal state is a java.awt.Paint object, which describes the colors that should be used to fill shapes. Whenever you ask Graphics2D to fill a shape, it uses its current Paint to fill the shape.
Graphics2D
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Graphics2D
Rendering is the process of taking a collection of shapes, text, and images and figuring out what colors the pixels should be on a screen or printer. Shapes, text, and images are called graphics primitives ; screens and printers are called output devices. If I wanted to be pompous, I'd tell you that rendering is the process of displaying graphics primitives on output devices. A rendering engine performs this work; in the 2D API, the Graphics2D class is the rendering engine. Figure 2.1 shows this process at a high level. The 2D rendering engine takes care of the details of underlying devices and can accurately reproduce the geometry and color of a drawing, regardless of the device that displays it.
Figure 2.1: Rendering, the short story
Apart from being a rendering engine, an instance of Graphics2D also represents a drawing surface, which is simply some collection of pixels, each of which holds a color. It might be the inside of a window or a page in a printer, or even an offscreen image. Each time you draw something new, the new element is added to the existing drawing represented by the Graphics2D.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
The Rendering Pipeline
Graphics2D uses its internal state to decide exactly how graphics primitives are converted to pixel colors. For example, part of Graphics2D's internal state is a java.awt.Paint object, which describes the colors that should be used to fill shapes. Whenever you ask Graphics2D to fill a shape, it uses its current Paint to fill the shape.
Graphics2D 's internal state is comprised of seven elements:
paint
The current paint determines what colors will be used to fill a shape. This also affects shape outlines and text, since stroked outlines and character shapes are both filled.
stroke
Graphics2D uses the current stroke for shapes that are passed to its draw() method. The stroke determines how the outline of the shape is drawn. The resulting shape (the stroked outline) is then filled.
font
Text is rendered by creating a shape that represents the characters to be drawn. The current font determines what shapes are created for a given set of characters. The resulting shape is then filled.
transformation
All primitives are geometrically transformed before they are rendered. This means that they may be moved, rotated, and stretched. Graphics2D's transformation converts primitives from User Space to Device Space. By default, Graphics2D creates a transformation that maps 72 User coordinates to one inch on the output device.
compositing rule
A compositing rule is used to determine how the colors of a primitive should be combined with existing colors on the
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
All About Alpha
Rendering is an approximation. When you ask to have an ideal shape filled, the rendering engine figures out how the pixels of an output device should be colored to best approximate the shape. For example, suppose the rendering engine is asked to fill a shape with some color. There's a fast way to do it, and then there's a good way to do it.
The fast method is to color the pixels whose centers fall within the shape. Using this algorithm, pixels are either fully colored or left unchanged. Figure 2.3 shows an example of this technique with a single letter shown on some device with very large pixels. The ideal outline of the shape is also shown. The filled shape exhibits unattractive jaggies, or ragged edges. Images produced using this algorithm are said to be aliased.
Figure 2.3: Aliased rendering
The better method for filling a shape involves a little more work. The basic idea is to calculate the intersection of the shape with each pixel of the output device. Pixels are colored in proportion to the amount they are covered by the shape. This reduces the jaggies that are symptomatic of aliased rendering. Not surprisingly, this technique is called antialiasing. Figure 2.4 shows the same shape as Figure 2.3, but rendered with antialiasing. The pixels on the edge aren't just black or white; they're varying shades of gray.
Figure 2.4: Antialiased rendering
Fortunately, the 2D API takes care of all the details for you. You just need to specify whether you want antialiasing, using rendering hints.
Inside the rendering pipeline, a rasterizer takes ideal shapes and produces coverage values for each pixel. The coverage values represent how much of each pixel is covered by the shape. These coverage values are called
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Compositing
Once the rasterizer has generated alpha values for an ideal shape, there are several ways to use them to modify the drawing surface of a Graphics2D. A compositing rule determines how the colors of a new graphics primitive are combined with the existing colors on a drawing surface, as shown in Figure 2.6.
In Figure 2.6, the alpha values are used to blend colors between the background color, white, and the color that is used to fill the shape, black. This is probably the most intuitive compositing rule, but there are other possibilities. Conceptually, at least, the rasterizer produces a set of alpha values for the new shape that is the same size as the drawing surface on which the shape will be rendered. Then this set of alpha values and the desired color of the new shape are combined, pixel by pixel, with the drawing surface. The equation that is used to combine these values is the compositing rule.
Figure 2.6: Adding to a drawing surface
For example, you might place the new shape behind the other elements on the drawing surface. The 2D API's compositing rules are covered in Chapter 5.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Coordinate Space
Java 2D objects live in a plane defined by Cartesian coordinates. This plane is called User Coordinate Space, or just User Space. When objects are drawn on a screen or a printer, User Space coordinates are transformed into Device Space coordinates. Device Space corresponds to a particular monitor or printer—usually, one unit in Device Space corresponds to one pixel of a device. By default, User Space and Device Space are aligned, with the x and y axes oriented as shown in Figure 2.7. The x axis increases from left to right, and the y axis increases from top to bottom. The origin is placed at the upper left corner of the drawing surface. This applies for any device, where left, right, top, and bottom are defined in terms of the device itself—the sides of a monitor, for example, or the orientation of a sheet of paper in a printer. Note that the y axis is aligned so that it increases as you go down—this may be the opposite of what you were expecting.
Figure 2.7: Device Space coordinate system
Although User Space and Device Space are aligned by default, some scaling must take place to ensure that objects are drawn the same size, regardless of the output device. Device Space is determined by the resolution of a particular device. A monitor, for example, typically has about 72 pixels per inch, while a laser printer generally has 300 or 600 pixels per inch (or DPI, dots per inch).
User Space is converted to Device Space when objects are drawn. A transformation is used to convert from one system to the other. The default transformation converts 72 User Space coordinates into 1 physical inch. Let's say, for example, that you create a rectangle that is 144 User Space coordinates wide and 72 User Space coordinates high. The default transformation into Device Space for a monitor will map User Space directly to Device Space. Since monitors have (more or less) 72 pixels per inch, the rectangle will be 2 inches wide and 1 inch high. If you draw the same rectangle on a 300 DPI printer, the default transformation will convert the rectangle into a 600 by 300 pixel rectangle. The end result will still be a rectangle that is 2 inches wide and 1 inch high.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Chapter 3: Geometry
Java 2D allows you to represent any shape as a combination of straight and curved line segments. This chapter describes the Java 2D classes that represent geometric shapes. You'll learn about the following topics:
  • classes that represent points
  • the two central interfaces for geometric shapes: Shape and PathIterator
  • 2D's toolbox of shapes in the java.awt.geom package
  • 2D's support for combining shapes with each other
The java.awt.geom.Point2D class encapsulates a single point (an x and a y) in User Space. It is the most basic of the Java 2D classes and is used throughout the API. Note that a point is not the same as a pixel. A pixel is a tiny square (ideally) on a screen or printer that contains some color. A point, by contrast, has no area, so it can't be rendered. Points are used to build rectangles or other shapes that have area and can be rendered.
Point2D demonstrates an inheritance pattern that is used throughout java.awt.geom. In particular, Point2D is an abstract class with inner child classes that provide concrete implementations. Figure 3.1 shows Point2D's family tree. It's a pattern that you'll see again and again in the java.awt.geom package.
Figure 3.1: Point2D family of classes
Point2D represents a point in User Space, but it doesn't specify how the point's coordinates are stored. The subclasses provide different levels of precision for storing the coordinates of the point. The original java.awt.Point, which dates back to JDK 1.0, stores the coordinates as integers. Java 2D provides Point2D.Float and Point2D.Double for higher precision.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Points
The java.awt.geom.Point2D class encapsulates a single point (an x and a y) in User Space. It is the most basic of the Java 2D classes and is used throughout the API. Note that a point is not the same as a pixel. A pixel is a tiny square (ideally) on a screen or printer that contains some color. A point, by contrast, has no area, so it can't be rendered. Points are used to build rectangles or other shapes that have area and can be rendered.
Point2D demonstrates an inheritance pattern that is used throughout java.awt.geom. In particular, Point2D is an abstract class with inner child classes that provide concrete implementations. Figure 3.1 shows Point2D's family tree. It's a pattern that you'll see again and again in the java.awt.geom package.
Figure 3.1: Point2D family of classes
Point2D represents a point in User Space, but it doesn't specify how the point's coordinates are stored. The subclasses provide different levels of precision for storing the coordinates of the point. The original java.awt.Point, which dates back to JDK 1.0, stores the coordinates as integers. Java 2D provides Point2D.Float and Point2D.Double for higher precision.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Shapes and Paths
As you saw in Chapter 2, the Graphics2D class is the rendering engine for the Java 2D API. Two of its basic operations are filling shapes and drawing their outlines. But Graphics2D doesn't know much about geometry, as the song says. In fact, Graphics2D only knows how to draw one thing: a java.awt.Shape. The Shape interface represents a geometric shape, something that has an outline and an interior. With Graphics2D, you can draw the border of the shape using draw(), and you can fill the inside of a shape using fill().
The java.awt.geom package is a toolbox of useful classes that implement the Shape interface. There are classes that represent ellipses, arcs, rectangles, and lines. First, I'll talk about the Shape interface, and then briefly discuss the java.awt.geom package.
The java.awt.Shape interface is one of the common currencies of the 2D API. It contains four groups of methods: getBounds(), contains(), intersects(), and getPathIterator().
The getBounds() methods return rectangles that completely enclose a Shape:
public abstract Rectangle getBounds()
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Lines and Curves
The 2D API includes shape classes that represent straight and curved line segments. These classes all implement the Shape interface, so they can be rendered and manipulated like any other Shape. Although you could create a single straight or curved line segment yourself using GeneralPath, it's easier to use these canned shape classes. It's interesting that these classes are Shapes, even though they represent the basic segment types that make up a Shape's path.
The java.awt.geom.Line2D class represents a line whose coordinates can be retrieved as doubles. Like Point2D, Line2D is abstract. Subclasses can store coordinates in any way they wish. Figure 3.11 shows the hierarchy.
Figure 3.11: Line2D family of classes
Line2D includes several setLine() methods you can use to set a line's endpoints:
public abstract void setLine(double x1, double x2, double y1, double y2)
This method sets the endpoints of the line to x1, y1, and x2, y2.
public void setLine(Point2D p1, Point2D p2)
This method sets the endpoints of the line to p1 and p2.
public void setLine(Line2D l)
This method sets the endpoints of the line to be the same as the endpoints of the given line.
Here are the constructors for the Line2D.Float class. Two of them allow you to specify the endpoints of the line, which saves you the trouble of calling setLine().
public Line2D.Float()
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Rectangles
As Figure 3.3 shows, java.awt.geom.RectangularShape is an important class. It's the abstract parent class of the rectangle, rounded rectangle, arc, and ellipse classes in java.awt.geom. It implements the methods of Shape and adds a few of its own.
First, a RectangularShape returns information about its location and size:
public abstract double getX()
This method returns the horizontal location of the left side of the rectangle.
public abstract double getY()
This method returns the vertical location of the top side of the rectangle.
public abstract double getWidth()
This method returns the width of the rectangle.
public abstract double getHeight()
This method returns the height of the rectangle.
public double getMinX()
This method returns the smallest x coordinate of the rectangle.
public double getMaxX()
This method returns the largest x coordinate of the rectangle.
public double getMinY()
This method returns the smallest y coordinate of the rectangle.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Ellipses and Arcs
An ellipse, like a rectangle, is fully defined by a location, a width, and a height. As with the other geometry classes, java.awt.geom.Ellipse2D is abstract. A concrete inner subclass, Ellipse2D.Float, stores its coordinates as floats:
public Ellipse2D.Float(float x, float y, float w, float h)
This constructor creates an Ellipse2D.Float using the specified location, width, and height.
Another inner subclass, Ellipse2D.Double, offers a corresponding constructor:
public Ellipse2D.Double(double x, double y, double w, double h)
Note that Ellipse2D is a descendent of RectangularShape. While this may not seem very intuitive, it does mean that Ellipse2D inherits all of Rectangular-Shape's methods. As with round rectangles, an ellipse's location (x and y) is outside the outline of the ellipse.
The 2D API includes java.awt.geom.Arc2D for drawing pieces of an ellipse. Arc2D defines three different kinds of arcs, as shown in Figure 3.18. These are represented by constants in the Arc2D class:
public static final int OPEN
This constant represents an open arc. This simply defines a curved line that is a portion of an ellipse's outline.
public static final int PIE
This constant represents an arc in the shape of a slice of pie. This outline is produced by drawing the curved arc as well as straight lines from the arc's endpoints to the center of the ellipse that defines the arc.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Constructive Area Geometry
Constructive area geometry is a fancy name for combining shapes. Two or more shapes can be combined using different rules or operations, much the way numbers can be combined in an equation. The 2D API supports four different operations for combining the areas of two shapes:
addition (union)
The addition of two shapes is the area covered by one or both of the shapes.
intersection
The intersection of two shapes is the area that is covered by both shapes simultaneously.
subtraction
The result of subtracting one shape from another is the area covered by one that is not covered by the other.
exclusive or
The exclusive or operator is the inverse of the intersection operator. In other words, the exclusive or of two shapes is the area that is covered by one or the other of the shapes. But it does not include the area covered by both.
It's hard to visualize these operators just by reading about them. Figure 3.21 shows the result of applying these operators to two overlapping shapes.
In Java 2D, the java.awt.geom.Area class supports constructive area geometry. This class offers two constructors:
public Area()
This constructor creates an empty Area. You can accumulate area using the add() method, described below.
public Area(Shape g)
This constructor creates an Area using the interior of the supplied Shape.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Chapter 4: Painting and Stroking
In the last chapter, we saw how shapes are defined in the 2D API. But what can you do with shapes?
  • Painting is the process of filling the interior of the shape with a color, color gradient, or texture.
  • Stroking is the process of drawing the shape's outline. You can draw an outline using different line widths, line styles, and colors.
Painting and stroking are very closely related. Stroking, in fact, is just the process of creating a shape that represents an outline and filling it. You can draw outlines using any type of paint. Figure 4.1 shows some examples. On the left, a circle has been filled with a color gradient. In the middle, the same circle's outline is drawn with a thick line using a solid color. On the right, the circle's outline is drawn with the same thick line using the color gradient.
Figure 4.1: Examples of painting and stroking
The following class produces the window shown in Figure 4.1. It uses some unfamiliar methods and classes: setPaint(), setStroke(), GradientPaint, and BasicStroke. I'll explain these classes and methods, and more, in the rest of the chapter. For now, this example should show you some of the potential of painting and stroking:
import java.awt.*;
import java.awt.geom.*;

public class PaintingAndStroking
    extends ApplicationFrame {
  public static void main(String[] args) {
    PaintingAndStroking f = new PaintingAndStroking();
    f.setTitle("PaintingAndStroking v1.0");
    f.setSize(300, 150);
    f.center();
    f.setVisible(true);
  }
  
  public void paint(Graphics g) {
    Graphics2D g2 = (Graphics2D)g;
    double x = 15, y = 50, w = 70, h = 70;
    Ellipse2D e = new Ellipse2D.Double(x, y, w, h);
    GradientPaint gp = new GradientPaint(75, 75, Color.white,
        95, 95, Color.gray, true);
    // Fill with a gradient.
    g2.setPaint(gp);
    g2.fill(e);
    // Stroke with a solid color.
    e.setFrame(x + 100, y, w, h);
    g2.setPaint(Color.black);
    g2.setStroke(new BasicStroke(8));
    g2.draw(e);
    // Stroke with a gradient.
    e.setFrame(x + 200, y, w, h);
    g2.setPaint(gp);
    g2.draw(e);
  }
}
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Painting
Filling the interior of a shape is a two-step process:
  1. First, tell the Graphics2D how to fill shapes with a call to setPaint(). This method accepts any object that implements the java.awt.Paint interface. The Graphics2D stores the Paint away as part of its state. When it comes time to fill a shape, Graphics2D will use the Paint to determine what colors should be used to fill the shape. The 2D API comes with three kinds of "canned" paints: solid colors, a linear color gradient, and a texture fill. You can add your own Paint implementations if you wish.
  2. Now you can tell Graphics2D to fill a shape by passing it to fill().
Paints are immutable, which means they can't be modified after they are created. The reason for this is to avoid funky behavior when rendering. Imagine, for example, if you wanted to fill a series of shapes with a solid color. First, you'd call setPaint() on the Graphics2D; then you would paint the shapes using fill(). But what if another part of your program changed the Paint that Graphics2D was using? The results might be quite bizarre. For this reason, objects that implement Paint should not allow themselves to be changed after they are created.
Figure 4.2 shows the three types of painting supported by the 2D API. The figure contains three shapes:
  • The ellipse is filled with a solid color.
  • The rounded rectangle is filled with a color gradient.
  • The arc is filled with a texture, built from van Gogh's Starry Night.
Figure 4.2: Three shapes and three paints
The