Solution to Flex Image Rotation and Flipping around Center

Today, I was trying to rotate and flip an image around its center point in Flex and the solutions I came across on the web didn’t seem to do the trick. For images that have been previously dragged/dropped, resized, rotated, or translated, the existing solutions are simply not sufficient. With that in mind, I’ve written a solution for rotating and flipping an image around its center that works perfectly in all situations.

The Code

Without further ado, here’s the solution:

private static function rotateImage(image:Image, degrees:Number):void {
	// Calculate rotation and offsets
	var radians:Number = degrees * (Math.PI / 180.0);
	var offsetWidth:Number = image.contentWidth/2.0;
	var offsetHeight:Number =  image.contentHeight/2.0;

	// Perform rotation
	var matrix:Matrix = new Matrix();
	matrix.translate(-offsetWidth, -offsetHeight);
	matrix.rotate(radians);
	matrix.translate(+offsetWidth, +offsetHeight);
	matrix.concat(image.transform.matrix);
	image.transform.matrix = matrix;	
}

Also, here’s a related function to perform a horizontal (left to right) flip of an image:

private static function flipImage(image:Image):void {
	// Calculate offset
	var offsetWidth:Number = image.contentWidth/2.0;
	var offsetHeight:Number =  image.contentHeight/2.0;

	// Perform horizontal flip
	var matrix:Matrix = new Matrix();
	matrix.translate(-offsetWidth, -offsetHeight);
	matrix.scale(-1, 1);  // change to matrix.scale(1,-1) for vertical flip
	matrix.translate(+offsetWidth, +offsetHeight);
	matrix.concat(image.transform.matrix);
	image.transform.matrix = matrix;
}

The Problem Explained

The problem, in a nutshell, is that Flex maintains the top left corner of the image for all transformation and ignores the center point. For example, just calling the rotate() method will produce rotations around the corner such as in this example. Many almost-solutions, see here and here, resolve this by first moving (translating) the image around its center by using the image’s vertical and horizontal midpoint.

A more complex issue arises, though, when the image is no longer positioned at its anchor, or in technical speak the image’s transformation tx/ty values are not 0. I found once you sufficiently drag/drop, rotate, or otherwise modify the image, all of the solutions tend to fall apart. In my situation, all the images were oriented with tx/ty referring to the anchor of the canvas, making these previous solutions difficult to rely upon.

The Solution Explained

After some fun manipulating matrices, I’ve written and tested a solution that will properly rotate an existing image around its center regardless of where it is positioned on the canvas or what transformations have been performed on it. It starts by applying rotation around the image’s center to the identity matrix. In this way, it simulates rotating the matrix around its midpoint translated position, but it is actually applied to the identity matrix. Once the rotational matrix has been calculated, the image is stuck at the top of the canvas. What we really need is a matrix that will take the rotational matrix we’ve just calculated and apply all the previous transformations to orient/rotate the image in the proper position. Well it so happens we have such a matrix, the image’s transformation matrix in fact! So, we apply the transformation matrix of the image to our calculated rotational matrix and voila! The result is our transformed image rotated by the specified amount around its center. We just set the image’s transformation matrix to this result and we are done.

Where’s the Flex Plug-in for Eclipse 3.5?

Eclipse + Flex = X

Over 7 months after Eclipse 3.5 (Galileo) was released, Adobe still has not released an update for its popular Flex Builder 3 plug-in that would make it compatible with the most recent version of Eclipse. Those of us who rely on the plug-in for Flex/Air development are still stuck using Eclipse 3.4 (Ganymede), or trying a number of manual install attempts, described here and here, neither of which worked for me. Ultimately, though, I’m not fond of hacky solutions for production-level products that I pay good money for.

Adobe has informally acknowledged the issue via its bug-tracking system, FB-21284 and FB-21025 (login required), although they have not publicly announced when a solution will be available. With the planned release of Eclipse 3.6 (Helios) now less than 5 months away, it makes you wonder when and even if the issue will be addressed. The Flex/Air projects have always been one of Adobe’s more grass-root movements, especially compared to its Creative and Web Suite products, and it’s a shame they have let the developers down. I call on everyone to contact Adobe with your concerns about Flex, with the hope they will address the issue if they understand how many developers this affects.

Updated (September 2010): Since releasing Flash Builder 4, Adobe has updated the Flex Eclipse plug-in to support Eclipse 3.5. Unfortunately, Eclipse 3.6 came out in June 2010 and is currently not supported, so Adobe is already another version behind. Also, I recommend developers stick with Flex 3 for now, as the new version is quite cumbersome to work with, meaning you’ll have to stay with Eclipse 3.4 for the time being to develop Flex applications.

Plugging Video & Image Memory Leaks in Adobe Air

Adobe AIR is known for memory leak issues, especially in regards to loading and unloading video. I have seen applications with alternating video and images crash unexpectedly after running for only 20 minutes with no user input. After doing some investigating on my own, I can confirm what others have said in the past: You have to take extra steps when unloading a video or image, or risk a serious memory leak.

Removing Images: Clear the “source” attribute
Let’s say you want to unload a set of images from display object myContainer. The obvious solution would be to myContainer.removeAllChildren(), but unfortunately, this is not enough to recover the memory for the images. To truly recover the memory, you need to call:

for each (var child:DisplayObject in myContainer.getChildren()) {
   if(child is Image) {
      (child as Image).source = "";
   }
}
myContainer.removeAllChildren();

My thanks to Russell Brown for this non-trivial solution. I can confirm his results; implementing this solution significantly reduces memory leakage.

Removing Movies: Close the movie
Next, let’s say you want to unload a movie. Most people would just stop the video and remove it from the screen but like the issue with images, this is not enough. Developers should use the following code to properly unload a movie and recover the memory:

for each (var child:DisplayObject in myContainer.getChildren()) {
   if(child is VideoDisplay) {
      (child as VideoDisplay).stop();
      (child as VideoDisplay).close();
      (child as VideoDisplay).source = "";
   }
}
myContainer.removeAllChildren();

Granted, this code assumes you may have multiple movies in your container, although it can be combined with the previous solution to remove images and movies at the same time. Thanks to Ryan Phelan for their groundwork discovering this issue.

Adobe’s Broken Garbage Collector
For whatever reason, simply removing an image or video from the scene and setting references of it to be null is not enough for the garbage collector to recover the memory. If you have an application that cycles through two movies, for example, you may see the memory continue to grow drastically over time as all the previous copies of the video are kept in memory. Hopefully, Adobe will resolve this in future versions of the AIR runtime, but in the meantime developers must take extra steps to sure items have been unloaded and memory reclaimed.