Chapter 15

Imagemaps


CONTENTS


People often call the medium of the World Wide Web hypermedia because it not only supports hypertext (textual links to other documents) but also graphical links to other documents. The ability to link certain parts of a graphic to other documents is called hyperimage, more commonly called imagemapping.

The idea behind hyperimages is that where you click on a certain image determines what document the browser accesses next. You can summarize the normal behavior of an imagemap as follows:

You can use imagemaps for a variety of purposes from a clickable hypertext map (like the one shown in Figure 15.1) to a fancy graphical index. In this chapter, you learn about the various implementations of imagemaps and using forms and the HTML attribute, ISMAP, for creating and controlling imagemaps.

As you can see from Figure 15.1, Harvard University uses an imagemap for a hypertext map of the campus. Clicking on shaded regions on the map zooms you in to a more detailed view.

Figure 15.1 : An example imagemap.

Server or Client?

What should take the responsibility of handling imagemaps: the server or the client? Clearly, the client needs to handle at least part of the processing. It needs to determine where the user has clicked on the image.

Handling imagemaps through the server using CGI is inefficient. It requires the client telling the server where the user clicked, the server spawning a new program, this program opening a map file that contains the defined regions and URLs, and finally the server accessing the new URL. If you program the server to handle imagemap processing internally, the process speeds up tremendously.

Note
Many servers do support imagemaps internally (meaning that you don't need to use a CGI program to process an imagemap) including NCSA, Apache, Netsite, and WN. Refer to the documentation for your particular server for more information.

Having the client handle imagemaps internally gives you the same (or better) speed benefits as a server that handles imagemaps internally. However, having either the client or the server deal with imagemaps internally means a loss in flexibility handling the imagemap. The client-side imagemap extensions to HTML (discussed in Chapter 14, "Proprietary Extensions"), for example, support only three shapes: rectangles, circles, and polygons. You might want the imagemap processor to determine whether the user has clicked closer to one point than another, for example. You might have hundreds of imagemaps that have the same basic look and that access the same type of information in different directories. Instead of having to specify the coordinates and different files for these hundreds of images, it would be easier to have one program that knows the specified format of the imagemap and the general locations of every file and would need only one parameter to find the right document locations.

Finally, your users might not have a browser that supports client-side imagemaps, or you might not be able to find a server that has the built-in imagemap capabilities that you need. A CGI imagemap program, however, works on all current Web servers that support CGI.

Note
If you use a client-side application such as a Java applet, you can extend the imagemap functionality to perform tasks you can't do with server-side imagemaps. For example, you can emphasize regions on the image when your mouse pointer lies above them. Or you can have messages pop up when you pass a certain region of the image.

Because of this flexibility, most imagemaps are implemented using CGI programs. All that is necessary is for the client to somehow pass to the CGI program the coordinates where the user clicks. Using CGI, you can obtain this information in two ways: forms and ISMAP. Both are discussed in the following sections.

Imagemaps Using Forms

You can pass the coordinates of a user's selection using HTML forms using the <input type=image> tag. The proper syntax for this tag is as follows:

<INPUT TYPE=IMAGE SRC=" . . . " NAME=" . . . " [ALIGN=" . . . "]>

SRC is the location of the image relative to document root, NAME is the name of the field, and ALIGN is equivalent to the ALIGN parameter for the <img> tag. On a browser that supports this tag, the image is displayed. Clicking on any part of the image is equivalent to clicking on a Submit button. If NAME is name, the values name.x and name.y are transmitted to the CGI program through normal form and URL encoding. These two fields contain the coordinates where the user clicked.

Pretty Tic-Tac-Toe

Using the <input type=image> tag, you can extend (and greatly simplify) the tic-tac-toe game from Chapter 13, "Multipart Forms and Maintaining State." The new specifications for a tic-tac-toe game using forms and images are as follows:

The first new requirement does not really take advantage of any imagemapping feature. To generate the tic-tac-toe board dynamically, I use Thomas Boutell's gd library; the code is shown in Listing 15.1.


Listing 15.1. Tic-tac-toe board using board.c.
#include <stdio.h>
#include <math.h>
#include "gd.h"
#include "string-lib.h"

#define LENGTH 170
#define CLENGTH 44
#define D1 55
#define D2 115

static int loc[3] = {0,60,120};

void draw_xo(gdImagePtr board,char xo,int x,int y,int color)
{
  if (xo == 'x') {
    gdImageLine(board,x,y,x+CLENGTH,y+CLENGTH,color);
    gdImageLine(board,x+CLENGTH,y,x,y+CLENGTH,color);
  }
  else if (xo == 'o')
    gdImageArc(board,x+CLENGTH/2,y+CLENGTH/2,CLENGTH,CLENGTH,0,360,color);
}

int main()
{
  char *state = getenv("QUERY_STRING");
  gdImagePtr board;
  int white,black;
  int i;

  /* create GIF */
  board = gdImageCreate(LENGTH,LENGTH);
  white = gdImageColorAllocate(board,255,255,255);
  gdImageColorTransparent(board,white);
  black = gdImageColorAllocate(board,0,0,0);
  gdImageLine(board,D1,0,D1,LENGTH-1,black);
  gdImageLine(board,D2,0,D2,LENGTH-1,black);
  gdImageLine(board,0,D1,LENGTH-1,D1,black);
  gdImageLine(board,0,D2,LENGTH-1,D2,black);
  if (state != NULL)
    for (i=0; i<9; i++)
      draw_xo(board,state[i],loc[i%3],loc[i/3],black);
  /* send GIF */
  printf("Content-Type: image/gif\r\n\r\n");
  gdImageGif(board,stdout);
}

Given the state information in the same form as provided in the Chapter 13 example, the board program displays a tic-tac-toe board with Xs and Os in the correct positions as shown in Figure 15.2. In other words, given a nine-character state string consisting of Xs, Os, and underscores, the board program displays the proper board.

Figure 15.2 : The board program in action.

Now that you have a program that dynamically generates the desired image, you can modify tictactoe.c to take the coordinates, update the board accordingly, and send a new form and image. Because the program is in C, I use read_cgi_input() to get the values of board.x and board.y. After I have these values, I determine where these coordinates are relative to the board position and take the appropriate action. The revised tic-tac-toe program, tictactoe.c, is shown in Listing 15.2.


Listing 15.2. The tictactoe.c program.
#include <stdio.h>
#include <math.h>
#include "cgi-lib.h"
#include "html-lib.h"

#define LENGTH 170
#define D1 55
#define D2 115

void set_board(int board[3][3],char *state)
{
  int i;

  for (i = 0; i<9; i++) {
    if (state[i] == 'x')
      board[i%3][i/3] = 1;
    else if (state[i] == 'o')
      board[i%3][i/3] = -1;
    else
      board[i%3][i/3] = 0;
  }
}

char *board2string(int board[3][3])
{
  char *str = malloc(10);
  int i,j;

  for (j=0; j<3; j++)
    for (i=0; i<3; i++) {
      if (board[i][j] == 1)
    str[i+j*3] = 'x';
      else if (board[i][j] == -1)
    str[i+j*3] = 'o';
      else
    str[i+j*3] = '_';
    }
  str[9] = '\0';
  return str;
}

int adjust_coord(int num)
{
  if (num > D2)
    return 2;
  else if (num > D1)
    return 1;
  else
    return 0;
}

int check_winner(int board[3][3])
{
  int i,j;
  short FOUND = 0;

  /* 0 = go on, 1 = human wins, -1 = computer wins, 2 = stalemate */

  /* sum up horizontals */
  for (j = 0; j<3; j++) {
    if (board[0][j]+board[1][j]+board[2][j] == 3)
      return 1;
    else if (board[0][j]+board[1][j]+board[2][j] == -3)
      return -1;
  }
  /* try verticals */
  for (i = 0; i<3; i++) {
    if (board[i][0]+board[i][1]+board[i][2] == 3)
      return 1;
    else if (board[i][0]+board[i][1]+board[i][2] == -3)
      return -1;
  }
  /* now test diagonals */
  i = board[0][0]+board[1][1]+board[2][2];
  j = board[2][0]+board[1][1]+board[0][2];
  if ( (i==3) || (j==3) )
    return 1;
  else if ( (i==-3) || (j==-3) )
    return -1;
  for (j = 0; j<3; j++)
    for (i = 0; i<3; i++)
      if (board[i][j] == 0)
    FOUND = 1;
  if (FOUND)
    return 0;
  else
    return 2;
}

void computer_move(int board[3][3])
{
  int positions[9];
  int i,j,move;
  int num = 0;

  /* we can assume there are empty positions; otherwise, this function
     would not have been called */
  /* find empty positions */
  for (j=0; j<3; j++)
    for (i=0; i<3; i++)
      if (board[i][j] == 0) {
    positions[num] = i+j*3;
    num++;
      }
  /* pick random number from 0 to num-1 */
  move = (int) ((double) num*rand()/(RAND_MAX+1.0));
  board[positions[move]%3][positions[move]/3] = -1;
}

void print_play(char *msg,char *state)
{
  html_begin(msg);
  h1(msg);
  printf("<p><img src=\"/cgi-bin/board?%s\"></p>\n",state);
printf("<p><a href=\"/cgi-bin/tictactoe\">");
  printf("Play again?</a></p>\n");
}

void print_move(char *msg,char *state)
{
  html_begin(msg);
  h1(msg);
  printf("<form action=\"/cgi-bin/tictactoe?%s\" method=POST>\n",state);
  printf("<input type=image name=\"board\" ");
  printf("src=\"/cgi-bin/board?%s\">\n",state);
  printf("</form>\n");
}

void print_board(int board[3][3], int x, int y)
{
  int winner;
  char state[9];

  html_header();
  strcpy(state,board2string(board));
  if (x != -1) { /* check for valid move and winner */
    if (board[x][y] == 0) { /* valid move */
      board[x][y] = 1;
      strcpy(state,board2string(board));
      winner = check_winner(board);
      if (winner == 1) /* human wins */
    print_play("You Win!",state);
      else if (winner == 2)
    print_play("Stalemate",state);
      else if (winner == 0) { /* computer's turn */
    computer_move(board);
    strcpy(state,board2string(board));
    winner = check_winner(board);
    if (winner == -1)
      print_play("Computer Wins!",state);
    else if (winner == 2)
      print_play("Stalemate",state);
    else
      print_move("Your Move",state);
      }
    }
    else
      print_move("Invalid Move. Try again.",state);
  }
  else
    print_move("Your Move",state);
  html_end();
}

int main()
{
  int board[3][3];
  int x,y;
  llist coordinates;

  if (QUERY_STRING == NULL)
    set_board(board,"_________");
  else
    set_board(board,QUERY_STRING);
  if (read_cgi_input(&coordinates)) {
    x = adjust_coord(atoi(cgi_val(coordinates,"board.x")));
    y = adjust_coord(atoi(cgi_val(coordinates,"board.y")));
  }
  else {
    x = -1; /* user didn't click on anything */
    y = -1;
  }
  print_board(board,x,y);
  list_clear(&coordinates);
}

I changed very little of tictactoe.c to incorporate the interactive imagemap, yet now the game is much more usable and better looking (although still as silly as ever). See the new game shown in Figure 15.3.

Figure 15.3 : The new tic-tac-toe game. The interface in more usable and attractive with imagemaps.

You cannot implement this tic-tac-toe game using a generic implementation of imagemaps (such as client-side HTML imagemaps, the NCSA imagemap CGI program, or any other standard imagemap implementation). You need a custom CGI program to interpret and respond to the user's clicks properly.

ISMAP

Although the <input type=image> form tag provides a nice, consistent interface to imagemaps, it has a few flaws. Historically, this tag has not been supported by browsers, and consequently, it is not a commonly used tag. This tag also does not enable you to specify an alternative tag for text-only browsers.

The more common way of implementing imagemaps is by using an attribute of the <img> tag called ISMAP. When you have a hyperlinked inline image normally, clicking anywhere on the image takes you to the link. For example, if you click anywhere on the image specified by the following you go to happy.html:

<a href="happy.html"><img src="happyface.gif"></a>

If you add the ISMAP parameter to the <img> tag, then the X and Y coordinates where the user clicks on the image are appended to the URL specified in the <a href> tag. For example, if you click on the pixel located at (10,15) on the image specified by the following:

<a href="http://myserver.org/happy.html">
<img src="happyface.gif" ISMAP></a>

the browser sends the server the following request and the coordinates are sent to QUERY_STRING:

http://myserver.org/happy.html?10,15

At this point, it is up to the server to interpret this request properly. Normally, you specify a CGI program in the <a href> tag to process the coordinates, although servers with built-in imagemap capabilities automatically know how to process these requests without the aid of CGI.

A Simple Imagemap CGI

Processing the results from an ISMAP request is easy. The coordinates are sent to QUERY_STRING. Everything before the comma is the X coordinate, and everything after the comma is the Y coordinate. What you do after you have this coordinate is slightly more complex and depends on the nature of your application. In general, though, you want to go to another document depending on where the user clicks.

Where are you going to define the regions and associated documents? You can hard code this information in your program, but for most applications, doing so is not very useful. Having a configuration file for every imagemap that defines the regions and associated URLs is nice. After your imagemap program determines the region in which the user clicks, it sends a Location header followed by the URL. To specify the location of the configuration file, you can append it to the URL and check the PATH_TRANSLATED environment variable.

Because you want to develop a simple imagemap program (as opposed to a complex one, for purposes of this chapter), this imagemap program checks only for rectangular regions. The configuration file, specified in the PATH_TRANSLATED environment variable, looks something like this:

0  10 300 http://www.mcp.com/
25 10  40  /images/brown.gif

The format for each line is as follows:

xmin ymin xmax ymax URL

In other words, the first two numbers define the upper-left corner of the rectangular region, the second two numbers define the lower-right corner, and the last item is either a URL or a document location relative to document root.

What should this imagemap program do if the user clicks on an undefined region? The best response is probably nothing, in this case, so just have it send a Status: 204 no content header.

Finally, how should this CGI respond when it is accessed from a text-only browser such as Lynx? This answer is somewhat of a dilemma. On the one hand, you want your pages to be as accessible as possible; on the other hand, by nature, a textual browser simply cannot take advantage of all the features a multimedia browser can. A good compromise is for the imagemap program to test whether the browser is a text browser by checking the HTTP_ACCEPT for the substring image (as in the MIME types image/gif or image/jpeg). If the browser is a text browser, then display all the URLs available in the map configuration file.

Here's a quick summary of the features of a simple imagemap program:

The Perl code for this program-imagemap.pl-which checks the HTTP_ACCEPT ENV variable, is shown in Listing 15.3. The proper HTML code for correct usage of imagemap is as follows:

<a href="/cgi-bin/imagemap.pl/happyface.map">
<img src="happyface.gif ISMAP></a>

Listing 15.3. The imagemap.pl program.
#!/usr/local/bin/perl

require 'cgi-lib.pl';

# get info
$mapfile = $ENV{'PATH_TRANSLATED'};
&CgiDie("Error","No .map file specified.") unless ($mapfile);

$server = $ENV{'SERVER_NAME'};
$port = $ENV{'SERVER_PORT'};

if ($ENV{'HTTP_ACCEPT'} =~ /image/) {
    $TEXT = 0;
}
else {
    $TEXT = 1;
}

$QUERY_STRING = $ENV{'QUERY_STRING'};
if (!$TEXT && !($QUERY_STRING =~ /,/)) {
    &CgiDie("Error","Your browser does not handle imagemaps correctly.");
}
($x = $QUERY_STRING) =~ s/,.*$//;
($y = $QUERY_STRING) =~ s/^.*,//;

# open .map file
open(MAP,$mapfile) || &CgiDie("Error","Can't open $mapfile");
$FOUND = 0;
$i = 0;
while ($line = <MAP>) {
    $line =~ s/[\r\n]//g;
    ($xmin,$ymin,$xmax,$ymax,$url) = split(/\s+/,$line);
    if ($TEXT) {
    $urls[$i] = $url;
    $i++;
    }
    elsif (&within($x,$y,$xmin,$ymin,$xmax,$ymax)) {
    $FOUND = 1;
    if ($url =~ /:/) { # full URL
        print "Location: $url\n\n";
    }
    else { # virtual URL
        print "Location: http://$server:$port$url\n\n";
    }
    }
}
close(MAP);
if ($TEXT) {
    print &PrintHeader,&HtmlTop("Imagemap: $ENV{'PATH_INFO'}");
    print "<ul>\n";
    foreach $url (@urls) {
    print " <li><a href=\"$url\">$url</a>\n";
    }
    print "</ul>\n";
    print &HtmlBot;
}
elsif (!$FOUND) {
    print "Status: 204 Do nothing\n\n";
}

sub within {
    local($x,$y,$xmin,$ymin,$xmax,$ymax) = @_;

    if (($x>=$xmin) && ($x<=$xmax) &&
    ($y>=$ymin) && ($y<=$ymax)) {
    return 1;
    }
    else {
    return 0;
    }
}

Although imagemap.pl is simplistic in that it is not very configurable, it is fairly powerful in many other ways. For example, it is robust. If someone clicks an undefined area, imagemap.pl ignores the click rather than sends an error message. If you specify an invalid or nonexistent configuration file, it sends an error message. It also makes your pages accessible to text browsers.

NCSA Imagemap

Perhaps the most well-used CGI program is imagemap.c, the imagemap program that is included in the NCSA server package (which, at the time of this writing, is the most popular server in use on the Internet). It is fairly full-featured, thanks to patches contributed by many people over the last few years.

NCSA's imagemap has both a central configuration file and a decentralized configuration. You can specify separate configuration files on your own the same way you do with imagemap.pl-by including it in the PATH_TRANSLATED variable. If you don't specify the location of the .map file, it checks a central configuration file for its location. If it can't find the location there (or if it can't find a central configuration file), then it gives an error.

The imagemap.c program also handles several different kinds of shapes: rectangles, circles, polygons, and points. It also lets you specify a default URL for the undefined regions. A typical .map file might look like the following:

default /index.html
rect /chapter1/index.html 0,0 100,40
circle http://www.harvard.edu/ 80,80 60,80
poly http://www.math.harvard.edu/ 120,0 140,0 145,20 115,25
point http://www.thespot.com/ 227,227

With rectangles, the first point is the upper-left corner, and the second is the lower-right corner. The first point on the circle coordinate is the center point, and the second point is any point on the edge of the circle. Each point following the poly attribute is a vertex of the polygon, and point specifies whether the user clicks near that point.

Finally, because imagemap.c is written in C, it is much more responsive than imagemap.pl (written in Perl). If you use a lot of imagemapping on your site, then you probably want to use a C version of imagemap. The code for imagemap.c is shown in Listing 15.4.


Listing 15.4. The imagemap.c program.
/*
** mapper 1.2
** 7/26/93 Kevin Hughes, kevinh@pulua.hcc.hawaii.edu
** "macmartinized" polygon code copyright 1992 by Eric Haines, erich@eye.com
** All suggestions, help, etc. gratefully accepted!
**
** 1.1 : Better formatting, added better polygon code.
** 1.2 : Changed isname(), added config file specification.
**
** 11/13/93: Rob McCool, robm@ncsa.uiuc.edu
**
** 1.3 : Rewrote configuration stuff for NCSA /htbin script
**
** 12/05/93: Rob McCool, robm@ncsa.uiuc.edu
**
** 1.4 : Made CGI/1.0 compliant.
**
** 06/27/94: Chris Hyams, cgh@rice.edu
**          Based on an idea by Rick Troth (troth@rice.edu)
**
** 1.5 : Imagemap configuration file in PATH_INFO. Backwards compatible.
**
**  Old-style lookup in imagemap table:
**    <a href="http://foo.edu/cgi-bin/imagemap/oldmap">
**
**  New-style specification of mapfile relative to DocumentRoot:
**    <a href="http://foo.edu/cgi-bin/imagemap/path/for/new.map">
**
**  New-style specification of mapfile in user's public HTML directory:
**    <a href="http://foo.edu/cgi-bin/imagemap/~username/path/for/new.map">
**
** 07/11/94: Craig Milo Rogers, Rogers@ISI.Edu
**
** 1.6 : Added "point" datatype: the nearest point wins. Overrides "default".
**
** 08/28/94: Carlos Varela, cvarela@ncsa.uiuc.edu
**
** 1.7 : Fixed bug: virtual URLs are now understood.
**      Better error reporting when not able to open configuration file.
**
** 03/07/95: Carlos Varela, cvarela@ncsa.uiuc.edu
**
** 1.8 : Fixed bug (strcat->sprintf) when reporting error.
**      Included getline() function from util.c in NCSA httpd distribution.
**
*/

#include <stdio.h>
#include <string.h>
#ifndef pyr
#include <stdlib.h>
#else
#include <ctype.h>
#endif
#include <sys/types.h>
#include <sys/stat.h>

#define CONF_FILE "/usr/local/etc/httpd/conf/imagemap.conf"

#define MAXLINE 500
#define MAXVERTS 100
#define X 0
#define Y 1
#define LF 10
#define CR 13

int isname(char);

int main(int argc, char **argv)
{
   char input[MAXLINE], *mapname, def[MAXLINE], conf[MAXLINE], errstr[MAXLINE];
   double testpoint[2], pointarray[MAXVERTS][2];
   int i, j, k;
   FILE *fp;
   char *t;
   double dist, mindist;
   int sawpoint = 0;

    if (argc != 2)
        servererr("Wrong number of arguments, client may not support ISMAP.");
    mapname=getenv("PATH_INFO");

    if((!mapname) || (!mapname[0]))
        servererr("No map name given. Please read the <A HREF=\"http://hoohoo.ncsa.uiuc.edu/docs/setup/admin/Imagemap.html\
Â">instructions</A>.<P>");


    mapname++;
    if(!(t = strchr(argv[1],',')))
        servererr("Your client doesn't support image mapping properly.");
    *t++ = '\0';
    testpoint[X] = (double) atoi(argv[1]);
    testpoint[Y] = (double) atoi(t);

    /*
     * if the mapname contains a '/', it represents a unix path -
     * we get the translated path, and skip reading the configuration file.
     */
    if (strchr(mapname,'/')) {
      strcpy(conf,getenv("PATH_TRANSLATED"));
      goto openconf;
    }

    if ((fp = fopen(CONF_FILE, "r")) == NULL){
        sprintf(errstr, "Couldn't open configuration file: %s", CONF_FILE);
        servererr(errstr);
    }

    while(!(getline(input,MAXLINE,fp))) {
        char confname[MAXLINE];
        if((input[0] == '#') || (!input[0]))
            continue;
        for(i=0;isname(input[i]) && (input[i] != ':');i++)
            confname[i] = input[i];
        confname[i] = '\0';
        if(!strcmp(confname,mapname))
            goto found;
    }
    /*
     * if mapname was not found in the configuration file, it still
     * might represent a file in the server root directory -
     * we get the translated path, and check to see if a file of that
     * name exists, jumping to the opening of the map file if it does.
     */
    if(feof(fp)) {
      struct stat sbuf;
      strcpy(conf,getenv("PATH_TRANSLATED"));
      if (!stat(conf,&sbuf) && ((sbuf.st_mode & S_IFMT) == S_IFREG))
    goto openconf;
      else
    servererr("Map not found in configuration file.");
    }

  found:
    fclose(fp);
    while(isspace(input[i]) || input[i] == ':') ++i;

    for(j=0;input[i] && isname(input[i]);++i,++j)
        conf[j] = input[i];
    conf[j] = '\0';

  openconf:
    if(!(fp=fopen(conf,"r"))){
    sprintf(errstr, "Couldn't open configuration file: %s", conf);
        servererr(errstr);
    }

    while(!(getline(input,MAXLINE,fp))) {
        char type[MAXLINE];
        char url[MAXLINE];
        char num[10];

        if((input[0] == '#') || (!input[0]))
            continue;

        type[0] = '\0';url[0] = '\0';

        for(i=0;isname(input[i]) && (input[i]);i++)
            type[i] = input[i];
        type[i] = '\0';

        while(isspace(input[i])) ++i;
        for(j=0;input[i] && isname(input[i]);++i,++j)
            url[j] = input[i];
        url[j] = '\0';

        if(!strcmp(type,"default") && !sawpoint) {
            strcpy(def,url);
            continue;
        }

        k=0;
        while (input[i]) {
            while (isspace(input[i]) || input[i] == ',')
                i++;
            j = 0;
            while (isdigit(input[i]))
                num[j++] = input[i++];
            num[j] = '\0';
            if (num[0] != '\0')
                pointarray[k][X] = (double) atoi(num);
            else
                break;
            while (isspace(input[i]) || input[i] == ',')
                i++;
            j = 0;
            while (isdigit(input[i]))
                num[j++] = input[i++];
            num[j] = '\0';
            if (num[0] != '\0')
                pointarray[k++][Y] = (double) atoi(num);
            else {
                fclose(fp);
                servererr("Missing y value.");
            }
        }
        pointarray[k][X] = -1;
        if(!strcmp(type,"poly"))
            if(pointinpoly(testpoint,pointarray))
                sendmesg(url);
        if(!strcmp(type,"circle"))
            if(pointincircle(testpoint,pointarray))
                sendmesg(url);
        if(!strcmp(type,"rect"))
            if(pointinrect(testpoint,pointarray))
                sendmesg(url);
        if(!strcmp(type,"point")) {
        /* Don't need to take square root. */
        dist = ((testpoint[X] - pointarray[0][X])
            * (testpoint[X] - pointarray[0][X]))
           + ((testpoint[Y] - pointarray[0][Y])
             * (testpoint[Y] - pointarray[0][Y]));
        /* If this is the first point, or the nearest, set the default. */
        if ((! sawpoint) || (dist < mindist)) {
        mindist = dist;
            strcpy(def,url);
        }
        sawpoint++;
    }
    }
    if(def[0])
        sendmesg(def);
    servererr("No default specified.");
}

sendmesg(char *url)
{
  if (strchr(url, ':')) /*** It is a full URL ***/
    printf("Location: ");
  else                   /*** It is a virtual URL ***/
    printf("Location: http://%s:%s", getenv("SERVER_NAME"),
           getenv("SERVER_PORT"));

    printf("%s%c%c",url,10,10);
    printf("This document has moved <A HREF=\"%s\">here</A>%c",url,10);
    exit(1);
}

int pointinrect(double point[2], double coords[MAXVERTS][2])
{
        return ((point[X] >= coords[0][X] && point[X] <= coords[1][X]) &&
        (point[Y] >= coords[0][Y] && point[Y] <= coords[1][Y]));
}

int pointincircle(double point[2], double coords[MAXVERTS][2])
{
        int radius1, radius2;

        radius1 = ((coords[0][Y] - coords[1][Y]) * (coords[0][Y] -
        coords[1][Y])) + ((coords[0][X] - coords[1][X]) * (coords[0][X] -
        coords[1][X]));
        radius2 = ((coords[0][Y] - point[Y]) * (coords[0][Y] - point[Y])) +
        ((coords[0][X] - point[X]) * (coords[0][X] - point[X]));
        return (radius2 <= radius1);
}

int pointinpoly(double point[2], double pgon[MAXVERTS][2])
{
        int i, numverts, inside_flag, xflag0;
        int crossings;
        double *p, *stop;
        double tx, ty, y;

        for (i = 0; pgon[i][X] != -1 && i < MAXVERTS; i++)
                ;
        numverts = i;
        crossings = 0;

        tx = point[X];
        ty = point[Y];
        y = pgon[numverts - 1][Y];

        p = (double *) pgon + 1;
        if ((y >= ty) != (*p >= ty)) {
                if ((xflag0 = (pgon[numverts - 1][X] >= tx)) ==
                (*(double *) pgon >= tx)) {
                        if (xflag0)
                               crossings++;
                }
                else {
                        crossings += (pgon[numverts - 1][X] - (y - ty) *
                        (*(double *) pgon - pgon[numverts - 1][X]) /
                        (*p - y)) >= tx;
                }
        }

        stop = pgon[numverts];

        for (y = *p, p += 2; p < stop; y = *p, p += 2) {
                if (y >= ty) {
                        while ((p < stop) && (*p >= ty))
                                p += 2;
                        if (p >= stop)
                                break;
                        if ((xflag0 = (*(p - 3) >= tx)) == (*(p - 1) >= tx)) {
                                if (xflag0)
                                        crossings++;
                        }
                        else {
                                crossings += (*(p - 3) - (*(p - 2) - ty) *
                            (*(p - 1) - *(p - 3)) / (*p - *(p - 2))) >= tx;
                        }
                }
                else {
                        while ((p < stop) && (*p < ty))
                                p += 2;
                        if (p >= stop)
                                break;
                        if ((xflag0 = (*(p - 3) >= tx)) == (*(p - 1) >= tx)) {
                                if (xflag0)
                                        crossings++;
                        }
                        else {
                                crossings += (*(p - 3) - (*(p - 2) - ty) *
                                (*(p - 1) - *(p - 3)) / (*p - *(p - 2))) >= tx;
                        }
                }
        }
        inside_flag = crossings & 0x01;
        return (inside_flag);
}

servererr(char *msg)
{
    printf("Content-type: text/html%c%c",10,10);
    printf("<title>Mapping Server Error</title>");
    printf("<h1>Mapping Server Error</h1>");
    printf("This server encountered an error:<p>");
    printf("%s", msg);
    exit(-1);
}

int isname(char c)
{
        return (!isspace(c));
}

int getline(char *s, int n, FILE *f) {
    register int i=0;

    while(1) {
        s[i] = (char)fgetc(f);

        if(s[i] == CR)
            s[i] = fgetc(f);

        if((s[i] == 0x4) || (s[i] == LF) || (i == (n-1))) {
            s[i] = '\0';
            return (feof(f) ? 1 : 0);
        }
        ++i;
    }
}

Although imagemap.c is very powerful, it lacks two important features that imagemap.pl has. First, if no default is specified, it sends an error if the user clicks on an undefined region. Second, it is not friendly for text browsers. Being able to define an alternative text file for your imagemaps in the .map file would be nice. Modifying imagemap.c to handle both problems is fairly easy, and I encourage you to try it as an exercise.

Summary

Imagemapping literally enables you to add a new dimension to your Web sites. Although it is not clear whether it is the role of the client or the server to handle imagemapping (both have been implemented), you can find some advantages to handling imagemaps on the server side using CGI applications.

You can get imagemap information from the client in two ways: HTML forms and the ISMAP attribute of the <IMG> tag. Both methods are almost equally usable and powerful, although the latter method is preferable because it is supported by more browsers and is slightly more flexible.