February 5, 2008

in Flash & Flex

Have you ever tried to implement zooming that works like Google Maps where you can use your mouse wheel to zoom to a particular point on the map?

Well, I did and it took me quite some time to get zooming to work like that. Typically, in Flash or Flex, if you scale an object it uses the upper left corner as reference point and this can lead to akward results like the ones in the following example:

Example: Zooming Broken

To zoom, use your mouse wheel or the arrow keys on your Keyboard, especially if you're on a Mac where Flash Player is (still) missing mouse wheel support. To rotate the object, press and hold the Alt key while scrolling. Panning works with drag and drop or, again, with the arrow keys on your keyboard. Example: Zooming Broken View Example

Basically, any transformation that you apply to a DisplayObject has its origin at the object's registration point. The registration point of an object is typically the upper left corner and cannot be changed directly in ActionScript.

While working on tandem, I tried to implement the behavior that normal mapping applications have where you can zoom to the coordinates your mouse points at by scrolling. Oh my, how long it took me. To work around the issue, I nested two Sprites and moved the inner object in such a way that the registration point of the outer one was at the coordinates of the mouse. This way to dynamically change the registration point was a hack at best.

However, the revelation came soon: zooming (or scaling) is a linear transformation. This means I can scale an object and readjust its position afterwards so that it appears as if it has never moved but rather scaled right from the origin I pointed at.

At this point I successfully wrote a function to scale from an arbitrary point on an object. This code was not too long, actually pretty sweet after all my previous, fruitless endeavours. But yesterday, after looking at the source code of Piccolo, a powerful framework for building Zoomable User Interfaces (ZUI) in Java or C#, I came across an even more elegant solution:

AffineTransform is the name of the class where the magic lies in Java.

What Are Affine Transformations?

Affine transformations are part of the mathematics of linear algebra. Simply put, they are a way to mathematically describe transformations such as scale, rotation, skew and translation. An interesting property of affine transformations is that they preserve the straightness of lines while transforming, so that parallel lines stay parallel.

I won't go into deeper discussion of this topic at this point but if you have time, I recommend you to read the theory behind affine transformations and linear algebra in general. Before this enlightening experience I am about to share here, linear algebra and I were not very close. I took a linear algebra course in first semester computer science but unfortunately the professors never got around using 2D or 3D as basis for their examples which would have been great. They didn't do it because it seemed that they only got excited about n-dimensional vector spaces where n > 5. Too bad.

Affine Transformations & Flash

When I made my discovery in the source code of Piccolo, I was about to port the Java AffineTransform class to ActionScript. Then I discovered that ActionScript had a similar class called Matrix.

Basically, any visible object in Flash has a transformation attached to it which can be accessed through a property called transform. What you'll get is a Transform object that gives you, through its matrix property, access to the transformation matrix of a DisplayObject.

Usually, we change the appearance of a Sprite or MovieClip (both inherit from DisplayObject) through properties such as x, y, width, height, scaleX, scaleY or rotation. These properties can be seen as a high-level abstraction of the underlying transformation matrix of a DisplayObject.

Basically, if you need more low-level access, for example to implement zooming or rotating around a particular point, use the Transform and Matrix class.

Enough theory, let's see how it's done.

Example: Zooming Done Right

Example: Zooming View Example | View Source | Download Source (ZIP, 5KB)

Code Walk-Trough

Let us go step by step through the code of the example class called ZoomCanvas that you'll find in the source of the example above. Let's say you have an object you want to scale at a certain point. First, you get its transformation Matrix:
   affineTransform = transform.matrix
Then you move the object to the origin of the point you want to scale from:
   affineTransform.translate( -originX, -originY )
After that, you are safe to scale the object:
   affineTransform.scale( scale, scale )
Then, you simply move the object back to its original position:
   affineTransform.translate( originX, originY )
In the end, you apply the new transformation to the object
   transform.matrix = affineTransform

Since I love clear and simple code, I was very pleased with the result. From my observations this method for transforming an object seems at least as fast as the conventional way of doing it.

I hope this helps you as much as it did help me.

Further Reading