1. Using the GD library to produce images on the fly.

Documentation for the GD library can be found on what's currently known as the Stein Laboratory

GD allows you to create color drawings using a number of primitives and emit drawings as GIF files. There are basically four steps to creating an image using the GD.pm library:

Let's look at some examples.

tucotuco.cs.indiana.edu% pwd
/nfs/paca/home/user2/dgerman/httpd/htdocs
tucotuco.cs.indiana.edu% vi one
tucotuco.cs.indiana.edu% cat one
#!/usr/bin/perl

use GD;     

# create a new image
$im = new GD::Image(100,100);

# allocate some colors
$white = $im->colorAllocate(255,255,255);
$black = $im->colorAllocate(0,0,0);       
$red = $im->colorAllocate(255,0,0);      
$blue = $im->colorAllocate(0,0,255);

# make the background transparent and interlaced
$im->transparent($white);
$im->interlaced('true');

# Put a black frame around the picture
$im->rectangle(0,0,99,99,$black);

# Draw a blue oval
$im->arc(50,50,95,75,0,360,$blue);

# And fill it with red
$im->fill(50,50,$red);

# make sure we are writing to a binary stream
binmode STDOUT;

# Convert the image to GIF and print it on standard output
print $im->gif;
tucotuco.cs.indiana.edu%
This is the Hello World! of the GD library. Let's now create a GIF file with one and place it where it can be accessed from the web.
tucotuco.cs.indiana.edu% ./one > one.gif
tucotuco.cs.indiana.edu% file one.gif
one.gif:        GIF file, v89
tucotuco.cs.indiana.edu% pwd
/nfs/paca/home/user2/dgerman/httpd/htdocs
tucotuco.cs.indiana.edu% ls -l one.gif
-rw-r--r--   1 dgerman  students     623 Oct  5 23:17 one.gif
tucotuco.cs.indiana.edu%  
As you can see the file is in GIF format. We have placed it in our DocumentRoot so it can be accessed from the web. In fact you can find it here.

We can also include it here (as you can see, below) with the following HTML code:

<img src="http://tucotuco.cs.indiana.edu:19800/one.gif"> 
and there you have it:

But this, of course, is a static file.

We can create an image on the fly if we eliminate the intermediary file that we stored in our DocumentRoot. In other words if we send the file to the browser as soon as we are done creating the image. So let's try it.

tucotuco.cs.indiana.edu% pwd
/nfs/paca/home/user2/dgerman/httpd/htdocs
tucotuco.cs.indiana.edu% ls -l one
-rwx------   1 dgerman  students     809 Oct  5 23:13 one
tucotuco.cs.indiana.edu% cp one ../cgi-bin/one
tucotuco.cs.indiana.edu% cd ../cgi-bin
tucotuco.cs.indiana.edu% ls -l one
-rwx------   1 dgerman  students     809 Oct  5 23:32 one
tucotuco.cs.indiana.edu% 
But if you try to access the script now you would obtain:
tucotuco.cs.indiana.edu% tail -1 ../logs/error_log
[Mon Oct  5 23:34:06 1998] [error] malformed header from ...
  ... script. Bad header=GIF89ad: /u/dgerman/httpd/cgi-bin/one
tucotuco.cs.indiana.edu% 
where the output was edited for readability.

We remember that one a script is written a lot of responsibility is taken, the server simply passes the data to the script and then waits for its output which it then sends to the client browser.

That means that we need to add the MIME type for the GIF file if we want to offer it over the web because nobody is going to do it for us.

The following code then, differs in exactly one place from the program that created the GIF file in the DocumentRoot directory. The line is underlined.

tucotuco.cs.indiana.edu% pwd
/nfs/paca/home/user2/dgerman/httpd/cgi-bin
tucotuco.cs.indiana.edu% vi one
tucotuco.cs.indiana.edu% cat one
#!/usr/bin/perl
  use GD;     
# create a new image
  $im = new GD::Image(100,100);
# allocate some colors
  $white = $im->colorAllocate(255,255,255);
  $black = $im->colorAllocate(0,0,0);       
  $red = $im->colorAllocate(255,0,0);      
  $blue = $im->colorAllocate(0,0,255);
# make the background transparent and interlaced
  $im->transparent($white);
  $im->interlaced('true');
# Put a black frame around the picture
  $im->rectangle(0,0,99,99,$black);
# Draw a blue oval
  $im->arc(50,50,95,75,0,360,$blue);
# And fill it with red
  $im->fill(50,50,$red);
# make sure we are writing to a binary stream
  binmode STDOUT;
# Convert the image to GIF and print it on standard output
  print qq{Content-type: image/gif\n\n}; # first things first 
  print $im->gif;
tucotuco.cs.indiana.edu% 
Try the script.

It generates a new image (one and the same) every time it is being accessed.

Now we know how to generate images on the fly.

Let's make them more time sensitive.

Let's make a clock.

First, a digital clock.

tucotuco.cs.indiana.edu% ls -ld lecture*
drwx------   2 dgerman  students     512 Sep 10 01:15 lecture4
drwxr-xr-x   2 dgerman  students     512 Sep 27 23:45 lecture9
tucotuco.cs.indiana.edu% mkdir lecture10
tucotuco.cs.indiana.edu% cd lecture10
tucotuco.cs.indiana.edu% vi dclock     
tucotuco.cs.indiana.edu% cat dclock
#!/usr/bin/perl

use GD; 
print "Content-type: image/gif\n\n"; 
($seconds, $minutes, $hour) = localtime(time); 
if ($hour > 12) { 
  $hour -= 12; 
  $ampm = "pm"; 
} else { 
  $ampm = "am"; 
}
if ($hour == 0) {
  $hour = 12; 
}  
$time = sprintf("%02d:%02d:%02d %s", 
  $hour, $minutes, $seconds, $ampm); 

$time_length = length($time); 
$font_length = 8;
$font_height = 16;
$x = $font_length * $time_length;  
$y = $font_height; 
$image = new GD::Image($x, $y); 
$black = $image->colorAllocate(0, 0, 0); 
$yellow = $image->colorAllocate(255, 255, 0); 
$image->string(gdLargeFont, 0, 0, $time, $yellow); 
print $image->gif;
exit(0); 
tucotuco.cs.indiana.edu% ls -l dclock
-rw-r--r--   1 dgerman  students     623 Oct  6 00:16 dclock
tucotuco.cs.indiana.edu% chmod 700 dclock
tucotuco.cs.indiana.edu% ls -l dclock
-rwx------   1 dgerman  students     623 Oct  6 00:16 dclock
Try the script.

Well, there's really no reason not to include it here, after all, so let's just do it:

<img src="http://tucotuco.cs.indiana.edu:19800/cgi-bin/lecture10/dclock"> 
That is:

Note though, that the time is the time on the server, and that the image is a snapshot.

Now let's try an analog clock.

tucotuco.cs.indiana.edu% vi aclock
tucotuco.cs.indiana.edu% cat aclock
#!/usr/bin/perl

use GD; print "Content-type: image/gif\n\n"; 
($seconds, $minutes, $hour) = localtime(time); 

$x = $y = 100; $R = $x / 2; $twopi = 2 * 3.141592; 

$dhx = $R * sin($hour / 12 * $twopi);  # [1]
$dhy = $R * cos($hour / 12 * $twopi);  # [2]

$dmx = $R * sin($minutes / 60 * $twopi); 
$dmy = $R * cos($minutes / 60 * $twopi); 

$dhy = - $dhy;               # mirror 
$dhy *= 0.66; $dhx *= 0.66;  # scale
$dhx += $R;   $dhy += $R;    # translate 

$ox = $R; $oy = $R;          # translate origin

$dmy = - $dmy;               # mirror
$dmx += $R; $dmy += $R;      # translate 

$image  = new GD::Image($x, $y); 
$black  = $image->colorAllocate(0, 0, 0); 
$yellow = $image->colorAllocate(255, 255, 0); 

$image->line($ox, $oy, int($dmx), int($dmy), $yellow); 
$image->line($ox, $oy, int($dhx), int($dhy), $yellow); 

$image->arc($R, $R, 2 * $R, 2 * $R, 0, 360, $yellow); 

print $image->gif;
exit(0); 
tucotuco.cs.indiana.edu% 
As you can see here the only trouble here is with the coordinates. The image needs to be reflected for the coordinates to match. (And we also need to translate it to keep the drawing within positive range and to scale it so that we can tell the minute hand from the hour hand - the hour hand is shorter).
<img src="http://tucotuco.cs.indiana.edu:19800/cgi-bin/lecture10/aclock">
gives us

This should be as above.

Question: if it isn't, how far apart can they be, and why?

Also, do you see a minor problem with the code?

You should.

The lines marked with [1] and [2] should be changed to:

$add = 0; # $add = $minutes / 60 / 12; 
 
$dhx = $R * sin(($hour / 12 + $add) * $twopi);  
$dhy = $R * cos(($hour / 12 + $add) * $twopi);
for the hour hand to move smoothly.


Now we can do a simple web counter.

Before that let's quickly look at colors.

tucotuco.cs.indiana.edu% pwd     
/nfs/paca/home/user2/dgerman/httpd/cgi-bin/lecture10
tucotuco.cs.indiana.edu% vi colors
tucotuco.cs.indiana.edu% cat colors
#!/usr/bin/perl

use GD; print "Content-type: image/gif\n\n"; 

$x = $y = 100; $image = new GD::Image($x, $y); 

srand(time); 

for ($i = 0; $i < 255; $i++) {
  $color[$i] = $image->colorAllocate(
                               int(rand(255)), 
                               int(rand(255)), 
                               int(rand(255))
                             ); 
} 

for ($i = 0; $i < $x; $i++) {
  for ($j = 0; $j < $y; $j++) {
      $image->setPixel($i, $j, $color[int(rand(255))]); 
  }
}

print $image->gif;
exit(0); 
tucotuco.cs.indiana.edu% 

generates this:

You see that you have access to a wide range of colours and possibilities.


The web counter is accessed from inside a web page.

It counts the number of times that page was loaded.

It needs to count all accesses, and to store them.

The source of data is a file.

tucotuco.cs.indiana.edu% pwd
/nfs/paca/home/user2/dgerman/httpd
tucotuco.cs.indiana.edu% mkdir datafiles
tucotuco.cs.indiana.edu% cd datafiles
tucotuco.cs.indiana.edu% mkdir counters
tucotuco.cs.indiana.edu% cd counters
tucotuco.cs.indiana.edu% pwd
/nfs/paca/home/user2/dgerman/httpd/datafiles/counters
tucotuco.cs.indiana.edu% vi c1
tucotuco.cs.indiana.edu% vi c1.semaphore 
tucotuco.cs.indiana.edu% ls -l c1*
-rw-r--r--   1 dgerman  students       2 Oct  6 14:00 c1
-rw-r--r--   1 dgerman  students       0 Oct  6 14:00 c1.semaphore
tucotuco.cs.indiana.edu% cat c1*
0
tucotuco.cs.indiana.edu
Now we have a place where we will keep the actual counter or counters.

#!/usr/bin/perl

use GD; print "Content-type: image/gif\n\n"; 
$location = "/u/dgerman/httpd/datafiles/counters"; 

open (U, "$location/c1.semaphore"); flock(U, 2); 
open (M, "$location/c1"); $x = ; close(M); $x += 1;
open (M, ">$location/c1"); print M $x, "\n"; close(M); 
                                    flock(U, 8); 

$counter = sprintf(" %s ", $x); 
$counter_length = length($counter); 
$font_length = 8; $font_height = 16; 
$x = $font_length * $counter_length; 
$y = $font_height; $image = new GD::Image($x, $y); 

$white = $image->colorAllocate(255, 255, 255); 
$blue = $image->colorAllocate(0, 0, 255); 
$image->transparent($white); 
$image->string(gdLargeFont, 0, 0, $counter, $blue); 

print $image->gif;
And here it is.

It has been accessed times.

Note that you can set/reset it by changing the $location/c1 file.


Histograms.

is the output of this script, with input University and 3.2 as the average. It produces this image tag:

<img src="http://www.best.indiana.edu/cgi-bin/norms/shape?distri=17:0:0:4:7:7:8:23:33:34:77:104:115:217:260:269:424:473:647:862:934:1107:1204:1700:1918:1981:2346:2374:2880:2994:3037:2925:2449:2481:2179:1752:1409:1021:802:416:392&avera=3.2"> 

The source of data is the QUERY_STRING (basically) although we get to the values with the $query->param(...) method.

#!/usr/bin/perl
use GD; 
use CGI;  
$query = new CGI; $x0 = 5; $y0 = 5; $xmax = 350; $ymax = 110; 
$im = new GD::Image($xmax+1, $ymax+1) || die;
$white = $im->colorAllocate(255, 255, 255);
$lightblue = $im->colorAllocate(190, 190, 255); 
$blue  = $im->colorAllocate(  0,   0, 255);
$grey =  $im->colorAllocate(200, 200, 200);
$yellow = $im->colorAllocate(255, 255, 0);
$im->transparent($white); $im->interlaced('true');  
$im->filledRectangle($x0, $y0, $x0 + 8 * 41, $y0 + 100, $white); 
$im->rectangle($x0, $y0-1, $x0 + 8 * 41+1, $y0 + 100 + 1, $blue); 
$distri = $query->param('distri'); @bins = split(/:/, $distri); 
$avera = $query->param('avera'); $max = -1; $sum = 0; 
foreach $val (@bins) { $sum += $val; $max = $val if ($max <= $val); }
for ($i=0, $lim=0.05; $i<= 40; $i++, $lim += 0.1) {
  # $percent = $i / 40 * 100; 
  $percent = 0; 
  if ($max > 0) { $percent = 100 * $bins[$i] / (1.2 * $max); } 
  if (($lim > $avera) && ($lim - 0.1 <= $avera)) {
    $im->filledRectangle($x0 + $i * 8 + 1, 100 - $percent + $y0, 
                         $x0 + ($i + 1) * 8, $y0 + 100,            
                         $lightblue); 
  } else {
    $im->filledRectangle($x0 + $i * 8 + 1, 100 - $percent + $y0, 
                         $x0 + ($i + 1) * 8, $y0 + 100,            
                         $blue); 
  } 
  if (100 - $percent > 0) {
    if (($lim > $avera) && ($lim - 0.1 <= $avera)) {
      $im->filledRectangle($x0 + $i * 8 + 1, $y0,                  
                           $x0 + ($i + 1) * 8, $y0 + 100 - $percent, 
                           $yellow); 
    } else {
      $im->filledRectangle($x0 + $i * 8 + 1, $y0,                  
                           $x0 + ($i + 1) * 8, $y0 + 100 - $percent, 
                           $grey);
    } 
  } 
} 

# print "Content-type: text/html\n\nText"; exit;  
print "Content-type: image/gif\n\n"; print $im->gif;
# 

Logs. (Pattern matching).

Sorting.

Java CGI (pipes).

tucotuco.cs.indiana.edu% pwd
/nfs/paca/home/user2/dgerman/httpd/cgi-bin/lecture10
tucotuco.cs.indiana.edu% vi jCGI
tucotuco.cs.indiana.edu% cat jCGI
#!/usr/bin/perl

open (AB, "| java myCGIscript");
while (<STDIN>) {
  print AB; 
} 
close(AB); 
tucotuco.cs.indiana.edu%vi myCGIscript.java
tucotuco.cs.indiana.edu% cat myCGIscript.java 
public class myCGIscript {
  public static void main (String[] args) {
    System.out.println("Content-type: text/html\n\n" +
                       "Hello, CGI world! Java, Java.");  
  } 
} 

tucotuco.cs.indiana.edu% javac myCGIscript.java
tucotuco.cs.indiana.edu% ./jCGI
^D
Content-type: text/html

Hello, CGI world! Java, Java.
tucotuco.cs.indiana.edu%
Try this combination. (Be patient.)

2. A simple Post'em system: an example/sketch of a term project.