Which image format is fastest to load? That was perhaps the most relevant question in last week’s article, so it’s time to explore it more deeply. Today’s article examines differences between different types of PNG, JPEG, and JPEG-XR files to answer questions like “does the JPEG quality setting matter?” and “is indexed PNG faster than full (ARGB) PNG?” Read on for the test and all the details.

To build today’s test, I started with the test code from last week’s article and modified it to remove the compression code and add more image types which I created using GIMP for PNG and BitmapData.encode for JPEG and JPEG-XR. I also added reporting of the image file size and the rate at which it was decompressed (in bytes/sec). Here’s the code:

package
{
	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 label:String;
		private var callback:Function;
 
		public function Test(url:String, label:String)
		{
			this.url = url;
			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;
 
			// Check decompression time
			beforeTime = getTimer();
			for (i = 0; i < NUM_BITMAPS; ++i)
			{
				bmds[i].getPixel(0, 0);
			}
			afterTime = getTimer();
			decompressTime = afterTime - beforeTime;
 
			callback(label, decompressTime, bmdBytes.length);
		}
	}
}
package
{
	import flash.display.Sprite;
	import flash.text.TextField;
	import flash.text.TextFieldAutoSize;
 
	public class DecompressPerformance 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", "PNG (ARGB)"),
			new Test(FILENAME + "_grey.png", "PNG (greyscale)"),
			new Test(FILENAME + "_indexed.png", "PNG (indexed)"),
			new Test(FILENAME + "_q1.jpg", "JPEG (q=1)"),
			new Test(FILENAME + "_q50.jpg", "JPEG (q=50)"),
			new Test(FILENAME + "_q100.jpg", "JPEG (q=100)"),
			new Test(FILENAME + "_q1.jxr", "JPEG-XR (q=1)"),
			new Test(FILENAME + "_q50.jxr", "JPEG-XR (q=50)"),
			new Test(FILENAME + "_q100.jxr", "JPEG-XR (q=100)")
		];
 
		public function DecompressPerformance()
		{
			logger = new TextField();
			logger.autoSize = TextFieldAutoSize.LEFT;
			logger.text = "Test,Decompress Time,File Size,Bytes/Sec\n";
			addChild(logger);
 
			startNextTest();
		}
 
		private function startNextTest(): void
		{
			if (tests.length == 0)
			{
				return;
			}
 
			tests.shift().start(onTestDone);
		}
 
		private function onTestDone(label:String, decompressTime:int, fileSize:uint): void
		{
			var rate:int = fileSize / (decompressTime/1000.0);
			logger.appendText(label + "," + decompressTime + "," + fileSize + "," + rate + "\n");
			startNextTest();
		}
	}
}

Download the test app

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 File Size Bytes/Sec
PNG (ARGB) 499 68012 136296
PNG (greyscale) 316 31386 99322
PNG (indexed) 189 25782 136412
JPEG (q=1) 143 18658 130475
JPEG (q=50) 146 31188 213616
JPEG (q=100) 167 126111 755155
JPEG-XR (q=1) 873 228039 261213
JPEG-XR (q=50) 644 10583 16433
JPEG-XR (q=100) 632 4583 7251

Decompression Times

File Sizes

Decompression Rates (PNG)

Decompression Rates (JPEG)

Decompression Rates (JPEG-XR)

Takeaways…

  • Last week’s article called PNG 3x slower to load than JPEG. However, that’s only true for ARGB (full-color) PNG. Greyscale PNG is 40% quicker and indexed is takes less than half the time of ARGB PNG.
  • The reason greyscale and indexed PNG are faster is due to their file size. Take a look at the decompression rates for PNG and you’ll see little difference between ARGB and Indexed.
  • Increasing JPEG quality increases the load time slightly, but the rate of a 100% quality JPEG is 8x faster than a 1% quality JPEG. This would seem to indicate a huge fixed cost (overhead) to decompressing JPEG files.
  • JPEG-XR is affected just like JPEG-XR, but the terminology is backward. Increasing the quality of a JPEG increases its file size and image quality. Increasing the quantization of a JPEG-XR decreases both of these.
  • JPEG-XR speeds up a massive 22x between a quantization of 0 and a quantization of 50 but only about 2x between 50 and 100. Still, adding quality with JPEG-XR is a lot more costly than with JPEG.

As usual, you’ll have to decide for yourself how your assets should be compressed. Which format? Which compression options? The above results can only show you the load-time performance impact of your decision. You’ll need to balance that against image quality and file size, but it’s definitely worth considering the load-time performance since it can be quite costly: a 6x slowdown between JPEG at 1% quality and JPEG-XR at 1% quantization. Even on my relatively-fast quad core CPU that’s a 70 millisecond difference, 4 whole frames at 60 FPS. How long would it be on a slower CPU like a mobile device?

Spot a bug? Got a question, comment, or suggestion? Post a comment!