Consider the following three problems:

- You want to support panning and zooming in your interface.
- You want objects in your object model to be able to use relative coordinates that reference their container object rather than absolute coordinates.
- You want to seamlessly convert application coordinates to device coordinates.

Each of these three problems can be easily solved using **affine
transformations**. An affine transformation is a transformation
that preserves parallel and perpendicular lines. The four major types
of affine transformations are:

- Translation: Moves an object
- Scaling: Resizes an object
- Rotation: Rotates an object
- Shear: Holds an object fixed in one dimension while moving its lines in the other direction.

The first three transformations are the most commonly used in graphical interfaces. Using these transformations, we can solve each of the above three problems as follows:

- Panning and Zooming: Panning involves a translation of the coordinate
axes and zooming involves a scaling of the coordinate axes.
- Relative Coordinates: Relative coordinates are implemented using
one or more translations of coordinate axes (each nested container
object has one translation).
- Application to Device Coordinates: Application coordinates can be mapped to device coordinates through a scaling transformation. For example, if one meter corresponds to 50 pixels, then the scale factor is (50, 50). If we want each meter to appear as one inch on the display, then we can query the toolkit for the device's resolution and that becomes the scale factor. For example, if the resolution is 128 pixels per inch, then 128 is our scale factor.

Scaling and rotation must be handled with care, or you can get unexpected results. In particular, both require that you know the point about which the scaling or rotation is being performed, in order to know what will happen. For example, very different results happen if you rotate an object about:

- its center
- its lower, left corner
- the window's origin

The Olsen text has an excellent discussion of the math behind transformations, so we won't discuss the math here.

The order in which you perform transformations can affect the result you see on the display, because matrix multiplication is associative, but not commutative. Here are some examples of where the order of transformations is important:

- When scaling and rotating an object, you typically want to first scale the object, and
then rotate it. Performing the operations in this order will preserve parallel and
perpendicular lines. If you rotate first, and then scale, you may not preserve parallel
and perpendicular lines. The Olsen text shows an example of how rotating a rectangle, and
then scaling it, turns the rectangle into an elongated diamond.
- When panning and zooming, you typically apply the pan operation (i.e., translation
first), and then the zoom operation (i.e., scaling). To see why the order should be
like this, look at the screen snapshots shown below, which are taken from
PanAndZoom.java:
Before Panning After Panning Zooming Before Panning Zooming After Panning

The Java 2D toolkit provides an `AffineTransform` class that allows
us to create the three types of transformations. The `Graphics2D`
class has a `setTransform` method that allows us to alter the current
transform used for drawing. The transform changes the coordinate axes. For
example, the coordinate axes start by default
at the upper left corner of the window. If a rectangle has the location
(30, 40), then it will be drawn starting at location (30,40). If
we perform a translation of
(200,100) pixels, we will start drawing at location (200,100) of the window.
The rectangle will now be drawn at location (230, 140), because that is
an offset of (30,40) from the new origin, which is at (200,100).

There are a number of things you need to be careful of in Java in order to make transformations work:

- Transformations are applied in the opposite order that they are applied
in the book. In the book, and in ordinary matrix multiplication,
transformations are applied from right to left. In most graphical
toolkits, including Java, transformations are applied in a LIFO order.
That is, the last transformation listed in your code is the first
transformation that actually gets applied, and the first transformation
listed in your code is the last transformation that actually gets
applied. The reason is that the top-down order of specifying your
transformations in your code corresponds to appending the transforms
to the matrix in left-to-right order. Hence the first transform in
your code becomes the leftmost transform in the multiplication order.
Thus it is the last transform actually applied to your shapes.
- If you alter the transform that is passed to paintComponent via the
Graphics2D object, then you should take care to ensure that the
original transform is restored when you exit paintComponent. The easiest
way to do this is to use the Graphics create/dispose methods to create
a copy of the Graphics object when you enter paintComponent, then
modify and use this copy in paintComponent, and finally to
dispose of this copy when you exit paintComponent.
If you are concerned about the costs of copying and disposing of the
Graphics object, then you can also
save the current transform when you enter a paintComponent
method using the Graphics2D object's
`getTransform`method. This method returns a*copy*of the current tranform, thus making it safe to alter the graphic object's transform. When you are finished in paintComponent, you will call the graphic object's`setTransform`with the saved copy to restore the transform.If you fail to restore the original transform, then you will get bizarre results. For example, if you have a border drawn around your canvas object, then the border will be transformed as well, because the transform you created will be applied to the border. An example of what happens when you forget to restore the transform is shown in the below figure. Notice how the border that goes around the canvas has also been transformed, which is an error. The border should be aligned with the zoom slider.

- You should modify the current transform, not create a new transform
object from scratch. The current transform will have you draw in the
appropriate place in the window. For example, if you are being drawn
in the center region of a BorderLayout, and the north region occupies
the first 100 pixels of screen real-estate, then the transform will be
set for you to draw at location (0, 100). If you create a new transform
object from scratch, it will start drawing at location (0, 0), and the
first 100 pixels of your graphics will end up being drawn under the
north region. The below figure shows the above scenario. In this example
I created a new transform rather than transforming the existing transform.
- The
`rotate(angle, x, y)`method of an AffineTransform object allows you to rotate about the coordinates (x, y). It is equivalent to writing:transform.translate(x,y); transform.rotate(angle); transform.translate(-x, -y);

- The method
`Math.toRadians(degrees)`will convert degrees to radians, which is what the AffineTransform object expects. - The AffineTransform class's
`inverseTransform(Point2D)`method will invert a transform matrix and apply the inversion to the specified point. This method allows you to convert mouse points in device coordinates into your application coordinates. - If you print an AffineTransform, it will print the two rows of its matrix, thus allowing you to check the coefficients in the matrix.

Here are a number of things to consider when scaling and/or rotating an object:

- When scaling an object, do not let the scale factor go to 0. If you do, then the
object disappears and the transform matrix becomes uninvertible. That is why you
see code that looks like:
scale = Math.max(0.01, zoomPercent / 100);

- Many objects are not preserved under rotation. Circles and lines are preserved, but
axis-aligned rectangles and ellipses are not. For example, an axis-aligned
rectangle becomes a polygon under rotation. If you apply a Java rotation to a
Rectangle2D object, you will get a PathShape back, rather than a Rectangle2D object,
because the object is no longer a rectangle.
- Continuing the previous thought, objects may "burst" out of their bounding boxes when they are rotated. For example, if you give a truck rectangular wheels, admittedly absurd, and then try to rotate the wheels, the wheels will intrude into the truck's body if you rotate the wheels about their center. The reason is that the rectangle becomes a diamond and bursts out of its original bounding box.

You should try compiling and running the two example java programs in this section and then examine the code. The code is commented to help you understand what is happening.

- PanAndZoom.java: Click the mouse button and drag it
to pan the objects in the window. Drag the slider to zoom the objects. This example
is adapted from a post by R.J. Lorimer entitled "Java2D: Have Fun With Affine
Transform". The original post and code can be found
at http://www.javalobby.org/java/forums/t19387.html.
- Transform.java: This class shows examples of how to use the rotate,
scale, and
translate transformations in Java. The class displays a rectangle
with two selection handles. One of the selection handles appears
rotated and the other is scaled to twice its normal size in the
x direction. The class also draws the x- and y-axes so that the
user can see the coordinate space set up by the current transform.
The user can interact with the application in one of four ways:

- Rotate the rectangle
- Translate the rectangle
- Scale (zoom) the rectangle
- Click somewhere in the main window and have the mouse coordinates be transformed relative to the upper left corner of the rectangle. The (X,Y) coordinates appear in the left controls window. For some reason, if you click in the window before a transformation has been applied, the affine transform object is messed up and the wrong result gets displayed. After you perform the first transformation, the mouse coordinates are correctly inverted.