Book Home Java Enterprise in a Nutshell Search this book

4.11. Transformations with AffineTransform

As we discussed earlier when we considered the Java 2D coordinate system, the java.awt.geom.AffineTransform class represents a general mapping from one coordinate system to another. AffineTransform defines a general coordinate-system transformation that can include translation, scaling, rotation, and shearing.

4.11.1. Setting Up an AffineTransform

One of the easiest ways to obtain an AffineTransform object is to use one of the static methods defined by AffineTransform. For example, getScaleInstance() returns an instance of AffineTransform that represents a simple scaling transformation.

Another way to get an AffineTransform is with the AffineTransform() constructor, of course. The no-argument version of the constructor returns an AffineTransform that represents the identity transform--that is, no transform at all. You can modify this empty transform with a number of methods. Note that AffineTransform defines several other constructors, but we have to wait to discuss them until after we've discussed the mathematics that underlie AffineTransform.

Once you have obtained an AffineTransform object, you can modify it with methods just like the methods defined by Graphics2D. Each of the translate(), scale(), rotate(), and shear() methods modifies an AffineTransform by adding the specified transformation to it. Note that there are two versions of rotate(). One rotates around the origin and the other rotates around a specified point; both use angles specified in radians. Remember that calls to these four methods are cumulative: you can build up a complex transformation as a combination of translation, scaling, rotation, and shearing.

AffineTransform also defines noncumulative methods. setToTranslation(), setToScale(), setToRotation(), and setToShear() set an AffineTransform to a single transform, replacing whatever transform was previously contained by the AffineTransform.

4.11.2. Performing Transformations

Once you have created and initialized an AffineTransform object, you can use it to transform points and shapes. AffineTransform defines a number of transform() methods that transform points represented by either java.awt. geom.Point2D objects or arrays of numbers. deltaTransform() is a variant of transform() that performs a transformation disregarding any translation component. It is designed for transforming distances or position-independent vectors, instead of actual points. inverseTransform() is the inverse of transform()--it converts points expressed in the new coordinate system back to the corresponding points in the original coordinate system.

The transform(), deltaTransform(), and inverseTransform() methods are fairly low-level and typically are not used directly by Java 2D programs. Instead, a program typically uses the createTransformedShape() method, which provides a powerful, high-level transformation capability. Given an arbitrary Shape object, this method returns a new Shape that has been transformed as specified by the AffineTransform object.

4.11.3. The Mathematics of AffineTransform

The coordinate system transformations described by AffineTransform have two very important properties:

  • Straight lines remain straight

  • Parallel lines remain parallel

An AffineTransform is a linear transform, so the transformation can be expressed in the matrix notation of linear algebra. An arbitrary AffineTransform can be mathematically expressed by six numbers arranged in a matrix like this: figure In this matrix, tx and ty are the translation amounts, sx and sy are the scaling factors, and shx and shy are the shearing factors, all in the X and Y dimensions, respectively. As we'll see in a moment, rotation is a combination of scaling and shearing, so there are not separate rx and ry numbers.

To transform a point from one coordinate system to another using an AffineTransform, we multiply the point by this matrix. Using matrix notation (and adding a few dummy matrix elements), the equation looks like this: figure This matrix equation is simply shorthand for the following system of equations:

x' = sx*x + shx*y + tx
y' = shy*x + sy*y + ty

The identity transform does not perform any transformation at all. It looks like this: figure

Mathematically, rotation is a combination of scaling and shearing. The rotation of an angle theta around the origin is expressed with a matrix like this: figure You don't need to understand how this rotation matrix works. If you remember basic trigonometry, however, you can use it and the preceding equations to verify that this matrix works for the base cases of 90-degree and 180-degree rotations.

As we've seen, it is possible to make cumulative changes to an AffineTransform. This is done by multiplying the current transformation matrix by the new transformation matrix. For example, suppose we perform a translation by 100 units in both the X and Y dimensions and follow this by scaling both the X and Y dimensions by a factor of 2. The resulting AffineTransform matrix is the product of the two individual matrices: figure Note that matrix multiplication is not commutative. If we perform the scaling operation first and then do the translation, we obtain a different result: figure

Most applications do not have to work with matrices explicitly in order to perform coordinate-system transformations. As we've seen, it typically is easier to use the translate(), scale(), rotate(), and shear() methods of either AffineTransform or Graphics2D. It is useful to understand the mathematics underlying AffineTransform, however.

You may, on occasion, have the need to create a custom AffineTransform object from a set of six numbers. A number of AffineTransform constructors and methods take matrix elements as arguments. These matrix elements are either passed in explicitly or specified in an array. Note that the matrix-element naming system used by the AffineTransform class is different than the system I've used here. The parameter names for AffineTransform methods are based on the following matrix: figure This is nothing more that a different naming scheme for the elements we are already familiar with: figure When matrix elements are passed to or returned by an AffineTransform in an array of float or double values, they are stored in this order: figure This corresponds to the following order using our mnemonic names: figure

Library Navigation Links

Copyright © 2001 O'Reilly & Associates. All rights reserved.