Saturday, February 5, 2011

AS3 Class SmartMask

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;
}

}
}

No comments:

Post a Comment