AS 3.0 Cloning/Concatenating Graphics

After reading this post, this morning, whilst everyone was out in the sun, I decided I would set myself a little task this to enable users to clone and concatenate graphics drawn within a Graphics instance in AS 3.0.

You can still draw directly in any Graphics instance, but any drawing done directly on a graphics instance will not be cloned. If you draw directly in a Graphics instance, you can still use CloneableGraphics.concat() on the CloneableGraphics instance and it will concatenate the graphics drawn directly in the Graphics instance with any returned from the CloneableGraphics instance.

CloneableGraphics is the main class that enables the functionality. You have to create an instance of this class in your Sprite, MovieClip etc. and pass it the instance of the Graphics class that is created by default. You then need to invoked all the drawing methods on the instance of this class instead of the graphics instance.
[as]package tink.graphics
{

import flash.display.Graphics;
import flash.display.BitmapData;
import flash.geom.Matrix;

public class CloneableGraphics
{

private var _graphics:Graphics;
private var _clone:Array;

/**
* Constructor.
*/
public function CloneableGraphics( graphics:Graphics = null ):void
{
if( graphics ) _graphics = graphics;

initialize();
}

/**
* Initializes the class by creating an Array to store all the drawing methods that take place.
*/
private function initialize():void
{
_clone = new Array();
}

/**
* Creates a CloneableGraphicsVO instance.
* This object stores the method name as a String and the arguments as an Array.
*
* Each instance is then push into a Array.
*/
private function createStep( method:String, arguments:Array ):void
{
var vo:CloneableGraphicsVO = new CloneableGraphicsVO();
vo.method = method;
vo.arguements = arguments;

_clone.push( vo );
}

/**
* This is where the re-drawing takes place.
* The method String is evaluated and then invoked in the switch statement, passing the correct parameters.
*/
private function invokeStep( step:Object ):void
{
var vo:CloneableGraphicsVO = CloneableGraphicsVO( step );
var method:Function = _graphics[ vo.method ];

method.apply( this, vo.arguements );
}

/**
* Concat
*
* Joins the Array of CloneableGraphicsVO instances with another Array of CloneableGraphicsVO instances
* that were created in another CloneableGraphics instance.
*/
public function concat( concat:Array ):void
{
_clone = _clone.concat( concat );

var iterations:int = concat.length;

for( var i:int = 0; i < iterations; i++ )
{
invokeStep( concat[ i ] );
}
}

/**
* Setter for graphics object
*
* Sets the Graphics instances to invoke the drawing methods on.
*/
public function set graphics( graphics:Graphics ):void
{
_graphics = graphics;
}

/**
* Getter/Setters for cloning
*
* Get
* Returns an Array of CloneableGraphicsVO instances created in tthe instance of this class.
*
* Set
* Clears the current Graphics instance and Array storing CloneableGraphicsVO instances by invoking 'clear()'.
* Then invokes 'concat()' passing the Array of CloneableGraphicsVO instance passed to the setter.
*/
public function get clone():Array
{
return _clone;
}
public function set clone( clone:Array ):void
{
clear();

concat( clone );
}

/**
* Graphics methods.
*
* Duplication of all public drawing methods of the Graphics class.
*
* Each invokes the drawing method on the referenced Graphics instance,
* then invokes 'createStep' passing the method (callee) name, and the arguments passed.
*/
public function beginBitmapFill( bitmap:BitmapData, matrix:Matrix = null, repeat:Boolean = true, smooth:Boolean = false ):void
{
_graphics.beginBitmapFill( bitmap, matrix, repeat, smooth );

createStep( "beginBitmapFill", arguments );
}

public function beginFill( color:uint, alpha:Number = 1.0 ):void
{
_graphics.beginFill( color, alpha );

createStep( "beginFill", arguments );
}

public function beginGradientFill( type:String, colors:Array, alphas:Array, ratios:Array, matrix:Matrix = null, spreadMethod:String = "pad", interpolationMethod:String = "rgb", focalPointRatio:Number = 0 ):void
{
_graphics.beginGradientFill(type, colors, alphas, ratios, matrix, spreadMethod, interpolationMethod, focalPointRatio );

createStep( "beginGradientFill", arguments );
}

public function clear():void
{
_graphics.clear();

_clone = new Array();
}

public function curveTo( controlX:Number, controlY:Number, anchorX:Number, anchorY:Number ):void
{
_graphics.curveTo( controlX, controlY, anchorX, anchorY );

createStep( "curveTo", arguments );
}

public function drawCircle( x:Number, y:Number, radius:Number ):void
{
_graphics.drawCircle( x, y, radius );

createStep( "drawCircle", arguments );
}

public function drawEllipse( x:Number, y:Number, width:Number, height:Number ):void
{
_graphics.drawEllipse( x, y, width, height );

createStep( "drawEllipse", arguments );
}

public function drawRect( x:Number, y:Number, width:Number, height:Number ):void
{
_graphics.drawRect( x, y, width, height );

createStep( "drawRect", arguments );
}

public function drawRoundRect( x:Number, y:Number, width:Number, height:Number, ellipseWidth:Number, ellipseHeight:Number ):void
{
_graphics.drawRoundRect( x, y, width, height, ellipseWidth, ellipseHeight );

createStep( "drawRoundRect", arguments );
}

public function endFill():void
{
_graphics.endFill();

createStep( "endFill", arguments );
}

public function lineGradientStyle( type:String, colors:Array, alphas:Array, ratios:Array, matrix:Matrix = null, spreadMethod:String = "pad", interpolationMethod:String = "rgb", focalPointRatio:Number = 0 ):void
{
_graphics.lineGradientStyle( type , colors, alphas, ratios, matrix, spreadMethod, interpolationMethod, focalPointRatio );

createStep( "lineGradientStyle", arguments );
}

public function lineStyle( thickness:Number, color:uint = 0, alpha:Number = 1.0, pixelHinting:Boolean = false, scaleMode:String = "normal", caps:String = null, joints:String = null, miterLimit:Number = 3 ):void
{
_graphics.lineStyle( thickness, color, alpha, pixelHinting, scaleMode, caps, joints, miterLimit );

createStep( "lineStyle", arguments );
}

public function lineTo(x:Number, y:Number):void
{
_graphics.lineTo( x, y );

createStep( "lineTo", arguments );
}

public function moveTo(x:Number, y:Number):void
{
_graphics.moveTo( x, y );

createStep( "moveTo", arguments );
}

}

}[/as]

CloneableGraphicsVO is a helper class. It is just used inside the CloneableGraphics class to keep things strict and tidy.
[as]package tink.graphics
{
public class CloneableGraphicsVO
{

public var method:String;
public var arguements:Array;

public function CloneableGraphicsVO():void
{
initialize();
}

private function initialize():void
{
arguements = new Array();
}

}
}[/as]

Heres an example of how you would use CloneableGraphics inside a Sprite.
[as]package
{
import flash.display.Sprite;

import tink.graphics.CloneableGraphics;

public class CloneGraphicsExample extends Sprite
{

public var cloneableGraphics:CloneableGraphics;

public function CloneGraphicsExample()
{
super();

initialize();
}

private function initialize():void
{
cloneableGraphics = new CloneableGraphics( graphics );
}
}
}[/as]

And heres the AS project that i tested the functionality in. The first instance created is the instance who's graphics instance is draw in and used to clone/concatenate.

The second instance shows you the clone taking place by creating a new CloneGraphicsExample and using the CloneableGraphics.clone() method to draw inside it.

The third instance shows you the cancatenating taking place. A new CloneGraphicsExample is created and draw inside. The CloneGraphicsExample.concat() method is then used to concatenate the graphics drawn and graphics from the first instance together.
[as]package
{

import flash.display.Sprite;

public class CloneGraphicsExampleProject extends Sprite
{

private var cloned : CloneGraphicsExample;
private var clone : CloneGraphicsExample;
private var concat : CloneGraphicsExample;

public function CloneGraphicsExampleProject()
{
super();

initialize();
}

private function initialize():void
{
cloned = new CloneGraphicsExample();
cloned.y = 0;
cloned.cloneableGraphics.beginFill( 0xFF0000, 1 );
cloned.cloneableGraphics.drawCircle( 25, 25, 25 );
cloned.cloneableGraphics.endFill();
addChild( cloned );

clone = new CloneGraphicsExample();
clone.y = 75;
clone.cloneableGraphics.clone = cloned.cloneableGraphics.clone;
addChild( clone );

concat = new CloneGraphicsExample();
concat.y = 150;
concat.cloneableGraphics.beginFill( 0x0000FF, 1 );
concat.cloneableGraphics.drawCircle( 50, 25, 25 );
concat.cloneableGraphics.endFill();
concat.cloneableGraphics.concat( cloned.cloneableGraphics.clone );
addChild( concat );
}

}
}[/as]
Download example AS 3.0 project.

10 Responses to “AS 3.0 Cloning/Concatenating Graphics”

  1. Hey mate, quite a nice solution. I think you can cut down the amount of code there quite a bit too. Using Function.apply() in the invokeStep() method will get rid of that huge switch() statement. Also, can you not use the flash.util.Proxy class to remove the need to re-define all of the drawing methods?

  2. Tink says:

    Someone else mentioned Function.apply. I was looking for somat like this!!!

    I’ve updated the code.

  3. Nice one :) Any chance you can offer a zip download, when you hit plaintext the text it shows is triple line spaced and doesn’t respect the tabbing you put in. Until they give us source-format in FlexBuilder that is!

  4. Tink says:

    I’ve added a ZIP.

  5. What do you use to display your code in WordPress?

  6. Reistlein says:

    Your algorithm is very great !!! , nice solution i learn many thing in action script 3 with your work.
    Thanks

  7. Just wanted to chime in and say I used this on a project today and it saved me a BOAT load of time. Thanks alot for sharing.

  8. Tink says:

    Glad it was useful

Leave a Reply