Are you using the fastest assets you can? Yes, even the file format of the assets you use has a big bearing on the performance of your app. Ask yourself: is PNG faster to decompress than JPEG? Is it faster to compress to JPEG-XR or PNG? Do the quality settings matter? Today’s article explores the performance of Flash’s main three image formats—PNG, JPEG, and JPEG-XR—to find out which decompresses fastest at load time and compresses fastest at save time.

The following two classes form the test app to explore compression and decompression performance. I’m forcing the image to decompress using the trick from Preloading Bitmap Decompression.

package
{
	import flash.geom.Rectangle;
	import flash.utils.getTimer;
	import flash.display.Bitmap;
	import flash.display.LoaderInfo;
	import flash.display.Loader;
	import flash.net.URLRequest;
	import flash.events.Event;
	import flash.net.URLLoaderDataFormat;
	import flash.net.URLLoader;
	import flash.display.BitmapData;
	import flash.utils.ByteArray;
 
	public class Test
	{
		private static const NUM_BITMAPS:int = 10;
 
		private var bmdBytes:ByteArray;
		private var bmds:Vector.<BitmapData> = new Vector.<BitmapData>();
		private var url:String;
		private var compressor:Object;
		private var label:String;
		private var callback:Function;
 
		public function Test(url:String, compressor:Object, label:String)
		{
			this.url = url;
			this.compressor = compressor;
			this.label = label;
		}
 
		public function start(callback:Function): void
		{
			this.callback = callback;
 
			var loader:URLLoader = new URLLoader();
			loader.dataFormat = URLLoaderDataFormat.BINARY;
			loader.addEventListener(Event.COMPLETE, onBytesLoaded);
			loader.load(new URLRequest(url));
		}
 
		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;
			bmds.push(bmd);
			if (bmds.length < NUM_BITMAPS)
			{
				loadBitmap();
				return;
			}
 
			var beforeTime:int;
			var afterTime:int;
			var i:int;
			var decompressTime:int;
			var compressTime:int;
 
			// Check decompression time
			beforeTime = getTimer();
			for (i = 0; i < NUM_BITMAPS; ++i)
			{
				bmds[i].getPixel(0, 0);
			}
			afterTime = getTimer();
			decompressTime = afterTime - beforeTime;
 
			// Replace each bitmap with a clone to dump compressed file data
			for (i = 0; i < NUM_BITMAPS; ++i)
			{
				bmds[i] = bmds[0].clone();
			}
 
			// Check compression time
			var rect:Rectangle = new Rectangle(0, 0, bmd.width, bmd.height);
			beforeTime = getTimer();
			for (i = 0; i < NUM_BITMAPS; ++i)
			{
				bmds[i].encode(rect, compressor);
			}
			afterTime = getTimer();
			compressTime = afterTime - beforeTime;
			callback(label, decompressTime, compressTime);
		}
	}
}
package
{
	import flash.display.JPEGEncoderOptions;
	import flash.display.JPEGXREncoderOptions;
	import flash.display.PNGEncoderOptions;
	import flash.display.Sprite;
	import flash.text.TextField;
	import flash.text.TextFieldAutoSize;
 
	public class CompressDecompressPerformance extends Sprite
	{
		private static const FILENAME:String = "Adobe_Flash_Professional_CS5_icon.";
		private var logger:TextField;
		private var tests:Vector.<Test> = new <Test>[
			new Test(FILENAME + "png", new PNGEncoderOptions(false), "PNG"),
			new Test(FILENAME + "png", new PNGEncoderOptions(true), "PNG (fastCompression)"),
			new Test(FILENAME + "jpg", new JPEGEncoderOptions(1), "JPEG (q=1)"),
			new Test(FILENAME + "jpg", new JPEGEncoderOptions(50), "JPEG (q=50)"),
			new Test(FILENAME + "jpg", new JPEGEncoderOptions(100), "JPEG (q=100)"),
			new Test(FILENAME + "jxr", new JPEGXREncoderOptions(1), "JPEG-XR (q=1)"),
			new Test(FILENAME + "jxr", new JPEGXREncoderOptions(50), "JPEG-XR (q=50)"),
			new Test(FILENAME + "jxr", new JPEGXREncoderOptions(100), "JPEG-XR (q=100)")
		];
 
		public function CompressDecompressPerformance()
		{
			logger = new TextField();
			logger.autoSize = TextFieldAutoSize.LEFT;
			logger.text = "Test,Decompress Time,Compress Time\n";
			addChild(logger);
 
			startNextTest();
		}
 
		private function startNextTest(): void
		{
			if (tests.length == 0)
			{
				return;
			}
 
			tests.shift().start(onTestDone);
		}
 
		private function onTestDone(label:String, decompressTime:int, compressTime:int): void
		{
			logger.appendText(label + "," + decompressTime + "," + compressTime + "\n");
			startNextTest();
		}
	}
}

(test PNG, test JPEG, test JPEG-XR)

Launch the test app

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.502.146
  • 2.3 Ghz Intel Core i7
  • Mac OS X 10.8.2

And here are the results I got:

Test Decompress Time Compress Time
PNG 496 5025
PNG (fastCompression) 496 246
JPEG (q=1) 147 224
JPEG (q=50) 147 225
JPEG (q=100) 147 239
JPEG-XR (q=1) 655 1276
JPEG-XR (q=50) 655 1050
JPEG-XR (q=100) 655 1042

Compression & Decompression Times

Decompression Times

Compression Times

Compression Times (no PNG)

The results vary wildly depending on file format and compression settings. Here are some takeaways:

  • JPEG is the fastest format to load/decompress. It’s over three times as fast as PNG and over four times faster than JPEG-XR
  • Using the fastCompression flag in PNGEncoderOptions speeds up PNG compression by 20x! You’d better have a lot of time on your hands if you’re not setting that to true.
  • Increasing the quality setting in JPEGEncoderOptions marginally slows down compression. It’s about 6% slower to compress at 100% quality compared to 1% quality.
  • Increasing the quantization/quality setting in JPEGXREncoderOptions marginally speeds up compression. It’s about 22% faster to compress at 100% quantization compared to 1% quantization.
  • Compressing PNG (with fastCompression) and JPEG take about the same amount of time. Compressing JPEG-XR takes 5x longer.
  • Decompressing a JPEG-XR or PNG image is actually slower than compressing a JPEG or PNG image (as long as you’re using fastCompression to encode your PNG)

If you can get away with it, the optimum performance would come from loading JPEG images and compressing to either JPEG or PNG with fastCompression. The worst would be loading JPEG-XR and saving PNG without fastCompression. Keep this in mind when you’re designing your app’s assets- there are performance tradeoffs to consider.

Spot a bug? Have a suggestion or question? Getting really different results? Post a comment!