Bubble Foundry


From the Laboratory: Image Resizing and Thumbnail Creation

by Peter.

This is the first in a series of posts on technical issues related to web site development and making user-friendly websites. These will focus on the technical details of developing web sites and applications so will probably be interesting to only a subset of readers. Within the next few months, time permitting, we will be launching a new section of the site called Bubble Foundry Labs, where you will be able to find both technical articles and experimental web applications. For non-technical types, these demo applications may prove easier to understand than this article series.


In this post I will discuss how to take a user-uploaded image and resize it, presumably reducing it so as to create a thumbnail. I will try to discuss the methods in language-neutral terms, though occasionally I will give examples in PHP and Python. This is an important topic, as good content management systems and other web-publishing systems should let users interact with images with a minimum of fuss.

The Logic of Thumbnails

First, the question ‘Why create thumbnails at all?’ must be answered. In img HTML tags you can set both the width and height. So you can create smaller images without creating more files! But no, the image may look smaller but it still is the same size (in kb) as the original, for it is the original. In doing so you have made a trade-off, and a bad one: the server saves minuscule amounts of processing time and disk space while causing increased bandwidth demands for the site, and thus web site visitors. Essentially the work of creating thumbnails is offloaded to the client’s computer and internet connection, hardly the most user friendly choice!

The next issue to address is when to create a thumbnail. There are several different approaches: when the original image is uploaded, when the thumbnail is first requested, or every time the thumbnail is requested. There are advantages to every approach, though I generally subscribe to the first one. I will briefly highlight the advantages and disadvantages of the other methods. Creating a thumbnail when needed, either the first time or every time, reduces initial processing and disk space requirements. Generating a thumbnail when requested means that thumbnail sizes can change over time without the need to update existing thumbnails. However, outside of scientific computing there are few if any websites dealing with images that are hundreds of megabytes each. Processing time and disk space are cheap, a user’s time is not. Therefore I believe it is poor choice.

So we come to my preferred method, creating thumbnails when the initial image is uploaded. This provides some assurance about thumbnail locations (once the image is there, it is always there) and minimizes user inconvenience. The uploading user is more tolerant of somewhat limited responsiveness because they are aware that a new image is being added to the web site.

Saving an Uploaded Image

So, how do we actually create a thumbnail? First, we take the uploaded file and save it to our destination for original images. In PHP the uploaded file is stored in $_FILES['variable_name'], while in the Pylons Python framework it is request.POST['variable_name']. In PHP I use the function move_uploaded_file() to move the file from a temporary location to its destination, while in Python I open the destination file for writing (permanent_image = open(destination, 'w')) and then copy the uploaded file to it (shutil.copyfileobj(image_file.file, permanent_image)).

Creating a Basic Thumbnail

Now that we have our original, full-size images saved to disk we can create the thumbnails, with GD (PHP) and PIL (Python) respectively. What I like to do is have an array (or dictionary, in Python) of thumbnail sizes. I will loop through the sizes, creating thumbnails. Now, doing so introduces some wrinkles. On one hand it makes (graphic) designers’ lives a lot easier if you can promise them that the thumbnails will conform to certain dimensions. However, two questions come up: what to do when the requested thumbnail image would actually be larger than the original image, and what to do if the image doesn’t scale nicely (ie unlike from from 1024 x 768 to 640 x 480)?

The question of thumbnails larger than the original images is a relatively simple one: I recommend either not creating a thumbnail at all or just copying the original image. The PIL Image.thumbnail() method makes your life easier by just preserving the original image’s dimensions. That being said, if the website design absolutely requires a thumbnail with this larger dimension, you might want to just enlarge the image. Another approach will discussed next in the consideration of different image dimensions.

The question of how to create a thumbnail with different target dimensions or width-height ratio than the original is a tricky one, as you can have many different situations. If your thumbnail dimensions aren’t strict, you can respect one of the dimensions, using the equation thumbnail_dimension2 = thumbnail_dimension1 / original_dimension1 * original_dimension2 to calculate the other. In PHP we could then use the following code to create our thumbnail:

$thumbnail_image = imagecreatetruecolor($thumbnail_width, $thumbnail_height);
imagecopyresampled($thumbnail_image, $original_image, 0, 0, 0, 0, $thumbnail_width, $thumbnail_height, $original_width, $original_height);
imageimagetype($thumbnail_image, $thumbnail_location);

Note that the PHP image library is funny, able to create the thumbnail image without being explicitly told the image type (JPEG, PNG, etc) but requires the image type in order to write it to disk.

And in Python:

thumbnail_image = original_image.copy()
thumbnail_image.thumbnail((thumbnail_width, thumbnail_height), Image.ANTIALIAS)
thumbnail_image.save(thumbnail_location)

Make sure to copy the original image first when using PIL, as otherwise you’ll end up reducing the size of your original file! Also, it is important that you call thumbnail() with the ‘antialias’ filter. If you don’t it will default to the ‘nearest’ filter, which creates ugly images.

The code and methodology above are all you need to create basic thumbnails that preserve the original image’s aspect ratio, ensuring that the thumbnail image doesn’t look stretched, pinched, or otherwise distorted.

Advanced Thumbnails

My strategy is in an intermediate step to shrink the original image to a placeholder image which fits within the dimensions of the target image but maintains the proper aspect ratio. Having done that, I then create the thumbnail image with a basic background (usually white, though it could be any other color or even transparent, depending on what image format you’re working with) and overlay the place-holder image on top. I make sure to center the placeholder image within the thumbnail image so that the thumbnail won’t look lopsided. That being said, if some but not all the dimensions concerned have odd numbers of pixels, the placeholder image will be 1 pixel off of the center.

Reversing the order of my previous examples, I will give the Python code first, as it is much simpler:

placeholder_image = original_image.copy()
placeholder_image.thumbnail(thumbnail_width, thumbnail_height)
placeholder_width, placeholder_height = placeholder_image.size
thumbnail_image = Image.new("RGB", (thumbnail_width, thumbnail_height), (255, 255, 255))
delta_width = thumbnail_width - placeholder_width
delta_height = thumbnail_height - placeholder_height
offset = (int(round(float(delta_width) / float(2))), int(round(float(delta_height) / float(2))))
thumbnail_image.paste(placeholder_image, offset)

As you can see, first we copy the original image and then we call thumbnail() on our copy. Because the thumbnail() function maintains the original image’s aspect ratio and in this case we assume that our thumbnail dimensions does not have the same ratio, the placeholder dimensions are not equal to the thumbnail ones. Therefore we calculate the difference between the values, giving us the delta values. Dividing the delta values in half us gives a tuple for the offset of the placeholder image from the top left corner of the final, thumbnail image. We then paste the placeholder image into the thumbnail image using this offset.

Unfortunately PHP does not have anything comparable to PIL’s thumbnail() function, so we will have to discover appropriate dimensions for the placeholder image ourselves. In the code below we will assume that we already know that we will need to create a placeholder image.

if ($original_width > $original_height)
{
$placeholder_width = $thumbnail_width;
$placeholder_height = ($placeholder_width / $original_width) * $original_height;
}
else
{
$placeholder_height = $thumbnail_height;
$placeholder_width = ($placeholder_height / $original_height) * $original_width;
}
$thumbnail_image = imagecreatetruecolor($thumbnail_width, $thumbnail_height);
$white = imagecolorallocate($thumbnail_image, 255, 255, 255);
imagefill($thumbnail_image, 0, 0, $white);
$offset_width = round(($thumbnail_width - $placeholder_width) / 2);
$offset_height = round(($thumbnail_height - $placeholder_height) / 2);
imagecopyresampled($thumbnail_image, $original_image, $offset_width, $offset_width, 0, 0, $placeholder_width, $placeholder_height, $original_width, $original_height);

Note that, unlike in Python, we do not actually create an intermediate image. In fact, the code is flexible enough that it would also work just fine if the placeholder and thumbnail values are the same, as the offset values would simply be zero, like in the previous PHP code example.

With the code above you should be able to always create thumbnails that both respect the original images’ aspect ratios and have the final dimensions that you desire.

Conclusion

Resizing images and creating thumbnails are not terribly difficult, but there are several checks you need to make so as to insure that the thumbnails you are creating don’t unnecessarily distort the image or degrade its quality. Happy coding!