Flash Player has had built-in PNG compression since version 11.3. But how does it fare against all of the other PNG compressors out there? Does it compress faster? Does it produce smaller file sizes? Today’s article explores your options when it comes to compressing PNG files so you can get the fastest or smallest PNG possible.

Here are the contenders:

The following test app simply loads up a big PNG image and compresses it with all of these options. The compression time and PNG file size are recorded.

package
{
	import com.adobe.images.PNGEncoder;
	import flash.utils.ByteArray;
	import by.blooddy.crypto.image.PNG24Encoder;
	import by.blooddy.crypto.image.PNGFilter;
	import flash.display.PNGEncoderOptions;
	import flash.geom.Rectangle;
	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.net.URLRequest;
	import flash.text.TextField;
	import flash.text.TextFieldAutoSize;
 
	public class CompressPerformance extends Sprite
	{
		public function CompressPerformance()
		{
			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 tf:TextField = new TextField();
			tf.autoSize = TextFieldAutoSize.LEFT;
			tf.text = "Compressor,Time,Size\n";
			addChild(tf);
 
			var bmd:BitmapData = ((ev.target as LoaderInfo).content as Bitmap).bitmapData;
			var beforeTime:int;
			var afterTime:int;
			var time:int;
			var rect:Rectangle = new Rectangle(0, 0, bmd.width, bmd.height);
			var bytes:ByteArray;
 
			beforeTime = getTimer();
			bytes = bmd.encode(rect, new PNGEncoderOptions(false));
			afterTime = getTimer();
			time = afterTime - beforeTime;
			tf.appendText("BitmapData.encode (fast=false)," + time + "," + bytes.length + "\n");
 
			beforeTime = getTimer();
			bytes = bmd.encode(rect, new PNGEncoderOptions(true));
			afterTime = getTimer();
			time = afterTime - beforeTime;
			tf.appendText("BitmapData.encode (fast=true)," + time + "," + bytes.length + "\n");
 
			beforeTime = getTimer();
			bytes = PNG24Encoder.encode(bmd, PNGFilter.NONE);
			afterTime = getTimer();
			time = afterTime - beforeTime;
			tf.appendText("Bloddy (filter=none)," + time + "," + bytes.length + "\n");
 
			beforeTime = getTimer();
			bytes = PNG24Encoder.encode(bmd, PNGFilter.SUB);
			afterTime = getTimer();
			time = afterTime - beforeTime;
			tf.appendText("Bloddy (filter=sub)," + time + "," + bytes.length + "\n");
 
			beforeTime = getTimer();
			bytes = PNG24Encoder.encode(bmd, PNGFilter.UP);
			afterTime = getTimer();
			time = afterTime - beforeTime;
			tf.appendText("Bloddy (filter=up)," + time + "," + bytes.length + "\n");
 
			beforeTime = getTimer();
			bytes = PNG24Encoder.encode(bmd, PNGFilter.AVERAGE);
			afterTime = getTimer();
			time = afterTime - beforeTime;
			tf.appendText("Bloddy (filter=average)," + time + "," + bytes.length + "\n");
 
			beforeTime = getTimer();
			bytes = PNG24Encoder.encode(bmd, PNGFilter.PAETH);
			afterTime = getTimer();
			time = afterTime - beforeTime;
			tf.appendText("Bloddy (filter=paeth)," + time + "," + bytes.length + "\n");
 
			beforeTime = getTimer();
			bytes = PNGEncoder2.encode(bmd);
			afterTime = getTimer();
			time = afterTime - beforeTime;
			tf.appendText("cameron314," + time + "," + bytes.length + "\n");
 
			beforeTime = getTimer();
			bytes = PNGEncoder.encode(bmd);
			afterTime = getTimer();
			time = afterTime - beforeTime;
			tf.appendText("as3corelib," + time + "," + bytes.length + "\n");
 
		}
	}
}

Launch the app
Download the 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:

Compressor Time Size
BitmapData.encode (fast=false) 568 71969
BitmapData.encode (fast=true) 25 138700
Bloddy (filter=none) 118 61149
Bloddy (filter=sub) 391 70242
Bloddy (filter=up) 697 73608
Bloddy (filter=average) 384 75916
Bloddy (filter=paeth) 480 72217
cameron314 117 606841
as3corelib 206 61092

PNG Compression Performance Graph

PNG Compression Size Graph

PNG Compression Size (no cameron314)

This data shows that the unquestioned performance champion is BitmapData.encode when you use the fastCompression option. Unfortunately, this produces a PNG that’s about twice the size of all the other compressors except the hugely-bloated output from cameron314. If this is too big of a file for you to store or your users to upload (e.g. on a slow 3G connection), the other compressors become a viable option. In particular, bloddy’s compressor produces an exceptionally small PNG in an exceptionally short amount of time, so long as you don’t turn on any of the filtering options. The cameron314 compressor is just as fast, but the file size produced is 10x bigger. The as3corelib compressor forms a sort of middle ground with half the speed but a file size that is the best by a slim margin.

This allows for a simple conclusion. If you don’t care about file size, use BitmapData.encode with fastCompression turned on. If you want to cut the file size in half or you can’t target Flash Player 11.3+, use the bloddy crypto compressor with no filtering options.

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