moock.org is supported in part by


May 25, 2009

get visible width/height of a display object

When it comes to the width and height variables of ActionScript's DisplayObject, what you see is not always what you get. For example, consider the following code, which draws a 50x75 rectangle at position (10, 10) in a Sprite, and then masks it with a 40-pixel-diameter circle:

// The mask
var maskShape:Sprite = new Sprite();
maskShape.graphics.beginFill(0);
maskShape.graphics.drawCircle(20, 20, 20);
addChild(maskShape);
// The rectangle
var rect:Sprite = new Sprite();
rect.graphics.beginFill(0xFF0000);
rect.graphics.drawRect(10, 10, 50, 75);
rect.mask = maskShape;
// Give the rectangle a parent
addChild(rect);

If we check, the "width" and "height" variables of "rect", we get 50 and 75, even though "rect" is masked.

trace(rect.width);   // 50
trace(rect.height);  // 75

But the "rect" object's width and height variables contradict what you see on screen:

On screen, the preceding graphic measures only 40 by 40 (the diameter of the circular mask).

The DisplayObject class's width and height variables can also seem misleading when a DisplayObjectContainer holds non-visible children. For example, the following code creates a container with two children, one of which is non-visible.

// The circle
var circle:Sprite = new Sprite();
circle.graphics.beginFill(0);
circle.graphics.drawCircle(10, 10, 10);
// The non-visible rectangle
var rect:Sprite = new Sprite();
rect.graphics.beginFill(0xFF0000);
rect.graphics.drawRect(10, 10, 50, 75);
rect.visible = false;
// The container
var container:Sprite = new Sprite();
container.addChild(circle);
container.addChild(rect);
// Give the container a parent
addChild(container);

If we check, the "width" and "height" variables of "container", we get 60 and 85, even though "rect" is non-visible:

trace(container.width);   // 60
trace(container.height);  // 85

Again, the "container" object's width and height variables contradict what you see on screen:

On screen, the preceding graphic measures only 20 by 20 (the diameter of the circle).

As a final example of misleading width and height values, consider the following code, which creates a simple text field:

var t:TextField = new TextField();
t.text = "hi there"
addChild(t);

If we check, the "width" and "height" variables of "t", we get 100 and 100--the dimensions of the TextField object's bounding box, even though that bounding box is not visible:

trace(t.width);   // 100
trace(t.height);  // 100

The TextField object's width and height variables again contradict what you see on screen:

On screen, the preceding graphic measures only 37 by 12 (the dimensions of the visible text, not the bounding box).

So what do you do if you need to know the dimensions of what's actually visible on screen? Unfortunately, in ActionScript, there's no convenient way to find that information. I've logged a bug with Adobe requesting that they add one. But until then, the only option is to copy your desired display object to a bitmap and measure the occupied pixels. I did just that on a recent MegaPhone project; you can grab the code below. Note, however, that the bitmap approach is slower than a native implementation would be, so if you use the code, all I ask is that you please vote for the preceding bug. Thanks!

/**
 * Returns the distance from the registration point of the specified 
 * object to the bottom-most visible pixel, ignoring any region
 * that is not visible due to masking. For example, if a display
 * object contains a 100-pixel-high shape and a 50-pixel-high mask,
 * getVisibleHeight() will return 50, whereas DisplayObject's
 * "height" variable would yield 100. 
 * 
 * The maximum measureable dimensions of the supplied object is
 * 2000x2000.
 */
function getVisibleHeight (o:DisplayObject):Number {
  var bitmapDataSize:int = 2000;
  var bounds:Rectangle;
  var bitmapData:BitmapData = new BitmapData(bitmapDataSize, 
                                             bitmapDataSize,
                                             true,
                                             0);
  bitmapData.draw(o);
  bounds = bitmapData.getColorBoundsRect( 0xFF000000, 0x00000000, false );
  bitmapData.dispose(); 
  return bounds.y + bounds.height;
}
/**
 * Returns the distance from the registration point of the specified 
 * object to the right-most visible pixel, ignoring any region
 * that is not visible due to masking. For example, if a display
 * object contains a 100-pixel-wide shape and a 50-pixel-wide mask,
 * getVisibleWidth() will return 50, whereas DisplayObject's
 * "width" variable would yield 100. 
 * 
 * The maximum measureable dimensions of the supplied object is
 * 2000x2000.
 * 
 * @since MegaPhone App Framework 1.0
 */
function getVisibleWidth (o:DisplayObject):Number {
  var bitmapDataSize:int = 2000;
  var bounds:Rectangle;
  var bitmapData:BitmapData = new BitmapData(bitmapDataSize, 
                                             bitmapDataSize, 
                                             true, 
                                             0);
  bitmapData.draw(o);
  bounds = bitmapData.getColorBoundsRect( 0xFF000000, 0x00000000, false );
  bitmapData.dispose(); 
  return bounds.x + bounds.width;
}

[Updates May 26, 2009: 1) Made the code faster based on an observation by Mario Klingemann. 2) Added the TextField example.]

Posted by moock at May 25, 2009 10:58 PM
Comments

Very useful, thanks for that.
I always struggled with the text bounding boxes measures, which I needed for animated, masked menus ...
This will definitely help.

Posted by: adehaas at June 2, 2009 05:25 AM

@visibledimensions: yes, that would be useful. it's left as an exercise for the reader.

@juan charvet: let me know if you encounter any problems with the code.

Posted by: colin at May 28, 2009 02:30 PM

Thanks for this, Colin. I'll put it to test on a project I currently have this exact challenge with.

Posted by: Juan Charvet at May 28, 2009 09:16 AM

hi,

very useful info, thanks. wouldn't it make sense to add an additional method, such as getVisibleDimensions() that, for example, returns a point object containing the width and height? that way the bitmap data would only have to be created once for situations when you need both the visible width and height at the same time...

Posted by: visibledimensions at May 28, 2009 08:12 AM

@nishant: the draw-to-bitmap approach is not as fast as a native player implementation would be, but it's still pretty darn fast, and should be fine for most purposes. i would think you could run the functions thousands of times per frame without much/any slowdown. popular pixel-perfect collision detection schemes use similar code successfully. i'd definitely give it a try. (scaling is a really neat idea, btw. let me know how it goes!)

Posted by: colin at May 27, 2009 03:09 PM

I eagerly read thru the whole thing hoping for a diamond of a discovery.

Good attempt, but sadly this will be quite a performance issue for me if I use this. How about you scale the object very small before drawing the bitmap, you can calculate the bounds to a good precision even then I guess. I'll give it a try :)

Posted by: Nishant at May 27, 2009 05:53 AM

@kudoshinichi: a generalized function that loops recursively through all children, catalogs their depths, then removes, unmasks, measures, reattaches, and remasks, in addition to measuring text metrics, would be significantly more work to write and maintain, would be more error prone, would be slower, would not even work with some components (e.g., flex framework) that don't expose skin children through getChildAt(), and might trigger unwanted side-effects such as layout code execution.

Posted by: colin at May 26, 2009 03:43 PM

Thanks for registering this as a bug with Adobe. Hopefully, they will listen. I am truly baffled why this isn't a built-in.

Posted by: Matthew Donadio at May 26, 2009 10:55 AM

Why don't you just loop through the parent container and removeChild, get the size then add child again?

Posted by: kudoshinichi at May 26, 2009 05:58 AM

Very useful functions, I've run into such issues many times, back from the days of actionscript 2, I can only wonder why Adobe hasnt got round to implementing such functions!

Posted by: Ncu at May 26, 2009 04:17 AM