As I discovered in the previous articles, loaded bitmaps are stored in memory in two forms: the compressed PNG, JPEG, JPEG-XR, GIF file and the uncompressed RGBA pixels. If you don’t use the pixels, Flash Player will reclaim its memory and then uncompress it if you use the bitmap later on. However, if you do plan to use the bitmap, isn’t the compressed file data just memory overhead? Today’s article will show you how to dump this unused file data and save a bunch of memory.

First, simply changing the loaded BitmapData is not enough to invalidate Flash’s cached file data. Even though you’ve changed some pixels of your BitmapData, Flash will still dump your changed pixel data if you don’t use it for a while. Then it’ll uncompress the original image file data when you use the BitmapData later on. This would seem to be an error or a bug in the Flash Player. If you know more about this process, please feel free to explain in the comments.

What’s needed is to actually create an entirely new BitmapData. This new object will not be loaded from an image file, either by URL or by a ByteArray. Instead, it’s simply a clone of the BitmapData you did load from an image file:

bmd = bmd.clone();

Now that’s a simple trick! Unfortunately, this one-liner is deceptive in its simplicity. The five lowly letters c-l-o-n-e result in a memory allocation large enough to fit all of the pixels of the bitmap, so Flash is temporarily using twice the memory necessary to store the bitmap. Also, Flash needs to copy the memory from one bitmap to another which may take a lot of time if you have a lot of pixels, either in one bitmap or many.

Now, let’s check out a test app to demonstrate the effectiveness of this trick. The test app loads an image file’s bytes and then loads 100 bitmaps from those bytes. Each bitmap is unique and taking up lots of memory. BitmapPreserver from the last article makes sure that their pixel data isn’t reclaimed by Flash Player. Observe the memory display and then click the button to unload the compressed file data for all of the bitmaps using the above trick. The image I’m using is about 60KB, so you should see a drop of about 6MB when you click that button. A debug version of Flash Player is required so that System.gc() can be run in order to ensure a timely result when you click the button. Have a look at the source code and then launch the app:

package
{
	import flash.utils.ByteArray;
	import flash.net.URLLoaderDataFormat;
	import flash.net.URLLoader;
	import flash.system.Capabilities;
	import flash.utils.getTimer;
	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.events.MouseEvent;
	import flash.net.URLRequest;
	import flash.system.System;
	import flash.text.TextField;
	import flash.text.TextFieldAutoSize;
	import flash.text.TextFormat;
 
	public class UnloadingCompressedBitmaps extends Sprite
	{
		private static const NUM_BITMAPS:int = 100;
 
		private var logger:TextField;
		private var bmdBytes:ByteArray;
		private var bmds:Vector.<BitmapData>;
		private var bmdCreateTime:int;
 
		public function UnloadingCompressedBitmaps()
		{
			logger = new TextField();
			logger.autoSize = TextFieldAutoSize.LEFT;
			addChild(logger);
 
			if (!Capabilities.isDebugger)
			{
				logger.text = "This app is designed to run in a debug version of Flash Player";
				return;
			}
 
			logger.text = "Loading bitmaps...";
			bmds = new Vector.<BitmapData>();
 
			var loader:URLLoader = new URLLoader();
			loader.dataFormat = URLLoaderDataFormat.BINARY;
			loader.addEventListener(Event.COMPLETE, onBytesLoaded);
			loader.load(new URLRequest("Adobe_Flash_Professional_CS5_icon.png"));
		}
 
		private function onBytesLoaded(ev:Event): void
		{
			bmdBytes = (ev.target as URLLoader).data;
			loadBitmap();
		}
 
		private function loadBitmap(): void
		{
			var loader:Loader = new Loader();
			loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoaded);
			loader.loadBytes(bmdBytes);
		}
 
		private function onLoaded(ev:Event): void
		{
			var bmd:BitmapData = ((ev.target as LoaderInfo).content as Bitmap).bitmapData;
			BitmapPreserver.addBitmap(bmd);
			bmds.push(bmd);
			if (bmds.length == NUM_BITMAPS)
			{
				bmdCreateTime = getTimer();
 
				var tf:TextField = new TextField();
				tf.mouseEnabled = false;
				tf.selectable = false;
				tf.defaultTextFormat = new TextFormat("_sans");
				tf.autoSize = TextFieldAutoSize.LEFT;
				tf.text = "Unload Compressed Bitmap Memory";
 
				var button:Sprite = new Sprite();
				button.buttonMode = true;
				button.graphics.beginFill(0xF5F5F5);
				button.graphics.drawRect(0, 0, tf.width+3, tf.height+3);
				button.graphics.endFill();
				button.graphics.lineStyle(1);
				button.graphics.drawRect(0, 0, tf.width+3, tf.height+3);
				button.addChild(tf);
				button.addEventListener(MouseEvent.CLICK, onUnload);
				button.y = height;
				addChild(button);
 
				addEventListener(Event.ENTER_FRAME, onEnterFrame);
			}
			else
			{
				loadBitmap();
			}
		}
 
		private function onEnterFrame(ev:Event): void
		{
			System.gc();
			logger.text = "System.totalMemory: " + System.totalMemory
				+ " (" + (getTimer()-bmdCreateTime) + " ms. since bitmap created)";
		}
 
		private function onUnload(ev:Event): void
		{
			removeChild(ev.target as Sprite);
 
			BitmapPreserver.removeAllBitmaps();
			for (var i:int; i < NUM_BITMAPS; ++i)
			{
				bmds[i] = bmds[i].clone();
				BitmapPreserver.addBitmap(bmds[i]);
			}
			bmdCreateTime = getTimer();
		}
	}
}

Launch the test app (in a debug player)

Obviously, there is a downside to using this technique. Flash Player is no longer able to reclaim the memory used to store the pixels of BitmapData objects that it thinks you’re not using. This is bad if you’re really not actually using the bitmaps and can tolerate the CPU hit when the bitmaps get re-uncompressed from the file data. But if you want maximum control, try out this trick and save a boatload of memory in your bitmap-intensive apps!

Spot a bug? Have a question or comment? Can you explain why Flash reclaims changed BitmapData pixels? Post a comment!