Posts Tagged ‘ BitmapData ’

I’m pleased to be speaking at “Arrivano i guru” stage of Milan the next September 26th.
Arrivano i guru is a moving conference iterating the whole italian peninsula. Here’s the complete list of stages
Main topics are Adobe Flash Platform , SEO, and graphics. Great speakers are involved, such as Tiziano Fruet, Luca Mezzalira, and many others.

This is my session’s outline:
(ITALIAN)
Special Effects in Flash CS4
Impariamo ad utilizzare bitmaps, filtri e tweens in Flash CS4 per creare effetti speciali custom.

I filtri in flash
- cosa sono i filtri in flash
- applicare i filtri con AS3

I blendmode in flash
- cosa sono i blendmode in flash
- applicare i blendmode AS3

Sveliamo i bitmap
- cos’è un bitmap in Flash
- i bitmap come tela di un pittore
- manipolare i bitmap in AS3

TweensyFX
- cos’è Tweensy
- animare i propri effetti speciali

(ENGLISH)
Special Effects in Flash CS4
Let’s learn to use bitmaps, filters and tweens within Flash CS4 to create custom special effects.

filters
- what are filters in flash?
- applying filters using AS3

blendmodes
- what blend modes are
- applying blendmodes using AS3

discovering bitmaps
- what are bitmaps for Flash?
- bitmaps as a painter’s canvas
- manipulating bitmaps using AS3

TweensyFX
- what’s Tweensy?
- creating and animating our own special effects

See you there ;)

 
Thursday, July 16th, 2009

Yesterday night it was too warm to sleep and I have no air conditioning at home, so I thought it was a good opportunity to make some experiments related to the high temperature… “the burning man” :D

Here’s the algorithm in human language:
- pick up the image from the webcam
- perform a threshold on it to exclude (alpha 0) every pixel below the average skin amount of red (between 44 and 90 depending on the light)
- perform a second threshold to exclude (alpha 0) every pixel above the average skin amount of green and blue (between 3344 and 6677 depending on the light)
- draw the result onto a new bitmapdata with the ADD blendmode
- apply blur to the bitmapdata
- generate a perlin noise of one channel (i chose red)
- displace the bitmapdata towards -y
- apply again a displacement map filter to displace horizontally (i used a cos function to make the x displacement a little likely)
- apply a displacement map towards +y with just a bit less strenght than the previous “-y” one (this is to make the fire propagation much more likely)
- then merge the original webcam output with the displaced one…and you’ve the burning man! :)

here the actionscript implementation

?View Code ACTIONSCRIPT
package
{
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.BitmapDataChannel;
	import flash.display.BlendMode;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.filters.BlurFilter;
	import flash.filters.DisplacementMapFilter;
	import flash.filters.DisplacementMapFilterMode;
	import flash.geom.ColorTransform;
	import flash.geom.Matrix;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import flash.media.Camera;
	import flash.media.Video;
 
	[SWF(width="320",height="240",frameRate="31",backgroundColor="0x000000")]
	public class BurningMan extends Sprite
	{
 
		private var _cam:Camera;
		private var _vid:Video;
 
		private var _fireThreshold:BitmapData;
		private var _firePerlin:BitmapData;
		private var _fireOffset:Point;
		private var _fireSpeed:int = 20;
		private var _fireDisplaceUP:DisplacementMapFilter;
		private var _fireDisplaceDWN:DisplacementMapFilter;
		private var _fireDisplaceSrc:BitmapData;
		private var _fireBlur:BlurFilter;
		private var _fireColorTransform:ColorTransform;
 
		private var _fireMerge:BitmapData;
 
		private var _camBd:BitmapData;
		private var _b:Bitmap;
 
		private static const WIDTH:Number = 320;
		private static const HEIGHT:Number = 240;
		private static const ORIGIN:Point = new Point();
		private static const REFLECT:Matrix = new Matrix(-1,0,0,1,WIDTH,0);
		private static const RESIZE_2X:Matrix = new Matrix(2,0,0,2);
 
		private var _rect:Rectangle;
 
		public function BurningMan()
		{
			super();
			init();
		}
 
		private function init():void
		{
			_cam = Camera.getCamera();
			_cam.setMode(WIDTH,HEIGHT,31);
			_vid = new Video(WIDTH,HEIGHT);
			_vid.attachCamera(_cam);
 
			_fireOffset = new Point();
			_fireThreshold = new BitmapData(WIDTH,HEIGHT,true,0x00000000);
			_fireMerge = new BitmapData(WIDTH,HEIGHT,true,0x00000000);
 
			_firePerlin = new BitmapData(WIDTH*.5,HEIGHT*.5,false,0x000000);
			_fireDisplaceSrc = new BitmapData(WIDTH,HEIGHT,false,0x000000);
			_fireDisplaceUP = new DisplacementMapFilter(_fireDisplaceSrc,ORIGIN,BitmapDataChannel.RED,BitmapDataChannel.RED,0,-15,DisplacementMapFilterMode.CLAMP);
			_fireDisplaceDWN = new DisplacementMapFilter(_fireDisplaceSrc,ORIGIN,BitmapDataChannel.RED,BitmapDataChannel.RED,0,9,DisplacementMapFilterMode.CLAMP);
			_fireBlur = new BlurFilter(2,2,4);
			_fireColorTransform = new ColorTransform(1.3,1.1,1,.7);
 
 
			_camBd = new BitmapData(WIDTH,HEIGHT,true,0x00000000);
			_rect = _camBd.rect;
			_b = new Bitmap(_camBd);
			_firePerlin.lock();
			_fireMerge.lock();
			addChild(_b);
 
 
			addEventListener(Event.ENTER_FRAME,update);
		}
 
		private function update(e:Event=null):void
		{
			//locks
			_camBd.lock();
 
			_fireOffset.y+=_fireSpeed;
			_firePerlin.perlinNoise(_firePerlin.width*.125,_firePerlin.height*.125,1,0,true,false,BitmapDataChannel.RED,false,[_fireOffset])
			_fireDisplaceSrc.draw(_firePerlin,RESIZE_2X);
 
			_camBd.draw(_vid,REFLECT);
			_fireThreshold.setVector(_rect,_camBd.getVector(_rect));
			_fireThreshold.threshold(_fireThreshold,_rect,ORIGIN,"<",0xFF440000,0x00000000,0xFF0000,true);
			_fireThreshold.threshold(_fireThreshold,_rect,ORIGIN,">",0xFF003344,0x00000000,0x00FFFF,true);
			_fireMerge.draw(_fireThreshold,null,_fireColorTransform,BlendMode.ADD,_rect);
 
			_fireDisplaceUP.scaleX = Math.cos(((_fireOffset.y/(2<<3))%360)*180/Math.PI)*2;
			_fireDisplaceDWN.scaleX = -_fireDisplaceUP.scaleX;
			_fireMerge.applyFilter(_fireMerge,_fireMerge.rect,ORIGIN,_fireBlur);
			_fireMerge.applyFilter(_fireMerge,_rect,ORIGIN,_fireDisplaceUP);
			_fireMerge.applyFilter(_fireMerge,_rect,ORIGIN,_fireDisplaceDWN);
			_camBd.draw(_fireMerge,null,_fireColorTransform,BlendMode.HARDLIGHT,_rect);
 
			//unlocks
			_camBd.unlock();
		}
	}
}

here’s a preview of myself empowered by the force of the fire!:D
burning man
click the photo to see the demo in action

 
Wednesday, July 15th, 2009

Today I encountered this blog post from Zevan (which blog is REALLY a good daily reading I suggest everyone to take) about bitmapData merging and I started tweaking some code doing some benchmarks to find out which way is the most performing. Here are my tests:

First strike (Zevan’s one): copyPixels

?View Code ACTIONSCRIPT
[SWF(width=650, height=650)]
var loader:Loader = new Loader();
loader.load(new URLRequest("http://actionsnippet.com/wp-content/chair.jpg"));
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoaded);
var w:Number;
var h:Number;
var rows:Number = 20;
var cols:Number = 20;
var tiles:Vector.<BitmapData> = new Vector.<BitmapData>();
var locX:Vector.<Number> = new Vector.<Number>();
var locY:Vector.<Number> = new Vector.<Number>();
var rX:Vector.<Number> = new Vector.<Number>();
var rY:Vector.<Number> = new Vector.<Number>();
var sX:Vector.<Number> = new Vector.<Number>();
var sY:Vector.<Number> = new Vector.<Number>();
function onLoaded(evt:Event):void{
	w = evt.target.width;
	h = evt.target.height;
	var image:BitmapData = Bitmap(evt.target.content).bitmapData;
	var tileWidth:Number = w / cols;
	var tileHeight:Number = h / rows;
	var inc:int = 0;
	var pnt:Point = new Point();
	var rect:Rectangle = new Rectangle(0,0,tileWidth,tileHeight);
	var startTime:Number = getTimer();
	for (var i:int = 0; i<rows; i++){
		for (var j:int = 0; j<cols; j ++){
			 var currTile:BitmapData= new BitmapData(tileWidth, tileHeight, true, 0x00000000);
			 rect.x = j * tileWidth;
			 rect.y = i * tileHeight;
			 currTile.copyPixels(image, rect, pnt, null, null, true);
			 tiles[inc] = currTile;
			 rect.x += 25;
			 rect.y += 25;
			 sX[inc] = rect.x;
			 sY[inc] = rect.y;
			 locX[inc] = rX[inc] = -rect.width * 2
			 locY[inc] = rY[inc] =  Math.random() * stage.stageHeight;
			 setTimeout(startAnimation, inc *4 + 100, inc, rect.x, rect.y);
			 inc++;
		}
	}
	trace("copyPixels",getTimer()-startTime,"ms");
	addEventListener(Event.ENTER_FRAME, onLoop);
}
function startAnimation(index:int, dx:Number, dy:Number):void{
	var interval:Number;
	var animate:Function = function(index:int):void{
		locX[index] += (dx - locX[index]) / 4;
		locY[index] += (dy - locY[index]) / 4;
		if (Math.abs(locX[index] - dx) <1 && Math.abs(locY[index] - dy)<1){
			locX[index] = dx;
			locY[index] = dy;
			clearInterval(interval);
		}
	}
   interval = setInterval(animate, 32, index);
}
var canvas:BitmapData = new BitmapData(stage.stageWidth,stage.stageHeight,false, 0xFFFFFF);
addChild(new Bitmap(canvas));
var loc:Point = new Point();
 
function onLoop(evt:Event):void {
	  canvas.fillRect(canvas.rect, 0xFFFFFF);
	  var startTime:Number = getTimer();
	  for (var i:int = 0; i<tiles.length; i++){
			var tile:BitmapData= tiles[i];
			loc.x = locX[i];
			loc.y = locY[i];
			canvas.copyPixels(tile, tile.rect, loc, null, null, true);
	  }
	  trace("copyPixels",getTimer()-startTime,"ms");
}

in my machine (mbp, osx) it takes ~27 ms to extract data and ~2-3ms each iteration for setting data on the canvas bitmapData

Second strike: getVector\setVector

?View Code ACTIONSCRIPT
[SWF(width=650, height=650)]
var loader:Loader = new Loader();
loader.load(new URLRequest("http://actionsnippet.com/wp-content/chair.jpg"));
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoaded);
var w:Number;
var h:Number;
var rows:Number = 20;
var cols:Number = 20;
var tiles:Vector.<Vector.<uint>> = new Vector.<Vector.<uint>>();
var tileRect:Rectangle;
var locX:Vector.<Number> = new Vector.<Number>();
var locY:Vector.<Number> = new Vector.<Number>();
var rX:Vector.<Number> = new Vector.<Number>();
var rY:Vector.<Number> = new Vector.<Number>();
var sX:Vector.<Number> = new Vector.<Number>();
var sY:Vector.<Number> = new Vector.<Number>();
function onLoaded(evt:Event):void{
	w = evt.target.width;
	h = evt.target.height;
	var image:BitmapData = Bitmap(evt.target.content).bitmapData;
	var tileWidth:Number = w / cols;
	var tileHeight:Number = h / rows;
	tileRect = new Rectangle(0,0,tileWidth,tileHeight);
	var inc:int = 0;
	var pnt:Point = new Point();
	var rect:Rectangle = new Rectangle(0,0,tileWidth,tileHeight);
	var startTime:Number = getTimer();
	for (var i:int = 0; i<rows; i++){
		for (var j:int = 0; j<cols; j ++){
			 rect.x = j * tileWidth;
			 rect.y = i * tileHeight;
			 tiles[tiles.length] = image.getVector(rect);
			 rect.x += 25;
			 rect.y += 25;
			 sX[inc] = rect.x;
			 sY[inc] = rect.y;
			 locX[inc] = rX[inc] = -rect.width * 2
			 locY[inc] = rY[inc] =  Math.random() * stage.stageHeight;
			 setTimeout(startAnimation, inc *4 + 100, inc, rect.x, rect.y);
			 inc++;
		}
	}
	trace("vector push:",getTimer()-startTime,"ms");
	addEventListener(Event.ENTER_FRAME, onLoop);
}
function startAnimation(index:int, dx:Number, dy:Number):void{
	var interval:Number;
	var animate:Function = function(index:int):void{
		locX[index] += (dx - locX[index]) / 4;
		locY[index] += (dy - locY[index]) / 4;
		if (Math.abs(locX[index] - dx) <1 && Math.abs(locY[index] - dy)<1){
			locX[index] = dx;
			locY[index] = dy;
			clearInterval(interval);
		}
	}
   interval = setInterval(animate, 32, index);
}
var canvas:BitmapData = new BitmapData(stage.stageWidth,stage.stageHeight,false, 0xFFFFFF);
addChild(new Bitmap(canvas));
var loc:Point = new Point();
 
function onLoop(evt:Event):void {
	  canvas.fillRect(canvas.rect, 0xFFFFFF);
	  var tmpVec:Vector.<uint>;
	  var startTime:Number = getTimer();
	  for (var i:int = 0; i<tiles.length; i++){
		  	tmpVec = tiles[i];
			tileRect.x = locX[i];
			tileRect.y = locY[i];
			canvas.setVector(tileRect,tmpVec);
	  }
	  trace("vector push:",getTimer()-startTime,"ms");
}

on my machine it takes only ~9 ms to extract tiles slices (yes, 3 times faster!!!) and ~1ms for pushing them all in the canvas for each loop iteration

Third strike: getPixels\setPixels

?View Code ACTIONSCRIPT
[SWF(width=650, height=650)]
var loader:Loader = new Loader();
loader.load(new URLRequest("http://actionsnippet.com/wp-content/chair.jpg"));
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoaded);
var w:Number;
var h:Number;
var rows:Number = 20;
var cols:Number = 20;
var tiles:Vector.<ByteArray> = new Vector.<ByteArray>();
var tileRect:Rectangle;
var locX:Vector.<Number> = new Vector.<Number>();
var locY:Vector.<Number> = new Vector.<Number>();
var rX:Vector.<Number> = new Vector.<Number>();
var rY:Vector.<Number> = new Vector.<Number>();
var sX:Vector.<Number> = new Vector.<Number>();
var sY:Vector.<Number> = new Vector.<Number>();
function onLoaded(evt:Event):void{
	w = evt.target.width;
	h = evt.target.height;
	var image:BitmapData = Bitmap(evt.target.content).bitmapData;
	var tileWidth:Number = w / cols;
	var tileHeight:Number = h / rows;
	tileRect = new Rectangle(0,0,tileWidth,tileHeight);
	var inc:int = 0;
	var pnt:Point = new Point();
	var rect:Rectangle = new Rectangle(0,0,tileWidth,tileHeight);
	var startTime:Number = getTimer();
	for (var i:int = 0; i<rows; i++){
		for (var j:int = 0; j<cols; j ++){
			 rect.x = j * tileWidth;
			 rect.y = i * tileHeight;
			 tiles[tiles.length] = image.getPixels(rect);
			 rect.x += 25;
			 rect.y += 25;
			 sX[inc] = rect.x;
			 sY[inc] = rect.y;
			 locX[inc] = rX[inc] = -rect.width * 2
			 locY[inc] = rY[inc] =  Math.random() * stage.stageHeight;
			 setTimeout(startAnimation, inc *4 + 100, inc, rect.x, rect.y);
			 inc++;
		}
	}
	trace("getPixels",getTimer()-startTime,"ms");
	addEventListener(Event.ENTER_FRAME, onLoop);
}
function startAnimation(index:int, dx:Number, dy:Number):void{
	var interval:Number;
	var animate:Function = function(index:int):void{
		locX[index] += (dx - locX[index]) / 4;
		locY[index] += (dy - locY[index]) / 4;
		if (Math.abs(locX[index] - dx) <1 && Math.abs(locY[index] - dy)<1){
			locX[index] = dx;
			locY[index] = dy;
			clearInterval(interval);
		}
	}
   interval = setInterval(animate, 32, index);
}
var canvas:BitmapData = new BitmapData(stage.stageWidth,stage.stageHeight,false, 0xFFFFFF);
addChild(new Bitmap(canvas));
var loc:Point = new Point();
 
function onLoop(evt:Event):void {
	  canvas.fillRect(canvas.rect, 0xFFFFFF);
	  var startTime:Number = getTimer();
	  for (var i:int = 0; i<tiles.length; i++){
			tileRect.x = locX[i];
			tileRect.y = locY[i];
			canvas.setPixels(tileRect,tiles[i]);
			tiles[i].position = 0;
	  }
	  trace("setPixels",getTimer()-startTime,"ms");
}

a nice one, ~ 17 ms to extract and ~2ms to loop on my mac, faster than copyPixels but vectors are still leading…

Fourth strike: merge

?View Code ACTIONSCRIPT
[SWF(width=650, height=650)]
var loader:Loader = new Loader();
loader.load(new URLRequest("http://actionsnippet.com/wp-content/chair.jpg"));
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoaded);
var w:Number;
var h:Number;
var rows:Number = 20;
var cols:Number = 20;
var tiles:Vector.<BitmapData> = new Vector.<BitmapData>();
var locX:Vector.<Number> = new Vector.<Number>();
var locY:Vector.<Number> = new Vector.<Number>();
var rX:Vector.<Number> = new Vector.<Number>();
var rY:Vector.<Number> = new Vector.<Number>();
var sX:Vector.<Number> = new Vector.<Number>();
var sY:Vector.<Number> = new Vector.<Number>();
function onLoaded(evt:Event):void{
	w = evt.target.width;
	h = evt.target.height;
	var image:BitmapData = Bitmap(evt.target.content).bitmapData;
	var tileWidth:Number = w / cols;
	var tileHeight:Number = h / rows;
	var inc:int = 0;
	var pnt:Point = new Point();
	var rect:Rectangle = new Rectangle(0,0,tileWidth,tileHeight);
	var startTime:Number = getTimer();
	for (var i:int = 0; i<rows; i++){
		for (var j:int = 0; j<cols; j ++){
			 var currTile:BitmapData= new BitmapData(tileWidth, tileHeight, true, 0x00000000);
			 rect.x = j * tileWidth;
			 rect.y = i * tileHeight;
			 currTile.merge(image,rect,pnt,0xFF,0xFF,0xFF,0xFF);
			 tiles[inc] = currTile;
			 rect.x += 25;
			 rect.y += 25;
			 sX[inc] = rect.x;
			 sY[inc] = rect.y;
			 locX[inc] = rX[inc] = -rect.width * 2
			 locY[inc] = rY[inc] =  Math.random() * stage.stageHeight;
			 setTimeout(startAnimation, inc *4 + 100, inc, rect.x, rect.y);
			 inc++;
		}
	}
	trace("merge",getTimer()-startTime,"ms");
	addEventListener(Event.ENTER_FRAME, onLoop);
}
function startAnimation(index:int, dx:Number, dy:Number):void{
	var interval:Number;
	var animate:Function = function(index:int):void{
		locX[index] += (dx - locX[index]) / 4;
		locY[index] += (dy - locY[index]) / 4;
		if (Math.abs(locX[index] - dx) <1 && Math.abs(locY[index] - dy)<1){
			locX[index] = dx;
			locY[index] = dy;
			clearInterval(interval);
		}
	}
   interval = setInterval(animate, 32, index);
}
var canvas:BitmapData = new BitmapData(stage.stageWidth,stage.stageHeight,false, 0xFFFFFF);
addChild(new Bitmap(canvas));
var loc:Point = new Point();
 
function onLoop(evt:Event):void {
	  canvas.fillRect(canvas.rect, 0xFFFFFF);
	  var startTime:Number = getTimer();
	  for (var i:int = 0; i<tiles.length; i++){
			var tile:BitmapData= tiles[i];
			loc.x = locX[i];
			loc.y = locY[i];
			canvas.merge(tile, tile.rect, loc, 0xFF,0xFF,0xFF,0xFF);
	  }
	  trace("merge",getTimer()-startTime,"ms");
}

~36 ms to extract and ~12ms to merge tiles on the canvas!!!…too slow all the way…:\

There are still some methods left such as getPixel\setPixel, getPixel32\setPixel32, and copyChannel but they’re a too much restrictive choice because they’re handling one pixel, or channel at time therefore a further loop would be required to get them doing this task.

Summary:
getVector\setVector : ~9ms\~1ms
getPixels\setPixels: ~17ms\~2ms
copyPixels: ~27ms\~2-3ms
merge: ~36ms\~12ms

make your choice ;)

NOTE: these benchmarks are valid from flash player 10 because we (both me and Zevan) used the Vector native type to store lists of typed data. To make them valid for previous version of the player make sure to replace vectors with arrays and check other types are already supported by the target player.

stay tuned ;)