Vous êtes sur la page 1sur 23

Programming Web Graphics with Perl

& GNU Software


By Shawn P. Wallace
1st Edition February 1999
1-56592-478-9, Order Number: 4789
470 pages, $29.95

Sample Chapter 8: Image Maps

Image maps allow a web developer to associate actions with regions of an image.
A user's click may activate a link to another page, run a server-side script, or
launch a client-side JavaScript program. In the typical nerdy hipster parlance of
the Web, these regions are known as "hot spots."
The requirements for a working image map are:
An image that can be included as an inline image on a web page
(generally this means GIF, JPEG, or PNG).
Some means of displaying the image and finding the coordinates of
the regions you want to define.
If a client-side map, a <MAP>element in the same document that
defines the regions of the image map.
If a server-side map, a map file on the server that defines the regions
of the image map in a format that the server recognizes.
If a server-side map, an image map program that handles the
resolution of the region from the coordinate clicked by the user
(most web servers now come with this program preconfigured or
built-in, so you probably don't have to worry about it).
The most difficult part of dealing with image maps from a programming or
automation point of view is the generation of the coordinates for the regions
within the image. This generally requires some intervention by the author. There
are some ways of setting up a site so that certain types of image maps may be
generated automatically.
Several people and companies have created tools for making it easier to designate
regions within an image map. A few of these free or inexpensive tools are
discussed at the end of the chapter.
Client-Side Versus Server-Side
In the beginning there was only one option for implementing clickable image
maps: the image map program that ran on the NCSA web server. This
configuration is now known as the server-side image map. Over the next few
years the inadequacies of this approach became apparent and an alternative
means of processing the image map within the user agent was proposed and
popularly accepted. This approach is known as a client-side image map. The
differences are as follows:
Client-side
The "map" associated with the image is embedded within the same
document as the image. When the user clicks on an image, the
coordinates of the point are interpreted by the user agent and
resolved by looking at the map for the image. If the point is within a
region, the target URL is followed by the user agent. Many browsers
also provide contextual information based on the internal map by
displaying the URL associated with a region in the status bar of the
browser when the mouse passes over the region.
Server-side
The map associated with the image resides on the server in a text file
that generally ends with a .map extension. This map file is specified
in an HTML document by the HREF attribute of an anchor element
that contains the image. When the user clicks on an image, the
coordinates of the point are sent to the server as an HTTP request,
and the server attempts to resolve the request by parsing the map
file. If the point is within one of the defined regions, that region's
URL is followed and returned to the client. With server-side maps,
the browser has no idea about the region definitions and cannot give
feedback to the user.
Client-side maps are preferable to server-side maps for several reasons, the most
important of which is that they offer a better response time to the users actions.
They are also more efficient in their use of bandwidth, and once a page is loaded,
they don't necessarily require an open connection to the server. Take, for
example, a server-side image map whose regions are not clearly defined. If the
user requires ten clicks to successfully find the "hot spot," that results in a lot of
network traffic with no results. Here's what the server log could look like for this
frustrating session:
dyn090e.shemp.net - - [18/Apr/1998:21:24:16 -0400]


"GET /shawn/face.map?52,61 HTTP/1.1
" 200 -

dyn090e.shemp.net - - [18/Apr/1998:21:24:18 -0400]


"GET /shawn/face.map?123,60 HTTP/1.1
" 200 -

dyn090e.shemp.net - - [18/Apr/1998:21:24:19 -0400]


"GET /shawn/face.map?211,90 HTTP/1.1
" 200 -

dyn090e.shemp.net - - [18/Apr/1998:21:24:20 -0400]


"GET /shawn/face.map?95,320 HTTP/1.1" 200 -

dyn090e.shemp.net - - [18/Apr/1998:21:24:21 -0400]


"GET /shawn/face.map?21,302 HTTP/1.1" 200 -

dyn090e.shemp.net - - [18/Apr/1998:21:24:22 -0400]


"GET /shawn/face.map?247,341 HTTP/1.1" 200 -

dyn090e.shemp.net - - [18/Apr/1998:21:24:23 -0400]


"GET /shawn/face.map?248,103 HTTP/1.1" 200 -

dyn090e.shemp.net - - [18/Apr/1998:21:24:25 -0400]


"GET /shawn/face.map?65,80 HTTP/1.1
" 200 -

dyn090e.shemp.net - - [18/Apr/1998:21:24:27 -0400]


"GET /shawn/face.map?36,205 HTTP/1.1
" 200 -

dyn090e.shemp.net - - [18/Apr/1998:21:24:31 -0400]


"GET /shawn/face.map?260,196 HTTP/1.1
" 302 -

dyn090e.shemp.net - - [18/Apr/1998:21:24:32 -0400]


"GET
/panicband.html HTTP/1.0
" 302 -

Another major advantage to using client-side maps is that they allow the user
agent to provide the user feedback about which regions of the image map are
"hot" and which are not. This is especially important for images where the
defined regions are not visually obvious.
The last important reason why you should generally choose to use client-side
maps is that they provide text-based browsers the ability to interpret the options
embedded in the image map description and represent them as a textual list of
links.[1] A client-side image map rendered in Lynx, for example, will first appear
as [USEMAP] (unless the image has an ALT tag specified, in which case that text will
be displayed). When this link is selected, the following menu is provided, where
each numbered item is a link to the appropriate URL:
facemap
MAP: http://www.shemp.org/index.html#facemap

1. Ear
2. Nose
3. Eye
Having said all that nasty stuff about how awful server-side image maps are, I'll
take several steps back and say that they do have their uses. If you have an image
map that is meant to be used on many pages, aserver side map will allow you to
share a map file between many images. Or you may have a very complicated
image map that would be too bulky to include as part of a document. Or you may
want to generate an image map dynamically and reference it from a static
HTML page. We will look at some of these applications later in the chapter.
While most browsers support client-side image maps, some older browsers do
not (specifically, Netscape Navigator before Version 2.0 or Internet
Explorer before Version 3.0). To accommodate those users, you may implement
both client- and server-side image maps within the same tag. In this case, the
user agent will use the client-side map description named internalmap if it is able,
and failing that, it will use the server-side map described in mapfile.map:
<A HREF = "mapfile.map">
<IMG SRC = "images/imagemap.gif" ISMAP USEMAP="#internalmap">
</A>
Implementing Client-Side Maps
The USEMAP attribute of the <I MG>tag indicates that the image is a client-side
image map. The USEMAP attribute is assigned a reference to a
named <MAP>element that exists separately within the document. Multiple
images within a document may share the same region definitions by simply
referring to the same <MAP>element. For example, the following HTML
element indicates that the image calledface.gif (shown in Figure 8-1) should be
associated with the <MAP>element named facemap:
<!-- A Client-side image map -->
<IMG SRC="face.gif" USEMAP="#facemap">
Figure 8-1. An image map


The <MAP> tag
The definition for a client-side map is constructed with the <MAP>element.
The <MAP>element is defined for HTML specifications after Version 2.0.
The <MAP>tag must contain a NAME attribute, which assigns the name that
the <IMG> tag's USEMAP attribute will specify, as shown in the earlier example.
The <MAP>tag requires a start tag and a closing tag. The content of
the <MAP>element can be a number of AREA elements which define the
regions. Defining the regions with a list of AREA elements would look like this:
<MAP NAME="facemap">
<AREA SHAPE=rect COORDS="252,168, 273,246"
HREF="http://www.shemp.org/panicband.html" ALT="Ear">
<AREA SHAPE=poly COORDS="141,166, 125,236, 193,232, 164,162"
HREF="http://www.shemp.org/frodus.html" ALT="Nose">
<AREA SHAPE=circle COORDS="100,180,30"
HREF="http://www.shemp.org/as220.html" ALT="Eye">
</MAP>
If regions defined within the same <MAP>element overlap with each other, the
region that is defined first will be the one that takes precedence. This definition
can be used effectively to produce complicated clickable areas with the simple
shape primitives available with the AREA element. For example, to produce a
clickable 20 20 circle, surrounded by a "dead region" of 20 pixels, which in
turn is surrounded by another 20-pixel-wide clickable concentric ring, use the
following ordering of AREA statements (whose regions are pictured in Figure 8-
2):
<IMG SRC="circles.gif" USEMAP="#circles">
<MAP NAME="circles">
<AREA SHAPE=circle
COORDS="75,75,20"
HREF="inner.html">
<AREA SHAPE=circle
COORDS="75,75,40"
NOHREF >
<AREA SHAPE=circle
COORDS="75,75,60"
HREF="outer.html">
</MAP>
Figure 8-2. Clickable concentric rings in a client-side map


The <AREA> tag
The <AREA>tag defines a region of a client-side image map. It consists of a lone
start tag and one or more of the following attributes:
SHAPE
The SHAPE attribute defines the shape of the clickable region.
Acceptable values are:
default: The entire image
rect: A rectangular region
circle: A circular region
poly: A polygonal region
COORDS
The COORDS attribute is a comma-delimited list of coordinates that
define the edges of the region. The coordinate system is based on (0,
0) being the upper left corner of the image. The number and order
of coordinates depends on the shape of the region:
default: No coordinates are specified.
rect: A rectangular region needs two coordinates, the upper left and lower
right corners.
circle: A circular region needs one coordinate and a value for the radius of the
circle, in pixels.
poly: For a polygonal region the coordinates should be a list of three or more
points that define the vertices of the polygon. It is assumed that the side of the
polygon between the last and first points are joined. A polygon can have at most
100 vertices.
HREF
The HREF attribute specifies the URL of the resource or link that will
be requested when the user clicks on this region.
NOHREF
The NOHREF is a Boolean attribute that specifies that the region has no
action associated with it. It can be used to "knock out" area regions
defined by other AREA tags within the same <MAP> element.
ALT
The ALT attribute provides a short description that may be used by
the browser to provide contextual information about the clickable
areas, or may be used by browsers that are not equipped to deal
with images.
ONFOCUS
This attribute may be used to assign a JavaScript event handler
script to the region that will be called whenever the mouse is over
that region.
ONBLUR
This attribute can be used in a similar way as the ONFOCUS attribute,
but it is activated when the region of the image map loses the focus.
Server-Side Maps
The I SMAP Boolean attribute in an <I MG>tag indicates that the image should
be treated as a server-side image map. The <IMG> element should be enclosed in a
hyperlink that points to the map file on the server to be used by the image:
<!-- A Server-side image map -->
<A HREF = "face.map">
<IMG SRC = "images/facemenu.gif" ISMAP>
</A>
When the user clicks on a point in the image, its coordinates are sent to the
server by appending a question mark and the x and y coordinates to the URL
given in the HREF attribute. Text-based user agents will always send a (0, 0)
coordinate when the link is selected. In the above example, the server would get
the following HTTP request when a user clicks on the point at (65, 80):
GET /face.map?65,80
HTTP/1.1

NOTE: In general, it is a good practice to specify the complete URL for each of
the actions in the map file. Relative path names (such as ../foo.html ) will be
interpreted by the web server asrelative to the location of the map file. This could
lead to confusion, so just use the whole URL.
The map file resides on the server and provides a description of the various
regions within the image map, in much the same way as the area statements of a
client-side map. There are a few different formats for server-side map files. The
most popular is the original NCSA format (used by Apache, WebStar, WebSite,
etc.), which describes the regions as in the following example:
# NCSA-style image map file = face.map

# The Right Ear: upper left and lower left points of a rectangle
rect http://www.shemp.org/example/panicband.html 252,168 273,246

# The Nose: a polygon described by four points
poly http://www.shemp.org/example/frodus.html 141,166 125,236 193,232
164,162

# The Right Eye: A circle with a center point and a radius
circle http://www.shemp.org/example/as220.html 100,180 30
The older, deprecated format for map files is the CERN format, which orders the
coordinates and URL slightly differently:
circ (306,204) 7
http://www.shemp.org/example/foobar.html
rect (324,131) (353,261) http://www.shemp.org/example/bargum.html
circ (103,279) 43 http://www.shemp.org/example/foogum.html

NOTE: A note about scaled image maps: if an image is scaled by the web
browser with the WIDTH and HEIGHT attributes (by specifying dimensions larger or
smaller than that of the source image), and the image is used as an image map,
the clickable regions of the map are not scaled along with the image. Each of the
clickable regions are defined as points within the frame of the image and have no
relation to the content of the image. This should be obvious, but is still worth
noting.
An Alternative: Image Buttons with the
<INPUT> Tag
The HTML form element can also be used to simulate a clickable image map. If
an INPUT field of a form is given the type image, the field will be rendered as an
inline image which, when clicked, will perform the action given in
the ACTION field of the form enclosing it. This type of element is generally called
an Image Button. An example of an Image Button that calls a script
called processclick.cgi is:
<FORM ACTION="http://www.shemp.net/cgi-bin/processclick.cgi">
<INPUT TYPE="image" NAME="img" SRC="menu.gif">
<INPUT TYPE="hidden" NAME="dept" SRC="hardware">
</FORM>
When the action specified by the form is a script, the script is sent the x and y
coordinates that the user clicked and the other information contained in the
form. This is why the Image Button is useful; scripts called from regular image
maps have direct access to the clicked coordinates, but cannot be sent additional
input. The x and y values for this example would be passed as parameters
named img.x and img.y, like this:
http://www.shemp.net/cgi-
bin/processclick.cgi?img.x=189&img.y=122&dept=hardware
This example could be useful in that the same menu image and CGI script could
be used for different departments, and the script could handle the context, rather
than having a separate CGI script for each department's image map.
As another example, the following CGI script will allow the user to click on the
Image Button and, using the GD module, will flood-fill the area indicated by the
user's click with the color specified by the user, as seen in Figure 8-3. Here's the
HTML for the page:
<!--An example of an Image Button within an INPUT field -->
<HTML>
<BODY>
<FORM ACTION="cgi-bin/floodfill.cgi">
<INPUT TYPE="image" NAME="image1" SRC="face.gif">
<INPUT TYPE="hidden" NAME="name" VALUE="face.gif">
<SELECT NAME="color">
<OPTION VALUE="255,0,0">Red</OPTION>
<OPTION VALUE="0,255,0">Green</OPTION>
<OPTION VALUE="0,0,255">Blue</OPTION>
</SELECT>
</FORM>
</BODY>
</HTML>
And here's the code, which needs to be set up as a CGI script with all the proper
permissions and such:
#!/usr/local/bin/perl

# floodfill.cgi
# A CGI script that will fill a region that is specified
# by an Image Button from the floodfill.html page.
#
use strict;
use CGI;
use GD;

# Get the parameters:
# 1. x and y coordinates of click
# 2. The name of the image file (passed from hidden field)
# 3. The color with which to fill the region
#
my $query = new CGI;
my $x = $query->param('image1.x');
my $y = $query->param('image1.y');
my $imagename = $query->param('name');
my @color = split /,/, $query->param('color');
my $fillcolor; # The fillcolor is the index in the colormap

# Open the image and read it in as a GIF
#
open(INFILE, "$imagename") || die "Couldn't open file.";
my $image = newFromGif GD::Image(\*INFILE);
close INFILE;

# If the color map is full, substitute the nearest color,
# otherwise allocate the color.
#
if ($image->colorsTotal() == 256) {
$fillcolor = $image->colorClosest(@color);
} else {
$fillcolor = $image->colorAllocate(@color);
}

$image->fill($x, $y, $fillcolor); # fill the region
print $query->header(-type => 'image/gif'); # print the header
binmode(STDOUT);
print $image->gif; # send the image
Figure 8-3. An INPUT field may be included as a part of an HTML form element as an
alternative clickable image map method. These two screenshots show the image before
(top) and after (bottom) the form was submitted by clicking on the forehead in the image.


The I NPUT tag essentially provides the same interface as a server-side image
map, but it requires that you implement the translation of the clicked coordinate
into a meaningful form. It can be used in applications where the few shape-
defining primitives of the image map are not robust enough to describe a very
complicated shape. The Perl CGI::ImageMap module provides some routines for
dealing with this implementation, and for emulating the behavior of regular
image maps.
Image Map Tools
There are basically two ways of specifying the coordinates of the regions within
an image map. The first is the "brute force" or "map hacker" method of using
an image display program such as xv, Paint Shop Pro, or display to bring up the
image in a window and manually picking out the points that define the shapes.
The second method is with a WYSIWYG image map editor.
Most of the commercial image editing packages aimed at web developers (such as
Adobe ImageReady and Macromedia Fireworks) offer built-in tools for making
image map regions fairly painless. Some web server packages also come with
image map tools. The following is a collection of tools that are either freeware or
shareware; ftp addresses are provided when available.
Map Hacker Tools for Generating Coordinates
These tools offer the ability to manually pick out coordinates for use in image
maps:
xv
xv is a useful all-purpose interactive image manipulation program
written by John Bradley for the X Window System. It offers the
ability to quickly manipulate many image file formats, and provides
a nice interface for common tasks such as cropping, sizing, and
manipulating the color tables of images. It can be used to find
coordinates for defining image map regions by clicking the mouse
inside the image window and drawing a selection rectangle, which
can be fine-tuned with handles. The info window will display the size
and position of the rectangular region. Individual points of a
polygon or a circle must be picked out individually. Version 2.x was
free (Version 3.0 and later are shareware). Version 2.0 of xv is
available from ftp://ftp.x.org/R5contrib/ and Version 3.0 may be had
atftp://ftp.cis.upenn.edu/pub/xv/.
display
The display program comes with the ImageMagick distribution (see
Chapter 5, I ndustrial-Strength Graphics Scripting with PerlMagick)
and will display a wide range of graphics file formats on any client
connected to an X server. It provides a graphical interface to many
of the ImageMagick manipulation functions and acts very nicely as a
tool for quickly determining the coordinates of an image map
region. Figure 8-4 shows the information provided by display when
an area of an image is selected. It is available from the ImageMagick
home page
athttp://www.wizards.dupont.com/cristy/ImageMagick.html.
Figure 8-4. ImageMagick's display utility may be used (on X-based platforms) to gather
coordinates for image map files

Gimp
As of this writing there is not a Gimp plug-in that will automatically
generate an image map file in the manner of the WYSIWYG tools.
The Gimp can be used in a similar way as xv or display, however,
and I'm sure someone will write a plug-in at some point.
Check http://registry.gimp.org for Gimp plug-ins. See Chapter
7, Web Graphics with the Gimp, for more on the Gimp.
WYSIWYG Map Editing Tools
There are several commercial packages that have built-in support for image
maps, such as Adobe Fireworks or most WYSIWYG HTML authoring
programs. There are also several free utilities that do just as good a job:
MapEdit
MapEdit is an image map editor written in Java, which means it
should be pretty much platform-independent. It allows you to define
regions within an image with several drawing tools. It is primarily
intended for creating client-side maps, but it will also export NCSA
style server-side map files. MapEdit will prompt you for an HTML
file in which the image map is referenced and will change the file
directly. If there are multiple image maps within a file it will provide
a selection list. When an image is selected and a region is defined,
the program will prompt you for the information to be added to that
region's AREA tag. MapEdit is available
at http://www.boutell.com/mapedit/.
Client-Side Image Map Editor (CSIME)
CSIME is another Java-based editor for creating client-side image
maps. It is platform-independent (if you can run Java) and free. You
can get it at http://web.wwa.com/~myc/csime/.
Map This
Map This is a free image map utility for Windows. It also creates
server-side (NCSA or CERN) or client-side map files. Map This is
available at http://galadriel.ecaetc.ohio-state.edu/tc/mt/.
Translating Server-Side Image Maps to
Client-Side Format
The NCSA server-side image map format can be a convenient way of storing the
information about an image map's region because it is a very simple format
stored in a portable text file. It is also good form to offer a server-side version of
an image map script as a backup to the client-side map. You may find it helpful
to have a script that will translate a server-side map file into a client-side format
for these reasons or because you are "modernizing" legacy image maps.
The following script will take an NCSA-format server-side map file as an
argument and print it to the command line for cutting and pasting into an
HTML document (or for inclusion in a larger HTML formatting project). It
handles two special cases that crop up when dealing with older-format server-
side image maps:
1. Circular regions were sometimes described by two (x, y)
coordinates--a center point and a point on the outer edge of the
circle--rather than the client-side syntax of center point and the
radius.
2. If the default region is listed first in a client-side map it will mask the
definitions of other regions. This script will move default region
descriptions to the end of the map element.
The translation script, called servertoclient.pl, is called with the name of a server-
side map file as a parameter. Here is the code:
#!/usr/local/bin/perl -w

# servertoclient.pl
# Converts NCSA-format image map files to client-side format
#
use strict;

my $servermap = $ARGV[0];
my $validshape = "default|rect|circle|poly";
my (@lines, @endlines, $shape, $url, @coords, $coordstring);
my $count = 0;

# We might need these, if there are circles defined with four values
#
my ($x1, $y1, $x2, $y2, $radius);

# The file is read in first and stored in a list of lines
# so that we can move any default regions at the top of the file
#
if ($servermap) {
open(INFILE, $servermap) || die "Can't open file $servermap...\n";
while (<INFILE>) {
if ($_ =~ /^default/) {
push @endlines, $_;
} else {
push @lines, $_;
}
}
push @lines, @endlines; # tack on any default definitions
close INFILE;
} else {
die "Please specify a server-side image map file...\n";
}

print "<IMG SRC=\"**image name goes here**\" USEMAP=\"#$servermap\">\n";
print "<MAP NAME=\"$servermap\">\n";

foreach my $line (@lines) {
$count++;
if ( ($line =~ /^#/) || ($line eq "\n") ) {
# A comment or blank line; do nothing
} else {
# The format of the map file is:
# SHAPE (arbitrary whitespace) URL (WS) COORD1 (WS) COORD2...
# Where the coordinates may be separated by commas or by
# arbitrary whitespace. This regex should do the job.
#
($shape, $url, @coords) = split /\s+?/, $line;
if ($shape =~ m/$validshape/o) {
# The coords list may still contain x,y chunks;
# split the coordinates into a discrete list
#
@coords = map {split ','} @coords;

# Check to see if it is an old circle definition;
# two x,y coordinates means it is...
#
if (($shape=~/circle/) && (@coords == 4)) {
($x1, $y1, $x2, $y2) = @coords;

# Good ol' pythagorean theorem
#
$radius = int(sqrt(abs($x2-$x1)**2 + abs($y2-$y1)**2));
@coords = ($x1, $y1, $radius);
}
# In a client-side definition, all coordinates are
# separated by commas...
#
$coordstring = join ",", @coords;
print "<AREA SHAPE=$shape\n";
print " COORDS=\"$coordstring\"\n";
print " HREF=\"$url\">\n";
}
}
}
print "</MAP>\n";
Image Maps on the Fly: A Clickable "Wander" Engine
As an interesting example of generating clickable image maps on the fly, let's
create a web page that will be driven by what we'll call a wander engine. The web
page will allow the user to wander through a landscape by clicking on directional
links, and the changing landscape will appear as a dynamically generated image
(see Figure 8-5). Each landscape area will be called a room. Each room can
contain objects that will be overlaid on the background by the CGI script that
dynamically creates the image (the background used in this example is pictured
in Figure 8-6 and the three objects are in Figure 8-7). The composite image will
be an image map where each of the objects can be clicked on to perform some
action.
Figure 8-5. The wander engine allows the user to navigate a landscape with objects by
clicking on navigational links or on the objects themselves

Figure 8-6. The background image for room 3 of the "wander"

Figure 8-7. The image files for objects 2, 3, 4, and 6 in the example

All of the information about a room is contained in the rooms.db database. For
this example, the database will be implemented using Shishir Gundavaram's
Sprite module (available from CPAN), which uses SQL queries to access the data
and stores the data as simple text files. The fields in the rooms.db database are:
The room number
The filename of the background image
A description of the room
The rooms numbers to the north, south, east, and west
A list of the objects in the room
Here is the rooms.db file that we will be using for this example:
Room::Background::Description::North::South::East::West::Objects
1::images/room1.gif::You are standing on a hillside.::2::3::4::5::2,3,4,6
2::images/room2.gif::You are in a maze of twisty wormholes,
all alike.::6::7::8::9::5
The information for each of the objects is stored in the objects.db database. The
fields in the object database are:
The object number
The filename of the image file for the object
A descriptive name for the object
The x and y values for the coordinate that specifies the placement of
the upper left corner of the image
The URL of the action associated with the image
The object database would look something like this:
Object::File::Name::X::Y::URL
1::images/foogum.gif::some gum::50::50::http://www.shemp.org/eat.cgi?item=1
2::images/worm.gif::a sad worm::28::98::http://www.shemp.org/wormhole.cgi
3::images/tree.gif::a lumpy tree::100::15::http://www.shemp.org/tree.html
4::images/flowers.gif::a patch of
flowers::275::20::http://www.shemp.org/f.html
5::images/bargum.gif::some bargum::70::100::http://www.shemp.org/bargum.cgi
6::images/apple.gif::a juicy
apple::185::120::http://www.shemp.org/eat.cgi?item=6
The CGI script that generates the web page is called wander.cgi. It is called by
passing it a room number as a parameter, as in the URL http://shemp.net/cgi-
bin/rooms.cgi?room=1. This script does a number of things:
1. Retrieves the information about the current room and the objects in
that room from the room and object databases.
2. Sorts the objects in order of increasing area. This is so that when we
add the AREA elements to the <MAP>element, the smallest images
will be added first and will get precedence over the larger images.
Also, when the image is created, the object images must be added in
the reverse order, so that the larger objects are added first and the
smaller objects added later.
3. Prints the HTML for the resulting page. This page includes a
description for a client-side image map (a <MAP>element) that is
generated by the data retrieved from the object database, and
an<I MG>element that calls the drawroom.cgi script to dynamically
create the image of the room. The background image and the objects
(if any) are passed to the drawroom.cgi script in the following form:

<IMG SRC="drawroom.cgi?background=backgroundname,0,0
&object1=object1name,x,y
&object2=...">
The navigation links and description for the room are also appended after the
image. Here is the code for wander.cgi:
#!/usr/local/bin/perl -w
use strict;
use CGI;
use Image::Size;
use Sprite; # The Sprite module is used to manage the databases

my ($number, $background, $description,
$north, $south, $east, $west, $objectlist);
my (%file, %name, %x, %y, %width, %height, %area, %url);

# Retrieve the parameters passed in via name value pairs
#
my $query = new CGI;
my $room = $query->param('room');

# Get relevant info from the room database with the Sprite module.
# Sprite allows you to use SQL queries with simple flat text files.
#
my $db = new Sprite;
$db->set_delimiter('Read', '::');

# Sprite may need to know your operating system; valid strings are:
# Unix, Win95, Windows95, MSDOS, NT, WinNT, OS2, VMS, or MacOS.
$db->set_os('Unix');


# Print the header for the HTML page...do it here so that error
# messages can be sent to the browser...
#
print $query->header(-type=>'text/html');

print <<EndHead;
<HTML>
<HEAD>
<TITLE>Wander Room #$room</TITLE>
</HEAD>

<BODY>
EndHead

# Query the room database (rooms.db) and select all of the columns of
# data for the room in question.
#
my @data = $db->sql (<<EndSQLQuery);
select * from rooms.db
where (Room = "$room")
EndSQLQuery

# The data is returned as a multi-dimensional array.
# The first element gives the return status of the query.
#
if (!(shift @data)) {
die "There was an error reading from the room database!<BR></BODY></HTML>";
}

# De-reference the return data and store it in our local room variables
#
foreach my $row (@data) {
($number, $background, $description,
$north, $south, $east, $west, $objectlist) = @$row;
}

# The object list for the room is stored in the database
# as a comma-delimited list
#
my @objects = split ",", $objectlist;

# Find information about the objects in the room by querying the object
# database (objects.db)...
#
foreach my $object (@objects) {
@data = $db->sql (<<EndSQLQuery);
select * from objects.db
where (Object = "$object")
EndSQLQuery

if (!(shift @data)) {
print "There was an error reading from the object database!<BR>";
}

# De-reference the object data and store it in the various
# object info hashes
#
foreach my $row (@data) {
$file{$object} = @$row[1],
$name{$object} = @$row[2],
$x{$object} = @$row[3],
$y{$object} = @$row[4],
$url{$object} = @$row[5];
($width{$object}, $height{$object}) = imgsize($file{$object});
$area{$object} = $width{$object} * $height{$object};
}
}

$db->close;

# Sort the objects by size first, in order of increasing area.
# The objects should be added to the image from largest to smallest.
# The objects should be listed in the MAP element from smallest to
# largest.
#
@objects = sort { $area{$a} <=> $area{$b} } @objects;

# Print the image tag that will call the image creation script
#
print "<IMG SRC=\"drawroom.cgi?background=$background,0,0";
my $count = 0;

foreach my $object (@objects) {
$count++;
print "&object$count=".$file{$object};
print ",$x{$object},$y{$object}";
}
print "\" USEMAP=\"#Room$room\">";

# Print the room description and the navigation links
#
print <<EndHTML;
<P>$description
</P>
<A HREF="wander.cgi?room=$north">North</A> |
<A HREF="wander.cgi?room=$south">South</A> |
<A HREF="wander.cgi?room=$east">East</A> |
<A HREF="wander.cgi?room=$west">West</A>
<BR>
EndHTML

# Print the map element. Each object will be clickable and will
# have a url action associated with it.
#
print "<MAP NAME=\"Room$room\">\n";
for my $object (@objects) {
print "<AREA HREF=\"$url{$object}\"";
print " SHAPE=rect";
print " COORDS=\"$x{$object},$y{$object},";
print $width{$object} + $x{$object} . ",";
print $height{$object} + $y{$object} . "\">";
print "There is $name{$object} here.<BR>\n";
}
print "</MAP>";
print "</BODY></HTML>";
The drawroom.cgi script uses the GD module to combine the background and
objects into a single image on the fly. It is called from the image tag of the page
generated by wander.cgi with parameters indicating the background GIF file and
the individual object GIF files. Each parameter consists of a comma-delimited
list of the image filename and the x, y offset of each object. The background
image has a hard-coded offset of 0, 0 to keep a consistent format. The URL that
would invoke the drawroom.cgi script would look something like:
<IMG SRC="drawroom.cgi?background=images/background.gif,0,0&
object1=images/frobotz3.gif,100,100&object2=images/frobotz1.gif,150,100&
object3=images/frobotz2.gif,0,0" USEMAP="#Room2">
The code for drawroom.cgi is as follows:
#!/usr/local/bin/perl -w

use strict;
use CGI;
use GD;

my (%filename, %x, %y);

# Get the parameters. Since there is not a fixed number of objects
# in any particular room, we must get the list of parameter names
# and iterate over the number of parameters.
#
my $query = CGI->new;
my @paramnames = $query->param;

# Each parameter consists of a comma-delimited string containing
# the filename, and x and y offset of the object.
#
foreach my $paramname (@paramnames) {
( $filename{$paramname},
$x{$paramname},
$y{$paramname} ) = split ",", $query->param($paramname);
}

open(INFILE, $filename{'background'});
my $background = newFromGif GD::Image(\*INFILE);

my ($overlay, $white);
One important thing to note is the order that the objects are placed onto the
background image. Each of the objects was sorted by area in
the wander.cgi script and passed into drawroom.cgi in order of increasing size.
The largest object should be placed first, and smaller objects placed on top. This
will reduce the risk of larger objects completely obscuring smaller objects in case
their regions overlap. Note that this is the reverse of the order that the regions
are listed in the <MAP>element.
# Copy the objects onto the background.
# Grep will return only the parameter names beginning with the string
`object.'
#
for (reverse (grep /^object/, @paramnames)) {
open INFILE, $filename{$_};
$overlay = newFromGif GD::Image(\*INFILE);

$white = $overlay->colorClosest(255, 255, 255);
$overlay->transparent($white);
$background->copy($overlay, $x{$_}, $y{$_}, 0, 0, $overlay->getBounds());
}

# Print the MIME header, then the GIF data
#
print $query->header(-type => 'image/gif',
-expires=>'-1s');
binmode(STDOUT);
print STDOUT $background->gif;

Vous aimerez peut-être aussi