First play at creating and using a filter with Pixel Bender


Best value to enter is around 40…

This comes from a proof of concept piece for a green screen job I’m working on. Initially I was trying to use just Flash to remove the background, I had some success but it was extremely slow. Started looking into Pixel Bender as an alternative solution and had some success.

I created a filter that compares each pixel of 2 images – if the pixel is within a threshold it
turns the alpha off.

The best solution I found with the green screen was to test using the Hue value of each image.

I then load this into Flash, passing it the threshold from the text field.

It does come out with jagged edges, I’m looking perhaps another filter for correcting this, but the current solution is to run the filter on a large image and then scale it down.

It also works best with a contrasting colour, like green, as seen with our chicken friend.

Here is the code for the above demo.

Full urls to background, chicken and the filter in the code below.

JK.

PS – Also for the first time tried out GreenSock’s LoaderMax, I like it, will try it out now as an alternative to BulkLoader.

package com.spinifexgroup
{
	import com.greensock.events.LoaderEvent;
	import com.greensock.loading.DataLoader;
	import com.greensock.loading.ImageLoader;
	import com.greensock.loading.LoaderMax;
	import com.greensock.loading.display.ContentDisplay;

	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.Shader;
	import flash.display.ShaderJob;
	import flash.display.Sprite;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.text.TextField;
	import flash.text.TextFieldAutoSize;
	import flash.text.TextFieldType;

	/**
	 * @author jono kafkaris
	 */
	[SWF(backgroundColor="#CCCCCC", frameRate="30", width="500", height="400")]
	public class GreenScreen extends Sprite
	{
		private const IMAGE_WIDTH:uint = 500;
		private const IMAGE_HEIGHT:uint = 334;
		
		private var _loaderMax:LoaderMax;
		private var _background:Bitmap;
		private var _foreground:Bitmap;
		private var _backgroundSubtractionShader:Shader;
		private var _filteredBitmap:Bitmap;
		private var _inputField:TextField;
		private var _button:Sprite;
		private var _threshold:uint = 0;
		
		public function GreenScreen()
		{
			if (stage) init();
			else addEventListener(Event.ADDED_TO_STAGE, init);
		}

		private function init(event:Event = null) : void
		{
			stage.align = StageAlign.TOP_LEFT;
			stage.scaleMode = StageScaleMode.NO_SCALE;
			
			loadContent();
		}

		private function loadContent() : void
		{
			_loaderMax = new LoaderMax({name:"mainQueue", onProgress:progressHandler, onComplete:completeHandler, onError:errorHandler});

			_loaderMax.append(new ImageLoader("https://kafkaris.com/examples/pixelbender-backgroundsubtraction/nobird.jpg", {name:"background"}));
			_loaderMax.append(new ImageLoader("https://kafkaris.com/examples/pixelbender-backgroundsubtraction/bird.jpg", {name:"foreground"}));
			_loaderMax.append(new DataLoader("https://kafkaris.com/examples/pixelbender-backgroundsubtraction/BackgroundRemoval.pbj", {name:"backgroundSubtraction", format:"binary"}));

			_loaderMax.load();
		}

		private function progressHandler(event:LoaderEvent):void
		{
			trace("progress: ", event.target.progress);
		}
		
		private function errorHandler(event:LoaderEvent):void
		{
			trace("error occured with ", event.target + ": ", event.text);
		}				
		
		private function completeHandler(event:LoaderEvent):void
		{
			_background = ContentDisplay(LoaderMax.getContent("background")).rawContent;
			_foreground = ContentDisplay(LoaderMax.getContent("foreground")).rawContent;
			_backgroundSubtractionShader = new Shader(LoaderMax.getContent("backgroundSubtraction"));
			
			setupStage();
		}

		private function setupStage() : void
		{
			_inputField = new TextField();
			_inputField.type = TextFieldType.INPUT;
			_inputField.border = true;
			_inputField.backgroundColor = 0xFFFFFF;
			_inputField.background = true;
			_inputField.restrict = "0-9";
			_inputField.x = 10;
			_inputField.y = IMAGE_HEIGHT + 10;
			_inputField.width = 30;
			_inputField.height = 14;
			_inputField.text = _threshold.toString();
			addChild(_inputField);
			
			_button = new Sprite();
			_button.buttonMode = true;
			_button.graphics.beginFill(0x000000);
			_button.graphics.drawRect(0, 0, 30, 14);
			_button.graphics.endFill();
			_button.x = _inputField.x + _inputField.width + 10;
			_button.y = _inputField.y;
			_button.addEventListener(MouseEvent.CLICK, buttonClickHandler);
			addChild(_button);

			var instructions:TextField = new TextField();
			instructions.autoSize = TextFieldAutoSize.LEFT;
			instructions.x = 10;
			instructions.y = _button.y + _button.height + 10;
			instructions.text = "Type in a value in the box above and click the black button";
			addChild(instructions);

			_filteredBitmap = new Bitmap(pixelBender());
			addChild(_filteredBitmap);			
		}

		private function buttonClickHandler(event:MouseEvent) : void
		{
			removeChild(_filteredBitmap);
			_threshold = uint(_inputField.text);
			_filteredBitmap = new Bitmap(pixelBender());
			addChild(_filteredBitmap);			
		}

		private function pixelBender():BitmapData
		{
			var bitmapData:BitmapData = new BitmapData(IMAGE_WIDTH, IMAGE_HEIGHT, true);
			
			_backgroundSubtractionShader.data.thresholdH.value = [_threshold];
			_backgroundSubtractionShader.data.src.input = _background.bitmapData;
			_backgroundSubtractionShader.data.src2.input = _foreground.bitmapData;

			var shaderJob:ShaderJob = new ShaderJob(_backgroundSubtractionShader, bitmapData);

			shaderJob.start();

			return bitmapData;
		}
	}
}