Nov 2, 2009
Moving rotated objects, the getBounds() bug, and how to fix it : Part One
Recently I was working on some bug fixes for a client project when I came across a bug with getBounds(). The issue that needed to be fixed had to do with an application that has an alignment feature. Originally I had coded the alignment command to examine the x/y coordinates and the dimensions of the objects in order to figure out which coordinates to move the items. This approach works correctly as long as the objects being aligned do not have any rotation set.
Throwing rotation into the mix no longer guarantees that the registration point is the top left coordinate of the item being aligned, which renders the first approach incorrect. The first part of this blog post will go into the getBounds() method, what the bug is, and how to get the accurate bounds.
This SWF above demonstrates the issues with the getBounds() method. On the stage is a MovieClip instance with a triangle shape in it, the MovieClip’s rotation is set to 0 at start up. Pressing the “Toggle MC Border” button draws a 1 pixel red line around the border of the MovieClip that has the triangle. Pressing the “Toggle Unrotated getBounds()” displays the rectangle returned by the getBounds() method before the MovieClip has been rotated. Before explaining the last two buttons, lets shift focus to the Rotation input at the top. The first two buttons show the border and the unrotated bounds border. The Rotation input lets us modify the rotation of the MovieClip. If the object is rotated to 45 degrees by typing in 45 and clicking the “Set Rotation” button the first two buttons still perform the same function. Except that now that the MovieClip is rotated the unrotated bounds rectangle now longer contains the shape inside of it. If you turn off both the MC Border and the Unrotated getBounds(), and click on “Toggle Rotated getBounds()”, you will see that the red border does not accurately draw to the expected size. The top and right borders are further away from the right edge, which should meet at the top right of the shape. If you click on “Toggle MC Border”, you can visually see why. The getBounds() method calculates the bounds Rectangle object by using the actual borders of the MovieClip, instead of actual graphical space that the MovieClip occupies. If you now click on the fourth button, “Toggle getAccurateBounds()”, you will see the blue rectangle drawn to the expected shape. If the MovieClip’s border is still on, the blue rectangle will be the same size as the red rectangle as seen in the screenshot below.

This is because the red 1 pixel border showing the MovieClip’s border is taken into account, accurately, by the getAccurateBounds() method that I wrote. That results in the bounds rectangle to do the same thing that getBounds() does. If you press “Toggle MC Border” and turn off the MovieClip’s border, and then turn the accurate border off and back on, you will see the difference in how the new actual bounds is actually wrapping the rotated triangle accurately.

Below is the _getAccurateBounds() method that is calculating the blue rectangle we see above. This method demonstrates the technique to find the accurate bounds, but would likely need to be slightly altered to fit within real implementations depending on the actual architecture of the application its going to be used in because of the fact that it currently requires me to remove it from the stage to draw it independently of any other display objects. It can probably also be accomplished by looping through the children of the clip’s parent to turn off any visible items and turn them back on after the drawing process, but for the sake of simplicity in this example I will simply attach it to my placeholder bounds clip and then put it back.
Anyhow, the way that I accomplished this was by isolating the MovieClip that I want an accurate bounds Rectangle object from. In my example I place it in a Sprite called boundsClip. Next I set up a ColorTransform object with a color of blue (0×0000FF). This ColorTransform object is used in the next step, where I create a BitmapData object sized to the clip’s parent’s dimensions, in this case the SWF’s stage. I then draw the boundsClip into the BitmapData object. That is why I place the clip into a placeholder Sprite, so it can be drawn into the BitmapData in the correct position using its x,y coordinates. In the draw() method, I use the colorTransform object to tint the bitmap blue. This is crucial to the last step. Before returning the accurate bounds, I place the clip back onto the stage. And finally, I get the accurate bounds of the clip by using the getColorBoundsRect() method on the BitmapData object. In the example application above I use this Rectangle object to accurately draw the blue rectangle, which shows the accurate bounds.
In my next post I will go into how to use the bounds rectangle to actually move the objects to a desired position.
You can download the FLA for the example application here: getBoundsBug.fla
function _getAccurateBounds( clip:Sprite ):Rectangle { // Make a placeholder clip to draw the clip isolated from anything else // that is on stage. var boundsClip:Sprite = new Sprite(); boundsClip.addChild( clip ); // Make a ColorTransform object to tint the bitmap that is going to be // drawn based on the clip passed into _getAccurateBounds() var boundsColorTint:uint = 0x0000FF; var colorTransform:ColorTransform = new ColorTransform(); colorTransform.color = boundsColorTint; // Draw a BitmapData object with the clip passed into _getAccurateBounds() // and tint the BitmapData to a color, I use blue. var bitmapData:BitmapData = new BitmapData( stage.stageWidth, stage.stageHeight, true, 0xFF000000 ); bitmapData.draw( boundsClip, null, colorTransform ); // Place the mc back onto the stage after drawing it. addChild( clip ); // Return the Rectangle object from the getColorBoundsRect() method on // the BitmapData object. This Rectangle object will contain the // correct bounds of the item you want the bounds of rotated or not. return bitmapData.getColorBoundsRect( 0xFFFFFF, boundsColorTint, false ); }


