Introduction
A major mistake some people make when creating an automated thumbnail generator is that they point their thumbnail <img> elements directly at their thumbnail script. This works, but it forces every single thumbnail request to go through that PHP script which causes needless overhead.
A more efficient option is to actually create a "thumbnails" folder and setup a 404 error handler that will attempt to generate and cache thumbnails on the fly before actually sending a 404 status code. That way the very next and future requests will simply bypass all scripting and return the image as if it's always been there.
The code for this is actually very simple. The thumbnail generator and the code for setting a 404 error handler in htaccess is less than one KB.
Here's a snapshot of my filesystem for this setup.
/backgrounds/cat-one/181ab7662fd27121c232eefeb319d4a9.jpg
/backgrounds/cat-two/181ab7662fd27121c232eefeb319d4a9.jpg
/thumbnails/
/thumbnails/.htaccess
/thumbnails/thumbnail.php
Here's the contents of htaccess within the /thumbnails/ directory.
RewriteEngine off
ErrorDocument 404 /thumbnails/thumbnail.php
And here is the PHP within /thumbnails/thumbnail.php
<?php
if(preg_match('#^/?thumbnails/([a-z0-9-]+)/([a-f0-9]{32})\.jpg$#i', $_SERVER['REQUEST_URI'], $img))
{
$cat = $img[1];
$img = $img[2];
$img_path = "../backgrounds/$cat/$img.jpg";
if(file_exists($img_path))
{
if(!is_dir($cat))
{
mkdir($cat);
}
$thumb_path = "$cat/$img.jpg";
$image = imagecreatefromjpeg($img_path);
$new_image = imagecreatetruecolor(167, 250);
imagecopyresampled($new_image, $image, 0, 0, 0, 0, 167, 250, 320, 480);
imagedestroy($image);
imagejpeg($new_image, $thumb_path);
header('HTTP/1.1 200'); // override the 404 response
header('Content-Type: image/jpeg');
flush(); @ob_flush();
imagejpeg($new_image);
flush(); @ob_flush();
imagedestroy($new_image);
exit;
}
}
?>
The htaccess Part
The htaccess part is pretty straight forward.
RewriteEngine off
ErrorDocument 404 /thumbnails/thumbnail.php
Basically the first line turns off mod_rewrite in this directory because it's not needed in this directory. It's not something that must be done, but if you're using it in your DocumentRoot and unless you plan on using mod_rewrite in here later on, it's a good idea to turn off like you would a light in a room nobody is in.
The second line tells Apache where to go if it can't find a file anywhere within the /thumbnails/ directory, or any sub-directories. In this case it's the thumbnail generator.
The Security Part
The script is wrapped in an IF statement that checks the syntax of the requested image address.
if(preg_match('#^/?thumbnails/([a-z0-9-]+)/([a-f0-9]{32})\.jpg$#i', $_SERVER['REQUEST_URI'], $img))
{
//...
}
In my case I'm using a very strict format for my images so I'm able to use a very secure pattern with preg_match to be sure the script isn't vulernable to any kind of URI injection exploits. This part of the script you'll need to modify to fit your file naming scheme.
Does The File Exist
preg_match, when successfull, will provide me with clean input I can use to see in the image I'm creating a thumbnail for even exists.
$cat = $img[1];
$img = $img[2];
$img_path = "../backgrounds/$cat/$img.jpg";
if(file_exists($img_path))
{
//...
}
A New Subdirectory
Now that I know there is a fullsize image to create a thumbnail for, I can check to see if a subdirectory by that name exists within the /thumbnails/ folder and create it if it doesn't exist. After which I can create a relative thumbnail image path from the clean input.
if(!is_dir($cat))
{
mkdir($cat);
}
$thumb_path = "$cat/$img.jpg";
Generate The Thumbnail
Once I'm sure the fullsize image exists, that there's a place to put a thumbnail, and I have a path/filename to save the thumbnail under I can actually get to generating the thumbnail.
$image = imagecreatefromjpeg($img_path);
$new_image = imagecreatetruecolor(167, 250);
imagecopyresampled($new_image, $image, 0, 0, 0, 0, 167, 250, 320, 480);
imagedestroy($image);
A lot of thumbnail scripts seem to store widths and heights in variables, but for this simple little script I just place the numbers since it's not hard to remember what is what in this situation.
If you remember that imagecreatetruecolor takes (width,height) you can easily tell where the width/height arguments in imagecopyresampled go and if you remember that thumbnails are generally smaller than fullsize images you can see where the original images width/height go.
Cache For Later
I'm not sure technically whether this is actually cacheing, or if it's just generating static content. I look at it as simply scripting automatic content creation, but I suppose you can call it whatever you want.
imagejpeg($new_image, $thumb_path);
Easy enough, it just saves the image to the thumbnail pather generated earlier.
Note that the GD thumbnail image hasn't been destroyed yet, just the fullsized one has.
Reason being we still need to send a copy of the thumbnail to the browser and there's no sense in keeping the fullsized image around any longer.
Oh Yeah, You Were Looking For Something
Now that the thumbnail has been generated and saved where browsers can just go straight to it later on, it's time to send the image to the browser that made the request to trigger this script in the first place.
header('HTTP/1.1 200');
header('Content-Type: image/jpeg');
flush(); @ob_flush();
imagejpeg($new_image);
flush(); @ob_flush();
imagedestroy($new_image);
exit;
In order to prevent the browser from bailing, we return an HTTP 200 status for the request. (thanks effim)
Then, since I'm using JPEG image thumbnails I send a simple image/jpeg content type.
Next I flush the headers out to the browser so the browser doesn't have to wait for the PHP buffer to fill up with image data before sending that header.
Then I use imagejpeg without a filename argument so that it will dump the image data out directly to the browser, after which I flush again then finally destroy the thumbnail images GD representation in memory.
Conclusion
Static content is always served faster than dynamically generated content. Any time you can serve static over dynamic it's good to do so, any time you can save yourself from having to generate that static content manually is just, well, good.
I actually include three more lines in the copies of this script I use live that load and copy a watermark onto thumbnails, but I'll leave figuring out how to do that up to you. 😁
This page was published on