JaVa
   

Affined Transformations

Affined transformations allow us to alter the way that we draw geometry, be it by translating, rotating, scaling, or shearing. Translating simply entails moving along the x or y axis; this means that you may translate the origin of your Graphics object from the default (0, 0) location to, say, (30, 30) and then draw a shape there. But all things are relative to one another, so the current location of your shape would then need to be set to (0, 0) to draw at position (30, 30) on the Graphics object. Rotating means angular rotations about the current origin of the Graphics object. Scaling is the ability to shrink or enlarge the geometry that you are drawing. Shearing allows you to distort your geometry over a given axis. Note that the source geometry is never affected by the transformations, but the way in which the geometry is drawn to the Graphics object is. In Java the Graphics object contains a transformation state through the AffineTransform class. This state can be manipulated to change the way your drawing is performed. There is a lot of math behind the affined transformations, but luckily in Java we are given simple methods such as translate and rotate to pass parameters to. The following example AffinedTransformer.java draws a square shape spiraling out from the center of the display window. Let's take a look at the code:

Code Listing 9-4: AffinedTransformer.java
import javax.swing.*;
import java.awt.*;
public class AffinedTransformer extends JFrame
{
 public AffinedTransformer()
 {
 super("Affined Transformation Demo");
 setDefaultCloseOperation(EXIT_ON_CLOSE);
 setResizable(false);
 getContentPane().setLayout(null);
 getContentPane().add(new DisplayArea(new Rectangle(0, 0,
 DISPLAY_WIDTH, DISPLAY_HEIGHT)));
 setVisible(true);
 resizeToInternalSize(DISPLAY_WIDTH, DISPLAY_HEIGHT);
 }
 public void resizeToInternalSize(int internalWidth, int
 internalHeight)
 {
 Insets insets = getInsets();
 final int newWidth = internalWidth + insets.left +
 insets.right;
 final int newHeight = internalHeight + insets.top +
 insets.bottom;
 Runnable resize = new Runnable()
 {
 public void run()
 {
 setSize(newWidth, newHeight);
 }
 };
 if(!SwingUtilities.isEventDispatchThread())
 {
 try
 {
 SwingUtilities.invokeAndWait(resize);
 }
 catch(Exception e) {}
 }
 else
 resize.run();
 validate();
 }
 public class DisplayArea extends JPanel
 {
 public DisplayArea(Rectangle bounds)
 {
 setLayout(null);
 setBounds(bounds);
 setOpaque(false);
 }
 public void paintComponent(Graphics g)
 {
 Graphics2D g2D = (Graphics2D)g;
 g2D.setColor(Color.blue);
 g2D.fillRect(0, 0, getWidth(), getHeight());
 g2D.translate(getWidth()/2, getHeight()/2);
 for(int i=0; i<120; i++)
 {
 g2D.setColor(new Color((float)i/120.0f, 0.0f, 0.0f));
 g2D.rotate(Math.toRadians(12));
 g2D.translate(i, 0);
 g2D.scale(1.01, 1.01);
 g2D.fillRect(-SQUARE_SIZE/2, -SQUARE_SIZE/2,
 SQUARE_SIZE, SQUARE_SIZE);
 g2D.setColor(Color.white);
 g2D.drawRect(-SQUARE_SIZE/2, -SQUARE_SIZE/2,
 SQUARE_SIZE, SQUARE_SIZE);
 g2D.translate(-i, 0);
 }
 }
 }
 public static void main(String[] args)
 {
 new AffinedTransformer();
 }
 private static final int SQUARE_SIZE = 10;
 private static final int DISPLAY_WIDTH = 400;
 private static final int DISPLAY_HEIGHT = 400;
}


Java End example

When you compile and run this example, your output should be similar to the following figure.

Java Click To expand
Screenshot-5:

The important code is performed inside the method paintComponent. First we translate the origin of the Graphics object to its center:

g2D.translate(getWidth()/2, getHeight()/2);


We then perform 120 iterations of drawing the square shape:

for(int i=0; i<120; i++)
{
 g2D.setColor(new Color((float)i/120.0f, 0.0f, 0.0f));
 g2D.rotate(Math.toRadians(12));
 g2D.translate(i, 0);
 g2D.scale(1.01, 1.01);
 g2D.fillRect(-SQUARE_SIZE/2, -SQUARE_SIZE/2, SQUARE_SIZE,
 SQUARE_SIZE);
 g2D.setColor(Color.white);
 g2D.drawRect(-SQUARE_SIZE/2, -SQUARE_SIZE/2, SQUARE_SIZE,
 SQUARE_SIZE);
 g2D.translate(-i, 0);
}


For each iteration, rotate 12 degrees, translate, scale the scene, and then perform the drawing. Note that the order of the translations and rotations are important, and if we swapped them around, we would get a completely different output. We then translate back to the origin ready for performing this routine again. By continually incrementing our rotation and translation from the origin, we get the effect of a spiralling square shape. We also scale the scene by 1.01 on each axis per iteration. Scaling to 1.0 will not scale the image at all, whereas scaling to 2.0 would draw our shape at double its normal size. Note that scaling to 2.0 twice would therefore draw our shape four times as big as its usual size. For the purpose of this example, we have scaled to a mere 1.01, so the image gets a little bit larger through each of its 120 drawing iterations. You can retrieve the AffineTransform instance of a Graphics object using the method getTransform if you want to delve deeper into the functionality of this class. You may also create your own AffineTransform instance and then use the method setTransform(AffineTransform) of the Graphics object too.

Please note that this code is distinctly bad because we are rendering the same image as a series of calculations every time the window refreshes itself, which can be when another window is moved over it, for example. Ideally, we would render this to an off-screen image and then render that in the painting method instead, as we shall see shortly in the "Off-screen Images" section.

JaVa
   
Comments