Dynamic mask using ActionScript

I was asked if is possible to use my inverted mask-class with a dynamic mask. The answer right now is ”not yet”. But it got me thinking and if you ask me again in a few days the answer might be ”of course, it always has”. So I Googled some stuff about dynamic masks and found that almost none suited what I wanted. I wanted the user to be able to double click somewhere inside the swf, add as many anchorpoints they want, be able to move them around and if they so choose, delete them.

Not as much Frankencode as I was affraid it would be but someone might find it useful so I thought I´d share it. The code is commented if you are new to AS3 and want to know what is going on.

And here is a demo of the little thing. Doubleclick inside the swf to add anchorpoints and doubleclick the handles to remove them.

package{
import flash.display.Sprite;
import flash.events.MouseEvent;
import flash.events.Event;

public class Main extends Sprite{

//Setup a some stuff
public var maskBG:Sprite = new Sprite();
public var cord:Array = [];

public function Main():void{

// The image that will appear thru the mask
var lemmyImage:Sprite = new Sprite();
lemmyImage.addChild(new Lemmy());
addChild(lemmyImage);

// An invisible sprite that tracks where the user click
var clickMe:Sprite = new Sprite();
clickMe.graphics.beginFill(0xFF0000,0);
clickMe.graphics.drawRect(0,0,stage.stageWidth,stage.stageHeight);
clickMe.graphics.endFill();
addChild(clickMe);

// Some eventhandlers to see where the user click and to update the mask
clickMe.doubleClickEnabled = true;
clickMe.addEventListener(MouseEvent.DOUBLE_CLICK,addCord);
stage.addEventListener(Event.ENTER_FRAME,updateMask);

addChild(maskBG);

// Set the mask layer to cover the image
lemmyImage.mask = maskBG;

}

private function moveStart(e:MouseEvent):void{
e.currentTarget.startDrag();
e.currentTarget.addEventListener(MouseEvent.MOUSE_MOVE, moveUpdate);
}

private function moveStop(e:MouseEvent):void{
e.currentTarget.stopDrag();
e.currentTarget.removeEventListener(MouseEvent.MOUSE_MOVE, moveUpdate);
}

private function moveUpdate(e:MouseEvent):void{
// Get the location in the array for the handler
var id:int = getHandleId(e.currentTarget.name);

// Update the array with the new values
cord[id] = {id:e.currentTarget.name,x:e.currentTarget.parent.mouseX,y:e.currentTarget.parent.mouseY};
}

private function removeHandle(e:MouseEvent):void{
// Get the location in the array for the handler
var id:int = getHandleId(e.currentTarget.name);

// Remove the part of the array by splicing it.
// Save the splica in a temporaryarray so that it is possible
// to merge the stuff after out deletedpart with the array again.
var a:Array = cord.splice(id,cord.length);
for(var i:int = 1;i<a.length;++i){
cord.push(a[i]);
}

e.currentTarget.parent.removeChild(e.currentTarget);
}

private function updateMask(e:Event):void{
// Clear the masklayer
maskBG.graphics.clear();
maskBG.graphics.lineStyle(2,0xffffff);
maskBG.graphics.beginFill(0xFF0000,1);

// If the value of i is 0 then we want to move to the locatino.
// If we use lineTo it will draw a line from the top left corner.
for(var i:int=0;i<cord.length;++i){
if(i==0){
maskBG.graphics.moveTo(cord[i].x,cord[i].y);
}else{
maskBG.graphics.lineTo(cord[i].x,cord[i].y);
}
}

maskBG.graphics.endFill();
}

private function getHandleId(val:String):int{
// Hack to find the location of the coordinate in the array
for(var i:int=0;i<cord.length;++i){
if(cord[i].id == val){
var id:int = i;
}
}

return id;
}

private function addCord(e:MouseEvent):void{

//Add handle
var handle:Sprite = new Sprite();
handle.graphics.beginFill(0xff0000,1);
handle.graphics.drawRect(0,0,10,10);
handle.graphics.endFill();
handle.x = e.currentTarget.mouseX-5;
handle.y = e.currentTarget.mouseY-5;

// I use the datemethod just cause it easy to get a uniquevalue.
// It's not close to perfect so you should rewrite this part.
// It was late when I did this, sorry  I guess =)
var now:Date = new Date();
handle.name = String(now.getUTCMinutes()+now.getMilliseconds()*Math.random());

// Set up the listeners for the handler
handle.doubleClickEnabled = true;
handle.addEventListener(MouseEvent.MOUSE_DOWN, moveStart);
handle.addEventListener(MouseEvent.MOUSE_DOWN, updateMask);
handle.addEventListener(MouseEvent.MOUSE_UP, moveStop);
handle.addEventListener(MouseEvent.DOUBLE_CLICK, removeHandle);
handle.addEventListener(Event.ADDED_TO_STAGE,updateMask);
addChild(handle);

// Push the coordinate into the array
cord.push({id:handle.name,x:e.currentTarget.mouseX,y:e.currentTarget.mouseY});
}
}
}

Kommentarer


  1. Any chance this is possible to do with bitmapdata instead of graphics class? I’m trying to make a sort of ”additive” mask, where animated objects move around, and as they do they reveal more of the image beneath. If i set the animated objects (or its wrapper parent) as the mask then I don’t see THEM animate anymore and it doesn’t retain previously revealed areas. using bitmapdata.draw(animatedMCparent) works to record ‘history’ but creating a bitmap of it and then setting to mask of the image mc wont actually mask.

  2. Its very much possible. I just posted a new piece of code that might be what you were asking for, http://blog.mattiasnorell.com/2010/04/16/dynamic-as3-mask-using-bitmapdata/ .

*