brian has been a Perl user since 1994. He is founder of the first Perl Users Group, NY.pm, and Perl Mongers, the Perl advocacy organization. He has been teaching Perl through Stonehenge Consulting for the past five years, and has been a featured speaker at The Perl Conference, Perl University, YAPC, COMDEX, and Builder.com. Contact brian at [email protected].
Idon't often create images for web sites, but when I do, I do the same thingsput a black border around the image and make a smaller, thumbnail image. I can do all of this stuff in just about any photo-editing software, but that's annoying. Fortunately, Perl can easily do both of these for me. This is really handy when I want to add many images at the same time.
As with most things in Perl, there is more than one way to do this. One popular way, which I discuss here, uses the Image::Magick module (also known as PerlMagick), although that requires me to install ImageMagick (http://www.imagemagick.org/), which sometimes isn't the easiest thing to install. Aside from that and some left over ugliness from the original C interface, the Image::Magick module gets my job done.
To add a border to an image, I could just use the convert utility that comes with ImageMagick:
convert a.gif -bordercolor black -border 1x1 b.gif
This command opens a.gif, sets the border color to black, draws a one-pixel-high and one-pixel-wide border, and saves the result in b.gif. The order of the options on the command line matter: Start with the original image name, then the operations, and finally, the output image name.
I want my own program, though. Although I don't show it in this article, the actual program I use does a lot of other things to find the images and store them in the right place. That's just a simple matter of programming, so I skip it so I can focus on the image manipulations.
For all of the images I specify on the command line, I want to add a one-pixel-wide black border. I start off my program in the usual order by turning on strictures, then I pull in the Image::Magick module; see Listing 1.
In the foreach() block, I put each filename from the command line into $file in turn. Inside the block, I create a new Image::Magick object. I get a file with the Read() method, and Image::Magick automatically figures out its type. It can handle any type that the underlying libMagick supports, which depends on which image libraries I configured when I compiled and installed it. Oddly, Image::Magick returns a false value when things succeed and true otherwise.
Once I read the image, I call my add_border subroutine. It's not much of a subroutine because Image::Magick makes things so easy. I set the border color to black with the Set() method. I could make this configurable, but I've never wanted a color other than black; you can have any color you like. I can also set just about any parameter in the same way as long as I know the parameter name. After I've set the border color, I call the Mogrify() method, which applies a particular routine to the image. In this case, I add the border with a width of one pixel on each side. If I wanted different widths on the top/bottom and left/right dimensions, I just adjust my "1x1" value in the Mogrify() call.
That's only part of the problem, though. I also want to make thumbnails. I can do this directly with the convert command as I used before:
convert fullsized.jpg -resize '100x90' resized.jpg
I can also do this programmatically by stealing from my previous script. Instead of adding a border, I call Mogrify() to resize the image. In this example, I've hardcoded the final image size based on the files I get off of my digital camera. It's a simple matter of programming to choose a scaling factor and adjust the size, and I leave that up to you.
I add another twist to this program, though. At the start, I create a directory named "Thumbnails" (see Listing 2). I'll use that to store the smaller versions of the image and I'll use the same filename for the full-size and thumbnail versions. They'll just be in separate directories. Later on, at the end of the foreach(), I need to save the result to the right directory and filename. I use the File::Spec module's catfile() method to join the directory and filename according to the convention on the current operating system. Once I have a good path name, I use it as the destination filename.
Image::Magick has many other functions to programmatically process the image, too. Sadly, the Image::Magick documentation points to a web page (http://www.imagemagick.org/www/perl .html), so you are out of luck unless you have Internet access at the same time you want to use Image::Magick. Do what I did: Save the web page source locally.
I can still do better than my two previous programs, though. I usually do those two tasks at the same time, and I also want to put a border around my thumbnail images. I want to run one program and get everything done at the same time. After I add the border to the image, I make the thumbnail. This time, instead of assuming that I have certain dimensions for the image, I do a simple calculation in scaled_dimensions() to set the longest dimension to 80 pixels and adjust the other one appropriately (see Listing 3).
And, finally, I leave you with one more ImageMagick trick, even though it doesn't use Perl. You may have noticed that Image::Magick doesn't need me to tell it about image formats. The Read() method figures it out, and the Write() method figures it out. This makes for a cheap image format conversion utility simply by choosing the file extension:
convert foo.gif foo.png
In the Perl script, you do the same thing: Read in the image in any format that libMagick supports, then write it to another format by changing the file extension. I'll leave the Perl version of that last convert utility to you, though. Good luck.
TPJ
#!/usr/bin/perl use strict; use Image::Magick; foreach my $file ( @ARGV ) { my $image = Image::Magick->new(); if( $image->Read( $file ) ) { warn "Could not read $file, skipping\n"; next; } add_border( $image ); if( $image->Write( $file ) ) { warn "Could not write $file, skipping\n"; next; } } # # # # # # # # # # # # # # # # # # # # # # # # # sub black_border { my $image = shift; $image->Set( bordercolor => 'black' ); $image->Mogrify( border => '1x1' ); }Back to article
Listing 2
#!/usr/bin/perl use strict; use File::Spec; use Image::Magick; my $dir = "Thumbnails"; unless( -d $dir or mkdir $dir, 0755 ) { die "$dir directory does not exist\n". "and I could not create it\n$!\n"; } foreach my $file ( @ARGV ) { my $image = Image::Magick->new(); if( $image->Read( $file ) ) { warn "Could not read $file, skipping\n"; next; } $image->Mogrify( resize => '400x300' ); my $output = File::Spec->catfile( $dir, $file ); if( $image->Write( $output ) ) { warn "Could not write $output, skipping\n"; next; } }Back to article
Listing 3
#!/usr/bin/perl use strict; use File::Spec; use Image::Magick; my $dir = "Thumbnails"; unless( -d $dir or mkdir $dir, 0755 ) { die "$dir directory does not exist\n". "and I could not create it\n$!\n"; } foreach my $file ( @ARGV ) { my $image = Image::Magick->new(); if( $image->Read( $file ) ) { warn "Could not read $file, skipping\n"; next; } add_border( $image ); if( $image->Write( $file ) ) { warn "Could not write $file, skipping\n"; next; } thumbnail( $image, File::Spec->catfile( $dir, $file ) ); } sub thumbnail { my $image = shift; my $dest = shift; my $width = $image->Get( 'width' ); my $height = $image->Get( 'height' ); my $longer = $width > $height ? $width : $height; my $new_size = scaled_dimensions( $image ); $image->Mogrify( resize => $new_size ); return $image->Write( $dest ) ? 0 : 1; } sub scaled_dimensions { my $image = shift; my $width = $image->Get( 'width' ); my $height = $image->Get( 'height' ); my $longer = $width > $height ? $width : $height; my $factor = $longer / 80; join "x", map { int( $_ / $factor ) } ( $width, $height ); } sub add_border { my $image = shift; $image->Set( bordercolor => 'black' ); $image->Mogrify( border => '1x1' ); }Back to article