Since Flash 8, BitmapData has offered a wide range of possibilities to improve performance. Many Flash apps, particularly games, choose to simply display a single, huge BitmapData, render the entire game scene into it and, for the most part, eschew Stage‘s whole system of DisplayObject and DisplayObjectContainer. When you’re doing this or just generally using BitmapData for more than just raster (e.g. JPEG, PNG) image display, you should know your options for composing a BitmapData out of other BitmapData. Today’s article discussing the performance of the two main ways of composing these BitmapData scenes: BitmapData.draw and BitmapData.copyPixels.

For starters, its worth pointing out that BitmapData.draw and BitmapData.copyPixels are two very different functions. For example, BitmapData.draw can draw any DisplayObject and optionally supports:

  • A transformation matrix (e.g. for scaling or rotating)
  • A color transform (e.g. for tinting or grayscale)
  • A custom blend mode
  • A clipping rectangle (i.e. to draw only a portion of the source)
  • Smoothing (i.e. to reduce jagginess)

On the other hand, BitmapData.copyPixels can only copy from another BitmapData and supports fewer, yet different options:

  • A clipping rectangle
  • An “alpha” BitmapData whose alpha values should be used instead

Usually, the API with fewer features will be quicker. In this case, it seems as though BitmapData.copyPixels was custom-designed to copy chunks of pixels from one BitmapData to another (i.e. “bit blitting“) and should perform very well against the jack-of-all-trades BitmapData.draw. To find out, let’s look at a performance test that uses the two functions on big and small BitmapData.

package
{
	import flash.display.*;
	import flash.utils.*;
	import flash.text.*;
	import flash.geom.*;
 
	public class CopyingPixels extends Sprite
	{
		private var __logger:TextField = new TextField();
		private function row(...cols): void
		{
			__logger.appendText(cols.join(",")+"\n");
		}
 
		public function CopyingPixels()
		{
			stage.align = StageAlign.TOP_LEFT;
			stage.scaleMode = StageScaleMode.NO_SCALE;
 
			var logger:TextField = __logger;
			logger.autoSize = TextFieldAutoSize.LEFT;
			addChild(logger);
 
			var beforeTime:int;
			var endTime:int;
			var bigToBigTime:int;
			var bigToSmallTime:int;
			var smallToBigTime:int;
			var smallToSmallTime:int;
			var i:int;
			const REPS:int = 10000;
			var bmdBig:BitmapData = new BitmapData(1000, 1000);
			var bmdBig2:BitmapData = new BitmapData(1000, 1000);
			var bmdSmall:BitmapData = new BitmapData(250, 250);
			var bmdSmall2:BitmapData = new BitmapData(250, 250);
			const COPY_DIM:int = 220;
			const COPY_RECT:Rectangle = new Rectangle(0, 0, COPY_DIM, COPY_DIM);
			const DEST_POINT:Point = new Point(5, 5);
			const DRAW_MATRIX:Matrix = new Matrix(1, 0, 0, 1, 5, 5);
 
			row(
				"Function",
				"Big-to-Big",
				"Big-to-Small",
				"Small-to-Big",
				"Small-to-Small"
			);
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				bmdBig.copyPixels(bmdBig2, COPY_RECT, DEST_POINT);
			}
			endTime = getTimer();
			bigToBigTime = endTime - beforeTime;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				bmdSmall.copyPixels(bmdBig, COPY_RECT, DEST_POINT);
			}
			endTime = getTimer();
			bigToSmallTime = endTime - beforeTime;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				bmdBig.copyPixels(bmdSmall, COPY_RECT, DEST_POINT);
			}
			endTime = getTimer();
			smallToBigTime = endTime - beforeTime;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				bmdSmall.copyPixels(bmdSmall2, COPY_RECT, DEST_POINT);
			}
			endTime = getTimer();
			smallToSmallTime = endTime - beforeTime;
 
			row(
				"copyPixels",
				bigToBigTime,
				bigToSmallTime,
				smallToBigTime,
				smallToSmallTime
			);
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				bmdBig.draw(bmdBig2, DRAW_MATRIX, null, null, COPY_RECT);
			}
			endTime = getTimer();
			bigToBigTime = endTime - beforeTime;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				bmdSmall.draw(bmdBig, DRAW_MATRIX, null, null, COPY_RECT);
			}
			endTime = getTimer();
			bigToSmallTime = endTime - beforeTime;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				bmdBig.draw(bmdSmall, DRAW_MATRIX, null, null, COPY_RECT);
			}
			endTime = getTimer();
			smallToBigTime = endTime - beforeTime;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				bmdSmall.draw(bmdSmall2, DRAW_MATRIX, null, null, COPY_RECT);
			}
			endTime = getTimer();
			smallToSmallTime = endTime - beforeTime;
 
			row(
				"draw",
				bigToBigTime,
				bigToSmallTime,
				smallToBigTime,
				smallToSmallTime
			);
		}
	}
}

I ran this performance test with the following environment:

  • Flex SDK (MXMLC) 4.5.1.21328, compiling in release mode (no debugging or verbose stack traces)
  • Release version of Flash Player 10.3.183.7
  • 2.4 Ghz Intel Core i5
  • Mac OS X 10.7.1

And got these results:

Function Big-to-Big Big-to-Small Small-to-Big Small-to-Small
copyPixels 256 247 242 231
draw 1474 1447 1443 1423

Copying Pixels Performance Chart

Clearly BitmapData.copyPixels has a huge advantage regardless of image size. If you’re going to draw a non-BitmapData several times (e.g. every frame), it’s definitely worth it to draw it once to a BitmapData and then use BitmapData.copyPixels from that source. On a related note, smaller BitmapData tends to perform better than larger BitmapData, so try to keep your images small if you’re trying to eek out some extra performance.

Spot a bug? Have a suggestion? Post a comment!