/* * LiveSlides.as - Slides by LiveChromaKey and LivePointer * Copyright (c) 2009 Yusuke Kawasaki http://www.kawa.net/ * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * */ package { import flash.display.Bitmap; import flash.display.BitmapData; import flash.display.Loader; import flash.display.LoaderInfo; import flash.display.PixelSnapping; import flash.display.Sprite; import flash.display.StageQuality; import flash.events.Event; import flash.events.IOErrorEvent; import flash.events.KeyboardEvent; import flash.events.MouseEvent; import flash.events.TimerEvent; import flash.geom.Rectangle; import flash.geom.Matrix; import flash.geom.Point; import flash.net.URLRequest; import flash.text.TextField; import flash.text.TextFormat; import flash.text.TextFormatAlign; import flash.utils.Timer; import flash.ui.Keyboard; import org.libspark.LivePointers.LP_Cluster; import org.libspark.LivePointers.LP_Detector; import org.libspark.LivePointers.LP_Guide; import org.libspark.LiveChromaKey.LCK_Core; public class LiveSlides extends Sprite { static private var filePrefix:String = 'slides/slides ('; static private var fileSuffix:String = ').jpg'; private var chromakey:LCK_Core; private var spFore:Sprite; private var spBack:Sprite; private var spLive:Sprite; private var zoomBack:Number = 0.0; private var zoomFore:Number = 0.0; private var horiFore:Number = 0.0; private var displayX:int = 640; private var displayY:int = 480; private var workX:int = 80; private var workY:int = 60; private var message:TextField; private var cntReady:int = 0; private var spGray:Sprite; private var lpdetector:LP_Detector; private var spPointer:Sprite; private var guides:Array; // of LP_Guide private var images:Array; private var loadno:int = 1; private var showno:int = 0; private var spImage:Sprite; private var pointerInterval:Number = 2000; private var slideVisible:Boolean = true; private var guidesH:Object = { greenball: 0x39A243, pinkball: 0xF70A7F, fingercap: 0x2A7777 }; public function LiveSlides ():void { // Image Layer spImage = new Sprite(); images = new Array(); this.loadNextImage(); // Caption Layer spGray = new Sprite(); spGray.graphics.beginFill( 0x000000 ); spGray.graphics.drawRect(0, displayY-20, displayX, 20); spGray.graphics.endFill(); spGray.alpha = 0.25; this.addChild( spGray ); // Message Layer var fmt:TextFormat = new TextFormat(); fmt.font = 'Comic Sans MS'; fmt.size = 16; fmt.color = 0xFFFFFF; fmt.bold = true; fmt.align = TextFormatAlign.CENTER; message = new TextField(); message.defaultTextFormat = fmt; message.multiline = true; message.visible = true; message.text = 'CLICK TO START'; message.x = 0; message.y = displayY - 24; message.width = displayX; this.addChild( message ); // Focus stage stage.addEventListener( MouseEvent.CLICK, this.start ); } private function loadNextImage ():void { var file:String = filePrefix + loadno + fileSuffix; var req:URLRequest = new URLRequest(file); var loader:Loader = new Loader(); images.push( loader ); loader.load(req); var info:LoaderInfo = loader.contentLoaderInfo; info.addEventListener(Event.COMPLETE, this.onLoadComplete); info.addEventListener(IOErrorEvent.IO_ERROR, this.onLoadFinished); loadno ++; } private function onLoadComplete (e:Event):void { var loader:Loader = images[images.length - 1]; var info:LoaderInfo = loader.contentLoaderInfo; info.removeEventListener(Event.COMPLETE, this.onLoadComplete); info.removeEventListener(IOErrorEvent.IO_ERROR, this.onLoadFinished); this.loadNextImage(); } private function onLoadFinished (e:IOErrorEvent):void { var loader:Loader = images.pop(); var info:LoaderInfo = loader.contentLoaderInfo; info.removeEventListener(Event.COMPLETE, this.onLoadComplete); info.removeEventListener(IOErrorEvent.IO_ERROR, this.onLoadFinished); } private function start(e:MouseEvent):void { stage.removeEventListener( MouseEvent.CLICK, this.start ); // LiveChromaKey chromakey = new LCK_Core(); chromakey.displayX = displayX; chromakey.displayY = displayY; chromakey.workX = workX; chromakey.workY = workY; chromakey.smoothing = true; chromakey.init(); spBack = chromakey.getBackground(); spFore = chromakey.getForeground(); spLive = chromakey.getLive(); spLive.visible = false; // LivePointers lpdetector = new LP_Detector(workX, workY); lpdetector.minVolume = 8; spPointer = lpdetector.getPreview(displayX,displayY); // Pointer guides guides = new Array(); for ( var name:String in guidesH ) { this.togglePointerGuide(name); } // Layers this.addChildAt(spLive, 0); this.addChildAt(spBack, 1); this.addChildAt(spImage, 2); this.addChildAt(spFore, 3); spFore.addChild(spPointer); // First slide this.showImage(0); stage.addEventListener( MouseEvent.MOUSE_WHEEL, this.onMouseWheel ); stage.addEventListener( KeyboardEvent.KEY_DOWN, this.onKeyDown ); stage.addEventListener( Event.ENTER_FRAME, this.onEnterFrame ); } private var guideToggle:Object = { }; private function togglePointerGuide (name:String):void { if ( ! guidesH[name] ) return; if ( guideToggle[name] ) { lpdetector.removeGuideByName(name); guideToggle[name] = false; } else { var gi:LP_Guide = new LP_Guide( name ); var color:uint = guidesH[name]; gi.fromRGB( color ); gi.max = 2; guides.push( gi ); lpdetector.addGuide(gi); guideToggle[name] = true; } } private function showImage (index:int = 0):void { if ( index < 0 ) return; if ( index > images.length-1 ) return; var image:Loader = images[index]; if ( ! image ) return; showno = index; while ( spImage.numChildren > 0 ) { spImage.removeChildAt( spImage.numChildren-1 ); } var bmdImage:BitmapData = new BitmapData(image.width, image.height, false, 0); bmdImage.draw(image); var bmImage:Bitmap = new Bitmap(bmdImage, PixelSnapping.AUTO, true); bmImage.scaleX = 1.0 * displayX / image.width; bmImage.scaleY = 1.0 * displayY / image.height; spImage.addChild(bmImage); } private var allowPointing:Boolean = true; private function onEnterFrame (e:Event):void { this.showMessage(); spFore.visible = chromakey.ready && slideVisible; spImage.visible = chromakey.ready && slideVisible; if ( chromakey.ready && allowPointing && slideVisible ) { // find pointers var source:BitmapData = chromakey.getForeBitmapData(); lpdetector.setFrame(source); var pointers:Array = lpdetector.findClusters(); if ( ! pointers ) return; // check pointers for ( var i:int = 0; i < pointers.length; i++ ) { var ci:LP_Cluster = pointers[i]; if ( ci.rect.left > workX * 3 / 5 ) { // right area this.showImage( showno + 1 ); // next slide this.stopPointing(); } else if ( ci.rect.right < workX * 2 / 5 ) { // left area this.showImage( showno - 1 ); // prev slide this.stopPointing(); } } } } private var timerPointing:Timer; private function stopPointing ():void { timerPointing = new Timer(pointerInterval, 1); timerPointing.addEventListener(TimerEvent.TIMER, onTimerPointing); timerPointing.start(); allowPointing = false; } private function onTimerPointing (e:TimerEvent):void { timerPointing.removeEventListener(TimerEvent.TIMER, onTimerPointing); allowPointing = true; } private function showMessage ():void { if ( chromakey.ready ) { if ( cntReady > 0 ) { message.text = 'READY'; message.visible = true; spGray.visible = false; } else if ( cntReady == 0 ) { message.visible = false; spGray.visible = false; } else { var match:Number = chromakey.getMatchLevel(); if ( match < 0.1 ) { message.text = 'PUSH ESC KEY TO RESET'; message.visible = true; spGray.visible = true; } else { message.visible = false; spGray.visible = false; } } cntReady --; } else { if ( cntReady < 1 ) { message.text = 'WAIT A MOMENT'; message.visible = true; spGray.visible = true; cntReady = 5; } } } private function onKeyDown (e:KeyboardEvent):void { if ( e.keyCode == Keyboard.RIGHT || e.keyCode == Keyboard.TAB ) { this.showImage( showno+1 ); } else if ( e.keyCode == Keyboard.LEFT ) { this.showImage( showno-1 ); } else if ( e.keyCode == Keyboard.HOME ) { this.showImage(0); } else if ( e.keyCode == Keyboard.END ) { this.showImage( images.length - 1 ); } else if ( e.keyCode == Keyboard.INSERT ) { horiFore -= 0.1 * (zoomFore < 0 ? 1.0 : -1.0); this.updateZooming(); } else if ( e.keyCode == Keyboard.DELETE ) { horiFore += 0.1 * (zoomFore < 0 ? 1.0 : -1.0); this.updateZooming(); } else if ( e.keyCode == Keyboard.PAGE_UP ) { zoomBack += 0.05; this.updateZooming(); } else if ( e.keyCode == Keyboard.PAGE_DOWN ) { zoomBack -= 0.05; this.updateZooming(); } else if ( e.keyCode == Keyboard.UP ) { zoomFore += 0.05; this.updateZooming(); } else if ( e.keyCode == Keyboard.DOWN ) { zoomFore -= 0.05; this.updateZooming(); } else if ( e.keyCode == Keyboard.ESCAPE ) { chromakey.runDetector(); } else if ( e.charCode > 0 ) { var chr:String = String.fromCharCode(e.charCode); if ( chr == '@' ) { this.homeZooming(); } else if ( chr == 'g' ) { this.togglePointerGuide( 'greenball' ); } else if ( chr == 'p' ) { this.togglePointerGuide( 'pinkball' ); } else if ( chr == 'f' ) { this.togglePointerGuide( 'fingercap' ); } else if ( chr == 'l' ) { slideVisible = ! slideVisible; spBack.visible = slideVisible; spLive.visible = ! slideVisible; } } } private function onMouseWheel (e:MouseEvent):void { zoomFore += e.delta / 10.0; this.updateZooming(); } private function homeZooming ():void { zoomBack = 0.0; zoomFore = 0.0; horiFore = 0.0; } private function updateZooming ():void { if ( zoomBack < 0.0 ) zoomBack = 0.0; if ( zoomBack > 4.0 ) zoomBack = 4.0; // 480 * 4 * 4 < 8192 if ( zoomFore > 4.0 ) zoomFore = 4.0; // 480 * 2 ** 4 < 8192 if ( horiFore > 1.0 ) horiFore = 1.0; if ( horiFore < -1.0 ) horiFore = -1.0; var aBack:Number = zoomBack * zoomBack; spBack.x = - displayX / 2.0 * aBack; spBack.y = - displayY * aBack; spBack.scaleX = 1.0 + aBack; spBack.scaleY = 1.0 + aBack; var aFore:Number = Math.pow( 2.0, zoomFore ); spFore.x = displayX * (1 - aFore) * (horiFore + 1.0) / 2.0; spFore.y = displayY * (1 - aFore); spFore.scaleX = aFore; spFore.scaleY = aFore; } } }