java.awt.geom.AffineTransform is a class
that represents a PostScript-like coordinate transformation i.e. a 3 × 3 matrix multiplication. It lets you
rotate around an anchor point, translate the axes and shear (change the
x and y scaling). Using negative scale factors, you can flip (reflect
around the x or y axis). The transform converts an x,y coordinate to a
new x',y' coordinate.
[ x'] [ m00 m01 m02 ] [ x ] [ m00 * x + m01 * y + m02 ]
[ y'] = [ m10 m11 m12 ] [ y ] = [ m10 * x + m11 * y + m12 ]
[ 1 ] [ 0 0 1 ] [ 1 ] [ 1 ]
Though AffineTransform is often associated
with drawing, it is not tied in any way to the Graphics
class. It is purely mathematical.
Gotchas
- When using AffineTransform in a print method, make sure you chain your AffineTransforms onto the existing one, or
else you will lose the scaling from printer points to device pixels
and your results will come out as miniatures. You should do the same
in paint methods too to prepare for
subpixel devices. In other words, new
AffineTransform is probably a bug. You
should be using gr2.getTransform();
- Rotations and quadrant rotations are clockwise, reversing hundreds
of years of tradition. A positive rotate()
rotates the clockwise around the origin, and negative counter
clockwise. quadrantRotate() measures in
90 degree increments. rotate()
measures in radians.
- Beware of transform().
It applies transforms in reverse order. You prepare the transform in
order from device pixels to user co-ordinates, but transform
runs them in reverse order taking user co-ordinate points and sizes
and converts them to device co-ordinates and sizes.
Tips
- Watch out: setToRotation is not
cumulative. Graphics2.setTransform
replaces the transform. It does not append the transform to the
existing transforms.
- It pays to learn how to use Graphics2D
and AffineTransform. It is much simpler
to draw something simple on a simple grid and transform it than it
is to compute all the transformed points on the natural grid
yourself.
- Angles are in radians
- You specify your points are in the current transformed user
coordinate system, and the Transform converts them to device
co-ordinates. The must common error is to trying to use a lower
level co-ordinate system in place of the one defined by the current
transform. Best to just experiment drawing lines on a Canvas
till you get the hang of it before tackling your real project.
- Graphics.drawImage
will do a simple scale for you, often inadvertently. You don’t
need an AffineTransform for that.
- Constantly remind yourself that y grows down.
- rotate and quadrantRotate
with lock points will just spin a rectangle around on its centre.
You will want some sort of translate touch up at the end to properly
position the rotated rectangle. That touchup must be done in
the new rotated co-ordinates.
- When you want to move the origin to point to [x,y], you say af.translate( x, y
); not af.translate(
-x, -y ); You can remember this convention by thinking about when
the transform is executed, the two values will be added to data
point [0,0] to get to the origin in device co-ordinates. Or you can
think of translate as meaning put the origin
at. Avoid thinking of it as meaning shift
the origin by such as such an amount or shift
the origin left/right/up/down even if it sometimes has that
effect.
- Keep in mind that at each stage you build your cascading Transform,
each stage has to be specified in the co-ordinate system (scale,
direction) of the previous step, not the initial system. You start
with the device co-ordinate system and work toward more abstract
co-ordinate system. When the transform is used, you start with the
abstract co-ordinates, and work backwards until you get device
co-ordinates.
- Cut out bits of paper and slide them around on graph paper. This
will give you a rough idea of what co-ordinates to expect at various
stages of calculation. It will help you narrow down where your
errors are coming form. Draw an arrow for x and y ascending at each
stage.
- AffineTransform has no flip
method to turn an Image upside down. Use
scale( 1.0,
-1.0 ) instead.
- AffineTransform has no mirror
method to reverse an image left to right. Use scale(
-1.0, 1.0
) instead.
- the Y axis grows down. This in confusing if you are familiar with
Cartesian coordinates
that mathematics uses.
- If you are rotating by multiples of 90 degrees, you will get
faster and more accurate results if you use quadrantRotate().
- If you scale or rotate, the Image will
spin around the upper left corner of the Image
and disappear off the screen. You must first move the origin to spot
you want to rotate around, the image, do your rotation, then move
the origin to some more suitable location. You will need some
translation before the rotation, after or both. The corrections you
need to move the origin after the rotate cause brain ache. It is not
immediately obvious whether you want negative or positive
translations and whether you should apply them in the x or y
direction. Any error results in a blank image. If you want to solve
the problem by brute force, you can try all 32 combinations of
rotation direction, positive or negative correction and whether you
apply in the x or y direction. And that does not even count the
magnitude of the correction. Imagine you had a little X made of wire
with x and y axes labelled that you could plop down over drawing at
the origin. Imagine spinning the X in clockwise or counter-clockwise
direction. Then make your origin correction in terms of the
directions on the arms of the spinned X rather than in terms of the
final image. Making such an X out of a paper clip and with coloured
tip made with a Sharpie,
that you place over your paper drawings will help immensely to grok
the notion of rotating the axes.
- A positive translate moves the origin right and down. Think of
translate as moving the origin, not moving objects.
- Try writing yourself a training program that just draws coloured
asymmetrical shapes. Experiment with various combinations of
transforms until you see the effects, stage by stage.
- When you finally get around to drawing something in your virtual
coordinate system, logically the various transforms are computed
starting with the most recently applied working back to the device
coordinate system.
- Rotating is trickier than you might suppose. Run this program and
follow through the steps to make sure you can follow what happens at
each stage.
Manual Use of AffineTransform
The basic idea is this. A transform maps every point x,y onto a new
point, by a combination of rotation, scaling, translation and
mirroring. You can manually transform a point like this:
Drawing with AffineTransform
You can also simply plug an AffineTransform
into your Graphics2D object. Then you can
specify simple coordinates and the drawing will appear in transformed
coordinates. Note that with Graphics2D,
your coordinates are doubles not ints.
Correcting Mouse Co-ordinates
The catch is, the mouse knows nothing about your transforms. You need
to convert mouse screen coordinates to your user-coordinates. This is
the inverse of what you normally do, namely converting convenient user
coordinates to pixel display coordinates. For this, you need the
inverse of your transform.
You can learn more with a Google search.
Rotating Images
Rotating Images is a tad trickier than you might expect. The code
snippet below will show you how it is done.
Learning More
Oracle’s Technote Guide on
2D : available:
Oracle’s Javadoc on
AffineTransform class : available:
Oracle’s Javadoc on
Graphics2D class : available: