Since they were introduced way back in Flash Player 8, bitmaps have become a core feature in almost all Flash apps. The way you handle them—creation, operations, and destroying—is one of the most important factors determining your app’s performance. Today’s article shows one little-known trick to help out the performance of loading and using bitmaps.

First of all, let’s review the steps that occur when your Flash app loads and uses a bitmap:

  1. The bytes of the bitmap file (e.g. PNG, JPEG, GIF) are loaded
  2. The file is decompressed into a block of ARGB pixels and stored in a BitmapData
  3. A Bitmap is created to use the BitmapData
  4. The Bitmap and/or BitmapData are used (e.g. put on the Stage, uploaded to a Texture)

Let’s look at some simple AS3 code that loads and uses a Bitmap to illustrate when these steps occur:

// This will (eventually) start step #1- loading the bytes of the bitmap file
var loader:Loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoaded);{
loader.load(new URLRequest("myImage.png"));
 
private function onLoaded(ev:Event): void
{
	// At some point before this function is called, #3 (Bitmap creation) occurs
 
	var loaderInfo:LoaderInfo = ev.target as LoaderInfo;
	var bitmap:Bitmap = loaderInfo.content as Bitmap;
	var bmd:BitmapData = bitmap.bitmapData;
 
	// This will (eventually) start step #2 (image decompression) and #4 (displaying it)
	addChild(bitmap);
}

This isn’t a very detailed description of when the steps are occurring. Flash Player will decide to start the load at some point and will only dispatch the event at a given point in the next frame or later. All of our handling of the Bitmap and BitmapData didn’t manage to trigger steps #2 or #4. Only adding the Bitmap to the Stage will eventually trigger them, but when?

To find out, let’s get a very large image file and see if the addChild function immediately does the expensive work of #2- decompressing the image from PNG to a block of ARGB pixels. Here’s a test app:

package
{
	import flash.display.Bitmap;
	import flash.display.Loader;
	import flash.display.LoaderInfo;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.net.URLRequest;
	import flash.text.TextField;
	import flash.text.TextFieldAutoSize;
	import flash.utils.getTimer;
 
	public class PreloadingBitmaps extends Sprite
	{
		public function PreloadingBitmaps()
		{
			var loader:Loader = new Loader();
			loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoaded);
			loader.load(new URLRequest("Adobe_Flash_Professional_CS5_icon.png"));
		}
 
		private function onLoaded(ev:Event): void
		{
			var logger:TextField = new TextField();
			logger.autoSize = TextFieldAutoSize.LEFT;
			addChild(logger);
 
			var beforeTime:int;
			var afterTime:int;
 
			beforeTime = getTimer();
			addChild((ev.target as LoaderInfo).content as Bitmap);
			afterTime = getTimer();
			logger.appendText("Time: " + (afterTime-beforeTime) + "\n");
		}
	}
}

I ran this test in the following environment:

  • Flex SDK (MXMLC) 4.6.0.23201, compiling in release mode (no debugging or verbose stack traces)
  • Release version of Flash Player 11.5.31.5
  • 2.3 Ghz Intel Core i7
  • Mac OS X 10.8.2

And here are the results that were printed:

Time: 0

Well, decompressing a 1052×1063 image does not take zero milliseconds. This means that addChild isn’t doing the work, so it’s deferred to some unknown time later on for it to be done. When? If this work is expensive, as in the case of a lot of bitmaps being used or just a giant one like this, this could represent a framerate “skip” or “jitter” when the decompression actually happens.

It’d be a lot nicer if we could control exactly when the image decompression happens. This way we could make sure it happens at an advantageous time such as a loading screen. Well, it turns out that we can actually force Flash Player to decompress the image on-demand. One simple way of doing this is to simply try to get the color value of one of its pixels with getPixel. Here’s a test app to demonstrate that it works:

package
{
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.Loader;
	import flash.display.LoaderInfo;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.net.URLRequest;
	import flash.text.TextField;
	import flash.text.TextFieldAutoSize;
	import flash.utils.getTimer;
 
	public class PreloadingBitmaps extends Sprite
	{
		public function PreloadingBitmaps()
		{
			var loader:Loader = new Loader();
			loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoaded);
			loader.load(new URLRequest("Adobe_Flash_Professional_CS5_icon.png"));
		}
 
		private function onLoaded(ev:Event): void
		{
			var logger:TextField = new TextField();
			logger.autoSize = TextFieldAutoSize.LEFT;
			addChild(logger);
 
			var bmd:BitmapData = ((ev.target as LoaderInfo).content as Bitmap).bitmapData;
 
			var beforeTime:int;
			var afterTime:int;
 
			beforeTime = getTimer();
			bmd.getPixel(0, 0);
			afterTime = getTimer();
			logger.appendText("First access: " + (afterTime-beforeTime) + "\n");
 
			beforeTime = getTimer();
			bmd.getPixel(0, 0);
			afterTime = getTimer();
			logger.appendText("Second access: " + (afterTime-beforeTime) + "\n");
		}
	}
}

In the same testing environment as above, this prints:

First access: 49
Second access: 0

The 49 milliseconds of the first access is definitely indicating that step #2 (image decompression) is occurring during the first getPixel call. Since the image decompression is already done by the first getPixel call, the second call has no image decompression to do and completes in under one millisecond.

With this simple tool in hand we can now force the image decompression to occur when we want it to. It’s now possible to move the decompression so that it happens during a loading screen or some other convenient time instead of delaying it to the critical first frames of the game, simulation, movie or whatever the app is displaying for the user.

Spot any bugs? Have any questions? Post a comment!