Coordinates

The Java 2D™ API maintains two coordinate spaces:
  • User space – The space in which graphics primitives are specified
  • Device space – The coordinate system of an output device such as a screen, window, or a printer
User space is a device-independent logical coordinate system, the coordinate space that your program uses. All geometries passed into Java 2D rendering routines are specified in user-space coordinates.
When the default transformation from user space to device space is used, the origin of user space is the upper-left corner of the component’s drawing area. The xcoordinate increases to the right, and the y coordinate increases downward, as shown in the following figure. The top-left corner of a window is 0,0. All coordinates are specified using integers, which is usually sufficient. However, some cases require floating point or even double precision which are also supported.
Device space is a device-dependent coordinate system that varies according to the target rendering device. Although the coordinate system for a window or screen might be very different from the coordinate system of a printer, these differences are invisible to Java programs. The necessary conversions between user space and device space are performed automatically during rendering.

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.

g2.scale(pixelsPerMeter, pixelsPerMeter);
g2.draw(new Line2D.Double(coordinates in meters)); // converts to pixels and draws scaled line
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 coordinates


Fundamental transformations
Coordinate 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.
  • Scaling: blowing up, or shrinking, all distances from a fixed point
  • Rotation: rotating all points around a fixed center
  • Translation: moving all points by a fixed amount
  • Shear: leaving one line fixed and "sliding" the lines parallel to it by an amount that is proportional to the distance from the fixed line
Figure 7-19 shows how these four fundamental transformations act on a unit square.

Figure 7-19. The fundamental transformations


The scale, rotate, translate, and shear methods of the Graphics2D class set the coordinate transformation of the graphics context to one of these fundamental transformations.


Compose 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 transformations


If 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);
Suy ra rằng, ma trận hệ số sau đây là kết quả từ nhiều phép biến đổi đồ họa (còn gọi là phép biến đổi tổng hợp), ví dụ, translate => rotate => scale => translate, ..


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
Example
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
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



API
java.awt.geom.AffineTransform 1.2
  • AffineTransform(double a, double b, double c, double d, double e, double f)
  • AffineTransform(float a, float b, float c, float d, float e, float f)
    construct the affine transform with matrix
  • AffineTransform(double[] m)
  • AffineTransform(float[] m)
    construct the affine transform with matrix
  • static AffineTransform getRotateInstance(double a)
    creates a rotation around the origin by the angle a (in radians). The transformation matrix is
  • If a is between 0 and p / 2, the rotation moves the positive x-axis toward the positive y-axis.
  • static AffineTransform getRotateInstance(double a, double x, double y)
    creates a rotation around the point (x,y) by the angle a (in radians).
  • static AffineTransform getScaleInstance(double sx, double sy)
    creates a scaling transformation that scales the x-axis by sx and the y-axis by sy. The transformation matrix is
  • static AffineTransform getShearInstance(double shx, double shy)
    creates a shear transformation that shears the x-axis by shx and the y-axis by shy. The transformation matrix is
  • static AffineTransform getTranslateInstance(double tx, double ty)
    creates a translation that moves the x-axis by tx and the y-axis by ty. The transformation matrix is
  • void setToRotation(double a)
  • void setToRotation(double a, double x, double y)
  • void setToScale(double sx, double sy)
  • void setToShear(double sx, double sy)
  • void setToTranslation(double tx, double ty)
    set this affine transformation to a basic transformation with the given parameters. See the getXxxInstance method for an explanation of the basic transformations and their parameters.

java.awt.Graphics2D 1.2
  • void setTransform(AffineTransform t)
    replaces the existing coordinate transformation of this graphics context with t.
  • void transform(AffineTransform t)
    composes the existing coordinate transformation of this graphics context with t.
  • void rotate(double a)
  • void rotate(double a, double x, double y)
  • void scale(double sx, double sy)
  • void shear(double sx, double sy)
  • void translate(double tx, double ty)
    compose the existing coordinate transformation of this graphics context with a basic transformation with the given parameters. See the AffineTransform.getXxxInstance method for an explanation of the basic transformations and their parameters.

Core Java™ 2 Volume II - Advanced Features, Seventh Edition