December 13, 2009

PNG study with CSS Sprites

While writing my first CSS Sprite article, I created a simple, in no way efficient, sprite generator. Last week I needed to show some numbers to the project manager to convince him the sprite technique is worth using, so I modified a bit the program to arrange the icons in different layouts and show the size savings.

Want to know with which layout I improved the file size by 78.6%?

Initial setup

For this example I've used a subset of 59 icons from the excelent Vaga icon set. The application loads and places them in different arrangements, sending the results to the browser using a data URI. Next to every sprite its size in bytes is shown, making it easier to choose the optimum one. In the end, I send the files to a PNG compressor to achieve a result even smaller.

Imagick

Imagemagick is a powerful set of command-line programs for image processing. There are interfaces for use it on nearly every programming language and, of course, there's one for our beloved PHP.

I've used the PECL Imagick extension for this example. Let's have a look at some code for loading:

$img = new Imagick('fileName');

Piece of cake, isn't it? It works with nearly any format you name, even PSD.

You can add more images to an Imagick object, like layers in any design application, with the addImage method.

$img->addImage('fileName');

The Imagick object implements the composite pattern, so every Imagick object is like a tree of image nodes. Think of it like layers on Photoshop, e.g. You can add one on top of another and, then, combine them, but with all the flexibility an API can offer.

// This is our well-known option in Photoshop
$img->flattenImages();
...
//Creates a new image containing all the images, side by side (row)
$combined = $img->appendImages(false);
...
// The same as below, but stacking the images (column)
$combined = $img->appendImages(true);

There's another function to distribute all images in a Imagick within a grid, but I can't find the way to hold transparency back. In the case you want to play with it the function is this:

$im->montageImage(ImagickDraw $draw, $tile, $thumbnail, $mode, $frame);
// The nearest I came is the next one (for a 5 columns sprite with a 1 pixel separation in between)
$montage = $montage = $im->montageImage(new ImagickDraw(), '5x0+0+0', '+1+1', Imagick::MONTAGEMODE_UNFRAME, 0);

With a combination of appendImage() and a couple of loops, I simulated the desired behaviour.

The result iconset

I chose 59 as the number of icons because its condition of prime number. The result, as I've tested later, is the same with a perfect square like 64 or a number with multiple divisors like 60. Here you have the complete list generated for 59 icons:

So we have a winner! Stacking all the icons one on top of another achieves the greatest saving. The overall size of the icons is 41.616 bytes, so the saving is 77.45%!

punyPNG

"You said 78.6%, you liar!" I heard you say. Wow, 77% of saving is quite impressive and, best of all, now the browser only needs to do one request, not 59. But yes, there's room yet for improvement and there is when punyPNG enters the scene.

punyPNG is a free image optimizer service. It can't be easier. You upload the images you want to optimize and, in seconds, you can download back optimized. Have a look at the results for all the previous files here.

The winner is...

I tried to optimize all the results in case any of the other images achieved a better optimization, but, nevertheless, the smallest is the one row sprite image. So, stacking all your icons (if they're the same width) is the way to go in order to have the smallest size. Always remember that, due to Opera and Safari issues with sprites, you shouldn't stack sprites for more than 2042 pixels in a single direction, so be cautious!