The BitmapData class is among the most useful classes in AS3. When it was introduced in Flash 8 it dramatically improved Flash development by opening up new potential for features and optimization. Since it’s used so often, it’s good to know as much about it as possible. Today I’m going to cover the performance difference that turning alpha (a.k.a. transparency) on makes.

Astute readers of Monday’s Map Performance article took note of the difference in performance of the getPixel and getPixel32 functions when alpha is enabled. I had noticed this empirically before, but never actually tested it in an isolated environment. Monday I did that with getPixel and getPixel32 and today I’m doing that with many more of the methods of the BitmapData class.

The below test is as straightforward as my tests usually are, but this time with a bit of a tweak. Since I tested so many functions and so many of them take so long to complete, I was forced to split them into two categories: light and heavy. The light tests (eg. getPixel) complete quickly and therefore need more iterations to amount to a significant number of milliseconds. The heavy tests (eg. perlinNoise) are just the opposite. I split these two types of tests into two functions and ran them over two frames. Without further ado, here is the test app:

package
{
	import flash.display.*;
	import flash.filters.*;
	import flash.geom.*;
	import flash.text.*;
	import flash.utils.*;
	import flash.events.*;
 
	/**
	*   An app to test the speed of BitmapData objects with and without alpha
	*   @author Jackson Dunstan
	*/
	public class BitmapDataAlphaTest extends Sprite
	{
		private var logger:TextField = new TextField();
 
		public function BitmapDataAlphaTest ()
		{
			stage.align = StageAlign.TOP_LEFT;
			stage.scaleMode = StageScaleMode.NO_SCALE;
 
			logger.autoSize = TextFieldAutoSize.LEFT;
			addChild(logger);
 
			addEventListener(Event.ENTER_FRAME, testLight);
		}
 
		private function log(msg:*): void { logger.appendText(msg + "\n"); }
 
		private function testLight(ev:Event): void
		{
			removeEventListener(Event.ENTER_FRAME, testLight);
 
			var i:int;
			const NUM_ITERATIONS:int = 1000000;
			var beforeTime:int;
 
			var bmdAlpha:BitmapData = new BitmapData(100, 100, true);
			var bmdNoAlpha:BitmapData = new BitmapData(100, 100, false);
 
			var rect:Rectangle = new Rectangle(0, 0, 100, 100);
			var blurFilter:BlurFilter = new BlurFilter();
 
			log("Light: (" + NUM_ITERATIONS + " iterations)");
 
			log("\tAlpha:");
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				bmdAlpha.floodFill(0, 0, 0xff000000);
			}
			log("\t\tfloodFill: " + (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				bmdAlpha.generateFilterRect(rect, blurFilter);
			}
			log("\t\tgenerateFilterRect: " + (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				bmdAlpha.getColorBoundsRect(0x00000000, 0x00000000);
			}
			log("\t\tgetColorBoundsRect: " + (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				bmdAlpha.getPixel(0, 0);
			}
			log("\t\tgetPixel: " + (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				bmdAlpha.getPixel32(0, 0);
			}
			log("\t\tgetPixel32: " + (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				bmdAlpha.scroll(0, 0);
			}
			log("\t\tscroll: " + (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				bmdAlpha.setPixel(0, 0, 0xff000000);
			}
			log("\t\tsetPixel: " + (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				bmdAlpha.setPixel32(0, 0, 0xff000000);
			}
			log("\t\tsetPixel32: " + (getTimer()-beforeTime));
 
			log("\tNo Alpha:");
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				bmdNoAlpha.floodFill(0, 0, 0xff000000);
			}
			log("\t\tfloodFill: " + (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				bmdNoAlpha.generateFilterRect(rect, blurFilter);
			}
			log("\t\tgenerateFilterRect: " + (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				bmdNoAlpha.getColorBoundsRect(0x00000000, 0x00000000);
			}
			log("\t\tgetColorBoundsRect: " + (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				bmdNoAlpha.getPixel(0, 0);
			}
			log("\t\tgetPixel: " + (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				bmdNoAlpha.getPixel32(0, 0);
			}
			log("\t\tgetPixel32: " + (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				bmdNoAlpha.scroll(0, 0);
			}
			log("\t\tscroll: " + (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				bmdNoAlpha.setPixel(0, 0, 0xff000000);
			}
			log("\t\tsetPixel: " + (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				bmdNoAlpha.setPixel32(0, 0, 0xff000000);
			}
			log("\t\tsetPixel32: " + (getTimer()-beforeTime));
 
			addEventListener(Event.ENTER_FRAME, testHeavy);
		}
 
		private function testHeavy(ev:Event): void
		{
			removeEventListener(Event.ENTER_FRAME, testHeavy);
 
			var i:int;
			const NUM_ITERATIONS:int = 1000;
			var beforeTime:int;
 
			var bmdAlpha:BitmapData = new BitmapData(100, 100, true);
			var bmdNoAlpha:BitmapData = new BitmapData(100, 100, false);
 
			var rect:Rectangle = new Rectangle(0, 0, 100, 100);
			var colorTransform:ColorTransform = new ColorTransform();
			var byteArray:ByteArray = new ByteArray();
			var vec:Vector.<uint> = new Vector.<uint>(100*100, true);
			for (i = 0; i < 100*100; ++i)
			{
				byteArray.writeUnsignedInt(0xff000000);
				vec[i] = 0xff000000;
			}
 
			log("Heavy: (" + NUM_ITERATIONS + " iterations)");
 
			log("\tAlpha:");
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				bmdAlpha.colorTransform(rect, colorTransform);
			}
			log("\t\tcolorTransform: " + (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				bmdAlpha.clone();
			}
			log("\t\tclone: " + (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				bmdAlpha.fillRect(rect, 0xff000000);
			}
			log("\t\tfillRect: " + (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				bmdAlpha.getPixels(rect);
			}
			log("\t\tgetPixels: " + (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				bmdAlpha.getVector(rect);
			}
			log("\t\tgetVector: " + (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				bmdAlpha.histogram(rect);
			}
			log("\t\thistogram: " + (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				bmdAlpha.noise(33);
			}
			log("\t\tnoise: " + (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				bmdAlpha.perlinNoise(64, 128, 1, 33, false, false);
			}
			log("\t\tperlinNoise: " + (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				byteArray.position = 0;
				bmdAlpha.setPixels(rect, byteArray);
			}
			log("\t\tsetPixels: " + (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				bmdAlpha.setVector(rect, vec);
			}
			log("\t\tsetVector: " + (getTimer()-beforeTime));
 
			log("\tNo Alpha:");
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				bmdNoAlpha.colorTransform(rect, colorTransform);
			}
			log("\t\tcolorTransform: " + (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				bmdNoAlpha.clone();
			}
			log("\t\tclone: " + (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				bmdNoAlpha.fillRect(rect, 0xff000000);
			}
			log("\t\tfillRect: " + (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				bmdNoAlpha.getPixels(rect);
			}
			log("\t\tgetPixels: " + (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				bmdNoAlpha.getVector(rect);
			}
			log("\t\tgetVector: " + (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				bmdNoAlpha.histogram(rect);
			}
			log("\t\thistogram: " + (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				bmdNoAlpha.noise(33);
			}
			log("\t\tnoise: " + (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				bmdNoAlpha.perlinNoise(64, 128, 1, 33, false, false);
			}
			log("\t\tperlinNoise: " + (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				byteArray.position = 0;
				bmdNoAlpha.setPixels(rect, byteArray);
			}
			log("\t\tsetPixels: " + (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				bmdNoAlpha.setVector(rect, vec);
			}
			log("\t\tsetVector: " + (getTimer()-beforeTime));
		}
	}
}

And here are the results for both the light and heavy functions as well as the speedup for not enabling alpha:

Light Functions (1000000 iterations)
Function 3.0 Ghz Intel Core 2 Duo, 4 GB RAM, Windows XP
floodFill 81 alpha, 80 no alpha, 1.25% speedup
generateFilterRect 453 alpha, 453 no alpha, 0% speedup
getColorBoundsRect 408 alpha, 411 no alpha, -0.73% speedup
getPixel 13 alpha, 10 no alpha, 30% speedup
getPixel32 12 alpha, 9 no alpha, 33.33% speedup
scroll 28 alpha, 28 no alpha, 0% speedup
setPixel 46 alpha, 42 no alpha, 9.52% speedup
setPixel32 45 alpha, 41 no alpha, 9.76% speedup
Heavy Functions (1000 iterations)
Function 3.0 Ghz Intel Core 2 Duo, 4 GB RAM, Windows XP
colorTransform 6 alpha, 6 no alpha, 0% speedup
clone 33 alpha, 47 no alpha, -29.79% speedup
fillRect 4 alpha, 4 no alpha, 0% speedup
getPixels 549 alpha, 477 no alpha, 15.09% speedup
getVector 73 alpha, 60 no alpha, 21.67% speedup
histogram 110 alpha, 98 no alpha, 12.24% speedup
noise 291 alpha, 289 no alpha, 0.69% speedup
perlinNoise 905 alpha, 871 no alpha, 3.9% speedup
setPixels 48 alpha, 31 no alpha, 54.84% speedup
setVector 45 alpha, 13 no alpha, 246.15% speedup

As you can see, the speedup is usually positive. In a few cases the speedup is zero and in a couple cases actually negative. The slowdown for getColorBoundsRect is negligible enough to be testing error, but the almost 30% slowdown for clone should be enough to give you pause if you are going to be cloning a lot. On the upside, there are quite a few massive performance increases to be had by disabling alpha. One of these stands out above all else: setVector. I don’t know what kind of optimization Adobe managed for no-alpha BitmapDatas, but it sure paid off in spades! Even if you’re not going to use that, there are 8 other methods that are ~10% faster or more. This makes for an average speedup of 27.4% (12.82% if you don’t count setVector).

Just because the alpha/transparent parameter is true by default, it’s worth your time to pause and consider whether or not you actually need it.