Affine Transforms in Java2D

title page &
other versions

.

 

8. Code Examples

The bulk of the work for Wiggly Text is implemented in the class TransformedTextLabel.   This subclass of JComponet displays a string, where each letter of the string can be transformed by its own AffineTransform instance.  We present three code examples here that are illustrative of  how to compute with affine transforms.  Note that these examples are somewhat abbreviated for clarity.   The omitted code has nothing to do with affine transforms and the code presented here will work.

Drawing with Transforms

The drawing is done in paintComponent() (shown here ):

public void paintComponent(Graphics gIn) {
    Graphics2D g = (Graphics2D)gIn.create();

    Rectangle2D bounds = getLetterBounds();
    g.translate(
        getWidth() / 2.0 - bounds.getCenterX(),
        getHeight() / 2.0 - bounds.getCenterY());

    AffineTransform baseTransform =
        g.getTransform();

    int n = length();
    for (int i = 0; i < n; i += 1) {
        g.setTransform(baseTransform);
        g.transform(getTransform(i));
        g.translate(-mCharWidths[i]/2.0, 0.0);

        g.drawString(
            mText.substring(i, i+1), 0, 0);
    }

    g.dispose();
}

On the first line we cast the passed in Graphics object to a Graphics2D.  In Java1.2, all Graphics objects are really instances of Graphics2D.  However, for compatibility, none of the existing method signatures were changed.

The next section gets the bounding box of all the transformed letters (more on that later), and then translates the coordinate system from 0,0 in the upper left (as passed in), so that the center of the bounding box is centered in the component.  We then save that transform away.

Then, for each letter, we do the following:

  1. The coordinate system is reset to the base transform, leaving the bounding box centered in the component.
  2. The AffineTransform object for the character is fetched with getTransform().  This object was precomputed in a subclass earlier and saved in an array.  This is then applied to the coordinate system with the transform() method of Graphics2D.
  3. The coordinate system is shifted by half the character's width.  This is so that drawing the character at 0,0 will position the center of the baseline of the character exactly where the character's transform placed 0,0.
  4. Finally, the character is drawn at 0,0.

Creating Transforms

Subclasses of TransformedTextLabel are required to compute an AffineTransform instance for each character on demand.  The class ArcedTextLabel rotates the coordinate system about a center point.  Each letter is rotated a different amount.  The code looks like:

public AffineTransform computeTransform(int i)
{
    double pos = getCharDist(i) / getTextDist();
    double angle = mArcAngle * (pos - 0.5);

    return AffineTransform.getRotateInstance(
        angle, 0.0, mRadius);
}

A relative position, between 0 and 1, along the arc is computed using two utility methods that give information about the text as if it were drawn normally:  getCharDist() returns the distance from the left edge to the center of a given character, and getTextDist() returns the total width of the text.  This position is then used to compute the amount of rotation.  Finally, we return an AffineTransform for rotation about a point below the center of the component, (0.0, mRadius).

Transforming Shapes

TransformTextLabel needs to compute the bounding box of all the transformed letters. It uses the bounding box to both provide a preferred size, and for centering the letters in the component (see the drawing above).   It computes the bounding box this with this code:

protected Rectangle2D computeLetterBounds() {
    Rectangle2D bounds = null;
    int n = length();

    GlyphVector gv = getFont().createGlyphVector(
        new FontRenderContext(
            ((Graphics2D)getGraphics()).getTransform(),
            true, true),
        mText);

    AffineTransform warpAT = new AffineTransform();

    for (int i = 0; i < n; i += 1) {
        warpAT.setToIdentity();
        warpAT.concatenate(getTransform(i));
        warpAT.translate(-mCharWidths[i]/2.0, 0.0);

        Rectangle2D letterRect =
            gv.getGlyphOutline(i).getBounds2D();
        Shape warpedRect =
            warpAT.createTransformedShape(letterRect);
        Rectangle2D dispRect =
            warpedRect.getBounds2D();

        if (bounds == null)
            bounds = dispRect;
        else
            Rectangle2D.union(
                bounds, dispRect, bounds);
    }

    return bounds;
}

In the loop of this code, warpAT is set up to be the same transform we'll use when drawing.  Then letterRect is set to the bounding rectangle of the characters outline.  This rectangle is warped by the transform with the createTransformedShape() method.  Note that warpedRect is a Shape, not a Rectangle2D: a rectangle transformed by an affine transform may not be a rectangle any more.   Then, the bounds of that warped shape is combined, via Rectangle2D.union, with previous bounds to create the complete bounding rectangle.

Affine Transform Lesson
1. Introduction
2. Ordering
3. Ordering, Part II
4. Try It
5. Graphics2D
6. AffineTransform
7. Wiggly Text
8. Code Examples
9. Links & Credits
Back ] Next ]

 

Back ] Next ]
 
pixel-008000.gif (807 bytes)
Copyright 1999
Glyphic Technology