Tangled in the Threads

Jon Udell, January 3, 2001

Managing Web Images

Combining ImageMagick, JavaScript, and Perl

Life's too short to process images and write HTML by hand. Here are some strategies for automating these chores.

Like many artists, my wife Luann has increasingly been using the web to showcase her work. When we first launched her site, we had just a handful of scanned images to work with, and it was straightforward to incorporate them into her pages. Recently, she's been having her work photographed professionally, and accumulating CDs full of images she wants to present on the web. At the same time, I've taken a lot of pictures with my digital camera, an Olympus D-460, and have posted some of these to the web.

All this photo activity has required me to think about how to manage images on the web, and to find ways to automate things that were formerly manual chores: producing thumbnails, linking them to their enlargements, framing and titling the images. I'll share some techniques here partly because they may be useful to you, and partly because they may prompt you to share related techniques with me -- preferably, by way of my newsgroup.

For the first incarnation of my wife's site, I used scanned images. This was feasible because Luann's work -- jewelry and decorative wall hangings -- generally lies flat, and doesn't spill over the edges my flatbed scanner. During this era, I made heavy use of the retouching software that came with the scanner, Kai's PhotoSoap. Because the jewelry wasn't really flat, the scanner often produced shadows and defects that needed to be cleaned up. Sporting the same radical interface that first wowed me when I saw Kai's Power Tools, PhotoSoap is an amazing piece of software with a deep feature set that I'll probably never fully plumb.

What PhotoSoap isn't, though -- at least in my Windows version of the product -- is scriptable. And when you start dealing with more than a handful of images, fixing up lousy scans quickly becomes a huge waste of time. The same holds true for digital pictures. In theory, my wife doesn't need to use a professional photographer for her work. She could photograph everything herself with the digital camera. In practice, there's more to it than meets the eye. The 1.3 megapixel limit of my Olympus D-460 isn't even really the bottleneck, I don't think, since that's plenty of resolution for the web. It's the lighting that really matters: both the equipment, and the know-how. So Luann hires a pro (Jeff Baird, in Brattleboro, VT), and he produces CD-ROMs full of images that don't really need any retouching. What they do need is conversion, into pairs of thumbnails and enlargements suitable for web display.

Make friends with ImageMagick

Somebody mentioned ImageMagick in one of my newsgroups long ago. It's a remarkable open source product that I can now, after just a few weeks, no longer imagine not having. It's available in source and binary form for many systems, including Windows and Linux, and I use it on both of these. It's really a suite of programs sharing a set of core libraries. One of these, display, requires X and so runs most conveniently on Unix/Linux. It's an interactive image editor that you can use to view, crop, resize, color-adjust, convolve, and otherwise mangle your images. Windows users who may be avoiding ImageMagick because of its X orientation: don't worry. Capabilities similar to ImageMagick's display are included in the software bundled with most scanners and digital cameras. What's you won't get, but what ImageMagick provides, are the command-line-driven tools convert, mogrify, and montage. And these just run just as happily on the Windows command line as the Unix command line.

Here's an example from a documentation project I recently worked on. In this case, my source images were Windows .BMP files -- screenshots that I'd used Windows Paint to crop. (Paint never has learned to save as GIF or JPG, a deficiency that ImageMagick corrects.) Given a set of files named Help_01.bmp, Help_02.bmp, ..., I invoked a command file (process.cmd) like this:

for %f in (Help_01 Help_02 Help_03 Help_04) do process %f

Here's the command file:

1 copy %1.bmp s_%1.bmp
2 convert s_%1.bmp s_%1.jpg
3 mogrify -geometry 200x200 s_%1.jpg
4 convert %1.bmp %1.gif
5 mogrify -border 2x2 -bordercolor black %1.gif

Line 1 copies the image to the same filename prefixed with "s_" -- that's my convention for naming thumbnails.

Line 2 reads the thumbnail BMP and writes out an equivalent JPG. In this case, the image is a screenshot, and the text isn't going to be readable no matter what format is used. So JPG compression is indicated, to ensure quick loading of the many thumbnails that will appear in the help file.

Line 3 transforms the "s_" version of the image, in place, to a thumbnail-sized image that's at most 200 pixels wide or high.

Line 4 reads the fullsized BMP and writes an equivalent GIF.

Line 5 transforms the fullsized GIF, in place, adding a 2-pixel black border.

Back when I ran the BYTE web site, my associate -- Joy Blake -- used Debabelizer to do these chores. It's a great program too, and probably does some things better than ImageMagick does. For $400, it should. But Debabelizer's built-in scripting, while incredibly useful, doesn't offer the ultimate flexibility of ImageMagick's command-line-driven tools. Managing collections of images comes down to managing namespaces. For that, I tend to favor Perl, which I use to automate the writing of command files that use the ImageMagick tools.

Displaying images with JavaScript

As a rule I avoid JavaScript, but I've found that it's generally accepted on sites that display artwork. Here's a chunk of JavaScript that you can wrap around a thumbnail in order to display the enlarged version of an image:

function openWindow ( doc, win, width, height, title )
    height += 20;
    width += 20;
    newWin = window.open
        ('', win, 'resizable=no,toolbar=no,menubar=no,
            width=' + width + ',height=' + height );
        ('<head><title>' + title + '</title></head>');
        ('<body bgcolor="black"><table border="1" cellspacing="0" 
        cellpadding="0" width="100%" align="center">
        <tr><td><img vspace="0" hspace="0" border="0" 
        src="' + doc + '"></td></tr></table>');

This routine expects the absolute width and height of the image. It adds 20 pixels to each dimension for two reasons. First, I've found that Netscape displays a lone image awkwardly without some extra padding around it. Second, the padding can be used to frame the image.

Rather than just display the raw image, the routine writes an HTML wrapper for it -- a table, with a black background and a border. True, you could use ImageMagick to produce these effects, but I like the HTML effect, and changing it doesn't require any image reprocessing.

The routine also expects a title, and we'll want to use a meaningful title, not just a numeric image name. All in all, calls to this routine need to marshall a fair amount of information. Here's an example from Luann's jewelry page:

<a href="jewelry.html#lhj" onClick="javascript:openWindow
  ('./img/1001.jpg','1001',340,230,'Lascaux Horse Necklace')">
<img vspace="4" hspace="4" border="2" WIDTH="125" HEIGHT="85"
 src="./img/s_1001.jpg" alt="Lascaux Horse Necklace 
(click to enlarge)"></a>

To write this chunk of HTML/JavaScript, we need:

Automating the production of HTML and JavaScript

Life's too short to write this kind of HTML/JavaScript more than once. So I lay out the page using placeholders for each image. For example:

<td valign="top">



This strategy allows me to focus on the macro-design of the page that will contain, and describe, the images.

Next, I capture the essential information about each image -- in this case, the filename, a description, and a price -- in a little database. A Perl hashtable does this handily:

my $images =
'1001' => 
	DESCRIPTION => 'Lascaux Horse Necklace',
	PRICE => '$55-75',
'1002' => 
	DESCRIPTION => 'Lascaux Horse Earrings',
	PRICE => '$35',

Now, templatize the HTML chunk that you want to substitute for each placeholder:

my $template = <<"EOT";
<table border="0">
<td align="center" valign="top">
<font face="Arial, Helvetica" color="#663333">
<a href="jewelry.html#TARGET" 
<img vspace="4" hspace="4" border="2" 
  alt="WINTITLE (click to enlarge)"></a>

Finally, read the layout file and replace the markers with HTML chunks:

open (F, "jewelry.tmpl") or die $!;
while (<F>)
	if ( m#^<a name=\"(\w+)# )
		{ $target = $1; }

sub processImage
	my ($filename, $target) = @_;
	my ($width,$height) = &jpegsize("./img/$filename.jpg");
	my $hash = $images->{$filename};
	my $chunk = $template;
	my $sfilename = 's_' . $filename;
	my ($swidth,$sheight) = &jpegsize("./img/$sfilename.jpg");
	my $winname = $filename;
	my $wintitle = $hash->{DESCRIPTION}; 
	$filename = "./img/$filename.jpg";
	$sfilename = "./img/$sfilename.jpg";
	$chunk =~ s/DESCRIPTION/$hash->{DESCRIPTION}/g;
	$chunk =~ s/PRICE/$hash->{PRICE}/g;
	$chunk =~ s/WINNAME/$winname/g;
	$chunk =~ s/WINTITLE/$wintitle/g;
	$chunk =~ s/FILENAME/"$filename"/eg;
	$chunk =~ s/THUMBNAIL/"$sfilename"/eg;
	$chunk =~ s/JWIDTH/$width/g;
	$chunk =~ s/SWIDTH/$swidth/g;
	$chunk =~ s/JHEIGHT/$height/g;
	$chunk =~ s/SHEIGHT/$sheight/g;
	$chunk =~ s/TARGET/$target/g;
	return $chunk;

The jpegsize routine, which I found at htp://www.nihongo.org/snowhare/utilities/, extracts the dimensions of both images.

There's nothing revolutionary here, but it all adds up to a huge productivity boost. As a result, it's feasible to include niceties -- like descriptive titles for popup windows -- which would otherwise require way too much time and effort. The underlying pattern of templates and substitution, used here at two levels (the main page, and each image chunk), is one that I find endlessly useful.

Jon Udell (http://udell.roninhouse.com/) was BYTE Magazine's executive editor for new media, the architect of the original www.byte.com, and author of BYTE's Web Project column. He's now an independent Web/Internet consultant, and is the author of Practical Internet Groupware, from O'Reilly and Associates. His recent BYTE.com columns are archived at http://www.byte.com/index/threads

Creative Commons License
This work is licensed under a Creative Commons License.