In Bonsai’s latest release (0.4.1) we added support for a new ‘getBoundingBox’ method on all DisplayObjects. We can now accurately calculate the bounding box of paths, and groups containing paths.

To demonstrate we’ve created a demo with a constantly morphing star and a constantly re-calculated bounding-box:

We calculate the bounding box of any given path by converting all of its segments to cubic bezier curves and then calculating the extrema of each curve individually. It’s then a simple case of finding the smallest and biggest values on each axis to determine the bounding box.

Why?

Bonsai has always had a split architecture. The renderer has always been seperate to the runner. There are very good reasons for this but unfortunately it meant we could never query the renderer-side for information about drawn objects. So SVG’s getBBox was off the table. This is a good thing though. It’s very slow. Without our runner-side implementation something like the morphing-star demo above would not be possible. There are also many other applications.

The API

All DisplayObject’s now have a ‘getBoundingBox’ method. It returns an object like this:

1
2
3
4
5
6
7
8
{
left: 22,
top: 44,
width: 100,
height: 100,
bottom: 144,
right: 122
}

Calling ‘getBoundingBox’ with no arguments will give you the bounding box of the object in local coordinate space. For example:

1
2
var rect = new Rect(50, 50, 100, 100);
rect.getBoundingBox(); // 0,0,100,100

To get the real position of the object in its parent coordinate space, you would simply pass a matrix transform so that the calculated extrema are transformed:

1
2
var rect = new Rect(50, 50, 100, 100);
rect.getBoundingBox( rect.attr(‘matrix’) ); // 50,50,100,100

Coordinate spaces

In Bonsai, and in most other drawing frameworks, local coordinate space is differentiated from the parent or global coordinate space. Take this simple example:

1
new Path().moveTo(0,0).lineTo(20,20);

That path would have a local bounding box of ’0,0,20,20′. Compare that with:

1
new Path().moveTo(10,10).lineTo(20,20);

That path would have a local bounding box of ’10,10,20,20′.

When we place a DisplayObject, such as one of these paths, onto the stage it is positioned according to:

  1. Its own x and y coordinates (its own transform) — this is not the same as the initial ‘moveTo(x, y)’ points.
  2. All of its ancestors’ collective transforms

We can get the bounding box relative to the path’s position within its own parent by grabbing the path’s transform (a 2D matrix in the form ‘scaleX, skewX, skewY, scaleY, translateX, translateY’) and passing it to the ‘getBoundingBox’ method:

1
var bbox = myPath.getBoundingBox( myPath.attr(‘matrix’) );

It follows that we can get the global bounding box of ‘myPath’ by collecting an concat’ing all transform matrices between and including the path and the global stage:

1
2
3
4
5
6
7
8
9
10
11
12
13
DisplayObject.prototype.getAbsoluteBoundingBox = function() {var matrix = this.attr(‘matrix’).clone();
var p = this;

while ((p = p.parent) && p.id !== 0) {
// Get parent’s transform and concat
// it with what we have so far:
matrix.concat(p.attr(‘matrix’));
}

return this.getBoundingBox( matrix );
};

And now we can directly call:

1
var bbox = myPath.getAbsoluteBoundingBox(); // gives global bounding box

Note: We are hoping to add something like this to the API very soon. Please follow progress here.