|
Suppose you need to draw an object such as an automobile. You know, from the manufacturer's specifications, the height, wheelbase, and total length. You could, of course, figure out all pixel positions, assuming some number of pixels per meter. However, there is an easier way: You can ask the graphics context to carry out the conversion for you.
The scale method of the Graphics2D class sets the coordinate transformation of the graphics context to a scaling transformation. That transformation changes user coordinates (user-specified units) to device coordinates (pixels). Figure 7-18 shows how the transformation works. Figure 7-18. User and device coordinatesCoordinate transformations are very useful in practice. They allow you to work with convenient coordinate values. The graphics context takes care of the dirty work of transforming them to pixels. There are four fundamental transformations.
Figure 7-19 shows how these four fundamental transformations act on a unit square. Figure 7-19. The fundamental transformationsThe scale, rotate, translate, and shear methods of the Graphics2D class set the coordinate transformation of the graphics context to one of these fundamental transformations. You can compose the transformations. For example, you may want to rotate shapes and double their size. Then, you supply both a rotation and a scaling transformation. g2.rotate(angle); g2.scale(2, 2); g2.draw(. . .); In this case, it does not matter in which order you supply the transformations. However, with most transformations, order does matter. For example, if you want to rotate and shear, then it makes a difference which of the transformations you supply first. You need to figure out what your intention is. The graphics context will apply the transformations in the opposite order in which you supplied them. That is, the last transformation that you supply is applied first. You can supply as many transformations as you like. For example, consider the following sequence of transformations: g2.translate(x, y); g2.rotate(a); g2.translate(-x, -y); The last transformation (which is applied first) moves the point (x, y) to the origin. The second transformation rotates with an angle a around the origin. The final transformation moves the origin back to (x, y). The overall effect is a rotation with center point (x, y)see Figure 7-20. Because rotating about a point other than the origin is such a common operation, there is a shortcut: g2.rotate(a, x, y); Figure 7-20. Composing transformationsIf you know some matrix theory, you are probably aware that all rotations, translations, scalings, shears, and their compositions can be expressed by matrix transformations of the form: Such a transformation is called an affine transformation. In the Java 2D API, the AffineTransform class describes such a transformation. If you know the components of a particular transformation matrix, you can construct it directly as AffineTransform t = new AffineTransform(a, b, c, d, e, f); Additionally, the factory methods getrotateInstance, getScaleInstance, getTRanslateInstance, and getShearInstance construct the matrices that represent these transformation types. For example, the call t = AffineTransform.getScaleInstance(2.0F, 0.5F); returns a transformation that corresponds to the matrix Finally, the instance methods setToRotation, setToScale, setToTranslation, and setToShear set a transformation object to a new type. Here is an example. t.setToRotation(angle); // sets t to a rotation You can set the coordinate transformation of the graphics context to an AffineTransform object. g2.setTransform(t); // replaces current transformation However, in practice, you shouldn't call the setTransform operation, since it replaces any existing transformation that the graphics context may have. For example, a graphics context for printing in landscape mode already contains a 90-degree rotation transformation. If you call setTransform, you obliterate that rotation. Instead, call the transform method. g2.transform(t); // composes current transformation with t It composes the existing transformation with the new AffineTransform object. If you just want to apply a transformation temporarily, then you first get the old transformation, compose with your new transformation, and finally restore the old transformation when you are done. AffineTransform oldTransform = g2.getTransform(); // save old transform g2.transform(t); // apply temporary transform // now draw on g2 g2.setTransform(oldTransform); // restore old transform The program in Example 7-5 lets the user choose among the four fundamental transformations. The paintComponent method draws a square, then applies the selected transformation and redraws the square. However, for a good visual appearance, we want to have the square and its transform appear on the center of the display panel. For that reason, the paintComponent method first sets the coordinate transformation to a translation. g2.translate(getWidth() / 2, getHeight() / 2); This translation moves the origin to the center of the component. Then, the paintComponent method draws a square that is centered around the origin. square = new Rectangle2D.Double(-50, -50, 100, 100); . . . g2.setPaint(Color.gray); g2.draw(square); However, because the graphics context applies the translation to the shape, the square is actually drawn with its center lying at the center of the component. Next, the transformation that the user selected is composed with the current transformation, and the square is drawn once again. g2.transform(t); g2.setPaint(Color.black); g2.draw(square); The original square is drawn in gray, and the transformed one in black (see Figure 7-21). Example 7-5. TransformTest.java[View full width] 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 displays the effects of various transformations. 9. */ 10. public class TransformTest 11. { 12. public static void main(String[] args) 13. { 14. JFrame frame = new TransformTestFrame(); 15. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 16. frame.setVisible(true); 17. } 18. } 19. 20. /** 21. This frame contains radio buttons to choose a transformation 22. and a panel to display the effect of the chosen 23. transformation. 24. */ 25. class TransformTestFrame extends JFrame 26. { 27. public TransformTestFrame() 28. { 29. setTitle("TransformTest"); 30. setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); 31. 32. canvas = new TransformPanel(); 33. add(canvas, BorderLayout.CENTER); 34. 35. JPanel buttonPanel = new JPanel(); 36. ButtonGroup group = new ButtonGroup(); 37. 38. JRadioButton rotateButton = new JRadioButton("Rotate", true); 39. buttonPanel.add(rotateButton); 40. group.add(rotateButton); 41. rotateButton.addActionListener(new 42. ActionListener() 43. { 44. public void actionPerformed(ActionEvent event) 45. { 46. canvas.setRotate(); 47. } 48. }); 49. 50. JRadioButton translateButton = new JRadioButton("Translate", false); 51. buttonPanel.add(translateButton); 52. group.add(translateButton); 53. translateButton.addActionListener(new 54. ActionListener() 55. { 56. public void actionPerformed(ActionEvent event) 57. { 58. canvas.setTranslate(); 59. } 60. }); 61. 62. JRadioButton scaleButton = new JRadioButton("Scale", false); 63. buttonPanel.add(scaleButton); 64. group.add(scaleButton); 65. scaleButton.addActionListener(new 66. ActionListener() 67. { 68. public void actionPerformed(ActionEvent event) 69. { 70. canvas.setScale(); 71. } 72. }); 73. 74. JRadioButton shearButton = new JRadioButton("Shear", false); 75. buttonPanel.add(shearButton); 76. group.add(shearButton); 77. shearButton.addActionListener(new 78. ActionListener() 79. { 80. public void actionPerformed(ActionEvent event) 81. { 82. canvas.setShear(); 83. } 84. }); 85. 86. add(buttonPanel, BorderLayout.NORTH); 87. } 88. 89. private TransformPanel canvas; 90. private static final int DEFAULT_WIDTH = 300; 91. private static final int DEFAULT_HEIGHT = 300; 92. } 93. 94. /** 95. This panel displays a square and its transformed image 96. under a transformation. 97. */ 98. class TransformPanel extends JPanel 99. { 100. public TransformPanel() 101. { 102. square = new Rectangle2D.Double(-50, -50, 100, 100); 103. t = new AffineTransform(); 104. setRotate(); 105. } 106. 107. public void paintComponent(Graphics g) 108. { 109. super.paintComponent(g); 110. Graphics2D g2 = (Graphics2D) g; 111. g2.translate(getWidth() / 2, getHeight() / 2); 112. g2.setPaint(Color.gray); 113. g2.draw(square); 114. g2.transform(t); 115. // we don't use setTransform because we want to compose with the current translation 116. g2.setPaint(Color.black); 117. g2.draw(square); 118. } 119. 120. /** 121. Set the transformation to a rotation. 122. */ 123. public void setRotate() 124. { 125. t.setToRotation(Math.toRadians(30)); 126. repaint(); 127. } 128. 129. /** 130. Set the transformation to a translation. 131. */ 132. public void setTranslate() 133. { 134. t.setToTranslation(20, 15); 135. repaint(); 136. } 137. 138. /** 139. Set the transformation to a scale transformation. 140. */ 141. public void setScale() 142. { 143. t.setToScale(2.0, 1.5); 144. repaint(); 145. } 146. 147. /** 148. Set the transformation to a shear transformation. 149. */ 150. public void setShear() 151. { 152. t.setToShear(-0.2, 0); 153. repaint(); 154. } 155. 156. private Rectangle2D square; 157. private AffineTransform t; 158. } Figure 7-21. The TransformTest program java.awt.geom.AffineTransform 1.2
java.awt.Graphics2D 1.2
|
|