Skip to page content or Skip to Accesskey List.

Work

Main Page Content

Php Frontend To Imagemagick

Rated 4.05 (Ratings: 4)

Want more?

  • More articles in Code
 
Picture of gvtulder

Gijs van Tulder

Member info

User since: 05 Feb 2002

Articles written: 7

What is ImageMagick?

title="Download ImageMagick (Link opens in a new window)">ImageMagick is

a powerful set of image manipulation utilities. It can read, write and

manipulate images in many image formats. It can resize, rotate, sharpen,

color reduce or add any other special effect to your images. And, best of all,

ImageMagick is directly available from the command line. In this article, we will

write a script to make it available from the query string. You can then use

it, for example, to automatically generate thumbnails of your images.

What our script will do

We will write a script that we can copy-paste in a directory with images and

that enables us to use ImageMagick's href="http://www.imagemagick.com/www/convert.html" target="_new"

title="Read more about convert (Link opens in a new window)">convert

utility on each of the images in that directory. The script will enable us to

give convert commands by changing the query string.

Maybe a simple example will better explain this idea. You've got an image:

http://wwww.example.com/img/image.jpg. You copy the

ImageMagick script magick.php to the same directory. The image is

now also available as

http://www.example.com/img/magick.php/image.jpg. So far,

your image hasn't changed. Now, imagine you want a thumbnail of the image

with a width of exactly 200 pixels. You can get that image by requesting the

url:

http://www.example.com/img/magick.php/image.jpg?resize(200).

On receiving a request, the script will:

  1. Parse the query string;
  2. Convert the query string to an ImageMagick command string;
  3. Run ImageMagick on the image;
  4. Send the modified image to the browser.

As you see, the script will run ImageMagick for every request. This isn't

very efficient. As you will probably use just a few commands (e.g.

thumbnail and original image) in your html files, caching the output will

speed up the system. We will add a point 5 to the list. The output of

ImageMagick should be cached. The script should send the cached image if it

exists, so ImageMagick won't be generating the same image over and over

again.

Commands

You can use the href="http://www.imagemagick.com/www/ImageMagick.html#opti" target="_new"

title="A list of all ImageMagick commands (Link opens in a new window">

standard commands/options of ImageMagick's convert utility. The

command is followed by the command's parameters. These parameters are

enclosed in brackets. Multiple commands are separated by a plus sign.

ImageMagick uses < and > in some parameters. You can't use these in

html-documents. Instead of < and >, you may use { and } in your query

string. The scripts then converts { to < and } to >.

Here are a few example convert commands and their query equivalent.

Command line Query string
-resize "100x150" ?resize(100x150)
-resize "800x600>" -roll "90" ?resize(800x600})+roll(90)
-flip -resize "800x600>" -flop ?flip+resize(800x600})+flop

Extra commands

The long list of ImageMagick commands didn't contain some things I wanted to

do. I added three 'extra' commands to the script to do this.

part

The first of these commands is

part(widthxheight). With

ImageMagick's crop command, it is possible to get a part of the

image. Unfortunately, this command only accepts absolute parameters. It can

crop w by h pixels, starting x pixels from the left and

y pixels from the top of the image. But what if I want to get 100x100

pixels from the center of the image? That's impossible if I don't know the

size of the image.

Enter the part command. It resizes the image to match either

the preferred width or the preferred height. Then it crops the image to get

the center part of that resized image. And that's what I wanted to do.

colorizehex

ImageMagick's colorize command accepts only decimal RGB

numbers, on a 0 to 100 scale. To colorize with red gives colorize

100/0/0
. This isn't ideal for web use, since html uses hex codes to

identify colors. The colorizehex(hex)

command does accept hex colors. It converts them to the ImageMagick

notation. Example: a red colorize is done with

colorizehex(FF0000).

type

The type(type) is available in

ImageMagick. It's just not a part of the commands, but is appended to the

name of the output file (e.g. jpg:output.jpg). I wanted to

include it in the query string, so I made it a command. You can now convert

the image to jpeg by using type(jpg) in your query.

Before we start

There are just two minor points left before we can start coding.

Do you have ImageMagick?

ImageMagick should be installed on your system before you can use it in your

scripts. This means you will either have to href="http://www.imagemagick.com/" target="_new"

title="Download ImageMagick (Link opens in a new window)">install

it yourself, or have your server admin do it for you.

If your server is running PHP in href="http://www.php.net/manual/en/features.safe-mode.php" target="_new"

title="Read more about safe mode (Links opens in a new window)">safe

mode, which it is likely to be if you're using a (free) shared host,

your scripts don't have the right to execute shell commands. As this script

runs ImageMagick as a shell command, you won't be able to use it. You could

a. ask your hosting provider to disable safe mode or b. use the href="http://www.evolt.org/article/Automated_Creation_of_Thumbnails_With_PHP/20/24498/index.html"

title="Automated Creation of Thumbnails With PHP">GD library to generate

your images. ImageMagick is far more powerful than the GD library, but you

can use the latter even in safe mode.

Why write your own script?

Directly running convert isn't the only way to use ImageMagick in your scripts.

The title="Imagick, part of PEAR (Link opens in a new window)">Imagick

module from the PEAR library,

title="PerlMagick, for Perl (Link opens in a new window)">PerlMagick,

a Perl interface to ImageMagick, can do this too. Then why bother and write

your own script? Because it gives you a far more flexible system. You just

enter your commands as the query string, and the script just sends them to

ImageMagick. The PEAR module, for instance, has a special PHP function for

each ImageMagick command. The script would have to translate the commands to

the corresponding functions, for which it would need an array with all

possible commands and functions. The direct method, withouth PEAR module, is

therefore faster to write.

The script

And, finally, here's the script that makes it all possible. If you copy all

parts, you'll end up with one script. Place it in your image directory, and

it's ready for use.

Configuration

You can specify where your images are and where you want the script to cache

the processed images. It defaults to the current directory, which is probably

where you want it. If the convert utility isn't available in the

PATH environment variable of your web server, you'll need to specify the full

path.

<?php

// location of source images (no trailing /)

$image_path = '.';

// location of cached images (no trailing /)

$cache_path = '.';

// location of imagemagick's convert utility

$convert_path = 'convert';

Check input

The path and file name of the requested image is available as

$_SERVER['PATH_INFO']. We need to check that such information is

given, and that the file exists.

// first, check if an image location is given

if (!isset($_SERVER['PATH_INFO'])) {

die('ERROR: No image specified.');

}

$image = $image_path.$_SERVER['PATH_INFO'];

// next, check if the file exists

if (!file_exists($image)) {

die('ERROR: That image does not exist.');

}

Parse commands

We need a regular expression to parse the query string and extract commands

and parameters.

// extract the commands from the query string

// eg.: ?resize(....)+flip+blur(...)

preg_match_all('/\+*(([a-z]+)(\(([^\)]*)\))?)\+*/',

$_SERVER['QUERY_STRING'],

$matches, PREG_SET_ORDER);

We now have an array $matches. Each element in that array is

another array, with in the third element (position 2) the command name and

on position 4 the parameters.

The cache file name will contain the name of the original file. We then add

the commands and parameters to it, so we get an unique name for each version

of the image.

// concatenate commands for use in cache file name

$cache = $_SERVER['PATH_INFO'];

foreach ($matches as $match) {

$cache .= '%'.$match[2].':'.$match[4];

}

$cache = str_replace('/','_',$cache);

$cache = $cache_path.'/'.$cache;

$cache = escapeshellcmd($cache);

Run convert

Now that we have the cache file name, we can look if we already have a cached

version of the requested image. If we do, we can just send that to the

browser. If we don't, we will ask convert to create it.

We will add each command to the string $commands. We will send

that string to convert to generate the image.

if (!file_exists($cache)) {

// there is no cached image yet, so we'll need to create it first

// convert query string to an imagemagick command string

$commands = '';

foreach ($matches as $match) {

// $match[2] is the command name

// $match[4] the parameter

// check input

if (!preg_match('/^[a-z]+$/',$match[2])) {

die('ERROR: Invalid command.');

}

if (!preg_match('/^[a-z0-9\/{}+-<>!@%]+$/',$match[4])) {

die('ERROR: Invalid parameter.');

}

// replace } with >, { with <

// > and < could give problems when using html

$match[4] = str_replace('}','>',$match[4]);

$match[4] = str_replace('{','<',$match[4]);

After we've checked the input and converted { to < and } to >, we will

add this command to the $convert string. But, since we used our

own, special commands, we will have to check if this command is one of them.

If it is, we will have to do a bit more work.

The colorizehex is quite simple. We will convert hex to decimal,

and then convert the 0-255 scale to ImageMagick's 0-100.

// check for special, scripted commands

switch ($match[2]) {

case 'colorizehex':

// imagemagick's colorize, but with hex-rgb colors

// convert to decimal rgb

$r = round((255 - hexdec(substr($match[4], 0, 2))) / 2.55);

$g = round((255 - hexdec(substr($match[4], 2, 2))) / 2.55);

$b = round((255 - hexdec(substr($match[4], 4, 2))) / 2.55);

// add command to list

$commands .= ' -colorize "'."$r/$g/$b".'"';

break;

The part command requires more work. We first get the size of the

source image using the getimagesize() function. After that we

let ImageMagick resize the image to match either the new width or the new

height. We want one of the image's dimensions to be equal to the new size, and

the other one to exceed that size. We can then crop the image to the requested

size.

case 'part':

// crops the image to the requested size

if (!preg_match('/^[0-9]+x[0-9]+$/',$match[4])) {

die('ERROR: Invalid parameter.');

}

list($width, $height) = explode('x', $match[4]);

// get size of the original

$imginfo = getimagesize($image);

$orig_w = $imginfo[0];

$orig_h = $imginfo[1];

// resize image to match either the new width

// or the new height

// if original width / original height is greater

// than new width / new height

if ($orig_w/$orig_h > $width/$height) {

// then resize to the new height...

$commands .= ' -resize "x'.$height.'"';

// ... and get the middle part of the new image

// what is the resized width?

$resized_w = ($height/$orig_h) * $orig_w;

// crop

$commands .= ' -crop "'.$width.'x'.$height.

'+'.round(($resized_w - $width)/2).'+0"';

} else {

// or else resize to the new width

$commands .= ' -resize "'.$width.'"';

// ... and get the middle part of the new image

// what is the resized height?

$resized_h = ($width/$orig_w) * $orig_h;

// crop

$commands .= ' -crop "'.$width.'x'.$height.

'+0+'.round(($resized_h - $height)/2).'"';

}

break;

The type command is really simple. We can just save the type name

for now.

case 'type':

// convert the image to this file type

if (!preg_match('/^[a-z]+$/',$match[4])) {

die('ERROR: Invalid parameter.');

}

$new_type = $match[4];

break;

If this command isn't special, we can simply add the command and parameters to

the command string.

default:

// nothing special, just add the command

if ($match[4]=='') {

// no parameter given, eg: flip

$commands .= ' -'.$match[2].'';

} else {

$commands .= ' -'.$match[2].' "'.$match[4].'"';

}

}

}

After we've run through the array we've got a list of commands in

$commands. We can now run convert.

convert needs the commands, the location of the source image and

the location of the output image to work. If a new file type is specified, we

add that type and a colon to the output file name.

// create the convert-command

$convert = $convert_path.' '.$commands.' "'.$image.'" ';

if (isset($new_type)) {

// send file type-command to imagemagick

$convert .= $new_type.':';

}

$convert .= '"'.$cache.'"';

// execute imagemagick's convert, save output as $cache

exec($convert);

}

Output

The $cache variable should now point to the file containing the

requested image. It was already cached or it was generated by

convert. If the file exists, we can retrieve some information

about that image to put in the http headers.

// there should be a file named $cache now

if (!file_exists($cache)) {

die('ERROR: Image conversion failed.');

}

// get image data for use in http-headers

$imginfo = getimagesize($cache);

$content_length = filesize($cache);

$last_modified = gmdate('D, d M Y H:i:s',filemtime($cache)).' GMT';

// array of getimagesize() mime types

$getimagesize_mime = array(1=>'image/gif',2=>'image/jpeg',3=>'image/png',

4=>'application/x-shockwave-flash',5=>'image/psd',

6=>'image/bmp',7=>'image/tiff',8=>'image/tiff',

9=>'image/jpeg',

13=>'application/x-shockwave-flash',

14=>'image/iff');

We can now check if the browser sent us a If-Modified-Since header. This is

used to update the browser cache. If the If-Modified-Since date of the browser

is equal to the date the image was last modified, we don't have to send the

image again. The cache of the browser still has an updated version.

// did the browser send an if-modified-since request?

if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {

// parse header

$if_modified_since = preg_replace('/;.*$/', '', $_SERVER['HTTP_IF_MODIFIED_SINCE']);

if ($if_modified_since == $last_modified) {

// the browser's cache is still up to date

header("HTTP/1.0 304 Not Modified");

header("Cache-Control: max-age=86400, must-revalidate");

exit;

}

}

The browser does really want a (new) version of the image. We send some headers

and then the image.

The Content-Type header is a bit special. We have to send a MIME

content type, but the PHP getimagesize() command only gives us

the number of the image type. With the $getimagesize_mime array

we can find the MIME type of that number. In case there is no number we use

the application/octet-stream type. I haven't tested that, but

it's probably better than text/html. (Note: Starting with

PHP 4.3, getimagesize() does return a MIME type. I didn't use it

to make the script compatible with older versions.)

// send other headers

header('Cache-Control: max-age=86400, must-revalidate');

header('Content-Length: '.$content_length);

header('Last-Modified: '.$last_modified);

if (isset($getimagesize_mime[$imginfo[2]])) {

header('Content-Type: '.$getimagesize_mime[$imginfo[2]]);

} else {

// send generic header

header('Content-Type: application/octet-stream');

}

// and finally, send the image

readfile($cache);

?>

Concluding

If you copied the parts of the script and saved it in your image directory,

it's ready for use. Just enter the url to the script, a slash, then the name

of your image and a query string. You should now get the image, modified to

suit your needs.

For those of you who don't like to copy-paste: you can download the href="http://gvtulder.f2o.org/evolt/magick/magick.php.txt" target="_new"

title="Download the full script (Link opens in a new window)">full script.

Tip

Maybe you don't like the ugly .php part in

your url (I don't). You can edit your Apache's configuration file, or

place a .htaccess file in your images directory and add the line:

DefaultType application/x-httpd-php

You can then rename the script to something without .php (ie. just

magick). The url is now much nicer.

Gijs van Tulder is a Dutch student. He likes to play with all things web related and fancies himself as a part-time amateur web developer.

The access keys for this page are: ALT (Control on a Mac) plus:

evolt.org Evolt.org is an all-volunteer resource for web developers made up of a discussion list, a browser archive, and member-submitted articles. This article is the property of its author, please do not redistribute or use elsewhere without checking with the author.