JPEG Compressor Roundup
Now that we’ve determined the best PNG compressors to create PNG images with, let’s delve into the world of JPEG compressors. As with PNG, we have multiple options to choose from in our Flash apps when we’re looking to encode images such as screenshots. Which is best? Today’s article delves into each compressor’s performance and file size efficiency.
Here are the contestants in today’s roundup, each with three JPEG quality settings in the normal range:
- BitmapData.encode (JPEGEncoderOptions.quality=25)
- BitmapData.encode (JPEGEncoderOptions.quality=50)
- BitmapData.encode (JPEGEncoderOptions.quality=75)
- Bloddy Crypto (quality=25)
- Bloddy Crypto (quality=50)
- Bloddy Crypto (quality=75)
- as3corelib (quality=25)
- as3corelib (quality=50)
- as3corelib (quality=75)
Each of these was used to compress three types of images:
- 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 by.blooddy.crypto.image.JPEGEncoder; import flash.display.JPEGEncoderOptions; import com.adobe.images.JPGEncoder; import flash.utils.ByteArray; 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 CompressJPEGPerformance extends Sprite { private var tf:TextField = new TextField(); public function CompressJPEGPerformance() { 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"); tf.scrollV = tf.maxScrollV; } 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 JPEGEncoderOptions(25)); afterTime = getTimer(); time = afterTime - beforeTime; row("BitmapData.encode (q=25)", time, bytes.length); beforeTime = getTimer(); bytes = bmd.encode(rect, new JPEGEncoderOptions(50)); afterTime = getTimer(); time = afterTime - beforeTime; row("BitmapData.encode (q=50)", time, bytes.length); beforeTime = getTimer(); bytes = bmd.encode(rect, new JPEGEncoderOptions(75)); afterTime = getTimer(); time = afterTime - beforeTime; row("BitmapData.encode (q=75)", time, bytes.length); beforeTime = getTimer(); bytes = JPEGEncoder.encode(bmd, 25); afterTime = getTimer(); time = afterTime - beforeTime; row("Blooddy (q=25)", time, bytes.length); beforeTime = getTimer(); bytes = JPEGEncoder.encode(bmd, 50); afterTime = getTimer(); time = afterTime - beforeTime; row("Blooddy (q=50)", time, bytes.length); beforeTime = getTimer(); bytes = JPEGEncoder.encode(bmd, 75); afterTime = getTimer(); time = afterTime - beforeTime; row("Blooddy (q=75)", time, bytes.length); beforeTime = getTimer(); bytes = new JPGEncoder(25).encode(bmd); afterTime = getTimer(); time = afterTime - beforeTime; row("as3corelib (q=25)", time, bytes.length); beforeTime = getTimer(); bytes = new JPGEncoder(50).encode(bmd); afterTime = getTimer(); time = afterTime - beforeTime; row("as3corelib (q=50)", time, bytes.length); beforeTime = getTimer(); bytes = new JPGEncoder(75).encode(bmd); afterTime = getTimer(); time = afterTime - beforeTime; row("as3corelib (q=75)", 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:
Compressor | Time | Size |
---|---|---|
BitmapData.encode (q=25) | 23 | 26588 |
BitmapData.encode (q=50) | 24 | 31188 |
BitmapData.encode (q=75) | 24 | 38327 |
Blooddy (q=25) | 83 | 40613 |
Blooddy (q=50) | 83 | 46948 |
Blooddy (q=75) | 84 | 56018 |
as3corelib (q=25) | 640 | 40541 |
as3corelib (q=50) | 649 | 46876 |
as3corelib (q=75) | 651 | 55946 |
Compressor | Time | Size |
---|---|---|
BitmapData.encode (q=25) | 31 | 65996 |
BitmapData.encode (q=50) | 35 | 129991 |
BitmapData.encode (q=75) | 35 | 159547 |
Blooddy (q=25) | 96 | 86675 |
Blooddy (q=50) | 107 | 155782 |
Blooddy (q=75) | 108 | 199564 |
as3corelib (q=25) | 734 | 86603 |
as3corelib (q=50) | 766 | 155724 |
as3corelib (q=75) | 769 | 199493 |
Compressor | Time | Size |
---|---|---|
BitmapData.encode (q=25) | 44 | 272445 |
BitmapData.encode (q=50) | 53 | 462318 |
BitmapData.encode (q=75) | 63 | 676571 |
Blooddy (q=25) | 141 | 507160 |
Blooddy (q=50) | 180 | 956231 |
Blooddy (q=75) | 219 | 1407526 |
as3corelib (q=25) | 860 | 507088 |
as3corelib (q=50) | 1005 | 956159 |
as3corelib (q=75) | 1128 | 1407454 |
In stark contrast to the complicated findings for PNG compressors, it’s easy to draw conclusions about the JPEG compressor options. The fastest compressor for all types of images, by a margin of about 3-4x, is BitmapData.encode
. The smallest file sizes are also achieved with BitmapData.encode
. So if you’re targeting Flash Player 11.3+, just use BitmapData.encode
. If you aren’t then you won’t have that option available to you. In that case, choose Blooddy crypto since it is takes the #2 spot for both performance and size on all images. The JPEG compressor in as3corelib is the slowest and produces the largest file sizes, so there really isn’t any reason to use it.
Spot a bug? Have a question or suggestion? Post a comment!
#1 by Mario on March 5th, 2013 ·
Yeah, I like clear results. For AIR (mobile) go with BitmapData (period). :-)
Thank you for posting all your research on performance!
#2 by Gene on March 6th, 2013 ·
Use the same algorithm can not be got a different size, so the quality use a different definition.
#3 by jackson on March 7th, 2013 ·
There are lots of options when compressing to JPEG which can contribute to file size. For more on the format and the various ways you can build a JPEG file, check out the Wikipedia entry for an overview. In short, each JPEG compressor has lots of leeway to change the output size, quality, and even file contents like the comments field and other metadata.