Tuesday, October 20th, 2009
Scaling images in PHP (done right)
Scaling images in PHP is quite easy, but there are some things to consider. (If you're short of time, right at the end you'll find the final script.)
Read the original image with imagecreatefromjpeg()
First of all you'll need to read the original image. If it's a JPEG file the imagecreatefromjpeg() function is the right choice:
$source_image = imagecreatefromjpeg("osaka.jpg");
If it's a GIF file you'll take imagecreatefromgif(), and if it's a PNG you will prefer imagecreatefrompng().
Get the size of the original image: getimagesize() vs. imagesx() and imagesy()
Reading the image is quite easy, but the first pitfall you'll encounter if you prepare to scale an image, and need to find out the dimensions of the original image.
Most tutorials will propose this way:
$source_image = imagecreatefromjpeg("osaka.jpg"); $source_image_size = getimagesize("osaka.jpg");
The problem with the getimagesize() function is that it needs to reopen the file to get the actual image size. This is usually not a big issue if you're reading a local file, but it will get critical if you're reading a file from the network like in:
$source_image = imagecreatefromjpeg("http://someurl/osaka.jpg"); $source_image_size = getimagesize("http://someurl/osaka.jpg");
Every time you call getimagesize() the whole image file get transferred over the network, and that quickly became mission-critical. Since you already have the image loaded with imagecreatefromjpeg() there is no need to load it again:
$source_image = imagecreatefromjpeg("osaka.jpg"); $source_imagex = imagesx($source_image); $source_imagey = imagesy($source_image);
Create the destination image: imagecreate() vs. imagecreatetruecolor()
Now we need to prepare the (scaled) destination image. There are two PHP functions which can be used to create an image: imagecreate() and imagecreatetruecolor(). The first creates a palette based image (with a maximum of 256 different colors), and the second one creates a true color image (with a maximum - as far as I know - of 256*256*256 = 16 million colors).
Let's compare the results of both function: On the left imagecreate() and on the right imagecreatetruecolor():
It's obvious: As long as you work with photographic images you'll need more than 256 colors.
So let's decide to use imagecreatetruecolor() and define a target size of 300x200 pixels:
$dest_imagex = 300; $dest_imagey = 200; $dest_image = imagecreatetruecolor($dest_imagex, $dest_imagey);
Scale the image: imagecopyresized() vs. imagecopyresampled()
Now it's time to do the actual scaling of the image. Again PHP offers to function for this purpose: imagecopyresized() and imagecopyresampled(). The first one uses a very simple algorithm to scale the image, it's fast but the quality is really poor. The second one uses a better, but slower algorithm, resulting in a very high quality image.
Poor quality, but fast:
imagecopyresized($dest_image, $source_image, 0, 0, 0, 0, $dest_imagex, $dest_imagey, $source_imagex, $source_imagey);
Best quality, but slow:
imagecopyresampled($dest_image, $source_image, 0, 0, 0, 0, $dest_imagex, $dest_imagey, $source_imagex, $source_imagey);
I don't want to go into the details of this functions, for a detailed explanation of all parameters please refer to the PHP manual.
Let's compare the results: On the left imagecopyresized() and on the right imagecopyresampled():
Again it's obvious: The quality of imagecopyresampled() is much better. In my opinion there is never a reason to use the faster imagecopyresized(). Why would I ever want a low quality image? Even if it's faster to get?
And finally push the image to the browser: imagejpeg() vs. imagepng()
After scaling the image, we now need to push the image to the user's browser. Probably the most popular image formats in the Internet are currently PNG and JPEG. Both will work great with photographic images but true-colored and loss-less PNG images usually results in larger file sizes than JPEG images.
To send a PNG image (with best compression rate 9) to the browser:
header("Content-Type: image/png"); imagepng($dest_image,NULL,9);
Or a JPEG image (with best quality 100):
header("Content-Type: image/jpeg"); imagejpeg($dest_image,NULL,100);
And in comparison, imagepng() on the left vs. imagejpeg() on the right:
Both look absolutely the same, but the JPEG image has a size of 57 KB (using the best quality of 100) and the PNG image is 102 KB big (using the highest available compression rate).
What's the best JPEG quality to choose?
JPEG images are not only smaller but also give you the flexibility to choose the quality and by this indirectly the file size. In PHP you can choose the quality in a range from 0 (worst quality, smaller file) to 100 (best quality, biggest file). Let' take a look.
Quality 100 (57 KB) and quality 80 (16 KB):
If you look very carefully at the quality 80 version on the right, you'll see very small artifacts by the JPEG compression.
Quality 60 (12 KB) and quality 40 (8 KB):
The loss of quality gets worse, and in my opinion the quality 40 image on the right looks terrible.
And now the whole script...
Many words end in a small script:
<?php $source_image = imagecreatefromjpeg("osaka.jpg"); $source_imagex = imagesx($source_image); $source_imagey = imagesy($source_image); $dest_imagex = 300; $dest_imagey = 200; $dest_image = imagecreatetruecolor($dest_imagex, $dest_imagey); imagecopyresampled($dest_image, $source_image, 0, 0, 0, 0, $dest_imagex, $dest_imagey, $source_imagex, $source_imagey); header("Content-Type: image/jpeg"); imagejpeg($dest_image,NULL,80); ?>
In this script I used a quality of 80, that's just my personal preference. You may choose whatever you like. But please, not less than 40.
In many tutorials the PHP script ends with several imagedestroy() function calls. imagedestroy() frees any memory associated with an image. This is a good idea if you sequentially work with different image resources within a single PHP script. But if the imagedestroy() is right at the end of a script, you may omit this function. When the script ends PHP will automatically free any resources.