In case Flash no longer exists; a copy of this site is included in the Flashpoint archive's "ultimate" collection.

Dead Code Preservation :: Archived AS3 works from wonderfl.net

Draw worm by Webcam Motion Tracking (ジェスチャーでお絵描き)

Webcam Motion Tracking
http://blog.soulwire.co.uk/flash/actionscript-3/webcam-motion-detection-tracking/

LOVE MATRIX.
a study for drawing curl curve.
license under the GNU Lesser General Public License.
// forked from nutsu's Draw worm by mouse gesture.
// forked from nutsu's Worm matrix based.
/**
Webcam Motion Tracking
http://blog.soulwire.co.uk/flash/actionscript-3/webcam-motion-detection-tracking/

LOVE MATRIX.
a study for drawing curl curve.
license under the GNU Lesser General Public License.
*/
package {
    import frocessing.display.F5MovieClip2D;
    import frocessing.geom.FMatrix2D;
	import frocessing.math.FMath;
	import frocessing.color.ColorHSV;
	import flash.geom.Matrix;
	import flash.display.*;
	import flash.filters.ColorMatrixFilter;
	import flash.media.Camera;
	import flash.media.Video;
	import flash.events.*;
	
    [SWF(width = "465", height = "465", frameRate = "30", backgroundColor = "0xffff")]
    public class WormMatrix extends Sprite {
        private var vms:Array;
        private var MAX_NUM:int = 100; 
        private var N:Number = 80;
        private var px:Number;
        private var py:Number;
        private var oldx:Number;
        private var oldy:Number;
		private var _motionTracker:MotionTracker;
		private var hsv:ColorHSV;
		private var _target:Shape;
		private var _bounds:Shape;
		private var _output:Bitmap;
		private var _source:Bitmap;
		private var _video:BitmapData;
		private var _view:BitmapData;
		private var _matrix:ColourMatrix;
		private var _mtx:Matrix;
		private var cv:Sprite = new Sprite();
		private var cam:Camera;
        public function WormMatrix () {
            super();
            stage.frameRate = 60;
            vms = [];
			hsv = new ColorHSV(0, 1, 1, 1);
			var camW:int = stage.stageWidth;
			var camH:int = stage.stageHeight;
			_mtx = new Matrix();
			_mtx.translate( -camW, 0 ); _mtx.scale( -1, 1 );
			
			// Create the camera
			cam = Camera.getCamera();
			if (cam == null) return;

			cam.setMode( camW, camH, stage.frameRate );
			cam.addEventListener(ActivityEvent.ACTIVITY, activityHandler);
			// Create a video
			var vid:Video = new Video( camW, camH );
			vid.attachCamera( cam );

			// Create the Motion Tracker
			_motionTracker = new MotionTracker( vid );

			// We flip the input as we want a mirror image
			_motionTracker.flipInput = true;
		
			/*** Create a few things to help us visualise what the MotionTracker is doing... ***/
			_matrix = new ColourMatrix();
			
			_motionTracker.blur = 20;
			_motionTracker.brightness = _matrix.brightness = 20;
			_motionTracker.contrast = _matrix.contrast = 150;
			_motionTracker.minArea = 10;
					
			// Display the camera input with the same filters (minus the blur) as the MotionTracker is using
			_video = new BitmapData( camW, camH, false, 0 );
			_view = _video.clone();
			_source = new Bitmap( _video );

			_source.filters = [ new ColorMatrixFilter( _matrix.getMatrix() ) ];
			addChild(new Bitmap(_view) );
			
			// Show the image the MotionTracker is processing and using to track
			_output = new Bitmap( _motionTracker.trackingImage );

			// A shape to represent the tracking point
			_target = new Shape();
			_target.graphics.beginFill(0xffffff, 0.5);
			_target.graphics.drawCircle( 0, 0, 7 );
			_target.graphics.endFill();
			addChild( _target );
			
			// A box to represent the activity area
			_bounds = new Shape();
			_bounds.x = _output.x;
			_bounds.y = _output.y;
			addChild( _bounds );
			addChild( cv );
			_target.x = px = oldx = camW / 2; _target.y = py = oldy = camH / 2;
        }
        public function activityHandler( event:ActivityEvent ):void {
                if( event.activating == true )addEventListener( Event.ENTER_FRAME, track );
        }
		private function track( e:Event ):void
		{
			_view.draw(_video,_mtx);
			cv.graphics.lineStyle();
			
            var len:int = vms.length;
            for( var i:int=0; i<len; i++ )
            {
                var o:WormObject = vms[i];
                if( o.count<N ){
                    drawWorm( o );
                    o.count++;
                }else {
                    len--;
                    vms.splice( i, 1 );
                    i--;
                }
            }

			// Tell the MotionTracker to update itself
			_motionTracker.track();
			
			// Move the target with some easing
			_target.x += ((_motionTracker.x + _bounds.x) - _target.x) / 10;
			_target.y += ((_motionTracker.y + _bounds.y) - _target.y) / 10;

			_video.draw( _motionTracker.input );
			if (_target.x == oldx && _target.y == oldy) { cv.graphics.clear(); return; }
			oldx = _target.x; oldy = _target.y;
			// If there is enough movement (see the MotionTracker's minArea property) then continue
			if ( !_motionTracker.hasMovement ) {   return; }
			
			// Draw the motion bounds so we can see what the MotionTracker is doing
			_bounds.graphics.clear();
			_bounds.graphics.lineStyle( 0, 0xFFFFFF,0.3 );
			_bounds.graphics.drawRect( _motionTracker.motionArea.x,
									   _motionTracker.motionArea.y,
									   _motionTracker.motionArea.width,
									   _motionTracker.motionArea.height
										);
            check();
		}
        public function check():void
        {
            var x0:Number = _target.x;
            var y0:Number = _target.y;
            var vx:Number = x0 - px;
            var vy:Number = y0 - py;
            var len:Number = Math.min( FMath.mag( vx, vy ), 50 );

            if( len<10 ) return;
            hsv.h = Math.random() * 360;
            var mtx:FMatrix2D = new FMatrix2D();
            mtx.rotate( Math.atan2( vy, vx ) );
            mtx.translate( x0, y0 );
            
            createObj( mtx, len );
            cv.graphics.lineStyle(1,hsv.value);

			cv.graphics.moveTo(px, py);cv.graphics.lineTo(x0, y0 );
            px = x0;
            py = y0;
        }
        public function createObj( mtx:FMatrix2D, len:Number ):void
        {
            var angle:Number = FMath.random(Math.PI/64,Math.PI/6);
            if( Math.random()>0.5 ) angle *= -1;
            var tmt:FMatrix2D = new FMatrix2D();
            tmt.scale( 0.95, 0.95 );
            tmt.rotate( angle );
            tmt.translate( len, 0 );
            var w:Number = 0.5;
               
            var obj:WormObject = new WormObject(hsv.value);
            obj.c1x = obj.p1x = -w * mtx.c + mtx.tx;
            obj.c1y = obj.p1y = -w * mtx.d + mtx.ty;
            obj.c2x = obj.p2x =  w * mtx.c + mtx.tx;
            obj.c2y = obj.p2y =  w * mtx.d + mtx.ty;
            obj.vmt = mtx;
            obj.tmt = tmt;
            obj.r   = angle;
            obj.w   = len/20;
            obj.count = 0;
                
            vms.push( obj );
            if( vms.length > MAX_NUM )
                vms.shift();
        }
        
        public function drawWorm( obj:WormObject ):void
        {
            if( Math.random()>0.9 ){
                obj.tmt.rotate( -obj.r*2 );
                obj.r *= -1;
            }
            obj.vmt.prepend( obj.tmt );
            var cc1x:Number = -obj.w*obj.vmt.c + obj.vmt.tx;
            var cc1y:Number = -obj.w*obj.vmt.d + obj.vmt.ty;
            var pp1x:Number = (obj.c1x+cc1x)/2;
            var pp1y:Number = (obj.c1y+cc1y)/2;
            var cc2x:Number = obj.w*obj.vmt.c + obj.vmt.tx;
            var cc2y:Number = obj.w*obj.vmt.d + obj.vmt.ty;
            var pp2x:Number = (obj.c2x+cc2x)/2;
            var pp2y:Number = (obj.c2y+cc2y)/2;
            cv.graphics.beginFill( obj.col );
            cv.graphics.moveTo( obj.p1x, obj.p1y );
            cv.graphics.curveTo( obj.c1x, obj.c1y, pp1x, pp1y );
            cv.graphics.lineTo( pp2x, pp2y );
            cv.graphics.curveTo( obj.c2x, obj.c2y, obj.p2x, obj.p2y );
            cv.graphics.endFill();
            obj.c1x = cc1x;
            obj.c1y = cc1y;
            obj.p1x = pp1x;
            obj.p1y = pp1y;
            obj.c2x = cc2x;
            obj.c2y = cc2y;
            obj.p2x = pp2x;
            obj.p2y = pp2y;
        }
    }
}

import frocessing.geom.FMatrix2D;
class WormObject{
    public var c1x:Number;
    public var c1y:Number;
    public var c2x:Number;
    public var c2y:Number;
    public var p1x:Number;
    public var p1y:Number;
    public var p2x:Number;
    public var p2y:Number;
    public var w:Number;
    public var r:Number;
    public var count:int;
    public var vmt:FMatrix2D;
    public var tmt:FMatrix2D;
	public var col:uint;
    public function WormObject(c:uint){col = c;}
}

class ColourMatrix 
{
	
	/*
	========================================================
	| Private Variables                         | Data Type  
	========================================================
	*/
	
	protected const LUMINANCE_R:				Number = 0.212671;
	protected const LUMINANCE_G:				Number = 0.715160;
	protected const LUMINANCE_B:				Number = 0.072169;
	protected const IDENTITY:					Array  = [1, 0, 0, 0, 0,
														  0, 1, 0, 0, 0,
														  0, 0, 1, 0, 0,
													  	  0, 0, 0, 1, 0];
										  
	protected var _matrix:						Array;
	protected var _hue:							Number;
	protected var _saturation:					Number;
	protected var _brightness:					Number;
	protected var _contrast:					Number;
	protected var _alpha:						Number;
	
	/*
	========================================================
	| Constructor
	========================================================
	*/
	
	public function ColourMatrix( matrix:Array = null ) 
	{
		init(matrix == null ? IDENTITY.concat() : matrix.concat());
	}
	
	/*
	========================================================
	| Private Methods
	========================================================
	*/
	
	protected function init( matrix:Array ):void
	{
		_matrix = matrix;
		setDefaultValues();
	}

	protected function setDefaultValues():void
	{
		_alpha      = 100;
		_brightness = 0;
		_contrast   = 0;
		_hue        = 0;
		_saturation = 0;
	}

	protected function multiply(matrix:Array):void
	{
		var aBuffer:Array = new Array();
		var n:int = 0;
		
		for(var i:int = 0; i < 4; i++)
		{
			for(var j:int = 0; j < 5; j++)
			{
				aBuffer[n + j] = matrix[n] 	   * _matrix[j] 	 + 
								 matrix[n + 1] * _matrix[j + 5]  + 
								 matrix[n + 2] * _matrix[j + 10] + 
								 matrix[n + 3] * _matrix[j + 15] + 
								 (j == 4 ? matrix[n + 4] : 0);
			}
			n += 5;
		}
		_matrix = aBuffer.concat();
	}
	
	/*
	========================================================
	| Public Methods
	========================================================
	*/
	
	public function getMatrix():Array
	{
		return _matrix.concat();
	}
	public function clone():ColourMatrix
	{
		return new ColourMatrix( getMatrix() );
	}
	
	public function reset():void
	{
		init( IDENTITY.concat() );
	}
	
	/*
	========================================================
	| Getters + Setters
	========================================================
	*/
	
	/* ALPHA */
	
	public function get alpha():Number { return _alpha; }
	
	/**
	 * Sets the alpha.
	 * @param	alpha	A value between 0 and 100 (0 being 0% alpha, 100 being 100% alpha).
	 */
	
	public function set alpha( a:Number ):void
	{
		var old:Number = _alpha / 100;
		
		_alpha = a;
		a /= 100;
		a /= old;
		
		var matrix:Array = [1, 0, 0, 0, 0,
							0, 1, 0, 0, 0,
							0, 0, 1, 0, 0,
							0, 0, 0, a, 0];
		
		multiply(matrix);
	}
	
	/* BRIGHTNESS */
	
	public function get brightness():Number { return _brightness; }
	
	/**
	 * Sets the brightness.
	 * @param	brightness		A value between -100 and 100 (0 being 'no changes').
	 */
	
	public function set brightness( b:Number ):void
	{
		var old:Number = _brightness * (255 / 100);
		
		_brightness = b;
		b *= (255 / 100);
		b -= old;
		
		var matrix:Array = [1, 0, 0, 0, b,
							0, 1, 0, 0, b,
							0, 0, 1, 0, b,
							0, 0, 0, 1, 0];
		multiply(matrix);
	}
	
	/* CONTRAST */
	
	public function get contrast():Number { return _contrast; }
	
	/**
	 * Sets the contrast.
	 * @param	contrast	A value between -100 and 100 (0 being 'no changes').
	 */
	
	public function set contrast( c:Number ):void
	{
		var old:Number = _contrast / 100 + 1;
		
		_contrast = c;
		c = c / 100 + 1;
		c /= old;
		
		var matrix:Array = [c, 0, 0, 0, 128 * (1 - c),
							0, c, 0, 0, 128 * (1 - c),
							0, 0, c, 0, 128 * (1 - c),
							0, 0, 0, 1, 0 ];
		multiply(matrix);
	}
	
	/* HUE */
	
	public function get hue():Number { return _hue; }
	
	/**
	 * Sets the hue.
	 * @param	angle	A value between -180 and 180 (0 being 'no changes').
	 */
	
	public function set hue( a:Number ):void
	{
		var old:Number = _hue * (Math.PI / 180);
		
		_hue = a;
		a *= Math.PI / 180;
		a -= old;
		
		var c:Number = Math.cos(a);
		var s:Number = Math.sin(a);
		var matrix:Array = [(LUMINANCE_R + (c * (1 - LUMINANCE_R))) + (s * (-LUMINANCE_R)),       (LUMINANCE_G + (c * (-LUMINANCE_G)))    + (s * (-LUMINANCE_G)), (LUMINANCE_B + (c * (-LUMINANCE_B)))    + (s * (1 - LUMINANCE_B)), 0, 0,
							(LUMINANCE_R + (c * (-LUMINANCE_R)))    + (s * 0.143),                (LUMINANCE_G + (c * (1 - LUMINANCE_G))) + (s * 0.14),           (LUMINANCE_B + (c * (-LUMINANCE_B)))    + (s * -0.283),            0, 0,
							(LUMINANCE_R + (c * (-LUMINANCE_R)))    + (s * (-(1 - LUMINANCE_R))), (LUMINANCE_G + (c * (-LUMINANCE_G)))    + (s * LUMINANCE_G),    (LUMINANCE_B + (c * (1 - LUMINANCE_B))) + (s * LUMINANCE_B),       0, 0,
							0,                                                                    0,                                                              0,                                                                 1, 0];
		multiply(matrix);
	}
	
	/* SATURATION */
	
	public function get saturation():Number { return _saturation; }
	
	/**
	 * Sets the saturation.
	 * @param	saturation		A value between -100 and 100 (0 being 'no changes').
	 */
	
	public function set saturation( s:Number ):void
	{
		var old:Number = (_saturation + 100) / 100;
		
		_saturation = Math.min(100, Math.max(-99.99, s));
		s = (_saturation + 100) / 100;
		s /= old;
		
		var r:Number  = (1 - s) * LUMINANCE_R;
		var g:Number  = (1 - s) * LUMINANCE_G;
		var b:Number  = (1 - s) * LUMINANCE_B;
		var matrix:Array = [r + s, g, b, 0, 0, 
							r, g + s, b, 0, 0,
							r, g, b + s, 0, 0,
							0, 0, 0, 1, 0];
	
		multiply(matrix);
	}
}
	
import flash.display.BitmapData;
import flash.display.BlendMode;
import flash.filters.BlurFilter;
import flash.filters.ColorMatrixFilter;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.media.Video;

class MotionTracker extends Point
{
	
	/*
	========================================================
	| Private Variables                         | Data Type  
	========================================================
	*/
	
	private static const DEFAULT_AREA:			int = 10;
	private static const DEFAULT_BLUR:			int = 20;
	private static const DEFAULT_BRIGHTNESS:	int = 20;
	private static const DEFAULT_CONTRAST:		int = 150;
	
	private var _src:							Video;
	private var _now:							BitmapData;
	private var _old:							BitmapData;
	
	private var _blr:							BlurFilter;
	private var _cmx:							ColourMatrix;
	private var _col:							ColorMatrixFilter;
	private var _box:							Rectangle;
	private var _act:							Boolean;
	private var _mtx:							Matrix;
	private var _min:							Number;
	
	/*
	========================================================
	| Constructor
	========================================================
	*/
	
	/**
	 * The MotionTracker class will track the movement within video data
	 * 
	 * @param	source	A video object which will be used to track motion
	 */
	
	public function MotionTracker( source:Video ) 
	{
		super();
		
		input = source;
		
		_cmx = new ColourMatrix();
		_blr = new BlurFilter();
		
		blur = DEFAULT_BLUR;
		minArea = DEFAULT_AREA;
		contrast = DEFAULT_CONTRAST;
		brightness = DEFAULT_BRIGHTNESS;
	}
	
	/*
	========================================================
	| Public Methods
	========================================================
	*/
	
	/**
	 * Track movement within the source Video object.
	 */
	
	public function track():void
	{
		_now.draw( _src, _mtx );
		_now.draw( _old, null, null, BlendMode.DIFFERENCE );
		
		_now.applyFilter( _now, _now.rect, new Point(), _col );
		_now.applyFilter( _now, _now.rect, new Point(), _blr );
		
		_now.threshold( _now, _now.rect, new Point(), '>', 0xFF333333, 0xFFFFFFFF );
		_old.draw( _src, _mtx );
		
		var area:Rectangle = _now.getColorBoundsRect( 0xFFFFFFFF, 0xFFFFFFFF, true );
		_act = ( area.width >( _src.width / 100) * _min || area.height > (_src.height / 100) * _min );
		
		if ( _act )
		{
			_box = area;
			x = _box.x + (_box.width / 2);
			y = _box.y + (_box.width / 2);
		}
	}
	
	/*
	========================================================
	| Getters + Setters
	========================================================
	*/
	
	/**
	 * The image the MotionTracker is working from
	 */
	public function get trackingImage():BitmapData { return _now; }
	/**
	 * The area of the image the MotionTracker is working from
	 */
	public function get trackingArea():Rectangle { return new Rectangle( _src.x, _src.y, _src.width, _src.height ); }
	/**
	 * Whether or not movement is currently being detected
	 */
	public function get hasMovement():Boolean { return _act; }
	/**
	 * The area in which movement is being detected
	 */
	public function get motionArea():Rectangle { return _box; }
	
	/* INPUT */
	
	/**
	 * The video (usualy created from a Camera) used to track motion
	 */
	public function get input():Video { return _src; }
	public function set input( v:Video ):void
	{
		_src = v;
		if ( _now != null ) { _now.dispose(); _old.dispose(); }
		_now = new BitmapData( v.width, v.height, false, 0 );
		_old = new BitmapData( v.width, v.height, false, 0 );
	}
	
	/* BLUR */
	
	/**
	 * the blur being applied to the input in order to improve accuracy
	 */
	public function get blur():int { return _blr.blurX; }
	public function set blur( n:int ):void { _blr.blurX = _blr.blurY = n; }
	
	/* BRIGHTNESS */
	
	/**
	 * The brightness filter being applied to the input
	 */
	public function get brightness():int { return _cmx.brightness; }
	public function set brightness( n:int ):void
	{
		_cmx.brightness = n;
		_col = new ColorMatrixFilter( _cmx.getMatrix() );
	}
	
	/* CONTRAST */
	
	/**
	 * The contrast filter being applied to the input
	 */
	public function get contrast():int { return _cmx.contrast; }
	public function set contrast( n:int ):void
	{
		_cmx.contrast = n;
		_col = new ColorMatrixFilter( _cmx.getMatrix() );
	}
	
	/* MIN AREA */
	
	
	/**
	 * The minimum area (percent of the input dimensions) of movement to be considered movement
	 */
	public function get minArea():int { return _min; }
	public function set minArea( n:int ):void
	{
		if ( n < 0 ) return;
		_min = n;
	}
	
	/* FLIP INPUT */
	
	/**
	 * Whether or not to flip the input for mirroring
	 */
	public function get flipInput():Boolean { return _mtx.a < 1; }
	public function set flipInput( b:Boolean ):void
	{
		_mtx = new Matrix();
		if (b) { _mtx.translate( -_src.width, 0 ); _mtx.scale( -1, 1 ); }
	}
	
}