[How-To] Code for ImageMagick
by
06 Apr 2009
Although vBulletin supports ImageMagick it is not widely supported in the mod community. In fact, when I ran into a need to support it for Egg Avatar I could not find another mod that did so to learn from.
After a lot of reading and poking around I discovered that, although the vBulletin image class that supports ImageMagick is far to specific to be useful, it does have one rather useful member: fetch_im_exec().
fetch_im_exec takes care of finding and calling ImageMagick for you, all you need to do is pass the proper command line.
Of course, I didn't want to fill my code with if/else statements every time I wanted to do something with an image, so it made sense to create my own class to abstract the process.
Step 1: Creating an Abstract Base Class
The idea here is that we are basically creating an 'interface'. A common set of methods that can be called without having to worry about whether GD or ImageMagick is the image processor.
PHP Code:
/**
* Abstracted tk image class
*/
class tk_Image_Abstract
{
/**
* Main data registry
*
* @var vB_Registry
*/
var $registry = null;
/**
* Constructor
* Don't allow direct construction of this abstract class
* Sets registry
*
* @return void
*/
function tk_Image_Abstract(&$registry)
{
if (!is_subclass_of($this, 'tk_Image_Abstract'))
{
trigger_error('Direct Instantiation of tk_Image_Abstract prohibited.', E_USER_ERROR);
return NULL;
}
$this->registry = &$registry;
}
/**
* Public functions
*/
function fetch_width($himage) {}
function fetch_height($himage) {}
function fetch_image($url) {}
function fetch_image_from_string($string) {}
function output($himage) {}
function create_truecolor($imgwidth, $imgheight, $himage) {}
function load_egg($url) {}
function copy_image($hdst, $hsrc, $dstx, $dsty, $srcx, $srcy, $srcwidth, $srcheight) {}
function destroy_image($himage) {}
function process_for_output($himage) {}
}
You can see that the class is rather simple. It holds the registry ($vbulletin), does not allow itself to be instantiated directly, and contains a bunch of empty functions as 'placeholders'. These empty functions are important though, but the reason is probably beyond the scope of this article.
Step 2: Create a 'factory class'
A factory class is a simple little class that checks some values and creates the proper real class for you. In this case I check to see if vbulletin is set to use GD or ImageMagick and return an instance of either tk_Image_GD or tk_Image_ImageMagick.
PHP Code:
class tk_Image
{
static $instance;
function get_instance($registry)
{
if ($instance)
{
return $instance;
}
else
{
$postfix = ($registry->options['imagetype'] ? $registry->options['imagetype'] : 'GD');
eval('$instance =& tk_Image_' . $postfix . '::get_instance($registry);');
return $instance;
}
}
}
Step 3: The real classes
The full code of each of these classes can be seen in the attached zip, but we're going to take a look at a stripped down version of each that implements the 'meat' method of an image processing, that is: fetch_image.
We'll look at the GD example first, because it's simpler.
PHP Code:
/**
* Image class for GD Image Library
*/
class tk_Image_GD extends tk_Image_Abstract
{
/**
* Constructor. Sets up resizable types, extensions, etc.
*
* @return void
*/
function tk_Image_GD(&$registry)
{
parent::tk_Image_Abstract($registry);
}
function get_instance($registry)
{
return new tk_Image_GD($registry);
}
/**
* Public functions
*/
function fetch_image($url)
{
$size = getimagesize($url);
$mime = $size['mime'];
if (strpos($mime, "jpeg") !== false)
{
$img = imagecreatefromjpeg($url);
}
elseif (strpos($mime, "png") !== false)
{
$img = imagecreatefrompng($url);
}
else
{
$img = imagecreatefromgif($url);
}
return $img;
}
}
Anyone who's dealt with image handling in GD will recognize this. We determine the type of image and use one of GD's imagecreate methods to load the image into memory and return a handle to it. For my needs I only handle jpeg, gif and png - you might need to add more types.
ImageMagick is in someways simple, but also trickier. It has no concept of working in memory or 'handles'. Everything is done with files on disk. So we have to 'fake' the process by creating an array of filenames we are working with and return positions in that array as the 'handles'.
Since the ImageMagick calls we will make later will directly alter the image, we need to make a temporary copy instead, to preserve the original (at least, for my purposes I did not want to alter the original).
PHP Code:
/**
* Image class for ImageMagick
*/
class tk_Image_Magick extends tk_Image_Abstract
{
var $vbimagemk;
var $is_ani = false;
static $images = array();
function tk_Image_Magick(&$registry)
{
parent::tk_Image_Abstract($registry);
$this->vbimagemk = new vb_Image_Magick($registry);
}
function get_instance($registry)
{
return new tk_Image_Magick($registry);
}
function check_error()
{
if ($error = $this->vbimagemk->fetch_error())
{
echo $error;
die();
}
}
/**
* Public functions
*/
function fetch_image_from_string($string)
{
// write a temp file
$tmpfname = tempnam("/tmp", 'tk_');
$handle = fopen($tmpfname, "w");
fwrite($handle, $string);
fclose($handle);
// determine the file type
$args = $tmpfname;
$fileinfo = $this->vbimagemk->fetch_im_exec('identify', $args, true);
$this->check_error();
$fileinfo = explode(' ', $fileinfo[0]);
$ext = $fileinfo[1];
// rename the file to add extension
rename($tmpfname, $tmpfname . '.' . $ext);
$this->images[] .= $tmpfname . '.' . $ext;
return count($this->images) - 1;
}
}
This method creates a temporary files, adds its name to the image array, and returns the array position as the 'handle' for later use. You can also see the first use of fetch_im_exec. It's really quite easy. The trick is reading up and learning the proper arguments to pass into ImageMagick to do what you want.
ImageMagick consists of two main 'executables', identify and covert. identify gathers information about an image, and convert alters it. Here I use identify to find out what type of image I'm working with to properly give the temp file an extensions of 'gif' or 'jpg' or whatever.
The second post in this article contains a full usage example of the zipped class I've provided. The class won't cover all your needs since it was written for mine, but it should be easy for you to adapt or add to it for whatever your needs are.
|