make visual clone of displayObject that's nested within other displayObjects, and add the clone to the stage layer in the same location, rotation, etc

StackOverflow https://stackoverflow.com/questions/14491610

Question

I want to be able to grab a copy of a DisplayObject that is nested within other transformed DisplayObjects (rotated, scaled, stretched objects), and be able to stamp it back into the same visual location, but on the stage layer. Essentially, being able to make a clone of a nested DisplayObject, but be able to add the clone to the stage layer, yet have it perfectly align (visually) with the original (same position, scale, rotation)

I have been working with something along the lines of:

// draw the pixels of a displayobject into a new bitmap object
var bitmapData:BitmapData = new BitmapData(nestedSprite.width, nestedSprite.height, true, 0xFFFFFF);
var bitmap:Bitmap = new Bitmap(bitmapData);
bitmapData.draw(nestedSprite);

// put the copy on the top most layer
stage.addChild(bitmap);

// position the copy to perfectly overlay the original, but on the top stage layer
var point:Point = nestedSprite.localToGlobal(new Point(0, 0));
bitmap.x = point.x;
bitmap.y = point.y;

But this only works well for displayObjects whose parents are not transformed; and for displayObjetcs that are perectly at the (0,0) origin. It falls apart for centered aligned objects or scaled parents, etc.

I am aware that I can add a matrix param to the .draw() method, as well as a clipping rectngle, and scale my bitmap afterwards, or setting the transform of one object to another, or use .transform.concatenatedMatrix, or use nestedObject.getBounds(null), or nestedSprite.getBounds(nestedSprite), etc. But I have unfortunately fallen into doing trial and error programming on this one, and with some many variables, this is never a good way to solve a programming problem.

Was it helpful?

Solution

I believe this function should work, the only extra step was offsetting the concatenated matrix so that the target would draw with its top left at (0, 0) on the Bitmap even if its origin was somewhere else. Hopefully the rest is self explanatory, but I can add more comments if anything doesn't make sense.

function createBitmapClone(target:DisplayObject):Bitmap {
    var targetTransform:Matrix = target.transform.concatenatedMatrix;
    var targetGlobalBounds:Rectangle = target.getBounds(target.stage);
    var targetGlobalPos:Point = target.localToGlobal(new Point());

    // Calculate difference between target origin and top left.
    var targetOriginOffset:Point = new Point(targetGlobalPos.x - targetGlobalBounds.left, targetGlobalPos.y - targetGlobalBounds.top);

    // Move transform matrix so that top left of target will be at (0, 0).
    targetTransform.tx = targetOriginOffset.x;
    targetTransform.ty = targetOriginOffset.y;

    var cloneData:BitmapData = new BitmapData(targetGlobalBounds.width, targetGlobalBounds.height, true, 0x00000000);
    cloneData.draw(target, targetTransform);
    var clone:Bitmap = new Bitmap(cloneData);

    // Move clone to target's global position, minus the origin offset.
    clone.x = targetGlobalPos.x - targetOriginOffset.x;
    clone.y = targetGlobalPos.y - targetOriginOffset.y;

    return clone;
}

Unfortunately, pixelBounds seems to return an origin of (0, 0) if there are any filters on the DisplayObjects, which obviously breaks things.

Edit: Replaced target.transform.pixelBounds with target.getBounds(target.stage) as a slight improvement. This keeps the position correct if there are filters, but filters on parent DisplayObjects still won't be included, and filters on the target can overlap the edges of the Bitmap. I'm not sure if there's a simple way to work around that.

Update: Jimmi Heiserman spotted that this function is broken if the swf is scaled. Without stage.scaleMode = StageScaleMode.NO_SCALE; though, the stageWidth and stageHeight parameters seem to stay unchanged, so the only (rather hacky) workaround I've found is to add an "unscaled" test Sprite and use its concatenatedMatrix to adjust the clone's position and scale:

function createScaledBitmapClone(target:DisplayObject):Bitmap {
    var targetTransform:Matrix = target.transform.concatenatedMatrix;
    var targetGlobalBounds:Rectangle = target.getBounds(target.stage);
    var targetGlobalPos:Point = target.localToGlobal(new Point());

    // Calculate difference between target origin and top left.
    var targetOriginOffset:Point = new Point(targetGlobalPos.x - targetGlobalBounds.left, targetGlobalPos.y - targetGlobalBounds.top);

    // Create a test Sprite to check if the stage is scaled.
    var testSprite:Sprite = new Sprite();
    target.stage.addChild(testSprite);
    var testMatrix:Matrix = testSprite.transform.concatenatedMatrix;
    target.stage.removeChild(testSprite);

    // Move transform matrix so that top left of target will be at (0, 0).
    targetTransform.tx = targetOriginOffset.x * testMatrix.a;
    targetTransform.ty = targetOriginOffset.y * testMatrix.d;

    var cloneData:BitmapData = new BitmapData(targetGlobalBounds.width * testMatrix.a, targetGlobalBounds.height * testMatrix.d, true, 0x00000000);
    cloneData.draw(target, targetTransform);
    var clone:Bitmap = new Bitmap(cloneData);

    // Move clone to target's global position, minus the origin offset, and cancel out stage scaling.
    clone.x = targetGlobalPos.x - targetOriginOffset.x;
    clone.y = targetGlobalPos.y - targetOriginOffset.y;
    clone.scaleX = 1 / testMatrix.a;
    clone.scaleY = 1 / testMatrix.d;

    return clone;
}

OTHER TIPS

Have you tried passing the parents transform into draw? draw takes a transform matrix as the second param.

If you have a handle on the parent you can use something like this

bitmapData.draw(nestedSprite, parent.transform.matrix);
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top