PageRenderTime 44ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/example/flar/flash-sources/com/flar/source/FLARCameraSource.as

https://github.com/t-ab/x3dom
ActionScript | 467 lines | 265 code | 63 blank | 139 comment | 38 complexity | 31282304c15e04198a4b2ce4b12ed653 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 sampleWidth:Number;
  68. private var sampleHeight:Number;
  69. private var sampleBmpData:BitmapData;
  70. private var sampleBitmap:Bitmap;
  71. private var sampleMatrix:Matrix;
  72. private var downsampleRatio:Number;
  73. /**
  74. * constructor.
  75. */
  76. public function FLARCameraSource () {}
  77. /**
  78. * Initialize this FLARCameraSource.
  79. * @param captureWidth width at which to capture video.
  80. * @param captureHeight height at which to capture video.
  81. * @param fps framerate of camera capture.
  82. * @param mirrored if true, video is flipped horizontally.
  83. * @param displayWidth width at which to display video.
  84. * @param displayHeight height at which to display video.
  85. * @param downsampleRatio amount to downsample camera input.
  86. * The captured video is scaled down by this value
  87. * before being sent to FLARToolkit for analysis.
  88. * FLARToolkit runs faster with more downsampling,
  89. * but also has more difficulty recognizing marker patterns.
  90. * a value of 1.0 results in no downsampling;
  91. * a value of 0.5 (the default) downsamples the camera input by half.
  92. *
  93. * @throws Error if no camera is found.
  94. * (thrown by this.initCamera, called from this method.)
  95. */
  96. public function init (
  97. captureWidth:int=320, captureHeight:int=240,
  98. fps:Number=30, mirrored:Boolean=true,
  99. displayWidth:int=-1, displayHeight:int=-1,
  100. downsampleRatio:Number=0.5) :void {
  101. // NOTE: removed init from ctor and made public to allow instantiation and addition to display list
  102. // while waiting for configuration file to load.
  103. if (displayWidth == -1) {
  104. displayWidth = captureWidth;
  105. }
  106. if (displayHeight == -1) {
  107. displayHeight = captureHeight;
  108. }
  109. this.initCamera(captureWidth, captureHeight, fps);
  110. this.downsampleRatio = downsampleRatio;
  111. // sampleWidth/Height describe size of BitmapData sent to FLARToolkit every frame
  112. this.sampleWidth = captureWidth * this.downsampleRatio;
  113. this.sampleHeight = captureHeight * this.downsampleRatio;
  114. // scale and crop camera source to fit within specified display width/height.
  115. var fitWidthRatio:Number = displayWidth / captureWidth;
  116. var fitHeightRatio:Number = displayHeight / captureHeight;
  117. var videoWidth:Number, videoHeight:Number;
  118. var videoX:Number=0, videoY:Number=0;
  119. if (fitHeightRatio > fitWidthRatio) {
  120. // fit to height, center horizontally, crop left/right edges
  121. videoWidth = fitHeightRatio * captureWidth;
  122. videoHeight = displayHeight;
  123. videoX = -0.5 * (videoWidth - displayWidth);
  124. this._resultsToDisplayRatio = 1 / fitHeightRatio;
  125. this.sampleWidth = this.sampleHeight * (displayWidth/displayHeight);
  126. } else {
  127. // fit to width, center vertically, crop top/bottom edges
  128. videoWidth = displayWidth;
  129. videoHeight = fitWidthRatio * captureHeight;
  130. videoY = -0.5 * (videoHeight - displayHeight);
  131. this._resultsToDisplayRatio = 1 / fitWidthRatio;
  132. this.sampleHeight = this.sampleWidth / (displayWidth/displayHeight);
  133. }
  134. this._resultsToDisplayRatio *= this.downsampleRatio;
  135. // source video
  136. this.video = new Video(videoWidth, videoHeight);
  137. this.video.x = videoX;
  138. this.video.y = videoY;
  139. this.video.attachCamera(this.camera);
  140. // BitmapData downsampled from source video, sent to FLARToolkit every frame
  141. this.sampleBmpData = new BitmapData(this.sampleWidth, this.sampleHeight, false, 0);
  142. this.sampleBitmap = new Bitmap(this.sampleBmpData);
  143. this.sampleBitmap.width = displayWidth;
  144. this.sampleBitmap.height = displayHeight;
  145. // cropped, full-res video displayed on-screen
  146. this.displayBmpData = new BitmapData(displayWidth, displayHeight, false, 0);
  147. this.displayBitmap = new Bitmap(this.displayBmpData);
  148. // cropped, full-res video for display
  149. this.addChild(this.displayBitmap);
  150. // uncomment to view source video
  151. // this.addChild(this.video);
  152. // uncomment to view downsampled video sent to FLARToolkit
  153. // this.addChild(this.sampleBitmap);
  154. // calls buildSampleMatrices
  155. this.mirrored = mirrored;
  156. this._inited = true;
  157. }
  158. /**
  159. * update the BitmapData source.
  160. */
  161. public function update () :void {
  162. this.displayBmpData.draw(this.video, this.displayMatrix);
  163. this.sampleBmpData.draw(this.video, this.sampleMatrix);
  164. }
  165. /**
  166. * validate that the selected Camera is active,
  167. * by checking Camera.activityLevel.
  168. * if camera is not active, FLARCameraSource attempts
  169. * to reinitialize with next available camera.
  170. *
  171. * @param bSuppressReinit if false (default), this method will reinitialize the camera with the next available camera.
  172. * @return true if selected Camera is active (activityLevel != -1).
  173. */
  174. public function validateCamera (bSuppressReinit:Boolean=false) :Boolean {
  175. if (this.camera.activityLevel == -1) {
  176. if (!bSuppressReinit) {
  177. this.initCamera(this.camera.width, this.camera.height, this.camera.fps);
  178. }
  179. return false;
  180. } else {
  181. this.onInitialCameraValidation();
  182. return true;
  183. }
  184. }
  185. /**
  186. * retrieve the BitmapData source used for analysis.
  187. * NOTE: returns the actual BitmapData object, not a clone.
  188. */
  189. public function get source () :BitmapData {
  190. return this.sampleBmpData;
  191. }
  192. /**
  193. * size of BitmapData source used for analysis.
  194. */
  195. public function get sourceSize () :Rectangle {
  196. return new Rectangle(0, 0, this.sampleWidth, this.sampleHeight);
  197. }
  198. /**
  199. * ratio of area of reported results to display size.
  200. * use to scale (multiply) results of FLARToolkit analysis to correctly fit display area.
  201. */
  202. public function get resultsToDisplayRatio () :Number {
  203. return this._resultsToDisplayRatio;
  204. }
  205. /**
  206. * set to true to flip the camera image horizontally.
  207. */
  208. public function get mirrored () :Boolean {
  209. return this._mirrored;
  210. }
  211. public function set mirrored (val:Boolean) :void {
  212. this._mirrored = val;
  213. this.buildSampleMatrices();
  214. }
  215. /**
  216. * if true, FLARCameraSource will use the default camera,
  217. * acquired via a <tt>Camera.getCamera()</tt> with no parameters.
  218. * if false (the default), FLARCameraSource will loop through the camera drivers
  219. * available on the system until it finds one that reports activity.
  220. */
  221. public function get useDefaultCamera () :Boolean {
  222. return this._useDefaultCamera;
  223. }
  224. public function set useDefaultCamera (val:Boolean) :void {
  225. this._useDefaultCamera = val;
  226. if (this.camera) {
  227. this.initCamera(this.camera.width, this.camera.height, this.camera.fps);
  228. }
  229. }
  230. /**
  231. * returns true if initialization is complete.
  232. */
  233. public function get inited () :Boolean {
  234. return this._inited;
  235. }
  236. /**
  237. * halts all processes and frees this instance for garbage collection.
  238. */
  239. public function dispose () :void {
  240. this.camera = null;
  241. this.video.clear();
  242. this.video.attachCamera(null);
  243. this.video = null;
  244. if (this.cameraValidationTimeout) {
  245. this.cameraValidationTimeout.cancel();
  246. }
  247. this.cameraValidationTimeout = null;
  248. this.attemptedCameras = null;
  249. this.displayBmpData.dispose();
  250. this.displayBmpData = null;
  251. this.displayBitmap = null;
  252. this.displayMatrix = null;
  253. this.sampleBmpData.dispose();
  254. this.sampleBmpData = null;
  255. this.sampleBitmap = null;
  256. this.sampleMatrix = null;
  257. }
  258. /**
  259. * the index, in Camera.names, of the active camera.
  260. * if no camera is currently active, returns -1.
  261. *
  262. * setting this value will destroy any active camera,
  263. * and reinitialize with the camera at the specified index.
  264. */
  265. public function get cameraIndex () :int {
  266. if (this.camera) {
  267. return this.camera.index;
  268. } else {
  269. return -1;
  270. }
  271. }
  272. public function set cameraIndex (index:int) :void {
  273. this.manualCameraIndex = index;
  274. if (this.camera) {
  275. this.initCamera(this.camera.width, this.camera.height, this.camera.fps);
  276. }
  277. }
  278. private function initCamera (captureWidth:int, captureHeight:int, fps:int) :void {
  279. if (this.cameraValidationTimeout) {
  280. this.cameraValidationTimeout.cancel();
  281. }
  282. if (this.camera) {
  283. this.destroyCamera();
  284. }
  285. var names:Array = Camera.names;
  286. if (this.useDefaultCamera) {
  287. this.camera = Camera.getCamera();
  288. } else {
  289. // set up Camera to capture source video
  290. if (this.manualCameraIndex >= 0) {
  291. // use camera index specified via cameraIndex accessor (setter)
  292. this.camera = Camera.getCamera(this.manualCameraIndex.toString());
  293. } else {
  294. // attempt to init cameras one-by-one until an active camera is selected,
  295. // or all options are exhausted, starting with the default camera.
  296. if (!this.attemptedCameras || this.attemptedCameras.length != Camera.names.length) {
  297. // if no cameras attempted yet, start with default camera
  298. this.attemptedCameras = new Vector.<Boolean>(Camera.names.length, true);
  299. this.camera = Camera.getCamera();
  300. this.attemptedCameras[this.camera.index] = true;
  301. } else {
  302. // else, loop through available camera drivers that have not yet been attempted
  303. for (var i:int=0; i<this.attemptedCameras.length; i++) {
  304. if (!this.attemptedCameras[i]) {
  305. this.attemptedCameras[i] = true;
  306. this.camera = Camera.getCamera(i.toString());
  307. break;
  308. }
  309. }
  310. }
  311. }
  312. }
  313. if (!this.camera) {
  314. this.dispatchEvent(new ErrorEvent(ErrorEvent.ERROR, true, false,
  315. "Camera not found. Please check your connections and ensure that your camera is not in use by another application."));
  316. return;
  317. }
  318. this.camera.setMode(captureWidth, captureHeight, fps);
  319. this.camera.addEventListener(ActivityEvent.ACTIVITY, this.onCameraActivity);
  320. this.camera.addEventListener(StatusEvent.STATUS, this.onCameraStatus);
  321. trace("[FLARManager] Initing camera '"+ this.camera.name +"'.");
  322. if (this.video) {
  323. // this is not the first attempt to create the camera,
  324. // so clear out the video object and reattach the camera.
  325. this.video.clear();
  326. this.video.attachCamera(this.camera);
  327. }
  328. if (!this.camera.muted) {
  329. // if user has already allowed camera,
  330. // validate the camera after CAMERA_VALIDATION_TIME milliseconds.
  331. this.cameraValidationTimeout = new Timeout(this.validateCamera, CAMERA_VALIDATION_TIME);
  332. }
  333. }
  334. private function onCameraActivity (evt:ActivityEvent) :void {
  335. this.onInitialCameraValidation();
  336. }
  337. private function onCameraStatus (evt:StatusEvent) :void {
  338. this.camera.removeEventListener(StatusEvent.STATUS, this.onCameraStatus);
  339. if (evt.code == "Camera.Muted") {
  340. this.destroyCamera();
  341. this.dispatchEvent(new ErrorEvent(ErrorEvent.ERROR, true, false,
  342. "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."));
  343. } else {
  344. this.cameraValidationTimeout = new Timeout(this.validateCamera, CAMERA_VALIDATION_TIME);
  345. }
  346. }
  347. private function onInitialCameraValidation () :void {
  348. this.camera.removeEventListener(ActivityEvent.ACTIVITY, this.onCameraActivity);
  349. this.camera.removeEventListener(StatusEvent.STATUS, this.onCameraStatus);
  350. this.cameraValidationTimeout.cancel();
  351. trace("[FLARManager] Initial camera validation complete...");
  352. this.cameraValidationBmpData = new BitmapData(this.displayBmpData.width, this.displayBmpData.height);
  353. this.cameraValidationBmpData.draw(this.displayBmpData);
  354. this.cameraValidationTimeout = new Timeout(this.onSecondaryCameraValidation, Math.max(50, CAMERA_VALIDATION_TIME*0.1));
  355. }
  356. /**
  357. * validates a selected camera by verifying a difference between the initial frame and a later frame.
  358. * based on a suggestion and implementation by Jim Alliban (http://www.augmatic.co.uk),
  359. * for properly detecting some non-active bluetooth cameras.
  360. * this method is public only for access by Timeout; it should not be called by developers.
  361. */
  362. public function onSecondaryCameraValidation () :void {
  363. var currentCameraBmpData:BitmapData = new BitmapData(this.displayBmpData.width, this.displayBmpData.height);
  364. currentCameraBmpData.draw(this.displayBmpData);
  365. this.cameraValidationBmpData.draw(currentCameraBmpData, new Matrix(), null, BlendMode.DIFFERENCE);
  366. var difference:uint = this.cameraValidationBmpData.threshold(
  367. this.cameraValidationBmpData,
  368. this.cameraValidationBmpData.rect,
  369. this.cameraValidationBmpData.rect.topLeft,
  370. ">", 0xFF111111, 0xFF00FF00, 0x00FFFFFF);
  371. this.cameraValidationBmpData.dispose();
  372. currentCameraBmpData.dispose();
  373. if (difference < VALID_CAMERA_MIN_FRAME_DIFFERENCE) {
  374. trace("[FLARManager] Secondary camera validation failed for camera '"+ this.camera.name +"'. Reiniting camera.");
  375. this.initCamera(this.camera.width, this.camera.height, this.camera.fps);
  376. } else {
  377. trace("[FLARManager] Validated camera '"+ this.camera.name +"'.");
  378. }
  379. }
  380. private function destroyCamera () :void {
  381. this.camera.removeEventListener(ActivityEvent.ACTIVITY, this.onCameraActivity);
  382. this.camera.removeEventListener(StatusEvent.STATUS, this.onCameraStatus);
  383. this.camera = null;
  384. }
  385. private function buildSampleMatrices () :void {
  386. if (!this.video) { return; }
  387. // construct transformation matrix used when updating displayed video
  388. // and when updating BitmapData source for FLARToolkit
  389. if (this._mirrored) {
  390. this.displayMatrix = new Matrix(-1, 0, 0, 1, this.video.width+this.video.x, this.video.y);
  391. } else {
  392. this.displayMatrix = new Matrix(1, 0, 0, 1, this.video.x, this.video.y);
  393. }
  394. // source does not get mirrored;
  395. // FLARToolkit must be able to recognize non-mirrored patterns.
  396. // transformation mirroring happens in FLARManager.detectMarkers().
  397. this.sampleMatrix = new Matrix(
  398. this._resultsToDisplayRatio, 0,
  399. 0, this._resultsToDisplayRatio,
  400. this._resultsToDisplayRatio*this.video.x,
  401. this._resultsToDisplayRatio*this.video.y);
  402. }
  403. }
  404. }