PageRenderTime 50ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/projects/ARWeddingInvitation/lib/com/danzen/interfaces/ostrich/OstrichCursor.as

http://thanhtran-sources.googlecode.com/
ActionScript | 411 lines | 209 code | 74 blank | 128 comment | 19 complexity | 951a7e3f1dd804670f1b84b0df531e55 MD5 | raw file
Possible License(s): Apache-2.0, LGPL-3.0
  1. package com.danzen.interfaces.ostrich {
  2. // OSTRICH INTRODUCTION
  3. // Ostrich lets you follow video motion with a cursor
  4. // for instance, you can wave at a Webcam and make a cursor move to where you wave
  5. // this can be used as an interface to control elements of your application
  6. // FEATURES AND CONSIDERATIONS
  7. // you can specify a region in which to look for motion
  8. // you can specify multiple cursors - each with their own region
  9. // the OstrichButton class is provided to trigger over, out and hold events (no click)
  10. // the fastest way to find out if a button is activated is to make it a cursor region
  11. // then use the cursor start and stop to catch activity in that region
  12. // you can make your own clips follow the OstrichCursor
  13. // WORKINGS
  14. // OstrichCursor is optimized for a single person standing in the middle using hands
  15. // a rectangle is put around all motion and then a cursor is positioned as follows:
  16. // the y position of the cursor is set to ten pixels beneath the top of the motion rectangle
  17. // if rectangle is at the left of the camera it takes the left edge of rectangle for cursor x position
  18. // if rectangle is at right it takes the right edge of rectangle for cursor x position
  19. // if rectangle is in the center it takes the center of the rectangle for cursor x position
  20. // if rectangle is anywhere else it uses the proportion to figure cursor x location
  21. // you can adjust this by reworking the class
  22. // http://ostrichflash.wordpress.com - by inventor Dan Zen - http://www.danzen.com
  23. // if you are using Ostrich for commercial purposes, you are welcome to donate to Dan Zen
  24. // donations can be made to agency@danzen.com at http://www.paypal.com
  25. // also be aware that Gesture Tek and perhaps others hold patents in these areas
  26. // INSTALLING CLASSES
  27. // suggested installation:
  28. // create a "classes" folder on your hard drive - for example c:\classes
  29. // add the classes folder to your Flash class path:
  30. // Flash menu choose Edit > Preferences > ActionScript - ActionScript 3 Settings
  31. // then use the + sign for the source path box to add the path to your classes folder
  32. // put the provided com/danzen/ directory with its folders and files in the classes folder
  33. // the readme has more information if you need it
  34. // USING OSTRICH
  35. // please make sure that the following director is in a folder in your class path:
  36. // com/danzen/interfaces/ostrich/
  37. // see the samples for how to use the Ostrich classes
  38. // OstrichCamera - sets up a Web cam for capturing motion
  39. // OstrichCursor - sets up a cursor that follows the motion in OstrichCamera
  40. // OstrichButton - sets up events for an invisible hotspot per OstrichCursor
  41. // OstrichBlobs - puts blobs on any motion
  42. // ******************
  43. // This class should only be called after an OstrichCamera.READY event fires
  44. import flash.display.BitmapData;
  45. import flash.display.MovieClip;
  46. import flash.display.Sprite;
  47. import flash.geom.*;
  48. import flash.filters.ColorMatrixFilter;
  49. import flash.filters.BlurFilter;
  50. import fl.transitions.Tween;
  51. import fl.transitions.easing.None;
  52. import flash.media.Camera;
  53. import flash.media.Video;
  54. import flash.utils.Timer;
  55. import flash.events.*
  56. public class OstrichCursor extends Sprite {
  57. // CONSTRUCTOR
  58. // OstrichCursor(theCam:OstrichCamera, theL:Number=0, theT:Number=0, theR:Number=0, theB:Number=0, theResponse:Number=4):void
  59. // OstrichCursor makes a cursor follow motion in a specified area of an OstrichCamera
  60. // you can then hide the cursor and make another shape follow it (custom cursor)
  61. // and / or you can use the cursor to rollover an OstrichButton and hold to activate
  62. // or you can use the cursor just as any other moving sprite to make a game controller, etc.
  63. //
  64. // PARAMETERS:
  65. // theCam:OstrichCamera - the cam used for motion detection
  66. // theL:Number - the left side x of the region (with respect to the OstrichCam left)
  67. // theT:Number - the top side y of the region (with respect to the OstrichCam top)
  68. // theR:Number - the right side x of the region (with respect to the OstrichCam left)
  69. // theB:Number - the bottom side y of the region (with respect to the OstrichCam top)
  70. // theResponse:Number - from 1-10 default 4. 1 is fast but jumpy - 10 is slow and smooth
  71. // EVENTS
  72. // OstrichCursor.MOTION_START motion has started for a cursor
  73. // OstrichCursor.MOTION_STOP motion has stopped for a cursor
  74. // METHODS (in addition to constructor)
  75. // dispose():void - stops and removes cursor
  76. // PROPERTIES
  77. // cursorNum:Number - read only - the cursor num starting at 0
  78. // cam:OstrichCamera - the cam feed passed to the OstrichCursor object
  79. // x:Number - the x position of the cursor - setting this will do you no good ;-)
  80. // y:Number - the y position of the cursor - setting this will do you no good ;-)
  81. // response:Number - between 1-10 - cursor is checked each followInterval
  82. // but reported every response number of times
  83. // movement between reports is averaged to smoothen the motion
  84. // CONSTANTS
  85. // MOTION_START:String - static constant (OstrichCursor.MOTION_START) for motion start event
  86. // MOTION_STOP:String - static constant (OstrichCursor.MOTION_STOP) for motion stop event
  87. // event constants
  88. public static const MOTION_START:String = "MotionStart";
  89. public static const MOTION_STOP:String = "MotionStop";
  90. // static constants and related
  91. private static var cursorTotal:Number = 0; // keeps track of cursor numbers starting at 0
  92. private var myCursorNum:Number; // used with getter method at botton of class to return cursorNum
  93. // a few initial condition varibles
  94. private var cursorSize:Number = 14; // a square is drawn for the cursor
  95. private var fromTop:Number = 10; // pixels cursor is drawn from top of motion rectangle
  96. private var followInterval:Number = 100; // motion checking interval in ms
  97. // various holder variables and checks
  98. private var myCam:OstrichCamera; // the cam instance
  99. private var myResponse:Number;
  100. private var motionRectangle:Sprite; // a holder for the motion rectangle (hidden)
  101. private var motionCursor:Sprite; // a holder for the cursor clip
  102. private var older:BitmapData; // the old frame of motion
  103. private var newer:BitmapData; // the new frame of motion
  104. private var myMatrix:Matrix; // to handle flipping of the camera
  105. private var rect:Rectangle; // from getColorBoundsRect around motion color between old and new frames
  106. private var motionCheck:Boolean = false; // true when motion over an interval based on followInterval * response
  107. private var timerFollow:Timer; // interval for testing motion based on followInterval
  108. private var timerMoveCursor:Timer; // interval for moving the cursor based on response * followInterval
  109. private var tweenObject:Object = new Object(); // store the tweens on an object so we can delete them
  110. // these are variables used in the calculations
  111. private var cursorSpeed:Number; // the interval the cursor moves based on followInterval * response in ms
  112. private var mL:Number; // m for motionRectangle
  113. private var mR:Number;
  114. private var mT:Number;
  115. private var mB:Number;
  116. private var mW:Number;
  117. private var mM:Number;
  118. private var mX:Number;
  119. private var cL:Number; // c for cursor
  120. private var cR:Number;
  121. private var cT:Number;
  122. private var cB:Number;
  123. private var cW:Number;
  124. private var cH:Number;
  125. private var cR1:Number;
  126. private var cR2:Number;
  127. private var cT1:Number;
  128. private var cT2:Number;
  129. private var moveX:Number;
  130. private var moveY:Number;
  131. private var motionL:Number = 0; // set some initial motion variables for the cursor
  132. private var motionR:Number = 0;
  133. private var motionT:Number = 0;
  134. private var motionB:Number = 0;
  135. private var motionLtotal:Number = 0;
  136. private var motionRtotal:Number = 0;
  137. private var motionTtotal:Number = 0;
  138. private var motionBtotal:Number = 0;
  139. private var motionTally:Number = 0;
  140. public function OstrichCursor(theCam:OstrichCamera, theL:Number=0, theT:Number=0, theR:Number=0, theB:Number=0, theResponse:Number=4) {
  141. myCam = theCam;
  142. // set the rectangle in which the cursor will work
  143. cL = theL;
  144. cT = theT;
  145. cR = (theR != 0) ? theR : theL + myCam.width;
  146. cB = (theB != 0) ? theB : theT + myCam.height;
  147. myResponse = Math.max(theResponse, 1);
  148. myResponse = Math.min(theResponse, 10);
  149. if (theCam.camCheck) { // double check the camera is ready
  150. init();
  151. } else {
  152. trace ("--------------------------------");
  153. trace ("please call the OstrichCursor class");
  154. trace ("after using an OstrichCamera.READY event");
  155. trace ("--------------------------------");
  156. }
  157. }
  158. private function init(): void {
  159. if (cursorTotal == 0) {trace ("hi from OstrichCursor");}
  160. myCursorNum = cursorTotal++; // which means cursorNum starts at 0
  161. // create the sprite that will hold cursor
  162. motionCursor = new Sprite();
  163. addChild(motionCursor);
  164. // create a sprite that will hold the overall motion rectangle that the cursor follows
  165. motionRectangle = new Sprite();
  166. //addChild(motionRectangle);
  167. cursorSpeed = myResponse * followInterval;
  168. // draw a cursor with black outside and grey inside square
  169. drawCursor(motionCursor);
  170. // drawing a camera always draws 100% so scale the rectangle down
  171. cW = (cR - cL) / myCam.xScale; cH = (cB - cT) / myCam.yScale;
  172. // this takes care of flipping the camera
  173. cR1 = cR / myCam.xScale;
  174. cR2 = myCam.camW - cR1;
  175. cT1 = cT / myCam.yScale;
  176. cT2 = cT1;
  177. // here we figure out translations required to capture our rectangle
  178. moveX = cR2;
  179. moveY = cT1;
  180. // whenever we draw to a bitmap with a blend we need a transformation matrix
  181. myMatrix = new Matrix();
  182. myMatrix.translate(-moveX, -moveY);
  183. // prepare two bitmap objects to store the previous and current video frames
  184. older = new BitmapData(cW, cH, false);
  185. newer = new BitmapData(cW, cH, false);
  186. // this interval runs the function follow every followInterval milliseconds
  187. // follow puts a rectangle around motion
  188. timerFollow = new Timer(followInterval);
  189. timerFollow.addEventListener(TimerEvent.TIMER, follow);
  190. timerFollow.start();
  191. // this interval runs moveCursor which puts the cursor on the top
  192. // of the motion rectangle and to the sides as applicable
  193. timerMoveCursor = new Timer(cursorSpeed);
  194. timerMoveCursor.addEventListener(TimerEvent.TIMER, moveCursor);
  195. timerMoveCursor.start();
  196. }
  197. private function follow(c: TimerEvent): void {
  198. // Generally, capture two frames across time and apply a difference filter
  199. // color will only show where there is movement - turn this to one color
  200. // draw a rectangle around the color to create a "motion rectangle"
  201. // Technique learned from Grant Skinner Talk at FITC 2006 & at Interaccess
  202. // We copy the picture from the old frame to a new bitmap
  203. newer.copyPixels(older,older.rect,new Point(0,0));
  204. // We then draw what is currently on the camera over top of the old frame
  205. // As we are specifying using the difference filter, any pixels of the new
  206. // frame that have the same color as the old frame will have a difference of zero
  207. // zero means black and then every where that is not black will be some color
  208. newer.draw(myCam.cam,myMatrix,null,"difference");
  209. // Below we draw the unaffected camera feed to the old frame so that
  210. // when the follow function is called again, we have a copy of the old frame
  211. older.draw(myCam.cam,myMatrix,null);
  212. // We apply the contrast color filter from the OstrichCamera to focus in on our motion
  213. newer.applyFilter(newer,newer.rect,new Point(0,0),myCam.cm);
  214. // We apply the blur filter from the OstrichCamera to smoothen our motion region
  215. newer.applyFilter(newer,newer.rect,new Point(0,0),myCam.bf);
  216. // We apply a threshold to turn all colors above almost black (first number)
  217. // to green (second number) the last number is a mask number (confusing)
  218. // this for some reason will not work unless we set the alpha channel up
  219. // even though we are not caring about alpha in our bitmap declaration
  220. // that is, the threshold would work but then the colorbounds would not
  221. newer.threshold(newer,newer.rect,new Point(0,0),">",0x00110000,0xFF00FF00,0x00FFFFFF);
  222. // Below we get a rectangle that encompasses the color (second number)
  223. // the first number is a mask (confusing because it deals with bitwise operators)
  224. // true means a rectangle around the color - false means a rectangle not around the color
  225. rect = newer.getColorBoundsRect(0x00FFFFFF,0xFF00FF00,true);
  226. // below we keep a running total of rectangle positions and a tally
  227. // this will allow us to average the rectangle to position the cursor
  228. if (rect.width > 0) {
  229. motionL = Math.round(myCam.cam.x - rect.right * myCam.xScale);
  230. motionR = Math.round(myCam.cam.x - (rect.right - rect.width) * myCam.xScale);
  231. motionT = Math.round(myCam.y + rect.top * myCam.yScale);
  232. motionB = Math.round(myCam.y + rect.bottom * myCam.yScale);
  233. motionLtotal += motionL;
  234. motionRtotal += motionR;
  235. motionTtotal += motionT;
  236. motionBtotal += motionB;
  237. motionTally++;
  238. }
  239. }
  240. private function moveCursor(c: TimerEvent): void {
  241. // handle checking for any motion
  242. if (motionTally > 0 && motionCheck == false && myCam.camCheck) {
  243. motionCheck = true;
  244. dispatchEvent(new Event(OstrichCursor.MOTION_START, true));
  245. } else if (motionTally == 0 && motionCheck && myCam.camCheck) {
  246. motionCheck = false;
  247. dispatchEvent(new Event(OstrichCursor.MOTION_STOP, true));
  248. }
  249. // averaging cursor motion
  250. mL = motionLtotal / motionTally - moveX * myCam.xScale;
  251. mR = motionRtotal / motionTally - moveX * myCam.xScale;
  252. mT = motionTtotal / motionTally + moveY * myCam.yScale;
  253. mB = motionBtotal / motionTally + moveY * myCam.yScale;
  254. // draw the motion rectangle
  255. drawRect(motionRectangle);
  256. // get a width and a middle used in the calculation that follows
  257. mW = mR - mL;
  258. //c.mM = c.mR + c.mW / 2 - (myCam._x - myCam._width);
  259. mM = mL + mW / 2 - (myCam.cam.x - myCam.width);
  260. // place cursor to left more as motion rectangle moves left
  261. // place cursor to right more as motion rectangle moves left
  262. // place cursor at the top of the motion rectangle
  263. mX = mL + mM / myCam.width * mW;
  264. motionLtotal = motionRtotal = motionTtotal = motionBtotal = motionTally = 0;
  265. if (mW > 0) {
  266. // tween cursor to next position
  267. delete(tweenObject.cursorTweenX);
  268. tweenObject.cursorTweenX = new Tween(motionCursor, "x", None.easeOut, motionCursor.x, mX, cursorSpeed/1000, true);
  269. delete(tweenObject.cursorTweenY);
  270. tweenObject.cursorTweenY = new Tween(motionCursor, "y", None.easeOut, motionCursor.y, mT + fromTop, cursorSpeed/1000, true);
  271. }
  272. }
  273. private function drawCursor(c: Sprite): void {
  274. c.graphics.moveTo(-cursorSize/2,-cursorSize/2);
  275. c.graphics.lineStyle(1,0x000000);
  276. c.graphics.lineTo(cursorSize/2,-cursorSize/2);
  277. c.graphics.lineTo(cursorSize/2, cursorSize/2);
  278. c.graphics.lineTo(-cursorSize/2, cursorSize/2);
  279. c.graphics.lineTo(-cursorSize/2,-cursorSize/2);
  280. c.graphics.moveTo(-cursorSize/2+1,-cursorSize/2+1);
  281. c.graphics.lineStyle(1,0xCCCCCC);
  282. c.graphics.lineTo(cursorSize/2-1, -cursorSize/2+1);
  283. c.graphics.lineTo(cursorSize/2-1, cursorSize/2-1);
  284. c.graphics.lineTo(-cursorSize/2+1, cursorSize/2-1);
  285. c.graphics.lineTo(-cursorSize/2+1,-cursorSize/2+1);
  286. }
  287. private function drawRect(r: Sprite): void {
  288. // used to draw the overall motion rectangle that the cursor follows
  289. r.graphics.clear();
  290. r.graphics.lineStyle(2,0xcc0000);
  291. r.graphics.moveTo(mL, mT);
  292. r.graphics.lineTo(mR, mT);
  293. r.graphics.lineTo(mR, mB);
  294. r.graphics.lineTo(mL, mB);
  295. r.graphics.lineTo(mL, mT);
  296. }
  297. // when another class follows the x y positions of the OstrichCursor it
  298. // really follows the x y position of the motionCursor sprite within OstrichCursor
  299. protected var theX:Number;
  300. public override function get x():Number {
  301. theX = motionCursor.x;
  302. return this.theX;
  303. }
  304. public override function set x(t:Number):void {
  305. motionCursor.x = t;
  306. }
  307. protected var theY:Number;
  308. public override function get y():Number {
  309. theY = motionCursor.y;
  310. return this.theY;
  311. }
  312. public override function set y(t:Number):void {
  313. motionCursor.y = t;
  314. }
  315. // these getter setter methods prevent the cursorNum from being set
  316. public function get cursorNum(): Number {return myCursorNum;}
  317. public function set cursorNum(t: Number): void {trace ("cursorNum is read only");}
  318. // these getter setter methods prevent the cam from being set
  319. public function get cam(): OstrichCamera {return myCam;}
  320. public function set cam(t: OstrichCamera): void {trace ("cam is read only");}
  321. public function get response():Number {
  322. return myResponse;
  323. }
  324. public function set response(r:Number): void {
  325. myResponse = Math.max(Math.min(10,r),1);
  326. cursorSpeed = myResponse * followInterval;
  327. timerMoveCursor.delay = cursorSpeed;
  328. }
  329. public function dispose(): void {
  330. if (timerFollow) {timerFollow.stop();}
  331. if (timerMoveCursor) {timerMoveCursor.stop();}
  332. delete(tweenObject.cursorTweenX);
  333. delete(tweenObject.cursorTweenY);
  334. removeChild(motionCursor);
  335. }
  336. }
  337. }