PNG, JPEG, and JPEG-XR Compression and Decompression Performance
Are you using the fastest assets you can? Yes, even the file format of the assets you use has a big bearing on the performance of your app. Ask yourself: is PNG faster to decompress than JPEG? Is it faster to compress to JPEG-XR or PNG? Do the quality settings matter? Today’s article explores the performance of Flash’s main three image formats—PNG, JPEG, and JPEG-XR—to find out which decompresses fastest at load time and compresses fastest at save time.
The following two classes form the test app to explore compression and decompression performance. I’m forcing the image to decompress using the trick from Preloading Bitmap Decompression.
package { import flash.geom.Rectangle; 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 compressor:Object; private var label:String; private var callback:Function; public function Test(url:String, compressor:Object, label:String) { this.url = url; this.compressor = compressor; 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; var compressTime:int; // Check decompression time beforeTime = getTimer(); for (i = 0; i < NUM_BITMAPS; ++i) { bmds[i].getPixel(0, 0); } afterTime = getTimer(); decompressTime = afterTime - beforeTime; // Replace each bitmap with a clone to dump compressed file data for (i = 0; i < NUM_BITMAPS; ++i) { bmds[i] = bmds[0].clone(); } // Check compression time var rect:Rectangle = new Rectangle(0, 0, bmd.width, bmd.height); beforeTime = getTimer(); for (i = 0; i < NUM_BITMAPS; ++i) { bmds[i].encode(rect, compressor); } afterTime = getTimer(); compressTime = afterTime - beforeTime; callback(label, decompressTime, compressTime); } } }
package { import flash.display.JPEGEncoderOptions; import flash.display.JPEGXREncoderOptions; import flash.display.PNGEncoderOptions; import flash.display.Sprite; import flash.text.TextField; import flash.text.TextFieldAutoSize; public class CompressDecompressPerformance 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", new PNGEncoderOptions(false), "PNG"), new Test(FILENAME + "png", new PNGEncoderOptions(true), "PNG (fastCompression)"), new Test(FILENAME + "jpg", new JPEGEncoderOptions(1), "JPEG (q=1)"), new Test(FILENAME + "jpg", new JPEGEncoderOptions(50), "JPEG (q=50)"), new Test(FILENAME + "jpg", new JPEGEncoderOptions(100), "JPEG (q=100)"), new Test(FILENAME + "jxr", new JPEGXREncoderOptions(1), "JPEG-XR (q=1)"), new Test(FILENAME + "jxr", new JPEGXREncoderOptions(50), "JPEG-XR (q=50)"), new Test(FILENAME + "jxr", new JPEGXREncoderOptions(100), "JPEG-XR (q=100)") ]; public function CompressDecompressPerformance() { logger = new TextField(); logger.autoSize = TextFieldAutoSize.LEFT; logger.text = "Test,Decompress Time,Compress Time\n"; addChild(logger); startNextTest(); } private function startNextTest(): void { if (tests.length == 0) { return; } tests.shift().start(onTestDone); } private function onTestDone(label:String, decompressTime:int, compressTime:int): void { logger.appendText(label + "," + decompressTime + "," + compressTime + "\n"); startNextTest(); } } }
(test PNG, test JPEG, test JPEG-XR)
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 | Compress Time |
---|---|---|
PNG | 496 | 5025 |
PNG (fastCompression) | 496 | 246 |
JPEG (q=1) | 147 | 224 |
JPEG (q=50) | 147 | 225 |
JPEG (q=100) | 147 | 239 |
JPEG-XR (q=1) | 655 | 1276 |
JPEG-XR (q=50) | 655 | 1050 |
JPEG-XR (q=100) | 655 | 1042 |
The results vary wildly depending on file format and compression settings. Here are some takeaways:
- JPEG is the fastest format to load/decompress. It’s over three times as fast as PNG and over four times faster than JPEG-XR
- Using the
fastCompression
flag inPNGEncoderOptions
speeds up PNG compression by 20x! You’d better have a lot of time on your hands if you’re not setting that to true. - Increasing the
quality
setting inJPEGEncoderOptions
marginally slows down compression. It’s about 6% slower to compress at 100% quality compared to 1% quality. - Increasing the
quantization
/quality setting inJPEGXREncoderOptions
marginally speeds up compression. It’s about 22% faster to compress at 100% quantization compared to 1% quantization. - Compressing PNG (with
fastCompression
) and JPEG take about the same amount of time. Compressing JPEG-XR takes 5x longer. - Decompressing a JPEG-XR or PNG image is actually slower than compressing a JPEG or PNG image (as long as you’re using
fastCompression
to encode your PNG)
If you can get away with it, the optimum performance would come from loading JPEG images and compressing to either JPEG or PNG with fastCompression
. The worst would be loading JPEG-XR and saving PNG without fastCompression
. Keep this in mind when you’re designing your app’s assets- there are performance tradeoffs to consider.
Spot a bug? Have a suggestion or question? Getting really different results? Post a comment!
#1 by henke37 on February 4th, 2013 ·
So what about the various png settings? There are those filters, the various bit detpths, some compression options and likely more that I don’t even know of…
#2 by jackson on February 4th, 2013 ·
Unfortunately, the only option in PNGEncoderOptions is
fastCompression
. To get access to all of those settings you’d need to write a PNG encoder in AS3, which would undoubtedly be far slower. There’s one in as3corelib, but it has zero encoding options so it’s even less flexible.#3 by skyboy on February 4th, 2013 ·
This might be throwing the results off a bit:
Also, the trade off for the speed gain is that images are much larger in size; there’s a quick overview here: http://www.fastswf.com/j_1jm4M with more in depth compression settings (where available) explored here: http://www.fastswf.com/3OVvOgU
The result lines show the byte size of the compressed image and the time it took to compress it, respectively.
Most of those classes also have filter options, which could make a huge different in performance and compression depending on the image, and most definitely for the perlin noise used in those tests.
#4 by jackson on February 4th, 2013 ·
Thank you for pointing this out! A one character typo really distorted the numbers in this article. I’ve updated the code, tables, graphs, and conclusions with the fix. The biggest change was that now all of the bitmaps are being compressed so the decompression times went way up. This means the conclusion stating that compression is always slower than decompression was wrong. I replaced it with one saying that decompression of PNG and JPEG-XR is slower than compression of PNG and JPEG. It also widened the performance gap between decompressing PNG and JPEG-XR. Compared to JPEG, PNG is 3x slower and JPEG-XR is 4x slower. JPEG really is fast.
As for byte size, I did consider this briefly while writing the article. The PNG test image is one I found on the web but the JPEG and JPEG-XR versions were created with
BitmapData.encode
. Here are their file sizes:PNG: 68 KB
JPEG: 42 KB
JPEG-XR: 21 KB
The PNG is 50% larger than the JPEG, but it takes 3x longer to decompress and the same time to compress (with
fastCompression
). The JPEG-XR is half the size of the JPEG but it takes 4x longer to decompress and 10x longer to compress. At least with this particular image (a 1063×1052 version of the Flash Pro CS5 icon), I’m not seeing any correlation on the compression or decompression side. However, different images may make a big difference. It’s probably something worth following up on. Also, those other compression libraries might be good to throw into the mix since I didn’t really expect any of them to come close to the nativeBitmapData.encode
function and it seems that sometimes they do.#5 by skyboy on February 4th, 2013 ·
I’m not sure what impact the file size will have on decompression times, but on PNG it may have a fairly large impact since icons and other similar groups of images can be compressed very highly by using indexed colors in addition to standard compression (less so on photos, where jpeg excels on file size at ~80% quality); and while that kind of compression isn’t something readily available for AS3 it may make a huge difference for loaded assets.
There’s a lot to explore for performance impacts here because of how many different specs. have evolved for images; you may have enough material for several dozen articles.
I’m also intending to add a PNG encoder to my library, but i’m writing it from spec. rather than optimizing existing code as with the OptimizedPerlin class and it’s not a very high priority. Perhaps I’ll have something that can outperform the native non-fast compression with comparable file size like I managed with the JSON class.
#6 by jackson on February 4th, 2013 ·
You’re right that there is definitely a lot to test. Indexed PNG and JPEG quality are just a couple more. This may be the start of a long series of articles. :)
Since
PNGEncoderOptions
has only one boolean option in it and obviously there are way more possible options for saving (e.g. indexing), it would sure be nice to have a way to enable those. If you get an encoder up and running, please let me know.