ImageMagick PHPPHP Frontend to ImageMagick: What is ImageMagick?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 doWe will write a script that we can copy-paste in a directory with images and that enables us to use ImageMagick's convert utility on each of the images in that directory. The script will enable us to give Maybe a simple example will better explain this idea. You've got an image: On receiving a request, the script will:
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. CommandsYou can use the 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.
Extra commandsThe 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. partThe first of these commands is Enter the colorizehexImageMagick's typeThe Before we startThere 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 install it yourself, or have your server admin do it for you. If your server is running PHP in 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 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 Imagick module from the PEAR library, 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 scriptAnd, 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. ConfigurationYou 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 <?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 inputThe path and file name of the requested image is available as // 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 commandsWe 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 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 convertNow 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 We will add each command to the string 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 The
// 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
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
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 // 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);
}
OutputThe // 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 =
The browser does really want a (new) version of the image. We send some headers and then the image. The // 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);
?>
ConcludingIf 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 full script. TipMaybe you don't like the ugly DefaultType application/x-httpd-php You can then rename the script to something without Gijs is a full time Dutch student in economics and a spare time Web developer. He spends his time developing scripts using PHP, MySQL and other external programs. Visit his site at: http://gvtulder.f2o.org/ |
||||||||||