Lesson 20

Pretty pictures

Perl is wonderful at hacking text and is therefore extremely useful for creating text files like webpages, hence its enormous popularity as a CGI scripting language. However, when it comes to graphics, the Open Source answer to picture manipulation isn't Perl, but ImageMagick. However, being the kitchen sink that it is, there is a tried and tested Perl interface to ImageMagick, called PerlMagick, that allows you to manipulate images from a comfortable perl object API, which you are probably now well acquainted with.

To use PerlMagick, you will need two things: firstly, the ImageMagick library, and secondly the PerlMagick (Image::Magick) modules. If you're feeling brave, you can build ImageMagick from the source code, which you can download from the ImageMagick website. If you're feeling less brave, you can download a precompiled binary from here. The binaries will automatically install PerlMagick for you, else, it's a simple matter of downloading them and building them as usual with CPAN or PPM.

The documentation for PerlMagick is somewhat scanty: frequently, it's just a matter of fiddling about until you understand what does what. However, the basic interface looks like this:

#!/usr/bin/perl
use strict;
use Image::Magick;
my $image1 = new Image::Magick;
my $status = $image1->Read( "old.jpg" );
die "Couldn't open file!" if "$status";
my $image2 = $image1->Clone();
#blah, do something to $image2
$image2->Write( "jpg:new.jpg" );

So far so self-explanatory: we create a new Image::Magick object and read a file into it. If the return value of Read stringifies to an error message, then we barf. We then create a Clone of the image object, and save it as a jpg with the Write method. Doing stuff to the image objects is our next port of call:

Slicing and dicing

I have had occasion to use Image::Magick twice for this website, so the two examples I'll use are real ones. When viewing my vegetable empire page, I noticed that the dendrogram ('family-tree') image on the left rended badly, took hours to download and caused problems when scrolling through the forty five plant images on the right hand side (I have since reimplemented the whole thing with a scary HTML table, so there's not much point in actually looking any more!). This was because it used to be one very long and very thin image. What I wanted to do was to slice it up into pieces, much like Macromedia Fireworks does, but without the grief of having to define forty-odd slices by hand. This is often what I use Perl for: automating things that would otherwise take me ages of repetitive work to achieve by hand. The script I used is below:

#!/usr/bin/perl
use strict;
use warnings;
use Image::Magick;
my $image = new Image::Magick;
my $status = $image->Read( "tree.bmp" );
die "Couldn't open file! $status" if "$status";
for my $count (1..45)
{
    my $image2 = $image->Clone();
    $image2->Crop( geometry => "280x210+0+" . 210*($count-1) );
    my $number = sprintf( "%02u", $count );
    print "creating $number\n";
    $image2->Write( "jpg:slices/slice$number.jpg" );
}

The original image was a huge GIF, but ImageMagick seems to have a few issues with this format (in that this script didn't work for no obvious reason when it was fed a GIF rather than a BMP). GIFs are evil Unisys proprietary pocket-liners anyway, so I first converted it into a bitmap (tree.bmp) with Photoshop or similar, before subjecting it to this script. Basically, the original was a 280px wide by 9450px long image, and I wanted to slice it into 45 equal bits, so I could bung it into an HTML table (yes, I should've used CSS, but whatever), so each plant image got its own slice of dendrogram. To do this, I looped over 45 times, cloning the image into a new object, cropping the image top and bottom to remove all but the middle bit that I actually wanted, then saving the numbered slices into a folder for later.

To crop an image, you need to supply a geometry string to the Crop method. I will be honest here and say that it took me a good two hours to work out how to use the cropping tool (I told you the docs were scanty). This was obviously not helped by the evil GIF problem: it took me most of those two hours before I realised that the utterly random slices I was getting were the GIF format's fault, not the fault of my (mis)understanding of the geometry string! A geometry string (which is used by various other ImageMagick methods too, so concentrate), defines a box in your image. For example:

Cropping an image.

A geometry string has the general form "width x height + width-offset + height-offset". The width and height are the size of the box you wish to define, here 280x210. The width-offset and height-offset are where the top left hand corner of this box ought to be placed (in pixels) relative to the top left hand corner of the image. In this case, we want no width-offset (flush with the left hand side), and a height-offset equal to 210*($count-1), i.e. position the top of the new slice where the bottom of the previous slice was.

Web porn's little helper

By far the most likely thing a web programmer will have to do in the way of image manipulation is automate the production of thumbnail images. There are plenty of freeware tools out there to do such things, and generate (generally awful) HTML markup to display said thumbs, but if you wanted to do things the seemingly easy way, you wouldn't be reading this, would you? If you want perfect control over your code, not to mention the ability to use lovely command line tools to do your work for you, rather than exceedingly difficult to automate GUI tools, then ImageMagick can help here too.

Here's a script to generate thumbs:

#!/usr/bin/perl
use strict;
use warnings;
use Image::Magick;
my $dir = shift;
chdir $dir or die "Can't change to $dir:$!\n";
opendir $DIR, "." or die "Can't open $dir: $!\n";
mkdir "thumbs", 0777 unless -d "thumbs";
foreach my $file ( grep /\.jpg$/i, grep -f, readdir $DIR )
{
    my $image = new Image::Magick;
    my $status = $image->Read( $file );
    die "Couldn't open file! $status" if  $status;
    my $width = $image->Get( 'columns' );
    my $height = $image->Get( 'rows' );
    my $image2 = $image->Clone();
    if ( $width >= $height )
    {
        my $new_height = int( ( 100 * $height ) / $width );
        $image2->Scale( geometry => "100x$new_height" );
    }
    else
    {
        my $new_width = int( ( 100 * $width ) / $height );
        $image2->Scale( geometry => "${new_width}x100" );
    }
    $file =~ s/\.jpg//;
    $image2->Write( "jpg:thumbs/${file}_small.jpg" );
}

This script will merrily work its way through a directory specified on the command line, grabbing all jpg files, and creating a suitably reduced thumbnail for each one. The new methods here are Get, which returns attributes of an image, like the width ('columns') and height ('rows') in pixels, and Scale, which can be used to shrink or expand an image. Like Crop, Scale takes a geometry string (no offset is required here, as this is meaningless). The image is scaled to 100px by something-less-than-100px, so both tall and wide images will take up less than a 100px box. This may not be exactly what you want, but the Perl code is so simple, it's hardly rocket science for you to diddle with it and customise the scaling to your heart's content.

Dynamic thumbs

What if you want to generate thumbnailed images dynamically, rather than saving them separately in a disc-space hungry directory? Well, first be sure this is what you want to do: if your dynamic thumbs script gets pummelled by millions of requests, it is likely to make your server fall over, and CGI is not blazingly fast anyway. In this case, you may want to use mod_perl instead, but this would cache the thumbs anyway, fixing the speed problem but getting you back to square on with the space problem. That said, here's a script to generate dynamic thumbs:

#!/usr/bin/perl -T
use strict;
use CGI;
use Image::Magick;
use CGI::Carp qw/fatalsToBrowser/;
# get the file we want to thumbnail
my $cgi = new CGI;
my $image_file = $cgi->param( 'image' );
( $image_file ) = $image_file =~ /^(\w+\.jpg)$/; # untaint
die "Invalid image file $image_file" unless $image_file;
# open image
my $image = new Image::Magick;
my $status = $image->Read( "../www/images/$image_file" );
die "Couldn't open image: $status" if $status;
# generate thumb from image
my $width = $image->Get( 'columns' );
my $height = $image->Get( 'rows' );
my $thumb = $image->Clone();
if ( $width >= $height )
{
    my $new_height = int( ( 100 * $height ) / $width );
    $thumb->Scale( geometry => "100x$new_height" );
}
else
{
    my $new_width = int( ( 100 * $width ) / $height );
    $thumb->Scale( geometry => "${new_width}x100" );
}
# print thumb to STDOUT
binmode STDOUT;
print $cgi->header( -type => "image/jpeg" );
$thumb->Write(file=>\*STDOUT);
exit( 0 );

This script does largely what the previous one did, only this time, it generates the thumb according to a CGI parameter, then prints the thumb out as a binary string to STDOUT (obviously having prepended the binary data with the correct Content-type header. It's then a simple matter of writing an HTML document with lots of calls to the script, as here:

<p><img src="cgi-bin/image.cgi?image=blah.jpg"></p>

If anyone has any other ImageMagick tricks they'd like to share with the world and see here, feel free to mail me!

Next…