PNG Compression Followup
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 |
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 |
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 |
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!
#1 by makc on February 25th, 2013
It’s blooddy, two “o” :)
#2 by jackson on February 25th, 2013
Whoops…
#3 by henke37 on February 25th, 2013
I think that the most important part is to quickly get the file output so that I can throw it as pngcrush afterwards.
#4 by makc on February 25th, 2013
Then it’s clearly “BitmapData.encode (fast=true)” for you.
#5 by makc on February 25th, 2013
wait… I kind of missed the part where cameron314 came first
#6 by jackson on February 25th, 2013
The fastest are BitmapData.encode (fast=true) for icons (i.e. images that lend themselves to PNG compression) and cameron314 (uncompressed) for photos and noise. However, you might want to take file size into account because the app is going to have to transmit the PNG file to the computer running pngcrush. For example, this may mean uploading a gigantic uncompressed PNG file. The combination of both CPU time to compress the PNG and the upload time might be a better overall measure of the quickness to pngcrush.
#7 by Davide on March 17th, 2013
What about Eugene of Inspirit.ru PNG encoder solution(s) ? (More info and classes at http://blog.inspirit.ru/?s=png+encoder and http://code.google.com/p/in-spirit/source/browse/trunk/projects/FlickrMosaic/src/ru/inspirit/utils/) ? You had chance to test them ?
#8 by jackson on March 18th, 2013
I haven’t tested the Inspirit PNG encoder, but I will check it out. Thanks for the tip!