forked from: 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.
Hi! Firstly thanks for this fantastic code- it's just what I've been looking for. I wonder though if anyone could advise, which file/part of the file do I need to alter to change the colour of the design drawn? I would be very grateful if anyone could help me out in this.
Thanks! Breda
// forked from TX_298's Draw worm by Webcam Motion Tracking (ジェスチャーでお絵描き)
// 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 ); }
}
}