For those who have an interest in programming an interface designed to manipulate images, Gijs has provided the information necessary so that you have the ability to resize, rotate, sharpen, color reduce or add any other special effect to your images. Read more ...
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 do We 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 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:
- Parse the query string;
- Convert the query string to an ImageMagick command string;
- Run ImageMagick on the image;
- 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 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 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 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 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.
| DISCLAIMER: The content provided in this article is not warranted or guaranteed by Developer Shed, Inc. The content provided is intended for entertainment and/or educational purposes in order to introduce to the reader key ideas, concepts, and/or product reviews. As such it is incumbent upon the reader to employ real-world tactics for security and implementation of best practices. We are not liable for any negative consequences that may result from implementing any information covered in our articles or tutorials. If this is a hardware review, it is not recommended to open and/or modify your hardware. |
More PHP Articles
More By Gijs van Tulder
developerWorks - FREE Tools! |
This whitepaper presents the benefits of successfully introducing static analysis into your organization using IBM Rational Software Analyzer. Additionally, it identifies some common pitfalls that can hinder the effective use of static analysis tooling as well as presents 10 simple strategies designed to help you quickly realize the value of static analysis using Rational Software Analyzer. FREE! Go There Now!
|
|
|
|
Visit IBM developerWorks to download a free trial version of Lotus Quickr 8.0, which enables collaboration by transforming the way everyday business content such as documents, rich media, photos, and video can be shared. Lotus Quickr makes it faster and easier to share content of all types (not just documents) within virtual teams. It is designed to make it easier to collaborate across organizational boundaries, while continuing to work within the context of familiar desktop applications. FREE! Go There Now!
|
|
|
|
Download a free trial version of IBM DB2 9.5 for Linux, UNIX, and Windows. DB2 9 is the result of a five-year development project that transformed traditional (static) database technology into an interactive data server that merges the high performance and ease of use of DB2 with the self-describing benefits of XML. FREE! Go There Now!
|
|
|
|
Download a free trial version of IBM Rational Developer for System i V7.1, which provides a complete development environment for traditional i5/OS application development. IBM Rational Developer for System i is a new eclipse-based workstation offering for i5/OS application development that provides a comprehensive Integrated Development Environment for edit/compile/debug of traditional RPG/COBOL/C/C++ i5/OS applications. FREE! Go There Now!
|
|
|
|
IBM Enterprise Modernization solutions help organizations evolve core IT systems towards modern architectures and technologies—reducing the burden of maintenance and freeing up resources to develop new business requirements and capabilities. With the IBM Enterprise Modernization Sandbox for System z you can evaluate IBM Enterprise Modernization solutions focused on five key areas: Assets, Architectures, Skills, Processes and Infrastructures, and Investment. Each solution is based upon real customer experiences and offers a proven path to get you started with your modernization projects. FREE! Go There Now!
|
|
|
|
Join this Rational Talks to You teleconference on December 4 at 1:00 pm ET to discuss how Rational Method Composer can help meet your compliance objectives. Get your questions answered! FREE! Go There Now!
|
|
|
|
Join this webcast to discover the key requirements for successful change and release management. Learn how to extend your .NET environment to improve productivity and collaboration, and address core problems afflicting team development. In this webcast, we’ll review typical challenges faced by customers and how to resolve them with the IBM Rational Change and Release Management solution, including Rational ClearCase, Rational ClearQuest and Rational Build Forge. Replay is available for 9 months. FREE! Go There Now!
|
|
|
|
Try the latest version of IBM Rational Manual Tester V7.0.1 by downloading a free trial from IBM developerWorks. This manual test authoring and execution tool promotes test step reuse to reduce the impact of software change on testers and business analysts and addresses the needs of teams performing at least a portion of their testing manually. FREE! Go There Now!
|
|
|
|
You can now evaluate IBM Rational Asset Manager V7.0 online without installing or configuring it on your own system! Rational Asset Manager helps create, modify, govern, find, and reuse any type of development assets, including SOA and systems development assets. Rational Asset Manager helps you reduce software development costs and improve quality by facilitating the reuse of all types of software development-related assets. Visit developerWorks to learn more about this product and register to explore its capabilities online. FREE! Go There Now!
|
|
|
|
Join this webcast to learn how IBM Rational's Functional Testing solution enables you to implement automation your way, at your pace, with your existing staff. In this webcast, you’ll learn how you can eliminate redundancy of manual test scripts, reduce errors, and increase test coverage through test automation. After this presentation you will understand how IBM Rational Functional Testing solution can streamline your manual testing and make test automation easily attainable. FREE! Go There Now!
|
|
|
|
All FREE IBM® developerWorks Tools! |