Coordinate Transformations


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


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.

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); 

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 

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