Shapes


Here are some of the methods in the Graphics class to draw shapes:

 drawLine drawRectangle drawRoundRect draw3DRect drawPolygon drawPolyline drawOval drawArc 

There are also corresponding fill methods. These methods have been in the Graphics class ever since JDK 1.0. The Java 2D API uses a completely different, object-oriented approach. Instead of methods, there are classes:

 Line2D Rectangle2D RoundRectangle2D Ellipse2D Arc2D QuadCurve2D CubicCurve2D GeneralPath 

These classes all implement the Shape interface.

Finally, the Point2D class describes a point with an x- and a y-coordinate. Points are useful to define shapes, but they aren't themselves shapes.

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.

The Line2D, Rectangle2D, RoundRectangle2D, Ellipse2D, and Arc2D classes correspond to the drawLine, drawRectangle, drawRoundRect, drawOval, and drawArc methods. (The concept of a "3D rectangle" has died the death that it so richly deservedthere is no analog to the draw3DRect method.) The Java 2D API supplies two additional classes: quadratic and cubic curves. We discuss these shapes later in this section. There is no Polygon2D class. Instead, the GeneralPath class describes paths that are made up from lines, quadratic and cubic curves. You can use a GeneralPath to describe a polygon; we show you how later in this section.

The classes

 Rectangle2D RoundRectangle2D Ellipse2D Arc2D 

all inherit from a common superclass RectangularShape. Admittedly, ellipses and arcs are not rectangular, but they have a bounding rectangle (see Figure 7-2).

Figure 7-2. The bounding rectangle of an ellipse and an arc


Each of the classes with a name ending in "2D" has two subclasses for specifying coordinates as float or double quantities. In Volume 1, you already encountered Rectangle2D.Float and Rectangle2D.Double.

The same scheme is used for the other classes, such as Arc2D.Float and Arc2D.Double.

Internally, all graphics classes use float coordinates because float numbers use less storage space and they have sufficient precision for geometric computations. However, the Java programming language makes it a bit more tedious to manipulate float numbers. For that reason, most methods of the graphics classes use double parameters and return values. Only when constructing a 2D object must you choose between a constructor with float or double coordinates. For example,

 Rectangle2D floatRect = new Rectangle2D.Float(5F, 10F, 7.5F, 15F); Rectangle2D doubleRect = new Rectangle2D.Double(5, 10, 7.5, 15); 

The Xxx2D.Float and Xxx2D.Double classes are subclasses of the Xxx2D classes. After object construction, essentially no benefit accrues from remembering the subclass, and you can just store the constructed object in a superclass variable, just as in the code example.

As you can see from the curious names, the Xxx2D.Float and Xxx2D.Double classes are also inner classes of the Xxx2D classes. That is just a minor syntactical convenience, to avoid an inflation of outer class names.

Figure 7-3 shows the relationships between the shape classes. However, the Double and Float subclasses are omitted. Legacy classes from the pre-2D library are marked with a gray fill.

Figure 7-3. Relationships between the shape classes


Using the Shape Classes

You already saw how to use the Rectangle2D, Ellipse2D, and Line2D classes in Volume 1, Chapter 7. In this section, you will learn how to work with the remaining 2D shapes.

For the RoundRectangle2D shape, you specify the top-left corner, width and height, and the x- and y-dimension of the corner area that should be rounded (see Figure 7-4). For example, the call

 RoundRectangle2D r = new RoundRectangle2D.Double(150, 200, 100, 50, 20, 20); 

Figure 7-4. Constructing a RoundRectangle2D


produces a rounded rectangle with circles of radius 20 at each of the corners.

To construct an arc, you specify the bounding box, the start angle, the angle swept out by the arc (see Figure 7-5), and the closure type, one of Arc2D.OPEN, Arc2D.PIE, or Arc2D.CHORD.

 Arc2D a = new Arc2D(x, y, width, height, startAngle, arcAngle, closureType); 

Figure 7-5. Constructing an elliptical arc


Figure 7-6 illustrates the arc types.

Figure 7-6. Arc types


However, the angles are not simply given in degrees, but they are distorted such that a 45-degree angle denotes the diagonal position, even if width and height are not the same. If you draw circular arcs (for example, in a pie chart), then you don't need to worry about this. However, for elliptical arcs, be prepared for an adventure in trigonometrysee the sidebar for details.

The Java 2D API supports quadratic and cubic curves. In this chapter, we do not get into the mathematics of these curves. We suggest you get a feel for how the curves look by running the program in Example 7-1. As you can see in Figures 7-7 and 7-8, quadratic and cubic curves are specified by two end points and one or two control points. Moving the control points changes the shape of the curves.

Figure 7-7. A quadratic curve


Figure 7-8. A cubic curve


To construct quadratic and cubic curves, you give the coordinates of the end points and the control points. For example,

 QuadCurve2D q = new QuadCurve2D.Double(startX, startY, controlX, controlY, endX, endY); CubicCurve2D c = new CubicCurve2D.Double(startX, startY, control1X, control1Y,    control2X, control2Y, endX, endY); 

Specifying Angles for Elliptical Arcs

The algorithm for drawing elliptical arcs uses distorted angles, which the caller must precompute. This sidebar tells you how. If you belong to the large majority of programmers who never draw elliptical arcs, just skip the sidebar. However, because the official documentation completely glosses over this topic, we thought it worth recording, to save those who need this information from a few hours of trigonometric agony.

You convert actual angles to distorted angles with the following formula:

[View full width]

distortedAngle = Math.atan2(Math.sin(angle) * width, Math.cos (angle) * height);

Sometimes (such as in the example program at the end of this section), you know an end point of the arc, or another point on the line joining the center of the ellipse and that end point. In that case, first compute

 dx = p.getX() - center.getX(); dy = p.getY() - center.getY(); 

Then, the distorted angle is

 distortedAngle = Math.atan2(-dy * width, dx * height); 

(The minus sign in front of dy is necessary because in the pixel coordinate system, the y-axis points downward, which leads to angle measurements that are clockwise, but you need to supply an angle that is measured counterclockwise.)

Convert the result from radians to degrees:

 distortedAngle = Math.toDegrees(distortedAngle); 

The result is a value between 2180 and 180.

Compute both the distorted start and end angles in this way. Then, compute the difference between the two distorted angles.

If either the start angle or the difference is negative, add 360. Then, supply the start angle and the angle difference to the arc constructor.

[View full width]

Arc2D a = new Arc2D(x, y, width, height, distortedStartAngle, distortedAngleDifference, closureType);

Not only is the documentation vague on the exact nature of the distortion, it is also quite misleading in calling the distorted angle difference value the "arc angle." Except for the case of a circular arc, that value is neither the actual arc angle nor its distortion.

If you run the example program at the end of this section, then you can visually check that this calculation yields the correct values for the arc constructor (see Figure 7-9 on page 464).

Figure 7-9. The ShapeTest program



Quadratic curves are not very flexible, and they are not commonly used in practice. Cubic curves (such as the Bezier curves drawn by the CubicCurve3D class) are, however, very common. By combining many cubic curves so that the slopes at the connection points match, you can create complex, smooth-looking curved shapes. For more information, we refer you to Computer Graphics: Principles and Practice, Second Edition in C by James D. Foley, Andries van Dam, Steven K. Feiner, et al. [Addison Wesley 1995].

You can build arbitrary sequences of line segments, quadratic curves, and cubic curves, and store them in a GeneralPath object. You specify the first coordinate of the path with the moveTo method. For example,

 GeneralPath path = new GeneralPath(); path.moveTo(10, 20); 

You then extend the path by calling one of the methods lineTo, quadTo, or curveTo. These methods extend the path by a line, a quadratic curve, or a cubic curve. To call lineTo, supply the end point. For the two curve methods, supply the control points, then the end point. For example,

 path.lineTo(20, 30); path.curveTo(control1X, control1Y, control2X, control2Y, endX, endY); 

You close the path by calling the closePath method. It draws a line back to the last moveTo.

To make a polygon, simply call moveTo to go to the first corner point, followed by repeated calls to lineTo to visit the other corner points. Finally, call closePath to close the polygon. The program in Example 7-1 shows this in more detail.

A general path does not have to be connected. You can call moveTo at any time to start a new path segment.

Finally, you can use the append method to add arbitrary Shape objects to a general path. The outline of the shape is added to the end to the path. The second parameter of the append method is true if the new shape should be connected to the last point on the path, false if it should not be connected. For example, the call

 Rectangle2D r = . . .; path.append(r, false); 

appends the outline of a rectangle to the path without connecting it to the existing path. But

 path.append(r, true); 

adds a straight line from the end point of the path to the starting point of the rectangle, and then adds the rectangle outline to the path.

The program in Example 7-1 lets you create sample paths. Figures 7-7 and 7-8 show sample runs of the program. You pick a shape maker from the combo box. The program contains shape makers for

  • Straight lines;

  • Rectangles, round rectangles, and ellipses;

  • Arcs (showing lines for the bounding rectangle and the start and end angles, in addition to the arc itself);

  • Polygons (using a GeneralPath); and

  • Quadratic and cubic curves.

Use the mouse to adjust the control points. As you move them, the shape continuously repaints itself.

The program is a bit complex because it handles a multiplicity of shapes and supports dragging of the control points.

An abstract superclass ShapeMaker encapsulates the commonality of the shape maker classes. Each shape has a fixed number of control points that the user can move around. The getPointCount method returns that value. The abstract method

 Shape makeShape(Point2D[] points) 

computes the actual shape, given the current positions of the control points. The toString method returns the class name so that the ShapeMaker objects can simply be dumped into a JComboBox.

To enable dragging of the control points, the ShapePanel class handles both mouse and mouse motion events. If the mouse is pressed on top of a rectangle, subsequent mouse drags move the rectangle.

The majority of the shape maker classes are simpletheir makeShape methods just construct and return the requested shape. However, the ArcMaker class needs to compute the distorted start and end angles. Furthermore, to demonstrate that the computation is indeed correct, the returned shape is a GeneralPath containing the arc itself, the bounding rectangle, and the lines from the center of the arc to the angle control points (see Figure 7-9).

Example 7-1. ShapeTest.java
   1. import java.awt.*;   2. import java.awt.event.*;   3. import java.awt.geom.*;   4. import java.util.*;   5. import javax.swing.*;   6.   7. /**   8.    This program demonstrates the various 2D shapes.   9. */  10. public class ShapeTest  11. {  12.    public static void main(String[] args)  13.    {  14.       JFrame frame = new ShapeTestFrame();  15.       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);  16.       frame.setVisible(true);  17.    }  18. }  19.  20. /**  21.    This frame contains a combo box to select a shape  22.    and a panel to draw it.  23. */  24. class ShapeTestFrame extends JFrame  25. {  26.    public ShapeTestFrame()  27.    {  28.       setTitle("ShapeTest");  29.       setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);  30.  31.       final ShapePanel panel = new ShapePanel();  32.       add(panel, BorderLayout.CENTER);  33.       final JComboBox comboBox = new JComboBox();  34.       comboBox.addItem(new LineMaker());  35.       comboBox.addItem(new RectangleMaker());  36.       comboBox.addItem(new RoundRectangleMaker());  37.       comboBox.addItem(new EllipseMaker());  38.       comboBox.addItem(new ArcMaker());  39.       comboBox.addItem(new PolygonMaker());  40.       comboBox.addItem(new QuadCurveMaker());  41.       comboBox.addItem(new CubicCurveMaker());  42.       comboBox.addActionListener(new  43.          ActionListener()  44.          {  45.             public void actionPerformed(ActionEvent event)  46.             {  47.                ShapeMaker shapeMaker = (ShapeMaker) comboBox.getSelectedItem();  48.                panel.setShapeMaker(shapeMaker);  49.             }  50.          });  51.       add(comboBox, BorderLayout.NORTH);  52.       panel.setShapeMaker((ShapeMaker) comboBox.getItemAt(0));  53.    }  54.  55.    private static final int DEFAULT_WIDTH = 300;  56.    private static final int DEFAULT_HEIGHT = 300;  57. }  58.  59. /**  60.    This panel draws a shape and allows the user to  61.    move the points that define it.  62. */  63. class ShapePanel extends JPanel  64. {  65.    public ShapePanel()  66.    {  67.       addMouseListener(new  68.          MouseAdapter()  69.          {  70.             public void mousePressed(MouseEvent event)  71.             {  72.                Point p = event.getPoint();  73.                for (int i = 0; i < points.length; i++)  74.                {  75.                   double x = points[i].getX() - SIZE / 2;  76.                   double y = points[i].getY() - SIZE / 2;  77.                   Rectangle2D r = new Rectangle2D.Double(x, y, SIZE, SIZE);  78.                   if (r.contains(p))  79.                   {  80.                      current = i;  81.                      return;  82.                   }  83.                }  84.             }  85.  86.             public void mouseReleased(MouseEvent event)  87.             {  88.                current = -1;  89.             }  90.          });  91.       addMouseMotionListener(new  92.          MouseMotionAdapter()  93.          {  94.             public void mouseDragged(MouseEvent event)  95.             {  96.                if (current == -1) return;  97.                points[current] = event.getPoint();  98.                repaint();  99.             } 100.          }); 101.       current = -1; 102.    } 103. 104.    /** 105.       Set a shape maker and initialize it with a random 106.       point set. 107.       @param aShapeMaker a shape maker that defines a shape 108.       from a point set 109.    */ 110.    public void setShapeMaker(ShapeMaker aShapeMaker) 111.    { 112.       shapeMaker = aShapeMaker; 113.       int n = shapeMaker.getPointCount(); 114.       points = new Point2D[n]; 115.       for (int i = 0; i < n; i++) 116.       { 117.          double x = generator.nextDouble() * getWidth(); 118.          double y = generator.nextDouble() * getHeight(); 119.          points[i] = new Point2D.Double(x, y); 120.       } 121.       repaint(); 122.    } 123. 124.    public void paintComponent(Graphics g) 125.    { 126.       super.paintComponent(g); 127.       if (points == null) return; 128.       Graphics2D g2 = (Graphics2D) g; 129.       for (int i = 0; i < points.length; i++) 130.       {  double x = points[i].getX() - SIZE / 2; 131.          double y = points[i].getY() - SIZE / 2; 132.          g2.fill(new Rectangle2D.Double(x, y, SIZE, SIZE)); 133.       } 134. 135.       g2.draw(shapeMaker.makeShape(points)); 136.    } 137. 138.    private Point2D[] points; 139.    private static Random generator = new Random(); 140.    private static int SIZE = 10; 141.    private int current; 142.    private ShapeMaker shapeMaker; 143. } 144. 145. /** 146.    A shape maker can make a shape from a point set. 147.    Concrete subclasses must return a shape in the makeShape 148.    method. 149. */ 150. abstract class ShapeMaker 151. { 152.    /** 153.       Constructs a shape maker. 154.       @param aPointCount the number of points needed to define 155.       this shape. 156.    */ 157.    public ShapeMaker(int aPointCount) 158.    { 159.       pointCount = aPointCount; 160.    } 161. 162.    /** 163.       Gets the number of points needed to define this shape. 164.       @return the point count 165.    */ 166.    public int getPointCount() 167.    { 168.       return pointCount; 169.    } 170. 171.    /** 172.       Makes a shape out of the given point set. 173.       @param p the points that define the shape 174.       @return the shape defined by the points 175.    */ 176.    public abstract Shape makeShape(Point2D[] p); 177. 178.    public String toString() 179.    { 180.       return getClass().getName(); 181.    } 182. 183.    private int pointCount; 184. } 185. 186. /** 187.    Makes a line that joins two given points. 188. */ 189. class LineMaker extends ShapeMaker 190. { 191.    public LineMaker() { super(2); } 192. 193.    public Shape makeShape(Point2D[] p) 194.    { 195.       return new Line2D.Double(p[0], p[1]); 196.    } 197. } 198. 199. /** 200.    Makes a rectangle that joins two given corner points. 201. */ 202. class RectangleMaker extends ShapeMaker 203. { 204.    public RectangleMaker() { super(2); } 205. 206.    public Shape makeShape(Point2D[] p) 207.    { 208.       Rectangle2D s = new Rectangle2D.Double(); 209.       s.setFrameFromDiagonal(p[0], p[1]); 210.       return s; 211.    } 212. } 213. 214. /** 215.    Makes a round rectangle that joins two given corner points. 216. */ 217. class RoundRectangleMaker extends ShapeMaker 218. { 219.    public RoundRectangleMaker() { super(2); } 220. 221.    public Shape makeShape(Point2D[] p) 222.    { 223.       RoundRectangle2D s = new RoundRectangle2D.Double(0, 0, 0, 0, 20, 20); 224.       s.setFrameFromDiagonal(p[0], p[1]); 225.       return s; 226.    } 227. } 228. 229. /** 230.    Makes an ellipse contained in a bounding box with two given 231.    corner points. 232. */ 233. class EllipseMaker extends ShapeMaker 234. { 235.    public EllipseMaker() { super(2); } 236. 237.    public Shape makeShape(Point2D[] p) 238.    { 239.       Ellipse2D s = new Ellipse2D.Double(); 240.       s.setFrameFromDiagonal(p[0], p[1]); 241.       return s; 242.    } 243. } 244. 245. /** 246.    Makes an arc contained in a bounding box with two given 247.    corner points, and with starting and ending angles given 248.    by lines emanating from the center of the bounding box and 249.    ending in two given points. To show the correctness of 250.    the angle computation, the returned shape contains the arc, 251.    the bounding box, and the lines. 252. */ 253. class ArcMaker extends ShapeMaker 254. { 255.    public ArcMaker() { super(4); } 256. 257.    public Shape makeShape(Point2D[] p) 258.    { 259.       double centerX = (p[0].getX() + p[1].getX()) / 2; 260.       double centerY = (p[0].getY() + p[1].getY()) / 2; 261.       double width = Math.abs(p[1].getX() - p[0].getX()); 262.       double height = Math.abs(p[1].getY() - p[0].getY()); 263. 264.       double distortedStartAngle = Math.toDegrees(Math.atan2(-(p[2].getY() - centerY) 265.             * width, (p[2].getX() - centerX) * height)); 266.       double distortedEndAngle = Math.toDegrees(Math.atan2(-(p[3].getY() - centerY) 267.             * width, (p[3].getX() - centerX) * height)); 268.       double distortedAngleDifference = distortedEndAngle - distortedStartAngle; 269.       if (distortedStartAngle < 0) distortedStartAngle += 360; 270.       if (distortedAngleDifference < 0) distortedAngleDifference += 360; 271. 272.       Arc2D s = new Arc2D.Double(0, 0, 0, 0, 273.          distortedStartAngle, distortedAngleDifference, Arc2D.OPEN); 274.       s.setFrameFromDiagonal(p[0], p[1]); 275. 276.       GeneralPath g = new GeneralPath(); 277.       g.append(s, false); 278.       Rectangle2D r = new Rectangle2D.Double(); 279.       r.setFrameFromDiagonal(p[0], p[1]); 280.       g.append(r, false); 281.       Point2D center = new Point2D.Double(centerX, centerY); 282.       g.append(new Line2D.Double(center, p[2]), false); 283.       g.append(new Line2D.Double(center, p[3]), false); 284.       return g; 285.    } 286. } 287. 288. /** 289.    Makes a polygon defined by six corner points. 290. */ 291. class PolygonMaker extends ShapeMaker 292. { 293.    public PolygonMaker() { super(6); } 294. 295.    public Shape makeShape(Point2D[] p) 296.    { 297.       GeneralPath s = new GeneralPath(); 298.       s.moveTo((float) p[0].getX(), (float) p[0].getY()); 299.       for (int i = 1; i < p.length; i++) 300.          s.lineTo((float) p[i].getX(), (float) p[i].getY()); 301.       s.closePath(); 302.       return s; 303.    } 304. } 305. 306. /** 307.    Makes a quad curve defined by two end points and a control 308.    point. 309. */ 310. class QuadCurveMaker extends ShapeMaker 311. { 312.    public QuadCurveMaker() { super(3); } 313. 314.    public Shape makeShape(Point2D[] p) 315.    { 316.       return new QuadCurve2D.Double(p[0].getX(), p[0].getY(), p[1].getX(), p[1].getY(), 317.          p[2].getX(), p[2].getY()); 318.    } 319. } 320. 321. /** 322.    Makes a cubic curve defined by two end points and two control 323.    points. 324. */ 325. class CubicCurveMaker extends ShapeMaker 326. { 327.    public CubicCurveMaker() { super(4); } 328. 329.    public Shape makeShape(Point2D[] p) 330.    { 331.       return new CubicCurve2D.Double(p[0].getX(), p[0].getY(), p[1].getX(), p[1].getY(), 332.          p[2].getX(), p[2].getY(), p[3].getX(), p[3].getY()); 333.    } 334. } 


 java.awt.geom.RoundRectangle2D.Double 1.2 

  • RoundRectangle2D.Double(double x, double y, double w, double h, double arcWidth, double arcHeight)

    constructs a round rectangle with the given bounding rectangle and arc dimensions.

    Parameters:

    x, y

    Top-left corner of bounding rectangle

     

    w, h

    Width and height of bounding rectangle

     

    arcWidth

    The horizontal distance from the center to the end of the elliptical boundary arc

     

    arcHeight

    The vertical distance from the center to the end of the elliptical boundary arc



 java.awt.geom.Arc2D.Double 1.2 

  • Arc2D.Double(double x, double y, double w, double h, double startAngle, double arcAngle, int type)

    constructs an arc with the given bounding rectangle, start, and arc angle and arc type.

    Parameters:

    x, y

    Top-left corner of bounding rectangle.

     

    w, h

    Width and height of bounding rectangle.

     

    startAngle

    The angular measurement between the x-axis and the line joining the center of the bounding rectangle with the starting point of the arc, in degrees. The angle is distorted so that an "angle" of 45° corresponds to the angle between the x-axis and the line joining the center and top-right corner of the bounding rectangle.

     

    arcAngle

    The difference between the distorted end and start anglessee the sidebar on page 462. For a circular arc, this value equals the angle swept out by the arc.

     

    type

    One of Arc2D.OPEN, Arc2D.PIE, and Arc2D.CHORD



 java.awt.geom.QuadCurve2D.Double 1.2 

  • QuadCurve2D.Double(double x1, double y1, double ctrlx, double ctrly, double x2, double y2)

    constructs a quadratic curve from a start point, a control point, and an end point.

    Parameters:

    x1, y1

    The start point

     

    ctrlx, ctrly

    The control point

     

    x2, y2

    The end points



 java.awt.geom.CubicCurve2D.Double 1.2 

  • CubicCurve2D.Double(double x1, double y1, double ctrlx1, double ctrly1, double ctrlx2, double ctrly2, double x2, double y2)

    constructs a cubic curve from a start point, two control points, and an end point.

    Parameters:

    x1, y1

    The start point

     

    ctrlx1, ctrly1

    The first control point

     

    ctrlx2, ctrly2

    The second control point

     

    x2, y2

    The end points



 java.awt.geom.GeneralPath 1.2 

  • GeneralPath()

    constructs an empty general path.

  • void moveTo(float x, float y)

    makes (x, y) the current point, that is, the starting point of the next segment.

  • void lineTo(float x, float y)

  • void quadTo(float ctrlx, float ctrly, float x, float y)

  • void curveTo(float ctrl1x, float ctrl1y, float ctrl2x, float ctrl2y, float x, float y)

    draw a line, quadratic curve, or cubic curve from the current point to the end point (x, y), and make that end point the current point.

  • void append(Shape s, boolean connect)

    adds the outline of the given shape to the general path. If connect is TRue, the current point of the general path is connected to the starting point of the added shape by a straight line.

  • void closePath()

    closes the path by drawing a straight line from the current point to the first point in the path.



    Core JavaT 2 Volume II - Advanced Features
    Building an On Demand Computing Environment with IBM: How to Optimize Your Current Infrastructure for Today and Tomorrow (MaxFacts Guidebook series)
    ISBN: 193164411X
    EAN: 2147483647
    Year: 2003
    Pages: 156
    Authors: Jim Hoskins

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net