about summary refs log tree commit diff
path: root/generator
diff options
context:
space:
mode:
authorgiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2006-08-19 03:12:28 +0000
committergiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2006-08-19 03:12:28 +0000
commit1fd361a1ea06e44286c213ca1f814f49306fdc43 (patch)
tree64c8c96cf54d8718847339a403e5e67b922e8c3f /generator
downloadnetpbm-mirror-1fd361a1ea06e44286c213ca1f814f49306fdc43.tar.gz
netpbm-mirror-1fd361a1ea06e44286c213ca1f814f49306fdc43.tar.xz
netpbm-mirror-1fd361a1ea06e44286c213ca1f814f49306fdc43.zip
Create Subversion repository
git-svn-id: http://svn.code.sf.net/p/netpbm/code/trunk@1 9d0c8265-081b-0410-96cb-a4ca84ce46f8
Diffstat (limited to 'generator')
-rw-r--r--generator/Makefile40
-rw-r--r--generator/pamgauss.c207
-rw-r--r--generator/pamgradient.c215
-rw-r--r--generator/pamseq.c202
-rw-r--r--generator/pamstereogram.c885
-rw-r--r--generator/pamstereogram.test70
-rw-r--r--generator/pbmmake.c184
-rw-r--r--generator/pbmmake.test9
-rw-r--r--generator/pbmpage.c291
-rw-r--r--generator/pbmtext.c732
-rw-r--r--generator/pbmtextps.c377
-rw-r--r--generator/pbmupc.c544
-rw-r--r--generator/pgmcrater.c383
-rw-r--r--generator/pgmkernel.c91
-rw-r--r--generator/pgmmake.c111
-rw-r--r--generator/pgmnoise.c77
-rw-r--r--generator/pgmramp.c170
-rw-r--r--generator/ppmcie.c1201
-rw-r--r--generator/ppmcolors.c87
-rw-r--r--generator/ppmforge.c1182
-rw-r--r--generator/ppmmake.c117
-rw-r--r--generator/ppmpat.c1060
-rwxr-xr-xgenerator/ppmrainbow74
-rw-r--r--generator/ppmrough.c292
-rw-r--r--generator/ppmwheel.c157
25 files changed, 8758 insertions, 0 deletions
diff --git a/generator/Makefile b/generator/Makefile
new file mode 100644
index 00000000..52de9b10
--- /dev/null
+++ b/generator/Makefile
@@ -0,0 +1,40 @@
+ifeq ($(SRCDIR)x,x)
+  SRCDIR = $(CURDIR)/..
+  BUILDDIR = $(SRCDIR)
+endif
+SUBDIR = generator
+VPATH=.:$(SRCDIR)/$(SUBDIR)
+
+include $(BUILDDIR)/Makefile.config
+
+# We tend to separate out the build targets so that we don't have
+# any more dependencies for a given target than it really needs.
+# That way, if there is a problem with a dependency, we can still
+# successfully build all the stuff that doesn't depend upon it.
+# This package is so big, it's useful even when some parts won't 
+# build.
+
+PORTBINARIES = pamgauss pamgradient pamseq pamstereogram \
+	       pbmpage pbmmake pbmtext pbmtextps pbmupc \
+	       pgmcrater pgmkernel pgmmake pgmnoise pgmramp \
+	       ppmcie ppmcolors ppmforge ppmmake ppmpat ppmrough ppmwheel \
+
+# We don't include programs that have special library dependencies in the
+# merge scheme, because we don't want those dependencies to prevent us
+# from building all the other programs.
+
+NOMERGEBINARIES = 
+MERGEBINARIES = $(PORTBINARIES)
+
+
+BINARIES = $(MERGEBINARIES) $(NOMERGEBINARIES)
+SCRIPTS = ppmrainbow
+
+OBJECTS = $(BINARIES:%=%.o)
+
+MERGE_OBJECTS = $(MERGEBINARIES:%=%.o2)
+
+.PHONY: all
+all: $(BINARIES)
+
+include $(SRCDIR)/Makefile.common
diff --git a/generator/pamgauss.c b/generator/pamgauss.c
new file mode 100644
index 00000000..eb5fa3fe
--- /dev/null
+++ b/generator/pamgauss.c
@@ -0,0 +1,207 @@
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+
+#include "pm_c_util.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+#include "pam.h"
+
+#define true (1)
+#define false (0)
+
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    unsigned int width;
+    unsigned int height;
+    sample maxval;
+    float sigma;
+    const char * tupletype;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+  Convert program invocation arguments (argc,argv) into a format the 
+  program can use easily, struct cmdlineInfo.  Validate arguments along
+  the way and exit program with message if invalid.
+
+  Note that some string information we return as *cmdlineP is in the storage 
+  argv[] points to.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def;
+        /* Instructions to OptParseOptions2 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int tupletypeSpec, maxvalSpec, sigmaSpec;
+    unsigned int option_def_index;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0,   "tupletype",  OPT_STRING, &cmdlineP->tupletype, 
+            &tupletypeSpec,     0);
+    OPTENT3(0,   "maxval",     OPT_UINT,   &cmdlineP->maxval, 
+            &maxvalSpec,        0);
+    OPTENT3(0,   "sigma",      OPT_FLOAT,  &cmdlineP->sigma, 
+            &sigmaSpec,        0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (!tupletypeSpec)
+        cmdlineP->tupletype = "";
+    else {
+        struct pam pam;
+        if (strlen(cmdlineP->tupletype)+1 > sizeof(pam.tuple_type))
+            pm_error("The tuple type you specified is too long.  "
+                     "Maximum %d characters.", sizeof(pam.tuple_type)-1);
+    }        
+
+    if (!sigmaSpec)
+        pm_error("You must specify the -sigma option.");
+    else if (cmdlineP->sigma <= 0.0)
+        pm_error("-sigma must be positive.  You specified %f", 
+                 cmdlineP->sigma);
+
+    if (!maxvalSpec)
+        cmdlineP->maxval = PNM_MAXMAXVAL;
+    else {
+        if (cmdlineP->maxval > PNM_OVERALLMAXVAL)
+            pm_error("The maxval you specified (%u) is too big.  "
+                     "Maximum is %u", (unsigned int) cmdlineP->maxval, 
+                     PNM_OVERALLMAXVAL);
+        if (cmdlineP->maxval < 1)
+            pm_error("-maxval must be at least 1");
+    }    
+
+    if (argc-1 < 2)
+        pm_error("Need two arguments: width and height.");
+    else if (argc-1 > 2)
+        pm_error("Only two argumeents allowed: with and height.  "
+                 "You specified %d", argc-1);
+    else {
+        cmdlineP->width = atoi(argv[1]);
+        cmdlineP->height = atoi(argv[2]);
+        if (cmdlineP->width <= 0)
+            pm_error("width argument must be a positive number.  You "
+                     "specified '%s'", argv[1]);
+        if (cmdlineP->height <= 0)
+            pm_error("height argument must be a positive number.  You "
+                     "specified '%s'", argv[2]);
+    }
+}
+
+
+
+static double
+distFromCenter(struct pam * const pamP,
+               int          const col,
+               int          const row) {
+
+    return sqrt(SQR(col - pamP->width/2) + SQR(row - pamP->height/2));
+}
+
+
+
+static double
+gauss(double const arg,
+      double const sigma) {
+/*----------------------------------------------------------------------------
+   Compute the value of the gaussian function with sigma parameter 'sigma'
+   and mu parameter zero of argument 'arg'.
+-----------------------------------------------------------------------------*/
+    double const pi = 3.14159;
+    double const coefficient = 1 / (sigma * sqrt(2*pi));
+    double const exponent = - SQR(arg-0) / (2 * SQR(sigma));
+
+    return coefficient * exp(exponent);
+}
+
+
+
+static double
+imageNormalizer(struct pam * const pamP,
+                double       const sigma) {
+/*----------------------------------------------------------------------------
+   Compute the value that has to be multiplied by the value of the 
+   one-dimensional gaussian function of the distance from center in
+   order to get the value for a normalized two-dimensional gaussian
+   function.  Normalized here means that the volume under the whole
+   curve is 1, just as the area under a whole one-dimensional gaussian
+   function is 1.
+-----------------------------------------------------------------------------*/
+    double volume;
+
+    unsigned int row;
+
+    volume = 0.0;   /* initial value */
+
+    for (row = 0; row < pamP->height; ++row) {
+        unsigned int col;
+        for (col = 0; col < pamP->width; ++col)
+            volume += gauss(distFromCenter(pamP, col, row), sigma);
+    }
+    return 1.0 / volume;
+}
+
+
+
+int
+main(int argc, char **argv) {
+
+    struct cmdlineInfo cmdline;
+    struct pam pam;
+    int row;
+    double normalizer;
+    tuplen * tuplerown;
+    
+    pnm_init(&argc, argv);
+   
+    parseCommandLine(argc, argv, &cmdline);
+
+    pam.size        = sizeof(pam);
+    pam.len         = PAM_STRUCT_SIZE(tuple_type);
+    pam.file        = stdout;
+    pam.format      = PAM_FORMAT;
+    pam.plainformat = 0;
+    pam.width       = cmdline.width;
+    pam.height      = cmdline.height;
+    pam.depth       = 1;
+    pam.maxval      = cmdline.maxval;
+    strcpy(pam.tuple_type, cmdline.tupletype);
+
+    normalizer = imageNormalizer(&pam, cmdline.sigma);
+    
+    pnm_writepaminit(&pam);
+   
+    tuplerown = pnm_allocpamrown(&pam);
+
+    for (row = 0; row < pam.height; ++row) {
+        int col;
+        for (col = 0; col < pam.width; ++col) {
+            double const gauss1 = gauss(distFromCenter(&pam, col, row),
+                                        cmdline.sigma);
+
+            tuplerown[col][0] = gauss1 * normalizer;
+        }
+        pnm_writepamrown(&pam, tuplerown);
+    }
+    
+    pnm_freepamrown(tuplerown);
+
+    return 0;
+}
diff --git a/generator/pamgradient.c b/generator/pamgradient.c
new file mode 100644
index 00000000..7717bf4b
--- /dev/null
+++ b/generator/pamgradient.c
@@ -0,0 +1,215 @@
+#include <string.h>
+
+#include "pam.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+
+
+
+struct cmdlineInfo {
+    tuple colorTopLeft;
+    tuple colorTopRight;
+    tuple colorBottomLeft;
+    tuple colorBottomRight;
+    unsigned depth;
+    unsigned int cols;
+    unsigned int rows;
+    unsigned int maxval;
+};
+
+static void
+parseCommandLine(int argc, char **argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+  Convert program invocation arguments (argc,argv) into a format the 
+  program can use easily, struct cmdlineInfo.  Validate arguments along
+  the way and exit program with message if invalid.
+
+  Note that some string information we return as *cmdlineP is in the storage 
+  argv[] points to.
+-----------------------------------------------------------------------------*/
+    optEntry * option_def;
+        /* Instructions to OptParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int maxvalSpec;
+    unsigned int option_def_index;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;
+    OPTENT3(0, "maxval", OPT_UINT, &cmdlineP->maxval, &maxvalSpec, 0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (!maxvalSpec)
+        cmdlineP->maxval = 255;
+    else {
+        if (cmdlineP->maxval > PAM_OVERALL_MAXVAL)
+            pm_error("The value you specified for -maxval (%u) is too big.  "
+                     "Max allowed is %u", cmdlineP->maxval,
+                     PAM_OVERALL_MAXVAL);
+        
+        if (cmdlineP->maxval < 1)
+            pm_error("You cannot specify 0 for -maxval");
+    }    
+
+    if (argc-1 != 6) {
+        pm_error("Need 6 arguments: colorTopLeft, colorTopRight, "
+                 "colorBottomLeft, colorBottomRight, width, height"); 
+    } else {
+        cmdlineP->colorTopLeft     = pnm_parsecolor(argv[1], cmdlineP->maxval);
+        cmdlineP->colorTopRight    = pnm_parsecolor(argv[2], cmdlineP->maxval);
+        cmdlineP->colorBottomLeft  = pnm_parsecolor(argv[3], cmdlineP->maxval);
+        cmdlineP->colorBottomRight = pnm_parsecolor(argv[4], cmdlineP->maxval);
+        cmdlineP->cols = atoi(argv[5]);
+        cmdlineP->rows = atoi(argv[6]);
+        if (cmdlineP->cols <= 0)
+            pm_error("width argument must be a positive number.  You "
+                     "specified '%s'", argv[5]);
+        if (cmdlineP->rows <= 0)
+            pm_error("height argument must be a positive number.  You "
+                     "specified '%s'", argv[6]);
+    }
+}
+
+
+
+static void
+freeCmdline(struct cmdlineInfo const cmdline) {
+
+    pnm_freepamtuple(cmdline.colorTopLeft);
+    pnm_freepamtuple(cmdline.colorTopRight);
+    pnm_freepamtuple(cmdline.colorBottomLeft);
+    pnm_freepamtuple(cmdline.colorBottomRight);
+}
+
+
+
+static void
+interpolate(struct pam * const pamP,
+            tuple *      const tuplerow,
+            tuple        const first,
+            tuple        const last) {
+
+    unsigned int plane;
+    
+    for (plane = 0; plane < pamP->depth; ++plane) {
+        int const spread = last[plane] - first[plane];
+
+        int col;
+
+        if (INT_MAX / pamP->width < abs(spread))
+            pm_error("Arithmetic overflow.  You must reduce the width of "
+                     "the image (now %u) or the range of color values "
+                     "(%u in plane %u) so that their "
+                     "product is less than %d",
+                     pamP->width, abs(spread), plane, INT_MAX);
+
+        for (col = 0; col < pamP->width; ++col)
+            tuplerow[col][plane] =
+                first[plane] + (spread * col / (int)pamP->width);
+    }
+}
+
+
+
+static int
+isgray(struct pam * const pamP,
+       tuple        const color) {
+
+    return (pamP->depth == 1)
+        || ((pamP->depth == 3)
+        && (color[PAM_RED_PLANE] == color[PAM_GRN_PLANE])
+        && (color[PAM_RED_PLANE] == color[PAM_BLU_PLANE])); 
+}
+
+
+
+static tuple *
+createEdge(const struct pam * const pamP,
+           tuple              const topColor,
+           tuple              const bottomColor) {
+/*----------------------------------------------------------------------------
+   Create a left or right edge, interpolating from top to bottom.
+-----------------------------------------------------------------------------*/
+    struct pam interpPam;
+    tuple * tupleRow;
+
+    interpPam = *pamP;  /* initial value */
+    interpPam.width = pamP->height;
+    interpPam.height = 1;
+
+    tupleRow = pnm_allocpamrow(&interpPam);
+
+    interpolate(&interpPam, tupleRow, topColor, bottomColor);
+
+    return tupleRow;
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+    struct pam pam;
+    tuple * tupleRow;
+    tuple * leftEdge;
+    tuple * rightEdge;
+    unsigned int row;
+    
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    pam.size             = sizeof pam;
+    pam.len              = PAM_STRUCT_SIZE(tuple_type);
+    pam.file             = stdout;
+    pam.plainformat      = 0;
+    pam.width            = cmdline.cols;
+    pam.height           = cmdline.rows;
+    pam.maxval           = cmdline.maxval;
+    pam.bytes_per_sample = pnm_bytespersample(pam.maxval);
+    pam.format           = PAM_FORMAT;
+    if (isgray(&pam, cmdline.colorTopLeft)
+            && isgray(&pam, cmdline.colorTopRight)
+            && isgray(&pam, cmdline.colorBottomLeft)
+            && isgray(&pam, cmdline.colorBottomRight)) {
+        pam.depth = 1;
+        strcpy(pam.tuple_type, PAM_PGM_TUPLETYPE);
+    } else {
+        pam.depth = 3;
+        strcpy(pam.tuple_type, PAM_PPM_TUPLETYPE);
+    }
+
+    pnm_writepaminit(&pam);
+    
+    tupleRow = pnm_allocpamrow(&pam);
+
+    leftEdge  = createEdge(&pam,
+                           cmdline.colorTopLeft, cmdline.colorBottomLeft);
+    rightEdge = createEdge(&pam,
+                           cmdline.colorTopRight, cmdline.colorBottomRight);
+
+    /* interpolate each row between the left edge and the right edge */
+    for (row = 0; row < pam.height; ++row) {
+        interpolate(&pam, tupleRow, leftEdge[row], rightEdge[row]);
+        pnm_writepamrow(&pam, tupleRow); 
+    }
+
+    pm_close(stdout);
+    pnm_freepamrow(rightEdge);
+    pnm_freepamrow(leftEdge);
+    pnm_freepamrow(tupleRow);
+
+    freeCmdline(cmdline);
+
+    return 0;
+}
diff --git a/generator/pamseq.c b/generator/pamseq.c
new file mode 100644
index 00000000..58419d12
--- /dev/null
+++ b/generator/pamseq.c
@@ -0,0 +1,202 @@
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "pam.h"
+#include "shhopt.h"
+
+#define true (1)
+#define false (0)
+
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    unsigned int depth;
+    sample maxval;
+    const char * tupletype;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+  Convert program invocation arguments (argc,argv) into a format the 
+  program can use easily, struct cmdlineInfo.  Validate arguments along
+  the way and exit program with message if invalid.
+
+  Note that some string information we return as *cmdlineP is in the storage 
+  argv[] points to.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def = malloc(100*sizeof(optEntry));
+        /* Instructions to OptParseOptions2 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int tupletypeSpec;
+    unsigned int option_def_index;
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0,   "tupletype",  OPT_STRING, &cmdlineP->tupletype, 
+            &tupletypeSpec,     0);
+
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (!tupletypeSpec)
+        cmdlineP->tupletype = "";
+    else {
+        struct pam pam;
+        if (strlen(cmdlineP->tupletype)+1 > sizeof(pam.tuple_type))
+            pm_error("The tuple type you specified is too long.  "
+                     "Maximum %d characters.", sizeof(pam.tuple_type)-1);
+    }        
+
+    if (argc-1 < 2)
+        pm_error("Need two arguments: depth and maxval.");
+    else if (argc-1 > 2)
+        pm_error("Only two argumeents allowed: depth and maxval.  "
+                 "You specified %d", argc-1);
+    else {
+        cmdlineP->depth = atoi(argv[1]);
+        cmdlineP->maxval = atoi(argv[2]);
+        if (cmdlineP->depth <= 0)
+            pm_error("depth argument must be a positive number.  You "
+                     "specified '%s'", argv[1]);
+        if (cmdlineP->maxval <= 0)
+            pm_error("maxval argument must be a positive number.  You "
+                     "specified '%s'", argv[2]);
+        if (cmdlineP->maxval > PNM_OVERALLMAXVAL)
+            pm_error("The maxval you specified (%u) is too big.  "
+                     "Maximum is %u", (unsigned int) cmdlineP->maxval, 
+                     PNM_OVERALLMAXVAL);
+        if (pm_maxvaltobits(cmdlineP->maxval) + 
+            pm_maxvaltobits(cmdlineP->depth-1) > sizeof(unsigned int)*8)
+            pm_error("The maxval (%u) and depth (%u) you specified result "
+                     "in a larger number of tuples than this program can "
+                     "handle (roughly %u)", 
+                     (unsigned int) cmdlineP->maxval, cmdlineP->depth,
+                     (unsigned int) -1);
+    }
+}
+
+
+
+static unsigned int
+powint(unsigned int base, unsigned int exponent) {
+/*----------------------------------------------------------------------------
+   This is standard pow(), but for integers and optimized for small
+   exponents.
+-----------------------------------------------------------------------------*/
+    unsigned int result;
+    unsigned int i;
+
+    result = 1;  /* initial value */
+    for (i = 0; i < exponent; ++i) 
+        result *= base;
+
+    return(result);
+}
+
+
+static void
+permuteHigherPlanes(struct pam const pam, int const nextplane, 
+                    tuple * const tuplerow, int * const colP, 
+                    tuple const lowerPlanes) {
+/*----------------------------------------------------------------------------
+   Create all the possible permutations of tuples whose lower-numbered planes
+   contain the values from 'lowerPlanes'.  I.e. vary the higher-numbered
+   planes between zero and maxval.
+
+   Write them sequentially into *tuplerow, starting at *colP.  Adjust
+   *colP to next the column after the ones we write.
+
+   lower-numbered means with plane numbers less than 'nextplane'.
+
+   We modify 'lowerPlanes' in the higher planes to undefined values.
+-----------------------------------------------------------------------------*/
+    if (nextplane == pam.depth - 1) {
+        sample value;
+        for (value = 0; value <= pam.maxval; ++value) {
+            unsigned int plane;
+            for (plane = 0; plane < nextplane; ++plane)
+                tuplerow[*colP][plane] = lowerPlanes[plane];
+            tuplerow[*colP][nextplane] = value;
+            ++(*colP);
+        }
+    } else {
+        sample value;
+
+        for (value = 0; value <= pam.maxval; ++value) {
+            /* We do something sleazy here and use Caller's lowerPlanes[]
+               variable as a local variable, modifying it in the higher
+               plane positions.  That's just for speed.
+            */
+            lowerPlanes[nextplane] = value;
+
+            permuteHigherPlanes(pam, nextplane+1, tuplerow, colP, lowerPlanes);
+        }
+    }
+}
+
+
+
+int
+main(int argc, char **argv) {
+
+    struct cmdlineInfo cmdline;
+    struct pam pam;
+    int col;
+    tuple lowerPlanes;
+        /* This is working storage passed to permuteHigherPlanes(),
+           which we call.  Note that because we always pass zero as the
+           "planes" argument to permuteHigherPlanes(), none of the 
+           "lower planes" value is defined as an input to 
+           permuteHigherPlanes().
+        */
+    tuple * tuplerow;
+    
+    pnm_init(&argc, argv);
+   
+    parseCommandLine(argc, argv, &cmdline);
+
+    pam.size = sizeof(pam);
+    pam.len = PAM_STRUCT_SIZE(tuple_type);
+    pam.file = stdout;
+    pam.format = PAM_FORMAT;
+    pam.plainformat = 0;
+    pam.width = powint(cmdline.maxval+1, cmdline.depth);
+    pam.height = 1;
+    pam.depth = cmdline.depth;
+    pam.maxval = cmdline.maxval;
+    strcpy(pam.tuple_type, cmdline.tupletype);
+
+    pnm_writepaminit(&pam);
+   
+    tuplerow = pnm_allocpamrow(&pam);
+
+    lowerPlanes = pnm_allocpamtuple(&pam);
+
+    col = 0;
+
+    permuteHigherPlanes(pam, 0, tuplerow, &col, lowerPlanes);
+
+    if (col != pam.width)
+        pm_error("INTERNAL ERROR: Wrote %d columns; should have written %d.",
+                 col, pam.width);
+
+    pnm_writepamrow(&pam, tuplerow);
+    
+    pnm_freepamrow(tuplerow);
+
+    return 0;
+}
diff --git a/generator/pamstereogram.c b/generator/pamstereogram.c
new file mode 100644
index 00000000..e9e58677
--- /dev/null
+++ b/generator/pamstereogram.c
@@ -0,0 +1,885 @@
+/* ----------------------------------------------------------------------
+ *
+ * Create a single image stereogram from a height map.
+ * by Scott Pakin <scott+pbm@pakin.org>
+ * Adapted to Netbpm conventions by Bryan Henderson.
+ * Revised by Scott Pakin.
+ *
+ * The core of this program is a simple adaptation of the code in
+ * "Displaying 3D Images: Algorithms for Single Image Random Dot
+ * Stereograms" by Harold W. Thimbleby, Stuart Inglis, and Ian
+ * H. Witten in IEEE Computer, 27(10):38-48, October 1994.  See that
+ * paper for a thorough explanation of what's going on here.
+ *
+ * ----------------------------------------------------------------------
+ *
+ * Copyright (C) 2006 Scott Pakin <scott+pbm@pakin.org>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * ----------------------------------------------------------------------
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <assert.h>
+
+#include "pam.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+
+/* Define a few helper macros. */
+#define round2int(X) ((int)((X)+0.5))      /* Nonnegative numbers only */
+
+enum outputType {OUTPUT_BW, OUTPUT_GRAYSCALE, OUTPUT_COLOR};
+
+/* ---------------------------------------------------------------------- */
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char * inputFilespec;  /* '-' if stdin */
+    unsigned int verbose;        /* -verbose option */
+    unsigned int crosseyed;      /* -crosseyed option */
+    unsigned int makemask;       /* -makemask option */
+    unsigned int dpi;            /* -dpi option */
+    float eyesep;                /* -eyesep option */
+    float depth;                 /* -depth option */
+    unsigned int maxvalSpec;     /* -maxval option count */
+    unsigned int maxval;         /* -maxval option value x*/
+    int guidesize;               /* -guidesize option */
+    unsigned int magnifypat;     /* -magnifypat option */
+    unsigned int xshift;         /* -xshift option */
+    unsigned int yshift;         /* -yshift option */
+    const char * patFilespec;    /* -patfile option.  Null if none */
+    unsigned int randomseed;     /* -randomseed option */
+    enum outputType outputType;  /* Type of output file */
+};
+
+
+
+static void
+parseCommandLine(int                 argc,
+                 char **             argv,
+                 struct cmdlineInfo *cmdlineP ) {
+/*----------------------------------------------------------------------------
+   Parse program command line described in Unix standard form by argc
+   and argv.  Return the information in the options as *cmdlineP.
+
+   If command line is internally inconsistent (invalid options, etc.),
+   issue error message to stderr and abort program.
+
+   Note that the strings we return are stored in the storage that
+   was passed to us as the argv array.  We also trash *argv.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def;
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    unsigned int patfileSpec, dpiSpec, eyesepSpec, depthSpec,
+        guidesizeSpec, magnifypatSpec, xshiftSpec, yshiftSpec, randomseedSpec;
+
+    unsigned int blackandwhite, grayscale, color;
+
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "verbose",         OPT_FLAG,   NULL,
+            (unsigned int *)&cmdlineP->verbose,       0);
+    OPTENT3(0, "crosseyed",       OPT_FLAG,   NULL,
+            &cmdlineP->crosseyed,     0);
+    OPTENT3(0, "makemask",        OPT_FLAG,   NULL,
+            &cmdlineP->makemask,      0);
+    OPTENT3(0, "blackandwhite",   OPT_FLAG,   NULL,
+            &blackandwhite,           0);
+    OPTENT3(0, "grayscale",       OPT_FLAG,   NULL,
+            &grayscale,               0);
+    OPTENT3(0, "color",           OPT_FLAG,   NULL,
+            &color,                   0);
+    OPTENT3(0, "dpi",             OPT_UINT,   &cmdlineP->dpi,
+            &dpiSpec,                 0);
+    OPTENT3(0, "eyesep",          OPT_FLOAT,  &cmdlineP->eyesep,
+            &eyesepSpec,              0);
+    OPTENT3(0, "depth",           OPT_FLOAT,  &cmdlineP->depth,
+            &depthSpec,               0);
+    OPTENT3(0, "maxval",          OPT_UINT,   &cmdlineP->maxval,
+            &cmdlineP->maxvalSpec,    0);
+    OPTENT3(0, "guidesize",       OPT_INT,    &cmdlineP->guidesize,
+            &guidesizeSpec,           0);
+    OPTENT3(0, "magnifypat",      OPT_UINT,   &cmdlineP->magnifypat,
+            &magnifypatSpec,          0);
+    OPTENT3(0, "xshift",          OPT_UINT,   &cmdlineP->xshift,
+            &xshiftSpec,              0);
+    OPTENT3(0, "yshift",          OPT_UINT,   &cmdlineP->yshift,
+            &yshiftSpec,              0);
+    OPTENT3(0, "patfile",         OPT_STRING, &cmdlineP->patFilespec,
+            &patfileSpec,             0);
+    OPTENT3(0, "randomseed",      OPT_UINT,   &cmdlineP->randomseed,
+            &randomseedSpec,          0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3( &argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+
+    if (blackandwhite + grayscale + color == 0)
+        cmdlineP->outputType = OUTPUT_BW;
+    else if (blackandwhite + grayscale + color > 1)
+        pm_error("You may specify only one of -blackandwhite, -grayscale, "
+                 "and -color");
+    else {
+        if (blackandwhite)
+            cmdlineP->outputType = OUTPUT_BW;
+        else if (grayscale)
+            cmdlineP->outputType = OUTPUT_GRAYSCALE;
+        else {
+            assert(color);
+            cmdlineP->outputType = OUTPUT_COLOR;
+        }
+    }
+    if (!patfileSpec)
+        cmdlineP->patFilespec = NULL;
+
+    if (!dpiSpec)
+        cmdlineP->dpi = 96;
+    else if (cmdlineP->dpi < 1)
+        pm_error("The argument to -dpi must be a positive integer");
+
+    if (!eyesepSpec)
+        cmdlineP->eyesep = 2.5;
+    else if (cmdlineP->eyesep <= 0.0)
+        pm_error("The argument to -eyesep must be a positive number");
+
+    if (!depthSpec)
+        cmdlineP->depth = (1.0/3.0);
+    else if (cmdlineP->depth < 0.0 || cmdlineP->depth > 1.0)
+        pm_error("The argument to -depth must be a number from 0.0 to 1.0");
+
+    if (cmdlineP->maxvalSpec) {
+        if (cmdlineP->maxval < 1)
+            pm_error("-maxval must be at least 1");
+        else if (cmdlineP->maxval > PNM_OVERALLMAXVAL)
+            pm_error("-maxval must be at most %u.  You specified %u",
+                     PNM_OVERALLMAXVAL, cmdlineP->maxval);
+    }
+
+    if (!guidesizeSpec)
+        cmdlineP->guidesize = 0;
+
+    if (!magnifypatSpec)
+        cmdlineP->magnifypat = 1;
+    else if (cmdlineP->magnifypat < 1)
+        pm_error("The argument to -magnifypat must be a positive integer");
+
+    if (!xshiftSpec)
+        cmdlineP->xshift = 0;
+
+    if (!yshiftSpec)
+        cmdlineP->yshift = 0;
+
+    if (!randomseedSpec)
+        cmdlineP->randomseed = time(NULL);
+
+    if (xshiftSpec && !cmdlineP->patFilespec)
+        pm_error("-xshift is valid only with -patfile");
+    if (yshiftSpec && !cmdlineP->patFilespec)
+        pm_error("-yshift is valid only with -patfile");
+
+    if (cmdlineP->makemask && cmdlineP->patFilespec)
+        pm_error("You may not specify both -makemask and -patfile");
+
+    if (cmdlineP->patFilespec && blackandwhite)
+        pm_error("-blackandwhite is not valid with -patfile");
+    if (cmdlineP->patFilespec && grayscale)
+        pm_error("-grayscale is not valid with -patfile");
+    if (cmdlineP->patFilespec && color)
+        pm_error("-color is not valid with -patfile");
+    if (cmdlineP->patFilespec && cmdlineP->maxvalSpec)
+        pm_error("-maxval is not valid with -patfile");
+
+
+    if (argc-1 < 1)
+        cmdlineP->inputFilespec = "-";
+    else if (argc-1 == 1)
+        cmdlineP->inputFilespec = argv[1];
+    else
+        pm_error("Too many non-option arguments: %d.  Only argument is "
+                 "input file name", argc-1);
+}
+
+
+
+static int
+separation(double             const dist,
+           double             const eyesep,
+           unsigned int       const dpi,
+           double             const dof /* depth of field */
+    ) {
+/*----------------------------------------------------------------------------
+  Return a separation in pixels which corresponds to a 3-D distance
+  between the viewer's eyes and a point on an object.
+-----------------------------------------------------------------------------*/
+    int const pixelEyesep = round2int(eyesep * dpi);
+
+    return round2int((1.0 - dof * dist) * pixelEyesep / (2.0 - dof * dist));
+}
+
+
+
+static void
+reportImageParameters(const char * const fileDesc,
+                      const struct pam * const pamP) {
+
+    pm_message("%s: tuple type '%s', %d wide x %d high x %d deep, maxval %lu",
+               fileDesc, pamP->tuple_type, pamP->width, pamP->height,
+               pamP->depth, pamP->maxval);
+}
+
+
+
+/*----------------------------------------------------------------------------
+   Background generators
+-----------------------------------------------------------------------------*/
+
+struct outGenerator;
+
+typedef tuple coord2Color(struct outGenerator *, int, int);
+    /* A type to use for functions that map a 2-D coordinate to a color. */
+typedef void outGenStateTerm(struct outGenerator *);
+
+
+typedef struct outGenerator {
+    struct pam pam;
+    coord2Color * getTuple;
+        /* Map from a height-map (x,y) coordinate to a tuple */
+    outGenStateTerm * terminateState;
+    void * stateP;
+} outGenerator;
+
+
+
+struct randomState {
+    /* The state of a randomColor generator. */
+    unsigned int magnifypat;
+    tuple *      currentRow;
+    unsigned int prevy;
+};
+
+
+static coord2Color randomColor;
+
+static tuple
+randomColor(outGenerator * const outGenP,
+            int            const x,
+            int            const y) {
+/*----------------------------------------------------------------------------
+   Return a random RGB value.
+-----------------------------------------------------------------------------*/
+    struct randomState * const stateP = outGenP->stateP;
+
+    /* Every time we start a new row, we select a new sequence of random
+       colors.
+    */
+    if (y/stateP->magnifypat != stateP->prevy/stateP->magnifypat) {
+        unsigned int const modulus = outGenP->pam.maxval + 1;
+        int col;
+
+        for (col = 0; col < outGenP->pam.width; ++col) {
+            tuple const thisTuple = stateP->currentRow[col];
+
+            unsigned int plane;
+
+            for (plane = 0; plane < outGenP->pam.depth; ++plane) {
+                unsigned int const randval = rand();
+                thisTuple[plane] = randval % modulus;
+            }
+        }
+    }
+
+    /* Return the appropriate column from the pregenerated color row. */
+    stateP->prevy = y;
+    return stateP->currentRow[x/stateP->magnifypat];
+}
+
+
+
+static outGenStateTerm termRandomColor;
+
+static void
+termRandomColor(outGenerator * const outGenP) {
+
+    struct randomState * const stateP = outGenP->stateP;
+
+    pnm_freepamrow(stateP->currentRow);
+}
+
+
+
+static void
+initRandomColor(outGenerator *     const outGenP,
+                const struct pam * const inPamP,
+                struct cmdlineInfo const cmdline) {
+
+    struct randomState * stateP;
+
+    outGenP->pam.format      = PAM_FORMAT;
+    outGenP->pam.plainformat = 0;
+
+    switch (cmdline.outputType) {
+    case OUTPUT_BW:
+        strcpy(outGenP->pam.tuple_type, PAM_PBM_TUPLETYPE);
+        outGenP->pam.maxval = 1;
+        outGenP->pam.depth = 1;
+        break;
+    case OUTPUT_GRAYSCALE:
+        strcpy(outGenP->pam.tuple_type, PAM_PGM_TUPLETYPE);
+        outGenP->pam.maxval =
+            cmdline.maxvalSpec ? cmdline.maxval : inPamP->maxval;
+        outGenP->pam.depth = 1;
+        break;
+    case OUTPUT_COLOR:
+        strcpy(outGenP->pam.tuple_type, PAM_PPM_TUPLETYPE);
+        outGenP->pam.maxval =
+            cmdline.maxvalSpec ? cmdline.maxval : inPamP->maxval;
+        outGenP->pam.depth = 3;
+        break;
+    }
+
+    MALLOCVAR_NOFAIL(stateP);
+
+    stateP->currentRow = pnm_allocpamrow(&outGenP->pam);
+    stateP->magnifypat = cmdline.magnifypat;
+    stateP->prevy      = (unsigned int)(-cmdline.magnifypat);
+
+    outGenP->stateP         = stateP;
+    outGenP->getTuple       = &randomColor;
+    outGenP->terminateState = &termRandomColor;
+}
+
+
+
+struct patternPixelState {
+    /* This is the state of a patternPixel generator.*/
+    struct pam   patPam;     /* Descriptor of pattern image */
+    tuple **     patTuples;  /* Entire image read from the pattern file */
+    unsigned int xshift;
+    unsigned int yshift;
+    unsigned int magnifypat;
+};
+
+
+
+static coord2Color patternPixel;
+
+static tuple
+patternPixel(outGenerator * const outGenP,
+             int            const x,
+             int            const y) {
+/*----------------------------------------------------------------------------
+  Return a pixel from the pattern file.
+-----------------------------------------------------------------------------*/
+    struct patternPixelState * const stateP = outGenP->stateP;
+    struct pam * const patPamP = &stateP->patPam;
+
+    int patx, paty;
+
+    paty = ((y - stateP->yshift) / stateP->magnifypat) % patPamP->height;
+
+    if (paty < 0)
+        paty += patPamP->height;
+
+    patx = ((x - stateP->xshift) / stateP->magnifypat) % patPamP->width;
+
+    if (patx < 0)
+        patx += patPamP->width;
+
+    return stateP->patTuples[paty][patx];
+}
+
+
+
+static outGenStateTerm termPatternPixel;
+
+static void
+termPatternPixel(outGenerator * const outGenP) {
+
+    struct patternPixelState * const stateP = outGenP->stateP;
+
+    pnm_freepamarray(stateP->patTuples, &stateP->patPam);
+}
+
+
+
+static void
+initPatternPixel(outGenerator *     const outGenP,
+                 struct cmdlineInfo const cmdline) {
+
+    struct patternPixelState * stateP;
+    FILE * patternFileP;
+
+    MALLOCVAR_NOFAIL(stateP);
+        
+    patternFileP = pm_openr(cmdline.patFilespec);
+
+    stateP->patTuples =
+        pnm_readpam(patternFileP,
+                    &stateP->patPam, PAM_STRUCT_SIZE(tuple_type));
+
+    pm_close(patternFileP);
+
+    stateP->xshift     = cmdline.xshift;
+    stateP->yshift     = cmdline.yshift;
+    stateP->magnifypat = cmdline.magnifypat;
+
+    outGenP->stateP          = stateP;
+    outGenP->getTuple        = &patternPixel;
+    outGenP->terminateState  = &termPatternPixel;
+    outGenP->pam.format      = stateP->patPam.format;
+    outGenP->pam.plainformat = stateP->patPam.plainformat;
+    outGenP->pam.depth       = stateP->patPam.depth;
+    outGenP->pam.maxval      = stateP->patPam.maxval;
+
+    if (cmdline.verbose)
+        reportImageParameters("Pattern file", &stateP->patPam);
+}
+
+
+
+static void
+createoutputGenerator(struct cmdlineInfo const cmdline,
+                      const struct pam * const inPamP,
+                      outGenerator **    const outputGeneratorPP) {
+
+    outGenerator * outGenP;
+    
+    MALLOCVAR_NOFAIL(outGenP);
+
+    outGenP->pam.size   = sizeof(struct pam);
+    outGenP->pam.len    = PAM_STRUCT_SIZE(tuple_type);
+    outGenP->pam.bytes_per_sample = pnm_bytespersample(outGenP->pam.maxval);
+    outGenP->pam.file   = stdout;
+    outGenP->pam.height = inPamP->height + 3 * abs(cmdline.guidesize);
+        /* Allow room for guides. */
+    outGenP->pam.width  = inPamP->width;
+
+    if (cmdline.patFilespec) {
+        /* Background pixels should come from the pattern file. */
+
+        initPatternPixel(outGenP, cmdline);
+    } else {
+        /* Background pixels should be generated randomly */
+
+        initRandomColor(outGenP, inPamP, cmdline);
+    }
+
+    *outputGeneratorPP = outGenP;
+}
+
+
+
+static void
+destroyoutputGenerator(outGenerator * const outputGeneratorP) {
+
+    outputGeneratorP->terminateState(outputGeneratorP);
+    free(outputGeneratorP);
+}
+
+
+/* End of background generators */
+
+/* ---------------------------------------------------------------------- */
+
+
+static void
+makeWhiteRow(const struct pam * const pamP,
+             tuple *            const tuplerow) {
+
+    int col;
+
+    for (col = 0; col < pamP->width; ++col) {
+        unsigned int plane;
+        for (plane = 0; plane < pamP->depth; ++plane)
+            tuplerow[col][plane] = pamP->maxval;
+    }
+}
+
+
+
+static void
+writeRowCopies(const struct pam *  const outPamP,
+               const tuple *       const outrow,
+               unsigned int        const copyCount) {
+
+    unsigned int i;
+    for (i = 0; i < copyCount; ++i)
+        pnm_writepamrow(outPamP, outrow);
+}
+
+
+
+/* Draw a pair of guide boxes. */
+static void
+drawguides(int                const guidesize,
+           const struct pam * const outPamP,
+           double             const eyesep,
+           unsigned int       const dpi,
+           double             const depthOfField) {
+
+    int const far = separation(0, eyesep, dpi, depthOfField);
+        /* Space between the two guide boxes. */
+    int const width = outPamP->width;    /* Width of the output image */
+
+    tuple *outrow;             /* One row of output data */
+    tuple blackTuple;
+    int col;
+
+    pnm_createBlackTuple(outPamP, &blackTuple);
+
+    outrow = pnm_allocpamrow(outPamP);
+
+    /* Leave some blank rows before the guides. */
+    makeWhiteRow(outPamP, outrow);
+    writeRowCopies(outPamP, outrow, guidesize);
+
+    /* Draw the guides. */
+    if ((width - far + guidesize)/2 < 0 ||
+        (width + far - guidesize)/2 >= width)
+        pm_message("warning: the guide boxes are completely out of bounds "
+                   "at %d DPI", dpi);
+    else if ((width - far - guidesize)/2 < 0 ||
+             (width + far + guidesize)/2 >= width)
+        pm_message("warning: the guide boxes are partially out of bounds "
+                   "at %d DPI", dpi);
+
+    for (col = (width - far - guidesize)/2;
+         col < (width - far + guidesize)/2;
+         ++col)
+        if (col >= 0 && col < width)
+            pnm_assigntuple(outPamP, outrow[col], blackTuple);
+
+    for (col = (width + far - guidesize)/2;
+         col < (width + far + guidesize)/2;
+         ++col)
+        if (col >= 0 && col < width)
+            pnm_assigntuple(outPamP, outrow[col], blackTuple);
+
+    writeRowCopies(outPamP,outrow, guidesize);
+
+    /* Leave some blank rows after the guides. */
+    makeWhiteRow(outPamP, outrow);
+    writeRowCopies(outPamP, outrow, guidesize);
+
+    pnm_freerow(outrow);
+}
+
+
+
+/* Do the bulk of the work.  See the paper cited above for code
+ * comments.  All I (Scott) did was transcribe the code and make
+ * minimal changes for Netpbm.  And some style changes by Bryan to
+ * match Netpbm style.
+ */
+static void
+makeStereoRow(const struct pam * const inPamP,
+              tuple *            const inRow,
+              int *              const same,
+              double             const depthOfField,
+              double             const eyesep,
+              unsigned int       const dpi) {
+
+#define Z(X) (1.0-inRow[X][0]/(double)inPamP->maxval)
+
+    int const width       = inPamP->width;
+    int const pixelEyesep = round2int(eyesep * dpi);
+        /* Separation in pixels between the viewer's eyes */
+
+    int col;
+
+    for (col = 0; col < width; ++col)
+        same[col] = col;
+
+    for (col = 0; col < width; ++col) {
+        int const s = separation(Z(col), eyesep, dpi, depthOfField);
+        int left, right;
+
+        left  = col - s/2;  /* initial value */
+        right = left + s;   /* initial value */
+
+        if (0 <= left && right < width) {
+            int visible;
+            int t;
+            double zt;
+
+            t = 1;  /* initial value */
+
+            do {
+                double const dof = depthOfField;
+                zt = Z(col) + 2.0*(2.0 - dof*Z(col))*t/(dof*pixelEyesep);
+                visible = Z(col-t) < zt && Z(col+t) < zt;
+                ++t;
+            } while (visible && zt < 1);
+            if (visible) {
+                int l;
+
+                l = same[left];
+                while (l != left && l != right) {
+                    if (l < right) {
+                        left = l;
+                        l = same[left];
+                    } else {
+                        same[left] = right;
+                        left = right;
+                        l = same[left];
+                        right = l;
+                    }
+                }
+                same[left] = right;
+            }
+        }
+    }
+}
+
+
+
+static void
+makeMaskRow(const struct pam * const outPamP,
+            const int *        const same,
+            const tuple *      const outRow) {
+    int col;
+
+    for (col = outPamP->width-1; col >= 0; --col) {
+        bool const duplicate = (same[col] != col);
+        unsigned int plane;
+
+        for (plane = 0; plane < outPamP->depth; ++plane)
+            outRow[col][plane] = duplicate ? outPamP->maxval : 0;
+    }
+}
+
+
+
+static void
+makeImageRow(outGenerator * const outGenP,
+             int            const row,
+             const int *    const same,
+             const tuple *  const outRow) {
+/*----------------------------------------------------------------------------
+  same[N] is one of two things:
+
+  same[N] == N means to generate a value for Column N independent of
+  other columns in the row.
+
+  same[N] > N means Column N should be identical to Column same[N].
+  
+  same[N] < N is not allowed.
+-----------------------------------------------------------------------------*/
+    int col;
+    for (col = outGenP->pam.width-1; col >= 0; --col) {
+        bool const duplicate = (same[col] != col);
+        
+        tuple newtuple;
+        unsigned int plane;
+
+        if (duplicate) {
+            assert(same[col] > col);
+            assert(same[col] < outGenP->pam.width);
+
+            newtuple = outRow[same[col]];
+        } else 
+            newtuple = outGenP->getTuple(outGenP, col, row);
+
+        for (plane = 0; plane < outGenP->pam.depth; ++plane)
+            outRow[col][plane] = newtuple[plane];
+    }
+}
+
+
+
+static void
+invertHeightRow(const struct pam * const heightPamP,
+                tuple *            const tupleRow) {
+
+    int col;
+
+    for (col = 0; col < heightPamP->width; ++col)
+        tupleRow[col][0] = heightPamP->maxval - tupleRow[col][0];
+}
+
+
+
+static void
+makeImageRows(const struct pam * const inPamP,
+              outGenerator *     const outputGeneratorP,
+              double             const depthOfField,
+              double             const eyesep,
+              unsigned int       const dpi,
+              bool               const crossEyed,
+              bool               const makeMask) {
+
+    tuple * inRow;     /* One row of pixels read from the height-map file */
+    tuple * outRow;    /* One row of pixels to write to the height-map file */
+    int * same;
+        /* Array: same[N] is the column number of a pixel to the right forced
+           to have the same color as the one in column N
+        */
+    int row;           /* Current row in the input and output files */
+
+    inRow = pnm_allocpamrow(inPamP);
+    outRow = pnm_allocpamrow(&outputGeneratorP->pam);
+    MALLOCARRAY(same, inPamP->width);
+    if (same == NULL)
+        pm_error("Unable to allocate space for \"same\" array.");
+
+    /* See the paper cited above for code comments.  All I (Scott) did was
+     * transcribe the code and make minimal changes for Netpbm.  And some
+     * style changes by Bryan to match Netpbm style.
+     */
+    for (row = 0; row < inPamP->height; ++row) {
+        pnm_readpamrow(inPamP, inRow);
+        if (crossEyed)
+            /* Invert heights for cross-eyed (as opposed to wall-eyed)
+               people.
+            */
+            invertHeightRow(inPamP, inRow);
+
+        /* Determine color constraints. */
+        makeStereoRow(inPamP, inRow, same, depthOfField, eyesep, dpi);
+
+        if (makeMask)
+            makeMaskRow(&outputGeneratorP->pam, same, outRow);
+        else
+            makeImageRow(outputGeneratorP, row, same, outRow);
+
+        /* Write the resulting row. */
+        pnm_writepamrow(&outputGeneratorP->pam, outRow);
+    }
+    free(same);
+    pnm_freepamrow(outRow);
+    pnm_freepamrow(inRow);
+}
+
+
+
+static void
+produceStereogram(FILE *             const ifP,
+                  struct cmdlineInfo const cmdline) {
+
+    struct pam inPam;    /* PAM information for the height-map file */
+    outGenerator * outputGeneratorP;
+        /* Handle of an object that generates background pixels */
+    
+    pnm_readpaminit(ifP, &inPam, PAM_STRUCT_SIZE(tuple_type));
+
+    createoutputGenerator(cmdline, &inPam, &outputGeneratorP);
+
+    if (cmdline.verbose) {
+        reportImageParameters("Input (height map) file", &inPam);
+        if (inPam.depth > 1)
+            pm_message("Ignoring all but the first plane of input.");
+        reportImageParameters("Output (stereogram) file",
+                              &outputGeneratorP->pam);
+    }
+
+    pnm_writepaminit(&outputGeneratorP->pam);
+
+    /* Draw guide boxes at the top, if desired. */
+    if (cmdline.guidesize < 0)
+        drawguides(-cmdline.guidesize, &outputGeneratorP->pam,
+                   cmdline.eyesep,
+                   cmdline.dpi, cmdline.depth);
+
+    makeImageRows(&inPam, outputGeneratorP,
+                  cmdline.depth, cmdline.eyesep, cmdline.dpi,
+                  cmdline.crosseyed, cmdline.makemask);
+
+    /* Draw guide boxes at the bottom, if desired. */
+    if (cmdline.guidesize > 0)
+        drawguides(cmdline.guidesize, &outputGeneratorP->pam,
+                   cmdline.eyesep, cmdline.dpi, cmdline.depth);
+
+    destroyoutputGenerator(outputGeneratorP);
+}
+
+
+
+static void
+reportParameters(struct cmdlineInfo const cmdline) {
+
+    unsigned int const pixelEyesep = round2int(cmdline.eyesep * cmdline.dpi);
+
+    pm_message("Eye separation: %.4g inch * %d DPI = %u pixels",
+               cmdline.eyesep, cmdline.dpi, pixelEyesep);
+
+    if (cmdline.magnifypat > 1)
+        pm_message("Background magnification: %uX * %uX",
+                   cmdline.magnifypat, cmdline.magnifypat);
+    pm_message("\"Optimal\" pattern width: %u / (%u * 2) = %u pixels",
+               pixelEyesep, cmdline.magnifypat,
+               pixelEyesep/(cmdline.magnifypat * 2));
+    pm_message("Unique 3-D depth levels possible: %u",
+               separation(0, cmdline.eyesep, cmdline.dpi, cmdline.depth) -
+               separation(1, cmdline.eyesep, cmdline.dpi, cmdline.depth) + 1);
+    if (cmdline.patFilespec && (cmdline.xshift || cmdline.yshift))
+        pm_message("Pattern shift: (%u, %u)", cmdline.xshift, cmdline.yshift);
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;      /* Parsed command line */
+    FILE * ifP;
+    
+    /* Parse the command line. */
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+    
+    if (cmdline.verbose)
+        reportParameters(cmdline);
+    
+    srand(cmdline.randomseed);
+
+    ifP = pm_openr(cmdline.inputFilespec);
+        
+    /* Produce a stereogram. */
+    produceStereogram(ifP, cmdline);
+
+    pm_close(ifP);
+    
+    return 0;
+}
diff --git a/generator/pamstereogram.test b/generator/pamstereogram.test
new file mode 100644
index 00000000..7eb01fff
--- /dev/null
+++ b/generator/pamstereogram.test
@@ -0,0 +1,70 @@
+# Make some input files
+pamdepth -quiet 255 ../testgrid.pbm >/tmp/testgrid.pgm
+
+
+# Random pattern
+
+echo Test 01.  Should print 610673698 293:
+./pamstereogram -randomseed=1 ../testgrid.pbm | cksum 
+echo Test 02.  Should print 610673698 293:
+./pamstereogram -randomseed=1 -blackandwhite ../testgrid.pbm | cksum 
+echo Test 03.  Should print 3439084201 170:
+pamseq -tupletype=GRAYSCALE 1 100 | ./pamstereogram -randomseed=1 | cksum 
+echo Test 04.  Should print 2484923390 1070:
+pamgauss 100 10 -maxval=10000 -sigma 20 | pamfunc -multiplier=500 | \
+  ./pamstereogram -randomseed=1 -dpi=10 | cksum
+
+# Makemask
+
+echo Test 10.  Should print 1266273778 293:
+./pamstereogram -randomseed=1 -makemask ../testgrid.pbm | cksum 
+
+echo Test 11.  Should print 3034751595 1070:
+./pamgauss 100 10 -maxval=10000 -sigma 20 | pamfunc -multiplier=500 | \
+  ./pamstereogram -randomseed=1 -dpi=10 -makemask | cksum
+
+# Grayscale
+
+echo Test 20.  Should print 2468969328 289:
+./pamstereogram -randomseed=1 -grayscale ../testgrid.pbm | cksum 
+echo Test 21.  Should print 1946982115 4068:
+./pamseq 1 100 | pnmtile 200 20 | \
+  ./pamstereogram -randomseed=1 -dpi=10 -grayscale | \
+  cksum
+echo Test 22.  Should print 2078013430 4068:
+./pamseq 1 100 | pnmtile 200 20 | \
+  ./pamstereogram -randomseed=1 -dpi=10 -grayscale -maxval 255 | \
+  cksum
+
+# Color
+
+echo Test 30.  Should print 1319392622 731:
+./pamstereogram -randomseed=1 -color ../testgrid.pbm | cksum 
+echo Test 31.  Should print 389886159 12062:
+./pamseq 1 100 | pnmtile 200 20 | \
+  ./pamstereogram -randomseed=1 -dpi=10 -color | \
+  cksum
+
+# Pattern file
+
+echo Test 40.  Should print 1834916830 660:
+pamgradient black gray50 white gray50 100 50 | \
+  ./pamstereogram -patfile ../testgrid.pbm -eyesep=.1 -crosseyed | cksum
+
+echo Test 41.  Should print 4016818756 5014:
+pamgradient black gray50 white gray50 100 50 | \
+  ./pamstereogram -patfile /tmp/testgrid.pgm -eyesep=.1 -crosseyed | cksum
+
+# drawguides
+
+echo Test 51.  Should print 2365956562 11071:
+pamgradient black gray50 white gray50 100 50 | \
+  ./pamstereogram -randomseed=1 -dpi 10 -guidesize=20 | cksum
+
+echo Test 51.  Should print 3502025270 1441:
+pamgradient black gray50 white gray50 100 50 | \
+  ./pamstereogram -patfile=../testgrid.pbm -dpi 10 -guidesize=20 | cksum
+
+
+# Clean up files we created
+rm /tmp/testgrid.pgm
diff --git a/generator/pbmmake.c b/generator/pbmmake.c
new file mode 100644
index 00000000..afe1dac3
--- /dev/null
+++ b/generator/pbmmake.c
@@ -0,0 +1,184 @@
+/* pbmmake.c - create a blank bitmap of a specified size
+ *
+ * Akira Urushibata ("Douso") wrote some of the core code that went
+ * into the Netpbm 10.23 (July 2004) version of this program and licenses
+ * that code to the public under GPL.
+ *
+ * Bryan Henderson wrote the rest of that version and contributed his
+ * work to the public domain.
+ *
+ * See doc/HISTORY for a full history of this program.
+**
+*/
+
+#include "shhopt.h"
+#include "mallocvar.h"
+#include "pbm.h"
+
+enum color {COLOR_BLACK, COLOR_WHITE, COLOR_GRAY};
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    unsigned int width;
+    unsigned int height;
+    enum color color;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo *cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def;
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+    unsigned int blackOpt, whiteOpt, grayOpt;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0, "black",     OPT_FLAG, NULL, &blackOpt, 0);
+    OPTENT3(0, "white",     OPT_FLAG, NULL, &whiteOpt, 0);
+    OPTENT3(0, "gray",      OPT_FLAG, NULL, &grayOpt,  0);
+    OPTENT3(0, "grey",      OPT_FLAG, NULL, &grayOpt,  0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We may have parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (blackOpt + whiteOpt + grayOpt > 1)
+        pm_error("You can specify only one of -black, -white, and -gray");
+    
+    if (blackOpt)
+        cmdlineP->color = COLOR_BLACK;
+    else if (whiteOpt)
+        cmdlineP->color = COLOR_WHITE;
+    else if (grayOpt)
+        cmdlineP->color = COLOR_GRAY;
+    else
+        cmdlineP->color = COLOR_WHITE;
+
+    if (argc-1 != 2)
+        pm_error("Wrong number of arguments (%d).  There are two "
+                 "non-option arguments: width and height in pixels",
+                 argc-1);
+    else {
+        cmdlineP->width  = atoi(argv[1]);
+        cmdlineP->height = atoi(argv[2]);
+
+        if (cmdlineP->width < 1) 
+            pm_error("Width must be positive.  You specified %d.", 
+                     cmdlineP->width);
+
+        if (cmdlineP->height < 1) 
+            pm_error("Height must be positive.  You specified %d.",
+                     cmdlineP->height);
+    }
+}
+
+
+
+static void 
+writeGrayRaster(unsigned int const cols, 
+                unsigned int const rows,
+                FILE *       const ofP) {
+
+    unsigned int const lastCol = (cols-1)/8;
+
+    unsigned char * bitrow0, * bitrow1;
+    unsigned int i;
+
+    bitrow0 = pbm_allocrow_packed(cols);
+    bitrow1 = pbm_allocrow_packed(cols);
+
+    for (i=0; i <= lastCol; ++i) { 
+        bitrow0[i] = (PBM_WHITE*0xaa) | (PBM_BLACK*0x55);
+        bitrow1[i] = (PBM_WHITE*0x55) | (PBM_BLACK*0xaa);
+        /* 0xaa = 10101010 ; 0x55 = 01010101 */
+    }
+    if (cols % 8 > 0) { 
+        bitrow0[lastCol] >>= 8 - cols % 8;
+        bitrow0[lastCol] <<= 8 - cols % 8;
+        bitrow1[lastCol] >>= 8 - cols % 8;
+        bitrow1[lastCol] <<= 8 - cols % 8;
+    }
+    if (rows > 1) {
+        unsigned int row;
+        for (row = 1; row < rows; row += 2) {
+            pbm_writepbmrow_packed(ofP, bitrow0, cols, 0);
+            pbm_writepbmrow_packed(ofP, bitrow1, cols, 0);
+        }
+    }
+    if (rows % 2 == 1)
+        pbm_writepbmrow_packed(ofP, bitrow0, cols, 0);
+
+    pbm_freerow(bitrow0);
+    pbm_freerow(bitrow1);
+}
+
+    
+
+static void
+writeSingleColorRaster(unsigned int const cols,
+                       unsigned int const rows,
+                       bit          const color,
+                       FILE *       const ofP) {
+
+    unsigned int const lastCol = (cols-1)/8;
+
+    unsigned char * bitrow0;
+    unsigned int i;
+
+    bitrow0 = pbm_allocrow_packed(cols);
+
+    for (i = 0; i <= lastCol; ++i) 
+        bitrow0[i] = color*0xff;
+
+    if (cols % 8 > 0) { 
+        bitrow0[lastCol] >>= 8 - cols % 8;
+        bitrow0[lastCol] <<= 8 - cols % 8;
+        /* row end trimming, really not necessary with white */
+    }
+    {
+        unsigned int row;
+        for (row = 0; row < rows; ++row)
+            pbm_writepbmrow_packed(ofP, bitrow0, cols, 0);
+    }
+    pbm_freerow(bitrow0);
+}
+
+
+
+int
+main(int argc, char * argv[]) {
+
+    struct cmdlineInfo cmdline;
+
+    pbm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    pbm_writepbminit(stdout, cmdline.width, cmdline.height, 0);
+    
+    if (cmdline.color == COLOR_GRAY)
+        writeGrayRaster(cmdline.width, cmdline.height, stdout);
+    else {
+        bit const color = cmdline.color == COLOR_WHITE ? PBM_WHITE : PBM_BLACK;
+        writeSingleColorRaster(cmdline.width, cmdline.height, color, stdout);
+    }
+    pm_close(stdout);
+    
+    return 0;
+}
diff --git a/generator/pbmmake.test b/generator/pbmmake.test
new file mode 100644
index 00000000..0fd99ccd
--- /dev/null
+++ b/generator/pbmmake.test
@@ -0,0 +1,9 @@
+echo Test 1.  Should print 3892756435 12
+./pbmmake -white 16 2 | cksum
+echo Test 2.  Should print 1576602925 8
+./pbmmake -black 1 1  | cksum
+echo Test 3.  Should print 4272952448 14
+./pbmmake -gray 7 7   | cksum
+echo Test 4.  Should print 1634688086 15
+./pbmmake -grey 8 8   | cksum
+echo Tests done.
diff --git a/generator/pbmpage.c b/generator/pbmpage.c
new file mode 100644
index 00000000..e10ee6d6
--- /dev/null
+++ b/generator/pbmpage.c
@@ -0,0 +1,291 @@
+/***************************************************************************
+                                pbmpage
+
+  This program produces a printed page test pattern in PBM format.
+
+  This was adapted from Tim Norman's 'pbmtpg' program, part of his
+  'pbm2ppa' package, by Bryan Henderson on 2000.05.01.  The only
+  change was to make it use the Netpbm libraries to generate the
+  output.
+
+  For copyright and licensing information, see the pbmtoppa program,
+  which was also derived from the same package.
+****************************************************************************/
+
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <stdio.h>
+
+#include "pbm.h"
+
+/* US is 8.5 in by 11 in */
+
+#define USWIDTH  (5100)
+#define USHEIGHT (6600)
+
+/* A4 is 210 mm by 297 mm == 8.27 in by 11.69 in */
+
+#define A4WIDTH  (4960)
+#define A4HEIGHT (7016)
+
+
+struct bitmap {
+    int Width;      /* width and height in 600ths of an inch */
+    int Height;
+    int Pwidth;     /* width in bytes */
+    char *bitmap;
+};
+
+static struct bitmap bitmap;
+
+
+
+static void
+setpixel(int const x,
+         int const y,
+         int const c) {
+
+    int const charidx = y * bitmap.Pwidth + x/8;
+    char const bitmask = 128 >> (x % 8);
+
+    if (x < 0 || x >= bitmap.Width)
+        return;
+    if (y < 0 || y >= bitmap.Height)
+        return;
+
+    if (c)
+        bitmap.bitmap[charidx] |= bitmask;
+    else
+        bitmap.bitmap[charidx] &= ~bitmask;
+}
+
+
+
+static void 
+setplus(int x,int y,int s)
+/*----------------------------------------------------------------------------
+   Draw a black plus sign centered at (x,y) with arms 's' pixels long.  
+   Leave the exact center of the plus white.
+-----------------------------------------------------------------------------*/
+{
+  int i;
+
+  for(i=0; i<s; i++)
+  {
+    setpixel(x+i,y,1);
+    setpixel(x-i,y,1);
+    setpixel(x,y+i,1);
+    setpixel(x,y-i,1);
+  }
+}
+
+
+
+static void 
+setblock(int x,int y,int s)
+{
+  int i,j;
+
+  for(i=0; i<s; i++)
+    for(j=0; j<s; j++)
+      setpixel(x+i,y+j,1);
+}
+
+
+
+static void 
+setchar(int x,int y,char c)
+{
+  int xo,yo;
+  static char charmap[10][5]= { { 0x3e, 0x41, 0x41, 0x41, 0x3e },
+				{ 0x00, 0x42, 0x7f, 0x40, 0x00 },
+				{ 0x42, 0x61, 0x51, 0x49, 0x46 },
+				{ 0x22, 0x41, 0x49, 0x49, 0x36 },
+				{ 0x18, 0x14, 0x12, 0x7f, 0x10 },
+				{ 0x27, 0x45, 0x45, 0x45, 0x39 },
+				{ 0x3e, 0x49, 0x49, 0x49, 0x32 },
+				{ 0x01, 0x01, 0x61, 0x19, 0x07 },
+				{ 0x36, 0x49, 0x49, 0x49, 0x36 },
+				{ 0x26, 0x49, 0x49, 0x49, 0x3e } };
+
+  if(c<='9' && c>='0')
+    for(xo=0; xo<5; xo++)
+      for(yo=0; yo<8; yo++)
+	if((charmap[c-'0'][xo]>>yo)&1)
+	  setblock(x+xo*3,y+yo*3,3);
+}
+
+
+
+static void 
+setstring(int x,int y,char* s)
+{
+  char* p;
+  int xo;
+
+  for(xo=0, p=s; *p; xo+=21, p++)
+    setchar(x+xo,y,*p);
+}
+
+
+
+static void 
+setCG(int x,int y)
+{
+  int xo,yo,zo;
+
+  for(xo=0; xo<=50; xo++)
+  {
+    yo=sqrt(50.0*50.0-xo*xo);
+    setpixel(x+xo,y+yo,1);
+    setpixel(x+yo,y+xo,1);
+    setpixel(x-1-xo,y-1-yo,1);
+    setpixel(x-1-yo,y-1-xo,1);
+    setpixel(x+xo,y-1-yo,1);
+    setpixel(x-1-xo,y+yo,1);
+    for(zo=0; zo<yo; zo++)
+    {
+      setpixel(x+xo,y-1-zo,1);
+      setpixel(x-1-xo,y+zo,1);
+    }
+  }
+}
+
+
+
+static void
+outputPbm(FILE *        const file,
+          struct bitmap const bitmap) {
+/*----------------------------------------------------------------------------
+  Create a pbm file containing the image from the global variable bitmap[].
+-----------------------------------------------------------------------------*/
+    int const forceplain = 0;
+    bit *pbmrow;
+    int row;
+    int bitmap_cursor;
+    
+    pbm_writepbminit(file, bitmap.Width, bitmap.Height, forceplain);
+  
+    /* We round the allocated row space up to a multiple of 8 so the ugly
+       fast code below can work.
+       */
+    pbmrow = pbm_allocrow(((bitmap.Width+7)/8)*8);
+    
+    bitmap_cursor = 0;
+    for (row = 0; row < bitmap.Height; row++) {
+        int col;
+        for (col = 0; col < bitmap.Width;) {
+            /* A little ugliness makes a big speed difference here. */
+            pbmrow[col++] = bitmap.bitmap[bitmap_cursor] & (1<<7);
+            pbmrow[col++] = bitmap.bitmap[bitmap_cursor] & (1<<6);
+            pbmrow[col++] = bitmap.bitmap[bitmap_cursor] & (1<<5);
+            pbmrow[col++] = bitmap.bitmap[bitmap_cursor] & (1<<4);
+            pbmrow[col++] = bitmap.bitmap[bitmap_cursor] & (1<<3);
+            pbmrow[col++] = bitmap.bitmap[bitmap_cursor] & (1<<2);
+            pbmrow[col++] = bitmap.bitmap[bitmap_cursor] & (1<<1);
+            pbmrow[col++] = bitmap.bitmap[bitmap_cursor] & (1<<0);
+                
+            bitmap_cursor++;
+        }
+        pbm_writepbmrow(file, pbmrow, bitmap.Width, forceplain); 
+    }
+    pbm_freerow(pbmrow);
+    pm_close(file);
+}
+
+
+static void
+framePerimeter(unsigned int const Width, 
+               unsigned int const Height) {
+
+    unsigned int x,y;
+
+    /* Top edge */
+    for (x = 0; x < Width; ++x)
+        setpixel(x, 0, 1);
+
+    /* Bottom edge */
+    for (x = 0; x < Width; ++x)
+        setpixel(x, Height-1, 1);
+
+    /* Left edge */
+    for (y = 0; y < Height; ++y)
+        setpixel(0, y, 1);
+
+    /* Right edge */
+    for (y = 0; y < Height; ++y)
+        setpixel(Width-1, y, 1);
+}
+
+
+
+int 
+main(int argc,char** argv) {
+
+    int TP=1;
+    int x,y;
+    char buf[128];
+    int Width;      /* width and height in 600ths of an inch */
+    int Height;
+
+    pbm_init(&argc, argv);
+
+    if (argc > 1 && strcmp(argv[1], "-a4") == 0) {
+        Width = A4WIDTH;
+        Height = A4HEIGHT;
+        argc--;
+        argv++;
+    } else {
+        Width = USWIDTH;
+        Height = USHEIGHT;
+    }
+
+    bitmap.Width = Width;
+    bitmap.Height = Height;
+    bitmap.Pwidth = (Width + 7) / 8;
+    bitmap.bitmap = malloc(bitmap.Pwidth * bitmap.Height);
+
+    if (argc>1)
+        TP = atoi(argv[1]);
+
+    switch (TP) {
+    case 1:
+        framePerimeter(Width, Height);
+        for (x = 0; x < Width; x += 100)
+            for(y = 0; y < Height; y += 100)
+                setplus(x, y, 4);
+        for(x = 0; x < Width; x += 100) {
+            sprintf(buf,"%d", x);
+            setstring(x + 3, (Height/200) * 100 + 3, buf);
+        }
+        for (y=0; y < Height; y += 100) {
+            sprintf(buf, "%d", y);
+            setstring((Width/200) * 100 + 3, y + 3, buf);
+        }
+        for (x = 0; x < Width; x += 10)
+            for (y = 0; y < Height; y += 100)
+                setplus(x, y, ((x%100) == 50) ? 2 : 1);
+        for (x=0; x < Width; x += 100)
+            for (y = 0; y < Height; y += 10)
+                setplus(x, y, ((y%100) == 50) ? 2 : 1);
+        setCG(Width/2, Height/2);
+        break;
+    case 2:
+        for (y = 0; y < 300; ++y)
+            setpixel(Width/2, Height/2-y, 1);
+        break;
+    case 3:
+        for (y = 0; y < 300; ++y) {
+            setpixel(y, y, 1);
+            setpixel(Width-1-y, Height-1-y, 1);
+        }
+        break;
+    default:
+        pm_error("unknown test pattern (%d)", TP);
+    }
+
+    outputPbm(stdout, bitmap);
+
+    return 0;
+}
diff --git a/generator/pbmtext.c b/generator/pbmtext.c
new file mode 100644
index 00000000..e1d132d0
--- /dev/null
+++ b/generator/pbmtext.c
@@ -0,0 +1,732 @@
+/* pbmtext.c - render text into a bitmap
+**
+** Copyright (C) 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#define _BSD_SOURCE 1      /* Make sure strdup() is in string.h */
+#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
+
+#include <string.h>
+
+#include "pbm.h"
+#include "pbmfont.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+
+struct cmdline_info {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *text;    /* text from command line or NULL if none */
+    const char *font;    /* -font option value or NULL if none */
+    const char *builtin; /* -builtin option value or NULL if none */
+    unsigned int dump;   
+        /* undocumented dump option for installing a new built-in font */
+    float space;   /* -space option value or default */
+    unsigned int width;     /* -width option value or zero */
+    int lspace;    /* lspace option value or default */
+    unsigned int nomargins;     /* -nomargins */
+};
+
+
+
+
+static void
+parse_command_line(int argc, char ** argv,
+                   struct cmdline_info *cmdline_p) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def = malloc(100*sizeof(optEntry));
+        /* Instructions to OptParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0, "font",      OPT_STRING, &cmdline_p->font, NULL,        0);
+    OPTENT3(0, "builtin",   OPT_STRING, &cmdline_p->builtin, NULL,     0);
+    OPTENT3(0, "dump",      OPT_FLAG,   NULL, &cmdline_p->dump,        0);
+    OPTENT3(0, "space",     OPT_FLOAT,  &cmdline_p->space, NULL,       0);
+    OPTENT3(0, "width",     OPT_UINT,   &cmdline_p->width, NULL,       0);
+    OPTENT3(0, "lspace",    OPT_INT,    &cmdline_p->lspace, NULL,      0);
+    OPTENT3(0, "nomargins", OPT_FLAG,   NULL, &cmdline_p->nomargins,   0);
+
+    /* Set the defaults */
+    cmdline_p->font = NULL;
+    cmdline_p->builtin = NULL;
+    cmdline_p->space = 0.0;
+    cmdline_p->width = 0;
+    cmdline_p->lspace = 0;
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdline_p and others. */
+
+    if (argc-1 == 0)
+        cmdline_p->text = NULL;
+    else {
+        char *text;
+        int i;
+        int totaltextsize;
+
+        totaltextsize = 1;  /* initial value */
+        
+        text = malloc(totaltextsize);  /* initial allocation */
+        text[0] = '\0';
+        
+        for (i = 1; i < argc; i++) {
+            if (i > 1) {
+                totaltextsize += 1;
+                text = realloc(text, totaltextsize);
+                if (text == NULL)
+                    pm_error("out of memory allocating space for input text");
+                strcat(text, " ");
+            } 
+            totaltextsize += strlen(argv[i]);
+            text = realloc(text, totaltextsize);
+            if (text == NULL)
+                pm_error("out of memory allocating space for input text");
+            strcat(text, argv[i]);
+        }
+        cmdline_p->text = text;
+    }
+}
+
+
+struct text {
+    char **      textArray;  /* malloc'ed */
+    unsigned int allocatedLineCount;
+    unsigned int lineCount;
+};
+
+
+static void
+allocTextArray(struct text * const textP,
+               unsigned int  const maxLineCount,
+               unsigned int  const maxColumnCount) {
+
+    unsigned int line;
+
+    textP->allocatedLineCount = maxLineCount;
+
+    MALLOCARRAY_NOFAIL(textP->textArray, maxLineCount);
+    
+    for (line = 0; line < maxLineCount; ++line)
+        MALLOCARRAY_NOFAIL(textP->textArray[line], maxColumnCount+1);
+
+    textP->lineCount = 0;
+}
+
+
+static void
+freeTextArray(struct text const text) {
+
+    unsigned int line;
+
+    for (line = 0; line < text.allocatedLineCount; ++line)
+        free((char **)text.textArray[line]);
+
+    free(text.textArray);
+}
+
+
+
+static void
+fix_control_chars(char * const buf, struct font * const fn) {
+
+    int i;
+
+    /* chop off terminating newline */
+    if (strlen(buf) >= 1 && buf[strlen(buf)-1] == '\n')
+        buf[strlen(buf)-1] = '\0';
+    
+    for ( i = 0; buf[i] != '\0'; ++i ) {
+        if ( buf[i] == '\t' ) { 
+            /* Turn tabs into the right number of spaces. */
+            int j, n, l;
+            n = ( i + 8 ) / 8 * 8;
+            l = strlen( buf );
+            for ( j = l; j > i; --j )
+                buf[j + n - i - 1] = buf[j];
+            for ( ; i < n; ++i )
+                buf[i] = ' ';
+            --i;
+        }
+        else if ( !fn->glyph[(unsigned char)buf[i]] )
+            /* Turn unknown chars into a single space. */
+            buf[i] = ' ';
+    }
+}
+
+
+
+static void
+fill_rect(bit** const bits, 
+          int   const row0, 
+          int   const col0, 
+          int   const height, 
+          int   const width, 
+          bit   const color) {
+
+    int row;
+
+    for (row = row0; row < row0 + height; ++row) {
+        int col;
+        for (col = col0; col < col0 + width; ++col)
+            bits[row][col] = color;
+    }
+}
+
+
+
+static void
+get_line_dimensions(const char line[], const struct font * const font_p, 
+                    const float intercharacter_space,
+                    int * const bwid_p, int * const backup_space_needed_p) {
+/*----------------------------------------------------------------------------
+   Determine the width in pixels of the line of text line[] in the font
+   *font_p, and return it as *bwid_p.  Also determine how much of this
+   width goes to the left of the nominal starting point of the line because
+   the first character in the line has a "backup" distance.  Return that
+   as *backup_space_needed_p.
+-----------------------------------------------------------------------------*/
+    int cursor;  /* cursor into the line of text */
+    float accumulated_ics;
+        /* accumulated intercharacter space so far in the line we are 
+           stepping through.  Because the intercharacter space might not be
+           an integer, we accumulate it here and realize full pixels whenever
+           we have more than one pixel.
+           */
+
+    int no_chars_yet; 
+        /* logical: we haven't seen any renderable characters yet in 
+           the line.
+        */
+    struct glyph * lastGlyphP;
+        /* Glyph of last character processed so far.  Undefined if
+           'no_chars_yet'.
+        */
+
+    no_chars_yet = TRUE;   /* initial value */
+    *bwid_p = 0;  /* initial value */
+    accumulated_ics = 0.0;  /* initial value */
+
+    for (cursor = 0; line[cursor] != '\0'; cursor++) {
+        struct glyph * const glyphP = 
+            font_p->glyph[(unsigned char)line[cursor]];
+
+        if (glyphP) {
+            if (no_chars_yet) {
+                no_chars_yet = FALSE;
+                if (glyphP->x < 0) 
+                    *backup_space_needed_p = -glyphP->x;
+                else {
+                    *backup_space_needed_p = 0;
+                    *bwid_p += glyphP->x;
+                }
+            } else {
+                /* handle extra intercharacter space (-space option) */
+                int full_pixels;  /* integer part of accumulated_ics */
+                accumulated_ics += intercharacter_space;
+                full_pixels = (int) accumulated_ics;
+                if (full_pixels > 0) {
+                    *bwid_p += full_pixels;
+                    accumulated_ics -= full_pixels;
+                }
+                lastGlyphP = glyphP;
+            }
+            *bwid_p += glyphP->xadd;
+        }
+    }
+    if (no_chars_yet)
+        /* Line has no renderable characters */
+        *backup_space_needed_p = 0;
+    else {
+        /* Line has at least one renderable character.
+           Recalculate width of last character in line so it ends
+           right at the right edge of the glyph (no extra space to
+           anticipate another character).
+        */
+        *bwid_p -= lastGlyphP->xadd;
+        *bwid_p += lastGlyphP->width + lastGlyphP->x;
+    }
+}
+
+
+
+static void
+insert_character(const struct glyph * const glyph, 
+                 int                  const toprow, 
+                 int                  const leftcol,
+                 bit **               const bits) {
+/*----------------------------------------------------------------------------
+   Insert one character (whose glyph is 'glyph') into the image bits[].
+   Its top left corner shall be row 'toprow', column 'leftcol'.
+-----------------------------------------------------------------------------*/
+
+    int glyph_y, glyph_x;  /* position within the glyph */
+
+    for (glyph_y = 0; glyph_y < glyph->height; glyph_y++) {
+        for (glyph_x = 0; glyph_x < glyph->width; glyph_x++) {
+            if (glyph->bmap[glyph_y * glyph->width + glyph_x])
+                bits[toprow+glyph_y][leftcol+glyph->x+glyph_x] = 
+                    PBM_BLACK;
+        }
+    }
+}    
+
+
+
+static void
+insert_characters(bit **        const bits, 
+                  struct text   const lp,
+                  struct font * const fontP, 
+                  int           const topmargin, 
+                  int           const leftmargin,
+                  float         const intercharacter_space,
+                  int           const lspace) {
+/*----------------------------------------------------------------------------
+   Render the text 'lp' into the image 'bits' using font *fontP and
+   putting 'intercharacter_space' pixels between characters and
+   'lspace' pixels between the lines.
+-----------------------------------------------------------------------------*/
+    int line;  /* Line number in input text */
+
+    for (line = 0; line < lp.lineCount; ++line) {
+        int row;  /* row in image of top of current typeline */
+        int leftcol;  /* Column in image of left edge of current glyph */
+        int cursor;  /* cursor into a line of input text */
+        float accumulated_ics;
+            /* accumulated intercharacter space so far in the line we
+               are building.  Because the intercharacter space might
+               not be an integer, we accumulate it here and realize
+               full pixels whenever we have more than one pixel. 
+            */
+
+        row = topmargin + line * (fontP->maxheight + lspace);
+        leftcol = leftmargin;
+        accumulated_ics = 0.0;  /* initial value */
+    
+        for (cursor = 0; lp.textArray[line][cursor] != '\0'; ++cursor) {
+            unsigned int const glyphIndex = 
+                (unsigned char)lp.textArray[line][cursor];
+            struct glyph* glyph;   /* the glyph for this character */
+
+            glyph = fontP->glyph[glyphIndex];
+            if (glyph != NULL) {
+                const int toprow = row + fontP->maxheight + fontP->y 
+                    - glyph->height - glyph->y;
+                    /* row number in image of top row in glyph */
+                
+                insert_character(glyph, toprow, leftcol, bits);
+
+                leftcol += glyph->xadd;
+                {
+                    /* handle extra intercharacter space (-space option) */
+                    int full_pixels;  /* integer part of accumulated_ics */
+                    accumulated_ics += intercharacter_space;
+                    full_pixels = (int) accumulated_ics;
+                    if (full_pixels > 0) {
+                        leftcol += full_pixels;
+                        accumulated_ics -= full_pixels;
+                    }
+                }
+            }
+        }
+    }
+}
+
+
+struct outputTextCursor {
+    struct text text;
+        /* The output text.  The lineCount field of this represents
+           the number of lines we have completed.  The line after that
+           is the one we are currently filling.
+        */
+    unsigned int maxWidth;
+        /* A line of output can't be wider than this many pixels */
+    float intercharacterSpace;
+        /* The amount of extra space, in characters, that should be added
+           between every two characters (Pbmtext -space option)
+        */
+    unsigned int columnNo;
+        /* The column Number (starting at 0) in the current line that we are
+           filling where the next character goes.
+        */
+    bool noCharsYet;
+        /* We haven't put any renderable characters yet in the
+           output line. 
+        */
+    unsigned int widthSoFar;
+        /* The accumulated width, in pixels, of all the characters now
+           in the current output line 
+        */
+    float accumulatedIcs;
+        /* accumulated intercharacter space so far in the line we
+           are stepping through.  Because the intercharacter space
+           might not be an integer, we accumulate it here and
+           realize full pixels whenever we have more than one
+           pixel.  
+        */
+};
+
+
+
+static void
+initializeFlowedOutputLine(struct outputTextCursor * const cursorP) {
+
+    cursorP->columnNo = 0;
+    cursorP->noCharsYet = TRUE;
+    cursorP->widthSoFar = 0.0;
+    cursorP->accumulatedIcs = 0.0;
+}
+
+
+
+static void
+initializeFlowedOutput(struct outputTextCursor * const cursorP,
+                       unsigned int              const maxLines,
+                       unsigned int              const maxWidth,
+                       float                     const intercharacterSpace) {
+    
+    allocTextArray(&cursorP->text, maxLines, maxWidth);
+    cursorP->maxWidth = maxWidth;
+    cursorP->intercharacterSpace = intercharacterSpace;
+    initializeFlowedOutputLine(cursorP);
+}
+
+
+
+static void
+finishOutputLine(struct outputTextCursor * const cursorP) {
+
+    if (cursorP->text.lineCount < cursorP->text.allocatedLineCount) {
+        char * const currentLine = 
+            cursorP->text.textArray[cursorP->text.lineCount];
+        currentLine[cursorP->columnNo++] = '\0';
+        ++cursorP->text.lineCount;
+    }
+}
+
+
+
+static void
+placeCharacterInOutput(char                      const lastch,
+                       struct font *             const fontP, 
+                       struct outputTextCursor * const cursorP) {
+/*----------------------------------------------------------------------------
+   Place a character of text in the text array at the position indicated
+   by *cursorP, keeping track of what space this character will occupy
+   when this text array is ultimately rendered using font *fontP.
+
+   Note that while we compute how much space the character will take when
+   rendered, we don't render it.
+-----------------------------------------------------------------------------*/
+    if (cursorP->text.lineCount < cursorP->text.allocatedLineCount) {
+        unsigned int const glyphIndex = (unsigned char)lastch;
+        if (fontP->glyph[glyphIndex]) {
+            if (cursorP->noCharsYet) {
+                cursorP->noCharsYet = FALSE;
+                if (fontP->glyph[glyphIndex]->x > 0) 
+                    cursorP->widthSoFar += fontP->glyph[glyphIndex]->x;
+            } else {
+                /* handle extra intercharacter space (-space option) */
+                unsigned int fullPixels;  /* integer part of accumulatedIcs */
+                cursorP->accumulatedIcs += cursorP->intercharacterSpace;
+                fullPixels = (int) cursorP->accumulatedIcs;
+                if (fullPixels > 0) {
+                    cursorP->widthSoFar += fullPixels;
+                    cursorP->accumulatedIcs -= fullPixels;
+                }
+            }
+            cursorP->widthSoFar += fontP->glyph[glyphIndex]->xadd;
+        }
+        if (cursorP->widthSoFar < cursorP->maxWidth) {
+            char * const currentLine = 
+                cursorP->text.textArray[cursorP->text.lineCount];
+            currentLine[cursorP->columnNo++] = lastch;
+        } else {
+            /* Line is full; finish it off, start the next one, and
+               place the character there.
+            */
+            /* TODO: We really should back up to the previous white space
+               character and move the rest of the line to the next line
+            */
+            finishOutputLine(cursorP);
+            initializeFlowedOutputLine(cursorP);
+            placeCharacterInOutput(lastch, fontP, cursorP);
+        }
+    }
+}
+
+
+
+static void
+flowText(struct text    const inputText,
+         int            const width, 
+         struct font *  const fontP, 
+         float          const intercharacterSpace,
+         struct text *  const outputTextP) {
+    
+    unsigned int const maxLineCount = 50;
+
+    unsigned int inputLine;  
+        /* Input line number on which we are currently working */
+    struct outputTextCursor outputCursor;
+
+    for (inputLine = 0; inputLine < inputText.lineCount; ++inputLine) {
+        unsigned int incursor;   /* cursor into the line we are reading */
+
+        initializeFlowedOutput(&outputCursor, maxLineCount,
+                               width, intercharacterSpace);
+        
+        for (incursor = 0; 
+             inputText.textArray[inputLine][incursor] != '\0'; 
+             ++incursor)
+            placeCharacterInOutput(inputText.textArray[inputLine][incursor],
+                                   fontP, &outputCursor);
+        finishOutputLine(&outputCursor);
+    }
+    *outputTextP = outputCursor.text;
+}
+
+
+
+static void
+truncateText(struct text   const inputText, 
+             unsigned int  const width, 
+             struct font * const fontP, 
+             float         const intercharacterSpace,
+             struct text * const outputTextP) {
+
+    struct text truncatedText;
+    int line;  /* Line number on which we are currently working */
+
+    allocTextArray(&truncatedText, inputText.lineCount, width);
+
+    for (line = 0; line < inputText.lineCount; ++line){
+        int cursor;  /* cursor into the line of text */
+        unsigned char lastch;  /* line[cursor] */
+        int widthSoFar;
+            /* How long the line we've built, in pixels, is so far */
+        float accumulatedIcs;
+        /* accumulated intercharacter space so far in the line we are 
+           stepping through.  Because the intercharacter space might not be
+           an integer, we accumulate it here and realize full pixels whenever
+           we have more than one pixel.
+           */
+
+        int noCharsYet; 
+        /* logical: we haven't seen any renderable characters yet in 
+           the line.
+        */
+        noCharsYet = TRUE;   /* initial value */
+        widthSoFar = 0;  /* initial value */
+        accumulatedIcs = 0.0;  /* initial value */
+
+        truncatedText.textArray[line][0] = '\0';  /* Start with empty line */
+
+        for (cursor = 0; 
+             inputText.textArray[line][cursor] != '\0' && widthSoFar < width; 
+             cursor++) {
+            lastch = inputText.textArray[line][cursor];
+            if (fontP->glyph[(unsigned char)lastch]) {
+                if (noCharsYet) {
+                    noCharsYet = FALSE;
+                    if (fontP->glyph[lastch]->x > 0) 
+                        widthSoFar += fontP->glyph[lastch]->x;
+                } else {
+                    /* handle extra intercharacter space (-space option) */
+                    int fullPixels;  /* integer part of accumulatedIcs */
+                    accumulatedIcs += intercharacterSpace;
+                    fullPixels = (int) intercharacterSpace;
+                    if (fullPixels > 0) {
+                        widthSoFar += fullPixels;
+                        accumulatedIcs -= fullPixels;
+                    }
+                }
+                widthSoFar += fontP->glyph[lastch]->xadd;
+            }
+            if (widthSoFar < width) {
+                truncatedText.textArray[line][cursor] = 
+                    inputText.textArray[line][cursor];
+                truncatedText.textArray[line][cursor+1] = '\0';
+            }
+        }
+    }
+    truncatedText.lineCount = inputText.lineCount;
+    *outputTextP = truncatedText;
+}
+
+
+
+static void
+getText(const char          cmdline_text[], 
+        struct font * const fn, 
+        struct text * const input_textP) {
+
+    struct text input_text;
+
+    if (cmdline_text) {
+        allocTextArray(&input_text, 1, strlen(cmdline_text));
+        strcpy(input_text.textArray[0], cmdline_text);
+        fix_control_chars(input_text.textArray[0], fn);
+        input_text.lineCount = 1;
+    } else {
+        /* Read text from stdin. */
+
+        unsigned int maxlines;  
+            /* Maximum number of lines for which we presently have space
+               in the text array 
+            */
+        char buf[5000];
+        char ** text_array;
+        unsigned int lineCount;
+
+        maxlines = 50;  /* initial value */
+        MALLOCARRAY_NOFAIL(text_array, maxlines);
+        
+        lineCount = 0;  /* initial value */
+        while (fgets(buf, sizeof(buf), stdin) != NULL) {
+            fix_control_chars(buf, fn);
+            if (lineCount >= maxlines) {
+                maxlines *= 2;
+                text_array = (char**) realloc((char*) text_array, 
+                                              maxlines * sizeof(char*));
+                if (text_array == NULL)
+                    pm_error("out of memory");
+            }
+            text_array[lineCount] = strdup(buf);
+            if (text_array[lineCount] == NULL)
+                pm_error("out of memory");
+            ++lineCount;
+        }
+        input_text.textArray = text_array;
+        input_text.lineCount = lineCount;
+        input_text.allocatedLineCount = lineCount;
+    }
+    *input_textP = input_text;
+}
+
+
+
+static void
+compute_image_width(struct text         const lp, 
+                    const struct font * const fn,
+                    float               const intercharacter_space,
+                    int *               const maxwidthP, 
+                    int *               const maxleftbP) {
+    int line;
+    
+    *maxwidthP = 0;  /* initial value */
+    *maxleftbP = 0;  /* initial value */
+    for (line = 0; line < lp.lineCount; ++line) {
+        int bwid, backup_space_needed;
+        
+        get_line_dimensions(lp.textArray[line], fn, intercharacter_space,
+                            &bwid, &backup_space_needed);
+        
+        *maxwidthP = MAX(*maxwidthP, bwid);
+        *maxleftbP = MAX(*maxleftbP, backup_space_needed);
+    }
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    struct cmdline_info cmdline;
+    bit** bits;
+    int rows, cols;
+    struct font* fontP;
+    int vmargin, hmargin;
+    struct text inputText;
+    struct text formattedText;
+    int maxwidth;
+        /* width in pixels of the longest line of text in the image */
+    int maxleftb;
+
+    pbm_init( &argc, argv );
+
+    parse_command_line(argc, argv, &cmdline);
+    
+    if (cmdline.font)
+        fontP = pbm_loadfont(cmdline.font);
+    else {
+        if (cmdline.builtin)
+            fontP = pbm_defaultfont(cmdline.builtin);
+        else
+            fontP = pbm_defaultfont("bdf");
+    }
+
+    if (cmdline.dump) {
+        pbm_dumpfont(fontP);
+        exit(0);
+    }
+
+    getText(cmdline.text, fontP, &inputText);
+    
+    if (cmdline.nomargins) {
+        vmargin = 0;
+        hmargin = 0;
+    } else {
+        if (inputText.lineCount == 1) {
+            vmargin = fontP->maxheight / 2;
+            hmargin = fontP->maxwidth;
+        } else {
+            vmargin = fontP->maxheight;
+            hmargin = 2 * fontP->maxwidth;
+        }
+    }
+
+    if (cmdline.width > 0) {
+        /* Flow or truncate lines to meet user's width request */
+        if (inputText.lineCount == 1) 
+            flowText(inputText, cmdline.width, fontP, cmdline.space,
+                     &formattedText);
+        else
+            truncateText(inputText, cmdline.width, fontP, cmdline.space,
+                         &formattedText);
+        freeTextArray(inputText);
+    } else
+        formattedText = inputText;
+    
+    rows = 2 * vmargin + 
+        formattedText.lineCount * fontP->maxheight + 
+        (formattedText.lineCount-1) * cmdline.lspace;
+
+    compute_image_width(formattedText, fontP, cmdline.space,
+                        &maxwidth, &maxleftb);
+
+    cols = 2 * hmargin + maxwidth;
+    bits = pbm_allocarray(cols, rows);
+
+    /* Fill background with white */
+    fill_rect(bits, 0, 0, rows, cols, PBM_WHITE);
+
+    /* Put the text in  */
+    insert_characters(bits, formattedText, fontP, vmargin, hmargin + maxleftb, 
+                      cmdline.space, cmdline.lspace);
+
+    /* All done. */
+    pbm_writepbm(stdout, bits, cols, rows, 0);
+
+    freeTextArray(formattedText);
+    pm_close(stdout);
+
+    return 0;
+}
diff --git a/generator/pbmtextps.c b/generator/pbmtextps.c
new file mode 100644
index 00000000..ef8ae008
--- /dev/null
+++ b/generator/pbmtextps.c
@@ -0,0 +1,377 @@
+/*
+ * pbmtextps.c -  render text into a bitmap using a postscript interpreter
+ *
+ * Copyright (C) 2002 by James McCann.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose and without fee is hereby granted, provided
+ * that the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  This software is provided "as is" without express or
+ * implied warranty.
+ *
+ * PostScript is a registered trademark of Adobe Systems International.
+ *
+ * Additions by Bryan Henderson contributed to public domain by author.
+ *
+ */
+#define _XOPEN_SOURCE   /* Make sure popen() is in stdio.h */
+#define _BSD_SOURCE     /* Make sure stdrup() is in string.h */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include "pbm.h"
+#include "nstring.h"
+#include "shhopt.h"
+
+
+#define BUFFER_SIZE 2048
+
+static const char *gs_exe_path = 
+#ifdef GHOSTSCRIPT_EXECUTABLE_PATH
+GHOSTSCRIPT_EXECUTABLE_PATH;
+#else
+0;
+#endif
+
+static const char *pnmcrop_exe_path = 
+#ifdef PNMCROP_EXECUTABLE_PATH
+PNMCROP_EXECUTABLE_PATH;
+#else
+0;
+#endif
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    int          res;         /* resolution, DPI */
+    int          fontsize;    /* Size of font in points */
+    const char * font;      /* Name of postscript font */
+    float        stroke;
+        /* Width of stroke in points (only for outline font) */
+    unsigned int verbose;
+    const char * text;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo *cmdlineP) {
+/*---------------------------------------------------------------------------
+  Note that the file spec array we return is stored in the storage that
+  was passed to us as the argv array.
+---------------------------------------------------------------------------*/
+    optEntry *option_def = malloc(100*sizeof(optEntry));
+    /* Instructions to OptParseOptions2 on how to parse our options.
+   */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+    int i;
+    char * text;
+    int totaltextsize = 0;
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0, "resolution", OPT_INT,    &cmdlineP->res,            NULL,  0);
+    OPTENT3(0, "font",       OPT_STRING, &cmdlineP->font,           NULL,  0);
+    OPTENT3(0, "fontsize",   OPT_INT,    &cmdlineP->fontsize,       NULL,  0);
+    OPTENT3(0, "stroke",     OPT_FLOAT,  &cmdlineP->stroke,         NULL,  0);
+    OPTENT3(0, "verbose",    OPT_FLAG,   NULL, &cmdlineP->verbose,         0);
+
+    /* Set the defaults */
+    cmdlineP->res = 150;
+    cmdlineP->fontsize = 24;
+    cmdlineP->font = "Times-Roman";
+    cmdlineP->stroke = -1;
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+
+    text = NULL;
+
+    for (i = 1; i < argc; i++) {
+        if (i > 1) {
+            totaltextsize += 1;
+            text = realloc(text, totaltextsize);
+            if (text == NULL)
+                pm_error("out of memory");
+            strcat(text, " ");
+        } 
+        totaltextsize += strlen(argv[i]);
+        text = realloc(text, totaltextsize);
+        if (text == NULL)
+            pm_error("out of memory");
+        strcat(text, argv[i]);
+    }
+    cmdlineP->text = text;
+}
+
+
+
+static const char *
+construct_postscript(struct cmdlineInfo const cmdl) {
+
+    const char * retval;
+    const char * template;
+
+    if (cmdl.stroke <= 0) 
+        template = "/%s findfont\n%d scalefont\nsetfont\n12 36 moveto\n"
+            "(%s) show\nshowpage\n";
+    else 
+        template = "/%s findfont\n%d scalefont\nsetfont\n12 36 moveto\n"
+            "%f setlinewidth\n0 setgray\n"
+            "(%s) true charpath\nstroke\nshowpage\n";
+
+    if (cmdl.stroke < 0)
+        asprintfN(&retval, template, cmdl.font, cmdl.fontsize, 
+                  cmdl.text);
+    else
+        asprintfN(&retval, template, cmdl.font, cmdl.fontsize, 
+                  cmdl.stroke, cmdl.text);
+
+    return retval;
+}
+
+
+
+static const char *
+gs_executable_name()
+{
+    static char buffer[BUFFER_SIZE];
+    if(! gs_exe_path) {
+        const char * const which = "which gs";
+        FILE *f;
+        memset(buffer, 0, BUFFER_SIZE);
+        if(!(f = popen(which, "r")))
+            pm_error("Can't find ghostscript");
+        fread(buffer, 1, BUFFER_SIZE, f);
+        if(buffer[strlen(buffer) - 1] == '\n')
+            buffer[strlen(buffer) - 1] = 0;
+        pclose(f);
+        if(buffer[0] != '/' && buffer[0] != '.')
+            pm_error("Can't find ghostscript");
+    }
+    else
+        strcpy(buffer, gs_exe_path);
+
+    return buffer;
+}
+
+
+
+static const char *
+crop_executable_name()
+{
+    static char buffer[BUFFER_SIZE];
+    if(! pnmcrop_exe_path) {
+        const char * const which = "which pnmcrop";
+        FILE *f;
+        memset(buffer, 0, BUFFER_SIZE);
+        if(!(f = popen(which, "r"))) {
+            return 0;
+        }
+    
+        fread(buffer, 1, BUFFER_SIZE, f);
+        if(buffer[strlen(buffer) - 1] == '\n')
+            buffer[strlen(buffer) - 1] = 0;
+        pclose(f);
+        if(buffer[0] != '/' && buffer[0] != '.') {
+            buffer[0] = 0;
+            pm_message("Can't find pnmcrop");
+        }
+    }
+    else
+        strcpy(buffer, pnmcrop_exe_path);
+
+    return buffer;
+}
+
+
+
+static const char *
+gsCommand(const char *       const psFname,
+          const char *       const outputFilename, 
+          struct cmdlineInfo const cmdline) {
+
+    const char * retval;
+    int const x = cmdline.res * 11;
+    int const y = cmdline.res * (cmdline.fontsize * 2 + 72)  / 72.;
+    asprintfN(&retval, "%s -g%dx%d -r%d -sDEVICE=pbm "
+              "-sOutputFile=%s -q -dBATCH -dNOPAUSE %s </dev/null >/dev/null", 
+              gs_executable_name(), x, y, cmdline.res, 
+              outputFilename, psFname);
+    return retval;
+}
+
+
+
+static const char *
+cropCommand(const char * const inputFileName) {
+
+    const char * retval;
+    
+    if (crop_executable_name()) {
+        asprintfN(&retval, "%s -top -right %s", 
+                  crop_executable_name(), inputFileName);
+        if (retval == NULL)
+            pm_error("Unable to allocate memory");
+    } else
+        retval = NULL;
+
+    return retval;
+}
+
+
+
+static void
+writeProgram(const char *       const psFname,
+             struct cmdlineInfo const cmdline) {
+
+    const char *ps;
+    FILE * psfile;
+
+    psfile = fopen(psFname, "w");
+    if (psfile == NULL)
+        pm_error("Can't open temp file '%s'.  Errno=%d (%s)",
+                 psFname, errno, strerror(errno));
+
+    ps = construct_postscript(cmdline);
+
+    if (cmdline.verbose)
+        pm_message("Postscript program = '%s'", ps);
+        
+    if (fwrite(ps, 1, strlen(ps), psfile) != strlen(ps))
+        pm_error("Can't write postscript to temp file");
+
+    fclose(psfile);
+
+    strfree(ps);
+}
+
+
+
+static void
+executeProgram(const char *       const psFname, 
+               const char *       const outputFname,
+               struct cmdlineInfo const cmdline) {
+
+    const char * com;
+    int rc;
+
+    com = gsCommand(psFname, outputFname, cmdline);
+    if (com == NULL)
+        pm_error("Can't allocate memory for a 'ghostscript' command");
+    
+    if (cmdline.verbose)
+        pm_message("Running Postscript interpreter '%s'", com);
+
+    rc = system(com);
+    if (rc != 0)
+        pm_error("Failed to run Ghostscript process.  rc=%d", rc);
+
+    strfree(com);
+}
+
+
+
+static void
+cropToStdout(const char * const inputFileName,
+             bool         const verbose) {
+
+    const char * com;
+
+    com = cropCommand(inputFileName);
+    if (com == NULL) {
+        /* No pnmcrop.  So don't crop. */
+        pm_message("Can't find pnmcrop command, image will be large");
+        asprintfN(&com, "cat %s", inputFileName);
+        if (com == NULL) 
+            pm_error("Unable to allocate memory.");
+    } else {
+        FILE *pnmcrop;
+
+        if (verbose)
+            pm_message("Running crop command '%s'", com);
+        
+        pnmcrop = popen(com, "r");
+        if (pnmcrop == NULL)
+            pm_error("Can't run pnmcrop process");
+        else {
+            char buf[2048];
+            bool eof;
+
+            eof = FALSE;
+            
+            while (!eof) {
+                int bytesRead;
+
+                bytesRead = fread(buf, 1, sizeof(buf), pnmcrop);
+                if (bytesRead > 0) {
+                    int rc;
+                    rc = fwrite(buf, 1, bytesRead, stdout);
+                    if (rc != bytesRead)
+                        pm_error("Can't write to stdout");
+                } else if (bytesRead == 0)
+                    eof = TRUE;
+                else
+                    pm_error("Failed to read output of Pnmcrop process.  "
+                             "Errno=%d (%s)", errno, strerror(errno));
+            }
+            fclose(pnmcrop);
+        }
+    }
+    strfree(com);
+}
+
+
+
+static void
+createOutputFile(struct cmdlineInfo const cmdline) {
+
+    const char * const template = "./pstextpbm.%d.tmp.%s";
+    
+    const char * psFname;
+    const char * uncroppedPbmFname;
+
+    asprintfN(&psFname, template, getpid(), "ps");
+    if (psFname == NULL)
+        pm_error("Unable to allocate memory");
+ 
+    writeProgram(psFname, cmdline);
+
+    asprintfN(&uncroppedPbmFname, template, getpid(), "pbm");
+    if (uncroppedPbmFname == NULL)
+        pm_error("Unable to allocate memory");
+ 
+    executeProgram(psFname, uncroppedPbmFname, cmdline);
+
+    unlink(psFname);
+    strfree(psFname);
+
+    cropToStdout(uncroppedPbmFname, cmdline.verbose);
+
+    unlink(uncroppedPbmFname);
+    strfree(uncroppedPbmFname);
+}
+
+
+
+int 
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+
+    pbm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    createOutputFile(cmdline);
+
+    return 0;
+}
diff --git a/generator/pbmupc.c b/generator/pbmupc.c
new file mode 100644
index 00000000..5f694a00
--- /dev/null
+++ b/generator/pbmupc.c
@@ -0,0 +1,544 @@
+/* pbmupc.c - create a Universal Product Code bitmap
+**
+** Copyright (C) 1988 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <string.h>
+
+#include "pbm.h"
+
+#define MARGIN 20
+#define DIGIT_WIDTH 14
+#define DIGIT_HEIGHT 23
+#define LINE1_WIDTH 2
+
+#define LINE2_WIDTH ( 2 * LINE1_WIDTH )
+#define LINE3_WIDTH ( 3 * LINE1_WIDTH )
+#define LINE4_WIDTH ( 4 * LINE1_WIDTH )
+#define LINES_WIDTH ( 7 * LINE1_WIDTH )
+#define SHORT_HEIGHT ( 8 * LINES_WIDTH )
+#define TALL_HEIGHT ( SHORT_HEIGHT + DIGIT_HEIGHT / 2 )
+
+static int alldig ARGS(( char* cp ));
+static void putdigit ARGS(( int d, bit** bits, int row0, int col0 ));
+static int addlines ARGS(( int d, bit** bits, int row0, int col0, int height, bit color ));
+static int rect ARGS(( bit** bits, int row0, int col0, int height, int width, bit color ));
+
+int
+main( argc, argv )
+    int argc;
+    char* argv[];
+    {
+    register bit** bits;
+    int argn, style, rows, cols, row, digrow, col, digcolofs;
+    char* typecode;
+    char* manufcode;
+    char* prodcode;
+    int sum, p, lc0, lc1, lc2, lc3, lc4, rc0, rc1, rc2, rc3, rc4;
+    const char* const usage = "[-s1|-s2] <type> <manufac> <product>";
+
+
+    pbm_init( &argc, argv );
+
+    argn = 1;
+    style = 1;
+
+    /* Check for flags. */
+    while ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' )
+	{
+	if ( pm_keymatch( argv[argn], "-s1", 3 ) )
+	    style = 1;
+	else if ( pm_keymatch( argv[argn], "-s2", 3 ) )
+	    style = 2;
+	else
+	    pm_usage( usage );
+	argn++;
+	}
+
+    if ( argn + 3 < argc )
+	pm_usage( usage );
+    typecode = argv[argn];
+    manufcode = argv[argn + 1];
+    prodcode = argv[argn + 2];
+    argn += 3;
+
+    if ( argn != argc )
+	pm_usage( usage );
+
+    if ( strlen( typecode ) != 1 || ( ! alldig( typecode ) ) ||
+	 strlen( manufcode ) != 5 || ( ! alldig ( manufcode ) ) ||
+	 strlen( prodcode ) != 5 || ( ! alldig ( prodcode ) ) )
+	pm_error(
+	    "type code must be one digit, and\n    manufacturer and product codes must be five digits" );
+    p = typecode[0] - '0';
+    lc0 = manufcode[0] - '0';
+    lc1 = manufcode[1] - '0';
+    lc2 = manufcode[2] - '0';
+    lc3 = manufcode[3] - '0';
+    lc4 = manufcode[4] - '0';
+    rc0 = prodcode[0] - '0';
+    rc1 = prodcode[1] - '0';
+    rc2 = prodcode[2] - '0';
+    rc3 = prodcode[3] - '0';
+    rc4 = prodcode[4] - '0';
+    sum = ( 10 - ( ( ( p + lc1 + lc3 + rc0 + rc2 + rc4 ) * 3 + lc0 + lc2 + lc4 + rc1 + rc3 ) % 10 ) ) % 10;
+
+    rows = 2 * MARGIN + SHORT_HEIGHT + DIGIT_HEIGHT;
+    cols = 2 * MARGIN + 12 * LINES_WIDTH + 11 * LINE1_WIDTH;
+    bits = pbm_allocarray( cols, rows );
+
+    (void) rect( bits, 0, 0, rows, cols, PBM_WHITE );
+    
+    row = MARGIN;
+    digrow = row + SHORT_HEIGHT;
+    col = MARGIN;
+    digcolofs = ( LINES_WIDTH - DIGIT_WIDTH ) / 2;
+
+    if ( style == 1 )
+	putdigit( p, bits, digrow, col - DIGIT_WIDTH - LINE1_WIDTH );
+    else if ( style == 2 )
+	putdigit(
+	    p, bits, row + SHORT_HEIGHT / 2, col - DIGIT_WIDTH - LINE1_WIDTH );
+    col = rect( bits, row, col, TALL_HEIGHT, LINE1_WIDTH, PBM_BLACK );
+    col = rect( bits, row, col, TALL_HEIGHT, LINE1_WIDTH, PBM_WHITE );
+    col = rect( bits, row, col, TALL_HEIGHT, LINE1_WIDTH, PBM_BLACK );
+    col = addlines( p, bits, row, col, TALL_HEIGHT, PBM_WHITE );
+    putdigit( lc0, bits, digrow, col + digcolofs );
+    col = addlines( lc0, bits, row, col, SHORT_HEIGHT, PBM_WHITE );
+    putdigit( lc1, bits, digrow, col + digcolofs );
+    col = addlines( lc1, bits, row, col, SHORT_HEIGHT, PBM_WHITE );
+    putdigit( lc2, bits, digrow, col + digcolofs );
+    col = addlines( lc2, bits, row, col, SHORT_HEIGHT, PBM_WHITE );
+    putdigit( lc3, bits, digrow, col + digcolofs );
+    col = addlines( lc3, bits, row, col, SHORT_HEIGHT, PBM_WHITE );
+    putdigit( lc4, bits, digrow, col + digcolofs );
+    col = addlines( lc4, bits, row, col, SHORT_HEIGHT, PBM_WHITE );
+    col = rect( bits, row, col, TALL_HEIGHT, LINE1_WIDTH, PBM_WHITE );
+    col = rect( bits, row, col, TALL_HEIGHT, LINE1_WIDTH, PBM_BLACK );
+    col = rect( bits, row, col, TALL_HEIGHT, LINE1_WIDTH, PBM_WHITE );
+    col = rect( bits, row, col, TALL_HEIGHT, LINE1_WIDTH, PBM_BLACK );
+    col = rect( bits, row, col, TALL_HEIGHT, LINE1_WIDTH, PBM_WHITE );
+    putdigit( rc0, bits, digrow, col + digcolofs );
+    col = addlines( rc0, bits, row, col, SHORT_HEIGHT, PBM_BLACK );
+    putdigit( rc1, bits, digrow, col + digcolofs );
+    col = addlines( rc1, bits, row, col, SHORT_HEIGHT, PBM_BLACK );
+    putdigit( rc2, bits, digrow, col + digcolofs );
+    col = addlines( rc2, bits, row, col, SHORT_HEIGHT, PBM_BLACK );
+    putdigit( rc3, bits, digrow, col + digcolofs );
+    col = addlines( rc3, bits, row, col, SHORT_HEIGHT, PBM_BLACK );
+    putdigit( rc4, bits, digrow, col + digcolofs );
+    col = addlines( rc4, bits, row, col, SHORT_HEIGHT, PBM_BLACK );
+    col = addlines( sum, bits, row, col, TALL_HEIGHT, PBM_BLACK );
+    col = rect( bits, row, col, TALL_HEIGHT, LINE1_WIDTH, PBM_BLACK );
+    col = rect( bits, row, col, TALL_HEIGHT, LINE1_WIDTH, PBM_WHITE );
+    col = rect( bits, row, col, TALL_HEIGHT, LINE1_WIDTH, PBM_BLACK );
+    if ( style == 1 )
+	putdigit( sum, bits, digrow, col + LINE1_WIDTH );
+
+    pbm_writepbm( stdout, bits, cols, rows, 0 );
+    pm_close( stdout );
+
+    exit( 0 );
+    }
+
+static int
+alldig( cp )
+    char* cp;
+    {
+    for ( ; *cp != '\0'; cp++ )
+	if ( *cp < '0' || *cp > '9' )
+	    return 0;
+    return 1;
+    }
+
+static void
+putdigit( d, bits, row0, col0 )
+    int d;
+    bit** bits;
+    int row0, col0;
+    {
+    int row, col;
+    static bit digits[10][DIGIT_HEIGHT][DIGIT_WIDTH] = {
+        /* 0 */
+        {
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,0,0,0,1,1,1,1,1,1,0,0,0,0},
+            {0,0,0,1,1,1,1,1,1,1,1,0,0,0},
+            {0,0,1,1,1,0,0,0,0,1,1,1,0,0},
+            {0,0,1,1,0,0,0,0,0,0,1,1,0,0},
+            {0,1,1,1,0,0,0,0,0,0,1,1,1,0},
+            {0,1,1,0,0,0,0,0,0,0,0,1,1,0},
+            {0,1,1,0,0,0,0,0,0,0,0,1,1,0},
+            {0,1,1,0,0,0,0,0,0,0,0,1,1,0},
+            {0,1,1,0,0,0,0,0,0,0,0,1,1,0},
+            {0,1,1,0,0,0,0,0,0,0,0,1,1,0},
+            {0,1,1,0,0,0,0,0,0,0,0,1,1,0},
+            {0,1,1,0,0,0,0,0,0,0,0,1,1,0},
+            {0,1,1,0,0,0,0,0,0,0,0,1,1,0},
+            {0,1,1,0,0,0,0,0,0,0,0,1,1,0},
+            {0,1,1,1,0,0,0,0,0,0,1,1,1,0},
+            {0,0,1,1,0,0,0,0,0,0,1,1,0,0},
+            {0,0,1,1,1,0,0,0,0,1,1,1,0,0},
+            {0,0,0,1,1,1,1,1,1,1,1,0,0,0},
+            {0,0,0,0,1,1,1,1,1,1,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0}
+        },
+        /* 1 */
+        {
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,0,0,0,0,0,1,1,0,0,0,0,0,0},
+            {0,0,0,0,0,1,1,1,0,0,0,0,0,0},
+            {0,0,0,0,1,1,1,1,0,0,0,0,0,0},
+            {0,0,0,1,1,1,1,1,0,0,0,0,0,0},
+            {0,0,1,1,1,0,1,1,0,0,0,0,0,0},
+            {0,0,1,1,0,0,1,1,0,0,0,0,0,0},
+            {0,0,0,0,0,0,1,1,0,0,0,0,0,0},
+            {0,0,0,0,0,0,1,1,0,0,0,0,0,0},
+            {0,0,0,0,0,0,1,1,0,0,0,0,0,0},
+            {0,0,0,0,0,0,1,1,0,0,0,0,0,0},
+            {0,0,0,0,0,0,1,1,0,0,0,0,0,0},
+            {0,0,0,0,0,0,1,1,0,0,0,0,0,0},
+            {0,0,0,0,0,0,1,1,0,0,0,0,0,0},
+            {0,0,0,0,0,0,1,1,0,0,0,0,0,0},
+            {0,0,0,0,0,0,1,1,0,0,0,0,0,0},
+            {0,0,0,0,0,0,1,1,0,0,0,0,0,0},
+            {0,0,0,0,0,0,1,1,0,0,0,0,0,0},
+            {0,0,0,0,0,0,1,1,0,0,0,0,0,0},
+            {0,0,0,0,0,0,1,1,0,0,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0}
+        },
+        /* 2 */
+        {
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,0,0,0,0,1,1,1,1,0,0,0,0,0},
+            {0,0,0,1,1,1,1,1,1,1,1,0,0,0},
+            {0,0,1,1,1,1,0,0,1,1,1,1,0,0},
+            {0,1,1,1,0,0,0,0,0,0,1,1,0,0},
+            {0,1,1,0,0,0,0,0,0,0,1,1,1,0},
+            {0,0,0,0,0,0,0,0,0,0,0,1,1,0},
+            {0,0,0,0,0,0,0,0,0,0,0,1,1,0},
+            {0,0,0,0,0,0,0,0,0,0,1,1,1,0},
+            {0,0,0,0,0,0,0,0,0,1,1,1,0,0},
+            {0,0,0,0,0,0,0,0,1,1,1,0,0,0},
+            {0,0,0,0,0,0,0,1,1,1,0,0,0,0},
+            {0,0,0,0,0,0,1,1,1,0,0,0,0,0},
+            {0,0,0,0,0,1,1,1,0,0,0,0,0,0},
+            {0,0,0,0,1,1,1,0,0,0,0,0,0,0},
+            {0,0,0,1,1,1,0,0,0,0,0,0,0,0},
+            {0,0,1,1,1,0,0,0,0,0,0,0,0,0},
+            {0,1,1,1,0,0,0,0,0,0,0,0,0,0},
+            {0,1,1,1,1,1,1,1,1,1,1,1,1,0},
+            {0,1,1,1,1,1,1,1,1,1,1,1,1,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0}
+        },
+        /* 3 */
+        {
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,1,1,1,1,1,1,1,1,1,1,1,1,0},
+            {0,1,1,1,1,1,1,1,1,1,1,1,1,0},
+            {0,0,0,0,0,0,0,0,0,0,1,1,1,0},
+            {0,0,0,0,0,0,0,0,0,1,1,1,0,0},
+            {0,0,0,0,0,0,0,0,1,1,1,0,0,0},
+            {0,0,0,0,0,0,0,1,1,1,0,0,0,0},
+            {0,0,0,0,0,0,1,1,1,0,0,0,0,0},
+            {0,0,0,0,0,1,1,1,1,0,0,0,0,0},
+            {0,0,0,0,0,1,1,1,1,1,1,0,0,0},
+            {0,0,0,0,0,0,0,0,1,1,1,1,0,0},
+            {0,0,0,0,0,0,0,0,0,0,1,1,0,0},
+            {0,0,0,0,0,0,0,0,0,0,1,1,1,0},
+            {0,0,0,0,0,0,0,0,0,0,0,1,1,0},
+            {0,0,0,0,0,0,0,0,0,0,0,1,1,0},
+            {0,1,1,0,0,0,0,0,0,0,1,1,1,0},
+            {0,1,1,1,0,0,0,0,0,0,1,1,0,0},
+            {0,0,1,1,1,1,0,0,1,1,1,1,0,0},
+            {0,0,0,1,1,1,1,1,1,1,1,0,0,0},
+            {0,0,0,0,0,1,1,1,1,0,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0}
+        },
+        /* 4 */
+        {
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,0,0,0,0,0,1,1,0,0,0,0,0,0},
+            {0,0,0,0,0,0,1,1,0,0,0,0,0,0},
+            {0,0,0,0,0,1,1,1,0,0,0,0,0,0},
+            {0,0,0,0,0,1,1,0,0,0,0,0,0,0},
+            {0,0,0,0,1,1,1,0,0,0,0,0,0,0},
+            {0,0,0,0,1,1,0,0,0,0,0,0,0,0},
+            {0,0,0,1,1,1,0,0,0,0,0,0,0,0},
+            {0,0,0,1,1,0,0,0,1,1,0,0,0,0},
+            {0,0,1,1,1,0,0,0,1,1,0,0,0,0},
+            {0,0,1,1,0,0,0,0,1,1,0,0,0,0},
+            {0,1,1,1,0,0,0,0,1,1,0,0,0,0},
+            {0,1,1,1,1,1,1,1,1,1,1,1,1,0},
+            {0,1,1,1,1,1,1,1,1,1,1,1,1,0},
+            {0,0,0,0,0,0,0,0,1,1,0,0,0,0},
+            {0,0,0,0,0,0,0,0,1,1,0,0,0,0},
+            {0,0,0,0,0,0,0,0,1,1,0,0,0,0},
+            {0,0,0,0,0,0,0,0,1,1,0,0,0,0},
+            {0,0,0,0,0,0,0,0,1,1,0,0,0,0},
+            {0,0,0,0,0,0,0,0,1,1,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0}
+        },
+        /* 5 */
+        {
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,1,1,1,1,1,1,1,1,1,1,1,1,0},
+            {0,1,1,1,1,1,1,1,1,1,1,1,1,0},
+            {0,1,1,0,0,0,0,0,0,0,0,0,0,0},
+            {0,1,1,0,0,0,0,0,0,0,0,0,0,0},
+            {0,1,1,0,0,0,0,0,0,0,0,0,0,0},
+            {0,1,1,0,0,0,0,0,0,0,0,0,0,0},
+            {0,1,1,0,0,0,0,0,0,0,0,0,0,0},
+            {0,1,1,1,1,1,1,1,1,0,0,0,0,0},
+            {0,1,1,1,1,1,1,1,1,1,1,0,0,0},
+            {0,0,0,0,0,0,0,0,1,1,1,1,0,0},
+            {0,0,0,0,0,0,0,0,0,0,1,1,0,0},
+            {0,0,0,0,0,0,0,0,0,0,1,1,1,0},
+            {0,0,0,0,0,0,0,0,0,0,0,1,1,0},
+            {0,0,0,0,0,0,0,0,0,0,0,1,1,0},
+            {0,1,1,0,0,0,0,0,0,0,1,1,1,0},
+            {0,1,1,1,0,0,0,0,0,0,1,1,0,0},
+            {0,0,1,1,1,1,0,0,1,1,1,1,0,0},
+            {0,0,0,1,1,1,1,1,1,1,1,0,0,0},
+            {0,0,0,0,0,1,1,1,1,0,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0}
+        },
+        /* 6 */
+        {
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,0,0,0,0,0,0,1,1,0,0,0,0,0},
+            {0,0,0,0,0,0,1,1,1,0,0,0,0,0},
+            {0,0,0,0,0,1,1,1,0,0,0,0,0,0},
+            {0,0,0,0,1,1,1,0,0,0,0,0,0,0},
+            {0,0,0,1,1,1,0,0,0,0,0,0,0,0},
+            {0,0,0,1,1,0,0,0,0,0,0,0,0,0},
+            {0,0,1,1,1,0,0,0,0,0,0,0,0,0},
+            {0,0,1,1,0,1,1,1,1,0,0,0,0,0},
+            {0,0,1,1,1,1,1,1,1,1,1,0,0,0},
+            {0,1,1,1,1,1,0,0,1,1,1,1,0,0},
+            {0,1,1,1,0,0,0,0,0,0,1,1,0,0},
+            {0,1,1,1,0,0,0,0,0,0,1,1,1,0},
+            {0,1,1,0,0,0,0,0,0,0,0,1,1,0},
+            {0,1,1,0,0,0,0,0,0,0,0,1,1,0},
+            {0,1,1,1,0,0,0,0,0,0,1,1,1,0},
+            {0,0,1,1,0,0,0,0,0,0,1,1,0,0},
+            {0,0,1,1,1,1,0,0,1,1,1,1,0,0},
+            {0,0,0,1,1,1,1,1,1,1,1,0,0,0},
+            {0,0,0,0,0,1,1,1,1,0,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0}
+        },
+        /* 7 */
+        {
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,1,1,1,1,1,1,1,1,1,1,1,1,0},
+            {0,1,1,1,1,1,1,1,1,1,1,1,1,0},
+            {0,0,0,0,0,0,0,0,0,0,1,1,1,0},
+            {0,0,0,0,0,0,0,0,0,0,1,1,0,0},
+            {0,0,0,0,0,0,0,0,0,1,1,1,0,0},
+            {0,0,0,0,0,0,0,0,0,1,1,0,0,0},
+            {0,0,0,0,0,0,0,0,1,1,1,0,0,0},
+            {0,0,0,0,0,0,0,0,1,1,0,0,0,0},
+            {0,0,0,0,0,0,0,1,1,1,0,0,0,0},
+            {0,0,0,0,0,0,0,1,1,0,0,0,0,0},
+            {0,0,0,0,0,0,1,1,1,0,0,0,0,0},
+            {0,0,0,0,0,0,1,1,0,0,0,0,0,0},
+            {0,0,0,0,0,1,1,1,0,0,0,0,0,0},
+            {0,0,0,0,0,1,1,0,0,0,0,0,0,0},
+            {0,0,0,0,0,1,1,0,0,0,0,0,0,0},
+            {0,0,0,0,1,1,1,0,0,0,0,0,0,0},
+            {0,0,0,0,1,1,0,0,0,0,0,0,0,0},
+            {0,0,0,0,1,1,0,0,0,0,0,0,0,0},
+            {0,0,0,0,1,1,0,0,0,0,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0}
+        },
+        /* 8 */
+        {
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,0,0,1,1,1,1,1,1,1,1,0,0,0},
+            {0,0,1,1,1,1,1,1,1,1,1,1,0,0},
+            {0,1,1,1,0,0,0,0,0,0,1,1,1,0},
+            {0,1,1,0,0,0,0,0,0,0,0,1,1,0},
+            {0,1,1,0,0,0,0,0,0,0,0,1,1,0},
+            {0,1,1,1,0,0,0,0,0,0,1,1,1,0},
+            {0,0,1,1,1,0,0,0,0,1,1,1,0,0},
+            {0,0,0,1,1,1,0,0,1,1,1,0,0,0},
+            {0,0,0,0,1,1,1,1,1,1,0,0,0,0},
+            {0,0,0,0,1,1,1,1,1,1,0,0,0,0},
+            {0,0,0,1,1,1,0,0,1,1,1,0,0,0},
+            {0,0,1,1,1,0,0,0,0,1,1,1,0,0},
+            {0,1,1,1,0,0,0,0,0,0,1,1,1,0},
+            {0,1,1,0,0,0,0,0,0,0,0,1,1,0},
+            {0,1,1,0,0,0,0,0,0,0,0,1,1,0},
+            {0,1,1,0,0,0,0,0,0,0,0,1,1,0},
+            {0,1,1,1,0,0,0,0,0,0,1,1,1,0},
+            {0,0,1,1,1,1,1,1,1,1,1,1,0,0},
+            {0,0,0,1,1,1,1,1,1,1,1,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0}
+        }, 
+        /* 9 */
+        {
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,0,0,0,0,1,1,1,1,0,0,0,0,0},
+            {0,0,0,1,1,1,1,1,1,1,1,0,0,0},
+            {0,0,1,1,1,1,0,0,1,1,1,1,0,0},
+            {0,0,1,1,0,0,0,0,0,0,1,1,0,0},
+            {0,1,1,1,0,0,0,0,0,0,1,1,1,0},
+            {0,1,1,0,0,0,0,0,0,0,0,1,1,0},
+            {0,1,1,0,0,0,0,0,0,0,0,1,1,0},
+            {0,1,1,1,0,0,0,0,0,0,1,1,1,0},
+            {0,0,1,1,0,0,0,0,0,0,1,1,1,0},
+            {0,0,1,1,1,1,0,0,1,1,1,1,1,0},
+            {0,0,0,1,1,1,1,1,1,1,1,1,0,0},
+            {0,0,0,0,0,1,1,1,1,0,1,1,0,0},
+            {0,0,0,0,0,0,0,0,0,1,1,1,0,0},
+            {0,0,0,0,0,0,0,0,0,1,1,0,0,0},
+            {0,0,0,0,0,0,0,0,1,1,1,0,0,0},
+            {0,0,0,0,0,0,0,1,1,1,0,0,0,0},
+            {0,0,0,0,0,0,1,1,1,0,0,0,0,0},
+            {0,0,0,0,0,1,1,1,0,0,0,0,0,0},
+            {0,0,0,0,0,1,1,0,0,0,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+            {0,0,0,0,0,0,0,0,0,0,0,0,0,0}
+        } 
+    };
+
+    for ( row = 0; row < DIGIT_HEIGHT; row++ )
+	for ( col = 0; col < DIGIT_WIDTH; col++ )
+	    bits[row0 + row][col0 + col] = digits[d][row][col];
+    }
+
+#if __STDC__
+static int
+addlines( int d, bit** bits, int row0, int col0, int height, bit color )
+#else /*__STDC__*/
+static int
+addlines( d, bits, row0, col0, height, color )
+    int d;
+    bit** bits;
+    int row0, col0, height;
+    bit color;
+#endif /*__STDC__*/
+    {
+    switch ( d )
+	{
+	case 0:
+	col0 = rect( bits, row0, col0, height, LINE3_WIDTH, color );
+	col0 = rect( bits, row0, col0, height, LINE2_WIDTH, 1 - color );
+	col0 = rect( bits, row0, col0, height, LINE1_WIDTH, color );
+	col0 = rect( bits, row0, col0, height, LINE1_WIDTH, 1 - color );
+	break;
+
+	case 1:
+	col0 = rect( bits, row0, col0, height, LINE2_WIDTH, color );
+	col0 = rect( bits, row0, col0, height, LINE2_WIDTH, 1 - color );
+	col0 = rect( bits, row0, col0, height, LINE2_WIDTH, color );
+	col0 = rect( bits, row0, col0, height, LINE1_WIDTH, 1 - color );
+	break;
+
+	case 2:
+	col0 = rect( bits, row0, col0, height, LINE2_WIDTH, color );
+	col0 = rect( bits, row0, col0, height, LINE1_WIDTH, 1 - color );
+	col0 = rect( bits, row0, col0, height, LINE2_WIDTH, color );
+	col0 = rect( bits, row0, col0, height, LINE2_WIDTH, 1 - color );
+	break;
+
+	case 3:
+	col0 = rect( bits, row0, col0, height, LINE1_WIDTH, color );
+	col0 = rect( bits, row0, col0, height, LINE4_WIDTH, 1 - color );
+	col0 = rect( bits, row0, col0, height, LINE1_WIDTH, color );
+	col0 = rect( bits, row0, col0, height, LINE1_WIDTH, 1 - color );
+	break;
+
+	case 4:
+	col0 = rect( bits, row0, col0, height, LINE1_WIDTH, color );
+	col0 = rect( bits, row0, col0, height, LINE1_WIDTH, 1 - color );
+	col0 = rect( bits, row0, col0, height, LINE3_WIDTH, color );
+	col0 = rect( bits, row0, col0, height, LINE2_WIDTH, 1 - color );
+	break;
+
+	case 5:
+	col0 = rect( bits, row0, col0, height, LINE1_WIDTH, color );
+	col0 = rect( bits, row0, col0, height, LINE2_WIDTH, 1 - color );
+	col0 = rect( bits, row0, col0, height, LINE3_WIDTH, color );
+	col0 = rect( bits, row0, col0, height, LINE1_WIDTH, 1 - color );
+	break;
+
+	case 6:
+	col0 = rect( bits, row0, col0, height, LINE1_WIDTH, color );
+	col0 = rect( bits, row0, col0, height, LINE1_WIDTH, 1 - color );
+	col0 = rect( bits, row0, col0, height, LINE1_WIDTH, color );
+	col0 = rect( bits, row0, col0, height, LINE4_WIDTH, 1 - color );
+	break;
+
+	case 7:
+	col0 = rect( bits, row0, col0, height, LINE1_WIDTH, color );
+	col0 = rect( bits, row0, col0, height, LINE3_WIDTH, 1 - color );
+	col0 = rect( bits, row0, col0, height, LINE1_WIDTH, color );
+	col0 = rect( bits, row0, col0, height, LINE2_WIDTH, 1 - color );
+	break;
+
+	case 8:
+	col0 = rect( bits, row0, col0, height, LINE1_WIDTH, color );
+	col0 = rect( bits, row0, col0, height, LINE2_WIDTH, 1 - color );
+	col0 = rect( bits, row0, col0, height, LINE1_WIDTH, color );
+	col0 = rect( bits, row0, col0, height, LINE3_WIDTH, 1 - color );
+	break;
+
+	case 9:
+	col0 = rect( bits, row0, col0, height, LINE3_WIDTH, color );
+	col0 = rect( bits, row0, col0, height, LINE1_WIDTH, 1 - color );
+	col0 = rect( bits, row0, col0, height, LINE1_WIDTH, color );
+	col0 = rect( bits, row0, col0, height, LINE2_WIDTH, 1 - color );
+	break;
+
+	default:
+	pm_error( "can't happen" );
+	}
+
+    return col0;
+    }
+
+#if __STDC__
+static int
+rect( bit** bits, int row0, int col0, int height, int width, bit color )
+#else /*__STDC__*/
+static int
+rect( bits, row0, col0, height, width, color )
+    bit** bits;
+    int row0, col0, height, width;
+    bit color;
+#endif /*__STDC__*/
+    {
+    int row, col;
+
+    for ( row = row0; row < row0 + height; row++ )
+	for ( col = col0; col < col0 + width; col++ )
+	    bits[row][col] = color;
+    return col0 + width;
+    }
diff --git a/generator/pgmcrater.c b/generator/pgmcrater.c
new file mode 100644
index 00000000..1833e604
--- /dev/null
+++ b/generator/pgmcrater.c
@@ -0,0 +1,383 @@
+/*
+
+			  Fractal cratering
+
+	   Designed and implemented in November of 1989 by:
+
+	    John Walker
+	    Autodesk SA
+	    Avenue des Champs-Montants 14b
+	    CH-2074 MARIN
+	    Switzerland
+	    Usenet: kelvin@Autodesk.com
+	    Fax:    038/33 88 15
+	    Voice:  038/33 76 33
+
+    The  algorithm  used  to  determine crater size is as described on
+    pages 31 and 32 of:
+
+	Peitgen, H.-O., and Saupe, D. eds., The Science Of Fractal
+	    Images, New York: Springer Verlag, 1988.
+
+    The  mathematical  technique  used	to calculate crater radii that
+    obey the proper area law distribution from a uniformly distributed
+    pseudorandom sequence was developed by Rudy Rucker.
+
+    Permission	to  use, copy, modify, and distribute this software and
+    its documentation  for  any  purpose  and  without	fee  is  hereby
+    granted,  without any conditions or restrictions.  This software is
+    provided "as is" without express or implied warranty.
+
+				PLUGWARE!
+
+    If you like this kind of stuff, you may also enjoy "James  Gleick's
+    Chaos--The  Software"  for  MS-DOS,  available for $59.95 from your
+    local software store or directly from Autodesk, Inc., Attn: Science
+    Series,  2320  Marinship Way, Sausalito, CA 94965, USA.  Telephone:
+    (800) 688-2344 toll-free or, outside the  U.S. (415)  332-2344  Ext
+    4886.   Fax: (415) 289-4718.  "Chaos--The Software" includes a more
+    comprehensive   fractal    forgery	  generator    which	creates
+    three-dimensional  landscapes  as  well as clouds and planets, plus
+    five more modules which explore other aspects of Chaos.   The  user
+    guide  of  more  than  200	pages includes an introduction by James
+    Gleick and detailed explanations by Rudy Rucker of the  mathematics
+    and algorithms used by each program.
+
+*/
+
+/* Modifications by Arjen Bax, 2001-06-21: Remove black vertical line at right
+ * edge. Make craters wrap around the image (enables tiling of image).
+ */
+
+#define _XOPEN_SOURCE   /* get M_PI in math.h */
+
+#include <assert.h>
+#include <math.h>
+
+#include "pm_c_util.h"
+#include "pgm.h"
+#include "mallocvar.h"
+
+static void gencraters ARGS((void));
+static void initseed ARGS((void));
+
+/* Definitions for obtaining random numbers. */
+
+#define Cast(low, high) ((low)+((high)-(low)) * ((rand() & 0x7FFF) / arand))
+
+/*  Data types	*/
+
+typedef int Boolean;
+#define FALSE 0
+#define TRUE 1
+
+#define V   (void)
+
+/*  Display parameters	*/
+
+#define SCRX	screenxsize	      /* Screen width */
+#define SCRY	screenysize	      /* Screen height */
+#define SCRGAMMA 1.0		      /* Display gamma */
+
+/*  Local variables  */
+
+#define ImageGamma  0.5 	      /* Inherent gamma of mapped image */
+
+static int screenxsize = 256;	      /* Screen X size */
+static int screenysize = 256;	      /* Screen Y size */
+static double dgamma = SCRGAMMA;      /* Display gamma */
+static double arand = 32767.0;	      /* Random number parameters */
+static long ncraters = 50000L;	      /* Number of craters to generate */
+static double CdepthPower = 1.5;      /* Crater depth power factor */
+static double DepthBias = 0.707107;   /* Depth bias */
+
+static int modulo(int t, int n)
+{
+    int m;
+    assert(n>0);
+    m = t % n;
+    while (m<0) {
+	m+=n;
+    }
+    return m;
+}
+
+/*  INITSEED  --  Generate initial random seed, if needed.  */
+
+static void initseed()
+{
+    int i;
+
+    i = time(NULL) * 0xF37C;
+    srand(i);
+    for (i = 0; i < 7; i++) 
+        V rand();
+}
+
+/*  GENCRATERS	--  Generate cratered terrain.	*/
+
+static void gencraters()
+{
+    int i, j, x, y;
+    long l;
+    unsigned short *aux;
+    int slopemin = -52, slopemax = 52;
+#define RGBQuant    255
+    unsigned char *slopemap;   /* Slope to pixel map */
+    gray *pixels;	       /* Pixel vector */
+
+#define Auxadr(x, y)  &aux[modulo(y, SCRY)*SCRX+modulo(x, SCRX)]
+
+    /* Acquire the elevation array and initialize it to mean
+       surface elevation. */
+
+    MALLOCARRAY(aux, SCRX * SCRY);
+    if (aux == NULL) 
+        pm_error("out of memory allocating elevation array");
+
+    /* Acquire the elevation buffer and initialize to mean
+       initial elevation. */
+
+    for (i = 0; i < SCRY; i++) {
+	unsigned short *zax = aux + (((long) SCRX) * i);
+
+	for (j = 0; j < SCRX; j++) {
+	    *zax++ = 32767;
+	}
+    }
+
+    /* Every time we go around this loop we plop another crater
+       on the surface.	*/
+
+    for (l = 0; l < ncraters; l++) {
+	double g;
+	int cx = Cast(0.0, ((double) SCRX - 1)),
+	    cy = Cast(0.0, ((double) SCRY - 1)),
+	    gx, gy, x, y;
+	unsigned long amptot = 0, axelev;
+	unsigned int npatch = 0;
+
+
+	/* Phase 1.  Compute the mean elevation of the impact
+		     area.  We assume the impact area is a
+		     fraction of the total crater size. */
+
+	/* Thanks, Rudy, for this equation  that maps the uniformly
+	   distributed	numbers  from	Cast   into   an   area-law
+	   distribution as observed on cratered bodies. */
+
+	g = sqrt(1 / (M_PI * (1 - Cast(0, 0.9999))));
+
+	/* If the crater is tiny, handle it specially. */
+
+#if 0
+	fprintf(stderr, "crater=%lu ", (unsigned long)l);
+	fprintf(stderr, "cx=%d ", cx);
+	fprintf(stderr, "cy=%d ", cy);
+	fprintf(stderr, "size=%g\n", g);
+#endif
+
+	if (g < 3) {
+
+	   /* Set pixel to the average of its Moore neighbourhood. */
+
+	    for (y = cy - 1; y <= cy + 1; y++) {
+		for (x = cx - 1; x <= cx + 1; x++) {
+		    amptot += *Auxadr(x, y);
+		    npatch++;
+		}
+	    }
+	    axelev = amptot / npatch;
+
+	    /* Perturb the mean elevation by a small random factor. */
+
+	    x = (g >= 1) ? ((rand() >> 8) & 3) - 1 : 0;
+	    *Auxadr(cx, cy) = axelev + x;
+
+	    /* Jam repaint sizes to correct patch. */
+
+	    gx = 1;
+	    gy = 0;
+
+	} else {
+
+	    /* Regular crater.	Generate an impact feature of the
+	       correct size and shape. */
+
+	    /* Determine mean elevation around the impact area. */
+
+	    gx = MAX(2, (g / 3));
+	    gy = MAX(2, g / 3);
+
+	    for (y = cy - gy; y <= cy + gy; y++) {
+		for (x = cx-gx; x <= cx + gx; x++) {
+		    amptot += *Auxadr(x,y);
+		    npatch++;
+		}
+	    }
+	    axelev = amptot / npatch;
+
+	    gy = MAX(2, g);
+	    g = gy;
+	    gx = MAX(2, g);
+
+	    for (y = cy - gy; y <= cy + gy; y++) {
+		double dy = (cy - y) / (double) gy,
+		       dysq = dy * dy;
+
+		for (x = cx - gx; x <= cx + gx; x++) {
+		    double dx = ((cx - x) / (double) gx),
+			   cd = (dx * dx) + dysq,
+			   cd2 = cd * 2.25,
+			   tcz = DepthBias - sqrt(fabs(1 - cd2)),
+			   cz = MAX((cd2 > 1) ? 0.0 : -10, tcz),
+			   roll, iroll;
+		    unsigned short av;
+
+		    cz *= pow(g, CdepthPower);
+		    if (dy == 0 && dx == 0 && ((int) cz) == 0) {
+		       cz = cz < 0 ? -1 : 1;
+		    }
+
+#define 	    rollmin 0.9
+		    roll = (((1 / (1 - MIN(rollmin, cd))) /
+			     (1 / (1 - rollmin))) - (1 - rollmin)) / rollmin;
+		    iroll = 1 - roll;
+
+		    av = (axelev + cz) * iroll + (*Auxadr(x,y) + cz) * roll;
+		    av = MAX(1000, MIN(64000, av));
+		    *Auxadr(x,y) = av;
+		}
+	    }
+	 }
+	if ((l % 5000) == 4999) {
+	    pm_message( "%ld craters generated of %ld (%ld%% done)",
+		l + 1, ncraters, ((l + 1) * 100) / ncraters);
+	}
+    }
+
+    i = MAX((slopemax - slopemin) + 1, 1);
+    MALLOCARRAY(slopemap, i);
+    if (slopemap == NULL)
+        pm_error("out of memory allocating slope map");
+
+    for (i = slopemin; i <= slopemax; i++) {
+
+        /* Confused?   OK,  we're using the  left-to-right slope to
+	   calculate a shade based on the sine of  the	angle  with
+	   respect  to the vertical (light incident from the left).
+	   Then, with one exponentiation, we account for  both	the
+	   inherent   gamma   of   the	 image	(ad-hoc),  and	the
+	   user-specified display gamma, using the identity:
+
+		 (x^y)^z = (x^(y*z))		     */
+
+	slopemap[i - slopemin] = i > 0 ?
+	    (128 + 127.0 *
+		pow(sin((M_PI / 2) * i / slopemax),
+		       dgamma * ImageGamma)) :
+	    (128 - 127.0 *
+		pow(sin((M_PI / 2) * i / slopemin),
+		       dgamma * ImageGamma));
+    }
+
+    /* Generate the screen image. */
+
+    pgm_writepgminit(stdout, SCRX, SCRY, RGBQuant, FALSE);
+    pixels = pgm_allocrow(SCRX);
+
+    for (y = 0; y < SCRY; y++) {
+	gray *pix = pixels;
+
+	for (x = 0; x < SCRX; x++) {
+	    int j = *Auxadr(x+1, y) - *Auxadr(x, y);
+	    j = MIN(MAX(slopemin, j), slopemax);
+	    *pix++ = slopemap[j - slopemin];
+	}
+	pgm_writepgmrow(stdout, pixels, SCRX, RGBQuant, FALSE);
+    }
+    pm_close(stdout);
+    pgm_freerow(pixels);
+
+#undef Auxadr
+#undef Scradr
+    free((char *) slopemap);
+    free((char *) aux);
+}
+
+/*  MAIN  --  Main program.  */
+
+int main(argc, argv)
+  int argc;
+  char *argv[];
+{
+    int i;
+    Boolean gammaspec = FALSE, numspec = FALSE,
+	    widspec = FALSE, hgtspec = FALSE;
+    const char * const usage = "[-number <n>] [-width|-xsize <w>]\n\
+                  [-height|-ysize <h>] [-gamma <f>]";
+
+    DepthBias = sqrt(0.5);	      /* Get exact value for depth bias */
+
+
+    pgm_init(&argc, argv);
+
+    i = 1;
+    while ((i < argc) && (argv[i][0] == '-') && (argv[i][1] != '\0')) {
+        if (pm_keymatch(argv[i], "-gamma", 2)) {
+	    if (gammaspec) {
+                pm_error("already specified gamma correction");
+	    }
+	    i++;
+            if ((i == argc) || (sscanf(argv[i], "%lf", &dgamma)  != 1))
+		pm_usage(usage);
+	    if (dgamma <= 0.0) {
+                pm_error("gamma correction must be greater than 0");
+	    }
+	    gammaspec = TRUE;
+        } else if (pm_keymatch(argv[i], "-number", 2)) {
+	    if (numspec) {
+                pm_error("already specified number of craters");
+	    }
+	    i++;
+            if ((i == argc) || (sscanf(argv[i], "%ld", &ncraters) != 1))
+		pm_usage(usage);
+	    if (ncraters <= 0) {
+                pm_error("number of craters must be greater than 0!");
+	    }
+	    numspec = TRUE;
+        } else if (pm_keymatch(argv[i], "-xsize", 2) ||
+                   pm_keymatch(argv[i], "-width", 2)) {
+	    if (widspec) {
+                pm_error("already specified a width/xsize");
+	    }
+	    i++;
+            if ((i == argc) || (sscanf(argv[i], "%d", &screenxsize) != 1))
+		pm_usage(usage);
+	    if (screenxsize <= 0) {
+                pm_error("screen width must be greater than 0");
+	    }
+	    widspec = TRUE;
+        } else if (pm_keymatch(argv[i], "-ysize", 2) ||
+                   pm_keymatch(argv[i], "-height", 2)) {
+	    if (hgtspec) {
+                pm_error("already specified a height/ysize");
+	    }
+	    i++;
+            if ((i == argc) || (sscanf(argv[i], "%d", &screenysize) != 1))
+		pm_usage(usage);
+	    if (screenxsize <= 0) {
+                pm_error("screen height must be greater than 0");
+	    }
+	    hgtspec = TRUE;
+	} else {
+	    pm_usage(usage);
+	}
+	i++;
+    }
+
+    initseed();
+    gencraters();
+
+    exit(0);
+}
diff --git a/generator/pgmkernel.c b/generator/pgmkernel.c
new file mode 100644
index 00000000..b741d596
--- /dev/null
+++ b/generator/pgmkernel.c
@@ -0,0 +1,91 @@
+/* pgmkernel.c - generate a portable graymap convolution kernel
+**
+** Creates a Portable Graymap file containing a convolution filter
+** with max value = 255 and minimum value > 127 that can be used as a 
+** smoothing kernel for pnmconvol.
+**
+** Copyright (C) 1992 by Alberto Accomazzi, Smithsonian Astrophysical
+** Observatory.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <math.h>
+#include "pgm.h"
+#include "mallocvar.h"
+
+int
+main ( argc, argv )
+    int argc;
+    char *argv[];
+{
+    register    int i, j;
+    int     argn = 1, ixsize, iysize, maxval = 255;
+    double  fxsize = 0.0, fysize = 0.0, w = 6.0, kxcenter, kycenter, 
+        tmax = 0, *fkernel;
+    const char  *usage = "[-weight f] width [height]";
+
+    pgm_init( &argc, argv );
+
+    while ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' )
+    {
+        if ( pm_keymatch( argv[argn], "-weight", 2 )) {
+            if (++argn >= argc)
+                pm_usage( usage );
+            else if (sscanf(argv[argn], "%lf", &w) != 1)
+                pm_usage( usage );
+        }
+        else
+            pm_usage( usage );
+        argn++;
+    }
+
+    if (argn == argc)
+        pm_usage( usage );
+    
+    if (sscanf(argv[argn], "%lf", &fxsize) != 1) 
+        pm_error( "error reading input kernel x size, (%s)\n", argv[argn]);
+
+    ++argn;
+    if (argn == argc - 1) {
+        if (sscanf(argv[argn], "%lf", &fysize) != 1)
+            pm_error( "error reading input kernel y size, (%s)\n", argv[argn]);
+    }
+    else if (argn == argc)
+        fysize = fxsize;
+    else
+        pm_usage( usage );
+
+    if (fxsize <= 1 || fysize <= 1)
+        pm_usage( usage );
+
+    kxcenter = (fxsize - 1) / 2.0;
+    kycenter = (fysize - 1) / 2.0;
+    ixsize = fxsize + 0.999;
+    iysize = fysize + 0.999;
+    MALLOCARRAY(fkernel, ixsize * iysize);
+    for (i = 0; i < iysize; i++) 
+        for (j = 0; j < ixsize; j++) {
+            fkernel[i*ixsize+j] = 1.0 / (1.0 + w * sqrt((double)
+                                                        (i-kycenter)*(i-kycenter)+
+                                                        (j-kxcenter)*(j-kxcenter)));
+            if (tmax < fkernel[i*ixsize+j])
+                tmax = fkernel[i*ixsize+j];
+        }
+
+    /* output PGM header + data (ASCII format only) */
+    printf("P2\n%d %d\n%d\n", ixsize, iysize, maxval);
+    
+    for (i = 0; i < iysize; i++, printf("\n"))
+        for (j = 0; j < ixsize; j++)
+            printf(" %3d", (int)(maxval * (fkernel[i*ixsize+j] / 
+                                           (2*tmax) + 0.5)));
+    
+    exit(0);
+}
+
diff --git a/generator/pgmmake.c b/generator/pgmmake.c
new file mode 100644
index 00000000..42d96581
--- /dev/null
+++ b/generator/pgmmake.c
@@ -0,0 +1,111 @@
+#include "pm_c_util.h"
+#include "mallocvar.h"
+#include "shhopt.h"
+#include "pgm.h"
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    gray grayLevel;
+    unsigned int cols;
+    unsigned int rows;
+    gray maxval;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+  Convert program invocation arguments (argc,argv) into a format the 
+  program can use easily, struct cmdlineInfo.  Validate arguments along
+  the way and exit program with message if invalid.
+
+  Note that some string information we return as *cmdlineP is in the storage 
+  argv[] points to.
+-----------------------------------------------------------------------------*/
+    optEntry * option_def;
+        /* Instructions to OptParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int maxvalSpec;
+    unsigned int option_def_index;
+
+    MALLOCARRAY(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0,   "maxval",    OPT_UINT, &cmdlineP->maxval, &maxvalSpec,    0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = false;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = false;  /* We have no parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (!maxvalSpec)
+        cmdlineP->maxval = PGM_MAXMAXVAL;
+    else {
+        if (cmdlineP->maxval > PGM_OVERALLMAXVAL)
+            pm_error("The value you specified for -maxval (%u) is too big.  "
+                     "Max allowed is %u", cmdlineP->maxval, PGM_OVERALLMAXVAL);
+        
+        if (cmdlineP->maxval < 1)
+            pm_error("You cannot specify 0 for -maxval");
+    }    
+
+    if (argc-1 < 3)
+        pm_error("Need 3 arguments: gray level, width, height.");
+    else if (argc-1 > 3)
+        pm_error("Only 3 arguments allowed: gray level, width, height.  "
+                 "You specified %d", argc-1);
+    else {
+        double const grayLevel = atof(argv[1]);
+        if (grayLevel < 0.0)
+            pm_error("You can't have a negative gray level (%f)", grayLevel);
+        if (grayLevel > 1.0)
+            pm_error("Gray level must be in the range [0.0, 1.0].  "
+                     "You specified %f", grayLevel);
+        cmdlineP->grayLevel = ROUNDU(grayLevel * cmdlineP->maxval);
+        cmdlineP->cols = atoi(argv[2]);
+        cmdlineP->rows = atoi(argv[3]);
+        if (cmdlineP->cols <= 0)
+            pm_error("width argument must be a positive number.  You "
+                     "specified '%s'", argv[2]);
+        if (cmdlineP->rows <= 0)
+            pm_error("height argument must be a positive number.  You "
+                     "specified '%s'", argv[3]);
+    }
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+    gray * grayrow;
+    unsigned int row;
+
+    pgm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    pgm_writepgminit(stdout, cmdline.cols, cmdline.rows, cmdline.maxval, 0);
+    grayrow = pgm_allocrow(cmdline.cols);
+
+    for (row = 0; row < cmdline.rows; ++row) {
+        unsigned int col;
+        for (col = 0; col < cmdline.cols; ++col)
+            grayrow[col] = cmdline.grayLevel;
+        pgm_writepgmrow(stdout, grayrow, cmdline.cols, cmdline.maxval, 0);
+	}
+
+    pgm_freerow(grayrow);
+    pm_close(stdout);
+
+    return 0;
+}
diff --git a/generator/pgmnoise.c b/generator/pgmnoise.c
new file mode 100644
index 00000000..3929759b
--- /dev/null
+++ b/generator/pgmnoise.c
@@ -0,0 +1,77 @@
+
+/*********************************************************************/
+/* pgmnoise -  create a portable graymap with white noise            */
+/* Frank Neumann, October 1993                                       */
+/* V1.1 16.11.1993                                                   */
+/*                                                                   */
+/* version history:                                                  */
+/* V1.0 12.10.1993  first version                                    */
+/* V1.1 16.11.1993  Rewritten to be NetPBM.programming conforming    */
+/*********************************************************************/
+
+#include "pgm.h"
+
+/* global variables */
+#ifdef AMIGA
+static char *version = "$VER: pgmnoise 1.1 (16.11.93)"; /* Amiga version identification */
+#endif
+
+/**************************/
+/* start of main function */
+/**************************/
+int main(argc, argv)
+int argc;
+char *argv[];
+{
+	int argn, rows, cols, i, j;
+	gray *destrow;
+	const char * const usage = "width height\n        width and height are picture dimensions in pixels\n";
+	time_t timenow;
+
+	/* parse in 'default' parameters */
+	pgm_init(&argc, argv);
+
+	argn = 1;
+
+	/* parse in dim factor */
+	if (argn == argc)
+		pm_usage(usage);
+	if (sscanf(argv[argn], "%d", &cols) != 1)
+		pm_usage(usage);
+	argn++;
+	if (argn == argc)
+		pm_usage(usage);
+	if (sscanf(argv[argn], "%d", &rows) != 1)
+		pm_usage(usage);
+
+	if (cols <= 0 || rows <= 0)
+		pm_error("picture dimensions should be positive numbers");
+	++argn;
+
+	if (argn != argc)
+		pm_usage(usage);
+
+	/* no error checking required here, ppmlib does it all for us */
+	destrow = pgm_allocrow(cols);
+
+	pgm_writepgminit(stdout, cols, rows, PGM_MAXMAXVAL, 0);
+
+	/* get time of day to feed the random number generator */
+	timenow = time(NULL);
+	srand(timenow);
+
+	/* create the (gray) noise */
+	for (i = 0; i < rows; i++)
+	{
+		for (j = 0; j < cols; j++)
+			destrow[j] = rand() % PGM_MAXMAXVAL;
+
+		/* write out one line of graphic data */
+		pgm_writepgmrow(stdout, destrow, cols, PGM_MAXMAXVAL, 0);
+	}
+
+	pgm_freerow(destrow);
+
+	exit(0);
+}
+
diff --git a/generator/pgmramp.c b/generator/pgmramp.c
new file mode 100644
index 00000000..b84ed345
--- /dev/null
+++ b/generator/pgmramp.c
@@ -0,0 +1,170 @@
+/* pgmramp.c - generate a grayscale ramp
+**
+** Copyright (C) 1989 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <math.h>
+#include "pgm.h"
+#include "shhopt.h"
+
+enum ramptype {RT_LR, RT_TB, RT_RECT, RT_ELLIP};
+
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    enum ramptype ramptype;
+    unsigned int cols;
+    unsigned int rows;
+    gray maxval;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+  Convert program invocation arguments (argc,argv) into a format the 
+  program can use easily, struct cmdlineInfo.  Validate arguments along
+  the way and exit program with message if invalid.
+
+  Note that some string information we return as *cmdlineP is in the storage 
+  argv[] points to.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def = malloc(100*sizeof(optEntry));
+        /* Instructions to OptParseOptions2 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int lrSpec, tbSpec, rectangleSpec, ellipseSpec;
+    unsigned int maxvalSpec;
+    unsigned int option_def_index;
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0,   "lr",        OPT_FLAG, NULL,              &lrSpec,        0);
+    OPTENT3(0,   "tb",        OPT_FLAG, NULL,              &tbSpec,        0);
+    OPTENT3(0,   "rectangle", OPT_FLAG, NULL,              &rectangleSpec, 0);
+    OPTENT3(0,   "ellipse",   OPT_FLAG, NULL,              &ellipseSpec,   0);
+    OPTENT3(0,   "maxval",    OPT_UINT, &cmdlineP->maxval, &maxvalSpec,    0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (lrSpec + tbSpec + rectangleSpec + ellipseSpec == 0)
+        pm_error("You must specify one of -lr, -tb, -rectangle, or -ellipse");
+    if (lrSpec + tbSpec + rectangleSpec + ellipseSpec > 1)
+        pm_error("You may specify at most one of "
+                 "-lr, -tb, -rectangle, or -ellipse");
+    if (lrSpec)
+        cmdlineP->ramptype = RT_LR;
+    else if (tbSpec)
+        cmdlineP->ramptype = RT_TB;
+    else if (rectangleSpec)
+        cmdlineP->ramptype = RT_RECT;
+    else if (ellipseSpec)
+        cmdlineP->ramptype = RT_ELLIP;
+    else
+        pm_error("INTERNAL ERROR - no ramp type option found");
+
+    if (!maxvalSpec)
+        cmdlineP->maxval = PGM_MAXMAXVAL;
+    else {
+        if (cmdlineP->maxval > PGM_OVERALLMAXVAL)
+            pm_error("The value you specified for -maxval (%u) is too big.  "
+                     "Max allowed is %u", cmdlineP->maxval, PGM_OVERALLMAXVAL);
+        
+        if (cmdlineP->maxval < 1)
+            pm_error("You cannot specify 0 for -maxval");
+    }    
+
+    if (argc-1 < 2)
+        pm_error("Need two arguments: width and height.");
+    else if (argc-1 > 2)
+        pm_error("Only two arguments allowed: width and height.  "
+                 "You specified %d", argc-1);
+    else {
+        cmdlineP->cols = atoi(argv[1]);
+        cmdlineP->rows = atoi(argv[2]);
+        if (cmdlineP->cols <= 0)
+            pm_error("width argument must be a positive number.  You "
+                     "specified '%s'", argv[1]);
+        if (cmdlineP->rows <= 0)
+            pm_error("height argument must be a positive number.  You "
+                     "specified '%s'", argv[2]);
+    }
+}
+
+
+
+int 
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+    gray *grayrow;
+    int rowso2, colso2;
+    unsigned int row;
+
+    pgm_init( &argc, argv );
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    colso2 = MAX(1, cmdline.cols / 2);
+    rowso2 = MAX(1, cmdline.rows / 2);
+    
+    pgm_writepgminit(stdout, cmdline.cols, cmdline.rows, cmdline.maxval, 0);
+    grayrow = pgm_allocrow(cmdline.cols);
+    
+    for (row = 0; row < cmdline.rows; ++row) {
+        unsigned int col;
+        for (col = 0; col < cmdline.cols; ++col) {
+            switch (cmdline.ramptype) {
+            case RT_LR:
+                grayrow[col] = 
+                    col * cmdline.maxval / MAX(cmdline.cols-1, 1);
+                break;
+            case RT_TB:
+                grayrow[col] = 
+                    row * cmdline.maxval / MAX(cmdline.rows-1, 1);
+                break;
+
+            case RT_RECT: {
+                float const r = fabs((int)(rowso2 - row)) / rowso2;
+                float const c = fabs((int)(colso2 - col)) / colso2;
+                grayrow[col] = 
+                    cmdline.maxval - (r + c) / 2.0 * cmdline.maxval;
+            }
+            break;
+            
+            case RT_ELLIP: {
+                float const r = fabs((int)(rowso2 - row)) / rowso2;
+                float const c = fabs((int)(colso2 - col)) / colso2;
+                float v;
+
+                v = r * r + c * c;
+                if ( v < 0.0 ) v = 0.0;
+                else if ( v > 1.0 ) v = 1.0;
+                grayrow[col] = cmdline.maxval - v * cmdline.maxval;
+            }
+            break;
+            }
+	    }
+        pgm_writepgmrow(stdout, grayrow, cmdline.cols, cmdline.maxval, 0);
+	}
+
+    pgm_freerow(grayrow);
+    pm_close(stdout);
+    return 0;
+}
diff --git a/generator/ppmcie.c b/generator/ppmcie.c
new file mode 100644
index 00000000..fda0ab7c
--- /dev/null
+++ b/generator/ppmcie.c
@@ -0,0 +1,1201 @@
+/*
+
+      Generate a PPM file representing a CIE color gamut chart
+
+               by John Walker  --  kelvin@@fourmilab.ch
+               WWW home page: http://www.fourmilab.ch/
+
+    Permission  to  use, copy, modify, and distribute this software and
+    its documentation  for  any  purpose  and  without  fee  is  hereby
+    granted,  without any conditions or restrictions.  This software is
+    provided "as is" without express or implied warranty.
+
+    This program was called cietoppm in Walker's original work.  
+    Because "cie" is not a graphics format, Bryan changed the name
+    when he integrated it into the Netpbm package in March 2000.
+*/
+
+/*
+  Modified by
+  Andrew J. S. Hamilton 21 May 1999
+  Andrew.Hamilton@Colorado.EDU
+  http://casa.colorado.edu/~ajsh/
+
+  Corrected XYZ -> RGB transform.
+  Introduced gamma correction.
+  Introduced option to plot 1976 u' v' chromaticities.
+*/
+
+#include <math.h>
+
+#include "pm_c_util.h"
+#include "ppm.h"
+#include "ppmdraw.h"
+#include "nstring.h"
+
+#define CLAMP(v, l, h)  ((v) < (l) ? (l) : (v) > (h) ? (h) : (v))
+#define TRUE    1
+#define FALSE   0
+
+#define Maxval  255                   /* Maxval to use in generated pixmaps */
+
+/* A  color  system is defined by the CIE x and y  coordinates of its
+   three primary illuminants and the x and y coordinates of the  white
+   point. */
+
+struct colorSystem {
+    const char *name;                       /* Color system name */
+    double xRed, yRed,                /* Red primary illuminant */
+           xGreen, yGreen,            /* Green primary illuminant */
+           xBlue, yBlue,              /* Blue primary illuminant */
+           xWhite, yWhite,            /* White point */
+           gamma;             /* gamma of nonlinear correction */
+};
+
+/* The  following  table  gives  the  CIE  color  matching  functions
+   \bar{x}(\lambda),  \bar{y}(\lambda),  and   \bar{z}(\lambda),   for
+   wavelengths  \lambda  at 5 nanometre increments from 380 nm through
+   780 nm.  This table is used in conjunction with  Planck's  law  for
+   the  energy spectrum of a black body at a given temperature to plot
+   the black body curve on the CIE chart. */
+
+static double cie_color_match[][3] = {
+    { 0.0014, 0.0000, 0.0065 },       /* 380 nm */
+    { 0.0022, 0.0001, 0.0105 },
+    { 0.0042, 0.0001, 0.0201 },
+    { 0.0076, 0.0002, 0.0362 },
+    { 0.0143, 0.0004, 0.0679 },
+    { 0.0232, 0.0006, 0.1102 },
+    { 0.0435, 0.0012, 0.2074 },
+    { 0.0776, 0.0022, 0.3713 },
+    { 0.1344, 0.0040, 0.6456 },
+    { 0.2148, 0.0073, 1.0391 },
+    { 0.2839, 0.0116, 1.3856 },
+    { 0.3285, 0.0168, 1.6230 },
+    { 0.3483, 0.0230, 1.7471 },
+    { 0.3481, 0.0298, 1.7826 },
+    { 0.3362, 0.0380, 1.7721 },
+    { 0.3187, 0.0480, 1.7441 },
+    { 0.2908, 0.0600, 1.6692 },
+    { 0.2511, 0.0739, 1.5281 },
+    { 0.1954, 0.0910, 1.2876 },
+    { 0.1421, 0.1126, 1.0419 },
+    { 0.0956, 0.1390, 0.8130 },
+    { 0.0580, 0.1693, 0.6162 },
+    { 0.0320, 0.2080, 0.4652 },
+    { 0.0147, 0.2586, 0.3533 },
+    { 0.0049, 0.3230, 0.2720 },
+    { 0.0024, 0.4073, 0.2123 },
+    { 0.0093, 0.5030, 0.1582 },
+    { 0.0291, 0.6082, 0.1117 },
+    { 0.0633, 0.7100, 0.0782 },
+    { 0.1096, 0.7932, 0.0573 },
+    { 0.1655, 0.8620, 0.0422 },
+    { 0.2257, 0.9149, 0.0298 },
+    { 0.2904, 0.9540, 0.0203 },
+    { 0.3597, 0.9803, 0.0134 },
+    { 0.4334, 0.9950, 0.0087 },
+    { 0.5121, 1.0000, 0.0057 },
+    { 0.5945, 0.9950, 0.0039 },
+    { 0.6784, 0.9786, 0.0027 },
+    { 0.7621, 0.9520, 0.0021 },
+    { 0.8425, 0.9154, 0.0018 },
+    { 0.9163, 0.8700, 0.0017 },
+    { 0.9786, 0.8163, 0.0014 },
+    { 1.0263, 0.7570, 0.0011 },
+    { 1.0567, 0.6949, 0.0010 },
+    { 1.0622, 0.6310, 0.0008 },
+    { 1.0456, 0.5668, 0.0006 },
+    { 1.0026, 0.5030, 0.0003 },
+    { 0.9384, 0.4412, 0.0002 },
+    { 0.8544, 0.3810, 0.0002 },
+    { 0.7514, 0.3210, 0.0001 },
+    { 0.6424, 0.2650, 0.0000 },
+    { 0.5419, 0.2170, 0.0000 },
+    { 0.4479, 0.1750, 0.0000 },
+    { 0.3608, 0.1382, 0.0000 },
+    { 0.2835, 0.1070, 0.0000 },
+    { 0.2187, 0.0816, 0.0000 },
+    { 0.1649, 0.0610, 0.0000 },
+    { 0.1212, 0.0446, 0.0000 },
+    { 0.0874, 0.0320, 0.0000 },
+    { 0.0636, 0.0232, 0.0000 },
+    { 0.0468, 0.0170, 0.0000 },
+    { 0.0329, 0.0119, 0.0000 },
+    { 0.0227, 0.0082, 0.0000 },
+    { 0.0158, 0.0057, 0.0000 },
+    { 0.0114, 0.0041, 0.0000 },
+    { 0.0081, 0.0029, 0.0000 },
+    { 0.0058, 0.0021, 0.0000 },
+    { 0.0041, 0.0015, 0.0000 },
+    { 0.0029, 0.0010, 0.0000 },
+    { 0.0020, 0.0007, 0.0000 },
+    { 0.0014, 0.0005, 0.0000 },
+    { 0.0010, 0.0004, 0.0000 },
+    { 0.0007, 0.0002, 0.0000 },
+    { 0.0005, 0.0002, 0.0000 },
+    { 0.0003, 0.0001, 0.0000 },
+    { 0.0002, 0.0001, 0.0000 },
+    { 0.0002, 0.0001, 0.0000 },
+    { 0.0001, 0.0000, 0.0000 },
+    { 0.0001, 0.0000, 0.0000 },
+    { 0.0001, 0.0000, 0.0000 },
+    { 0.0000, 0.0000, 0.0000 }        /* 780 nm */
+};
+
+/* The following table gives the  spectral  chromaticity  co-ordinates
+   x(\lambda) and y(\lambda) for wavelengths in 5 nanometre increments
+   from 380 nm through  780  nm.   These  co-ordinates  represent  the
+   position in the CIE x-y space of pure spectral colors of the given
+   wavelength, and  thus  define  the  outline  of  the  CIE  "tongue"
+   diagram. */
+
+static double spectral_chromaticity[81][3] = {
+    { 0.1741, 0.0050 },               /* 380 nm */
+    { 0.1740, 0.0050 },
+    { 0.1738, 0.0049 },
+    { 0.1736, 0.0049 },
+    { 0.1733, 0.0048 },
+    { 0.1730, 0.0048 },
+    { 0.1726, 0.0048 },
+    { 0.1721, 0.0048 },
+    { 0.1714, 0.0051 },
+    { 0.1703, 0.0058 },
+    { 0.1689, 0.0069 },
+    { 0.1669, 0.0086 },
+    { 0.1644, 0.0109 },
+    { 0.1611, 0.0138 },
+    { 0.1566, 0.0177 },
+    { 0.1510, 0.0227 },
+    { 0.1440, 0.0297 },
+    { 0.1355, 0.0399 },
+    { 0.1241, 0.0578 },
+    { 0.1096, 0.0868 },
+    { 0.0913, 0.1327 },
+    { 0.0687, 0.2007 },
+    { 0.0454, 0.2950 },
+    { 0.0235, 0.4127 },
+    { 0.0082, 0.5384 },
+    { 0.0039, 0.6548 },
+    { 0.0139, 0.7502 },
+    { 0.0389, 0.8120 },
+    { 0.0743, 0.8338 },
+    { 0.1142, 0.8262 },
+    { 0.1547, 0.8059 },
+    { 0.1929, 0.7816 },
+    { 0.2296, 0.7543 },
+    { 0.2658, 0.7243 },
+    { 0.3016, 0.6923 },
+    { 0.3373, 0.6589 },
+    { 0.3731, 0.6245 },
+    { 0.4087, 0.5896 },
+    { 0.4441, 0.5547 },
+    { 0.4788, 0.5202 },
+    { 0.5125, 0.4866 },
+    { 0.5448, 0.4544 },
+    { 0.5752, 0.4242 },
+    { 0.6029, 0.3965 },
+    { 0.6270, 0.3725 },
+    { 0.6482, 0.3514 },
+    { 0.6658, 0.3340 },
+    { 0.6801, 0.3197 },
+    { 0.6915, 0.3083 },
+    { 0.7006, 0.2993 },
+    { 0.7079, 0.2920 },
+    { 0.7140, 0.2859 },
+    { 0.7190, 0.2809 },
+    { 0.7230, 0.2770 },
+    { 0.7260, 0.2740 },
+    { 0.7283, 0.2717 },
+    { 0.7300, 0.2700 },
+    { 0.7311, 0.2689 },
+    { 0.7320, 0.2680 },
+    { 0.7327, 0.2673 },
+    { 0.7334, 0.2666 },
+    { 0.7340, 0.2660 },
+    { 0.7344, 0.2656 },
+    { 0.7346, 0.2654 },
+    { 0.7347, 0.2653 },
+    { 0.7347, 0.2653 },
+    { 0.7347, 0.2653 },
+    { 0.7347, 0.2653 },
+    { 0.7347, 0.2653 },
+    { 0.7347, 0.2653 },
+    { 0.7347, 0.2653 },
+    { 0.7347, 0.2653 },
+    { 0.7347, 0.2653 },
+    { 0.7347, 0.2653 },
+    { 0.7347, 0.2653 },
+    { 0.7347, 0.2653 },
+    { 0.7347, 0.2653 },
+    { 0.7347, 0.2653 },
+    { 0.7347, 0.2653 },
+    { 0.7347, 0.2653 },
+    { 0.7347, 0.2653 }                /* 780 nm */
+};
+
+static pixel **pixels;                /* Pixel map */
+static int pixcols, pixrows;          /* Pixel map size */
+static int sxsize = 512, sysize = 512; /* X, Y size */
+
+/* Standard white point chromaticities. */
+
+#define IlluminantC     0.3101, 0.3162  /* For NTSC television */
+#define IlluminantD65   0.3127, 0.3291  /* For EBU and SMPTE */
+
+/* Gamma of nonlinear correction.
+   See Charles Poynton's ColorFAQ Item 45 and GammaFAQ Item 6 at
+   http://www.inforamp.net/~poynton/ColorFAQ.html
+   http://www.inforamp.net/~poynton/GammaFAQ.html
+*/
+
+#define GAMMA_REC709    0.      /* Rec. 709 */
+
+static struct colorSystem const
+    NTSCsystem = {
+        "NTSC",
+        0.67,  0.33,  0.21,  0.71,  0.14,  0.08,
+        IlluminantC,   GAMMA_REC709
+    },
+    EBUsystem = {
+        "EBU (PAL/SECAM)",
+        0.64,  0.33,  0.29,  0.60,  0.15,  0.06,
+        IlluminantD65, GAMMA_REC709
+    },
+    SMPTEsystem = {
+        "SMPTE",
+        0.630, 0.340, 0.310, 0.595, 0.155, 0.070,
+        IlluminantD65, GAMMA_REC709
+    },
+    HDTVsystem = {
+        "HDTV",
+        0.670, 0.330, 0.210, 0.710, 0.150, 0.060,
+        IlluminantD65,  GAMMA_REC709
+    },
+    /* Huh? -ajsh
+    CIEsystem = {
+        "CIE",
+        0.7355, 0.2645, 0.2658, 0.7243, 0.1669, 0.0085,
+        0.3894, 0.3324, GAMMA_REC709
+        },
+    */
+    CIEsystem = {
+        "CIE",
+        0.7355,0.2645,0.2658,0.7243,0.1669,0.0085, 0.33333333, 0.33333333,
+        GAMMA_REC709
+    },
+    Rec709system = {
+        "CIE REC 709",
+        0.64,  0.33,  0.30,  0.60,  0.15,  0.06,
+        IlluminantD65, GAMMA_REC709
+    };
+
+/* Customsystem  is a variable that is initialized to CIE Rec 709, but
+   we modify it with information specified by the user's options.
+*/
+static struct colorSystem Customsystem = {
+    "Custom",
+    0.64,  0.33,  0.30,  0.60,  0.15,  0.06,  
+    IlluminantD65, GAMMA_REC709
+};
+    
+
+
+static void
+upvp_to_xy(double   const up,
+           double   const vp,
+           double * const xc,
+           double * const yc) {
+/*----------------------------------------------------------------------------
+    Given 1976 coordinates u', v', determine 1931 chromaticities x, y
+-----------------------------------------------------------------------------*/
+    *xc = 9*up / (6*up - 16*vp + 12);
+    *yc = 4*vp / (6*up - 16*vp + 12);
+}
+
+
+
+static void
+xy_to_upvp(double   const xc,
+           double   const yc,
+           double * const up,
+           double * const vp) {
+/*----------------------------------------------------------------------------
+    Given 1931 chromaticities x, y, determine 1976 coordinates u', v'
+-----------------------------------------------------------------------------*/
+    *up = 4*xc / (- 2*xc + 12*yc + 3);
+    *vp = 9*yc / (- 2*xc + 12*yc + 3);
+}
+
+
+
+static void
+xyz_to_rgb(const struct colorSystem * const cs,
+           double                      const xc,
+           double                      const yc,
+           double                      const zc,
+           double *                    const r,
+           double *                    const g,
+           double *                    const b) {
+/*----------------------------------------------------------------------------
+    Given  an additive tricolor system CS, defined by the CIE x and y
+    chromaticities of its three primaries (z is derived  trivially  as
+    1-(x+y)),  and  a  desired chromaticity (XC, YC, ZC) in CIE space,
+    determine the contribution of each primary in a linear combination
+    which   sums  to  the  desired  chromaticity.   If  the  requested
+    chromaticity falls outside the  Maxwell  triangle  (color  gamut)
+    formed  by the three primaries, one of the r, g, or b weights will
+    be negative.  
+
+    Caller can use constrain_rgb() to desaturate an outside-gamut
+    color to the closest representation within the available
+    gamut. 
+-----------------------------------------------------------------------------*/
+    double xr, yr, zr, xg, yg, zg, xb, yb, zb;
+    double xw, yw, zw;
+    double rx, ry, rz, gx, gy, gz, bx, by, bz;
+    double rw, gw, bw;
+
+    xr = cs->xRed;    yr = cs->yRed;    zr = 1 - (xr + yr);
+    xg = cs->xGreen;  yg = cs->yGreen;  zg = 1 - (xg + yg);
+    xb = cs->xBlue;   yb = cs->yBlue;   zb = 1 - (xb + yb);
+
+    xw = cs->xWhite;  yw = cs->yWhite;  zw = 1 - (xw + yw);
+
+    /* xyz -> rgb matrix, before scaling to white. */
+    rx = yg*zb - yb*zg;  ry = xb*zg - xg*zb;  rz = xg*yb - xb*yg;
+    gx = yb*zr - yr*zb;  gy = xr*zb - xb*zr;  gz = xb*yr - xr*yb;
+    bx = yr*zg - yg*zr;  by = xg*zr - xr*zg;  bz = xr*yg - xg*yr;
+
+    /* White scaling factors.
+       Dividing by yw scales the white luminance to unity, as conventional. */
+    rw = (rx*xw + ry*yw + rz*zw) / yw;
+    gw = (gx*xw + gy*yw + gz*zw) / yw;
+    bw = (bx*xw + by*yw + bz*zw) / yw;
+
+    /* xyz -> rgb matrix, correctly scaled to white. */
+    rx = rx / rw;  ry = ry / rw;  rz = rz / rw;
+    gx = gx / gw;  gy = gy / gw;  gz = gz / gw;
+    bx = bx / bw;  by = by / bw;  bz = bz / bw;
+
+    /* rgb of the desired point */
+    *r = rx*xc + ry*yc + rz*zc;
+    *g = gx*xc + gy*yc + gz*zc;
+    *b = bx*xc + by*yc + bz*zc;
+}
+
+
+
+static int
+constrain_rgb(double * const r,
+              double * const g,
+              double * const b) {
+/*----------------------------------------------------------------------------
+    If  the  requested RGB shade contains a negative weight for one of
+    the primaries, it lies outside the color  gamut  accessible  from
+    the  given  triple  of  primaries.  Desaturate it by adding white,
+    equal quantities of R, G, and B, enough to make RGB all positive.
+-----------------------------------------------------------------------------*/
+    double w;
+
+    /* Amount of white needed is w = - min(0, *r, *g, *b) */
+    w = (0 < *r) ? 0 : *r;
+    w = (w < *g) ? w : *g;
+    w = (w < *b) ? w : *b;
+    w = - w;
+
+    /* Add just enough white to make r, g, b all positive. */
+    if (w > 0) {
+        *r += w;  *g += w; *b += w;
+
+        return 1;                     /* Color modified to fit RGB gamut */
+    }
+
+    return 0;                         /* Color within RGB gamut */
+}
+
+
+
+static void
+gamma_correct(const struct colorSystem * const cs,
+              double *                   const c) {
+/*----------------------------------------------------------------------------
+    Transform linear RGB values to nonlinear RGB values.
+
+    Rec. 709 is ITU-R Recommendation BT. 709 (1990)
+    ``Basic Parameter Values for the HDTV Standard for the Studio and for
+    International Programme Exchange'', formerly CCIR Rec. 709.
+
+    For details see
+       http://www.inforamp.net/~poynton/ColorFAQ.html
+       http://www.inforamp.net/~poynton/GammaFAQ.html
+-----------------------------------------------------------------------------*/
+    double gamma;
+
+    gamma = cs->gamma;
+
+    if (gamma == 0.) {
+    /* Rec. 709 gamma correction. */
+    double cc = 0.018;
+    if (*c < cc) {
+        *c *= (1.099 * pow(cc, 0.45) - 0.099) / cc;
+    } else {
+        *c = 1.099 * pow(*c, 0.45) - 0.099;
+    }
+    } else {
+    /* Nonlinear color = (Linear color)^(1/gamma) */
+    *c = pow(*c, 1./gamma);
+    }
+}
+
+
+
+static void
+gamma_correct_rgb(const struct colorSystem * const cs,
+                  double * const r,
+                  double * const g,
+                  double * const b) {
+    gamma_correct(cs, r);
+    gamma_correct(cs, g);
+    gamma_correct(cs, b);
+}
+
+
+
+#define Sz(x) (((x) * MIN(pixcols, pixrows)) / 512)
+#define B(x, y) ((x) + xBias), (y)
+#define Bixels(y, x) pixels[y][x + xBias]
+
+
+
+static void
+computeMonochromeColorLocation(
+    double                     const waveLength,
+    int                        const pxcols,
+    int                        const pxrows,
+    bool                       const upvp,
+    int *                      const xP,
+    int *                      const yP) {
+
+    int const ix = (waveLength - 380) / 5;
+    double const px = spectral_chromaticity[ix][0];
+    double const py = spectral_chromaticity[ix][1];
+
+    if (upvp) {
+        double up, vp;
+        xy_to_upvp(px, py, &up, &vp);
+        *xP = up * (pxcols - 1);
+        *yP = (pxrows - 1) - vp * (pxrows - 1);
+    } else {
+        *xP = px * (pxcols - 1);
+        *yP = (pxrows - 1) - py * (pxrows - 1);
+    }
+}
+
+
+
+static void
+makeAllBlack(pixel **     const pixels,
+             unsigned int const cols,
+             unsigned int const rows) {
+
+    unsigned int row;
+    for (row = 0; row < rows; ++row) {
+        unsigned int col;
+        for (col = 0; col < cols; ++col)
+            PPM_ASSIGN(pixels[row][col], 0, 0, 0);
+    }
+}
+
+
+
+static void
+drawTongueOutline(pixel ** const pixels,
+                  int    const pixcols,
+                  int    const pixrows,
+                  pixval const maxval,
+                  bool   const upvp,
+                  int    const xBias,
+                  int    const yBias) {
+
+    int const pxcols = pixcols - xBias;
+    int const pxrows = pixrows - yBias;
+
+    pixel rgbcolor;
+    int wavelength;
+    int lx, ly;
+    int fx, fy;
+
+    PPM_ASSIGN(rgbcolor, maxval, maxval, maxval);
+
+    for (wavelength = 380; wavelength <= 700; wavelength += 5) {
+        int icx, icy;
+
+        computeMonochromeColorLocation(wavelength, pxcols, pxrows, upvp,
+                                       &icx, &icy);
+        
+        if (wavelength > 380)
+            ppmd_line(pixels, pixcols, pixrows, Maxval,
+                      B(lx, ly), B(icx, icy),
+                      PPMD_NULLDRAWPROC, (char *) &rgbcolor);
+        else {
+            fx = icx;
+            fy = icy;
+        }
+        lx = icx;
+        ly = icy;
+    }
+    ppmd_line(pixels, pixcols, pixrows, maxval,
+              B(lx, ly), B(fx, fy),
+              PPMD_NULLDRAWPROC, (char *) &rgbcolor);
+}
+
+
+
+static void
+findTongue(pixel ** const pixels,
+           int      const pxcols,
+           int      const xBias,
+           int      const row,
+           bool *   const presentP,
+           int *    const leftEdgeP,
+           int *    const rightEdgeP) {
+/*----------------------------------------------------------------------------
+  Find out if there is any tongue on row 'row' of image 'pixels', and if
+  so, where.
+
+  We assume the image consists of all black except a white outline of the
+  tongue.
+-----------------------------------------------------------------------------*/
+    int i;
+
+    for (i = 0;
+         i < pxcols && PPM_GETR(Bixels(row, i)) == 0;
+         ++i);
+
+    if (i >= pxcols)
+        *presentP = FALSE;
+    else {
+        int j;
+        int const leftEdge = i;
+
+        *presentP = TRUE;
+        
+        for (j = pxcols - 1;
+             j >= leftEdge && PPM_GETR(Bixels(row, j)) == 0;
+             --j);
+
+        *rightEdgeP = j;
+        *leftEdgeP = leftEdge;
+    }
+}
+
+
+
+static void
+fillInTongue(pixel **                   const pixels,
+             int                        const pixcols,
+             int                        const pixrows,
+             pixval                     const maxval,
+             const struct colorSystem * const cs,
+             bool                       const upvp,
+             int                        const xBias,
+             int                        const yBias,
+             bool                       const highlightGamut) {
+
+    int const pxcols = pixcols - xBias;
+    int const pxrows = pixrows - yBias;
+
+    int y;
+
+    /* Scan the image line by line and  fill  the  tongue  outline
+       with the RGB values determined by the color system for the x-y
+       co-ordinates within the tongue.
+    */
+
+    for (y = 0; y < pxrows; ++y) {
+        bool present;  /* There is some tongue on this line */
+        int leftEdge; /* x position of leftmost pixel in tongue on this line */
+        int rightEdge; /* same, but rightmost */
+
+        findTongue(pixels, pxcols, xBias, y, &present, &leftEdge, &rightEdge);
+
+        if (present) {
+            int x;
+
+            for (x = leftEdge; x <= rightEdge; ++x) {
+                double cx, cy, cz, jr, jg, jb, jmax;
+                int r, g, b, mx;
+
+                if (upvp) {
+                    double up, vp;
+                    up = ((double) x) / (pxcols - 1);
+                    vp = 1.0 - ((double) y) / (pxrows - 1);
+                    upvp_to_xy(up, vp, &cx, &cy);
+                    cz = 1.0 - (cx + cy);
+                } else {
+                    cx = ((double) x) / (pxcols - 1);
+                    cy = 1.0 - ((double) y) / (pxrows - 1);
+                    cz = 1.0 - (cx + cy);
+                }
+
+                xyz_to_rgb(cs, cx, cy, cz, &jr, &jg, &jb);
+
+                mx = Maxval;
+        
+                /* Check whether the requested color  is  within  the
+                   gamut  achievable with the given color system.  If
+                   not, draw it in a reduced  intensity,  interpolated
+                   by desaturation to the closest within-gamut color. */
+        
+                if (constrain_rgb(&jr, &jg, &jb)) {
+                    mx = highlightGamut ? Maxval : ((Maxval + 1) * 3) / 4;
+                }
+                /* Scale to max(rgb) = 1. */
+                jmax = MAX(jr, MAX(jg, jb));
+                if (jmax > 0) {
+                    jr = jr / jmax;
+                    jg = jg / jmax;
+                    jb = jb / jmax;
+                }
+                /* gamma correct from linear rgb to nonlinear rgb. */
+                gamma_correct_rgb(cs, &jr, &jg, &jb);
+                r = mx * jr;
+                g = mx * jg;
+                b = mx * jb;
+                PPM_ASSIGN(Bixels(y, x), (pixval) r, (pixval) g, (pixval) b);
+            }
+        }
+    }
+}
+
+
+
+static void
+drawAxes(pixel ** const pixels,
+         int    const pixcols,
+         int    const pixrows,
+         pixval const maxval,
+         bool   const upvp,
+         int    const xBias,
+         int    const yBias) {
+
+    int const pxcols = pixcols - xBias;
+    int const pxrows = pixrows - yBias;
+
+    pixel rgbcolor;  /* Color of axes */
+    int i;
+
+    PPM_ASSIGN(rgbcolor, maxval, maxval, maxval);
+    ppmd_line(pixels, pixcols, pixrows, maxval,
+              B(0, 0), B(0, pxrows - 1), PPMD_NULLDRAWPROC,
+              (char *) &rgbcolor);
+    ppmd_line(pixels, pixcols, pixrows, maxval,
+              B(0, pxrows - 1), B(pxcols - 1, pxrows - 1),
+              PPMD_NULLDRAWPROC, (char *) &rgbcolor);
+    
+    /* Draw tick marks on X and Y axes every 0.1 units.  Also
+       label axes.
+    */
+    
+    for (i = 1; i <= 9; i += 1) {
+        char s[20];
+
+        /* X axis tick */
+
+        sprintf(s, "0.%d", i);
+        ppmd_line(pixels, pixcols, pixrows, maxval,
+                  B((i * (pxcols - 1)) / 10, pxrows - Sz(1)),
+                  B((i * (pxcols - 1)) / 10, pxrows - Sz(4)),
+                  PPMD_NULLDRAWPROC, (char *) &rgbcolor);
+        ppmd_text(pixels, pixcols, pixrows, maxval,
+                  B((i * (pxcols - 1)) / 10 - Sz(11), pxrows + Sz(12)),
+                  Sz(10), 0, s, PPMD_NULLDRAWPROC, (char *) &rgbcolor);
+
+        /* Y axis tick */
+
+        sprintf(s, "0.%d", 10 - i);
+        ppmd_line(pixels, pixcols, pixrows, maxval,
+                  B(0, (i * (pxrows - 1)) / 10),
+                  B(Sz(3), (i * (pxrows - 1)) / 10),
+                  PPMD_NULLDRAWPROC, (char *) &rgbcolor);
+
+        ppmd_text(pixels, pixcols, pixrows, maxval,
+                  B(Sz(-30), (i * (pxrows - 1)) / 10 + Sz(5)),
+                  Sz(10), 0, s,
+                  PPMD_NULLDRAWPROC, (char *) &rgbcolor);
+    }
+    ppmd_text(pixels, pixcols, pixrows, maxval,
+              B((98 * (pxcols - 1)) / 100 - Sz(11), pxrows + Sz(12)),
+              Sz(10), 0, (upvp ? "u'" : "x"),
+              PPMD_NULLDRAWPROC, (char *) &rgbcolor);
+    ppmd_text(pixels,  pixcols, pixrows, maxval,
+              B(Sz(-22), (2 * (pxrows - 1)) / 100 + Sz(5)),
+              Sz(10), 0, (upvp ? "v'" : "y"),
+              PPMD_NULLDRAWPROC, (char *) &rgbcolor);
+}
+
+
+
+static void
+plotWhitePoint(pixel **                   const pixels,
+               int                        const pixcols,
+               int                        const pixrows,
+               pixval                     const maxval,
+               const struct colorSystem * const cs,
+               bool                       const upvp,
+               int                        const xBias,
+               int                        const yBias) {
+
+    int const pxcols = pixcols - xBias;
+    int const pxrows = pixrows - yBias;
+
+    int wx, wy;
+
+    pixel rgbcolor;  /* Color of the white point mark */
+
+    PPM_ASSIGN(rgbcolor, 0, 0, 0);
+
+    if (upvp) {
+        double wup, wvp;
+        xy_to_upvp(cs->xWhite, cs->yWhite, &wup, &wvp);
+        wx = wup;
+        wy = wvp;
+        wx = (pxcols - 1) * wup;
+        wy = (pxrows - 1) - ((int) ((pxrows - 1) * wvp));
+    } else {
+        wx = (pxcols - 1) * cs->xWhite;
+        wy = (pxrows - 1) - ((int) ((pxrows - 1) * cs->yWhite));
+    }
+
+    PPM_ASSIGN(rgbcolor, 0, 0, 0);
+    /* We draw the four arms of the cross separately so as to
+       leave the pixel representing the precise white point
+       undisturbed.
+    */
+    ppmd_line(pixels, pixcols, pixrows, maxval,
+              B(wx + Sz(3), wy), B(wx + Sz(10), wy),
+              PPMD_NULLDRAWPROC, (char *) &rgbcolor);
+    ppmd_line(pixels, pixcols, pixrows, maxval,
+              B(wx - Sz(3), wy), B(wx - Sz(10), wy),
+              PPMD_NULLDRAWPROC, (char *) &rgbcolor);
+    ppmd_line(pixels, pixcols, pixrows, maxval,
+              B(wx, wy + Sz(3)), B(wx, wy + Sz(10)),
+              PPMD_NULLDRAWPROC, (char *) &rgbcolor);
+    ppmd_line(pixels, pixcols, pixrows, maxval,
+              B(wx, wy - Sz(3)), B(wx, wy - Sz(10)),
+              PPMD_NULLDRAWPROC, (char *) &rgbcolor);
+}
+
+
+
+static void
+plotBlackBodyCurve(pixel **                   const pixels,
+                   int                        const pixcols,
+                   int                        const pixrows,
+                   pixval                     const maxval,
+                   bool                       const upvp,
+                   int                        const xBias,
+                   int                        const yBias) {
+
+    int const pxcols = pixcols - xBias;
+    int const pxrows = pixrows - yBias;
+
+    double t;  /* temperature of black body */
+    pixel rgbcolor;  /* color of the curve */
+    int lx, ly;  /* Last point we've drawn on curve so far */
+
+    PPM_ASSIGN(rgbcolor, 0, 0, 0);
+
+    /* Plot black body curve from 1000 to 30000 kelvins. */
+
+    for (t = 1000.0; t < 30000.0; t += 50.0) {
+        double lambda, X = 0, Y = 0, Z = 0;
+        int xb, yb;
+        int ix;
+
+        /* Determine X, Y, and Z for blackbody by summing color
+           match functions over the visual range. */
+
+        for (ix = 0, lambda = 380; lambda <= 780.0; ix++, lambda += 5) {
+            double Me;
+
+            /* Evaluate Planck's black body equation for the
+               power at this wavelength. */
+
+            Me = 3.74183e-16 * pow(lambda * 1e-9, -5.0) /
+                (exp(1.4388e-2/(lambda * 1e-9 * t)) - 1.0);
+            X += Me * cie_color_match[ix][0];
+            Y += Me * cie_color_match[ix][1];
+            Z += Me * cie_color_match[ix][2];
+        }
+        if (upvp) {
+            double up, vp;
+            xy_to_upvp(X / (X + Y + Z), Y / (X + Y + Z), &up, &vp);
+            xb = (pxcols - 1) * up;
+            yb = (pxrows - 1) - ((pxrows - 1) * vp);
+        } else {
+            xb = (pxcols - 1) * X / (X + Y + Z);
+            yb = (pxrows - 1) - ((pxrows - 1) * Y / (X + Y + Z));
+        }
+
+        if (t > 1000) {
+            ppmd_line(pixels, pixcols, pixrows, Maxval,
+                      B(lx, ly), B(xb, yb), PPMD_NULLDRAWPROC,
+                      (char *) &rgbcolor);
+
+            /* Draw tick mark every 1000 kelvins */
+
+            if ((((int) t) % 1000) == 0) {
+                ppmd_line(pixels, pixcols, pixrows, Maxval,
+                          B(lx, ly - Sz(2)), B(lx, ly + Sz(2)),
+                          PPMD_NULLDRAWPROC, (char *) &rgbcolor);
+
+                /* Label selected tick marks with decreasing density. */
+
+                if (t <= 5000.1 || (t > 5000.0 && 
+                                    ((((int) t) % 5000) == 0) &&
+                                    t != 20000.0)) {
+                    char bb[20];
+
+                    sprintf(bb, "%g", t);
+                    ppmd_text(pixels, pixcols, pixrows, Maxval,
+                              B(lx - Sz(12), ly - Sz(4)), Sz(6), 0, bb,
+                              PPMD_NULLDRAWPROC, (char *) &rgbcolor);
+                }
+  
+            }
+        }
+        lx = xb;
+        ly = yb;
+    }
+}
+
+
+
+static bool
+overlappingLegend(bool const upvp,
+                  int  const waveLength) {
+
+    bool retval;
+
+    if (upvp)
+        retval = (waveLength == 430 || waveLength == 640);
+    else 
+        retval = (waveLength == 460 || waveLength == 630 || waveLength == 640);
+    return retval;
+}
+
+
+
+static void
+plotMonochromeWavelengths(
+    pixel **                   const pixels,
+    int                        const pixcols,
+    int                        const pixrows,
+    pixval                     const maxval,
+    const struct colorSystem * const cs,
+    bool                       const upvp,
+    int                        const xBias,
+    int                        const yBias) {
+
+    int const pxcols = pixcols - xBias;
+    int const pxrows = pixrows - yBias;
+
+    int x;  /* The wavelength we're plotting */
+
+    for (x = (upvp? 420 : 450);
+         x <= 650;
+         x += (upvp? 10 : (x > 470 && x < 600) ? 5 : 10)) {
+
+        pixel rgbcolor;
+
+        /* Ick.  Drop legends that overlap and twiddle position
+           so they appear at reasonable positions with respect to
+           the tongue.
+        */
+
+        if (!overlappingLegend(upvp, x)) {
+            double cx, cy, cz, jr, jg, jb, jmax;
+            char wl[20];
+            int bx = 0, by = 0, tx, ty, r, g, b;
+            int icx, icy;  /* Location of the color on the tongue */
+
+            if (x < 520) {
+                bx = Sz(-22);
+                by = Sz(2);
+            } else if (x < 535) {
+                bx = Sz(-8);
+                by = Sz(-6);
+            } else {
+                bx = Sz(4);
+            }
+
+            computeMonochromeColorLocation(x, pxcols, pxrows, upvp,
+                                           &icx, &icy);
+
+            /* Draw the tick mark */
+            PPM_ASSIGN(rgbcolor, maxval, maxval, maxval);
+            tx = icx + ((x < 520) ? Sz(-2) : ((x >= 535) ? Sz(2) : 0));
+            ty = icy + ((x < 520) ? 0 : ((x >= 535) ? Sz(-1) : Sz(-2))); 
+            ppmd_line(pixels, pixcols, pixrows, Maxval,
+                      B(icx, icy), B(tx, ty),
+                      PPMD_NULLDRAWPROC, (char *) &rgbcolor);
+
+            /* The following flailing about sets the drawing color to
+               the hue corresponding to the pure wavelength (constrained
+               to the display gamut). */
+
+            if (upvp) {
+                double up, vp;
+                up = ((double) icx) / (pxcols - 1);
+                vp = 1.0 - ((double) icy) / (pxrows - 1);
+                upvp_to_xy(up, vp, &cx, &cy);
+                cz = 1.0 - (cx + cy);
+            } else {
+                cx = ((double) icx) / (pxcols - 1);
+                cy = 1.0 - ((double) icy) / (pxrows - 1);
+                cz = 1.0 - (cx + cy);
+            }
+
+            xyz_to_rgb(cs, cx, cy, cz, &jr, &jg, &jb);
+            (void) constrain_rgb(&jr, &jg, &jb);
+
+            /* Scale to max(rgb) = 1 */
+            jmax = MAX(jr, MAX(jg, jb));
+            if (jmax > 0) {
+                jr = jr / jmax;
+                jg = jg / jmax;
+                jb = jb / jmax;
+            }
+            /* gamma correct from linear rgb to nonlinear rgb. */
+            gamma_correct_rgb(cs, &jr, &jg, &jb);
+            r = Maxval * jr;
+            g = Maxval * jg;
+            b = Maxval * jb;
+            PPM_ASSIGN(rgbcolor, (pixval) r, (pixval) g, (pixval) b);
+
+            sprintf(wl, "%d", x);
+            ppmd_text(pixels, pixcols, pixrows, Maxval,
+                      B(icx + bx, icy + by), Sz(6), 0, wl,
+                      PPMD_NULLDRAWPROC, (char *) &rgbcolor);
+        }
+    }
+}
+
+
+
+static void
+writeLabel(pixel **                   const pixels,
+           int                        const pixcols,
+           int                        const pixrows,
+           pixval                     const maxval,
+           const struct colorSystem * const cs) {
+
+    pixel rgbcolor;  /* color of text */
+    char sysdesc[256];
+
+    PPM_ASSIGN(rgbcolor, maxval, maxval, maxval);
+    
+    snprintfN(sysdesc, sizeof(sysdesc),
+              "System: %s\n"
+              "Primary illuminants (X, Y)\n"
+              "     Red:  %0.4f, %0.4f\n"
+              "     Green: %0.4f, %0.4f\n"
+              "     Blue:  %0.4f, %0.4f\n"
+              "White point (X, Y): %0.4f, %0.4f",
+              cs->name, cs->xRed, cs->yRed, cs->xGreen, cs->yGreen,
+              cs->xBlue, cs->yBlue, cs->xWhite, cs->yWhite);
+    sysdesc[sizeof(sysdesc)-1] = '\0';  /* for robustness */
+
+    ppmd_text(pixels, pixcols, pixrows, Maxval,
+              pixcols / 3, Sz(24), Sz(12), 0, sysdesc,
+              PPMD_NULLDRAWPROC, (char *) &rgbcolor);
+}
+
+
+
+int
+main(int argc,
+     char * argv[]) {
+
+    int argn;
+    const char * const usage = "[-[no]black] [-[no]wpoint] [-[no]label] [-no[axes]] [-full]\n\
+[-xy|-upvp] [-rec709|-ntsc|-ebu|-smpte|-hdtv|-cie]\n\
+[-red <x> <y>] [-green <x> <y>] [-blue <x> <y>]\n\
+[-white <x> <y>] [-gamma <g>]\n\
+[-size <s>] [-xsize|-width <x>] [-ysize|-height <y>]";
+    const struct colorSystem *cs;
+
+    int widspec = FALSE, hgtspec = FALSE;
+    int xBias, yBias;
+    int upvp = FALSE;             /* xy or u'v' color coordinates? */
+    int showWhite = TRUE;             /* Show white point ? */
+    int showBlack = TRUE;             /* Show black body curve ? */
+    int fullChart = FALSE;            /* Fill entire tongue ? */
+    int showLabel = TRUE;             /* Show labels ? */
+    int showAxes = TRUE;              /* Plot axes ? */
+
+    ppm_init(&argc, argv);
+    argn = 1;
+
+    cs = &Rec709system;  /* default */
+    while (argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0') {
+        if (pm_keymatch(argv[argn], "-xy", 2)) {
+            upvp = FALSE;
+        } else if (pm_keymatch(argv[argn], "-upvp", 1)) {
+            upvp = TRUE;
+        } else if (pm_keymatch(argv[argn], "-xsize", 1) ||
+                   pm_keymatch(argv[argn], "-width", 2)) {
+            if (widspec) {
+                pm_error("already specified a size/width/xsize");
+            }
+            argn++;
+            if ((argn == argc) || (sscanf(argv[argn], "%d", &sxsize) != 1))
+                pm_usage(usage);
+            widspec = TRUE;
+        } else if (pm_keymatch(argv[argn], "-ysize", 1) ||
+                   pm_keymatch(argv[argn], "-height", 2)) {
+            if (hgtspec) {
+                pm_error("already specified a size/height/ysize");
+            }
+            argn++;
+            if ((argn == argc) || (sscanf(argv[argn], "%d", &sysize) != 1))
+                pm_usage(usage);
+            hgtspec = TRUE;
+        } else if (pm_keymatch(argv[argn], "-size", 2)) {
+            if (hgtspec || widspec) {
+                pm_error("already specified a size/height/ysize");
+            }
+            argn++;
+            if ((argn == argc) || (sscanf(argv[argn], "%d", &sysize) != 1))
+                pm_usage(usage);
+            sxsize = sysize;
+            hgtspec = widspec = TRUE;
+        } else if (pm_keymatch(argv[argn], "-rec709", 1)) {
+            cs = &Rec709system;
+        } else if (pm_keymatch(argv[argn], "-ntsc", 1)) {
+            cs = &NTSCsystem;
+        } else if (pm_keymatch(argv[argn], "-ebu", 1)) {
+            cs = &EBUsystem;
+        } else if (pm_keymatch(argv[argn], "-smpte", 2)) {
+            cs = &SMPTEsystem;
+        } else if (pm_keymatch(argv[argn], "-hdtv", 2)) {
+            cs = &HDTVsystem;                 
+        } else if (pm_keymatch(argv[argn], "-cie", 1)) {
+            cs = &CIEsystem;                 
+        } else if (pm_keymatch(argv[argn], "-black", 3)) {
+            showBlack = TRUE;         /* Show black body curve */
+        } else if (pm_keymatch(argv[argn], "-wpoint", 2)) {
+            showWhite = TRUE;         /* Show white point of color system */
+        } else if (pm_keymatch(argv[argn], "-noblack", 3)) {
+            showBlack = FALSE;        /* Don't show black body curve */
+        } else if (pm_keymatch(argv[argn], "-nowpoint", 3)) {
+            showWhite = FALSE;        /* Don't show white point of system */
+        } else if (pm_keymatch(argv[argn], "-label", 1)) {
+            showLabel = TRUE;         /* Show labels. */
+        } else if (pm_keymatch(argv[argn], "-nolabel", 3)) {
+            showLabel = FALSE;        /* Don't show labels */
+        } else if (pm_keymatch(argv[argn], "-axes", 1)) {
+            showAxes = TRUE;          /* Show axes. */
+        } else if (pm_keymatch(argv[argn], "-noaxes", 3)) {
+            showAxes = FALSE;         /* Don't show axes */
+        } else if (pm_keymatch(argv[argn], "-full", 1)) {
+            fullChart = TRUE;         /* Fill whole tongue full-intensity */
+        } else if (pm_keymatch(argv[argn], "-gamma", 2)) {
+            cs = &Customsystem;
+            argn++;
+            if ((argn == argc) ||
+                (sscanf(argv[argn], "%lf", &Customsystem.gamma) != 1))
+                pm_usage(usage);
+        } else if (pm_keymatch(argv[argn], "-red", 1)) {
+            cs = &Customsystem;
+            argn++;
+            if ((argn == argc) ||
+                (sscanf(argv[argn], "%lf", &Customsystem.xRed) != 1))
+                pm_usage(usage);
+            argn++;
+            if ((argn == argc) ||
+                (sscanf(argv[argn], "%lf", &Customsystem.yRed) != 1))
+                pm_usage(usage);
+        } else if (pm_keymatch(argv[argn], "-green", 1)) {
+            cs = &Customsystem;
+            argn++;
+            if ((argn == argc) ||
+                (sscanf(argv[argn], "%lf", &Customsystem.xGreen) != 1))
+                pm_usage(usage);
+            argn++;
+            if ((argn == argc) ||
+                (sscanf(argv[argn], "%lf", &Customsystem.yGreen) != 1))
+                pm_usage(usage);
+        } else if (pm_keymatch(argv[argn], "-blue", 1)) {
+            cs = &Customsystem;
+            argn++;
+            if ((argn == argc) ||
+                (sscanf(argv[argn], "%lf", &Customsystem.xBlue) != 1))
+                pm_usage(usage);
+            argn++;
+            if ((argn == argc) ||
+                (sscanf(argv[argn], "%lf", &Customsystem.yBlue) != 1))
+                pm_usage(usage);
+        } else if (pm_keymatch(argv[argn], "-white", 1)) {
+            cs = &Customsystem;
+            argn++;
+            if ((argn == argc) ||
+                (sscanf(argv[argn], "%lf", &Customsystem.xWhite) != 1))
+                pm_usage(usage);
+            argn++;
+            if ((argn == argc) ||
+                (sscanf(argv[argn], "%lf", &Customsystem.yWhite) != 1))
+                pm_usage(usage);
+        } else {
+            pm_usage(usage);
+        }
+        argn++;
+    }
+
+    if (argn != argc) {               /* Extra bogus arguments ? */
+        pm_usage(usage);
+    }
+
+    pixcols = sxsize;
+    pixrows = sysize;
+
+    pixels = ppm_allocarray(pixcols, pixrows);
+
+    /* Partition into plot area and axes and establish subwindow. */
+
+    xBias = Sz(32);
+    yBias = Sz(20);
+
+    makeAllBlack(pixels, pixcols, pixrows);
+
+    drawTongueOutline(pixels, pixcols, pixrows, Maxval, upvp, xBias, yBias);
+
+    fillInTongue(pixels, pixcols, pixrows, Maxval, cs, upvp, xBias, yBias,
+                 fullChart);
+
+    if (showAxes)
+        drawAxes(pixels, pixcols, pixrows, Maxval, upvp, xBias, yBias);
+
+    if (showWhite)
+        plotWhitePoint(pixels, pixcols, pixrows, Maxval,
+                       cs, upvp, xBias, yBias);
+
+    if (showBlack)
+        plotBlackBodyCurve(pixels, pixcols, pixrows, Maxval,
+                           upvp, xBias, yBias);
+
+    /* Plot wavelengths around periphery of the tongue. */
+
+    if (showAxes)
+        plotMonochromeWavelengths(pixels, pixcols, pixrows, Maxval,
+                                  cs, upvp, xBias, yBias);
+
+    if (showLabel)
+        writeLabel(pixels, pixcols, pixrows, Maxval, cs);
+
+    ppm_writeppm(stdout, pixels, pixcols, pixrows, Maxval, FALSE);
+
+    return 0;
+}
diff --git a/generator/ppmcolors.c b/generator/ppmcolors.c
new file mode 100644
index 00000000..9112e1b8
--- /dev/null
+++ b/generator/ppmcolors.c
@@ -0,0 +1,87 @@
+/******************************************************************************
+                                ppmcolors
+*******************************************************************************
+  Generate a color map containing all the colors representable with a certain
+  maxval.
+  
+  THIS PROGRAM IS OBSOLETE.  USE PAMSEQ INSTEAD.  WE KEEP THIS AROUND
+  FOR BACKWARD COMPATIBILITY.
+
+******************************************************************************/
+
+#include "ppm.h"
+#include "shhopt.h"
+#include "nstring.h"
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    unsigned int maxval;   
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv, struct cmdlineInfo *cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optStruct3 opt;  /* set by OPTENT3 */
+    optEntry *option_def = malloc(100*sizeof(optEntry));
+    unsigned int option_def_index;
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0,   "maxval",       OPT_UINT,   
+            &cmdlineP->maxval,  NULL, 0);
+    
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We may have parms that are negative numbers */
+
+    /* defaults */
+    cmdlineP->maxval = 5;
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (cmdlineP->maxval < 1)
+        pm_error("-maxval must be at least 1");
+    else if (cmdlineP->maxval > PPM_OVERALLMAXVAL)
+        pm_error("-maxval too high.  The maximum allowable value is %u",
+                 PPM_OVERALLMAXVAL);
+
+    if (argc-1 > 0)
+        pm_error("This program takes no arguments.  You specified %d",
+                 argc-1);
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+    const char * cmd;   /* malloc'ed */
+    int rc;
+
+    ppm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    asprintfN(&cmd, "pamseq 3 %u -tupletype=RGB | pamtopnm", cmdline.maxval);
+
+    rc = system(cmd);
+
+    if (rc != 0) 
+        pm_error("pamseq|pamtopnm pipeline failed.  system() rc = %d", rc);
+
+    strfree(cmd);
+    exit(rc);
+}
+
+
+
+
diff --git a/generator/ppmforge.c b/generator/ppmforge.c
new file mode 100644
index 00000000..64b1ad79
--- /dev/null
+++ b/generator/ppmforge.c
@@ -0,0 +1,1182 @@
+/*
+
+        Fractal forgery generator for the PPM toolkit
+
+    Originally  designed  and  implemented  in December of 1989 by
+    John Walker as a stand-alone program for the  Sun  and  MS-DOS
+    under  Turbo C.  Adapted in September of 1991 for use with Jef
+        Poskanzer's raster toolkit.
+
+    References cited in the comments are:
+
+        Foley, J. D., and Van Dam, A., Fundamentals of Interactive
+        Computer  Graphics,  Reading,  Massachusetts:  Addison
+        Wesley, 1984.
+
+        Peitgen, H.-O., and Saupe, D. eds., The Science Of Fractal
+        Images, New York: Springer Verlag, 1988.
+
+        Press, W. H., Flannery, B. P., Teukolsky, S. A., Vetterling,
+        W. T., Numerical Recipes In C, New Rochelle: Cambridge
+        University Press, 1988.
+
+    Author:
+        John Walker
+        http://www.fourmilab.ch/
+
+    Permission  to  use, copy, modify, and distribute this software and
+    its documentation  for  any  purpose  and  without  fee  is  hereby
+    granted,  without any conditions or restrictions.  This software is
+    provided "as is" without express or implied warranty.
+
+*/
+
+#define _XOPEN_SOURCE   /* get M_PI in math.h */
+
+#include <math.h>
+#include <assert.h>
+
+#include "pm_c_util.h"
+#include "ppm.h"
+#include "mallocvar.h"
+
+#ifdef VMS
+static double const hugeVal = HUGE_VAL;
+#else
+static double const hugeVal = 1e50;
+#endif
+
+/* Definitions used to address real and imaginary parts in a two-dimensional
+   array of complex numbers as stored by fourn(). */
+
+#define Real(v, x, y)  v[1 + (((x) * meshsize) + (y)) * 2]
+#define Imag(v, x, y)  v[2 + (((x) * meshsize) + (y)) * 2]
+
+/* Co-ordinate indices within arrays. */
+
+typedef struct {
+    double x;
+    double y;
+    double z; 
+} vector;
+
+/* Definition for obtaining random numbers. */
+
+#define nrand 4               /* Gauss() sample count */
+#define Cast(low, high) ((low)+(((high)-(low)) * ((rand() & 0x7FFF) / arand)))
+
+/* prototypes */
+static void fourn ARGS((float data[], int nn[], int ndim, int isign));
+static void initgauss ARGS((unsigned int seed));
+static double gauss ARGS((void));
+static void spectralsynth ARGS((float **x, unsigned int n, double h));
+static void temprgb ARGS((double temp, double *r, double *g, double *b));
+static void etoile ARGS((pixel *pix));
+/*  Local variables  */
+
+static double arand, gaussadd, gaussfac; /* Gaussian random parameters */
+static double fracdim;            /* Fractal dimension */
+static double powscale;           /* Power law scaling exponent */
+static int meshsize = 256;        /* FFT mesh size */
+static unsigned int seedarg;        /* Seed specified by user */
+static bool seedspec = FALSE;      /* Did the user specify a seed ? */
+static bool clouds = FALSE;        /* Just generate clouds */
+static bool stars = FALSE;         /* Just generate stars */
+static int screenxsize = 256;         /* Screen X size */
+static int screenysize = 256;         /* Screen Y size */
+static double inclangle, hourangle;   /* Star position relative to planet */
+static bool inclspec = FALSE;      /* No inclination specified yet */
+static bool hourspec = FALSE;      /* No hour specified yet */
+static double icelevel;           /* Ice cap theshold */
+static double glaciers;           /* Glacier level */
+static int starfraction;          /* Star fraction */
+static int starcolor;            /* Star color saturation */
+
+/*  FOURN  --  Multi-dimensional fast Fourier transform
+
+    Called with arguments:
+
+       data       A  one-dimensional  array  of  floats  (NOTE!!!   NOT
+              DOUBLES!!), indexed from one (NOTE!!!   NOT  ZERO!!),
+              containing  pairs of numbers representing the complex
+              valued samples.  The Fourier transformed results  are
+              returned in the same array.
+
+       nn         An  array specifying the edge size in each dimension.
+              THIS ARRAY IS INDEXED FROM  ONE,  AND  ALL  THE  EDGE
+              SIZES MUST BE POWERS OF TWO!!!
+
+       ndim       Number of dimensions of FFT to perform.  Set to 2 for
+              two dimensional FFT.
+
+       isign      If 1, a Fourier transform is done; if -1 the  inverse
+              transformation is performed.
+
+        This  function  is essentially as given in Press et al., "Numerical
+        Recipes In C", Section 12.11, pp.  467-470.
+*/
+
+static void fourn(data, nn, ndim, isign)
+    float data[];
+    int nn[], ndim, isign;
+{
+    register int i1, i2, i3;
+    int i2rev, i3rev, ip1, ip2, ip3, ifp1, ifp2;
+    int ibit, idim, k1, k2, n, nprev, nrem, ntot;
+    float tempi, tempr;
+    double theta, wi, wpi, wpr, wr, wtemp;
+
+#define SWAP(a,b) tempr=(a); (a) = (b); (b) = tempr
+
+    ntot = 1;
+    for (idim = 1; idim <= ndim; idim++)
+        ntot *= nn[idim];
+    nprev = 1;
+    for (idim = ndim; idim >= 1; idim--) {
+        n = nn[idim];
+        nrem = ntot / (n * nprev);
+        ip1 = nprev << 1;
+        ip2 = ip1 * n;
+        ip3 = ip2 * nrem;
+        i2rev = 1;
+        for (i2 = 1; i2 <= ip2; i2 += ip1) {
+            if (i2 < i2rev) {
+                for (i1 = i2; i1 <= i2 + ip1 - 2; i1 += 2) {
+                    for (i3 = i1; i3 <= ip3; i3 += ip2) {
+                        i3rev = i2rev + i3 - i2;
+                        SWAP(data[i3], data[i3rev]);
+                        SWAP(data[i3 + 1], data[i3rev + 1]);
+                    }
+                }
+            }
+            ibit = ip2 >> 1;
+            while (ibit >= ip1 && i2rev > ibit) {
+                i2rev -= ibit;
+                ibit >>= 1;
+            }
+            i2rev += ibit;
+        }
+        ifp1 = ip1;
+        while (ifp1 < ip2) {
+            ifp2 = ifp1 << 1;
+            theta = isign * (M_PI * 2) / (ifp2 / ip1);
+            wtemp = sin(0.5 * theta);
+            wpr = -2.0 * wtemp * wtemp;
+            wpi = sin(theta);
+            wr = 1.0;
+            wi = 0.0;
+            for (i3 = 1; i3 <= ifp1; i3 += ip1) {
+                for (i1 = i3; i1 <= i3 + ip1 - 2; i1 += 2) {
+                    for (i2 = i1; i2 <= ip3; i2 += ifp2) {
+                        k1 = i2;
+                        k2 = k1 + ifp1;
+                        tempr = wr * data[k2] - wi * data[k2 + 1];
+                        tempi = wr * data[k2 + 1] + wi * data[k2];
+                        data[k2] = data[k1] - tempr;
+                        data[k2 + 1] = data[k1 + 1] - tempi;
+                        data[k1] += tempr;
+                        data[k1 + 1] += tempi;
+                    }
+                }
+                wr = (wtemp = wr) * wpr - wi * wpi + wr;
+                wi = wi * wpr + wtemp * wpi + wi;
+            }
+            ifp1 = ifp2;
+        }
+        nprev *= n;
+    }
+}
+#undef SWAP
+
+/*  INITGAUSS  --  Initialize random number generators.  As given in
+           Peitgen & Saupe, page 77. */
+
+static void initgauss(seed)
+    unsigned int seed;
+{
+    /* Range of random generator */
+    arand = pow(2.0, 15.0) - 1.0;
+    gaussadd = sqrt(3.0 * nrand);
+    gaussfac = 2 * gaussadd / (nrand * arand);
+    srand(seed);
+}
+
+/*  GAUSS  --  Return a Gaussian random number.  As given in Peitgen
+           & Saupe, page 77. */
+
+static double gauss()
+{
+    int i;
+    double sum = 0.0;
+
+    for (i = 1; i <= nrand; i++) {
+        sum += (rand() & 0x7FFF);
+    }
+    return gaussfac * sum - gaussadd;
+}
+
+/*  SPECTRALSYNTH  --  Spectrally  synthesized  fractal  motion in two
+               dimensions.  This algorithm is given under  the
+               name   SpectralSynthesisFM2D  on  page  108  of
+               Peitgen & Saupe. */
+
+static void spectralsynth(x, n, h)
+    float **x;
+    unsigned int n;
+    double h;
+{
+    unsigned bl;
+    int i, j, i0, j0, nsize[3];
+    double rad, phase, rcos, rsin;
+    float *a;
+
+    bl = ((((unsigned long) n) * n) + 1) * 2 * sizeof(float);
+    a = (float *) calloc(bl, 1);
+    if (a == (float *) 0) {
+        pm_error("Cannot allocate %d x %d result array (% d bytes).",
+                 n, n, bl);
+    }
+    *x = a;
+
+    for (i = 0; i <= n / 2; i++) {
+        for (j = 0; j <= n / 2; j++) {
+            phase = 2 * M_PI * ((rand() & 0x7FFF) / arand);
+            if (i != 0 || j != 0) {
+                rad = pow((double) (i * i + j * j), -(h + 1) / 2) * gauss();
+            } else {
+                rad = 0;
+            }
+            rcos = rad * cos(phase);
+            rsin = rad * sin(phase);
+            Real(a, i, j) = rcos;
+            Imag(a, i, j) = rsin;
+            i0 = (i == 0) ? 0 : n - i;
+            j0 = (j == 0) ? 0 : n - j;
+            Real(a, i0, j0) = rcos;
+            Imag(a, i0, j0) = - rsin;
+        }
+    }
+    Imag(a, n / 2, 0) = 0;
+    Imag(a, 0, n / 2) = 0;
+    Imag(a, n / 2, n / 2) = 0;
+    for (i = 1; i <= n / 2 - 1; i++) {
+        for (j = 1; j <= n / 2 - 1; j++) {
+            phase = 2 * M_PI * ((rand() & 0x7FFF) / arand);
+            rad = pow((double) (i * i + j * j), -(h + 1) / 2) * gauss();
+            rcos = rad * cos(phase);
+            rsin = rad * sin(phase);
+            Real(a, i, n - j) = rcos;
+            Imag(a, i, n - j) = rsin;
+            Real(a, n - i, j) = rcos;
+            Imag(a, n - i, j) = - rsin;
+        }
+    }
+
+    nsize[0] = 0;
+    nsize[1] = nsize[2] = n;          /* Dimension of frequency domain array */
+    fourn(a, nsize, 2, -1);       /* Take inverse 2D Fourier transform */
+}
+
+
+static unsigned int
+initseed(void) {
+    /*  Generate initial random seed.  */
+
+    int i;
+
+    i = time(NULL) ^ 0xF37C;
+    srand(i);
+    for (i = 0; i < 7; ++i)
+        rand();
+    return rand();
+}
+
+
+
+/*  TEMPRGB  --  Calculate the relative R, G, and B components  for  a
+         black  body  emitting  light  at a given temperature.
+         The Planck radiation equation is solved directly  for
+         the R, G, and B wavelengths defined for the CIE  1931
+         Standard    Colorimetric    Observer.    The   color
+         temperature is specified in degrees Kelvin. */
+
+static void temprgb(temp, r, g, b)
+    double temp;
+    double *r, *g, *b;
+{
+    double c1 = 3.7403e10,
+        c2 = 14384.0,
+        er, eg, eb, es;
+
+/* Lambda is the wavelength in microns: 5500 angstroms is 0.55 microns. */
+
+#define Planck(lambda)  ((c1 * pow((double) lambda, -5.0)) /  \
+                         (pow(M_E, c2 / (lambda * temp)) - 1))
+
+        er = Planck(0.7000);
+        eg = Planck(0.5461);
+        eb = Planck(0.4358);
+#undef Planck
+
+        es = 1.0 / MAX(er, MAX(eg, eb));
+
+        *r = er * es;
+        *g = eg * es;
+        *b = eb * es;
+}
+
+/*  ETOILE  --  Set a pixel in the starry sky.  */
+
+static void etoile(pix)
+    pixel *pix;
+{
+    if ((rand() % 1000) < starfraction) {
+#define StarQuality 0.5       /* Brightness distribution exponent */
+#define StarIntensity   8         /* Brightness scale factor */
+#define StarTintExp 0.5       /* Tint distribution exponent */
+        double v = StarIntensity * pow(1 / (1 - Cast(0, 0.9999)),
+                                       (double) StarQuality),
+            temp,
+            r, g, b;
+
+        if (v > 255) {
+            v = 255;
+        }
+
+        /* We make a special case for star color  of zero in order to
+           prevent  floating  point  roundoff  which  would  otherwise
+           result  in  more  than  256 star colors.  We can guarantee
+           that if you specify no star color, you never get more than
+           256 shades in the image. */
+
+        if (starcolor == 0) {
+            int vi = v;
+
+            PPM_ASSIGN(*pix, vi, vi, vi);
+        } else {
+            temp = 5500 + starcolor *
+                pow(1 / (1 - Cast(0, 0.9999)), StarTintExp) *
+                ((rand() & 7) ? -1 : 1);
+            /* Constrain temperature to a reasonable value: >= 2600K 
+               (S Cephei/R Andromedae), <= 28,000 (Spica). */
+            temp = MAX(2600, MIN(28000, temp));
+            temprgb(temp, &r, &g, &b);
+            PPM_ASSIGN(*pix, (int) (r * v + 0.499),
+                       (int) (g * v + 0.499),
+                       (int) (b * v + 0.499));
+        }
+    } else {
+        PPM_ASSIGN(*pix, 0, 0, 0);
+    }
+}
+
+
+
+static double
+uprj(unsigned int const a,
+     unsigned int const size) {
+
+    return (double)a/(size-1);
+}
+
+
+
+static double
+atSat(double const x,
+      double const y,
+      double const dsat) {
+
+    return x*(1.0-dsat) + y*dsat;
+}
+
+
+
+static unsigned char *
+makeCp(float *      const a,
+       unsigned int const n,
+       pixval       const maxval) {
+
+    /* Prescale the grid points into intensities. */
+
+    unsigned char * cp;
+    unsigned char * ap;
+
+    if (UINT_MAX / n < n)
+        pm_error("arithmetic overflow squaring %u", n);
+    cp = malloc(n * n);
+    if (cp == NULL)
+        pm_error("Unable to allocate %u bytes for cp array", n);
+
+    ap = cp;
+    {
+        unsigned int i;
+        for (i = 0; i < n; i++) {
+            unsigned int j;
+            for (j = 0; j < n; j++)
+                *ap++ = ((double)maxval * (Real(a, i, j) + 1.0)) / 2.0;
+        }
+    }
+    return cp;
+}
+
+
+
+static void
+createPlanetStuff(float *          const a,
+                  unsigned int     const n,
+                  double **        const uP,
+                  double **        const u1P,
+                  unsigned int **  const bxfP,
+                  unsigned int **  const bxcP,
+                  unsigned char ** const cpP,
+                  vector *         const sunvecP,
+                  unsigned int     const cols,
+                  pixval           const maxval) {
+
+    double *u, *u1;
+    unsigned int *bxf, *bxc;
+    unsigned char * cp;
+    double shang, siang;
+    bool flipped;
+
+    /* Compute incident light direction vector. */
+
+    shang = hourspec ? hourangle : Cast(0, 2 * M_PI);
+    siang = inclspec ? inclangle : Cast(-M_PI * 0.12, M_PI * 0.12);
+
+    sunvecP->x = sin(shang) * cos(siang);
+    sunvecP->y = sin(siang);
+    sunvecP->z = cos(shang) * cos(siang);  /* initial value */
+
+    /* Allow only 25% of random pictures to be crescents */
+
+    if (!hourspec && ((rand() % 100) < 75)) {
+        flipped = (sunvecP->z < 0);
+        sunvecP->z = fabs(sunvecP->z);
+    } else
+        flipped = FALSE;
+
+    if (!clouds) {
+        pm_message(
+            "        -inclination %.0f -hour %d -ice %.2f -glaciers %.2f",
+            (siang * (180.0 / M_PI)),
+            (int) (((shang * (12.0 / M_PI)) + 12 +
+                    (flipped ? 12 : 0)) + 0.5) % 24, icelevel, glaciers);
+        pm_message("        -stars %d -saturation %d.",
+                   starfraction, starcolor);
+    }
+
+    cp = makeCp(a, n, maxval);
+
+    /* Fill the screen from the computed  intensity  grid  by  mapping
+       screen  points onto the grid, then calculating each pixel value
+       by bilinear interpolation from  the  surrounding  grid  points.
+       (N.b. the pictures would undoubtedly look better when generated
+       with small grids if  a  more  well-behaved  interpolation  were
+       used.)
+       
+       Also compute the line-level interpolation parameters that
+       caller will need every time around his inner loop.  
+    */
+
+    MALLOCARRAY(u,   cols);
+    MALLOCARRAY(u1,  cols);
+    MALLOCARRAY(bxf, cols);
+    MALLOCARRAY(bxc, cols);
+    
+    if (u == NULL || u1 == NULL || bxf == NULL || bxc == NULL) 
+        pm_error("Cannot allocate %d element interpolation tables.", cols);
+    {
+        unsigned int j;
+        for (j = 0; j < cols; j++) {
+            double const bx = (n - 1) * uprj(j, cols);
+            
+            bxf[j] = floor(bx);
+            bxc[j] = bxf[j] + 1;
+            u[j] = bx - bxf[j];
+            u1[j] = 1 - u[j];
+        }
+    }
+    *uP   = u;  *u1P  = u1;
+    *bxfP = bxf; *bxcP = bxc;
+    *cpP  = cp;
+}
+
+
+
+static void
+generateStarrySkyRow(pixel *      const pixels, 
+                     unsigned int const cols) {
+/*----------------------------------------------------------------------------
+  Generate a starry sky.  Note  that no FFT is performed;
+  the output is  generated  directly  from  a  power  law
+  mapping  of  a  pseudorandom sequence into intensities. 
+-----------------------------------------------------------------------------*/
+    unsigned int j;
+    
+    for (j = 0; j < cols; j++)
+        etoile(pixels + j);
+}
+
+
+
+static void
+generateCloudRow(pixel *         const pixels,
+                 unsigned int    const cols,
+                 double          const t,
+                 double          const t1,
+                 double *        const u,
+                 double *        const u1,
+                 unsigned char * const cp,
+                 unsigned int *  const bxc,
+                 unsigned int *  const bxf,
+                 int             const byc,
+                 int             const byf,
+                 pixval          const maxval) {
+
+    /* Render the FFT output as clouds. */
+
+    unsigned int j;
+
+    for (j = 0; j < cols; j++) {
+        double r;
+        pixval w;
+        
+        r = 0.0;  /* initial value */
+        /* Note that where t1 and t are zero, the cp[] element
+           referenced below does not exist.
+        */
+        if (t1 > 0.0)
+            r += t1 * u1[j] * cp[byf + bxf[j]] +
+                t1 * u[j]  * cp[byf + bxc[j]];
+        if (t > 0.0)
+            r += t * u1[j] * cp[byc + bxf[j]] +
+                t * u[j]  * cp[byc + bxc[j]];
+        
+        w = (r > 127.0) ? (maxval * ((r - 127.0) / 128.0)) : 0;
+        
+        PPM_ASSIGN(*(pixels + j), w, w, maxval);
+    }
+}
+
+
+
+static void
+makeLand(int *  const irP,
+         int *  const igP,
+         int *  const ibP,
+         double const r) {
+/*----------------------------------------------------------------------------
+  Land area.  Look up color based on elevation from precomputed
+  color map table.
+-----------------------------------------------------------------------------*/
+    static unsigned char pgnd[][3] = {
+        {206, 205, 0}, {208, 207, 0}, {211, 208, 0},
+        {214, 208, 0}, {217, 208, 0}, {220, 208, 0},
+        {222, 207, 0}, {225, 205, 0}, {227, 204, 0},
+        {229, 202, 0}, {231, 199, 0}, {232, 197, 0},
+        {233, 194, 0}, {234, 191, 0}, {234, 188, 0},
+        {233, 185, 0}, {232, 183, 0}, {231, 180, 0},
+        {229, 178, 0}, {227, 176, 0}, {225, 174, 0},
+        {223, 172, 0}, {221, 170, 0}, {219, 168, 0},
+        {216, 166, 0}, {214, 164, 0}, {212, 162, 0},
+        {210, 161, 0}, {207, 159, 0}, {205, 157, 0},
+        {203, 156, 0}, {200, 154, 0}, {198, 152, 0},
+        {195, 151, 0}, {193, 149, 0}, {190, 148, 0},
+        {188, 147, 0}, {185, 145, 0}, {183, 144, 0},
+        {180, 143, 0}, {177, 141, 0}, {175, 140, 0},
+        {172, 139, 0}, {169, 138, 0}, {167, 137, 0},
+        {164, 136, 0}, {161, 135, 0}, {158, 134, 0},
+        {156, 133, 0}, {153, 132, 0}, {150, 132, 0},
+        {147, 131, 0}, {145, 130, 0}, {142, 130, 0},
+        {139, 129, 0}, {136, 128, 0}, {133, 128, 0},
+        {130, 127, 0}, {127, 127, 0}, {125, 127, 0},
+        {122, 127, 0}, {119, 127, 0}, {116, 127, 0},
+        {113, 127, 0}, {110, 128, 0}, {107, 128, 0},
+        {104, 128, 0}, {102, 127, 0}, { 99, 126, 0},
+        { 97, 124, 0}, { 95, 122, 0}, { 93, 120, 0},
+        { 92, 117, 0}, { 92, 114, 0}, { 92, 111, 0},
+        { 93, 108, 0}, { 94, 106, 0}, { 96, 104, 0},
+        { 98, 102, 0}, {100, 100, 0}, {103,  99, 0},
+        {106,  99, 0}, {109,  99, 0}, {111, 100, 0},
+        {114, 101, 0}, {117, 102, 0}, {120, 103, 0},
+        {123, 102, 0}, {125, 102, 0}, {128, 100, 0},
+        {130,  98, 0}, {132,  96, 0}, {133,  94, 0},
+        {134,  91, 0}, {134,  88, 0}, {134,  85, 0},
+        {133,  82, 0}, {131,  80, 0}, {129,  78, 0}
+    };
+
+    unsigned int const ix = ((r - 128) * (ARRAY_SIZE(pgnd) - 1)) / 127;
+    
+    *irP = pgnd[ix][0];
+    *igP = pgnd[ix][1];
+    *ibP = pgnd[ix][2];
+} 
+
+
+
+static void
+makeWater(int *  const irP,
+          int *  const igP,
+          int *  const ibP,
+          double const r,
+          pixval const maxval) {
+
+    /* Water.  Generate clouds above water based on elevation.  */
+
+    *irP = *igP = r > 64 ? (r - 64) * 4 : 0;
+    *ibP = maxval;
+}
+
+
+
+static void
+addIce(int *  const irP,
+       int *  const igP,
+       int *  const ibP,
+       double const r,
+       double const azimuth,
+       double const icelevel,
+       double const glaciers,
+       pixval const maxval) {
+
+    /* Generate polar ice caps. */
+    
+    double const icet = pow(fabs(sin(azimuth)), 1.0 / icelevel) - 0.5;
+    double const ice = MAX(0.0, 
+                           (icet + glaciers * MAX(-0.5, (r - 128) / 128.0)));
+    if  (ice > 0.125) {
+        *irP = maxval;
+        *igP = maxval;
+        *ibP = maxval;
+    }
+}
+
+
+
+static void
+limbDarken(int *          const irP,
+           int *          const igP,
+           int *          const ibP,
+           unsigned int   const col,
+           unsigned int   const row,
+           unsigned int   const cols,
+           unsigned int   const rows,
+           vector         const sunvec,
+           pixval         const maxval) {
+
+    /* With Gcc 2.95.3 compiler optimization level > 1, I have seen this
+       function confuse all the variables and ultimately generate a 
+       completely black image.  Adding an extra reference to 'rows' seems
+       to put things back in order, and the assert() below does that.
+       Take it out, and the problem comes back!  04.02.21.
+    */
+
+    /* Apply limb darkening by cosine rule. */
+
+    double const atthick  = 1.03;
+    double const atSatFac = 1.0;
+    double const athfac   = sqrt(atthick * atthick - 1.0);
+        /* Atmosphere thickness as a percentage of planet's diameter */
+
+    double const dy = 2 * ((double)rows/2 - row) / rows;
+    double const dysq = dy * dy;
+    /* Note: we are in fact normalizing this horizontal position by the
+       vertical size of the picture.  And we know rows >= cols.
+    */
+    double const dx   = 2 * ((double)cols/2 - col) / rows;
+    double const dxsq = dx * dx;
+
+    double const ds = MIN(1.0, sqrt(dxsq + dysq));
+    
+    /* Calculate atmospheric absorption based on the thickness of
+       atmosphere traversed by light on its way to the surface.  
+    */
+    double const dsq = ds * ds;
+    double const dsat = atSatFac * ((sqrt(atthick * atthick - dsq) -
+                                     sqrt(1.0 * 1.0 - dsq)) / athfac);
+
+    assert(rows >= cols);  /* An input requirement */
+
+    *irP = atSat(*irP, maxval/2, dsat);
+    *igP = atSat(*igP, maxval/2, dsat);
+    *ibP = atSat(*ibP, maxval,   dsat);
+    {
+        double const PlanetAmbient = 0.05;
+
+        double const sqomdysq = sqrt(1.0 - dysq);
+        double const svx = sunvec.x;
+        double const svy = sunvec.y * dy;
+        double const svz = sunvec.z * sqomdysq;
+        double const di = 
+            MAX(0, MIN(1.0, svx * dx + svy + svz * sqrt(1.0 - dxsq)));
+        double const inx = PlanetAmbient * 1.0 + (1.0 - PlanetAmbient) * di;
+
+        *irP *= inx;
+        *igP *= inx;
+        *ibP *= inx;
+    }
+}
+
+
+
+static void
+generatePlanetRow(pixel *         const pixelrow,
+                  unsigned int    const row,
+                  unsigned int    const rows,
+                  unsigned int    const cols,
+                  double          const t,
+                  double          const t1,
+                  double *        const u,
+                  double *        const u1,
+                  unsigned char * const cp,
+                  unsigned int *  const bxc,
+                  unsigned int *  const bxf,
+                  int             const byc,
+                  int             const byf,
+                  vector          const sunvec,
+                  pixval          const maxval) {
+
+    unsigned int const StarClose = 2;
+
+    double const azimuth    = asin(((((double) row) / (rows - 1)) * 2) - 1);
+    unsigned int const lcos = (rows / 2) * fabs(cos(azimuth));
+
+    unsigned int col;
+
+    for (col = 0; col < cols; ++col)
+        PPM_ASSIGN(pixelrow[col], 0, 0, 0);
+
+    for (col = cols/2 - lcos; col <= cols/2 + lcos; ++col) {
+        double r;
+        int ir, ig, ib;
+
+        r = 0.0;   /* initial value */
+        
+        /* Note that where t1 and t are zero, the cp[] element
+           referenced below does not exist.  
+        */
+        if (t1 > 0.0)
+            r += t1 * u1[col] * cp[byf + bxf[col]] +
+                t1 * u[col]  * cp[byf + bxc[col]];
+        if (t > 0.0)
+            r += t * u1[col] * cp[byc + bxf[col]] +
+                t * u[col]  * cp[byc + bxc[col]];
+
+        if (r >= 128) 
+            makeLand(&ir, &ig, &ib, r);
+        else 
+            makeWater(&ir, &ig, &ib, r, maxval);
+
+        addIce(&ir, &ig, &ib, r, azimuth, icelevel, glaciers, maxval);
+
+        limbDarken(&ir, &ig, &ib, col, row, cols, rows, sunvec, maxval);
+
+        PPM_ASSIGN(pixelrow[col], ir, ig, ib);
+    }
+
+    /* Left stars */
+
+    for (col = 0; (int)col < (int)(cols/2 - (lcos + StarClose)); ++col)
+        etoile(&pixelrow[col]);
+
+    /* Right stars */
+
+    for (col = cols/2 + (lcos + StarClose); col < cols; ++col)
+        etoile(&pixelrow[col]);
+}
+
+
+
+static void 
+genplanet(bool         const stars,
+          bool         const clouds,
+          float *      const a, 
+          unsigned int const cols,
+          unsigned int const rows,
+          unsigned int const n,
+          unsigned int const rseed) {
+/*----------------------------------------------------------------------------
+  Generate planet from elevation array.
+
+  If 'stars' is true, a is undefined.  Otherwise, it is defined.
+-----------------------------------------------------------------------------*/
+    pixval const maxval = PPM_MAXMAXVAL;
+
+    unsigned char *cp;
+    double *u, *u1;
+    unsigned int *bxf, *bxc;
+
+    pixel *pixelrow;
+    unsigned int row;
+
+    vector sunvec;
+
+    ppm_writeppminit(stdout, cols, rows, maxval, FALSE);
+
+    if (stars) {
+        pm_message("night: -seed %d -stars %d -saturation %d.",
+                   rseed, starfraction, starcolor);
+        cp = NULL; 
+        u = NULL; u1 = NULL;
+        bxf = NULL; bxc = NULL;
+    } else {
+        pm_message("%s: -seed %d -dimension %.2f -power %.2f -mesh %d",
+                   clouds ? "clouds" : "planet",
+                   rseed, fracdim, powscale, meshsize);
+        createPlanetStuff(a, n, &u, &u1, &bxf, &bxc, &cp, &sunvec, 
+                          cols, maxval);
+    }
+
+    pixelrow = ppm_allocrow(cols);
+    for (row = 0; row < rows; ++row) {
+        if (stars)
+            generateStarrySkyRow(pixelrow, cols);
+        else {
+            double const by = (n - 1) * uprj(row, rows);
+            int    const byf = floor(by) * n;
+            int    const byc = byf + n;
+            double const t = by - floor(by);
+            double const t1 = 1 - t;
+
+            if (clouds)
+                generateCloudRow(pixelrow, cols,
+                                 t, t1, u, u1, cp, bxc, bxf, byc, byf,
+                                 maxval);
+            else 
+                generatePlanetRow(pixelrow, row, rows, cols,
+                                  t, t1, u, u1, cp, bxc, bxf, byc, byf,
+                                  sunvec,
+                                  maxval);
+        }
+        ppm_writeppmrow(stdout, pixelrow, cols, maxval, FALSE);
+    }
+    pm_close(stdout);
+
+    ppm_freerow(pixelrow);
+    if (cp)  free(cp);
+    if (u)   free(u);
+    if (u1)  free(u1);
+    if (bxf) free(bxf);
+    if (bxc) free(bxc);
+}
+
+
+
+static void
+applyPowerLawScaling(float * const a,
+                     int     const meshsize,
+                     double  const powscale) {
+
+    /* Apply power law scaling if non-unity scale is requested. */
+    
+    if (powscale != 1.0) {
+        unsigned int i;
+        for (i = 0; i < meshsize; i++) {
+            unsigned int j;
+            for (j = 0; j < meshsize; j++) {
+                double const r = Real(a, i, j);
+                if (r > 0)
+                    Real(a, i, j) = pow(r, powscale);
+            }
+        }
+    }
+}
+
+
+
+static void
+computeExtremeReal(const float * const a,
+                   int           const meshsize,
+                   double *      const rminP,
+                   double *      const rmaxP) {
+    
+    /* Compute extrema for autoscaling. */
+
+    double rmin, rmax;
+    unsigned int i;
+
+    rmin = hugeVal;
+    rmax = -hugeVal;
+
+    for (i = 0; i < meshsize; i++) {
+        unsigned int j;
+        for (j = 0; j < meshsize; j++) {
+            double r = Real(a, i, j);
+            
+            rmin = MIN(rmin, r);
+            rmax = MAX(rmax, r);
+        }
+    }
+    *rminP = rmin;
+    *rmaxP = rmax;
+}
+
+
+
+static void
+replaceWithSpread(float * const a,
+                  int     const meshsize) {
+/*----------------------------------------------------------------------------
+  Replace the real part of each element of the 'a' array with a
+  measure of how far the real is from the middle; sort of a standard
+  deviation.
+-----------------------------------------------------------------------------*/
+    double rmin, rmax;
+    double rmean, rrange;
+    unsigned int i;
+
+    computeExtremeReal(a, meshsize, &rmin, &rmax);
+
+    rmean = (rmin + rmax) / 2;
+    rrange = (rmax - rmin) / 2;
+
+    for (i = 0; i < meshsize; i++) {
+        unsigned int j;
+        for (j = 0; j < meshsize; j++) {
+            Real(a, i, j) = (Real(a, i, j) - rmean) / rrange;
+        }
+    }
+}
+
+
+
+static bool
+planet(unsigned int const cols,
+       unsigned int const rows,
+       bool         const stars,
+       bool         const clouds) {
+/*----------------------------------------------------------------------------
+   Make a planet.
+-----------------------------------------------------------------------------*/
+    float * a;
+    bool error;
+    unsigned int rseed;        /* Current random seed */
+    
+    if (seedspec)
+        rseed = seedarg;
+    else 
+        rseed = initseed();
+    
+    initgauss(rseed);
+    
+    if (stars) {
+        a = NULL;
+        error = FALSE;
+    } else {
+        spectralsynth(&a, meshsize, 3.0 - fracdim);
+        if (a == NULL) {
+            error = TRUE;
+        } else {
+            applyPowerLawScaling(a, meshsize, powscale);
+                
+            replaceWithSpread(a, meshsize);
+
+            error = FALSE;
+        }
+    }
+    if (!error)
+        genplanet(stars, clouds, a, cols, rows, meshsize, rseed);
+
+    if (a != NULL)
+        free(a);
+
+    return !error;
+}
+
+
+
+
+int 
+main(int argc, char ** argv) {
+
+    bool success;
+    int i;
+    const char * const usage = "\n\
+[-width|-xsize <x>] [-height|-ysize <y>] [-mesh <n>]\n\
+[-clouds] [-dimension <f>] [-power <f>] [-seed <n>]\n\
+[-hour <f>] [-inclination|-tilt <f>] [-ice <f>] [-glaciers <f>]\n\
+[-night] [-stars <n>] [-saturation <n>]";
+    bool dimspec = FALSE, meshspec = FALSE, powerspec = FALSE,
+        widspec = FALSE, hgtspec = FALSE, icespec = FALSE,
+        glacspec = FALSE, starspec = FALSE, starcspec = FALSE;
+
+    int cols, rows;     /* Dimensions of our output image */
+
+    ppm_init(&argc, argv);
+    i = 1;
+    
+    while ((i < argc) && (argv[i][0] == '-') && (argv[i][1] != '\0')) {
+
+        if (pm_keymatch(argv[i], "-clouds", 2)) {
+            clouds = TRUE;
+        } else if (pm_keymatch(argv[i], "-night", 2)) {
+            stars = TRUE;
+        } else if (pm_keymatch(argv[i], "-dimension", 2)) {
+            if (dimspec) {
+                pm_error("already specified a dimension");
+            }
+            i++;
+            if ((i == argc) || (sscanf(argv[i], "%lf", &fracdim)  != 1))
+                pm_usage(usage);
+            if (fracdim <= 0.0) {
+                pm_error("fractal dimension must be greater than 0");
+            }
+            dimspec = TRUE;
+        } else if (pm_keymatch(argv[i], "-hour", 3)) {
+            if (hourspec) {
+                pm_error("already specified an hour");
+            }
+            i++;
+            if ((i == argc) || (sscanf(argv[i], "%lf", &hourangle) != 1))
+                pm_usage(usage);
+            hourangle = (M_PI / 12.0) * (hourangle + 12.0);
+            hourspec = TRUE;
+        } else if (pm_keymatch(argv[i], "-inclination", 3) ||
+                   pm_keymatch(argv[i], "-tilt", 2)) {
+            if (inclspec) {
+                pm_error("already specified an inclination/tilt");
+            }
+            i++;
+            if ((i == argc) || (sscanf(argv[i], "%lf", &inclangle) != 1))
+                pm_usage(usage);
+            inclangle = (M_PI / 180.0) * inclangle;
+            inclspec = TRUE;
+        } else if (pm_keymatch(argv[i], "-mesh", 2)) {
+            unsigned int j;
+
+            if (meshspec) {
+                pm_error("already specified a mesh size");
+            }
+            i++;
+            if ((i == argc) || (sscanf(argv[i], "%d", &meshsize) != 1))
+                pm_usage(usage);
+
+            /* Force FFT mesh to the next larger power of 2. */
+
+            for (j = meshsize; (j & 1) == 0; j >>= 1) ;
+
+            if (j != 1) {
+                for (j = 2; j < meshsize; j <<= 1) ;
+                meshsize = j;
+            }
+            meshspec = TRUE;
+        } else if (pm_keymatch(argv[i], "-power", 2)) {
+            if (powerspec) {
+                pm_error("already specified a power factor");
+            }
+            i++;
+            if ((i == argc) || (sscanf(argv[i], "%lf", &powscale) != 1))
+                pm_usage(usage);
+            if (powscale <= 0.0) {
+                pm_error("power factor must be greater than 0");
+            }
+            powerspec = TRUE;
+        } else if (pm_keymatch(argv[i], "-ice", 3)) {
+            if (icespec) {
+                pm_error("already specified ice cap level");
+            }
+            i++;
+            if ((i == argc) || (sscanf(argv[i], "%lf", &icelevel) != 1))
+                pm_usage(usage);
+            if (icelevel <= 0.0) {
+                pm_error("ice cap level must be greater than 0");
+            }
+            icespec = TRUE;
+        } else if (pm_keymatch(argv[i], "-glaciers", 2)) {
+            if (glacspec) {
+                pm_error("already specified glacier level");
+            }
+            i++;
+            if ((i == argc) || (sscanf(argv[i], "%lf", &glaciers) != 1))
+                pm_usage(usage);
+            if (glaciers <= 0.0) {
+                pm_error("glacier level must be greater than 0");
+            }
+            glacspec = TRUE;
+        } else if (pm_keymatch(argv[i], "-stars", 3)) {
+            if (starspec) {
+                pm_error("already specified a star fraction");
+            }
+            i++;
+            if ((i == argc) || (sscanf(argv[i], "%d", &starfraction) != 1))
+                pm_usage(usage);
+            starspec = TRUE;
+        } else if (pm_keymatch(argv[i], "-saturation", 3)) {
+            if (starcspec) {
+                pm_error("already specified a star color saturation");
+            }
+            i++;
+            if ((i == argc) || (sscanf(argv[i], "%d", &starcolor) != 1))
+                pm_usage(usage);
+            starcspec = TRUE;
+        } else if (pm_keymatch(argv[i], "-seed", 3)) {
+            if (seedspec) {
+                pm_error("already specified a random seed");
+            }
+            i++;
+            if ((i == argc) || (sscanf(argv[i], "%u", &seedarg) != 1))
+                pm_usage(usage);
+            seedspec = TRUE;
+        } else if (pm_keymatch(argv[i], "-xsize", 2) ||
+                   pm_keymatch(argv[i], "-width", 2)) {
+            if (widspec) {
+                pm_error("already specified a width/xsize");
+            }
+            i++;
+            if ((i == argc) || (sscanf(argv[i], "%d", &screenxsize) != 1))
+                pm_usage(usage);
+            widspec = TRUE;
+        } else if (pm_keymatch(argv[i], "-ysize", 2) ||
+                   pm_keymatch(argv[i], "-height", 3)) {
+            if (hgtspec) {
+                pm_error("already specified a height/ysize");
+            }
+            i++;
+            if ((i == argc) || (sscanf(argv[i], "%d", &screenysize) != 1))
+                pm_usage(usage);
+            hgtspec = TRUE;
+        } else {
+            pm_usage(usage);
+        }
+        i++;
+    }
+
+
+    /* Set defaults when explicit specifications were not given.
+
+       The  default  fractal  dimension  and  power  scale depend upon
+       whether we are generating a planet or clouds. 
+    */
+    
+    if (!dimspec) {
+        fracdim = clouds ? 2.15 : 2.4;
+    }
+    if (!powerspec) {
+        powscale = clouds ? 0.75 : 1.2;
+    }
+    if (!icespec) {
+        icelevel = 0.4;
+    }
+    if (!glacspec) {
+        glaciers = 0.75;
+    }
+    if (!starspec) {
+        starfraction = 100;
+    }
+    if (!starcspec) {
+        starcolor = 125;
+    }
+
+    /* Force  screen to be at least  as wide as it is high.  Long,
+       skinny screens  cause  crashes  because  picture  width  is
+       calculated based on height.  
+    */
+
+    cols = (MAX(screenysize, screenxsize) + 1) & (~1);
+    rows = screenysize;
+
+    success = planet(cols, rows, stars, clouds);
+
+    exit(success ? 0 : 1);
+}
diff --git a/generator/ppmmake.c b/generator/ppmmake.c
new file mode 100644
index 00000000..eee32485
--- /dev/null
+++ b/generator/ppmmake.c
@@ -0,0 +1,117 @@
+/* ppmmake.c - create a pixmap of a specified color and size
+**
+** Copyright (C) 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "pm_c_util.h"
+#include "mallocvar.h"
+#include "shhopt.h"
+#include "ppm.h"
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    pixel color;
+    unsigned int cols;
+    unsigned int rows;
+    pixval maxval;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+  Convert program invocation arguments (argc,argv) into a format the 
+  program can use easily, struct cmdlineInfo.  Validate arguments along
+  the way and exit program with message if invalid.
+
+  Note that some string information we return as *cmdlineP is in the storage 
+  argv[] points to.
+-----------------------------------------------------------------------------*/
+    optEntry * option_def;
+        /* Instructions to OptParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int maxvalSpec;
+    unsigned int option_def_index;
+
+    MALLOCARRAY(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0,   "maxval",    OPT_UINT, &cmdlineP->maxval, &maxvalSpec,    0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = false;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = false;  /* We have no parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (!maxvalSpec)
+        cmdlineP->maxval = PPM_MAXMAXVAL;
+    else {
+        if (cmdlineP->maxval > PPM_OVERALLMAXVAL)
+            pm_error("The value you specified for -maxval (%u) is too big.  "
+                     "Max allowed is %u", cmdlineP->maxval, PPM_OVERALLMAXVAL);
+        
+        if (cmdlineP->maxval < 1)
+            pm_error("You cannot specify 0 for -maxval");
+    }    
+
+    if (argc-1 < 3)
+        pm_error("Need 3 arguments: color, width, height.");
+    else if (argc-1 > 3)
+        pm_error("Only 3 arguments allowed: color, width, height.  "
+                 "You specified %d", argc-1);
+    else {
+        cmdlineP->color = ppm_parsecolor(argv[1], cmdlineP->maxval);
+        cmdlineP->cols = atoi(argv[2]);
+        cmdlineP->rows = atoi(argv[3]);
+        if (cmdlineP->cols <= 0)
+            pm_error("width argument must be a positive number.  You "
+                     "specified '%s'", argv[2]);
+        if (cmdlineP->rows <= 0)
+            pm_error("height argument must be a positive number.  You "
+                     "specified '%s'", argv[3]);
+    }
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+    pixel * pixrow;
+    unsigned int row;
+
+    ppm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ppm_writeppminit(stdout, cmdline.cols, cmdline.rows, cmdline.maxval, 0);
+    pixrow = ppm_allocrow(cmdline.cols);
+
+    for (row = 0; row < cmdline.rows; ++row) {
+        unsigned int col;
+        for (col = 0; col < cmdline.cols; ++col)
+            pixrow[col] = cmdline.color;
+        ppm_writeppmrow(stdout, pixrow, cmdline.cols, cmdline.maxval, 0);
+	}
+
+    ppm_freerow(pixrow);
+    pm_close(stdout);
+
+    return 0;
+}
diff --git a/generator/ppmpat.c b/generator/ppmpat.c
new file mode 100644
index 00000000..343100d5
--- /dev/null
+++ b/generator/ppmpat.c
@@ -0,0 +1,1060 @@
+/* ppmpat.c - make a pixmap
+**
+** Copyright (C) 1989, 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#define _XOPEN_SOURCE  /* get M_PI in math.h */
+
+#include <math.h>
+
+#include "pm_c_util.h"
+#include "ppm.h"
+#include "ppmdraw.h"
+
+
+
+
+static pixel
+random_anticamo_color(pixval const maxval) {
+
+    int v1, v2, v3;
+    pixel p;
+
+    v1 = (maxval + 1) / 4;
+    v2 = (maxval + 1) / 2;
+    v3 = 3 * v1;
+
+    switch (rand() % 15) {
+    case 0: case 1:
+        PPM_ASSIGN(p, rand() % v1 + v3, rand() % v2, rand() % v2);
+        break;
+
+    case 2:
+    case 3:
+        PPM_ASSIGN(p, rand() % v2, rand() % v1 + v3, rand() % v2);
+        break;
+        
+    case 4:
+    case 5:
+        PPM_ASSIGN(p, rand() % v2, rand() % v2, rand() % v1 + v3);
+        break;
+
+    case 6:
+    case 7:
+    case 8:
+        PPM_ASSIGN(p, rand() % v2, rand() % v1 + v3, rand() % v1 + v3);
+        break;
+
+    case 9:
+    case 10:
+    case 11:
+        PPM_ASSIGN(p, rand() % v1 + v3, rand() % v2, rand() % v1 + v3);
+        break;
+
+    case 12:
+    case 13:
+    case 14:
+        PPM_ASSIGN(p, rand() % v1 + v3, rand() % v1 + v3, rand() % v2);
+        break;
+    }
+    
+    return p;
+}
+
+
+
+/* Camouflage stuff. */
+
+static pixel
+random_camo_color(pixval const maxval) {
+
+    int v1, v2, v3;
+    pixel p;
+
+    v1 = (maxval + 1 ) / 8;
+    v2 = (maxval + 1 ) / 4;
+    v3 = (maxval + 1 ) / 2;
+
+    switch (rand() % 10) {
+    case 0:
+    case 1:
+    case 2:
+        /* light brown */
+        PPM_ASSIGN(p, rand() % v3 + v3, rand() % v3 + v2, rand() % v3 + v2);
+        break;
+
+    case 3:
+    case 4:
+    case 5:
+        /* dark green */
+        PPM_ASSIGN(p, rand() % v2, rand() % v2 + 3 * v1, rand() % v2);
+        break;
+    
+    case 6:
+    case 7:
+        /* brown */
+        PPM_ASSIGN(p, rand() % v2 + v2, rand() % v2, rand() % v2);
+        break;
+
+    case 8:
+    case 9:
+        /* dark brown */
+        PPM_ASSIGN(p, rand() % v1 + v1, rand() % v1, rand() % v1);
+        break;
+    }
+
+    return p;
+}
+
+
+
+static float
+rnduni(void) {
+    return rand() % 32767 / 32767.0;
+}
+
+
+
+#define BLOBRAD 50
+
+#define MIN_POINTS 7
+#define MAX_POINTS 13
+
+#define MIN_ELLIPSE_FACTOR 0.5
+#define MAX_ELLIPSE_FACTOR 2.0
+
+#define MIN_POINT_FACTOR 0.5
+#define MAX_POINT_FACTOR 2.0
+
+
+
+static void
+camo(pixel **     const pixels,
+     unsigned int const cols,
+     unsigned int const rows,
+     pixval       const maxval,
+     bool         const antiflag) {
+
+    pixel color;
+    int n, i, cx, cy;
+    struct fillobj * fh;
+
+    /* Clear background. */
+    if (antiflag)
+        color = random_anticamo_color( maxval );
+    else
+        color = random_camo_color( maxval );
+
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, 0, cols, rows, PPMD_NULLDRAWPROC,
+        &color);
+
+    n = (rows * cols) / (BLOBRAD * BLOBRAD) * 5;
+    for (i = 0; i < n; ++i) {
+        int points, p, xs[MAX_POINTS], ys[MAX_POINTS], x0, y0;
+        float a, b, c, theta, tang, tx, ty;
+        
+        cx = rand() % cols;
+        cy = rand() % rows;
+        
+        points = rand() % ( MAX_POINTS - MIN_POINTS + 1 ) + MIN_POINTS;
+        a = rnduni() * (MAX_ELLIPSE_FACTOR - MIN_ELLIPSE_FACTOR) +
+            MIN_ELLIPSE_FACTOR;
+        b = rnduni() * (MAX_ELLIPSE_FACTOR - MIN_ELLIPSE_FACTOR) +
+            MIN_ELLIPSE_FACTOR;
+        theta = rnduni() * 2.0 * M_PI;
+        for (p = 0; p < points; ++p) {
+            tx = a * sin(p * 2.0 * M_PI / points);
+            ty = b * cos(p * 2.0 * M_PI / points);
+            tang = atan2(ty, tx) + theta;
+            c = rnduni() * (MAX_POINT_FACTOR - MIN_POINT_FACTOR) +
+                MIN_POINT_FACTOR;
+            xs[p] = cx + BLOBRAD * c * sin(tang);
+            ys[p] = cy + BLOBRAD * c * cos(tang);
+        }
+        x0 = (xs[0] + xs[points - 1]) / 2;
+        y0 = (ys[0] + ys[points - 1]) / 2;
+
+        fh = ppmd_fill_create();
+
+        ppmd_polyspline(
+            pixels, cols, rows, maxval, x0, y0, points, xs, ys, x0, y0,
+            ppmd_fill_drawproc, fh );
+        
+        if (antiflag)
+            color = random_anticamo_color(maxval);
+        else
+            color = random_camo_color(maxval);
+        ppmd_fill(pixels, cols, rows, maxval, fh, PPMD_NULLDRAWPROC, &color);
+        
+        ppmd_fill_destroy(fh);
+    }
+}
+
+
+
+static pixel
+random_color(pixval const maxval) {
+
+    pixel p;
+
+    PPM_ASSIGN(p,
+               rand() % (maxval + 1),
+               rand() % (maxval + 1),
+               rand() % (maxval + 1)
+        );
+    
+    return p;
+}
+
+
+
+#define DARK_THRESH 0.25
+
+#if __STDC__
+static pixel
+random_bright_color( pixval maxval )
+#else /*__STDC__*/
+static pixel
+random_bright_color( maxval )
+    pixval maxval;
+#endif /*__STDC__*/
+    {
+    pixel p;
+
+    do
+    {
+    p = random_color( maxval );
+    }
+    while ( PPM_LUMIN( p ) <= maxval * DARK_THRESH );
+
+    return p;
+    }
+
+#if __STDC__
+static pixel
+random_dark_color( pixval maxval )
+#else /*__STDC__*/
+static pixel
+random_dark_color( maxval )
+    pixval maxval;
+#endif /*__STDC__*/
+    {
+    pixel p;
+
+    do
+    {
+    p = random_color( maxval );
+    }
+    while ( PPM_LUMIN( p ) > maxval * DARK_THRESH );
+
+    return p;
+    }
+
+static pixel
+average_two_colors( p1, p2 )
+pixel p1, p2;
+    {
+    pixel p;
+
+    PPM_ASSIGN(
+    p, ( (int) PPM_GETR(p1) + (int) PPM_GETR(p2) ) / 2,
+    ( (int) PPM_GETG(p1) + (int) PPM_GETG(p2) ) / 2,
+    ( (int) PPM_GETB(p1) + (int) PPM_GETB(p2) ) / 2 );
+
+    return p;
+    }
+
+/* Gingham stuff. */
+
+static void
+average_drawproc(pixel** const pixels, 
+                 int const cols, 
+                 int const rows, 
+                 pixval const maxval, 
+                 int const col, 
+                 int const row, 
+                 const void* const clientdata )
+{
+    if ( col >= 0 && col < cols && row >= 0 && row < rows )
+        pixels[row][col] =
+            average_two_colors( pixels[row][col], *( (pixel*) clientdata ) );
+}
+
+static void
+gingham2( pixel** pixels, int cols, int rows, pixval maxval )
+{
+    pixel const backcolor = random_dark_color( maxval );
+    pixel const forecolor = random_bright_color( maxval );
+    int const colso2 = cols / 2;
+    int const rowso2 = rows / 2;
+
+    /* Warp. */
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, 0, colso2, rows, PPMD_NULLDRAWPROC,
+        &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, colso2, 0, cols - colso2, rows,
+        PPMD_NULLDRAWPROC, &forecolor );
+
+    /* Woof. */
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, 0, cols, rowso2, average_drawproc,
+        &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, rowso2, cols, rows - rowso2,
+        average_drawproc, &forecolor );
+    }
+
+#if __STDC__
+static void
+gingham3( pixel** pixels, int cols, int rows, pixval maxval )
+#else /*__STDC__*/
+static void
+gingham3( pixels, cols, rows, maxval )
+    pixel** pixels;
+    int cols, rows;
+    pixval maxval;
+#endif /*__STDC__*/
+    {
+    int colso4, rowso4;
+    pixel backcolor, fore1color, fore2color;
+
+    colso4 = cols / 4;
+    rowso4 = rows / 4;
+    backcolor = random_dark_color( maxval );
+    fore1color = random_bright_color( maxval );
+    fore2color = random_bright_color( maxval );
+
+    /* Warp. */
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, 0, colso4, rows, PPMD_NULLDRAWPROC,
+        &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, colso4, 0, colso4, rows, PPMD_NULLDRAWPROC,
+        &fore1color );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 2 * colso4, 0, colso4, rows,
+        PPMD_NULLDRAWPROC, &fore2color );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 3 * colso4, 0, cols - colso4, rows,
+        PPMD_NULLDRAWPROC, &fore1color );
+
+    /* Woof. */
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, 0, cols, rowso4, average_drawproc,
+        &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, rowso4, cols, rowso4, average_drawproc,
+        &fore1color );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, 2 * rowso4, cols, rowso4,
+        average_drawproc, &fore2color );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, 3 * rowso4, cols, rows - rowso4,
+        average_drawproc, &fore1color );
+    }
+
+#if __STDC__
+static void
+madras( pixel** pixels, int cols, int rows, pixval maxval )
+#else /*__STDC__*/
+static void
+madras( pixels, cols, rows, maxval )
+    pixel** pixels;
+    int cols, rows;
+    pixval maxval;
+#endif /*__STDC__*/
+    {
+    int cols2, rows2, cols3, rows3, cols12, rows12, cols6a, rows6a, cols6b,
+    rows6b;
+    pixel backcolor, fore1color, fore2color;
+
+    cols2 = cols * 2 / 44;
+    rows2 = rows * 2 / 44;
+    cols3 = cols * 3 / 44;
+    rows3 = rows * 3 / 44;
+    cols12 = cols - 10 * cols2 - 4 * cols3;
+    rows12 = rows - 10 * rows2 - 4 * rows3;
+    cols6a = cols12 / 2;
+    rows6a = rows12 / 2;
+    cols6b = cols12 - cols6a;
+    rows6b = rows12 - rows6a;
+    backcolor = random_dark_color( maxval );
+    fore1color = random_bright_color( maxval );
+    fore2color = random_bright_color( maxval );
+
+    /* Warp. */
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, 0, cols2, rows, PPMD_NULLDRAWPROC,
+        &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, cols2, 0, cols3, rows, PPMD_NULLDRAWPROC,
+        &fore1color );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, cols2 + cols3, 0, cols2, rows,
+        PPMD_NULLDRAWPROC, &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 2 * cols2 + cols3, 0, cols2, rows,
+        PPMD_NULLDRAWPROC, &fore2color );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 3 * cols2 + cols3, 0, cols2, rows,
+        PPMD_NULLDRAWPROC, &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 4 * cols2 + cols3, 0, cols6a, rows,
+        PPMD_NULLDRAWPROC, &fore1color );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 4 * cols2 + cols3 + cols6a, 0, cols2, rows,
+        PPMD_NULLDRAWPROC, &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 5 * cols2 + cols3 + cols6a, 0, cols3, rows,
+        PPMD_NULLDRAWPROC, &fore2color );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 5 * cols2 + 2 * cols3 + cols6a, 0, cols2,
+        rows, PPMD_NULLDRAWPROC, &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 6 * cols2 + 2 * cols3 + cols6a, 0, cols3,
+        rows, PPMD_NULLDRAWPROC, &fore2color );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 6 * cols2 + 3 * cols3 + cols6a, 0, cols2,
+        rows, PPMD_NULLDRAWPROC, &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 7 * cols2 + 3 * cols3 + cols6a, 0, cols6b,
+        rows, PPMD_NULLDRAWPROC, &fore1color );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 7 * cols2 + 3 * cols3 + cols6a + cols6b, 0,
+        cols2, rows, PPMD_NULLDRAWPROC, &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 8 * cols2 + 3 * cols3 + cols6a + cols6b, 0,
+        cols2, rows, PPMD_NULLDRAWPROC, &fore2color );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 9 * cols2 + 3 * cols3 + cols6a + cols6b, 0,
+        cols2, rows, PPMD_NULLDRAWPROC, &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 10 * cols2 + 3 * cols3 + cols6a + cols6b, 
+        0, cols3, rows, PPMD_NULLDRAWPROC, &fore1color );
+
+    /* Woof. */
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, 0, cols, rows2, average_drawproc,
+        &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, rows2, cols, rows3, average_drawproc,
+        &fore2color );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, rows2 + rows3, cols, rows2,
+        average_drawproc, &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, 2 * rows2 + rows3, cols, rows2,
+        average_drawproc, &fore1color );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, 3 * rows2 + rows3, cols, rows2,
+        average_drawproc, &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, 4 * rows2 + rows3, cols, rows6a,
+        average_drawproc, &fore2color );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, 4 * rows2 + rows3 + rows6a, cols, rows2,
+        average_drawproc, &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, 5 * rows2 + rows3 + rows6a, cols, rows3,
+        average_drawproc, &fore1color );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, 5 * rows2 + 2 * rows3 + rows6a, cols,
+        rows2, average_drawproc, &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, 6 * rows2 + 2 * rows3 + rows6a, cols,
+        rows3, average_drawproc, &fore1color );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, 6 * rows2 + 3 * rows3 + rows6a, cols,
+        rows2, average_drawproc, &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, 7 * rows2 + 3 * rows3 + rows6a, cols,
+        rows6b, average_drawproc, &fore2color );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, 7 * rows2 + 3 * rows3 + rows6a + rows6b,
+        cols, rows2, average_drawproc, &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, 8 * rows2 + 3 * rows3 + rows6a + rows6b,
+        cols, rows2, average_drawproc, &fore1color );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, 9 * rows2 + 3 * rows3 + rows6a + rows6b,
+        cols, rows2, average_drawproc, &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, 
+        10 * rows2 + 3 * rows3 + rows6a + rows6b,
+        cols, rows3, average_drawproc, &fore2color );
+    }
+
+#if __STDC__
+static void
+tartan( pixel** pixels, int cols, int rows, pixval maxval )
+#else /*__STDC__*/
+static void
+tartan( pixels, cols, rows, maxval )
+    pixel** pixels;
+    int cols, rows;
+    pixval maxval;
+#endif /*__STDC__*/
+    {
+    int cols1, rows1, cols3, rows3, cols10, rows10, cols5a, rows5a, cols5b,
+    rows5b;
+    pixel backcolor, fore1color, fore2color;
+
+    cols1 = cols / 22;
+    rows1 = rows / 22;
+    cols3 = cols * 3 / 22;
+    rows3 = rows * 3 / 22;
+    cols10 = cols - 3 * cols1 - 3 * cols3;
+    rows10 = rows - 3 * rows1 - 3 * rows3;
+    cols5a = cols10 / 2;
+    rows5a = rows10 / 2;
+    cols5b = cols10 - cols5a;
+    rows5b = rows10 - rows5a;
+    backcolor = random_dark_color( maxval );
+    fore1color = random_bright_color( maxval );
+    fore2color = random_bright_color( maxval );
+
+    /* Warp. */
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, 0, cols5a, rows, PPMD_NULLDRAWPROC,
+        &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, cols5a, 0, cols1, rows, PPMD_NULLDRAWPROC,
+        &fore1color );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, cols5a + cols1, 0, cols5b, rows,
+        PPMD_NULLDRAWPROC, &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, cols10 + cols1, 0, cols3, rows,
+        PPMD_NULLDRAWPROC, &fore2color );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, cols10 + cols1 + cols3, 0, cols1, rows,
+        PPMD_NULLDRAWPROC, &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, cols10 + 2 * cols1 + cols3, 0, cols3, rows,
+        PPMD_NULLDRAWPROC, &fore2color );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, cols10 + 2 * cols1 + 2 * cols3, 0, cols1,
+        rows, PPMD_NULLDRAWPROC, (char*) &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, cols10 + 3 * cols1 + 2 * cols3, 0, cols3,
+        rows, PPMD_NULLDRAWPROC, &fore2color );
+
+    /* Woof. */
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, 0, cols, rows5a, average_drawproc,
+        &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, rows5a, cols, rows1, average_drawproc,
+        &fore1color );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, rows5a + rows1, cols, rows5b,
+        average_drawproc, &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, rows10 + rows1, cols, rows3,
+        average_drawproc, &fore2color );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, rows10 + rows1 + rows3, cols, rows1,
+        average_drawproc, &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, rows10 + 2 * rows1 + rows3, cols, rows3,
+        average_drawproc, &fore2color );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, rows10 + 2 * rows1 + 2 * rows3, cols,
+        rows1, average_drawproc, &backcolor );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, rows10 + 3 * rows1 + 2 * rows3, cols,
+        rows3, average_drawproc, &fore2color );
+    }
+
+/* Poles stuff. */
+
+#define MAXPOLES 500
+
+#if __STDC__
+static void
+poles( pixel** pixels, int cols, int rows, pixval maxval )
+#else /*__STDC__*/
+static void
+poles( pixels, cols, rows, maxval )
+    pixel** pixels;
+    int cols, rows;
+    pixval maxval;
+#endif /*__STDC__*/
+    {
+    int poles, i, xs[MAXPOLES], ys[MAXPOLES], col, row;
+    pixel colors[MAXPOLES];
+
+    poles = cols * rows / 30000;
+
+    /* Place and color poles randomly. */
+    for ( i = 0; i < poles; ++i )
+    {
+    xs[i] = rand() % cols;
+    ys[i] = rand() % rows;
+    colors[i] = random_bright_color( maxval );
+    }
+
+    /* Now interpolate points. */
+    for ( row = 0; row < rows; ++row )
+    for ( col = 0; col < cols; ++col )
+        {
+        register long dist1, dist2, newdist, r, g, b;
+        pixel color1, color2;
+
+        /* Find two closest poles. */
+        dist1 = dist2 = 2000000000;
+        for ( i = 0; i < poles; ++i )
+        {
+        newdist = ( col - xs[i] ) * ( col - xs[i] ) +
+              ( row - ys[i] ) * ( row - ys[i] );
+        if ( newdist < dist1 )
+            {
+            dist1 = newdist;
+            color1 = colors[i];
+            }
+        else if ( newdist < dist2 )
+            {
+            dist2 = newdist;
+            color2 = colors[i];
+            }
+        }
+
+        /* And assign interpolated color. */
+        newdist = dist1 + dist2;
+        r = PPM_GETR(color1)*dist2/newdist + PPM_GETR(color2)*dist1/newdist;
+        g = PPM_GETG(color1)*dist2/newdist + PPM_GETG(color2)*dist1/newdist;
+        b = PPM_GETB(color1)*dist2/newdist + PPM_GETB(color2)*dist1/newdist;
+        PPM_ASSIGN( pixels[row][col], r, g, b );
+        }
+    }
+
+/* Squig stuff. */
+
+#define SQUIGS 5
+#define SQ_POINTS 7
+#define SQ_MAXCIRCLE_POINTS 5000
+
+static int sq_radius, sq_circlecount;
+static pixel sq_colors[SQ_MAXCIRCLE_POINTS];
+static int sq_xoffs[SQ_MAXCIRCLE_POINTS], sq_yoffs[SQ_MAXCIRCLE_POINTS];
+
+static void
+sq_measurecircle_drawproc(pixel** const pixels, 
+                          int const cols, 
+                          int const rows, 
+                          pixval const maxval, 
+                          int const col, 
+                          int const row, 
+                          const void* const clientdata)
+{
+    sq_xoffs[sq_circlecount] = col;
+    sq_yoffs[sq_circlecount] = row;
+    ++sq_circlecount;
+}
+
+static void
+sq_rainbowcircle_drawproc(pixel** const pixels, 
+                          int const cols, 
+                          int const rows, 
+                          pixval const maxval, 
+                          int const col, 
+                          int const row, 
+                          const void* const clientdata )
+{
+    int i;
+
+    for ( i = 0; i < sq_circlecount; ++i )
+    ppmd_point_drawproc(
+        pixels, cols, rows, maxval, col + sq_xoffs[i], row + sq_yoffs[i],
+        &(sq_colors[i]) );
+    }
+
+
+
+static void
+sq_assign_colors(int     const circlecount,
+                 pixval  const maxval,
+                 pixel * const colors) {
+
+    pixel rc1, rc2, rc3;
+    float cco3;
+    unsigned int i;
+
+    rc1 = random_bright_color(maxval);
+    rc2 = random_bright_color(maxval);
+    rc3 = random_bright_color(maxval);
+    cco3 = (circlecount - 1) / 3.0;
+
+    for (i = 0; i < circlecount ; ++i) {
+        if (i < cco3) {
+            float const frac = (float)i/cco3;
+            PPM_ASSIGN(colors[i],
+                       (float) PPM_GETR(rc1) +
+                       ((float) PPM_GETR(rc2) - (float) PPM_GETR(rc1)) * frac,
+                       (float) PPM_GETG(rc1) +
+                       ((float) PPM_GETG(rc2) - (float) PPM_GETG(rc1)) * frac,
+                       (float) PPM_GETB(rc1) +
+                       ((float) PPM_GETB(rc2) - (float) PPM_GETB(rc1)) * frac
+                );
+        } else if (i < 2.0 * cco3) {
+            float const frac = (float)i/cco3 - 1.0;
+            PPM_ASSIGN(colors[i],
+                       (float) PPM_GETR(rc2) +
+                       ((float) PPM_GETR(rc3) - (float) PPM_GETR(rc2)) * frac,
+                       (float) PPM_GETG(rc2) +
+                       ((float) PPM_GETG(rc3) - (float) PPM_GETG(rc2)) * frac,
+                       (float) PPM_GETB(rc2) +
+                       ((float) PPM_GETB(rc3) - (float) PPM_GETB(rc2)) * frac
+                       );
+        } else {
+            float const frac = (float)i/cco3 - 2.0;
+            PPM_ASSIGN(colors[i],
+                       (float) PPM_GETR(rc3) +
+                       ((float) PPM_GETR(rc1) - (float) PPM_GETR(rc3)) * frac,
+                       (float) PPM_GETG(rc3) +
+                       ((float) PPM_GETG(rc1) - (float) PPM_GETG(rc3)) * frac,
+                       (float) PPM_GETB(rc3) +
+                       ((float) PPM_GETB(rc1) - (float) PPM_GETB(rc3)) * frac
+                );
+        }
+    }
+}
+
+
+#if __STDC__
+static void
+squig( pixel** pixels, int cols, int rows, pixval maxval )
+#else /*__STDC__*/
+static void
+squig( pixels, cols, rows, maxval )
+    pixel** pixels;
+    int cols, rows;
+    pixval maxval;
+#endif /*__STDC__*/
+    {
+    pixel color;
+    int i, j, xc[SQ_POINTS], yc[SQ_POINTS], x0, y0, x1, y1, x2, y2, x3, y3;
+
+    /* Clear image to black. */
+    PPM_ASSIGN( color, 0, 0, 0 );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, 0, cols, rows, PPMD_NULLDRAWPROC,
+        &color );
+
+    /* Draw the squigs. */
+    (void) ppmd_setlinetype( PPMD_LINETYPE_NODIAGS );
+    (void) ppmd_setlineclip( 0 );
+    for ( i = SQUIGS; i > 0; --i )
+    {
+    /* Measure circle. */
+    sq_radius = ( cols + rows ) / 2 / ( 25 + i * 2 );
+    sq_circlecount = 0;
+    ppmd_circle(
+        pixels, cols, rows, maxval, 0, 0, sq_radius,
+        sq_measurecircle_drawproc, NULL );
+    sq_assign_colors( sq_circlecount, maxval, sq_colors );
+
+    /* Choose wrap-around point. */
+    switch ( rand() % 4 )
+        {
+        case 0:
+        x1 = rand() % cols;
+        y1 = 0;
+        if ( x1 < cols / 2 )
+        xc[0] = rand() % ( x1 * 2 );
+        else
+        xc[0] = cols - 1 - rand() % ( ( cols - x1 ) * 2 );
+        yc[0] = rand() % rows;
+        x2 = x1;
+        y2 = rows - 1;
+        xc[SQ_POINTS - 1] = 2 * x2 - xc[0];
+        yc[SQ_POINTS - 1] = y2 - yc[0];
+        x0 = xc[SQ_POINTS - 1];
+        y0 = yc[SQ_POINTS - 1] - rows;
+        x3 = xc[0];
+        y3 = yc[0] + rows;
+        break;
+
+        case 1:
+        x2 = rand() % cols;
+        y2 = 0;
+        if ( x2 < cols / 2 )
+        xc[SQ_POINTS - 1] = rand() % ( x2 * 2 );
+        else
+        xc[SQ_POINTS - 1] = cols - 1 - rand() % ( ( cols - x2 ) * 2 );
+        yc[SQ_POINTS - 1] = rand() % rows;
+        x1 = x2;
+        y1 = rows - 1;
+        xc[0] = 2 * x1 - xc[SQ_POINTS - 1];
+        yc[0] = y1 - yc[SQ_POINTS - 1];
+        x0 = xc[SQ_POINTS - 1];
+        y0 = yc[SQ_POINTS - 1] + rows;
+        x3 = xc[0];
+        y3 = yc[0] - rows;
+        break;
+
+        case 2:
+        x1 = 0;
+        y1 = rand() % rows;
+        xc[0] = rand() % cols;
+        if ( y1 < rows / 2 )
+        yc[0] = rand() % ( y1 * 2 );
+        else
+        yc[0] = rows - 1 - rand() % ( ( rows - y1 ) * 2 );
+        x2 = cols - 1;
+        y2 = y1;
+        xc[SQ_POINTS - 1] = x2 - xc[0];
+        yc[SQ_POINTS - 1] = 2 * y2 - yc[0];
+        x0 = xc[SQ_POINTS - 1] - cols;
+        y0 = yc[SQ_POINTS - 1];
+        x3 = xc[0] + cols;
+        y3 = yc[0];
+        break;
+
+        case 3:
+        x2 = 0;
+        y2 = rand() % rows;
+        xc[SQ_POINTS - 1] = rand() % cols;
+        if ( y2 < rows / 2 )
+        yc[SQ_POINTS - 1] = rand() % ( y2 * 2 );
+        else
+        yc[SQ_POINTS - 1] = rows - 1 - rand() % ( ( rows - y2 ) * 2 );
+        x1 = cols - 1;
+        y1 = y2;
+        xc[0] = x1 - xc[SQ_POINTS - 1];
+        yc[0] = 2 * y1 - yc[SQ_POINTS - 1];
+        x0 = xc[SQ_POINTS - 1] + cols;
+        y0 = yc[SQ_POINTS - 1];
+        x3 = xc[0] - cols;
+        y3 = yc[0];
+        break;
+        }
+
+    for ( j = 1; j < SQ_POINTS - 1; ++j )
+        {
+        xc[j] = ( rand() % ( cols - 2 * sq_radius ) ) + sq_radius;
+        yc[j] = ( rand() % ( rows - 2 * sq_radius ) ) + sq_radius;
+        }
+
+    ppmd_line(
+        pixels, cols, rows, maxval, x0, y0, x1, y1,
+        sq_rainbowcircle_drawproc, NULL );
+    ppmd_polyspline(
+        pixels, cols, rows, maxval, x1, y1, SQ_POINTS, xc, yc, x2, y2,
+        sq_rainbowcircle_drawproc, NULL );
+    ppmd_line(
+        pixels, cols, rows, maxval, x2, y2, x3, y3,
+        sq_rainbowcircle_drawproc, NULL );
+    }
+    }
+
+
+
+/* Test pattern.  Just a place to put ppmdraw exercises. */
+
+static void
+test(pixel **     const pixels,
+     unsigned int const cols,
+     unsigned int const rows,
+     pixval       const maxval) {
+
+    pixel color;
+    struct fillobj * fh;
+
+    /* Clear image to black. */
+    PPM_ASSIGN( color, 0, 0, 0 );
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, 0, cols, rows, PPMD_NULLDRAWPROC,
+        &color);
+
+    fh = ppmd_fill_create();
+
+    ppmd_line(pixels, cols, rows, maxval, 
+              cols/8, rows/8, cols/2, rows/4, ppmd_fill_drawproc, fh);
+    ppmd_line(pixels, cols, rows, maxval, 
+              cols/2, rows/4, cols-cols/8, rows/8, ppmd_fill_drawproc, fh);
+    ppmd_line(pixels, cols, rows, maxval, 
+              cols-cols/8, rows/8, cols/2, rows/2, ppmd_fill_drawproc, fh);
+    ppmd_spline3(pixels, cols, rows, maxval, 
+                 cols/2, rows/2, cols/2-cols/16, rows/2-rows/10, 
+                 cols/2-cols/8, rows/2, ppmd_fill_drawproc, fh);
+    ppmd_spline3(pixels, cols, rows, maxval, 
+                 cols/2-cols/8, rows/2, cols/4+cols/16, rows/2+rows/10, 
+                 cols/4, rows/2, ppmd_fill_drawproc, fh);
+    ppmd_line(pixels, cols, rows, maxval, 
+              cols/4, rows/2, cols/8, rows/2, ppmd_fill_drawproc, fh);
+    ppmd_line(pixels, cols, rows, maxval, 
+              cols/8, rows/2, cols/8, rows/8, ppmd_fill_drawproc, fh);
+
+    PPM_ASSIGN(color, maxval, maxval, maxval);
+    ppmd_fill(pixels, cols, rows, maxval, fh, PPMD_NULLDRAWPROC, &color);
+
+    ppmd_fill_destroy(fh);
+
+}
+
+
+
+int
+main(int argc, char ** argv) {
+
+    pixel** pixels;
+    int argn, pattern, cols, rows;
+#define PAT_NONE 0
+#define PAT_GINGHAM2 1
+#define PAT_GINGHAM3 2
+#define PAT_MADRAS 3
+#define PAT_TARTAN 4
+#define PAT_POLES 5
+#define PAT_SQUIG 6
+#define PAT_CAMO 7
+#define PAT_ANTICAMO 8
+#define PAT_TEST 9
+    const char* const usage = "-gingham|-g2|-gingham3|-g3|-madras|-tartan|-poles|-squig|-camo|-anticamo <width> <height>";
+
+
+    ppm_init(&argc, argv);
+
+    argn = 1;
+    pattern = PAT_NONE;
+
+    while ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' )
+    {
+        if ( pm_keymatch( argv[argn], "-gingham2", 9 ) ||
+             pm_keymatch( argv[argn], "-g2", 3 ) )
+        {
+            if ( pattern != PAT_NONE )
+                pm_error( "only one base pattern may be specified" );
+            pattern = PAT_GINGHAM2;
+        }
+        else if ( pm_keymatch( argv[argn], "-gingham3", 9 ) ||
+                  pm_keymatch( argv[argn], "-g3", 3 ) )
+        {
+            if ( pattern != PAT_NONE )
+                pm_error( "only one base pattern may be specified" );
+            pattern = PAT_GINGHAM3;
+        }
+        else if ( pm_keymatch( argv[argn], "-madras", 2 ) )
+        {
+            if ( pattern != PAT_NONE )
+                pm_error( "only one base pattern may be specified" );
+            pattern = PAT_MADRAS;
+        }
+        else if ( pm_keymatch( argv[argn], "-tartan", 2 ) )
+        {
+            if ( pattern != PAT_NONE )
+                pm_error( "only one base pattern may be specified" );
+            pattern = PAT_TARTAN;
+        }
+        else if ( pm_keymatch( argv[argn], "-poles", 2 ) )
+        {
+            if ( pattern != PAT_NONE )
+                pm_error( "only one base pattern may be specified" );
+            pattern = PAT_POLES;
+        }
+        else if ( pm_keymatch( argv[argn], "-squig", 2 ) )
+        {
+            if ( pattern != PAT_NONE )
+                pm_error( "only one base pattern may be specified" );
+            pattern = PAT_SQUIG;
+        }
+        else if ( pm_keymatch( argv[argn], "-camo", 2 ) )
+        {
+            if ( pattern != PAT_NONE )
+                pm_error( "only one base pattern may be specified" );
+            pattern = PAT_CAMO;
+        }
+        else if ( pm_keymatch( argv[argn], "-anticamo", 2 ) )
+        {
+            if ( pattern != PAT_NONE )
+                pm_error( "only one base pattern may be specified" );
+            pattern = PAT_ANTICAMO;
+        }
+        else if ( pm_keymatch( argv[argn], "-test", 3 ) )
+        {
+            if ( pattern != PAT_NONE )
+                pm_error( "only one base pattern may be specified" );
+            pattern = PAT_TEST;
+        }
+        else
+            pm_usage( usage );
+        ++argn;
+    }
+    if ( pattern == PAT_NONE )
+        pm_error( "a base pattern must be specified" );
+
+    if ( argn == argc )
+        pm_usage( usage);
+    if ( sscanf( argv[argn], "%d", &cols ) != 1 )
+        pm_usage( usage );
+    ++argn;
+    if ( argn == argc )
+        pm_usage( usage);
+    if ( sscanf( argv[argn], "%d", &rows ) != 1 )
+        pm_usage( usage );
+    ++argn;
+
+    if ( argn != argc )
+        pm_usage( usage);
+
+    srand( (int) ( time( 0 ) ^ getpid( ) ) );
+    pixels = ppm_allocarray( cols, rows );
+
+    switch ( pattern )
+    {
+    case PAT_GINGHAM2:
+        gingham2( pixels, cols, rows, PPM_MAXMAXVAL );
+        break;
+
+    case PAT_GINGHAM3:
+        gingham3( pixels, cols, rows, PPM_MAXMAXVAL );
+        break;
+
+    case PAT_MADRAS:
+        madras( pixels, cols, rows, PPM_MAXMAXVAL );
+        break;
+
+    case PAT_TARTAN:
+        tartan( pixels, cols, rows, PPM_MAXMAXVAL );
+        break;
+
+    case PAT_POLES:
+        poles( pixels, cols, rows, PPM_MAXMAXVAL );
+        break;
+
+    case PAT_SQUIG:
+        squig( pixels, cols, rows, PPM_MAXMAXVAL );
+        break;
+
+    case PAT_CAMO:
+        camo( pixels, cols, rows, PPM_MAXMAXVAL, 0 );
+        break;
+
+    case PAT_ANTICAMO:
+        camo( pixels, cols, rows, PPM_MAXMAXVAL, 1 );
+        break;
+
+    case PAT_TEST:
+        test( pixels, cols, rows, PPM_MAXMAXVAL );
+        break;
+
+    default:
+        pm_error( "can't happen!" );
+    }
+
+    /* All done, write it out. */
+    ppm_writeppm( stdout, pixels, cols, rows, PPM_MAXMAXVAL, 0 );
+    pm_close( stdout );
+
+    exit( 0 );
+}
+
diff --git a/generator/ppmrainbow b/generator/ppmrainbow
new file mode 100755
index 00000000..0effeecf
--- /dev/null
+++ b/generator/ppmrainbow
@@ -0,0 +1,74 @@
+#!/usr/bin/perl -wl
+use strict;
+use Getopt::Long;
+
+my ($FALSE, $TRUE) = (0,1);
+
+(my $myname = $0) =~ s#\A.*/##;
+
+my ($Twid, $Thgt, $tmpdir, $norepeat, $verbose);
+
+# set defaults
+$Twid = 600;
+$Thgt = 8;
+$tmpdir = $ENV{"TMPDIR"} || "/tmp";
+$norepeat = $FALSE;
+$verbose = $FALSE;
+
+GetOptions("width=i"   => \$Twid,
+           "height=i"  => \$Thgt,
+           "tmpdir=s"  => \$tmpdir,
+           "norepeat!" => \$norepeat,
+           "verbose!"  => \$verbose);
+
+die "invalid width and/or height\n" unless $Twid >= 1 && $Thgt >= 1;
+
+my $verboseCommand = $verbose ? "set -x;" : "";
+
+if (@ARGV < 1) {
+    die("You must specify at least one color as an argument");
+} elsif (@ARGV < 2 && $norepeat) {
+    die("With the -norepeat option, you must specify at least two colors " .
+        "as arguments.");
+}
+
+my @colorlist;
+
+@colorlist = @ARGV;
+if (!$norepeat) {
+    push @colorlist, $ARGV[0];
+}
+
+my $tmpprefix = $tmpdir . "/$myname.$$.";
+
+my $widthRemaining;
+my $n;
+my @outlist;
+
+$n = 0;
+$widthRemaining = $Twid;
+@outlist = ();
+
+while (@colorlist >= 2) {
+    my $outfile = sprintf("%s%03u.ppm", $tmpprefix, $n);
+    push(@outlist, $outfile);
+
+    my $w = int(($widthRemaining-1)/(@colorlist-1))+1;
+    my $rc = system("$verboseCommand pgmramp -lr $w $Thgt | " .
+                    "pgmtoppm \"$colorlist[0]-$colorlist[1]\" >$outfile");
+    if ($rc != 0) {
+        die("pgmramp|pgmtoppm failed.");
+    }
+    $widthRemaining -= $w;
+    $n++;
+    shift @colorlist;
+}
+
+0 == system qq{$verboseCommand pnmcat -lr @outlist}
+    or exit 1;
+
+exit 0;
+
+END {
+    unlink @outlist if @outlist;
+}
diff --git a/generator/ppmrough.c b/generator/ppmrough.c
new file mode 100644
index 00000000..f8823173
--- /dev/null
+++ b/generator/ppmrough.c
@@ -0,0 +1,292 @@
+/* ppmrough.c - create a PPM image containing two colors with a ragged
+   border between them
+**
+** Copyright (C) 2002 by Eckard Specht.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.  */
+
+#include <math.h>
+#include <sys/time.h>
+#include "ppm.h"
+#include "shhopt.h"
+
+static pixel** PIX;
+static pixval BG_RED, BG_GREEN, BG_BLUE;
+
+
+struct cmdlineInfo {
+  /* All the information the user supplied in the command line,
+     in a form easy for the program to use.
+  */
+  unsigned int left, right, top, bottom;
+  unsigned int width, height, var;
+  const char *bg_rgb;
+  const char *fg_rgb;
+  unsigned init;
+  unsigned int verbose;
+};
+
+
+static void
+/*-------------------------------------------------------------------------- */
+   parseCommandLine(int argc, char ** argv, struct cmdlineInfo *cmdlineP)
+/*-------------------------------------------------------------------------- */
+{
+  optEntry *option_def = malloc(100*sizeof(optEntry));
+    /* Instructions to OptParseOptions2 on how to parse our options.    */
+  optStruct3 opt;
+
+  unsigned int option_def_index;
+
+  option_def_index = 0;   /* incremented by OPTENTRY */
+  OPTENT3(0, "width",   OPT_UINT,   &cmdlineP->width,   NULL, 0);
+  OPTENT3(0, "height",  OPT_UINT,   &cmdlineP->height,  NULL, 0);
+  OPTENT3(0, "left",    OPT_UINT,   &cmdlineP->left,    NULL, 0);
+  OPTENT3(0, "right",   OPT_UINT,   &cmdlineP->right,   NULL, 0);
+  OPTENT3(0, "top",     OPT_UINT,   &cmdlineP->top,     NULL, 0);
+  OPTENT3(0, "bottom",  OPT_UINT,   &cmdlineP->bottom,  NULL, 0);
+  OPTENT3(0, "bg",      OPT_STRING, &cmdlineP->bg_rgb,  NULL, 0);
+  OPTENT3(0, "fg",      OPT_STRING, &cmdlineP->fg_rgb,  NULL, 0);
+  OPTENT3(0, "var",     OPT_UINT,   &cmdlineP->var,     NULL, 0);
+  OPTENT3(0, "init",    OPT_UINT,   &cmdlineP->init,    NULL, 0);
+  OPTENT3(0, "verbose", OPT_FLAG,   NULL, &cmdlineP->verbose, 0);
+
+  /* Set the defaults */
+  cmdlineP->width = 100;
+  cmdlineP->height = 100;
+  cmdlineP->left = cmdlineP->right = cmdlineP->top = cmdlineP->bottom = -1;
+  cmdlineP->bg_rgb = NULL;
+  cmdlineP->fg_rgb = NULL;
+  cmdlineP->var = 10;
+
+  opt.opt_table = option_def;
+  opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+  opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+  optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+
+  if (argc-1 != 0)
+    pm_error("There are no arguments.  You specified %d.", argc-1);
+}
+
+static void
+/* ----------------------------------------- */
+   proc_left(int const r1, int const r2, int const c1, int const c2, 
+             unsigned int const var)
+/* ----------------------------------------- */
+{
+  int cm, rm, c;
+
+  if (r1 + 1 == r2)  return;
+  rm = (r1 + r2) >> 1;
+  cm = (c1 + c2) >> 1;
+  cm += (int)floor(((float)rand() / RAND_MAX - 0.5) * var + 0.5);
+
+  for (c = 0; c < cm; c++)
+    PPM_ASSIGN(PIX[rm][c], BG_RED, BG_GREEN, BG_BLUE);
+
+  proc_left(r1, rm, c1, cm, var);
+  proc_left(rm, r2, cm, c2, var);
+}
+
+static void
+/* ------------------------------------------ */
+   proc_right(int const r1, int const r2, int const c1, int const c2,
+              unsigned int const width, unsigned int const var)
+/* ------------------------------------------ */
+{
+  int cm, rm, c;
+
+  if (r1 + 1 == r2)  return;
+  rm = (r1 + r2) >> 1;
+  cm = (c1 + c2) >> 1;
+  cm += (int)floor(((float)rand() / RAND_MAX - 0.5) * var + 0.5);
+
+  for (c = cm; c < width; c++)
+    PPM_ASSIGN(PIX[rm][c], BG_RED, BG_GREEN, BG_BLUE);
+
+  proc_right(r1, rm, c1, cm, width, var);
+  proc_right(rm, r2, cm, c2, width, var);
+}
+
+static void
+/* ---------------------------------------- */
+   proc_top(int const c1, int const c2, int const r1, int const r2,
+            unsigned int const var)
+/* ---------------------------------------- */
+{
+  int rm, cm, r;
+
+  if (c1 + 1 == c2)  return;
+  cm = (c1 + c2) >> 1;
+  rm = (r1 + r2) >> 1;
+  rm += (int)floor(((float)rand() / RAND_MAX - 0.5) * var + 0.5);
+
+  for (r = 0; r < rm; r++)
+    PPM_ASSIGN(PIX[r][cm], BG_RED, BG_GREEN, BG_BLUE);
+
+  proc_top(c1, cm, r1, rm, var);
+  proc_top(cm, c2, rm, r2, var);
+}
+
+static void
+/* ------------------------------------------- */
+   proc_bottom(int const c1, int const c2, int const r1, int const r2,
+               unsigned int const height, unsigned int const var)
+/* ------------------------------------------- */
+{
+  int rm, cm, r;
+
+  if (c1 + 1 == c2)  return;
+  cm = (c1 + c2) >> 1;
+  rm = (r1 + r2) >> 1;
+  rm += (int)floor(((float)rand() / RAND_MAX - 0.5) * var + 0.5);
+
+  for (r = rm; r < height; r++)
+    PPM_ASSIGN(PIX[r][cm], BG_RED, BG_GREEN, BG_BLUE);
+
+  proc_bottom(c1, cm, r1, rm, height, var);
+  proc_bottom(cm, c2, rm, r2, height, var);
+}
+
+
+int
+/* ============================ */
+   main(int argc, char* argv[])
+/* ============================ */
+{
+  struct cmdlineInfo cmdline;
+  pixel bgcolor, fgcolor;
+  pixval fg_red, fg_green, fg_blue;
+  int rows, cols, row;
+  int left, right, top, bottom;
+  int left_r1, left_r2, left_c1, left_c2;
+  int right_r1, right_r2, right_c1, right_c2;
+  int top_r1, top_r2, top_c1, top_c2;
+  int bottom_r1, bottom_r2, bottom_c1, bottom_c2;
+  unsigned init;
+  struct timeval tv;
+  int col;
+
+  ppm_init(&argc, argv);
+
+  parseCommandLine(argc, argv, &cmdline);
+
+  init = cmdline.init;
+  if (init == 0) {
+    gettimeofday(&tv, NULL);
+    srand((unsigned int)tv.tv_usec);
+  }
+  else
+    srand(init);
+
+  cols = cmdline.width;
+  rows = cmdline.height;
+  left = cmdline.left;
+  right = cmdline.right;
+  top = cmdline.top;
+  bottom = cmdline.bottom;
+
+  if (cmdline.bg_rgb)
+    bgcolor = ppm_parsecolor(cmdline.bg_rgb, PPM_MAXMAXVAL);
+  else
+    PPM_ASSIGN(bgcolor, 0, 0, 0);
+  BG_RED = PPM_GETR(bgcolor);
+  BG_GREEN = PPM_GETG(bgcolor);
+  BG_BLUE = PPM_GETB(bgcolor);
+
+  if (cmdline.fg_rgb)
+    fgcolor = ppm_parsecolor(cmdline.fg_rgb, PPM_MAXMAXVAL);
+  else
+    PPM_ASSIGN(fgcolor, PPM_MAXMAXVAL, PPM_MAXMAXVAL, PPM_MAXMAXVAL);
+  fg_red = PPM_GETR(fgcolor);
+  fg_green = PPM_GETG(fgcolor);
+  fg_blue = PPM_GETB(fgcolor);
+
+  if (cmdline.verbose) {
+    pm_message("width is %d, height is %d, variance is %d.", 
+               cols, rows, cmdline.var);
+    if (left >= 0) pm_message("ragged left border is required");
+    if (right >= 0) pm_message("ragged right border is required");
+    if (top >= 0) pm_message("ragged top border is required");
+    if (bottom >= 0) pm_message("ragged bottom border is required");
+    pm_message("background is %s", ppm_colorname(&bgcolor, PPM_MAXMAXVAL, 1));
+    pm_message("foreground is %s", ppm_colorname(&fgcolor, PPM_MAXMAXVAL, 1));
+    if (init >= 0) pm_message("srand() initialized with seed %u", init);
+  }
+
+  /* Allocate memory for the whole pixmap */
+  PIX = ppm_allocarray(cols, rows);
+
+  /* First, set all pixel to foreground color */
+  for (row = 0; row < rows; row++)
+    for (col = 0; col < cols; col++)
+      PPM_ASSIGN(PIX[row][col], fg_red, fg_green, fg_blue);
+
+  /* Make a ragged left border */
+  if (left >= 0) {
+    left_c1 = left_c2 = left;
+    left_r1 = 0;
+    left_r2 = rows - 1;
+    for (col = 0; col < left_c1; col++)
+      PPM_ASSIGN(PIX[left_r1][col], BG_RED, BG_GREEN, BG_BLUE);
+    for (col = 0; col < left_c2; col++)
+      PPM_ASSIGN(PIX[left_r2][col], BG_RED, BG_GREEN, BG_BLUE);
+
+    proc_left(left_r1, left_r2, left_c1, left_c2, cmdline.var);
+  }
+
+  /* Make a ragged right border */
+  if (right >= 0) {
+    right_c1 = right_c2 = cols - right - 1;
+    right_r1 = 0;
+    right_r2 = rows - 1;
+    for (col = right_c1; col < cols; col++)
+      PPM_ASSIGN(PIX[right_r1][col], BG_RED, BG_GREEN, BG_BLUE);
+    for (col = right_c2; col < cols; col++)
+      PPM_ASSIGN(PIX[right_r2][col], BG_RED, BG_GREEN, BG_BLUE);
+
+    proc_right(right_r1, right_r2, right_c1, right_c2, 
+               cmdline.width, cmdline.var);
+  }
+
+  /* Make a ragged top border */
+  if (top >= 0) {
+    top_r1 = top_r2 = top;
+    top_c1 = 0;
+    top_c2 = cols - 1;
+    for (row = 0; row < top_r1; row++)
+      PPM_ASSIGN(PIX[row][top_c1], BG_RED, BG_GREEN, BG_BLUE);
+    for (row = 0; row < top_r2; row++)
+      PPM_ASSIGN(PIX[row][top_c2], BG_RED, BG_GREEN, BG_BLUE);
+
+    proc_top(top_c1, top_c2, top_r1, top_r2, cmdline.var);
+  }
+
+  /* Make a ragged bottom border */
+  if (bottom >= 0) {
+    bottom_r1 = bottom_r2 = rows - bottom - 1;
+    bottom_c1 = 0;
+    bottom_c2 = cols - 1;
+    for (row = bottom_r1; row < rows; row++)
+      PPM_ASSIGN(PIX[row][bottom_c1], BG_RED, BG_GREEN, BG_BLUE);
+    for (row = bottom_r2; row < rows; row++)
+      PPM_ASSIGN(PIX[row][bottom_c2], BG_RED, BG_GREEN, BG_BLUE);
+
+    proc_bottom(bottom_c1, bottom_c2, bottom_r1, bottom_r2, 
+                cmdline.height, cmdline.var);
+  }
+
+  /* Write pixmap */
+  ppm_writeppm(stdout, PIX, cols, rows, PPM_MAXMAXVAL, 0);
+
+  ppm_freearray(PIX, rows);
+
+  pm_close(stdout);
+  exit(0);
+}
diff --git a/generator/ppmwheel.c b/generator/ppmwheel.c
new file mode 100644
index 00000000..ef5021f9
--- /dev/null
+++ b/generator/ppmwheel.c
@@ -0,0 +1,157 @@
+/* ppmwheel.c - create a color circle of a specified size
+**
+** This was adapted by Bryan Henderson in January 2003 from ppmcirc.c by
+** Peter Kirchgessner:
+**
+** Copyright (C) 1995 by Peter Kirchgessner.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <string.h>
+#include <math.h>
+
+#include "ppm.h"
+
+#ifndef PI
+#define PI  3.14159265358979323846
+#endif
+
+#ifndef ABS
+#define ABS(a) ((a) < 0 ? -(a) : (a))
+#endif
+
+static void 
+hsv_rgb(double const in_h, double const in_s, double const in_v, 
+        double * const r, double * const g, double * const b) {
+/*----------------------------------------------------------------------------
+   This is a stripped down hsv->rgb converter that works only for
+   Saturation of zero.
+-----------------------------------------------------------------------------*/
+    double h, s, v;
+
+    h = in_h < 0.0 ? 0.0 : in_h > 360.0 ? 360.0 : in_h;
+
+    v = in_v < 0.0 ? 0.0 : in_v > 1.0 ? 1.0 : in_v;
+
+    s = in_s < 0.0 ? 0.0 : in_s > 1.0 ? 1.0 : in_s;
+
+    if (s != 0.0)
+        pm_error("Internal error: non-zero saturation");
+
+    if (h <= 60.0) {          /* from red to yellow */
+        *r = 1.0;
+        *g = h / 60.0;
+        *b = 0.0;
+    } else if ( h <= 120.0 ) {   /* from yellow to green */
+        *r = 1.0 - (h - 60.0) / 60.0;
+        *g = 1.0;
+        *b = 0.0;
+    } else if ( h <= 180.0 ) {   /* from green to cyan */
+        *r = 0.0;
+        *g = 1.0;
+        *b = (h - 120.0) / 60.0;
+    } else if ( h <= 240.0 ) {    /* from cyan to blue */
+        *r = 0.0;
+        *g = 1.0 - (h - 180.0) / 60.0;
+        *b = 1.0;
+    } else if ( h <= 300.0) {    /* from blue to magenta */
+        *r = (h - 240.0) / 60.0;
+        *g = 0.0;
+        *b = 1.0;
+    } else {                      /* from magenta to red */
+        *r = 1.0;
+        *g = 0.0;
+        *b = 1.0 - (h - 300.0) / 60.0;
+    }
+
+    if ( v >= 0.5) {
+        v = 2.0 - 2.0 * v;
+        v = sqrt (v);
+        *r = 1.0 + v * (*r - 1.0);
+        *g = 1.0 + v * (*g - 1.0);
+        *b = 1.0 + v * (*b - 1.0);
+    } else {
+        v *= 2.0;
+        v = sqrt (sqrt ( sqrt (v)));
+        *r *= v;
+        *g *= v;
+        *b *= v;
+    }
+}
+
+
+int
+main(int argc, char *argv[]) {
+    pixel *orow;
+    int rows, cols;
+    pixval maxval;
+    unsigned int row;
+    unsigned int xcenter, ycenter, radius;
+    long diameter;
+    char * tailptr;
+
+    ppm_init( &argc, argv );
+
+    if (argc-1 != 1)
+        pm_error("Program takes one argument:  diameter of color wheel");
+
+    diameter = strtol(argv[1], &tailptr, 10);
+    if (strlen(argv[1]) == 0 || *tailptr != '\0')
+        pm_error("You specified an invalid diameter: '%s'", argv[1]);
+    if (diameter <= 0)
+        pm_error("Diameter must be positive.  You specified %ld.", diameter);
+    if (diameter < 4)
+        pm_error("Diameter must be at least 4.  You specified %ld", diameter);
+
+    cols = rows = diameter;
+    
+    orow = ppm_allocrow(cols);
+
+    maxval = PPM_MAXMAXVAL;
+    ppm_writeppminit(stdout, cols, rows, maxval, 0);
+
+    radius = diameter/2 - 1;
+
+    xcenter = cols / 2;
+    ycenter = rows / 2;
+
+    for (row = 0; row < rows; ++row) {
+        unsigned int col;
+        for (col = 0; col < cols; ++col) {
+            double const dx = (int)col - (int)xcenter;
+            double const dy = (int)row - (int)ycenter;
+            double const dist = sqrt(dx*dx + dy*dy);
+
+            pixval r, g, b;
+
+            if (dist > radius) {
+                r = g = b = maxval;
+            } else {
+                double hue, sat, val;
+                double dr, dg, db;
+
+                hue = atan2(dx, dy) / PI * 180.0;
+                if (hue < 0.0) 
+                    hue = 360.0 + hue;
+                sat = 0.0;
+                val = dist / radius;
+
+                hsv_rgb(hue, sat, val, &dr, &dg, &db);
+
+                r = (pixval)(maxval * dr);
+                g = (pixval)(maxval * dg);
+                b = (pixval)(maxval * db);
+            }
+            PPM_ASSIGN (orow[col], r, g, b );
+        }
+        ppm_writeppmrow(stdout, orow, cols, maxval, 0);
+    }
+    pm_close(stdout);
+    exit(0);
+}