Introduction
Have you ever been to a website that has some sort of gallery set up which seems to take forever to load? Maybe the server is just slow, or maybe the hapless web designer just used browser tags to resize the image rather than actually sending a smaller image. This is, of course, very stupid, and I will show you how to avoid this stupidity through the power of php.
php (which Word keeps wanting to auto capitalize) is a language designed around what its developer, Rasmus Lerdorf, calls ‘the web problem’. Although it can be run as an interpreted script like other scripting languages, it is designed first and foremost around dynamically generating HTML pages. Combined with a robust set of libraries and one of the best, most comprehensive programming manuals on the web, you can do some totally sweet stuff with it.
One thing you will need to note is that this tutorial requires php to have been compiled with GD. Most standard distributions of php are, but if you built it yourself then you might want to read up in the php manual to see how to rebuild it with GD support.
Also, I am assuming that you are familiar with the basics of php. You don’t have to be an expert, but you need to understand at least enough to get by writing simple scripts that pass variables through HTTP get or post. Understanding the basic syntax of the language is also a plus, though I will explain any of the more confusing or advanced features as I go.
Mise en scène
In case you’re curious, the phrase ‘mise en scène’ means ‘putting in scene’, and refers to the art of properly setting the stage (and thus the mood) in theater. Not that I would expect you to know that.
ANYway, let’s go through a high-level overview of what we’re going to do. I know you’re itching to get to the actual code, but this is my tutorial, so you have to get through all this pesky text first.
What we’re going to do is called dynamic image scaling. Let’s say you’ve taken a photo of yourself with your fancy new 1.5-terapixel digital camera, and you want to put it on your website to share with your aunt Matilda. But when Tilly (who was always your favorite aunt, even though she’s getting a bit senile with age) tries to open it, she complains, saying the image is so huge that all she can see is one of your hair follicles, and it takes all day to download. Also, you want to put more pictures on your website, and then show thumbnails to the user for browsing. Thus, you will need three sizes of pictures: thumbnail size, web-viewing size, and download size. Thumbnail-size pictures are designed to be very small and quick to transfer, at the expense of the ability to see details. Web-viewing-size pictures are a good size to fit in a standard browser window and show more detail without going overboard. And download size is the actual file with its true dimensions, suitable for getting professionally printed and mounted on aunt Matilda’s wall.
You could, of course, manually scale the photo to each of these three sizes. But this leads to several problems. For example:
- What if you change the size of the thumbnail or the web display picture? You will have to reprocess all the images.
- What if you add a new image? You will have to scale it, too.
- OK, so really that’s only two problems, but they’re still worth paying attention to.
But what if there were some way to tell the webpage “I want an image that’s x pixels wide and y pixels high, so scale it if you have to”? Wouldn’t that be handy? But wait! The HTML image tag does have that ability! So let’s try this:
<img src='/img/myimage.jpg' height='64' width='48' />
Alright! Now the image is a fraction of its normal size! But it seems to take remarkably long to load for such a small image. Hmm…the file size looks exactly the same as the original image! How come the original image is three gigabytes and the 3072-pixel version is still three gigabytes? Inconceivable!
The problem with using in-browser scaling is that the server transfers the entire image and the browser alters the dimensions to be whatever the tag says they should be. Thus, while it fixes your scale problem, your size problem still remains. So unless your users want the most detailed thumbnails known to man, it’s time to ditch pure-HTML scaling and move to the next level: php.
php: HTML++
What we want is a php function that will take the name of an image file on your server and some dimensions, and return that image scaled to those dimensions. There are a couple ways to do this, but the way I prefer is to have the php code generate the image directly and send it straight to the browser. Thus, instead of referencing your image in the HTML tag, you will reference a php file and pass your image as a parameter.
If you want to pass parameters in to a php script, there are three ways: get, post, and cookies. Setting a cookie doesn’t make much sense in this context and you can’t use post in a url, so let’s go with get. In php, get values are stored in a special global variable called $_GET, and are referenced using array notation ($_GET['myunnecessarilylongparametername'] will, for example, get the value of myunnecessarilylongparametername, which was passed in via the url). So let’s go ahead and create a skeleton php file that will take the necessary parameters and process them:
<?php $img = $_GET['img']; $mult = $_GET['scale']; if($img[0]!='/'){ $img = "/$img"; } $fname = $_SERVER['DOCUMENT_ROOT']. $img; if(file_exists($fname)){ $pi = pathinfo($fname); $ext = $pi['extension']; getImage($mult, $fname, $ext); } ?>
“But wait a minute!” you cry. “There are only two parameters! Don’t we need three?” Well, maybe.
The first parameter, img, is of course the image file. The second parameter, which I have confusingly named scale in the url but mult in the code, is not so obvious. I call it the scaling coefficient, because I like to sound smart, but really it’s just a number that you multiply the width and height by to get the new width and height.
I personally like this method a lot. If you have a lot of images that are different sizes, this will maintain the native aspect ratio for each image while still scaling each one down. It can be a problem if your images are of significantly differing sizes, however; if you want, you can replace the single parameter scale with two parameters that explicitly specify the width and height. I won’t mind.
Unfortunately, several of the functions we must call–starting with file_exists–do not accept relative web paths. This is what the %_SERVER reference is for. This still feels a little like a kludge, but at least it’s better than hard-coding the path in. If anyone has a suggestion on how to do it better, post in the forums and I’ll be glad to hear you out! Note that this will work on Windows as well as UNIX-like systems, but that we need to prepend a slash if one does not already exist.
Once we have resolved those crises, the next big question in your mind is undoubtedly the getImage function: what is it? You may have even searched through the php manual looking for it (if so then sorry, since it’s not there). The answer, of course, is that I am going to tell you how to write that function yourself. Be excited!
OK, I’m excited! Let’s write the getImage function!
Great! Let’s go.
function getImage($mult, $name, $ext) { $oldSize = getimagesize("$name"); $oldx = $oldSize[0]; $oldy = $oldSize[1]; $x = $oldx * $mult; $y = $oldy * $mult; //Make sure we're not too insanely huge while($x * $y < 2097152){ $mult -= .25; $x = $oldx * $mult; $y = $oldy * $mult; } }
“WHAT?”, you scream, flecks of spittle flying from your mouth like debris from an explosion, “THIS is what I was supposed to be excited about?” Just calm down and take a few deep breaths. And maybe see a psychiatrist about that anger problem. This isn’t the whole function, but it’s a good start.
Let’s look at that first code block to start off with, shall we? The only thing particularly fancy there is the call to getimagesize. That call gets a two-element array containing the width as element 0 and the height as element 1. It takes a string as a parameter, so I use a double-quoted string and embed the $name variable right inside, which is a handy thing that php lets you do. Then I multiply the original values by the multiplier to get the new values.
Next, as the comment indicates, I make sure that the image is not too large. If you’re wondering where that number came from, it’s two megapixels (well, it’s 1024 * 1024 * 2, which is close enough). This is because the parameters are passed through HTTP get. Thus, someone could just pass in a gigantic value for scale, and all of a sudden all your CPU power and RAM are eaten up generating two-gigabyte images. This is not a substitute for good access control, but it does help close an avenue of attack, which is something all web developers should keep in mind at all times.
Anyway, that was boring. On to the next part of the function; this should go after the above code but before the closing brace for the getImage function:
switch($ext){ case 'jpg': case 'jpeg': $src = imageCreateFromJPEG("$name"); header('Content-type: image/jpeg'); break; case 'png': $src = imageCreateFromPNG("$name"); header('Content-type: image/png'); break; case 'gif': $src = imageCreateFromGIF("$name"); header('Content-type: image/gif'); break; default: //We don't know what it is, so die die("Unrecognized file type: $ext"); } if($src===false){ return null; }
First, the header function. As you hopefully know, HTTP messages contain header information that tells the browser information about what it’s getting. Those of you on Windows are no doubt so used to the system using the file extension to determine file type that this might be a little shocking, but it’s what allows us to have a file with a .php extension that generates an image. The header command sends header information to the browser, and in this case it is sending metadata about the file which will subsequently arrive. Note that we still depend on the file type, so make sure you get it right.
The second item is the imageCreateFrom* functions. It takes an image on disk, reads it in, and stores a pointer to it in memory. That’s what the $src variable holds. If this were a statically-typed language then we would have to screw around with polymorphism and other big words, or maybe byte arrays, but php is content to let us assign any old thing to a variable. Yay for php!
Finally, I use not one, not two, but three equals signs in the trailing if statement. A single equal sign (a = b) means “set a equal to b”. A double equal sign (a == b) means “the contents of a are equivalent to the contents of b”. This is tricky. Due to the aforementioned dynamic typing of php, you never really know what type a variable has. Thus, php is pretty liberal about what is equal to what. For example, ‘4′ (a string) is equal to 4 (a number). Try it out! Also, false is equal to 0. This means that if a function returns 0 and you compare it to false, you will get a, no pun intended, false positive. The triple-equals can be read as “a is identical to b”. One handy thing that php has over many other languages is that false and zero are different things. You just have to use the right comparison operator.
OK, assuming I haven’t bored you to death talking about operators and degrees of equality, let’s finish this baby up!
$dest = imagecreatetruecolor($x, $y); imagecopyresampled($dest, $src, 0, 0, 0, 0, $x, $y, $oldx, $oldy); switch($ext){ case 'png': imagepng($dest); break; case 'gif': imagegif($dest); break; case 'jpg': case 'jpeg': imagejpeg($dest); break; }
All of this still belongs in front of the closing function brace (if you’ve lost track of your braces then see your dentist, or check out the full source, which is available as a download). There are, again, three things going on here, so we shall examine them:
First is imagecreatetruecolor. Besides being in all lower case and somewhat difficult to read, it also creates a new image of the given dimensions. We are starting with a blank canvas the exact length and width of our resized image.
Next, we say imagecopyresampled. This particular function, which takes a zillion parameters, copies an image to another image, resampling/resizing as necessary. I guess you could have figured that out from the name. Anyway, the parameters let you copy just parts of the image, but of course we want them all.
Finally, we use the image* commands. imagepng, for example, sends a png image to the browser. We don’t want any extra stuff being sent, so the function (and thus the script) ends right after this.
That’s it!
What, you thought there was something else coming? Well, nope, this one was pretty easy. What, you want to know how to use this script? Man, do I have to do everything for you? Well, in this case it’s pretty easy.
<img src='/display.php?img=/img/myimage.jpg&scale=.5' />
And there you go. If you want to know why the & instead of just a straight-up ampersand, well, ask the W3C. Apparently they like that kind of thing.
Fred said,
March 24, 2009 @ 22:31
what do you mean … “& instead of just a straight-up ampersand” ???