owen

I wanted to get some thumbnail creation code in PHP to use for a little project I was working on, and so I traipsed over to Google and asked for “php thumbnail”. I took a peek at the first search result, snagged the code, inserted it in my test app, and went merrily on my way. And that will be the last time I trust Google to find me code.

There are many problems with the code that I got. It’s not malicious, but it does many easy things wrong. And the one thing that it’s supposed to do well - the thing for which Google found it - it doesn’t do correctly.

I can’t entirely blame the code author, because this particular bit of code is very commonly written incorrectly. But what’s a guy to do when you just want a simple PHP function (as opposed to a whole class) that generates thumbnails? Well, it’s about time someone did something about that. Here we go.

Here’s what most people do wrong. You can’t simply compare whether the width of the original image is greater than the height of the original image. You need to compare the aspect ratio (the ratio of width to height) of the source size to the destination (thumbnail) size. Why?

Assume the destination size is a square, 100 pixels by 100 pixels. The aspect ratio of the destination size is 1 (100/100). If the source image is wider than it is tall, then you need to maximize the width of the output. If the source image is taller than it is wide, then you need to maximize the height of the output. In either of these cases, you’re fitting the larger of the two sides into the 100×100 pixel space. That the comparison of original width to original height works out is pure coincidence. To see where that evaluation fails, consider a different set of dimensions.

Assume the destination size is a rectangle, 100 pixels wide by 50 pixels high. If you have an image that is 50×50, it should obviously not be resized. According to the algorithm I was using, you would end up resizing the image to 100×50, which is obviously not right.

Worse yet, if your source image was 75×50, it should still not be resized because it is already at the maximum output height. You end up with a very oddly resized image using the old, incorrect algorithm.

If instead of comparing the size of the source image dimensions to itself, you compare the ratio of those dimensions to the ratio of the dimensions of the output image, then you know which way the source image will better fit into the destination size. You can then use that information to anchor your dimensions for the thumbnail output.

When you’re done playing mental math with how to use those dimensions, there are a few other things that we should clean up in the PHP, too.

First, you don’t explode() a file extension off the end of the file. If you already have the image file, use the built-in getimagesize() image function to determine the dimensions of the file and its file type all in one pass. When you have to determine filetype based on extension, use PHP’s pathinfo() function. It returns other information that might be useful later.

For the love of all that is good, will people please start using switch/case? There is a point when you gain enough experience in coding to know that you’re going to be making comparisons to a bunch of things and that you’ll have to handle each differently. Even if you only have to deal with one thing to start off (like a jpeg, for example) you still get that feeling that maybe this shouldn’t be an if(), but a switch(). Given the option, use the switch(). There’s not much worse than a bunch of ifelse(){} statements stacked thoughtlessly onto one another.

Let’s also try returning some values on success or failure instead of die()ing. Sure, we might not use the return value. There might be nothing we can do if the thumbnail creation fails, but at least we can try to let someone know if they care. I often care.

PHP GD now supports GIF input and output, so there is no need to omit it. There are also a few tricky functions in PHP that let you apply matrix transformations to your images, so you can do things like sharpen the thumbnail before it’s saved. I stole borrowed the code for sharpening directly from the online PHP manual.

Granted, this function to create thumbnails isn’t perfect, but in my opinion, it’s a big improvement over what came before, and it’s still small enough to include as a clip of commonly used code.

Here is the function, with a decent amount of inline and phpdoc comments:

/** * Creates a resized image from a source image, saves resized image to file * @param string $src_filename Filename of the image to resize * @param string $dst_filename Filename of the resized image to output * @param integer $max_width Maximum width of resized image * @param integer $max_height Maximum height of resized image * @return boolean true on success, false on failure */ function createthumb( $src_filename, $dst_filename, $max_width, $max_height ) { // Get information about the image list($src_width, $src_height, $type, $attr) = getimagesize( $src_filename );
// Load the image based on filetype
switch( $type ) {
case IMAGETYPE_JPEG:
	$src_img = imagecreatefromjpeg( $src_filename );
	break;
case IMAGETYPE_PNG:
	$src_img = imagecreatefrompng( $src_filename );
	break;
case IMAGETYPE_GIF:
	$src_img = imagecreatefromgif( $src_filename );
	break;
default:
	return false;
}
// Did the image fail to load?
if ( !$src_img ) {
	return false;
}

// Calculate the output size based on the original's aspect ratio
if ( $src_width / $src_height > $max_width / $max_height ) {
	$thumb_w = $max_width;
	$thumb_h = $src_height * $max_width / $src_width;
}
else {
	$thumb_w = $src_width * $max_height / $src_height;
	$thumb_h = $max_height;
}

// Create the output image and copy to source to it
$dst_img = ImageCreateTrueColor( $thumb_w, $thumb_h );
imagecopyresampled( $dst_img, $src_img, 0, 0, 0, 0, $thumb_w, $thumb_h, $src_width, $src_height );

/* Sharpen before save?
$sharpenMatrix = array( array(-1, -1, -1), array(-1, 16, -1), array(-1, -1, -1) );
$divisor = 8;
$offset = 0;
imageconvolution( $dst_img, $sharpenMatrix, $divisor, $offset );
//*/

// Get information about the output image 
$path_info = pathinfo($dst_filename);
 
// Setup default return info
$return = true;
 
// Load the image based on filetype
switch ( strtolower( $path_info['extension'] ) ) {
case 'jpg':
case 'jpeg':
	imagejpeg( $dst_img, $dst_filename );
	break;
case 'png':
	imagepng( $dst_img, $dst_filename );
	break;
case 'gif':
	imagegif( $dst_img, $dst_filename );
	break;
default:
	$return = $false;
}

// Clean up memory
imagedestroy( $dst_img );
imagedestroy( $src_img );
 
return $return;

}