Since JDK version 1.0, the Graphics class had methods to draw lines, rectangles, ellipses, and so on. But those drawing operations are very limited. For example, you cannot vary the line thickness and you cannot rotate the shapes. Beginning with SDK version 2.0, J2SE includes the Java 2D library which implements a very powerful set of graphical operations. In this chapter, we will only look at the basics of the Java 2D library—see the Advanced AWT chapter in Volume 2 for more information on the advanced features. To draw shapes in the Java 2D library, you need to obtain an object of the Graphics2D class. This class is a subclass of the Graphics class. If you use a version of the SDK that is Java 2D enabled, methods such as paintComponent automatically receive an object of the Graphics2D class. Simply use a cast, as follows:
public void paintComponent(Graphics g)
Graphics2D g2 = (Graphics2D)g;
. . .
The Java 2D library organizes geometric shapes in an object-oriented fashion. In particular, there are classes to represent lines, rectangles and ellipses:
Line2D Rectangle2D Ellipse2D
These classes all implement the Shape interface.
To draw a shape, you first create an object of a class that implements the Shape interface and then call the draw method of the Graphics2D class. For example,
The Java 2D library supports more complex shapes—in particular, arcs, quadratic and cubic curves, and general paths. See Volume 2 for more information.
Rectangle2D rect = . . .;
There is a complexity when using the Java 2D shape classes. Unlike the 1.0 draw methods, which used integer pixel coordinates, the Java 2D shapes use floating-point coordinates. In many cases, that is a great convenience because it allows you to specify your shapes in coordinates that are meaningful to you (such as millimeters or inches) and then translate to pixels. The Java 2D library uses single-precision float quantities for many of its internal floating-point calculations. Single precision is sufficient—after all, the ultimate purpose of the geometric computations is to set pixels on the screen or printer. As long as any roundoff errors stay within one pixel, the visual outcome is not affected. Furthermore, float computations are faster on some platforms, and float values require half the storage of double values. However, manipulating float values is sometimes inconvenient for the programmer because the Java coding language is adamant about requiring casts when converting double values into float values. For example, consider the following statement:
Before the Java 2D library appeared, programmers used methods of the Graphics class such as drawRectangle to draw shapes. Superficially, the old-style method calls look a bit simpler. However, by using the Java 2D library, you keep your options open—you can later enhance your drawings with some of the many tools that the Java 2D library supplies.
float f = 1.2; // Error
This statement does not compile because the constant 1.2 has type double, and the compiler is nervous about loss of precision. The remedy is to add an F suffix to the floating-point constant:
float f = 1.2F; // Ok
Now consider this statement:
Rectangle2D r = . . .
float f = r.getWidth(); // Error
This statement does not compile either, for the same reason. The getWidth method returns a double. This time, the remedy is to provide a cast:
float f = (float)r.getWidth(); // Ok
Because the suffixes and casts are a bit of a pain, the designers of the 2D library decided to supply two versions of each shape class: one with float coordinates for frugal programmers, and one with double coordinates for the lazy ones. (In this tutorial, we'll fall into the second camp and use double coordinates whenever we can.) The library designers chose a curious, and initially confusing, method for packaging these choices. Consider the Rectangle2D class. This is an abstract class with two concrete subclasses, which are also static inner classes:
Screenshot-8 shows the inheritance diagram.
Screenshot-8. 2D rectangle classes
It is best to try to ignore the fact that the two concrete classes are static inner classes—that is just a gimmick to avoid names such as FloatRectangle2D and DoubleRectangle2D. (For more information on static inner classes, see .) When you construct a Rectangle2D.Float object, you supply the coordinates as float numbers. For a Rectangle2D.Double object, you supply them as double numbers.
Rectangle2D.Float floatRect = new Rectangle2D.Float(10.0F,
25.0F, 22.5F, 20.0F);
Rectangle2D.Double doubleRect = new Rectangle2D.Double(10.0,
25.0, 22.5, 20.0);
Actually, since both Rectangle2D.Float and Rectangle2D.Double extend the common Rectangle2D class, and the methods in the subclasses simply override methods in the Rectangle2D superclass, there is no benefit in remembering the exact shape type. You can simply use Rectangle2D variables to hold the rectangle references.
Rectangle2D floatRect = new Rectangle2D.Float(10.0F,
25.0F, 22.5F, 20.0F);
Rectangle2D doubleRect = new Rectangle2D.Double(10.0,
25.0, 22.5, 20.0);
That is, you only need to use the pesky inner classes when you construct the shape objects. The construction parameters denote the top-left corner, width, and height of the rectangle.
The Rectangle2D methods use double parameters and return values. For example, the getWidth method returns a double value, even if the width is stored as a float in a Rectangle2D.Float object.
Actually, the Rectangle2D.Float class has one additional method that is not inherited from Rectangle2D, namely, setRect(float x, float y, float h, float w). You lose that method if you store the Rectangle2D.Float reference in a Rectangle2D variable. But it is not a big loss—the Rectangle2D class has a setRect method with double parameters.
What we just discussed for the Rectangle2D classes holds for the other shape classes as well. Furthermore, there is a Point2D class with subclasses Point2D.Float and Point2D.Double. Here is how to make a point object.
Simply use the Double shape classes to avoid dealing with float values altogether. However, if you are constructing thousands of shape objects, then you can consider using the Float classes to conserve memory.
Point2D p = new Point2D.Double(10, 20);
The classes Rectangle2D and Ellipse2D both inherit from a common superclass RectangularShape. Admittedly, ellipses are not rectangular, but they have a bounding rectangle (see Screenshot-9).
The Point2D class is very useful—it is more object-oriented to work with Point2D objects than with separate x- and y- values. Many constructors and methods accept Point2D parameters. We suggest that you use Point2D objects when you can—they usually make geometric computations easier to understand.
Screenshot-9. The bounding rectangle of an ellipse
The RectangularShape class defines over 20 methods that are common to these shapes, among them such useful methods as getWidth, getHeight, getCenterX, and getCenterY (but sadly, at the time of this writing, not a getCenter method that returns the center as a Point2D object). Finally, there are a couple of legacy classes from JDK 1.0 that have been fit into the shape class hierarchy. The Rectangle and Point classes, which store a rectangle and a point with integer coordinates, extend the Rectangle2D and Point2D classes. Screenshot-10 shows the relationships between the shape classes. However, the Double and Float subclasses are omitted. Legacy classes are marked with a gray fill.
Screenshot-10. Relationships between the shape classes
Rectangle2D and Ellipse2D objects are simple to construct. You need to specify
- The x- and y-coordinates of the top-left corner;
- The width and height.
For ellipses, these refer to the bounding rectangle. For example,
Ellipse2D e = new Ellipse2D.Double(150, 200, 100, 50);
constructs an ellipse that is bounded by a rectangle with the top-left corner at (150, 200), width 100, and height 50. However, sometimes you don't have the top-left corner readily available. It is quite common to have two diagonal corner points of a rectangle, but perhaps they aren't the top-left and bottom-right corners. You can't simply construct a rectangle as
Rectangle2D rect = new Rectangle2D.Double(px, py,
qx - px, qy - py); // Error
If p isn't the top-left corner, one or both of the coordinate differences will be negative and the rectangle will come out empty. In that case, first create a blank rectangle and use the setFrameFromDiagonal method.
Rectangle2D rect = new Rectangle2D.Double();
rect.setFrameFromDiagonal(px, py, qx, qy);
Or, even better, if you know the corner points as Point2D objects p and q,
When constructing an ellipse, you usually know the center, width, and height, and not the corner points of the bounding rectangle (which don't even lie on the ellipse). There is a setFrameFromCenter method that uses the center point, but it still requires one of the four corner points. Thus, you will usually end up constructing an ellipse as follows:
Ellipse2D ellipse = new Ellipse2D.Double(centerX - width / 2,
centerY - height / 2, width, height);
To construct a line, you supply the start and end points, either as Point2D objects or as pairs of numbers:
Line2D line = new Line2D.Double(start, end);
Line2D line = new Line2D.Double(startX, startY, endX, endY);
The program in Example 7-4 draws
- A rectangle;
- The ellipse that is enclosed in the rectangle;
- A diagonal of the rectangle;
- A circle that has the same center as the rectangle.
Screenshot-11 shows the result.
Example 7-4 DrawTest.java
1. import java.awt.*;
2. import java.awt.geom.*;
3. import javax.swing.*;
5. public class DrawTest
7. public static void main(String args)
9. DrawFrame frame = new DrawFrame();
16. A frame that contains a panel with drawings
18. class DrawFrame extends JFrame
20. public DrawFrame()
23. setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
25. // add panel to frame
27. DrawPanel panel = new DrawPanel();
28. Container contentPane = getContentPane();
32. public static final int DEFAULT_WIDTH = 400;
33. public static final int DEFAULT_HEIGHT = 400;
37. A panel that displays rectangles and ellipses.
39. class DrawPanel extends JPanel
41. public void paintComponent(Graphics g)
44. Graphics2D g2 = (Graphics2D)g;
46. // draw a rectangle
48. double leftX = 100;
49. double topY = 100;
50. double width = 200;
51. double height = 150;
53. Rectangle2D rect = new Rectangle2D.Double(leftX, topY,
54. width, height);
57 // draw the enclosed ellipse
59. Ellipse2D ellipse = new Ellipse2D.Double();
63. // draw a diagonal line
65. g2.draw(new Line2D.Double(leftX, topY,
66. leftX + width, topY + height));
68. // draw a circle with the same center
70. double centerX = rect.getCenterX();
71. double centerY = rect.getCenterY();
72. double radius = 150;
74. Ellipse2D circle = new Ellipse2D.Double();
75. circle.setFrameFromCenter(centerX, centerY,
76. centerX + radius, centerY + radius);
Screenshot-11. Rectangles and ellipses
- double getCenterX()
- double getCenterY()
- double getMinX()
- double getMinY()
- double getMaxX()
- double getMaxY()
return the center, minimum, or maximum x- or y-value of the enclosing rectangle.
- double getWidth()
- double getHeight()
return the width or height of the enclosing rectangle.
- double getX()
- double getY()
return the x- or y-coordinate of the top-left corner of the enclosing rectangle.