/* * PROJECT: FLARManager * http://transmote.com/flar * Copyright 2009, Eric Socolofsky * -------------------------------------------------------------------------------- * This work complements FLARToolkit, developed by Saqoosha as part of the Libspark project. * http://www.libspark.org/wiki/saqoosha/FLARToolKit * FLARToolkit is Copyright (C)2008 Saqoosha, * and is ported from NYARToolkit, which is ported from ARToolkit. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this framework; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * For further information please contact: * * http://transmote.com/flar * */ package com.transmote.flar.source { import __AS3__.vec.Vector; import com.transmote.utils.time.Timeout; import flash.display.Bitmap; import flash.display.BitmapData; import flash.display.BlendMode; import flash.display.Sprite; import flash.events.ActivityEvent; import flash.events.ErrorEvent; import flash.events.StatusEvent; import flash.geom.Matrix; import flash.geom.Rectangle; import flash.media.Camera; import flash.media.Video; /** * Use the contents of a Camera feed as a source image for FLARToolkit marker detection. * * @author Eric Socolofsky * @url http://transmote.com/flar * @see com.transmote.flar.FLARManager */ public class FLARCameraSource extends Sprite implements IFLARSource { private static const CAMERA_VALIDATION_TIME:Number = 3000; private static const VALID_CAMERA_MIN_FRAME_DIFFERENCE:uint = 10; private var _resultsToDisplayRatio:Number; private var _mirrored:Boolean; private var _useDefaultCamera:Boolean; private var _inited:Boolean; private var camera:Camera; private var video:Video; private var manualCameraIndex:int = -1; private var cameraValidationTimeout:Timeout; private var attemptedCameras:Vector.; private var cameraValidationBmpData:BitmapData; private var displayBmpData:BitmapData; private var displayBitmap:Bitmap; private var displayMatrix:Matrix; private var displayWidth:Number; private var displayHeight:Number; private var sampleWidth:Number; private var sampleHeight:Number; private var sampleBmpData:BitmapData; private var sampleBitmap:Bitmap; private var sampleMatrix:Matrix; private var downsampleRatio:Number; /** * constructor. */ public function FLARCameraSource () {} /** * Initialize this FLARCameraSource. * @param captureWidth width at which to capture video. * @param captureHeight height at which to capture video. * @param fps framerate of camera capture. * @param mirrored if true, video is flipped horizontally. * @param displayWidth width at which to display video. * @param displayHeight height at which to display video. * @param downsampleRatio amount to downsample camera input. * The captured video is scaled down by this value * before being sent to FLARToolkit for analysis. * FLARToolkit runs faster with more downsampling, * but also has more difficulty recognizing marker patterns. * a value of 1.0 results in no downsampling; * a value of 0.5 (the default) downsamples the camera input by half. * * @throws Error if no camera is found. * (thrown by this.initCamera, called from this method.) */ public function init ( captureWidth:int=320, captureHeight:int=240, fps:Number=30, mirrored:Boolean=true, displayWidth:int=-1, displayHeight:int=-1, downsampleRatio:Number=0.5) :void { // NOTE: removed init from ctor and made public to allow instantiation and addition to display list // while waiting for configuration file to load. if (displayWidth == -1) { displayWidth = captureWidth; } if (displayHeight == -1) { displayHeight = captureHeight; } this.displayHeight = displayHeight; this.displayWidth = displayWidth; this.initCamera(captureWidth, captureHeight, fps); this.downsampleRatio = downsampleRatio; // sampleWidth/Height describe size of BitmapData sent to FLARToolkit every frame this.sampleWidth = captureWidth * this.downsampleRatio; this.sampleHeight = captureHeight * this.downsampleRatio; // scale and crop camera source to fit within specified display width/height. var fitWidthRatio:Number = displayWidth / captureWidth; var fitHeightRatio:Number = displayHeight / captureHeight; var videoWidth:Number, videoHeight:Number; var videoX:Number=0, videoY:Number=0; if (fitHeightRatio > fitWidthRatio) { // fit to height, center horizontally, crop left/right edges videoWidth = fitHeightRatio * captureWidth; videoHeight = displayHeight; videoX = -0.5 * (videoWidth - displayWidth); this._resultsToDisplayRatio = 1 / fitHeightRatio; this.sampleWidth = this.sampleHeight * (displayWidth/displayHeight); } else { // fit to width, center vertically, crop top/bottom edges videoWidth = displayWidth; videoHeight = fitWidthRatio * captureHeight; videoY = -0.5 * (videoHeight - displayHeight); this._resultsToDisplayRatio = 1 / fitWidthRatio; this.sampleHeight = this.sampleWidth / (displayWidth/displayHeight); } this._resultsToDisplayRatio *= this.downsampleRatio; // source video this.video = new Video(videoWidth, videoHeight); this.video.x = videoX; this.video.y = videoY; this.video.attachCamera(this.camera); /* // FTK 2.5.1 -- now have to retrieve BitmapData from FTK. // BitmapData downsampled from source video, sent to FLARToolkit every frame this.sampleBmpData = new BitmapData(this.sampleWidth, this.sampleHeight, false, 0); this.sampleBitmap = new Bitmap(this.sampleBmpData); this.sampleBitmap.width = displayWidth; this.sampleBitmap.height = displayHeight; */ // cropped, full-res video displayed on-screen this.displayBmpData = new BitmapData(displayWidth, displayHeight, false, 0); this.displayBitmap = new Bitmap(this.displayBmpData); // cropped, full-res video for display this.addChild(this.displayBitmap); // uncomment to view source video // this.addChild(this.video); // calls buildSampleMatrices this.mirrored = mirrored; this._inited = true; } /** * update the BitmapData source. */ public function update () :void { this.displayBmpData.draw(this.video, this.displayMatrix); if (this.sampleBmpData) { this.sampleBmpData.draw(this.video, this.sampleMatrix); } } /** * validate that the selected Camera is active, * by checking Camera.activityLevel. * if camera is not active, FLARCameraSource attempts * to reinitialize with next available camera. * * @param bSuppressReinit if false (default), this method will reinitialize the camera with the next available camera. * @return true if selected Camera is active (activityLevel != -1). */ public function validateCamera (bSuppressReinit:Boolean=false) :Boolean { if (this.camera.activityLevel == -1) { if (!bSuppressReinit) { this.initCamera(this.camera.width, this.camera.height, this.camera.fps); } return false; } else { this.onInitialCameraValidation(); return true; } } /** * the BitmapData source used for analysis, constructed by FLARToolkit. * NOTE: returns the actual BitmapData object, not a clone. */ public function get source () :BitmapData { return this.sampleBmpData; } public function set source (val:BitmapData) :void { // BitmapData downsampled from source video, sent to FLARToolkit every frame this.sampleBmpData = val; this.sampleBitmap = new Bitmap(this.sampleBmpData); this.sampleBitmap.width = this.displayWidth; this.sampleBitmap.height = this.displayHeight; // uncomment to view downsampled video sent to FLARToolkit // this.addChild(this.sampleBitmap); } /** * size of BitmapData source used for analysis. */ public function get sourceSize () :Rectangle { return new Rectangle(0, 0, this.sampleWidth, this.sampleHeight); } /** * ratio of area of reported results to display size. * use to scale (multiply) results of FLARToolkit analysis to correctly fit display area. */ public function get resultsToDisplayRatio () :Number { return this._resultsToDisplayRatio; } /** * set to true to flip the camera image horizontally. */ public function get mirrored () :Boolean { return this._mirrored; } public function set mirrored (val:Boolean) :void { this._mirrored = val; this.buildSampleMatrices(); } /** * if true, FLARCameraSource will use the default camera, * acquired via a Camera.getCamera() with no parameters. * if false (the default), FLARCameraSource will loop through the camera drivers * available on the system until it finds one that reports activity. */ public function get useDefaultCamera () :Boolean { return this._useDefaultCamera; } public function set useDefaultCamera (val:Boolean) :void { this._useDefaultCamera = val; if (this.camera) { this.initCamera(this.camera.width, this.camera.height, this.camera.fps); } } /** * returns true if initialization is complete. */ public function get inited () :Boolean { return this._inited; } /** * halts all processes and frees this instance for garbage collection. */ public function dispose () :void { this.camera = null; this.video.clear(); this.video.attachCamera(null); this.video = null; if (this.cameraValidationTimeout) { this.cameraValidationTimeout.cancel(); } this.cameraValidationTimeout = null; this.attemptedCameras = null; this.displayBmpData.dispose(); this.displayBmpData = null; this.displayBitmap = null; this.displayMatrix = null; if (this.sampleBmpData) { this.sampleBmpData.dispose(); this.sampleBmpData = null; } this.sampleBitmap = null; this.sampleMatrix = null; } /** * the index, in Camera.names, of the active camera. * if no camera is currently active, returns -1. * * setting this value will destroy any active camera, * and reinitialize with the camera at the specified index. */ public function get cameraIndex () :int { if (this.camera) { return this.camera.index; } else { return -1; } } public function set cameraIndex (index:int) :void { this.manualCameraIndex = index; if (this.camera) { this.initCamera(this.camera.width, this.camera.height, this.camera.fps); } } private function initCamera (captureWidth:int, captureHeight:int, fps:int) :void { if (this.cameraValidationTimeout) { this.cameraValidationTimeout.cancel(); } if (this.camera) { this.destroyCamera(); } var names:Array = Camera.names; if (this.useDefaultCamera) { this.camera = Camera.getCamera(); } else { // set up Camera to capture source video if (this.manualCameraIndex >= 0) { // use camera index specified via cameraIndex accessor (setter) this.camera = Camera.getCamera(this.manualCameraIndex.toString()); } else { // attempt to init cameras one-by-one until an active camera is selected, // or all options are exhausted, starting with the default camera. if (!this.attemptedCameras || this.attemptedCameras.length != Camera.names.length) { // if no cameras attempted yet, start with default camera this.attemptedCameras = new Vector.(Camera.names.length, true); this.camera = Camera.getCamera(); this.attemptedCameras[this.camera.index] = true; } else { // else, loop through available camera drivers that have not yet been attempted for (var i:int=0; i", 0xFF111111, 0xFF00FF00, 0x00FFFFFF); this.cameraValidationBmpData.dispose(); currentCameraBmpData.dispose(); if (difference < VALID_CAMERA_MIN_FRAME_DIFFERENCE) { trace("[FLARManager] Secondary camera validation failed for camera '"+ this.camera.name +"'. Reiniting camera."); this.initCamera(this.camera.width, this.camera.height, this.camera.fps); } else { trace("[FLARManager] Validated camera '"+ this.camera.name +"'."); } } private function destroyCamera () :void { this.camera.removeEventListener(ActivityEvent.ACTIVITY, this.onCameraActivity); this.camera.removeEventListener(StatusEvent.STATUS, this.onCameraStatus); this.camera = null; } private function buildSampleMatrices () :void { if (!this.video) { return; } // construct transformation matrix used when updating displayed video // and when updating BitmapData source for FLARToolkit if (this._mirrored) { this.displayMatrix = new Matrix(-1, 0, 0, 1, this.video.width+this.video.x, this.video.y); } else { this.displayMatrix = new Matrix(1, 0, 0, 1, this.video.x, this.video.y); } // source does not get mirrored; // FLARToolkit must be able to recognize non-mirrored patterns. // transformation mirroring happens in FLARManager.detectMarkers(). this.sampleMatrix = new Matrix( this._resultsToDisplayRatio, 0, 0, this._resultsToDisplayRatio, this._resultsToDisplayRatio*this.video.x, this._resultsToDisplayRatio*this.video.y); } } }