Flash/ActionScript: Draw a display object “onto” a

2019-04-17 15:49发布

问题:

How do I properly draw one vector object onto a specific position of another in ActionScript, accounting for positioning, transparency, etc?

My idea (as suggested e.g. here) was to use beginBitmapFill() and drawRect() to draw a bitmap copy (made using BitmapData.draw()) of one MovieClip onto the .graphics property of the other MovieClip, but this proved more difficult than I thought, mainly for these reasons:

  1. Transparency is not preserved: where the source MovieClip is transparent, the target Graphics becomes white. I've tried using a BlendMode, but it doesn't help. It seems white pixels are actually copied into the BitmapData. I must be missing something, but I don't know what. It seems the problem was that the documentation for the BitmapData constructor is wrong: transparency does not default to true, but to false. So if you create a new BitmapData(x, y, true) transparency is preserved, otherwise not.

  2. Positioning. If the source MovieClip is centered (with the center at (0, 0), you have to apply a Matrix to move it when copying it to a BitmapData, or the bitmap will only contain the bottom right of the source MovieClip. Figuring out how much to move it is tricky, and I assume it involved getting the boundaries somehow. (If the source object is perfectly centered within its canvas, you can simply offset it by half its width and height, of course.)

  3. beginBitmapFill tiling. It seems if you don't start the drawRect() at (0, 0), the bitmap fill doesn't start from (0, 0) within the bitmap either. This means you have to shift the source bitmap once again, based on where you want to start drawing.

(Then there's other stuff like having to lineStyle(0,0,0) so the drawRect() doesn't draw a border etc.)

All this tricky trouble leads me to believe I'm going about this the wrong way. Is there an easier way to do this? Is there a library somewhere that can help me? Has anyone successfully done this? Perhaps my drawing canvas shouldn't be a Sprite but a Bitmap? But then I don't know how to draw to it using lineTo() etc.

Here's the situation: I have a Sprite to which I draw using its .graphics, using lineTo() etc. Now I want to use a MovieClip as a brush when drawing (or just place a copy of another MovieClip on the drawing surface), making the other MovieClip part of the graphics of the first one. I can't addChild() because then the vector graphics wouldn't interact with the drawn graphics.


EDIT: Theo has provided a working solution that works perfectly except for the fact that it does not apply the rotation and scaling applied to the DisplayObject being drawn onto the Graphics surface. This is Theo's solution:

private function drawOntoGraphics(source : IBitmapDrawable, target : Graphics, position : Point = null) : void
{
    position = position == null ? new Point() : position;
    var bounds : Rectangle = DisplayObject(source).getBounds(DisplayObject(source));
    var bitmapData : BitmapData = new BitmapData(bounds.width, bounds.height, true, 0x00000000);
    bitmapData.draw(source, new Matrix(1, 0, 0, 1, -bounds.x, -bounds.y), null, null, null, true);
    target.beginBitmapFill(bitmapData, new Matrix(1, 0, 0, 1, bounds.x + position.x, bounds.y + position.y));
    target.drawRect(bounds.x + position.x, bounds.y + position.y, bounds.width, bounds.height);
}

To support rotation and scaling, I assume the following has to be modified:

  1. The Matrix used in draw() should copy the scaling and rotation from the source DisplayObject?
  2. The Matrix used in beginBitmapFill() should copy this scaling and rotation as well? Or maybe that would create "double" scaling and rotation?
  3. The drawRect() coordinates should be modified to match the size of the scaled and rotated DisplayObject? Which means getBounds() will not be accurate?

EDIT: Here is a working implementation which supports scaling and rotation, made from all the feedback:

static function DrawOntoGraphics(source:MovieClip, target:Graphics, 
        position:Point = null):void {
    var sourceClass:Class = getDefinitionByName(getQualifiedClassName(source)) 
        as Class;   
    var sourceCopy:MovieClip = new sourceClass();
    sourceCopy.rotation = source.rotation;
    sourceCopy.scaleX = source.scaleX;
    sourceCopy.scaleY = source.scaleY;
    sourceCopy.graphics.clear();
    var placeholder:MovieClip = new MovieClip();
    placeholder.addChild(sourceCopy);
    if (!position) 
        position = new Point();
    var bounds:Rectangle = placeholder.getBounds(placeholder);
    var bitmapData:BitmapData = new BitmapData(bounds.width, bounds.height, 
        true, 0x00000000);
    var drawMatrix:Matrix = new Matrix(1, 0, 0, 1, -bounds.x, -bounds.y);
    bitmapData.draw(placeholder, drawMatrix);           
    placeholder.removeChild(sourceCopy);
    var fillMatrix:Matrix = new Matrix(1, 0, 0, 1, bounds.x + position.x, 
        bounds.y + position.y);
    target.beginBitmapFill(bitmapData, fillMatrix);
    target.lineStyle(0, 0x00000000, 0);
    target.drawRect(bounds.x + position.x, bounds.y + position.y, 
        bounds.width, bounds.height);
    target.endFill();
}

Somewhat related but not so hot question: How to put an image (say, PNG) on a graphics in Flex 3?

回答1:

Using the display objects getBounds function can be a reliable solution to translate the coordinates while drawing :

private function drawOntoGraphics(source : IBitmapDrawable, target : Graphics, position : Point = null) : void
{
        position = position == null ? new Point() : position;

        var bounds : Rectangle = DisplayObject(source).getBounds(DisplayObject(source));
        var bitmapData : BitmapData = new BitmapData(bounds.width, bounds.height, true, 0x00000000);

        bitmapData.draw(source, new Matrix(1, 0, 0, 1, -bounds.x, -bounds.y), null, null, null, true);
        target.beginBitmapFill(bitmapData, new Matrix(1, 0, 0, 1, bounds.x + position.x, bounds.y + position.y));
        target.drawRect(bounds.x + position.x, bounds.y + position.y, bounds.width, bounds.height);
}

In addition to your comments... Below the same method using a BitmapData instead of the Graphics object as canvas:

private function drawOntoBitmapData(source : IBitmapDrawable, target : BitmapData, position : Point = null) : void
{
    position = position == null ? new Point() : position;
    var bounds : Rectangle = DisplayObject(source).getBounds(DisplayObject(source));
    var bitmapData : BitmapData = new BitmapData(bounds.width, bounds.height, true, 0x00000000);
    bitmapData.draw(source, new Matrix(1, 0, 0, 1, -bounds.x, -bounds.y), null, null, null, true);
    target.draw(bitmapData, new Matrix(1, 0, 0, 1, bounds.x + position.x, bounds.y + position.y));
}


回答2:

The way i see it you have several ways to approach this:

  • Use the graphics property of a "canvas" Shape
    Pros: Nice and simple
    Cons: Very limited, especially if you need to remove things, since you would have to redraw the whole canvas.

  • Use a Bitmap and the .draw() method
    Pros: Also nice and simple, and you can do duplication of things more easily.
    Cons: Basically has the same problems as the prior approach, deleting anything would need a complete redraw. Also it won't be scalable since it's a bitmap.

  • Use a Sprite and add primitives as children
    Pros: Very flexible.
    Cons: This allows you to move, scale and delete objects individually. However, it will be slightly more complicated than the previous two options.

Using the last approach (which I would recommend if you're going to do anything more than something very small with this) your problem can be solved by using Bitmaps that you add to your "drawing", these support transparency (like you've found out) and are a bit more straightforward than using bitmapFill (which is a bit tricky to get right).

I have an example of using a bitmap to clone and draw other DisplayObjects on my blog. It's not exactly what you want but the code might be of some use, atleast the matrix part for the draw method.



回答3:

bzlm - you could always make the starting X position a constant, and combine drawRect with a Matrix translate.

var xPos:Number = 750;
var matrix:Matrix = new Matrix();
matrix.translate(xPos,0);
mySprite.graphics.beginBitmapFill(myBitmap,matrix);
mySprite.graphics.drawRect(xPos,yPos,imgWidth,imgHeight);