PageRenderTime 76ms CodeModel.GetById 0ms RepoModel.GetById 1ms app.codeStats 0ms

/src/com/transmote/flar/source/FLARCameraSource.as

http://github.com/tcha-tcho/EZFLAR
ActionScript | 490 lines | 270 code | 63 blank | 157 comment | 38 complexity | 247458da003b0c2a81f6268b60ec8588 MD5 | raw file
  1. /*
  2. * PROJECT: FLARManager
  3. * http://transmote.com/flar
  4. * Copyright 2009, Eric Socolofsky
  5. * --------------------------------------------------------------------------------
  6. * This work complements FLARToolkit, developed by Saqoosha as part of the Libspark project.
  7. * http://www.libspark.org/wiki/saqoosha/FLARToolKit
  8. * FLARToolkit is Copyright (C)2008 Saqoosha,
  9. * and is ported from NYARToolkit, which is ported from ARToolkit.
  10. *
  11. * This program is free software; you can redistribute it and/or
  12. * modify it under the terms of the GNU General Public License
  13. * as published by the Free Software Foundation; either version 2
  14. * of the License, or (at your option) any later version.
  15. *
  16. * This program is distributed in the hope that it will be useful,
  17. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  19. * GNU General Public License for more details.
  20. *
  21. * You should have received a copy of the GNU General Public License
  22. * along with this framework; if not, write to the Free Software
  23. * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  24. *
  25. * For further information please contact:
  26. * <eric(at)transmote.com>
  27. * http://transmote.com/flar
  28. *
  29. */
  30. package com.transmote.flar.source {
  31. import __AS3__.vec.Vector;
  32. import com.transmote.utils.time.Timeout;
  33. import flash.display.Bitmap;
  34. import flash.display.BitmapData;
  35. import flash.display.BlendMode;
  36. import flash.display.Sprite;
  37. import flash.events.ActivityEvent;
  38. import flash.events.ErrorEvent;
  39. import flash.events.StatusEvent;
  40. import flash.geom.Matrix;
  41. import flash.geom.Rectangle;
  42. import flash.media.Camera;
  43. import flash.media.Video;
  44. /**
  45. * Use the contents of a Camera feed as a source image for FLARToolkit marker detection.
  46. *
  47. * @author Eric Socolofsky
  48. * @url http://transmote.com/flar
  49. * @see com.transmote.flar.FLARManager
  50. */
  51. public class FLARCameraSource extends Sprite implements IFLARSource {
  52. private static const CAMERA_VALIDATION_TIME:Number = 3000;
  53. private static const VALID_CAMERA_MIN_FRAME_DIFFERENCE:uint = 10;
  54. private var _resultsToDisplayRatio:Number;
  55. private var _mirrored:Boolean;
  56. private var _useDefaultCamera:Boolean;
  57. private var _inited:Boolean;
  58. private var camera:Camera;
  59. private var video:Video;
  60. private var manualCameraIndex:int = -1;
  61. private var cameraValidationTimeout:Timeout;
  62. private var attemptedCameras:Vector.<Boolean>;
  63. private var cameraValidationBmpData:BitmapData;
  64. private var displayBmpData:BitmapData;
  65. private var displayBitmap:Bitmap;
  66. private var displayMatrix:Matrix;
  67. private var displayWidth:Number;
  68. private var displayHeight:Number;
  69. private var sampleWidth:Number;
  70. private var sampleHeight:Number;
  71. private var sampleBmpData:BitmapData;
  72. private var sampleBitmap:Bitmap;
  73. private var sampleMatrix:Matrix;
  74. private var downsampleRatio:Number;
  75. /**
  76. * constructor.
  77. */
  78. public function FLARCameraSource () {}
  79. /**
  80. * Initialize this FLARCameraSource.
  81. * @param captureWidth width at which to capture video.
  82. * @param captureHeight height at which to capture video.
  83. * @param fps framerate of camera capture.
  84. * @param mirrored if true, video is flipped horizontally.
  85. * @param displayWidth width at which to display video.
  86. * @param displayHeight height at which to display video.
  87. * @param downsampleRatio amount to downsample camera input.
  88. * The captured video is scaled down by this value
  89. * before being sent to FLARToolkit for analysis.
  90. * FLARToolkit runs faster with more downsampling,
  91. * but also has more difficulty recognizing marker patterns.
  92. * a value of 1.0 results in no downsampling;
  93. * a value of 0.5 (the default) downsamples the camera input by half.
  94. *
  95. * @throws Error if no camera is found.
  96. * (thrown by this.initCamera, called from this method.)
  97. */
  98. public function init (
  99. captureWidth:int=320, captureHeight:int=240,
  100. fps:Number=30, mirrored:Boolean=true,
  101. displayWidth:int=-1, displayHeight:int=-1,
  102. downsampleRatio:Number=0.5) :void {
  103. // NOTE: removed init from ctor and made public to allow instantiation and addition to display list
  104. // while waiting for configuration file to load.
  105. if (displayWidth == -1) {
  106. displayWidth = captureWidth;
  107. }
  108. if (displayHeight == -1) {
  109. displayHeight = captureHeight;
  110. }
  111. this.displayHeight = displayHeight;
  112. this.displayWidth = displayWidth;
  113. this.initCamera(captureWidth, captureHeight, fps);
  114. this.downsampleRatio = downsampleRatio;
  115. // sampleWidth/Height describe size of BitmapData sent to FLARToolkit every frame
  116. this.sampleWidth = captureWidth * this.downsampleRatio;
  117. this.sampleHeight = captureHeight * this.downsampleRatio;
  118. // scale and crop camera source to fit within specified display width/height.
  119. var fitWidthRatio:Number = displayWidth / captureWidth;
  120. var fitHeightRatio:Number = displayHeight / captureHeight;
  121. var videoWidth:Number, videoHeight:Number;
  122. var videoX:Number=0, videoY:Number=0;
  123. if (fitHeightRatio > fitWidthRatio) {
  124. // fit to height, center horizontally, crop left/right edges
  125. videoWidth = fitHeightRatio * captureWidth;
  126. videoHeight = displayHeight;
  127. videoX = -0.5 * (videoWidth - displayWidth);
  128. this._resultsToDisplayRatio = 1 / fitHeightRatio;
  129. this.sampleWidth = this.sampleHeight * (displayWidth/displayHeight);
  130. } else {
  131. // fit to width, center vertically, crop top/bottom edges
  132. videoWidth = displayWidth;
  133. videoHeight = fitWidthRatio * captureHeight;
  134. videoY = -0.5 * (videoHeight - displayHeight);
  135. this._resultsToDisplayRatio = 1 / fitWidthRatio;
  136. this.sampleHeight = this.sampleWidth / (displayWidth/displayHeight);
  137. }
  138. this._resultsToDisplayRatio *= this.downsampleRatio;
  139. // source video
  140. this.video = new Video(videoWidth, videoHeight);
  141. this.video.x = videoX;
  142. this.video.y = videoY;
  143. this.video.attachCamera(this.camera);
  144. /*
  145. // FTK 2.5.1 -- now have to retrieve BitmapData from FTK.
  146. // BitmapData downsampled from source video, sent to FLARToolkit every frame
  147. this.sampleBmpData = new BitmapData(this.sampleWidth, this.sampleHeight, false, 0);
  148. this.sampleBitmap = new Bitmap(this.sampleBmpData);
  149. this.sampleBitmap.width = displayWidth;
  150. this.sampleBitmap.height = displayHeight;
  151. */
  152. // cropped, full-res video displayed on-screen
  153. this.displayBmpData = new BitmapData(displayWidth, displayHeight, false, 0);
  154. this.displayBitmap = new Bitmap(this.displayBmpData);
  155. // cropped, full-res video for display
  156. this.addChild(this.displayBitmap);
  157. // uncomment to view source video
  158. this.addChild(this.video);
  159. // calls buildSampleMatrices
  160. this.mirrored = mirrored;
  161. this._inited = true;
  162. }
  163. /**
  164. * update the BitmapData source.
  165. */
  166. public function update () :void {
  167. this.displayBmpData.draw(this.video, this.displayMatrix);
  168. if (this.sampleBmpData) {
  169. this.sampleBmpData.draw(this.video, this.sampleMatrix);
  170. }
  171. }
  172. /**
  173. * validate that the selected Camera is active,
  174. * by checking Camera.activityLevel.
  175. * if camera is not active, FLARCameraSource attempts
  176. * to reinitialize with next available camera.
  177. *
  178. * @param bSuppressReinit if false (default), this method will reinitialize the camera with the next available camera.
  179. * @return true if selected Camera is active (activityLevel != -1).
  180. */
  181. public function validateCamera (bSuppressReinit:Boolean=false) :Boolean {
  182. if (this.camera.activityLevel == -1) {
  183. if (!bSuppressReinit) {
  184. this.initCamera(this.camera.width, this.camera.height, this.camera.fps);
  185. }
  186. return false;
  187. } else {
  188. this.onInitialCameraValidation();
  189. return true;
  190. }
  191. }
  192. /**
  193. * the BitmapData source used for analysis, constructed by FLARToolkit.
  194. * NOTE: returns the actual BitmapData object, not a clone.
  195. */
  196. public function get source () :BitmapData {
  197. return this.sampleBmpData;
  198. }
  199. public function set source (val:BitmapData) :void {
  200. // BitmapData downsampled from source video, sent to FLARToolkit every frame
  201. this.sampleBmpData = val;
  202. this.sampleBitmap = new Bitmap(this.sampleBmpData);
  203. this.sampleBitmap.width = this.displayWidth;
  204. this.sampleBitmap.height = this.displayHeight;
  205. // uncomment to view downsampled video sent to FLARToolkit
  206. /* this.addChild(this.sampleBitmap);*/
  207. }
  208. /**
  209. * size of BitmapData source used for analysis.
  210. */
  211. public function get sourceSize () :Rectangle {
  212. return new Rectangle(0, 0, this.sampleWidth, this.sampleHeight);
  213. }
  214. /**
  215. * ratio of area of reported results to display size.
  216. * use to scale (multiply) results of FLARToolkit analysis to correctly fit display area.
  217. */
  218. public function get resultsToDisplayRatio () :Number {
  219. return this._resultsToDisplayRatio;
  220. }
  221. /**
  222. * set to true to flip the camera image horizontally.
  223. */
  224. public function get mirrored () :Boolean {
  225. return this._mirrored;
  226. }
  227. public function set mirrored (val:Boolean) :void {
  228. this._mirrored = val;
  229. this.buildSampleMatrices();
  230. }
  231. /**
  232. * if true, FLARCameraSource will use the default camera,
  233. * acquired via a <tt>Camera.getCamera()</tt> with no parameters.
  234. * if false (the default), FLARCameraSource will loop through the camera drivers
  235. * available on the system until it finds one that reports activity.
  236. */
  237. public function get useDefaultCamera () :Boolean {
  238. return this._useDefaultCamera;
  239. }
  240. public function set useDefaultCamera (val:Boolean) :void {
  241. this._useDefaultCamera = val;
  242. if (this.camera) {
  243. this.initCamera(this.camera.width, this.camera.height, this.camera.fps);
  244. }
  245. }
  246. /**
  247. * returns true if initialization is complete.
  248. */
  249. public function get inited () :Boolean {
  250. return this._inited;
  251. }
  252. /**
  253. * halts all processes and frees this instance for garbage collection.
  254. */
  255. public function dispose () :void {
  256. this.camera = null;
  257. this.video.clear();
  258. this.video.attachCamera(null);
  259. this.video = null;
  260. if (this.cameraValidationTimeout) {
  261. this.cameraValidationTimeout.cancel();
  262. }
  263. this.cameraValidationTimeout = null;
  264. this.attemptedCameras = null;
  265. this.displayBmpData.dispose();
  266. this.displayBmpData = null;
  267. this.displayBitmap = null;
  268. this.displayMatrix = null;
  269. if (this.sampleBmpData) {
  270. this.sampleBmpData.dispose();
  271. this.sampleBmpData = null;
  272. }
  273. this.sampleBitmap = null;
  274. this.sampleMatrix = null;
  275. }
  276. /**
  277. * the index, in Camera.names, of the active camera.
  278. * if no camera is currently active, returns -1.
  279. *
  280. * setting this value will destroy any active camera,
  281. * and reinitialize with the camera at the specified index.
  282. */
  283. public function get cameraIndex () :int {
  284. if (this.camera) {
  285. return this.camera.index;
  286. } else {
  287. return -1;
  288. }
  289. }
  290. public function set cameraIndex (index:int) :void {
  291. this.manualCameraIndex = index;
  292. if (this.camera) {
  293. this.initCamera(this.camera.width, this.camera.height, this.camera.fps);
  294. }
  295. }
  296. private function initCamera (captureWidth:int, captureHeight:int, fps:int) :void {
  297. if (this.cameraValidationTimeout) {
  298. this.cameraValidationTimeout.cancel();
  299. }
  300. if (this.camera) {
  301. this.destroyCamera();
  302. }
  303. var names:Array = Camera.names;
  304. if (this.useDefaultCamera) {
  305. this.camera = Camera.getCamera();
  306. } else {
  307. // set up Camera to capture source video
  308. if (this.manualCameraIndex >= 0) {
  309. // use camera index specified via cameraIndex accessor (setter)
  310. this.camera = Camera.getCamera(this.manualCameraIndex.toString());
  311. } else {
  312. // attempt to init cameras one-by-one until an active camera is selected,
  313. // or all options are exhausted, starting with the default camera.
  314. if (!this.attemptedCameras || this.attemptedCameras.length != Camera.names.length) {
  315. // if no cameras attempted yet, start with default camera
  316. this.attemptedCameras = new Vector.<Boolean>(Camera.names.length, true);
  317. this.camera = Camera.getCamera();
  318. this.attemptedCameras[this.camera.index] = true;
  319. } else {
  320. // else, loop through available camera drivers that have not yet been attempted
  321. for (var i:int=0; i<this.attemptedCameras.length; i++) {
  322. if (!this.attemptedCameras[i]) {
  323. this.attemptedCameras[i] = true;
  324. this.camera = Camera.getCamera(i.toString());
  325. break;
  326. }
  327. }
  328. }
  329. }
  330. }
  331. if (!this.camera) {
  332. this.dispatchEvent(new ErrorEvent(ErrorEvent.ERROR, true, false,
  333. "Camera not found. Please check your connections and ensure that your camera is not in use by another application."));
  334. return;
  335. }
  336. this.camera.setMode(captureWidth, captureHeight, fps);
  337. this.camera.addEventListener(ActivityEvent.ACTIVITY, this.onCameraActivity);
  338. this.camera.addEventListener(StatusEvent.STATUS, this.onCameraStatus);
  339. trace("[EZFLAR::FM] Initing camera '"+ this.camera.name +"'.");
  340. if (this.video) {
  341. // this is not the first attempt to create the camera,
  342. // so clear out the video object and reattach the camera.
  343. this.video.clear();
  344. this.video.attachCamera(this.camera);
  345. }
  346. if (!this.camera.muted) {
  347. // if user has already allowed camera,
  348. // validate the camera after CAMERA_VALIDATION_TIME milliseconds.
  349. this.cameraValidationTimeout = new Timeout(this.validateCamera, CAMERA_VALIDATION_TIME);
  350. }
  351. }
  352. private function onCameraActivity (evt:ActivityEvent) :void {
  353. this.onInitialCameraValidation();
  354. }
  355. private function onCameraStatus (evt:StatusEvent) :void {
  356. this.camera.removeEventListener(StatusEvent.STATUS, this.onCameraStatus);
  357. if (evt.code == "Camera.Muted") {
  358. this.destroyCamera();
  359. this.dispatchEvent(new ErrorEvent(ErrorEvent.ERROR, true, false,
  360. "Camera access denied by user. If you wish to view this content, please right/ctrl-click here and click 'settings' to enable your camera, and then refresh this page."));
  361. } else {
  362. this.cameraValidationTimeout = new Timeout(this.validateCamera, CAMERA_VALIDATION_TIME);
  363. }
  364. }
  365. private function onInitialCameraValidation () :void {
  366. this.camera.removeEventListener(ActivityEvent.ACTIVITY, this.onCameraActivity);
  367. this.camera.removeEventListener(StatusEvent.STATUS, this.onCameraStatus);
  368. this.cameraValidationTimeout.cancel();
  369. trace("[EZFLAR::FM] Initial camera validation complete...");
  370. this.cameraValidationBmpData = new BitmapData(this.displayBmpData.width, this.displayBmpData.height);
  371. this.cameraValidationBmpData.draw(this.displayBmpData);
  372. this.cameraValidationTimeout = new Timeout(this.onSecondaryCameraValidation, Math.max(50, CAMERA_VALIDATION_TIME*0.1));
  373. }
  374. /**
  375. * validates a selected camera by verifying a difference between the initial frame and a later frame.
  376. * based on a suggestion and implementation by Jim Alliban (http://www.augmatic.co.uk),
  377. * for properly detecting some non-active bluetooth cameras.
  378. * this method is public only for access by Timeout; it should not be called by developers.
  379. */
  380. public function onSecondaryCameraValidation () :void {
  381. var currentCameraBmpData:BitmapData = new BitmapData(this.displayBmpData.width, this.displayBmpData.height);
  382. currentCameraBmpData.draw(this.displayBmpData);
  383. this.cameraValidationBmpData.draw(currentCameraBmpData, new Matrix(), null, BlendMode.DIFFERENCE);
  384. var difference:uint = this.cameraValidationBmpData.threshold(
  385. this.cameraValidationBmpData,
  386. this.cameraValidationBmpData.rect,
  387. this.cameraValidationBmpData.rect.topLeft,
  388. ">", 0xFF111111, 0xFF00FF00, 0x00FFFFFF);
  389. this.cameraValidationBmpData.dispose();
  390. currentCameraBmpData.dispose();
  391. /*
  392. FIXME crap peace of junk! this will always fail in IDE...
  393. */
  394. /*
  395. if (difference < VALID_CAMERA_MIN_FRAME_DIFFERENCE) {
  396. trace("[EZFLAR::FM] Secondary camera validation failed for camera '"+ this.camera.name +"'. Reiniting camera.");
  397. this.initCamera(this.camera.width, this.camera.height, this.camera.fps);
  398. } else {
  399. trace("[EZFLAR::FM] Validated camera '"+ this.camera.name +"'.");
  400. }
  401. */
  402. }
  403. private function destroyCamera () :void {
  404. this.camera.removeEventListener(ActivityEvent.ACTIVITY, this.onCameraActivity);
  405. this.camera.removeEventListener(StatusEvent.STATUS, this.onCameraStatus);
  406. this.camera = null;
  407. }
  408. private function buildSampleMatrices () :void {
  409. if (!this.video) { return; }
  410. // construct transformation matrix used when updating displayed video
  411. // and when updating BitmapData source for FLARToolkit
  412. if (this._mirrored) {
  413. this.displayMatrix = new Matrix(-1, 0, 0, 1, this.video.width+this.video.x, this.video.y);
  414. } else {
  415. this.displayMatrix = new Matrix(1, 0, 0, 1, this.video.x, this.video.y);
  416. }
  417. // source does not get mirrored;
  418. // FLARToolkit must be able to recognize non-mirrored patterns.
  419. // transformation mirroring happens in FLARManager.detectMarkers().
  420. this.sampleMatrix = new Matrix(
  421. this._resultsToDisplayRatio, 0,
  422. 0, this._resultsToDisplayRatio,
  423. this._resultsToDisplayRatio*this.video.x,
  424. this._resultsToDisplayRatio*this.video.y);
  425. }
  426. }
  427. }