Chapter 34

A Graphical Web Page Counter


CONTENTS


A page counter is a simple script that runs each time a page is accessed and updates a data file. Each time a page is viewed, the script must read in a count from a data file and increment the count. The count then is displayed, as shown in Figure 34.1. Ideally, it would be easiest to call a program from within an HTML document. Because doing so won't become practical until the HTML 3.0 standard is agreed on, the question then becomes how to circumvent this restriction. The answer comes from the way images are viewed within an HTML document. By using the IMG tag, you easily can view an image. The source field within the IMG tag can specify any URL that can be read as a graphic image. In this chapter, I discuss using the IMG tag and examine two different programs that can generate a graphics image. The image is simply the number of times a page has been accessed.

Figure 34.1: An example of the images generated from the two examples.

Before I discuss the two methods in detail, I'll give you a simple sample script, written in C. The basic idea is to open a data file specified as an argument, read in a number from the file, increment the number, and write the new number to the file. Given the number read from the file, the script prints out the number as plain text that any WWW browser can read. Ironically, graphics-based browsers such as Mosaic and Netscape cannot process plain text within an image field and can handle only more complicated image formats such as GIF. Only text-based browsers can handle text as an image.

Because of this drawback, I discuss two methods of generating graphics output in this chapter. The first and simpler example converts the number into the X Window System's bitmap format. The second example builds an image through the use of Silicon Graphics's (SGI) Open Inventor graphics libraries (Open Inventor is commercially licensed). By building a scene as an Open Inventor scene database, you easily can convert the scene into SGI's RGB graphics file format. After you create and store the 3-D scene in the RGB format, you have to convert it to the GIF format by using the utilities in the netpbm libraries.

The IMG Tag

The goal is to count and display the number of times an HTML document has been loaded by a WWW browser. To execute a program, you use the IMG tag. Before you see the actual program, I'll discuss the format of the image tag and the method for executing a program.

Here are two flavors of the IMG tag:

<IMG SRC="http://www.this.here.machine/~user/document.name">
<IMG ALIGN=alignment SRC="http://www.this.here.machine/~user/document.name">

The first version simply places an image at the current location and justifies any text to line up with the bottom of the image. The second version enables you to specify where you want the text to be placed. The alignment option can be top or middle. By specifying a URL for the image, you can specify an executable program.

You execute the counting program by specifying its URL in the source field of the IMG tag. As long as the output of the program is consistent with the MIME standard, the output is treated as an image and is displayed in the appropriate manner. If I have a script called counter in my own cgi directory, ~black/public_html/cgi-bin, for example, and the program can open up the file that is passed as its command-line argument, the program can be executed each time a page is loaded into a browser:

<IMG ALIGN=MIDDLE SRC="http://www.math.unh.edu/~black/cgi-bin
/counter?example.dat">

Here I have specified that any text outside the image be lined up with the middle of the image (the alignment field is optional). When it is called, the program (called counter here) updates the count found in example.dat and displays the current number.

Counting Each Time a Page Is Viewed

The idea is to execute a program each time a page is accessed. At first glance, the program itself has a simple job to do. It must open a file, increment a counter, update the file, and output the result. The program is to be executed using the IMG tag. The downside is that if the IMG tag is used, the program's output has to be in a graphics format that is understood by the majority of available Web browsers.

In the following sections, you look at two examples: the first sends the output in the X Window System's bitmap format, and the second creates a file in SGI's RGB format and then converts it to a GIF.

A Simple Test Script

Because the difficulties in creating a counter center on the conversion of output to a convenient graphics format, it is easy to forget that the ultimate goal is to simply count the number of times someone looks at your page. This first example is a simple program that keeps track of and prints the number of times it has been run. The two programs in which output is in a graphics format are built on this example.

To maintain a count, the file in which the count will be stored is passed as a command-line argument so that the same program eventually can be used as a counter for more than one page. The program simply opens the file (if it exists), updates the count, and saves the new count in the file. Given the updated count, an appropriate message is printed. Because a file is opened, some subtleties are involved here. The file might not exist and, if not, the count is initialized to one. Furthermore, on a UNIX system, the user NOBODY or FTP on some systems owns the process and must be able to read and write to the file in the specified directory. If the file does not exist, the file that is created will be owned by NOBODY, and the user might not be able to read or write to the file without help from the system administrator.

Listing 34.1 shows the steps required to implement a simple counter. An updated file can be found at http://www.math.unh.edu/~black/resource/simpleCounter.c++.


Listing 34.1. simpleCounter.c++.
// Set a few default values

// Set the default file name for the count.
// If a command line argument is given this will
// be overridden.
#define COUNTER_FILE_NAME  "counter.dat"
#define MAX_CHAR_LENGTH 256


#include <stdlib.h>
#include <stdio.h>
#include <strings.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>



void main(int argc, char **argv) {

  char counterName[MAX_CHAR_LENGTH];
  unsigned long num;
  FILE *fp;


  // Set the default counter name.
  // If an argument was passed use the argument for
  // the filename where the current count is kept.
  strcpy(counterName,COUNTER_FILE_NAME);
  if(argc>1)
    strcpy(counterName,argv[1]);


  // Open the file with the current count.
  // If file does not exist initialize count
  //      to zero.
  fp = fopen(counterName,"r");
  if(fp==NULL)
    num = 0;
  else {
    fscanf(fp,"%d",&num);
    fclose(fp);
  }

  // Update the count and save the new count.
  ++num;
  if(fp = fopen(counterName,"w")) {
    fprintf(fp,"%d\n",num);
    fclose(fp);
  }


  printf("Content-type:text/html\n\n");
  printf("<html>\n");
  printf("<head>\n");
  printf("<title>text counter</title>\n");
  printf("</head>\n");
  printf("<body>\n");
  printf("This page has been accessed %d times.\n",num);
  printf("</body>\n");
  printf("</html>\n");

  exit(0);

}

In all the examples, the program must specify the format of its output. In this example, because the output is to be an HTML text file, the first line must tell the browser what to expect:

printf("Content-type:text/html\n\n");

After the browser knows the format of the program's output, it can react accordingly.

If this program resides in my cgi-bin directory, for example, and is called simpleCounter.cgi, the full path name is ~black/public_html/cgi-bin/simpleCounter.cgi. If I want to keep track of the number of times a page has been viewed using a file called ~black/public_html/cgi-bin/data/example.dat, then the program is called using the following URL:

http://www.math.unh.edu/~black/cgi-bin/simpleCounter.cgi?data/example.dat

When this program is run as a CGI script, it sends out the single sentence

This page has been accessed 12 times

The number is updated each time you reload the file.

Image in X-Bitmap Format

The bad news is that, to be used within the IMG tag, the program must send its output in a graphics format. The simplest thing to do is to use the X Window System's bitmap format. By specifying the bitmap for each number, you can construct the final bitmap by arranging the bitmaps in the proper order. Of course, this is easier said than done; refer to the following example.

The examples shown in Listings 34.2 and 34.3 update the counter in the same way as the first example. The program tests to see whether a text-based browser is being used, however. If so, the output is simply the number, and the program exits. If the browser is graphics-based, the program converts the number into the X Window System's bitmap format. The individual digits to be displayed are stored in the array VISITS, and this array is used to subscript into the array NUMBER, which contains the bitmaps for each number. The entries in NUMBER[3][.], for example, contain the bitmap for the number 3. The array NUMBER is defined in the file bitmapCounter.h (see Listing 34.3) and easily can be replaced by the bitmaps of your choice. I used the X Window System's program bitmap to create the bitmaps and simply converted the output to an array of strings using a Perl script. An updated file can be found at http://www.math.unh.edu/~black/resource/bitmapCounter.c++.


Listing 34.2. bitmapCounter.c++.
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>

#include "bitmapCounter.h"


// Set the default file name for the count.
// If a command line argument is given this will
// be overridden.
#define COUNTER_FILE_NAME  "counter.dat"
#define MAX_CHAR_LENGTH 256


#define WIDTH 6

void main(int argc, char **argv) {

  char counterName[MAX_CHAR_LENGTH];
  int visits[WIDTH+1];
  unsigned long num,i,j;
  FILE *fp;
  const char *path;


  // Set the default counter name.
  // If an argument was passed use the argument for
  // the filename where the current count is kept.
  strcpy(counterName,COUNTER_FILE_NAME);
  if(argc>1)
    strcpy(counterName,argv[1]);


  // Open the file with the current count.
  // If file does not exist initialize count
  //      to zero.
  fp = fopen(counterName,"r");
  if(fp==NULL)
    num = 0;
  else {
    fscanf(fp,"%d",&num);
    fclose(fp);
  }

  // Update the count and save the new count.
  ++num;
  if(fp = fopen(counterName,"w")) {
    fprintf(fp,"%d\n",num);
    fclose(fp);
  }


  // Test to see if this is a text based browser
  path = getenv("PATH_INFO");
  if((path!=NULL)&&(strstr(path,"text"))) {
    printf("Content-type:text/plain\n\n");
    printf("%d\n",num);
  }

  else {

    // Convert the current count to an array of numbers.
    visits[WIDTH] = '\0';
    for(i=0;i<WIDTH;++i) {
      j = num%10;
      visits[WIDTH-1-i] = j;
      num /= 10;
    }

    // MIME type is x bitmap
    printf("Content-type:image/x-xbitmap\n\n");

    // print the counter definitions
    printf("#define counter_width %d\n",WIDTH*counter_width);
    printf("#define counter_height %d\n\n",counter_height);

    // print out the bitmap itself
    printf("static char counter_bits[] = {\n");
    for(i=0;i<counter_height;++i) {
      for(j=0;j<WIDTH;++j) {
         printf("%s",number[visits[j]][i]);
         if((i<counter_height-1)||(j<WIDTH-1))
             printf(", ");
      }
      printf("\n");
    }
    printf("}\n");

  } /* else(strstr) */

}

Listing 34.3 shows the header file for bitmapCounter. An updated file is available at http://www.math.unh.edu/~black/resource/bitmapCounter.h.


Listing 34.3. bitmapCounter.h.
#define counter_width 8
#define counter_height 12

static char *number[10][12] =  {
  {"0x7e", "0x7e", "0x66", "0x66", "0x66", "0x66",
           "0x66", "0x66", "0x66", "0x66", "0x7e", "0x7e"},
  {"0x18", "0x1e", "0x1e", "0x18", "0x18", "0x18",
           "0x18", "0x18", "0x18", "0x18", "0x7e", "0x7e"},
  {"0x3c", "0x7e", "0x66", "0x60", "0x70", "0x38",
           "0x1c", "0x0c", "0x06", "0x06", "0x7e", "0x7e"},
  {"0x3c", "0x7e", "0x66", "0x60", "0x70", "0x38",
           "0x38", "0x70", "0x60", "0x66", "0x7e", "0x3c"},
  {"0x60", "0x66", "0x66", "0x66", "0x66", "0x66",
           "0x7e", "0x7e", "0x60", "0x60", "0x60", "0x60"},
  {"0x7e", "0x7e", "0x02", "0x02", "0x7e", "0x7e",
           "0x60", "0x60", "0x60", "0x66", "0x7e", "0x7e"},
  {"0x7e", "0x7e", "0x66", "0x06", "0x06", "0x7e",
           "0x7e", "0x66", "0x66", "0x66", "0x7e", "0x7e"},
  {"0x7e", "0x7e", "0x60", "0x60", "0x60", "0x60",
           "0x60", "0x60", "0x60", "0x60", "0x60", "0x60"},
  {"0x7e", "0x7e", "0x66", "0x66", "0x7e", "0x7e",
           "0x66", "0x66", "0x66", "0x66", "0x7e", "0x7e"},
  {"0x7e", "0x7e", "0x66", "0x66", "0x7e", "0x7e",
           "0x60", "0x60", "0x60", "0x66", "0x7e", "0x7e"}
};

/*  *********************************************************************
     The previous bitmaps were generated using the unix "bitmap" program.
     Each row contains the bitmap for each number.  For example, row
     three, number[3][.], contains the bitmap for the number 3.  This
     bitmap is kept separate to make it easier to exchange with the bitmaps
     of your choice.
    ********************************************************************* */

The program can be executed using the IMG tag whenever a page is viewed. If the program is called bitmapCounter.cgi, for example, the image is created when the browser comes across the IMG tag:

<IMG ALIGN=top SRC="http://www./~black/cgi-bin/bitmapCounter.cgi?data/Example.dat>

The output is shown in Figure 34.1 and compared with the output of the counter in the next section.

Open Inventor and a 3-D Counter

If you are not satisfied with the clunky look of the bitmapped images in the previous example, this example might be more to your liking. The Open Inventor 3-D graphics libraries offer an object-oriented environment to display 3-D objects. Through the use of a scene database, you can define objects and their orientations and display them in real time. After you define the objects, the actual render is handled by the routines available in the Open Inventor libraries. Moreover, the libraries allow for the easy conversion of a scene into an image file in Silicon Graphics's RGB format.

The downside is that you must convert the image file into the GIF format so that a graphics-based browser can read and display the image. Using the widely available routines found in the netpbm library, you can accomplish this conversion, but you must play a couple of games to pipe the final output to stdout. If a Web browser asks that NOBODY start a process on a remote server, and if that process, in turn, forks a child process, the browser does not get any information that is sent to the stdout stream of the child process. For this reason, the conversion routines that are forked from the server process cannot send their final output to their stdout stream but instead must pipe their output to the server process that then sends the output to its standard output.

For this example, I give a brief overview of the Open Inventor scene database (see the following section). For a better description of the libraries, you should examine other sources such as Wernecke and the Open Inventor man pages (see "Bibliography," later in this chapter). After the overview, I give an example in which a counter is used to update the number of hits on a Web page, and the number is converted to text that then is displayed as a 3-D object.

Open Inventor

Open Inventor is a commercially available, object-oriented, 3-D graphics system produced by Silicon Graphics (SGI). The standard graphics libraries that are part of SGI's operating system, IRIX, are the opengl libraries. The opengl libraries are a set of graphics routines explicitly designed to simplify programming 3-D graphics routines. The Open Inventor libraries represent a more convenient interface to opengl. To free you, the programmer, from worrying about the details of rendering an image, the Open Inventor libraries enable you to construct a tree, called the scene database, that defines objects and their transformations.

After you define the objects in a scene, the libraries offer many convenience routines that allow for real-time viewing and manipulation of the scene. Some convenience routines enable you to explicitly create certain actions, such as printing an image or picking an object in a scene. One of the fortunate by-products of the graphics-conversion process discussed later in this chapter is that the final GIF output maintains the same view, pixel by pixel.

The Open Inventor libraries include routines that can pick a given 3-D object by giving a pixel on the view. You can use the ISMAP option within the IMG tag to find which pixel a user has chosen in an image. In this way, you can use the Open Inventor libraries to build interactive 3-D graphics on the Web. After the objects in the scene database are defined, one of Open Inventor's greatest strengths is the ease of manipulating the scene.

Objects are defined as one of the simple primitives recognized by Open Inventor (cubes, spheres, and cylinders), a mesh, or a 2-D or 3-D text field. In this example, a 3-D text field is used to display the count. You can manipulate the objects in the scene database in a variety of ways. You can transform an object's position through a translation, rotation, scaling, or other manipulation of the coordinate axis. You also can change the appearance by specifying the color properties of an object. Because the order in which translations and color changes matters, you also can isolate the effects of these changes. The Open Inventor libraries are quite extensive and quite powerful. Because I cannot do justice to the full capabilities of Open Inventor, the focus of this section remains on the basic capabilities.

Figure 34.2 shows a sample scene database for a sphere and a box. The two objects are offset and have the color properties given within the scene database.

Figure 34.2: A sample scene database for a sphere and a box.

Before building the more complicated scene database required for the counter, examine the simple scene database shown in Figure 34.2. The scene should consist of a red sphere of radius 2.5 centered at the point (2,1,3) and a blue box centered at (6,4,2) whose sides are length 3.5, 1.5, and 0.5. Before viewing a scene, you must define a camera and light that specify exactly what the rendering contains and how it is viewed. The scene database is a tree, and to find the actions that are performed to render the scene, you must traverse the tree. To do so, first start at the very top of the tree, perform the action specified by the current node, and then, starting with the leftmost child, do the same for each child. At any point in the tree, you first perform the action defined by the present node and then move through its children from left to right.

To build the scene database for this example, you initialize the top (or root) node; the first child is a light, the second child is a camera, and two objects are placed in the tree, as shown in Figure 34.2. Because you are to place the two objects at two different locations, a transformation of coordinates is required. By making the objects (a sphere and a cube) the children of a separator, any changes in color and transformation do not affect any other objects that come after them while you're moving through the tree.

Of course, when it comes to adding something like a 3-D text item, it is not quite so easy. To display text, you must specify the font and the font size. Moreover, the font defines only how the front of the object is to look; you also must specify a cross-section to make it a 3-D object. You do this first by specifying a vector containing the coordinates of the profile (perpendicular to the face of each letter) and another vector specifying the order in which the entries in the coordinate vector are to be evaluated. These manipulations are demonstrated in Figure 34.3; for a more complete description, see Wernecke.

Figure 34.3: A scene database for a piece of 3-D text.

The scene database to be constructed must place a single piece of text in the view of the camera, as shown in Figure 34.3. The camera is placed on the positive z axis pointing at the origin. The text is put in place with a text node that centers the text at the origin, facing toward the positive z axis. To accentuate the full 3-D effect, the scene database includes a transformation so that the camera looks at the text at an angle. You do this by placing the light and camera and then rotating the coordinate system around the y axis. For this example, the material or color of the text is set to red. After you set the color, you place a material binding node in the scene database. This is done to define the way in which the color is mapped onto the object, which, for this example, makes the entire text object red.

Next, you define the font. Following the font, you define the profile with the index to the profile as the next child. Finally, you place the text itself at the current origin.

After you define the scene database, you save it to an image file in SGI's RGB format. You do this by setting the camera's viewport to a predefined size and rendering the scene in a local buffer. Through the use of the Off Screen Rendering action, the scene is rendered and saved in an image file. You specify the file in which the image is stored through the use of the UNIX tmpnam command. Before exiting, you remove this file using the UNIX remove command. When implementing this example, you first should find out how these commands are implemented on your UNIX system. If the program exits before removing the file, you easily can leave many image files in one of your temporary directories.

After the file name is found, the scene is rendered and saved to a file. In this step, the principal disadvantage of the Inventor libraries is demonstrated. To be able to render the scene in a local buffer, the library acts as an X Window System's client and must act through the X Manager. If the Web server that is running a program is not running the X Manager, the program cannot render the objects defined in the scene database.

Converting to GIF Format

After you generate the scene and save the image to a temporary file, you must convert that image to the GIF format. In the example shown in Listing 34.4, two methods are given. Both methods use the netpbm libraries to fork the required conversion routines from the server process. Because a forked process cannot reliably send output to its stdout and be picked up by a Web browser, both methods rely on a server process to pass the information, via pipes, to the necessary processes. The difference is that one requires three pipes, whereas the other requires two.


Listing 34.4. exampleCounter.c++.
// Set a few default values

// 1) set the default font and font size
#define FONT_TO_BE_USED "Helvetica-Bold"
#define FONT_SIZE 5.0

// 2) Factor to set the camera's z - position
#define CAMERA_HEIGHT 0.25

// Set the default file name for the count.
// If a command line argument is given this will
// be overridden.
#define COUNTER_FILE_NAME  "counter.dat"
#define MAX_CHAR_LENGTH 256


#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

#include <Inventor/Xt/SoXt.h>
#include <Inventor/Xt/SoXtRenderArea.h>
#include <Inventor/nodes/SoDirectionalLight.h>
#include <Inventor/nodes/SoMaterial.h>
#include <Inventor/nodes/SoPerspectiveCamera.h>
#include <Inventor/nodes/SoSeparator.h>
#include <Inventor/nodes/SoFont.h>
#include <Inventor/nodes/SoText3.h>
#include <Inventor/nodes/SoMaterialBinding.h>
#include <Inventor/nodes/SoTransform.h>
#include <Inventor/nodes/SoProfileCoordinate2.h>
#include <Inventor/nodes/SoLinearProfile.h>


#include <Inventor/SoOffscreenRenderer.h>
#include <Inventor/SbViewportRegion.h>
#include <Inventor/SbLinear.h>
#include <Inventor/fields/SoSFVec3f.h>

#include <Inventor/SoDB.h>
#include <Inventor/nodekits/SoNodeKit.h>
#include <Inventor/SoInteraction.h>


/* Routine to build the scene data base */
SoSeparator* buildScene(char *visits,SoCamera *theCamera);

/* Routines to convert from RGB to GIF format */
void convertToGIF(char *tmpSGIName);
void convertToGIF2Files(char *tmpSGIName);


/* Routine to handle an interrupt.  (need to
   delete temporary files!) */
void handleSignal(void);

/* Make temporary file names global in case they have to be
   deleted by the interrupt handler. */
  char tmpSGIName[L_tmpnam];
  char tmpGIFName[L_tmpnam];


void main(int argc, char **argv) {

  char counterName[MAX_CHAR_LENGTH];
  char visits[7];
  unsigned long num,i,j;
  FILE *fp;
  const char *path;

  // Initialize file names to NULL */
  tmpSGIName[0] = '\0';
  tmpGIFName[0] = '\0';


  if(signal(SIGTERM,handleSignal)==SIG_ERR) {
    perror("picture.c++ : Could not initialize signal interrupt");
  }


  // Set the default counter name.
  // If an argument was passed use the argument for
  // the filename where the current count is kept.
  strcpy(counterName,COUNTER_FILE_NAME);
  if(argc>1)
    strcpy(counterName,argv[1]);


  // Open the file with the current count.
  // If file does not exist initialize count
  //      to zero.
  fp = fopen(counterName,"r");
  if(fp==NULL)
    num = 0;
  else {
    fscanf(fp,"%d",&num);
    fclose(fp);
  }

  // Update the count and save the new count.
  ++num;
  if(fp = fopen(counterName,"w")) {
    fprintf(fp,"%d\n",num);
    fclose(fp);
  }


  // Check to see if this is a text based browser
  path = getenv("PATH_INFO");
  if((path!=NULL)&&(strstr(path,"text"))) {
    printf("Content-type:text/plain\n\n");
    printf("%d\n",num);
    exit(0);
  }

  // Convert the current count to an ASCII string.
  visits[6] = '\0';
  for(i=0;i<6;++i) {
    j = num%10;
    visits[5-i] = '0' + (char) j;
    num /= 10;
  }

  // Set dislay.  Inventor bombs out if it cannot
  // open an X client.
  putenv("DISPLAY=your.machine.com:0.0");

  // Initialize Inventor and Xt
  Widget myWindow = SoXt::init(argv[0]);
  if (myWindow == NULL) exit(1);


  // Initialize the scene data base.
  SoSeparator *root;
  SoPerspectiveCamera *myCamera = new SoPerspectiveCamera;
  root = buildScene(visits,myCamera);


  // Original version set up a window to help debug
  // the OpenInventor part.
  // Not needed now.
  //
  // SoXtRenderArea *myRenderArea = new SoXtRenderArea(myWindow);
  // myCamera->viewAll(root, myRenderArea->getViewportRegion());
  // myRenderArea->setSceneGraph(root);
  // myRenderArea->setTitle("Trackball");
  // myRenderArea->show();

  // SoXt::show(myWindow);
  // SoXt::mainLoop();

  // Set up the viewport and the camera position.
  SbViewportRegion vp;
  vp.setWindowSize(SbVec2s(200,100));
  myCamera->viewportMapping = SoCamera::ADJUST_CAMERA;
  myCamera->viewAll(root,vp,2.0);

  float x=0.0,y=0.0,z=0.0;
  SbVec3f pos;

  pos = myCamera->position.getValue();
  pos.getValue(x,y,z);
  myCamera->position.setValue(x,y,CAMERA_HEIGHT*z);

  // Get ready to apply the actual rendering
  SoOffscreenRenderer myRender(vp);
  myRender.render(root);

  // *******************************************************
  // The scene has been created and we need to convert
  // it to a gif format to be output to stdout.
  //
  // The scene will be written to a temporary file in
  // SGI's rgb format.  This file will be converted to
  // gif using the netpbm routines.  To do so the rgb
  // data file will be fed to the sgitopnm program.
  // This process will be forked off and it will get
  // its data through a pipe.
  //
  // The output for the sgitopnm process will be piped
  // to another forked process, ppmtogif, which will
  // make the final conversion to gif.  The output from
  // this final process will be piped to another temporary
  // file whose contents will be printed to stdout by the
  // server process.
  // ******************************************************

  // Render the scene and save it in rgb format in the
  // file tmpSGIName.
  char *theName;

  theName = tmpnam(tmpSGIName);
  fp = fopen(tmpSGIName,"w");
  myRender.writeToRGB(fp);
  fclose(fp);

  // convert the rgb file to gif and send output to stdout.
#define USE_ONE_TMP_FILE
#ifdef USE_ONE_TMP_FILE
  convertToGIF(tmpSGIName);
#else
  convertToGIF2Files(tmpSGIName);
#endif

  // clean up the temporary files.
  remove(tmpSGIName);

  exit(0);

}


//  Routine to build the scene to be viewed.
//  Returns a pointer to the root node of the tree.
// The scene is simply the string "visits" rotated about
// the y axis.
SoSeparator* buildScene(char *visits,SoCamera *theCamera) {

  // initialize the root node
  SoSeparator *root = new SoSeparator;
  root->ref();


  // Set the camera and the light.
  // Will reference the camera later when the view changes.
  Root->addChild(theCamera);
  root->addChild(new SoDirectionalLight);

  // Rotate world coordinates so that number is at an angle.
  // This will emphasize the 3D effect.
  SoTransform *rotateIt = new SoTransform;
  rotateIt->rotation.setValue(SbVec3f(0.0,1.0,0.0),-0.6);
  root->addChild(rotateIt);

  // Make the numbers red.
  SoMaterial *numberMat = new SoMaterial;
  numberMat->ambientColor.setValue(0.7, 0.7, 0.7);
  numberMat->diffuseColor.setValue(0.7, 0.1, 0.1);
  numberMat->specularColor.setValue(0.5,0.5, 0.5);
  numberMat->shininess = 0.5;
  root->addChild(numberMat);

  // The material will be mapped to the every object
  SoMaterialBinding *theBinding = new SoMaterialBinding;
  theBinding->value = SoMaterialBinding::PER_PART;
  root->addChild(theBinding);


  // Set the font
  SoFont *theFont = new SoFont;
  theFont->name.setValue(FONT_TO_BE_USED);
  theFont->size.setValue(FONT_SIZE);
  root->addChild(theFont);

  // Put a small bevel on the characters
  // and add lots of depth!
  SoProfileCoordinate2 *theProfile = new SoProfileCoordinate2;
  SbVec2f coords[4];
  coords[0].setValue(0.0,0.0);
  coords[1].setValue(0.5,0.1);
  coords[2].setValue(10.0,0.1);
  coords[3].setValue(10.2,0.0);
  theProfile->point.setValues(0,4,coords);
  root->addChild(theProfile);

  SoLinearProfile *theIndex = new SoLinearProfile;
  long index[4];
  index[0] = 0;
  index[1] = 1;
  index[2] = 2;
  index[3] = 3;
  theIndex->index.setValues(0,4,index);
  root->addChild(theIndex);

  // Finally set the text to be drawn
  SoText3 *theNumber = new SoText3;
  theNumber->parts = SoText3::ALL;
  theNumber->justification.setValue(SoText3::RIGHT);
  theNumber->string = visits;
  root->addChild(theNumber);


  return(root);

}




void convertToGIF(char *tmpSGIName) {


  // create a pipe for the sgitopnm process and spawn
  // the process.
  int pid;
  int pipesgi2pnm[2];
  if(pipe(pipesgi2pnm) < 0) {
    perror("No pipe for sgitopnm");
    exit(1);
  }

  pid = fork();
  if(pid<0) {
    perror("no fork for sgitopnm");
    exit(1);
  }


  if(pid==0) {
    // this must be the forked process.
    // redirect stdout to tmpSGIName.
    close(1);
    dup(pipesgi2pnm[1]);
    close(0);
    dup(pipesgi2pnm[0]);

    // execute sgitopnm.  Read from file tmpSGIName
    // and send stdout through pipe.
    execl("sgitopnm","sgitopnm",tmpSGIName,NULL);
    perror("sgi not done\n");
    exit(1);
  }


  // create a pipe to the ppmtogif process and spawn
  // the second process.
  int pipeppm2gif[2];
  if(pipe(pipeppm2gif) < 0) {
    perror("No pipe for ppmtogif");
    exit(1);
  }

  // Second fork
  pid=fork();
  if(pid<0) {
    perror("no fork for ppmtogif");
    exit(1);
  }

  if(pid==0) {
    close(1);
    dup(pipeppm2gif[1]);
    close(0);
    dup(pipeppm2gif[0]);

    // execute ppmtogif.  Convert the stream so that
    // black (#000000) is transparent.
    // Read from pipe which is mapped from stdin and
    // send output through pipe to server process.
    execl("ppmtogif","ppmtogif","-trans","#000000",NULL);
    perror("gif not done\n");
    exit(1);
  }


  // First thing to output is the MIME type.
  // No spaces!
  printf("Content-type:image/gif%c%c",10,10);


  // Read output from sgitopnm and send it to the
  // ppmtogif process.
  FILE *fpin,*fpout,*fpGIF;
  char c,*p;
  char pixel[MAX_CHAR_LENGTH];
  unsigned long row,col;

  fpin  = fdopen(pipesgi2pnm[0],"r");
  fpout = fdopen(pipeppm2gif[1],"w");
  fpGIF = fdopen(pipeppm2gif[0],"r");

  // skip over header
  do {
    c = getc(fpin);
    putc(c,fpout);
  } while (c!='\n');


  // Get number of pixels in rows
  p = pixel;
  do {
    c = getc(fpin);
    putc(c,fpout);
    *p++ = c;
  } while (c!='\n');
  *p = '\0';

  // Get number of pixels in columns
  do {
    c = getc(fpin);
    putc(c,fpout);
  } while (c!='\n');

  p = pixel;
  while(*p++!=' ');
  -p;
  *p = '\0';
  row = (unsigned long) atoi(pixel);
  p++;
  col = (unsigned long) atoi(p);

  // Loop through and take all of the output from sgitopnm and
  // send it to ppmtogif.
  int num = 0;
  while(num<3*row*col) {
    c = getc(fpin);
    putc(c,fpout);
    ++num;
  }


  // Close all of the pipes except for the input from
  // ppmtogif.
  fclose(fpout);
  fclose(fpin);
  close(pipesgi2pnm[1]);


  // When all of the processes have completed read in the
  // gif file from the open pipe and print it to stdout.
  while(wait(NULL)!=pid);

  while(!feof(fpGIF)) {
    c = getc(fpGIF);
    putchar(c);
  }
  fclose(fpGIF);
  fflush(stdout);


}





void convertToGIF2Files(char *tmpSGIName) {

  // Will need an additional temporary file to store intermediate
  // results.
  FILE *fp;
  char *theName;

  // create a pipe for the sgitopnm process and spawn
  // the process.
  int pid;
  int pipesgi2pnm[2];
  if(pipe(pipesgi2pnm) < 0) {
    perror("No pipe for sgitopnm");
    exit(1);
  }

  pid = fork();
  if(pid<0) {
    perror("no fork for sgitopnm");
    exit(1);
  }


  if(pid==0) {
    // this must be the forked process.
    // redirect stdout to tmpSGIName.
    close(1);
    dup(pipesgi2pnm[1]);
    close(0);
    dup(pipesgi2pnm[0]);

    // execute sgitopnm.  Read from file tmpSGIName
    // and send stdout through pipe.
    execl("sgitopnm","sgitopnm",tmpSGIName,NULL);
    perror("sgi not done\n");
    exit(1);
  }


  // create a pipe to the ppmtogif process and spawn
  // the second process.
  int pipeppm2gif[2];
  if(pipe(pipeppm2gif) < 0) {
    perror("No pipe for ppmtogif");
    exit(1);
  }

  theName = tmpnam(tmpGIFName);
  // Second fork
  pid=fork();
  if(pid<0) {
    perror("no fork for ppmtogif");
    exit(1);
  }

  if(pid==0) {
    fp = fopen(tmpGIFName,"w");
    close(1);
    dup(fileno(fp));
    close(0);
    dup(pipeppm2gif[0]);

    // execute ppmtogif.  Convert the stream so that
    // black (#000000) is transparent.
    // Read from pipe which is mapped from stdin and
    // send output to tmpGIFName.
    execl("ppmtogif","ppmtogif","-trans","#000000",NULL);
    perror("gif not done\n");
    exit(1);
  }


  // First thing to output is the MIME type.
  // No spaces!
  printf("Content-type:image/gif%c%c",10,10);


  // Read output from sgitopnm and send it to the
  // ppmtogif process.
  FILE *fpin,*fpout;
  char c,*p;
  char pixel[MAX_CHAR_LENGTH];
  unsigned long row,col;


  fpin  = fdopen(pipesgi2pnm[0],"r");
  fpout = fdopen(pipeppm2gif[1],"w");

  // skip over header
  do {
    c = getc(fpin);
    putc(c,fpout);
  } while (c!='\n');


  // Get number of pixels in rows
  p = pixel;
  do {
    c = getc(fpin);
    putc(c,fpout);
    *p++ = c;
  } while (c!='\n');
  *p = '\0';

  // Get number of pixels in columns
  do {
    c = getc(fpin);
    putc(c,fpout);
  } while (c!='\n');

  p = pixel;
  while(*p++!=' ');
  -p;
  *p = '\0';
  row = (unsigned long) atoi(pixel);
  p++;
  col = (unsigned long) atoi(p);

  // Read the output from sgitopnm and send it to ppmtogif.
  int num = 0;
  while(num<3*row*col) {
    c = getc(fpin);
    putc(c,fpout);
    ++num;
  }
  // close incoming pipe
  fclose(fpin);
  close(pipeppm2gif[0]);

  // close outgoing pipe
  fclose(fpout);
  close(pipesgi2pnm[1]);

  // Wait for children to die.
  while(wait(NULL)!=pid);

  // Children are dead.  Must've written the gif
  // file out to tmpGIFName by now.
  // Open the file and print it to stdout.
  fp = fopen(tmpGIFName,"r");
  while(!feof(fp)) {
    c = getc(fp);
    putchar(c);
  }
  fflush(stdout);

  // clean up the temporary files.
  remove(tmpGIFName);


}


/* Routine to delete temporary file names if an interrupt
   signal is sent to the process.  If somebody hits the
   "stop" button on their browser you want to exit gracefully! */

void handleSignal(void) {

  remove(tmpSGIName);
  remove(tmpGIFName);

  perror("Counter: Signal caught");

  exit(1);

}

The method using three pipes retrieves the pnm image from the netpbm routine sgitopnm through a pipe; this new image is sent to the ppmtogif conversion routine. The result from the final conversion then is sent to the server process, which then is sent to stdout (see Fig. 34.4). The difference between this method and the method using two pipes is that the latter saves the output of the conversion to GIF in another temporary file that then is read by the server process and sent to stdout. In the last step of the method requiring three pipes, the server process reads the output from a pipe after the final conversion process terminates. Because some folks get squeamish over this process, I put both methods in the code. Personally, I prefer using three pipes instead of creating a second temporary file.

For small images such as the counter in these examples, the method using one temporary file is quickest and most convenient. For larger files, however, you must use the method using two temporary files. The method using three pipes requires that all the output from the final conversion fit within the pipe's buffer. Although this is not a problem for the small image of a number, it is a problem for larger images.

For this discussion, I explain the more difficult method: the one using three pipes. To convert the image formats, you start the required programs via the fork command. The image data is sent back and forth from the server process through pipes. Because the routines send their output through their stdout, the standard output must be redirected through the proper pipe. The steps required are demonstrated in Figure 34.4, but for a more complete discussion, see Curry (see "Bibliography," later in this chapter).

Figure 34.4: Diagram of the pipes required to convert the graphics file in the SGI format to the GIF format.

To convert the RGB file to the GIF format, the file is read by the sgitopnm program, and its data is piped to the ppmtogif program that also is found in the netpbm distribution. To execute these routines, the server routine forks off two child processes through the use of the fork command. The fork command simply creates a new process that is nearly an exact copy of the original process. The only difference between this new process and the one that spawned it is that the process ID returned by the fork command is zero.

After the server forks the two conversion processes, the necessary programs are started with the use of the execl command. Both conversion routines print the converted image to their own standard output. To send this information through a pipe back to the server process, each process first must redirect its standard output, stdout. This is done with the dup command, which duplicates a file descriptor.

Every open file has a corresponding file descriptor associated with it; a file descriptor is simply an integer. Three standard files are opened by default for any C program: 0 for standard output, 1 for standard input, and 2 for standard error. The dup command accepts a file descriptor and creates a new file descriptor that points to the same file or pipe. The new descriptor takes on the smallest available value. The standard output or input can be redirected by closing it and then duplicating an existing pipe with the corresponding file descriptor.

Suppose that you want to redirect the standard output of a program to the output given by another file descriptor. In particular, suppose that you have an array of integers, int pipesgi2pnm[2], and the first entry in the array, pipesgi2ppm[0], contains a file descriptor. To direct the output to the standard output, close the standard output, close(0); then duplicate the file descriptor, dup(pipesgi2pnm[0]);. After you duplicate the file descriptor, any output that normally would have gone to the standard output is sent to the file associated with the specified file descriptor.

The idea is to take advantage of UNIX pipes and send the output of the conversion routines to the server process. Before the conversion processes are forked, file descriptors for new pipes are found using the pipe command. The new file descriptors for a pipe are defined, and then the fork command is called. Because the file descriptors are defined before the fork, both the server and the forked process retain the file descriptors. Because a pipe is defined in terms of file descriptors, information can be passed between the two processes in the same way that information is passed between a program and an open file.

The argument for the pipe command is an array of two integers. From the previous example, the file descriptors for a pipe are defined in the array pipesgi2pnm. Before forking a process to convert the image, the pipe command defines the pipes to send the information between the server and conversion process, pipe(pipesgi2pnm);. The pipes in the example conform to the convention given in Curry. The pipe from the first file descriptor, pipesgi2pnm[0], is used to send information from the child process to the server process, whereas the pipe from the second file descriptor, pipesgi2pnm[1], is used to send information from the server to the child process.

After the pipes are defined, the conversion process is forked from the server process. Before the actual conversion program is started, the standard output and input are redirected to send and receive information through the pipe. When it's done, you execute the conversion program by using the execl command. A disadvantage of execl is that you must specifically define the path to the program. I have implemented this by creating a symbolic link from the cgi-bin directory to the specific conversion routine. In this way, if another conversion routine is to be used, you easily can substitute other programs with a minimal amount of effort.

Bibliography

Barkakati, N., The Waite Group's Essential Guide to ANSI C, Howard W. Sams & Co., Indianapolis, IN, USA. 1988.
Curry, David A., Using C on the UNIX System, O'Reilly & Associates, Inc., Sebastopol, CA, USA. 1985.
Gilly, D., UNIX in a Nutshell, O'Reilly & Associates, Inc., Sebastopol, CA, USA. 1986.
netpbm man pages. Source files found at ftp.cs.ubc.ca in the ftp/archive/netpbm subdirectory.
Silicon Graphics IRIX 5.3 man pages.
Wall, L. and R. L. Schwartz, Programming Perl, O'Reilly & Associates, Inc., Sebastopol, CA, USA. 1991.
Wernecke, Josie, The Inventor Mentor, Addison-Wesley Publishing Company, Reading, MA, USA. 1993.

Web Counter Check