I am completely in the woods here - i have a sprite with a shape drawn in there (there are two different sprites in the example.) At any given point, I need to get the x and y value of the topmost point of the shape. The sprite does rotate so it's going to change at any point as well. I don't even know where to begin here - help?
Looping through all pixels could be rather costly. As a rule of thumb, having the player checking pixels is better, whenever possible. Here's my approach:
function getTopMost(dpo:DisplayObject,global:Boolean = false):Point {
var bounds:Rectangle = dpo.getBounds(dpo);
var bmd:BitmapData = new BitmapData(bounds.width,1,true,0);
var mat:Matrix = new Matrix();
mat.translate(-bounds.x,-bounds.y);
bmd.draw(dpo,mat);
var colRect:Rectangle = bmd.getColorBoundsRect(0xff000000,0,false);
bmd.dispose();
var point:Point = new Point(bounds.x + colRect.x,bounds.y + colRect.y);
var transfMatrix:Matrix = global ? dpo.transform.concatenatedMatrix : dpo.transform.matrix;
return transfMatrix.transformPoint(point);
}
First, we get the bounding rect of the display object. That way we discard any transparent pixel right away. Now, that rectangle already gives you the "lowest" y of the sprite (the upper bound), so now we only have to find the value for the corresponding x offset to get the point we're looking for. At this point we have to test for pixels, so we need a BitampData object. We only need to draw the visible part of the display object (the area enclosed by bounds
). But since we already know the value for y, a 1 px height will be enough. This BitmapData should allow transparency and we'll fill it up with transparent black pixels before drawing (so we can detect non-transparent pixels after; here we are considering transparent a pixel whose alpha value is 0 and non-transparent anything else; you could use a threshold instead if you wanted; it would require some minor changes to the code). When drawing, we apply a translation to only draw the part we want, an imaginary "line" of 1 px height along the upper bound, as wide as the object's bounds.
Once we have the BitmapData drawn, we get a rectangle that encloses all non-transparent pixels using getColorBoundsRect
. This rectangle's x value is the x we were looking for. And now we have both x and y. Those coords are relative to the object visible area (bounds
), so we have to offset them with the x and y of bounds
. And that's the top-leftmost point of the shape within the sprite.
But this is an untransformed point, so if you rotate or scale the sprite, this point will not reflect those transformations. Transforming this point is easy, though. Just use the sprite's transform matrix, to get the point relative to its parent. Or, if you want a global point that takes into account all transformations from this object up to the stage, use the concatenated matrix of the sprite instead.
Edit
A function that does the same as the above but let's you specify an alpha threshold in the range 0 - 255. It uses the same idea I explained previously, although it's implementation is a bit more involved.
function getTopMostThreshold(dpo:DisplayObject,alphaVal:int,global:Boolean = false):Point {
if(alphaVal < 0) {
alphaVal = 0;
}
if(alphaVal > 0xff) {
alphaVal = 0xff;
}
var alphaThreshold:uint = alphaVal << 24;
var dpoBounds:Rectangle = dpo.getBounds(dpo);
// draw all pixel with alpha > 0
var boundsBmd:BitmapData = new BitmapData(dpoBounds.width,dpoBounds.height,true,0);
var mat:Matrix = new Matrix();
mat.translate(-dpoBounds.x,-dpoBounds.y);
boundsBmd.draw(dpo,mat);
// any pixel with alpha >= threshold will be set to alpha = 0xff
boundsBmd.threshold(boundsBmd,
new Rectangle(0,0,boundsBmd.width,boundsBmd.height),
new Point(0,0),
">=",
alphaThreshold,
0xff000000,
0xff000000);
// this rect encloses all pixels that passed the threshold (and hence were set to alpha = 0xff)
// With this, we have the value for y
var alphaBounds:Rectangle = boundsBmd.getColorBoundsRect(0xff000000,0xff000000,true);
// now, let's find the value for x; we know y, so 1 px height will be enough
var aux:BitmapData = new BitmapData(alphaBounds.width,1,true,0);
// again, any pixel with alpha >= threshold will be set to alpha = 0xff
aux.threshold(boundsBmd,
alphaBounds,
new Point(0,0),
">=",
alphaThreshold,
0xff000000,
0xff000000);
// this rect will gives us the x value
var colRect:Rectangle = aux.getColorBoundsRect(0xff000000,0xff000000,true);
boundsBmd.dispose();
aux.dispose();
// now, just add up the offsets:
// the bounds of the dpo (that is, every px with alpha > 0)
// the area that encloses every px with alpha >= threshold
// the color bounds rect of the aux bmd we used to find x
var point:Point = new Point(dpoBounds.x + alphaBounds.x + colRect.x,
dpoBounds.y + alphaBounds.y + colRect.y);
var transfMatrix:Matrix = global ? dpo.transform.concatenatedMatrix : dpo.transform.matrix;
return transfMatrix.transformPoint(point);
}
Hey I feel you are trying to use this sprite as it is. If so, I would suggest you set the registration point at that location ie setting the origin of the image at that particular point. The sprite can be then easily used & checks performed using x & y coordinates.
- If you are doing it dynamically ie you are drawing the image through as3 then start from that point to draw. Your registration point will remain same as origin.
- If you are using flash to draw the image set the image so that the point occurs at 0,0.