Tuesday, October 20th, 2009

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().

For this small tutorial I'll use this image from the Osaka Aquarium Kaiyukan.

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():

scaled_256colors.jpgoriginal_scaled.jpg

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():

resized.jpgresampled.jpg

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:

scalepng.pngscaledjpg-100.jpg

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):

scaledjpg-100.jpg
scaledjpeg-80.jpg

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):

scaledjpeg-60.jpg
scaledjpeg-40.jpg

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.

Postscript

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.

7 Responses to “Scaling images in PHP (done right)”

  1. st4lk3r says:

    Very good article.

    I have a huge movie collection and an on-line catalogue maintained with my own PHP/MySQL app. Each movie has a poster. I got an idea to create a big mosaic from scaled-down posters, starting from 250 best movies of all time (according to IMDB).

    Your article really helped to greatly improve the final quality: never use imagecopyresized() but use imagecopyresampled() instead. Yes, the second method is slower but it's acceptable in most cases.

    In case anyone is interested, you can see the final result here:

    http://www.luka.in.rs/top250wallpaper.php
    (huge poster, 5500x3200px, feel free to print it :)

  2. [...] read through the whole article, but this may be a good place to start: http://oswaldatwork.thetaoofamp.com/2009/10/scaling-images-in-php-done-right/ It's just a matter of scaling the image, which I've never had to explicitly do myself, but it cannot [...]

  3. Nigel Nquande says:

    Your script outputs the image to the web browser. Forgive me if this is a stupid/ lazy question : How would I save the image on the server for later use (id est : if there is a scaled image with the same name in './scaled/' directory, return that, else scale the image, store it and return the scaled image)?

  4. vizzy says:

    hey oswald, cool, grad durch zufall auf der suche nach ner kurzen anleitung auf deine seite gestossen. ... gruss vom volker, cebit 2011, tellmatic :D

    ich wusste gleich, dat gesicht, dat kennich doch irgendwoher

  5. Ryan says:

    This is a great article and explanation of why to use the different methods as well as the how. I appreciate that this is combined with an example script. However, with your opinion of always choosing the higher-quality image imagecopyresampled() vs. the faster resize imagecopyresized(), I would argue the reason to choose performance starts to make sense in a case where multiple images are resized on the fly - especially as the number of concurrent users increases. If these images are being resized and displayed as simply thumbnails, low quality will be acceptable and could greatly improve performance for a high-traffic site. However, for single-file operations or with few users (such as a website admin uploading pics to be converted once and stored) I agree the quality pic makes the most sense.

  6. This improved my original image copy algorithm by over 100%. Brilliant! Thank you for the great post.

  7. And I see that you created XAMPP... I'm using it right now. Thank you for that too :)

Leave a Reply

This blog copyright 2010-2012 by Kai 'Oswald' Seidler