Jono Kafkaris
Interactive Developer

Pixel Bender – Background Subtraction

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("http://kafkaris.com/examples/pixelbender-backgroundsubtraction/nobird.jpg", {name:"background"}));
			_loaderMax.append(new ImageLoader("http://kafkaris.com/examples/pixelbender-backgroundsubtraction/bird.jpg", {name:"foreground"}));
			_loaderMax.append(new DataLoader("http://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;
		}
	}
}

5 Comments

  1. Og2t

    4 years ago

    Cool, you might want to check Kynd’s implementation out: http://www.kynd.info/log/?p=54

  2. asmedrano

    4 years ago

    very cool!

  3. Paul Hinrichsen

    4 years ago

    Hi

    I am trying to run your code in Flash CS6. I set it up correctly (I think) I have the Greensock com folder in the same folder as the .fla and I have included the Greensock .swc but although I get no reported errors nothing happens – the screen is blank.

    Any chance of a zip file that works so that I can compare and see where I am going wrong?

    Having said that GREAT piece of work !

    Thanks

    Paul

  4. Paul Hinrichsen

    4 years ago

    OK found the error !!!!!

    Its a little thing called PATIENCE !!!!

    Sometimes it takes a second or two to complete. Works GREAT though.

    Thanks again.

    Paul

Leave a Reply

Your email address will not be published. Required fields are marked *


four + = 10

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

css.php