July 19, 2009

Image Rotation with Pixel Bender

Unlike my previous experiments with Pixel Bender, today I set out to implement a basic image manipulation routine, image rotation. While Flash natively provides the ability to rotate a DisplayObject that doesn't help if you want to combine multiple Pixel Bender filters that should use the rotated image as the source image. Turns out rotating an image in Pixel Bender was a little harder than I thought, but I ended up with a nice solution.

Rotation is a simple image manipulation that can take advantage of transformation matrixes like you can create in Flash. The normal 2D rotation transformation matrix looks like this, given d is the angle of rotation, x and y are the original pixel location, and x' and y' are the new pixel location:

 cos(d)-sin(d)  *  x  =  x' 
sin(d)cos(d) y y'

The problem with this rotation (and the standard DisplayObject.rotation property) is that the rotation is performed relative to the origin. In this case it is (0, 0) or the upper left corner of the image. To address this problem we switch to 3x3 transformation matrixes and first translate the image to define a new origin (like changing the registration point on a MovieClip) before performing the rotation. By combining a translation, rotation, and reverse translation we can rotate the image around an arbitrary point but still have the result appear anchored in the upper left. The full matrix transform then looks like this, given a and b are the translation in x and y respectively:

 10a  *  cos(d)-sin(d)0  *  10-a  *  x  =  x' 
01b sin(d)cos(d)0 01-b y y'
001 001 001 1 1

Now we are getting somewhere. The next problem is that we don't know what x and y are. We do know x' and y' as that is what outCoord() gives us. In order to use this transformation matrix in Pixel Bender we need to solve for x and y. It turns out the matrixes reduce to a linear system of equations. Thankfully I have an old symbolic solver kicking around that did the dirty work. First simplify the transform matrixes:

 (x - a)*cos(d) + (b - y)*sin(d) + a  =  x' 
(y - b)*cos(d) + (x - a)*sin(d) + b y'
1 1

That leaves us with two equations and two unknowns. Using a linear equation solver, we end up with:

x = (x' - a)*cos(d) + (y' - b)*sin(d) + a
y = (y' - b)*cos(d) + (a - x')*sin(d) + b

The translation into a Pixel Bender kernel is straight forward from here. Using a parameter called origin I've replaced a and b, outCoord() replaced x' and y', and a parameter called rotation (specified in degrees) is used for d.

float2 dstCoord = outCoord();
float angle = radians(rotation);
float cosAngle = cos(angle);
float sinAngle = sin(angle);
float x = (dstCoord.x - origin.x) * cosAngle + (dstCoord.y - origin.y) * sinAngle + origin.x;
float y = (dstCoord.y - origin.y) * cosAngle + (origin.x - dstCoord.x) * sinAngle + origin.y;
dst = sampleNearest(src, float2(x, y));

After the kernel was created and exported for use in Flash I slapped together a simple test application to play with it (view source enabled). Note: if you use the kernel in the Pixel Bender toolkit you will want to setup a minValue and maxValue for the origin parameter based on your test image size, otherwise you can't meaningfully adjust it.

If you played with the application you probably noticed the extension slider. By default a shader filter in Flash will map the source image to an equally sized output image. Increasing the various extension properties of ShaderFilter you can create an output image larger than the input which avoids any output image clipping.

I suspect if Pixel Bender graphs were supported in Flash you could easily setup a series of filters that do the image translation and rotation separately instead of combining them all into a single matrix. It might even be easy enough to do that with an array of filters but this isn't something I've played with much.

Tags: flex graphics image pixelbender