PNG Compressor Roundup
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:
- 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
- as3corelib
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 |
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!
#1 by Philippe on February 18th, 2013 ·
Are the results similar with different kind of drawings?
#2 by jackson on February 18th, 2013 ·
This is a good question, but it’s hard to test since there are so many possible images. I’ll do a follow-up article to test some more types of images.
#3 by henke37 on February 18th, 2013 ·
Now, how well does the resulting images crush? You should compare against the output of pngcrush.
#4 by jackson on February 18th, 2013 ·
Good idea. I just got version 1.7.51 of pngcrush and crushed the test PNG with the “-brute” option to ensure the smallest possible file size. After running for 18.903 seconds it produced a PNG with 60,389 bytes. So bloddy crypto is adding about 1.26% to the file size compared to pngcrush and doing the job about 160x faster. Sounds pretty good to me!
#5 by skyboy on February 18th, 2013 ·
The performance and filesize of cameron314’s compressor varies pretty significantly based on what the compression level has been set to with this line:
The various levels being: FAST, NORMAL, GOOD, UNCOMPRESSED.
In my testing with the perlin noise, normal is the best option as good takes significantly longer with only minimal decreases in file size. The default is fast, if I recall the documentation correctly.
The bloddy.crypto package’s various options result in smaller file sizes on particular types of images, some best for photos, others for icons, some may even be good for static.
The most curious thing, however, is that the as3corelib package performs much better than the native code and produces a smaller file in a large swath of cases, despite Adobe employees writing both of these.
#6 by jackson on February 18th, 2013 ·
Thanks for pointing out the compression level setting on the cameron314 compressor; I hadn’t spotted it since it’s not a parameter of the function and the function documentation says nothing about a compression level. Here are the results I get in the same test environment for each of the four settings:
Surprisingly, the “normal” setting is the fastest despite providing far smaller file sizes than “uncompressed” and “fast”. That makes it the fastest version with the exception of
BitmapData.encode
withfastCompression
turned on. It’s unfortunate that the file size produced is almost as large though, so you probably wouldn’t use it since it’s slower for not much file size gain. If you’re going to slow down, why not slow down a tad more and cut the file size in half with bloddy crypto? However, it may still be useful if you need a Haxe implementation for something like NME or Neko VM on the server.It is quite strange that Adobe’s pure-AS3 as3corelib from 2008 can beat their native code implementation from 2012 in both performance and file size when
BitmapData.encode
hasfastCompression
turned off. It would be really interesting to find out why the native code version is so slow.#7 by Judah on May 26th, 2014 ·
Hi Jackson,
Have you tested mx.graphics.codec.JPEGEncoder or mx.graphics.codec.PNGEncoder classes? Also, do you have any articles on base 64 encoding performance? I’m using mx.utils.Base64Encoder and it’s the longest part of the process in my application where bitmap encoding takes 100-300 ms, base 64 can be up to 3- 4k ms (or 3 to 4 seconds).
#8 by jackson on May 26th, 2014 ·
I haven’t tested any of the
mx
encoders, but perhaps I’ll have a followup article on them some day.