package be.aboutme.differenceKey
{
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.DisplayObject;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.filters.BlurFilter;

	/**
	 * This class calculates the difference between 2 displayobjects
	 * and only shows the pixels that are truely difference
	 * 
	 * This can be used to create difference and/or color keying applications
	 * 
	 * Usage:
	 * 
	 * 1. Create a new instance of this class:
	 * 
	 * <code>var differenceImage:DifferenceImage = new DifferenceImage();</code>
	 * 
	 * 2. Set the background property to the the image you want to remove from the picture.
	 * Most of the time, this will be the webcam image without the user in the frame
	 * 
	 * <code>differenceImage.background = referenceToBackground;</code>
	 * 
	 * 3. Set the foreground property to the image you want to keep.
	 * Most of the time, this will be the webcame image with the user inside the frame
	 * 
	 * <code>differenceImage.foreground = referenceToForeground;</code>
	 * 
	 * 4. Add it to the displaylist to see the keyed result.
	 * 
	 * <code>addChild(differenceImage);</code>
	 */ 
	public class DifferenceImage extends Sprite
	{
		
		private var keyContainer:Sprite;
		private var differenceKeyShader:DifferenceKeyShader;
		private var foregroundBitmap:Bitmap;
		private var foregroundBitmapData:BitmapData;
		private var backgroundBitmap:Bitmap;
		private var backgroundBitmapData:BitmapData;
		private var outputMaskBitmap:Bitmap;
		private var outputMaskBitmapData:BitmapData;
		private var outputBitmap:Bitmap;
		private var outputBitmapData:BitmapData;
		
		private var animatedBackground:Boolean;
		private var backgroundInvalidated:Boolean;
		
		/**
		 * Amount of difference in the first color component to remove a pixel
		 * The first color component is Red in RGB mode, or Hue in HSB mode.
		 * This is a value between 0 and 1
		 * 
		 * @default 0.1
		 */ 
		public function set tolerance1(value:Number):void
		{
			differenceKeyShader.tolerance1 = value;
			if(foregroundBitmap != null)
			{
				foregroundBitmap.blendShader = differenceKeyShader;
			}
		}
		public function get tolerance1():Number
		{
			return differenceKeyShader.tolerance1;
		}
		
		/**
		 * Amount of difference in the second color component to remove a pixel
		 * The second color component is Green in RGB mode, or Saturation in HSB mode.
		 * This is a value between 0 and 1
		 * 
		 * @default 0.1
		 */ 
		public function set tolerance2(value:Number):void
		{
			differenceKeyShader.tolerance2 = value;
			if(foregroundBitmap != null)
			{
				foregroundBitmap.blendShader = differenceKeyShader;
			}
		}
		public function get tolerance2():Number
		{
			return differenceKeyShader.tolerance2;
		}
		
		/**
		 * Amount of difference in the third component to remove a pixel
		 * The third color component is Blue in RGB mode, or Brightness in HSB mode.
		 * This is a value between 0 and 1
		 * 
		 * @default 0.1
		 */ 
		public function set tolerance3(value:Number):void
		{
			differenceKeyShader.tolerance3 = value;
			if(foregroundBitmap != null)
			{
				foregroundBitmap.blendShader = differenceKeyShader;
			}
		}
		public function get tolerance3():Number
		{
			return differenceKeyShader.tolerance3;
		}
		
		/**
		 * Amount of blur to apply before we calculate the difference
		 * This is usefull to remove noise from the images before the difference calculation
		 * 
		 * @default 10
		 */ 
		private var _preBlur:Number;
		public function get preBlur():Number
		{
			return _preBlur;
		}
		public function set preBlur(value:Number):void
		{
			_preBlur = value;
			if(backgroundBitmap) backgroundBitmap.filters = [new BlurFilter(_preBlur, _preBlur)];
			if(foregroundBitmap) foregroundBitmap.filters = [new BlurFilter(_preBlur, _preBlur)];
		}
		
		/**
		 * Amount of blur to apply after we calculated the difference
		 * This is usefull to remove gaps from the image mask
		 * 
		 * @default 10
		 */ 
		private var _postBlur:Number;
		public function get postBlur():Number
		{
			return _postBlur;
		}
		public function set postBlur(value:Number):void
		{
			_postBlur = value;
			if(outputMaskBitmap) outputMaskBitmap.filters = [new BlurFilter(_postBlur, _postBlur)];
		}
		
		/**
		 * Use RGB (0) or HSB (1) mode for the calculations
		 * 
		 * @default 0
		 */ 
		public function set mode(value:uint):void
		{
			differenceKeyShader.mode = value;
			if(foregroundBitmap != null)
			{
				foregroundBitmap.blendShader = differenceKeyShader;
			}
		}
		public function get mode():uint
		{
			return differenceKeyShader.mode;
		}
		
		private var _foreground:DisplayObject;
		/**
		 * The image containing the pixels you want to keep in the keyed image.
		 * Most of the time, this will be a reference to a webcam feed with the user inside the frame
		 */ 
		public function get foreground():DisplayObject
		{
			return _foreground;
		}
		public function set foreground(value:DisplayObject):void
		{
			_foreground = value;
			if(_foreground != null)
			{
				resetBitmap("foregroundBitmap", "foregroundBitmapData");
				foregroundBitmapData = new BitmapData(_foreground.width, _foreground.height, true, 0x00000000);
				foregroundBitmap = new Bitmap(foregroundBitmapData);
				foregroundBitmap.blendShader = differenceKeyShader;
				foregroundBitmap.filters = [new BlurFilter(_preBlur, _preBlur)];
				keyContainer.addChild(foregroundBitmap);
				
				//create output
				resetBitmap("outputBitmap", "outputBitmapData");
				outputBitmapData = new BitmapData(_foreground.width, _foreground.height, true, 0x00000000);
				outputBitmap = new Bitmap(outputBitmapData);
				outputBitmap.cacheAsBitmap = true;
				addChild(outputBitmap);
				
				resetBitmap("outputMaskBitmap", "outputMaskBitmapData");
				outputMaskBitmapData = outputBitmapData.clone();
				outputMaskBitmap = new Bitmap(outputMaskBitmapData);
				outputMaskBitmap.filters = [new BlurFilter(_postBlur, _postBlur)];
				outputMaskBitmap.cacheAsBitmap = true;
				addChild(outputMaskBitmap);
				
				outputBitmap.mask = outputMaskBitmap;
				
				executeKeying();
			}
		}
		
		private var _background:DisplayObject;
		/**
		 * The image containing the pixels you want to remove from the foreground.
		 * Most of the time, this will be a webcam snapshot with the user outside of the frame
		 */ 
		public function get background():DisplayObject
		{
			return _background;
		}
		public function set background(value:DisplayObject):void
		{
			_background = value;
			if(_background != null)
			{
				resetBitmap("backgroundBitmap", "backgroundBitmapData");
				backgroundBitmapData = new BitmapData(_background.width, _background.height, true, 0x00000000);
				backgroundBitmap = new Bitmap(backgroundBitmapData);
				backgroundBitmap.filters = [new BlurFilter(_preBlur, _preBlur)];
				keyContainer.addChildAt(backgroundBitmap, 0);
				backgroundInvalidated = true;
				executeKeying();
			}
		}
		
		public function DifferenceImage()
		{
			differenceKeyShader = new DifferenceKeyShader();
			_preBlur = 10;
			_postBlur = 10;
			
			keyContainer = new Sprite();
			
			addEventListener(Event.ENTER_FRAME, enterFrameHandler);
		}
		
		private function enterFrameHandler(event:Event):void
		{
			executeKeying();
		}
		
		private function resetBitmap(bitmapName:String, bitmapDataName:String):void
		{
			if(this[bitmapName] != null)
			{
				this[bitmapName].parent.removeChild(this[bitmapName]);
				this[bitmapName] = null;
			}
			if(this[bitmapDataName] != null)
			{
				this[bitmapDataName].dispose();
				this[bitmapDataName] = null;
			}
		}
		
		private function executeKeying():void
		{
			if(_foreground != null)
			{
				if(_background != null)
				{
					foregroundBitmapData.draw(_foreground);
					
					if(backgroundInvalidated)
					{
						backgroundBitmapData.draw(_background);
						backgroundInvalidated = false;
					}
					
					outputBitmapData.draw(_foreground);
					outputMaskBitmapData.draw(keyContainer);
					
					if(animatedBackground) backgroundInvalidated = true;
				}
				else
				{
					outputBitmapData.draw(_foreground);
					outputMaskBitmapData.draw(_foreground);
				}
			}
		}
		
	}
}