|
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 arcEach 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 classesUsing the Shape ClassesYou 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 RoundRectangle2Dproduces 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 arcFigure 7-6 illustrates the arc types. Figure 7-6. Arc typesHowever, 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 curveFigure 7-8. A cubic curveTo 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);
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
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.java1. 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
java.awt.geom.Arc2D.Double 1.2
java.awt.geom.QuadCurve2D.Double 1.2
java.awt.geom.CubicCurve2D.Double 1.2
java.awt.geom.GeneralPath 1.2
|
|