The SmartMask class is used to create a complex shaped mask.
Define properties maskWidth and maskHeight to draw a rectangle on this object. Then, cut shapes from the rectangle using method addSilhouet(). Undo the cutting by calling removeSilhouet().
These methods require a BitmapData object as parameter. You could use method getSilhouetData() to create that BitmapData object.
mitch/components/SmartMask.as
package mitch.components
{
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.DisplayObject;
import flash.events.Event;
import flash.geom.ColorTransform;
import flash.geom.Point;
import mx.core.UIComponent;
/**
* <p>The SmartMask class is used to create a complex shaped mask.</p>
*
* <p>Define properties <code>maskWidth</code> and <code>maskHeight</code>
* to draw a rectangle on this object. Then, cut shapes from the rectangle
* using method <code>addSilhouet()</code>. Undo the cutting by calling
* <code>removeSilhouet()</code>. These methods require a BitmapData object
* as parameter. You could use method <code>getSilhouetData()</code>
* to create that BitmapData object.</p>
*
* @author Mitch
*
*/
public class SmartMask extends UIComponent
{
/**
* @private
* Results in a color, other than blue.
*/
private static const NOT_BLUE:ColorTransform = new ColorTransform(
1, 0, 0, 1, 255, 0, 0, 255);
/**
* @private
* Represents the color blue;
*/
private static const BLUE:uint = 0x0000FF;
/**
* @private
* The Bitmap object that will display the bitmap data of the mask.
*/
private var _bitmap:Bitmap;
/**
* @private
* All added silhouets.
*/
private var _silhouets:Array;
/**
* The bitmap data that will be used
* in method <code>updateBitmapData()</code>.
*/
protected var data:BitmapData;
/**
* Constructor.
*
*/
public function SmartMask()
{
super();
_silhouets = [];
_bitmap = new Bitmap();
}
//------------------------------
// maskWidth
//------------------------------
/**@private */
private var _maskWidth:Number = 550;
[Bindable("maskWidthChanged")]
/**
* The width of the mask, in pixels.
*/
public function get maskWidth():Number { return _maskWidth; }
/**@private */
public function set maskWidth(value:Number):void {
var oldValue:Number = _maskWidth;
_maskWidth = value;
if(oldValue != value) {
invalidateSmartMask();
dispatchEvent(new Event("maskWidthChanged"));
}
}
//------------------------------
// maskHeight
//------------------------------
/**@private */
private var _maskHeight:Number = 400;
[Bindable("maskHeightChanged")]
/**
* The height of the mask, in pixels.
*/
public function get maskHeight():Number { return _maskHeight; }
/**@private */
public function set maskHeight(value:Number):void {
var oldValue:Number = _maskHeight;
_maskHeight = value;
if(oldValue != value) {
invalidateSmartMask();
dispatchEvent(new Event("maskHeightChanged"));
}
}
//------------------------------
// createChildren()
//------------------------------
/**@inheritDoc */
protected override function createChildren():void {
addChild(_bitmap);
}
//------------------------------
// getSilhouetData()
//------------------------------
/**
* Returns a new BitmapData object that reflects the defined DisplayObject.
* Fully transparent pixels will be maintained.
* Other pixels will change to a color, that is not blue.
* <p>This method can be used in combination with method
* <code>addSilhouet()</code>.</p>
* @param source The DisplayObject to get the silhouet from.
* @return New BitmapData object that reflects the defined DisplayObject.
*
*/
final public function getSilhouetData(source:DisplayObject):BitmapData
{
var result:BitmapData = new BitmapData(
source.x + source.width, source.y + source.height, true, 0x00000000);
result.draw(source, source.transform.matrix, NOT_BLUE);
return result;
}
//------------------------------
// addSilhouet()
//------------------------------
/**
* Adds a silhouet to this SmartMask.
* So that we cannot see through it anymore.
* <p>The BitmapData object at
* parameter <code>silhouet</code> should not contain the color blue.
* Preferably, it'd be a transparent BitmapData object, with an opaque
* silhouet drawn in it.</p>
* @param silhouet The BitmapData object with a silhouet drawn in it.
* @throws ArgumentError Parameter silhouet is undefined.
*/
public function addSilhouet(silhouet:BitmapData):void
{
if(!silhouet) throw new ArgumentError("Parameter silhouet is undefined.");
if(getSilhouetIndex(data) != -1) return;
_silhouets.push(silhouet);
if(!data) updateSmartMask();
drawSilhouet(silhouet);
return;
}
//------------------------------
// removeSilhouet()
//------------------------------
/**
* Removes a silhouet from this SmartMask.
* So that we can see through it again.
*
* @param silhouet The BitmapData object that was
* added using <code>addSilhouet()</code>.
*
*/
public function removeSilhouet(silhouet:BitmapData):void
{
if(!silhouet) return;
var index:int = getSilhouetIndex(silhouet);
if(index == -1) return;
_silhouets.splice(index, 1);
invalidateSmartMask();
}
//------------------------------
// invalidateSmartMask()
//------------------------------
/**
* Marks this smart mask invalid. So, the next frame,
* all silhouets are redrawn. Also, property <code>data</code> will be reset,
* so it's size will reflect properties
* <code>maskWidth</code> and <code>maskHeight</code> again.
*/
public function invalidateSmartMask():void {
addEventListener(Event.ENTER_FRAME, validateSmartMask);
}
//------------------------------
// validateSmartMask()
//------------------------------
/**@private */
private function validateSmartMask(event:Event):void {
removeEventListener(Event.ENTER_FRAME, validateSmartMask);
updateSmartMask();
drawAllSilhouets();
}
//------------------------------
// updateSmartMask()
//------------------------------
/**
* @private
* Resets property <code>data</code> to a new rectangle of which the size
* reflects properties <code>maskWidth</code> and <code>maskHeight</code>.
* <p>The rectangle will be colored blue. So, the silhouets should be a
* different color.</p>
*/
private function updateSmartMask():void
{
data = new BitmapData(maskWidth, maskHeight, false, BLUE);
}
//------------------------------
// drawAllSilhouets()
//------------------------------
/**
* @private
* Calls <code>drawSilhouet()</code> for every
* BitmapData object defined at <code>addSilhouet()</code>.
* @see #drawSilhouet() drawSilhouet()
*/
private function drawAllSilhouets():void
{
var i:int = _silhouets?_silhouets.length:0;
_silhouets.length = 0;
while(i-->0) {
var element:Object = _silhouets[i];
if(element is BitmapData) drawSilhouet(element as BitmapData);
}
}
//------------------------------
// drawSilhouet()
//------------------------------
/**
* @private
* Draws the <code>silhouet</code> on the BitmapData object
* at property <code>data</code>.
* <p>The BitmapData object at
* parameter <code>silhouet</code> should not contain the color blue.
* Preferably, it'd be a transparent BitmapData object, with an opaque
* silhouet drawn in it.</p>
* @param silhouet The silhouet BitmapData to draw
* on property <code>data</code>.
*
*/
private function drawSilhouet(silhouet:BitmapData):void
{
data.draw(silhouet);
invalidateBitmapData()
}
//------------------------------
// invalidateBitmapData()
//------------------------------
/**
* Marks the bitmap data of property <code>bitmap</code> invalid.
* So, this SmartMask object's display is updated the next frame,
* after calling this method.
*
* <p>This method is called internally in method <code>drawSilhouet()</code>.
* So method <code>drawSilhouet()</code> can be called multiple times in one frame,
* but the display will only be updated once.</p>
*/
protected final function invalidateBitmapData():void
{
addEventListener(Event.ENTER_FRAME, validateBitmapData);
}
//------------------------------
// validateBitmapData()
//------------------------------
/**@private */
private function validateBitmapData(event:Event):void
{
removeEventListener(Event.ENTER_FRAME, validateBitmapData);
updateBitmapData(_bitmap);
}
//------------------------------
// updateBitmapData()
//------------------------------
/**
* @private
* <p>Copies the blue pixels from the BitmapData object
* at property <code>data</code>.
* Everything bút the silhouets is colored blue.</p>
*/
private function updateBitmapData(bitmap:Bitmap):void
{
//transparent data
var output:BitmapData = new BitmapData(
maskWidth, maskHeight, true, 0x00000000);
//copy only blue pixels
output.threshold(
data, data.rect, new Point(), "==", 0xFF0000, 0xFF0000, 0xFFFFFF, true);
//update bitmap
bitmap.bitmapData = output
}
//------------------------------
// getSilhouetIndex()
//------------------------------
/**@private */
private function getSilhouetIndex(silhouet:BitmapData):int
{
var i:int = _silhouets?_silhouets.length:0;
while(i-->0) {
if(_silhouets[i] == silhouet) {
return i;
}
}
return -1;
}
}
}