Does the type of image matter when you’re compressing it to PNG? Does it affect performance? Size? This week’s article looks into these questions to find out how each of the PNG compressors performs on three different types of images: an icon, a photo, and random noise.

The PNG compression test has grown since last week’s article to include more compressor options on the cameron314 test:

  • BitmapData.encode (PNGEncoderOptions.fastCompression=false)
  • BitmapData.encode (PNGEncoderOptions.fastCompression=true)
  • Bloddy Crypto (filter=none)
  • Bloddy Crypto (filter=sub)
  • Bloddy Crypto (filter=up)
  • Bloddy Crypto (filter=average)
  • Bloddy Crypto (filter=paeth)
  • cameron314 (compression level=uncompressed)
  • cameron314 (compression level=fast)
  • cameron314 (compression level=normal)
  • cameron314 (compression level=good)
  • as3corelib

And three image types:

  • The Flash Pro CS5 icon from previous PNG test articles
  • A random photo with the same width and height as the icon
  • Noise generated by BitmapData.noise with the same width and height as the icon

Here’s the code for the test app:

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;
 
	public class CompressPerformance extends Sprite
	{
		private var tf:TextField = new TextField();
 
		public function CompressPerformance()
		{
			tf.width = stage.stageWidth;
			tf.height = stage.stageHeight;
			addChild(tf);
 
			load("Adobe_Flash_Professional_CS5_icon.png", onIconLoaded);
		}
 
		private function onIconLoaded(ev:Event): void
		{
			var bmd:BitmapData = ((ev.target as LoaderInfo).content as Bitmap).bitmapData;
			test("Icon", bmd);
 
			load("10082934002.jpg", onPhotoLoaded);
		}
 
		private function onPhotoLoaded(ev:Event): void
		{
			var bmd:BitmapData = ((ev.target as LoaderInfo).content as Bitmap).bitmapData;
			test("Photo", bmd);
 
			load("Adobe_Flash_Professional_CS5_icon.png", onIconLoadedAgain);
		}
 
		private function onIconLoadedAgain(ev:Event): void
		{
			var bmd:BitmapData = ((ev.target as LoaderInfo).content as Bitmap).bitmapData;
			bmd.noise(Math.random()*int.MAX_VALUE);
 
			test("Noise", bmd);
		}
 
		private function row(...cols): void
		{
			tf.appendText(cols.join(",")+"\n");
		}
 
		private function load(url:String, callback:Function): void
		{
			var loader:Loader = new Loader();
			loader.contentLoaderInfo.addEventListener(Event.COMPLETE, callback);
			loader.load(new URLRequest(url));
		}
 
		private function test(title:String, bmd:BitmapData): void
		{
			var beforeTime:int;
			var afterTime:int;
			var time:int;
			var rect:Rectangle = new Rectangle(0, 0, bmd.width, bmd.height);
			var bytes:ByteArray;
 
			row(title);
			row("Compressor,Time,Size");
			bmd.getPixel(0, 0);
 
			beforeTime = getTimer();
			bytes = bmd.encode(rect, new PNGEncoderOptions(false));
			afterTime = getTimer();
			time = afterTime - beforeTime;
			row("BitmapData.encode (fast=false)", time, bytes.length);
 
			beforeTime = getTimer();
			bytes = bmd.encode(rect, new PNGEncoderOptions(true));
			afterTime = getTimer();
			time = afterTime - beforeTime;
			row("BitmapData.encode (fast=true)", time, bytes.length);
 
			beforeTime = getTimer();
			bytes = PNG24Encoder.encode(bmd, PNGFilter.NONE);
			afterTime = getTimer();
			time = afterTime - beforeTime;
			row("Bloddy (filter=none)", time, bytes.length);
 
			beforeTime = getTimer();
			bytes = PNG24Encoder.encode(bmd, PNGFilter.SUB);
			afterTime = getTimer();
			time = afterTime - beforeTime;
			row("Bloddy (filter=sub)", time, bytes.length);
 
			beforeTime = getTimer();
			bytes = PNG24Encoder.encode(bmd, PNGFilter.UP);
			afterTime = getTimer();
			time = afterTime - beforeTime;
			row("Bloddy (filter=up)", time, bytes.length);
 
			beforeTime = getTimer();
			bytes = PNG24Encoder.encode(bmd, PNGFilter.AVERAGE);
			afterTime = getTimer();
			time = afterTime - beforeTime;
			row("Bloddy (filter=average)", time, bytes.length);
 
			beforeTime = getTimer();
			bytes = PNG24Encoder.encode(bmd, PNGFilter.PAETH);
			afterTime = getTimer();
			time = afterTime - beforeTime;
			row("Bloddy (filter=paeth)", time, bytes.length);
 
			PNGEncoder2.level = CompressionLevel.UNCOMPRESSED;
			beforeTime = getTimer();
			bytes = PNGEncoder2.encode(bmd);
			afterTime = getTimer();
			time = afterTime - beforeTime;
			row("cameron314 (uncompressed)", time, bytes.length);
 
			PNGEncoder2.level = CompressionLevel.FAST;
			beforeTime = getTimer();
			bytes = PNGEncoder2.encode(bmd);
			afterTime = getTimer();
			time = afterTime - beforeTime;
			row("cameron314 (fast)", time, bytes.length);
 
			PNGEncoder2.level = CompressionLevel.NORMAL;
			beforeTime = getTimer();
			bytes = PNGEncoder2.encode(bmd);
			afterTime = getTimer();
			time = afterTime - beforeTime;
			row("cameron314 (normal)", time, bytes.length);
 
			PNGEncoder2.level = CompressionLevel.GOOD;
			beforeTime = getTimer();
			bytes = PNGEncoder2.encode(bmd);
			afterTime = getTimer();
			time = afterTime - beforeTime;
			row("cameron314 (good)", time, bytes.length);
 
			beforeTime = getTimer();
			bytes = PNGEncoder.encode(bmd);
			afterTime = getTimer();
			time = afterTime - beforeTime;
			row("as3corelib", time, bytes.length);
 
			row();
		}
	}
}

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

And here are the results I got:

Icon
Compressor Time Size
BitmapData.encode (fast=false) 631 71969
BitmapData.encode (fast=true) 37 138700
Bloddy (filter=none) 134 61149
Bloddy (filter=sub) 462 70242
Bloddy (filter=up) 868 73608
Bloddy (filter=average) 460 75916
Bloddy (filter=paeth) 582 72217
cameron314 (uncompressed) 96 4474569
cameron314 (fast) 87 606841
cameron314 (normal) 69 115264
cameron314 (good) 149 105291
as3corelib 224 61092

PNG Compression Performance (icon)
PNG Compression Size (icon)
PNG Compression Size (icon, small only)

Photo
Compressor Time Size
BitmapData.encode (fast=false) 1411 1581023
BitmapData.encode (fast=true) 123 2598418
Bloddy (filter=none) 195 2604346
Bloddy (filter=sub) 1129 1646064
Bloddy (filter=up) 674 1641705
Bloddy (filter=average) 822 1669681
Bloddy (filter=paeth) 1411 1584675
cameron314 (uncompressed) 49 3356208
cameron314 (fast) 66 1761713
cameron314 (normal) 123 1735160
cameron314 (good) 655 1606533
as3corelib 466 2833906

PNG Compression Performance (photo)
PNG Compression Size (photo)
PNG Compression Size (photo, small only)

Noise
Compressor Time Size
BitmapData.encode (fast=false) 191 3356458
BitmapData.encode (fast=true) 225 3875539
Bloddy (filter=none) 327 3837742
Bloddy (filter=sub) 349 3838815
Bloddy (filter=up) 354 3838150
Bloddy (filter=average) 357 3839126
Bloddy (filter=paeth) 413 3837929
cameron314 (uncompressed) 66 4474569
cameron314 (fast) 103 3833976
cameron314 (normal) 200 3827015
cameron314 (good) 1260 3827065
as3corelib 537 3837685

PNG Compression Performance (noise)
PNG Compression Size (noise)
PNG Compression Size (noise, small only)

Conclusion

The results for the icon are, of course, the same as in last week’s article. For the photo, bloddy crypto continues to be fastest when using no filter, cameron314 gets slower as you turn up the compression level, and as3corelib is somewhere in the middle. On the other hand, file size no longer has huge outliers. Instead, there’s a sort of two-tier system that makes it easy to choose a compressor: simply pick the fastest one from the small file size tier. In this case that compressor is cameron314 (fast).

For random noise, performance-wise the picture is quite different compared to the icon. BitmapData.encode with fastCompression off is actually faster than when it’s on. Bloddy crypto and cameron314 take longer as you turn up the filter and compression level, respectively, and as3corelib is just generally slow. File size is almost identical regardless of compressor except for uncompressed PNGs from cameron314, so you might as well choose the second-fastest: cameron314 (fast).

The icon image from the previous articles is a good example of an image that should be compressed as a PNG. Random noise and photos are poor choices for PNG regardless of the compressor, as you can see from the 10x increase in file size with the photo. If you’re going to compress any of these with PNG, your compressor choice should change to cameron314 (fast) since it seems to be very close to the fastest at both while producing among the smallest PNG files. That’s quite a turnaround for cameron314 (fast) because it’s file sizes are so bad in the icon test that it’s not worth using.

In summary, for images that lend themselves to PNG, use bloddy crypto with no filter. For all other images, use cameron314 with the “fast” compression level.

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