Home
Interests
Photos
Favorites

Make Friends With ImageMagick
By Jon Udell, Byte.com
Jan 5, 2001 (8:21 AM)
URL: http://www.byte.com/column/BYT20010104S0007

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 on 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. 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, 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 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, etc., 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 one copies the image to the same filename prefixed with "s_" -- that's my convention for naming thumbnails.

Line two 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 three transforms the "s_" version of the image, in place, to a thumbnail-sized image that's at most 200 pixels wide or high.

Line four reads the full-sized BMP and writes an equivalent GIF.

Line five transforms the full-sized GIF, in place, adding a 2-pixel black border.

Back when I ran the Byte Magazine website, 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 );
newWin.document.writeln
('<head><title>' + title + '</title></head>');
newWin.document.writeln
('<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:

The names and dimensions of the thumbnail and enlargement.

Text for the ALT tag, and for the pop-up window's title.

A fragment to append to the HREF tag's SRC attribute. This fragment corresponds to an anchor that occurs once per row. Using it here means that when you click a thumbnail to invoke its popup enlargement, the main page will synch to the row containing that thumbnail. That's a nice UI effect, which helps emphasize the relationship between the thumbnail and its enlargement. It also means that Javascript-disabled browsers will at least get some visible result from clicking the thumbnail.

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">
_1002_
</td>

This strategy lets me 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">
<tr>
<td align="center" valign="top">
<font face="Arial, Helvetica" color="#663333">
<p>DESCRIPTION
<div>PRICE</div>
<div>
<a href="jewelry.html#TARGET"
onClick="javascript:openWindow('FILENAME','WINNAME',
JWIDTH,JHEIGHT,'WINTITLE')">
<img vspace="4" hspace="4" border="2"
WIDTH="SWIDTH" HEIGHT="SHEIGHT" src="THUMBNAIL"
alt="WINTITLE (click to enlarge)"></a>
</div>
</p>
</td>
</table>
EOT

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; }
s#_(\d{4,4})_#processImage($1,$target)#e;
print;
}
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 pop-up 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.

Byte Newsgroups:Join Jon and the others in various, ongoing discussions in the newsgroups.

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.

Here's what Willem van Schaik
had to say about the Javascript
thumbnail viewer I mentioned in the
last column:

I liked the pop-up window method Jon developed for his wife's Web pages. But
while implementing it in my own website, I discovered it didn't work the same in
all browsers. The problem was that no margin definition was included and that
the image size was not specified, but was using a "width=100%."

This was, of course, easily fixed by replacing the "width += 20" with an
additional variable "winWidth = width + 18" (18 instead of 20 is only to
make it match with my chosen margin size). The second improvement I
made was to add some code so that the window is closed when the user
clicks on it, and to show that with an "alt=..." parameter.

If you want to see the result, give it a try at:

http://www.schaik.com/nlsat/nlsat.html

(It also shows some cool satellite images.)

The script now becomes:

<script language="JavaScript">
<!--
function openWindow (doc, win, width, height, title)
{
winHeight = height + 18;
winWidth = width + 18;
newWin = window.open ('', win, 'resizable=no,
toolbar=no,menubar=no,width=' +
winWidth + ',height=' + winHeight );
newWin.document.writeln ('<head><title>'
+ title + '</title></head>');
newWin.document.writeln
('<body bgcolor="white" marginwidth="8"
marginheight="8"
leftmargin="8" topmargin="8"><table border="1"
cellspacing="0" cellpadding="0" align="center">
<tr><td><a href="javascript:window.parent.close();">
<img src="' + doc + '" width="'
+ width + '" height="' + height +
'" hspace="0" vspace="0" border="0" alt="' + title + '
(click to close)">
</a></td></tr></table>');
}
//-->
</script>

and to use this in your HTML code:

<td><a href="#tag"
onclick="javascript:openwindow('imgName.jpg','imgTag',
640,480,'imgTitle')"><img
src="imgIcon.gif" width="100" height="75" border="0"
alt="imgTitle (click to enlarge)"></a></td>

Here, you should replace everything in the form of imgXxxxx, and, of
course, replace the image and icon sizes with your own values. And you
should put the whole thing on one single line.

Just my $0.02. I hope it is useful to some people,

Questions or problems regarding this web site should be directed to abeckman@outdoorssite.com.

Copyright 2008 Art Beckman. All rights reserved.

Last Modified: March 9, 2008