Pages

Sep 4, 2013

PNG Compression : 5 simple improvements

PNG is a simple format that supports transparency and lossless compression, and used almost all over the web for it’s support of transparency. Sadly though, PNG files don’t compress as small as their JPG, or GIF counterparts, which can cause lots of headaches for HTML5 game developers. This article will look at some ways to fix that.



1. Use a better Deflate
The Deflate algorithm is what PNG uses internally to compress its’ pixel data. I’ve already talked about the fact that  Deflate/GZIP are older algorithms, and don’t compress as well as newer codecs. Which means that your PNG images are not being compressed as small as they should be.


Generally web developers get around the limitations of GZIP by using a better GZIP encoder. That is, they manually GZIP files offline, and provide them to the server, such that it will be passed directly to the user. Advanced compressors like Zopfli and 7zip can be used to produce .gz files (AKA GZIP), and will regularly produce smaller GZIP files at that. These compressors gain wins with the use of advanced searching / matching algorithms coupled with data structures that utilize more memory for better pattern matching.


Looking back at PNG, a great set of tools like advPNG have popped up with this same solution in mind : they will re-pack your PNG data using better compressors to generate a smaller PNG file. Another great example includes combining PNGOUT (by the legendary Ken Silverman) coupled with tools like OptiPNG or Zopfli. Of course, each of these systems creates slightly different results, given the input systems, so it may be wise to adopt a system which will compress against multiple compressors and pick the smallest file; If you're feeling lazy, ScriptPNG will do the heavy lifting for you.


To be clear, if you’re not using an advanced PNG compressor to create your PNG files, then you have bloated PNGs.


2. Improve PNG compression with a lossy pre-processor

Most advanced image compression systems rely on a two stage algorithm, they first apply a lossy version of compression to the source data, and then a lossless one. In essence, the lossy transform often removes information from the stream, alongside re-configuring symbols such that the lossless phase can compress better.


Sadly, PNG’s Deflate option is only a lossless encoder, but that shouldn’t stop you from embracing a lossy preprocessor if you want one. Image processing tools like ImageAlpha and ImageOptim, can compress your PNG image in a lossy method as a pre-process before passing it off to the final PNG format. This creates a two-step process where your lossy, and lossless compression are done by two separate applications. The results are impressive, the reduced color space allows the lossless compressor to find, and make, more frequent matches in the file, yielding to better compression. 

To be clear, if you’re not using a lossy pre-processor to create your compressed PNG files, then you have bloated PNGs.



3. Splitting your transparent layer for improved compression.

HTML5 game developers typically transfer more image data than your standard website, most of it being transparent frames for various sprite based flipbook animations. Sadly this process forces game devs to use the PNG format in order to get transparency for their canvas games. However some cunning developers have devised a few work-arounds for images to get better compression and transparency. For example, you can split your color data, and transparency data into two separate image files (two JPGs, for example), and restoring them on the client using a CANVAS element. Although this does increase the number of requests that occur on the network, the savings in image size can be significant for developers who have tons of transparent images on their site (like). To be clear, if you’re not splitting your RGB and Alpha data into separate compressed data sets (where it makes sense), then you have bloated PNGs.


4. Don’t Trust Deflate

The Deflate compression algorithm is a combination of two lossless compressors, LZ77, and Huffman. LZ77 is a dictionary compressor, that will work to encode a symbol (or group of symbols) by attempting to match it with symbols it has already seen. I.E. searches all N-1 symbols to see if symbol N has ever been seen before. In order to keep things sane, the Deflate algorithm will only check the previous 32k values (this is called it’s ‘search window’). This restricted window size helps improve compression speeds, and focuses on general locality-based duplication that most image and text data tend to exhibit.


This does run into one issue however, in that if you have seen a symbol before (or pixel in our case) but it’s outside the 32k window, then you’re out of luck. The symbol will be encoded in it’s raw, uncompressed form, and hopefully you’ll see it again in the future to take advantage of it. This is the ebb and flow of the LZ family of algorithms.


Little known to most web developers, this can have serious ramifications for your content sizes. For example, consider the following figure:

The leftmost image is a 90 x 270 image; Most likely a sprite sheet that was created, the kiwi’s in the image are exact duplicates in each tile. Note that each tile is 90x90 pixels. The 2nd image, we add 2 transparent columns to the image, making the total size of the image 92x270 pixels, and each kiwi-tile is 92x90 pixels. What’s astonishing about these two images, is than when they are saved in their compressed form the 2nd image is twice as large as the first.


Logically this seems wrong. Adding 540 pixels to an image shouldn’t cause a 2x bloat in compression. However when we look a little closer, we can see why this is occurring; The following heat map of the images represents how compressed a given pixel is. Deep blue = very compressed, yellow/red = not very compressed
What are we looking at here? In the first image, you can see that the kiwi’s in the 2nd and 3rd tiles are defined as deep blue meaning that they are very well compressed; their final contribution to the file size is small. In the second image however, you can see that the kiwis mostly green, yellow and red, meaning that they are not as well compressed.

It just so happens that our two pixel larger image exceeds the size of the lz77 window for our tiles. Meaning the lower two tiles won’t find as many matches, and won’t compress as well. Each 90x90 tile easily fits in the 32k window (90x90x4 = 32,400), so subsequent tiles match properly. However our 92x90 tile exceeds the window (92x90x4 = 33,120) resulting in worse compression.


Now, this should be scary, as well as confusing. Firstly there’s no clear definition about what really to do here. Ideally we’d find more icon/sprite packers would perhaps sort images based upon their histograms in an attempt to create smaller images.


To be clear, Deflate really sucks and I wish we’d move away from it on the web.



5. Don’t use PNG
Everything I’ve covered here attempts to improve the compression of PNG through algorithms which are easily available in other codecs. The best way to improve PNG compression, is to not use PNG, instead, use WebP. The WebP format will still be usable in HTML5 canvas, although it doesn’t have universal adoption in all web browsers just yet.




~Main

You can find Colt McAnlis here:

  

7 comments: