iNET Interactive - Online Advertising Agency
          
   Home    Authors    About    Login    Contact Us
   Search:   
Advanced Search     
  Articles

  ASP (26)
  ASP.NET (19)
  C and C++ (4)
  CFML (2)
  CGI and Perl (16)
  Flash (2)
  Java (7)
  JavaScript (28)
  PHP (92)
  MySQL (13)
  MSSQL (3)
  HTML (35)
  SEO (9)
  Visual Basic (12)
  CSS (13)
  SSI (5)
  XML (12)
  C# (14)

  Developer News

July 3, 2008
Poll: Which Web editor do you use?
About
 
July 3, 2008
Book Review: Head First JavaScript
WebReference.com
 
July 3, 2008
10 Things You Can Do With a Wiki
About
 
July 2, 2008
Web Host Reviews - 10 New Reviews
About
 
July 2, 2008
14 Reasons You Should Join a Social Network
About
 
July 1, 2008
Mark Boulton's Freelance Design Secrets
SitePoint
 
Courtesy of moreover.com
 
Want to receive new articles via e-mail? Click here!
/Home /PHP

PHP frontend to ImageMagick 

  Views:    23590
  Votes:    3
by Gijs van Tulder 1/17/04 Rating: 

Synopsis:

In this article, we will write a script to connect ImageMagick to PHP. You can then generate thumbnails and other versions of your images on-the-fly, by just editing the url.
Pages: firstback1 2 3 5 forwardlast
The Article

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

?>

Pages: firstback1 2 3 5 forwardlast

Similar/related articles:


 
  Sponsors