Oct
03
2008

The trouble with getBounds()

What do you do when you want to know the exact details of an objects whereabouts on stage?  Within Actionscript’s command set, there are a few options, such as the object’s own .x .y .width .height properties.  However, your best bet is to call the getBounds() function.  That’ll tell you the extents of what’s being drawn to the screen.  That is, unless you happen to be using a mask inside the movie clip.

A tiny object being masked by a huge shape will cause getBounds() to return the extents of… the mask, not the visible object.  It gets even better if your object happens to move beyond the masked area–thus making it totally invisible.  Now getBounds() exapands to include the area of the giant mask plus that of the tiny object, which is no longer being drawn to the screen at all.

In fact, there appears to be no built-in method for reliably determining the actual screen area a MovieClip is occupying.  Do we call this a bug, or was it really meant to work this way??? :S

I decided to try writing a handy dandy function to remedy this problem, and in so doing discovered another serious limitation involving masks.  When you create an arrangement of layers in the Flash IDE with, let’s say three or four objects being masked by one mask, you’ve done something that apparently is only possible with the Flash IDE, and not with AS3.  If you have a look at the .mask property of each of those masked objects, they’ll all be set to NULL.  There’s no sign of any masking going on, as far as ActionScript is concerned.  Suppose you manually set the .mask property of one of those three or four masked objects?  The result is that all the other objects will be unmasked.  Using the .mask property strictly pairs one mask to one display object.  So now you’ve got to find a way of duplicating your mask, so each object has its own mask…!  :grit:

Well, nonetheless I wrote my own routine: getVisibility() which reliably tracks down whatever is actually visible, and nothing more.  It’s reliable, that is, as long as you manually set your masks in code–one mask for each object.  If a better solution exists, I’d like to know about it.  For now, here’s a little demo of my function versus AS3’s native functions…

(Either JavaScript is not active or you are using an old version of Adobe Flash Player. Please install the newest Flash Player.)

There’s only one error I can detect in the result of getVisibility() …When objects are rotated, their visibility is based upon a rotated bounding box, rather than the precise shape of the object.  Still, this gets us considerably closer to a solution.

The .fla can be downloaded HERE.  Here’s the code for my getVisibility() function…

function getVisibility(obj:DisplayObject):Rectangle {
 
	var vis:Rectangle;
 
	if (obj.parent == null) return new Rectangle();
 
	if (obj is DisplayObjectContainer) {
		vis = getChildVisibility(obj, obj.parent);
	} else {
		vis = obj.getBounds(obj.parent);
	}
 
	// Is the DisplayObject masked?
	if (obj.mask != null) {
		vis = vis.intersection(obj.mask.getBounds(obj.parent));
	}
 
	// Is the DisplayObject partly or completely off-stage?
	vis = vis.intersection(obj.stage.getBounds(obj.parent));
 
	return vis;
}
 
function getChildVisibility(obj:*, target:DisplayObjectContainer):Rectangle {
 
	var vis:Rectangle = new Rectangle();
	var child:DisplayObject;
	var childRect:Rectangle;
	var i:uint;
 
	for (i = 1; i <= obj.numChildren; i++) {
		child = obj.getChildAt(i-1);
 
		if (child != null) {
			if (child.visible) {
				if (child is DisplayObjectContainer) {
					childRect = getChildVisibility(child, target);
				} else {
					childRect = child.getBounds(target);
				}
				if (child.mask != null) {
					childRect = childRect.intersection(child.mask.getBounds(target));
				}
				vis = vis.union(childRect);
			}
		}
	}
	return vis;
}


Written by barliesque in: AS3 | Tags: , , , ,

6 Comments »

  • hey david,
    you could use a bitmap to check the real dimensions. too bad actionscript doesn’t provide something built-in. here’s a simple example:

    package {
      import flash.display.*;
      import flash.geom.ColorTransform;
      import flash.geom.Rectangle;
      import flash.text.TextField;
     
      public class Test extends Sprite {
        private var t:TextField;
        private var m:Shape;
     
        public function Test () {
          t = new TextField();
          t.background = true;
          t.backgroundColor = 0x00FF00;
          t.text = "Hi there";
     
          m = new Shape();
          m.graphics.beginFill(0x000000);
          m.graphics.drawRect(0, 0, 15, 25);
     
          addChild(t);
          addChild(m);
          t.mask = m;
     
          trace(t.height);  // 100 (not 25)
          trace(getVisibleHeight(t));
        }
     
        public function getVisibleHeight (o:DisplayObject):Number {
          var bounds:Rectangle;
          var bitmapData:BitmapData = new BitmapData(o.width, o.height, true, 0);
          bitmapData.draw(o, null, new ColorTransform( 1, 1, 1, 1, 255, -255, -255, 255 ) );
          bounds = bitmapData.getColorBoundsRect( 0xFF000000, 0xFF000000 );
          bitmapData.dispose(); 
          return bounds.y + bounds.height;
        }
      }
    }
    Comment | 9.Nov.08
  • Thanks for dropping by Colin.

    That’s an approach to the problem I hadn’t considered. Looks like a pretty reliable method without the problem of having to reconstruct masks in code. Nice! I only wonder if the performance hit of pre-rendering the MovieClip makes it usable on a frame-by-frame basis.

    Comment | 10.Nov.08
  • matt

    this saved me a lot of time and frustration, thanks David!

    Comment | 15.Oct.09
  • [...] During my search, I found a post from David Barlia: http://studio.barliesque.com/blog/2008/10/the-trouble-with-getbounds/ [...]

    Pingback | 26.Jan.10
  • Hi,
    tkx for your code.
    I took the BitmapData approach from colin and made a function that works with DisplayObject whose upper left corner is not at 0,0. It returns a Rectangle object too.

    http://blog.open-design.be/2010/01/26/getbounds-on-displayobject-not-functioning-properly/

    Comment | 26.Jan.10
  • hello,

    i tried this with 2 squares named “test” and “masque” in the IDE :

    test.mask = masque;
    test.scrollRect = new Rectangle(0, 0, 20, 20);

    var real:Rectangle;

    real = test.getRect(test)
    .intersection(test.mask.getRect(test))
    .intersection(test.scrollRect);

    trace(real); (x=10, y=10, w=10, h=10)

    i don’t know if it looks like what you want

    Comment | 22.Apr.10

RSS feed for comments on this post. TrackBack URL


Leave a comment!

NOTE: All comments are moderated, so may take a day or two to appear.

Powered by WordPress | Theme based on Aeros by theBuckMaker | Admin