WEB Advent 2010 / Thumbnails

Many of us have had the pleasure of building a web application that requires the automated building of thumbnails from images that are uploaded at runtime. Thumbnails are hardly the stuff of Nobel laureates, but they’re an uncommon enough annoyance that implementing code to produce them seems like a fresh experience each time.

The easiest way to produce thumbnails for a site is to use a pre-built library designed for that purpose. There are a few decent libraries for this, including the PHP Thumbnailer Class, which provides dead-simple ways to manipulate images. Whether used as library code, or as a plugin to other software, this type of library will often do even more than produce thumbnails; it may add watermarks and easily enable programmatic rotation of the output image.

Using the PHP Thumbnailer Class to create thumbnails is simple:

include_once('libs/ThumbLib.inc.php');

$imagefile = dirname(__FILE__) . '/sample.jpg';
PhpThumbFactory::create($imagefile)-resize(100,100)-show();

This code creates a thumbnail object that contains the source image, resizes it to fit entirely inside a 100×100 area, and then dumps it to the browser with the correct headers.

There is another very handy method, adaptiveResize(), that will resize the image as closely as possible to the provided dimensions, then crop the image from the center so that it fills the entire requested size.

The difference in these two images, apart from the cropped area, is the result of choosing which edge of the image the algorithm uses to resize it. In the case of resize(), the source image is resized so that its height fits inside the output area. In the case of adaptiveResize(), the source image is resized so that its width fits inside the output area.

To simplify further examples, I’ll show only the code that is like resize(), in that the output image is equal to or smaller than the provided dimensions, and doesn’t need to be cropped. To create code like adaptiveResize(), you would simply resize the image by the opposite side and use a cropping method on the result.

Regardless of which style of thumbnail your application requires, which side you choose is ultimately determined using a comparison of aspect ratios.

Understanding aspect ratios

If you search for a function that produces thumbnails to add to your application, you’ll likely encounter quite a few that simply do it wrong. They’ll calculate the size of the thumbnail based only on one dimension of the original image, or base the output size solely on the orientation of the original image. This is incorrect, since it will often leave some thumbnail images incorrectly cropped.

To determine which dimension of the original image to use as the base for resizing, you need to compare the aspect ratio of the original to the aspect ratio of the thumbnail.

The aspect ratio of an image is the ratio of its width to its height. The actual value of the ratio is unimportant, but the comparison of the ratios of the original image to the thumbnail image yields the correct dimension to use for resizing.

Consider these two images:

The top source image has an aspect ratio of 2/3. This is easily expressed as a decimal by dividing the width in pixels by the height in pixels, (66 ÷ 100) ≅ 0.66. The bottom source image has an aspect ratio of 3/2, or (100 ÷ 66) ≅ 1.5.

For both images, the thumbnail output size is an atypical vertical strip. If you’re anything like me, the atypical is usually how you end up writing custom thumbnail routines in the first place. Still, the aspect ratio of 1/2 can be computed the same way, (50 ÷ 100) = 0.5.

The surprise is that in both cases, the aspect ratio of the source image is larger than the aspect ratio of the thumbnail, therefore they should both be resized so that their width matches the width of the thumbnail, with the height reduced in equal proportion:

// Original values obtained from the top image
// can come from getimagesize(), imagesx()/imagesy(), or similar
$source_width = 66; 
$source_height = 100;
$thumbnail_width = 50;
$thumbnail_height = 100;

// Compute aspect ratios
$source_ar = $source_width / $source_height;
$thumbnail_ar = $thumbnail_width / $thumbnail_height;

// Compute the output width and height based on the 
// comparison of aspect ratios
if ($source_ar > $thumbnail_ar) {
    // Use the thumbnail width
    $output_width = $thumbnail_width;
    $output_height = round($original_height
        / $original_width * $thumbnail_width);
}
else {
    // Use the thumbnail height
    $output_height = $thumbnail_height;
    $output_width = round($original_width
        / $original_height * $thumbnail_height);
}

If the height and width of the thumbnail aren’t the same, then comparing the aspect ratio of the original image to that of the thumbnail will tell us which edge should be used as the fulcrum for the resize.

Ideally, a thumbnail library will do all of this math for you, but in case you’re stuck writing your own, it’s useful to understand the computations involved.

Even if you use a library, it is probably a good idea to consider caching.

Lazy thumbnail cache

Libraries that produce thumbnails often have a very basic method for creating their images that outputs the image directly to the browser. That’s fine on the first pass, but you should probably not have PHP build that thumbnail on each page load. This is compounded if you have multiple thumbnails on a page, which is often the case.

Caching is a great idea, but it’s useful to do it in a way that takes full advantage of your web server’s capabilities. The following is geared toward use on Apache, but can be fashioned to work with Lighttpd or Nginx.

The basic idea is to use a front controller pattern. Create thumbnails via PHP for requests that have no matching file, and then save that file to the requested location so that Apache will serve it directly — without loading PHP — on subsequent requests.

If you’re using a PHP Framework, like Lithium or Zend Framework, then you’ve already got this in place, but here are some basic .htaccess lines to enable mod_rewrite for the front controller pattern:

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . create_thumbnail.php [PT]

If you’re using Apache 2.3, you can use mod_dir and FallbackResource:

FallbackResource create_thumbnail.php

Then you need a simple PHP script to map the requested URL in the thumbnail directory to the file that should be turned into a thumbnail:

<?php
include_once('libs/ThumbLib.inc.php');

// Set up the directories for the source and thumbnail images
$source_dir = '/var/www/test.vm2/htdocs/images/';
$thumb_dir = '/var/www/test.vm2/htdocs/thumbs/';

// Figure out where to load and save this image
$source_file = $source_dir . basename($_SERVER['REQUEST_URI']);
$output_file = $thumb_dir . basename($_SERVER['REQUEST_URI']);

// Serve the thumbnail and save it
PhpThumbFactory::create($source_file)->adaptiveResize(100,100)
    ->show()
    ->save($output_file);
?>

The important thing not to overlook is to output the image directly to the browser while saving it to disk, otherwise the first user who tries to load the thumbnail will fail to see anything.

In conclusion

Thumbnails are a really common occurrence that is simple to manage with a few tricks and a good library. A good understanding of how the math behind the library code works can go a long way if you’re ever pressed to produce something custom.

Other posts