2

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.

Jimmi Heiserman
  • 109
  • 1
  • 10

2 Answers2

1

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;
}
David Mear
  • 2,254
  • 2
  • 13
  • 21
  • Thank you very much for this code write-up. It is _very_ close to what I need, but I discovered something odd. The scale was not quite right, but the position and rotation, etc was correct. Then I realized that the size of my browser window determined how far off the scale was. It is because my swf is set to scale `showAll`, via my html. So this is not working perfectly for _me_, due to this. The bitmap is the right size, but the content within the new bitmap is scaled wrong. I will play with this more later, but perhaps you know the fix as well. Thanks again! – Jimmi Heiserman Jan 26 '13 at 02:46
  • Ah, that's a strange one. It seems as if the concatenated matrix and the bounds are being scaled even if the stage is set to noScale. I tried a couple of things, but I've been reduced to trial and error here too. I'll update my answer if I find the fix though. – David Mear Jan 26 '13 at 23:19
  • 1
    I've added a possible fix to my answer. I couldn't find a proper way to get the stage's scale though, so it's a bit hacky, but I hope that helps! – David Mear Jan 27 '13 at 16:44
  • This does the trick! This is also the most thoroughly researched problem anyone has done for me on this site, and has come through with a truly working solution to what should have been an easy one, but turned into a rather deep rabbit hole. Much Thanks for working out every caveat! – Jimmi Heiserman Jan 28 '13 at 13:36
  • From a cleanup perspective, perhaps remove the testSprite from the stage right after you get the testMatrix? A rather small memory leak? Or do you think it is garbage collected because it contains no data, and was created in a local scope? I haven't tested it, but I suspect you can still reference it with getChildAt and numChildren, etc. Thanks again! – Jimmi Heiserman Jan 28 '13 at 13:43
  • 1
    Glad I could help, it was an interesting problem! You're definitely right about removing the test sprite though, that is a memory leak. I'll adjust my code in case anyone else uses it. – David Mear Jan 28 '13 at 14:09
0

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);
francis
  • 5,889
  • 3
  • 27
  • 51
  • While I do suspect the answer will have to do with passing a matrix into the draw command, I don't think just the _parent matrix_ is enough. I also suspect that solution will include `.transfer.concatenatedMatrix`, as that will take into account _all_ of the parents on the display list. But still no luck. – Jimmi Heiserman Jan 24 '13 at 04:38