about summary refs log tree commit diff
path: root/other
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 /other
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 'other')
-rw-r--r--other/Makefile75
-rw-r--r--other/pamarith.c503
-rw-r--r--other/pambayer.c297
-rw-r--r--other/pamchannel.c199
-rw-r--r--other/pamdepth.c175
-rw-r--r--other/pamendian.c70
-rw-r--r--other/pamlookup.c299
-rw-r--r--other/pampick.c250
-rw-r--r--other/pamsplit.c187
-rw-r--r--other/pamstack.c240
-rw-r--r--other/pamsummcol.c257
-rw-r--r--other/pamx/COPYRIGHT72
-rw-r--r--other/pamx/Makefile43
-rw-r--r--other/pamx/Makefile251
-rw-r--r--other/pamx/fill.c82
-rw-r--r--other/pamx/fill.h16
-rw-r--r--other/pamx/image.c331
-rw-r--r--other/pamx/image.h90
-rw-r--r--other/pamx/pamx.c364
-rw-r--r--other/pamx/send.c872
-rw-r--r--other/pamx/send.h38
-rw-r--r--other/pamx/valtomem.h65
-rw-r--r--other/pamx/window.c1209
-rw-r--r--other/pamx/window.h38
-rw-r--r--other/pamx/ximageinfo.h25
-rw-r--r--other/pnmcolormap.c973
-rw-r--r--other/ppmdcfont.c200
-rw-r--r--other/ppmddumpfont.c89
-rw-r--r--other/ppmdmkfont.c705
-rw-r--r--other/ppmsvgalib.c283
-rwxr-xr-xother/ppmtomap5
31 files changed, 8103 insertions, 0 deletions
diff --git a/other/Makefile b/other/Makefile
new file mode 100644
index 00000000..87f92f96
--- /dev/null
+++ b/other/Makefile
@@ -0,0 +1,75 @@
+ifeq ($(SRCDIR)x,x)
+  SRCDIR = $(CURDIR)/..
+  BUILDDIR = $(SRCDIR)
+endif
+SUBDIR = other
+VPATH=.:$(SRCDIR)/$(SUBDIR)
+
+include $(BUILDDIR)/Makefile.config
+
+SUBDIRS = pamx
+
+ifneq ($(LINUXSVGALIB),NONE)
+  ifneq ($(LINUXSVGAHDR_DIR),)
+    INCLUDES += -I$(LINUXSVGAHDR_DIR)
+  endif
+endif
+
+# 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 = pamarith pambayer pamchannel pamdepth \
+	pamendian pamlookup pampick pamsplit \
+	pamstack pamsummcol pnmcolormap \
+	ppmdcfont ppmddumpfont ppmdmkfont 
+
+BINARIES = $(PORTBINARIES)
+
+ifneq ($(LINUXSVGALIB),NONE)
+  BINARIES += ppmsvgalib
+endif
+
+SCRIPTS = ppmtomap
+
+OBJECTS = $(BINARIES:%=%.o)
+
+# 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.
+
+MERGEBINARIES = $(BINARIES)
+MERGE_OBJECTS = $(MERGEBINARIES:%=%.o2)
+
+.PHONY: all
+all: $(BINARIES) $(SUBDIRS:%=%/all)
+
+include $(SRCDIR)/Makefile.common
+
+ppmsvgalib: %: %.o $(NETPBMLIB) $(LIBOPT)
+	$(LD) $(LDFLAGS) -o $@ $< \
+	  $(shell $(LIBOPT) $(NETPBMLIB) $(LINUXSVGALIB)) \
+	  $(MATHLIB) $(LDLIBS) \
+	  $(LADD) 
+
+install.bin: install.bin.local
+.PHONY: install.bin.local
+install.bin.local: $(PKGDIR)/bin
+# Remember that $(SYMLINK) might just be a copy command.
+# In July 2002, pamarith replaced pnmarith
+	cd $(PKGDIR)/bin ; \
+	  rm -f pnmarith ; \
+	  $(SYMLINK) pamarith$(EXE) pnmarith
+# In December 2005, pamsplit replaced pnmsplit
+	cd $(PKGDIR)/bin ; \
+	  rm -f pnmsplit ; \
+	  $(SYMLINK) pamsplit$(EXE) pnmsplit
+# In February 2006, pamdepth replaced pnmdepth
+	cd $(PKGDIR)/bin ; \
+	  rm -f pnmdepth ; \
+	  $(SYMLINK) pamdepth$(EXE) pnmdepth
+
+FORCE:
diff --git a/other/pamarith.c b/other/pamarith.c
new file mode 100644
index 00000000..c1e7f1ed
--- /dev/null
+++ b/other/pamarith.c
@@ -0,0 +1,503 @@
+#include <assert.h>
+#include <string.h>
+
+#include "shhopt.h"
+#include "pam.h"
+
+enum function {FN_ADD, FN_SUBTRACT, FN_MULTIPLY, FN_DIVIDE, FN_DIFFERENCE,
+               FN_MINIMUM, FN_MAXIMUM, FN_MEAN, FN_COMPARE,
+               FN_AND, FN_OR, FN_NAND, FN_NOR, FN_XOR,
+               FN_SHIFTLEFT, FN_SHIFTRIGHT
+              };
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *input1Filespec;  
+    const char *input2Filespec;  
+    enum function function;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** const argv,
+                 struct cmdlineInfo * const 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;
+    
+    unsigned int addSpec, subtractSpec, multiplySpec, divideSpec,
+        differenceSpec,
+        minimumSpec, maximumSpec, meanSpec, compareSpec,
+        andSpec, orSpec, nandSpec, norSpec, xorSpec,
+        shiftleftSpec, shiftrightSpec;
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0, "add",         OPT_FLAG,   NULL, &addSpec,        0);
+    OPTENT3(0, "subtract",    OPT_FLAG,   NULL, &subtractSpec,   0);
+    OPTENT3(0, "multiply",    OPT_FLAG,   NULL, &multiplySpec,   0);
+    OPTENT3(0, "divide",      OPT_FLAG,   NULL, &divideSpec,     0);
+    OPTENT3(0, "difference",  OPT_FLAG,   NULL, &differenceSpec, 0);
+    OPTENT3(0, "minimum",     OPT_FLAG,   NULL, &minimumSpec,    0);
+    OPTENT3(0, "maximum",     OPT_FLAG,   NULL, &maximumSpec,    0);
+    OPTENT3(0, "mean",        OPT_FLAG,   NULL, &meanSpec,       0);
+    OPTENT3(0, "compare",     OPT_FLAG,   NULL, &compareSpec,    0);
+    OPTENT3(0, "and",         OPT_FLAG,   NULL, &andSpec,        0);
+    OPTENT3(0, "or",          OPT_FLAG,   NULL, &orSpec,         0);
+    OPTENT3(0, "nand",        OPT_FLAG,   NULL, &nandSpec,       0);
+    OPTENT3(0, "nor",         OPT_FLAG,   NULL, &norSpec,        0);
+    OPTENT3(0, "xor",         OPT_FLAG,   NULL, &xorSpec,        0);
+    OPTENT3(0, "shiftleft",   OPT_FLAG,   NULL, &shiftleftSpec,  0);
+    OPTENT3(0, "shiftright",  OPT_FLAG,   NULL, &shiftrightSpec, 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 (addSpec + subtractSpec + multiplySpec + divideSpec + differenceSpec +
+        minimumSpec + maximumSpec + meanSpec + compareSpec +
+        andSpec + orSpec + nandSpec + norSpec + xorSpec +
+        shiftleftSpec + shiftrightSpec > 1)
+        pm_error("You may specify only one function");
+
+    if (argc-1 != 2)
+        pm_error("You must specify two arguments:  the files which are "
+                 "the operands of the "
+                 "dyadic function.  You specified %d", argc-1);
+    else {
+        cmdlineP->input1Filespec = argv[1];
+        cmdlineP->input2Filespec = argv[2];
+    }
+
+    if (addSpec)
+        cmdlineP->function = FN_ADD;
+    else if (subtractSpec)
+        cmdlineP->function = FN_SUBTRACT;
+    else if (multiplySpec)
+        cmdlineP->function = FN_MULTIPLY;
+    else if (divideSpec)
+        cmdlineP->function = FN_DIVIDE;
+    else if (differenceSpec)
+        cmdlineP->function = FN_DIFFERENCE;
+    else if (minimumSpec)
+        cmdlineP->function = FN_MINIMUM;
+    else if (maximumSpec)
+        cmdlineP->function = FN_MAXIMUM;
+    else if (meanSpec)
+        cmdlineP->function = FN_MEAN;
+    else if (compareSpec)
+        cmdlineP->function = FN_COMPARE;
+    else if (andSpec)
+        cmdlineP->function = FN_AND;
+    else if (orSpec)
+        cmdlineP->function = FN_OR;
+    else if (nandSpec)
+        cmdlineP->function = FN_NAND;
+    else if (norSpec)
+        cmdlineP->function = FN_NOR;
+    else if (xorSpec)
+        cmdlineP->function = FN_XOR;
+    else if (shiftleftSpec)
+        cmdlineP->function = FN_SHIFTLEFT;
+    else if (shiftrightSpec)
+        cmdlineP->function = FN_SHIFTRIGHT;
+    else
+        pm_error("You must specify a function (e.g. '-add')");
+}        
+
+
+
+enum category {
+    CATEGORY_FRACTIONAL_ARITH,
+        /* Arithmetic in which each sample represents a the fraction
+           sample/maxval.
+        */
+    CATEGORY_BITSTRING,
+        /* And, Or, etc.  Maxval isn't a scale factor at all; it's a mask. */
+    CATEGORY_SHIFT
+        /* Left argument is a bit string, but right argument is a whole
+           number (left maxval is a mask; right maxval is meaningless).
+        */
+};
+
+
+
+static enum category
+functionCategory(enum function const function) {
+
+    enum category retval;
+    
+    switch (function) {
+    case FN_ADD:
+    case FN_SUBTRACT:
+    case FN_DIFFERENCE:
+    case FN_MINIMUM:
+    case FN_MAXIMUM:
+    case FN_MEAN:
+    case FN_COMPARE:
+    case FN_MULTIPLY:
+    case FN_DIVIDE:
+        retval = CATEGORY_FRACTIONAL_ARITH;
+        break;
+    case FN_AND:
+    case FN_OR:
+    case FN_NAND:
+    case FN_NOR:
+    case FN_XOR:
+        retval = CATEGORY_BITSTRING;
+        break;
+    case FN_SHIFTLEFT:
+    case FN_SHIFTRIGHT:
+        retval = CATEGORY_SHIFT;
+        break;
+    }
+    return retval;
+}
+
+
+
+static void
+computeOutputType(struct pam *  const outpamP,
+                  struct pam    const inpam1,
+                  struct pam    const inpam2,
+                  enum function const function) {
+
+    outpamP->size        = sizeof(struct pam);
+    outpamP->len         = PAM_STRUCT_SIZE(tuple_type);
+    outpamP->file        = stdout;
+    outpamP->format      = MAX(inpam1.format, inpam2.format);
+    outpamP->plainformat = FALSE;
+    outpamP->height      = inpam1.height;
+    outpamP->width       = inpam1.width;
+    outpamP->depth       = MAX(inpam1.depth, inpam2.depth);
+
+    switch (functionCategory(function)) {    
+    case CATEGORY_FRACTIONAL_ARITH:
+        if (function == FN_COMPARE)
+            outpamP->maxval = 2;
+        else
+            outpamP->maxval = MAX(inpam1.maxval, inpam2.maxval);
+        break;
+    case CATEGORY_BITSTRING:
+        if (inpam2.maxval != inpam1.maxval)
+            pm_error("For a bit string operation, the maxvals of the "
+                     "left and right image must be the same.  You have "
+                     "left=%u and right=%u", 
+                     (unsigned)inpam1.maxval, (unsigned)inpam2.maxval);
+
+        if (pm_bitstomaxval(pm_maxvaltobits(inpam1.maxval)) != inpam1.maxval)
+            pm_error("For a bit string operation, the maxvals of the inputs "
+                     "must be a full binary count, i.e. a power of two "
+                     "minus one such as 0xff.  You have 0x%x",
+                     (unsigned)inpam1.maxval);
+
+        outpamP->maxval = inpam1.maxval;
+        break;
+    case CATEGORY_SHIFT:
+        if (pm_bitstomaxval(pm_maxvaltobits(inpam1.maxval)) != inpam1.maxval)
+            pm_error("For a bit shift operation, the maxval of the left "
+                     "input image "
+                     "must be a full binary count, i.e. a power of two "
+                     "minus one such as 0xff.  You have 0x%x",
+                     (unsigned)inpam1.maxval);
+        outpamP->maxval = inpam1.maxval;
+    }
+    outpamP->bytes_per_sample = (pm_maxvaltobits(outpamP->maxval)+7)/8;
+    strcpy(outpamP->tuple_type, inpam1.tuple_type);
+}
+
+
+
+static samplen
+applyNormalizedFunction(enum function const function,
+                        samplen       const leftArg,
+                        samplen       const rightArg) {
+
+    samplen result;
+
+    switch (function) {
+    case FN_ADD:
+        result = MIN(1., leftArg + rightArg);
+        break;
+    case FN_SUBTRACT:
+        result = MAX(0., leftArg - rightArg);
+        break;
+    case FN_MULTIPLY:
+        result = leftArg * rightArg;
+        break;
+    case FN_DIVIDE:
+        result = (rightArg > leftArg) ?
+        leftArg / rightArg : 1.;
+        break;
+    case FN_DIFFERENCE:
+        result = leftArg > rightArg ? 
+            leftArg - rightArg : rightArg - leftArg;
+        break;
+    case FN_MINIMUM:
+        result = MIN(leftArg, rightArg);
+        break;
+    case FN_MAXIMUM:
+        result = MAX(leftArg, rightArg);
+        break;
+    case FN_MEAN:
+        result = (leftArg + rightArg) / 2.0;
+        break;
+    case FN_COMPARE:
+        result = 
+            leftArg > rightArg ? 1. : leftArg < rightArg ? 0. : .5;
+        break;
+    default:
+        pm_error("Internal error.  applyNormalizedFunction() called "
+                 "for a function it doesn't know how to do: %u", function);
+    }
+
+    return result;
+}
+
+
+
+static void
+doNormalizedArith(struct pam *  const inpam1P,
+                  struct pam *  const inpam2P,
+                  struct pam *  const outpamP,
+                  enum function const function) {
+
+    tuplen * tuplerown1;
+    tuplen * tuplerown2;
+    tuplen * tuplerownOut;
+    unsigned int row;
+
+    tuplerown1   = pnm_allocpamrown(inpam1P);
+    tuplerown2   = pnm_allocpamrown(inpam2P);
+    tuplerownOut = pnm_allocpamrown(outpamP);
+
+    for (row = 0; row < outpamP->height; ++row) {
+        unsigned int col;
+        pnm_readpamrown(inpam1P, tuplerown1);
+        pnm_readpamrown(inpam2P, tuplerown2);
+        
+        for (col = 0; col < outpamP->width; ++col) {
+            unsigned int outplane;
+            
+            for (outplane = 0; outplane < outpamP->depth; ++outplane) {
+                unsigned int const plane1 = MIN(outplane, inpam1P->depth-1);
+                unsigned int const plane2 = MIN(outplane, inpam2P->depth-1);
+
+                tuplerownOut[col][outplane] = 
+                    applyNormalizedFunction(function, 
+                                            tuplerown1[col][plane1], 
+                                            tuplerown2[col][plane2]);
+                assert(tuplerownOut[col][outplane] >= 0.);
+                assert(tuplerownOut[col][outplane] <= 1.);
+
+            }
+        }
+        pnm_writepamrown(outpamP, tuplerownOut);
+    }
+
+    pnm_freepamrown(tuplerown1);
+    pnm_freepamrown(tuplerown2);
+    pnm_freepamrown(tuplerownOut);
+}
+
+
+
+static sample
+applyUnNormalizedFunction(enum function const function,
+                          sample        const leftArg,
+                          sample        const rightArg,
+                          sample        const maxval) {
+/*----------------------------------------------------------------------------
+   Apply dyadic function 'function' to the arguments 'leftArg' and
+   'rightArg', assuming both are based on the same maxval 'maxval'.
+   Return a value which is also a fraction of 'maxval'.
+
+   Exception: for the shift operations, 'rightArg' is not based on any
+   maxval.  It is an absolute bit count.
+-----------------------------------------------------------------------------*/
+    sample result;
+
+    switch (function) {
+    case FN_ADD:
+        result = MIN(maxval, leftArg + rightArg);
+        break;
+    case FN_SUBTRACT:
+        result = MAX(0, (int)leftArg - (int)rightArg);
+        break;
+    case FN_DIFFERENCE:
+        result = leftArg > rightArg ? leftArg - rightArg : rightArg - leftArg;
+        break;
+    case FN_MINIMUM:
+        result = MIN(leftArg, rightArg);
+        break;
+    case FN_MAXIMUM:
+        result = MAX(leftArg, rightArg);
+        break;
+    case FN_MEAN:
+        result = (leftArg + rightArg + 1) / 2;
+        break;
+    case FN_COMPARE:
+        result = leftArg > rightArg ? 2 : leftArg < rightArg ? 0 : 1;
+        break;
+    case FN_MULTIPLY:
+        result = (leftArg * rightArg + maxval/2) / maxval;
+        break;
+    case FN_DIVIDE:
+        result = (rightArg > leftArg) ?
+            (leftArg * maxval + rightArg/2) / rightArg : maxval;
+        break;
+
+    case FN_AND:
+        result = leftArg & rightArg;
+        break;
+    case FN_OR:
+        result = leftArg | rightArg;
+        break;
+    case FN_NAND:
+        result = ~(leftArg & rightArg) & maxval;
+        break;
+    case FN_NOR:
+        result = ~(leftArg | rightArg) & maxval;
+        break;
+    case FN_XOR:
+        result = leftArg ^ rightArg;
+        break;
+    case FN_SHIFTLEFT:
+        result = (leftArg << rightArg) & maxval;
+        break;
+    case FN_SHIFTRIGHT:
+        result = leftArg >> rightArg;
+        break;
+    default:
+        pm_error("Internal error.  applyUnNormalizedFunction() called "
+                 "for a function it doesn't know how to do: %u", function);
+    }
+
+    return result;
+}
+
+
+
+static void
+doUnNormalizedArith(struct pam *  const inpam1P,
+                    struct pam *  const inpam2P,
+                    struct pam *  const outpamP,
+                    enum function const function) {
+/*----------------------------------------------------------------------------
+   Take advantage of the fact that both inputs and the output use the same
+   maxval to do the computation without time-consuming normalization of
+   sample values.
+-----------------------------------------------------------------------------*/
+    sample const maxval = outpamP->maxval;
+
+    tuple * tuplerow1;
+    tuple * tuplerow2;
+    tuple * tuplerowOut;
+    unsigned int row;
+
+    /* Input conditions: */
+    assert(inpam1P->maxval == maxval);
+    assert(inpam2P->maxval == maxval);
+    assert(outpamP->maxval == maxval);
+
+    tuplerow1   = pnm_allocpamrow(inpam1P);
+    tuplerow2   = pnm_allocpamrow(inpam2P);
+    tuplerowOut = pnm_allocpamrow(outpamP);
+
+    for (row = 0; row < outpamP->height; ++row) {
+        unsigned int col;
+        pnm_readpamrow(inpam1P, tuplerow1);
+        pnm_readpamrow(inpam2P, tuplerow2);
+        
+        for (col = 0; col < outpamP->width; ++col) {
+            unsigned int outplane;
+            
+            for (outplane = 0; outplane < outpamP->depth; ++outplane) {
+                unsigned int const plane1 = MIN(outplane, inpam1P->depth-1);
+                unsigned int const plane2 = MIN(outplane, inpam2P->depth-1);
+
+                tuplerowOut[col][outplane] = 
+                    applyUnNormalizedFunction(function, 
+                                              tuplerow1[col][plane1], 
+                                              tuplerow2[col][plane2],
+                                              maxval);
+
+                assert(tuplerowOut[col][outplane] >= 0);
+                assert(tuplerowOut[col][outplane] <= outpamP->maxval);
+            }
+        }
+        pnm_writepamrow(outpamP, tuplerowOut);
+    }
+
+    pnm_freepamrow(tuplerow1);
+    pnm_freepamrow(tuplerow2);
+    pnm_freepamrow(tuplerowOut);
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+    struct pam inpam1;
+    struct pam inpam2;
+    struct pam outpam;
+    FILE * if1P;
+    FILE * if2P;
+    
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    if1P = pm_openr(cmdline.input1Filespec);
+    if2P = pm_openr(cmdline.input2Filespec);
+
+    pnm_readpaminit(if1P, &inpam1, PAM_STRUCT_SIZE(tuple_type));
+    pnm_readpaminit(if2P, &inpam2, PAM_STRUCT_SIZE(tuple_type));
+
+    if (inpam1.width != inpam2.width || inpam1.height != inpam2.height)
+        pm_error("The two images must be the same width and height.  "
+                 "The first is %ux%ux%u, but the second is %ux%ux%u",
+                 inpam1.width, inpam1.height, inpam1.depth,
+                 inpam2.width, inpam2.height, inpam2.depth);
+
+    if (inpam1.depth != 1 && inpam2.depth != 1 && inpam1.depth != inpam2.depth)
+        pm_error("The two images must have the same depth or one of them "
+                 "must have depth 1.  The first has depth %u and the second "
+                 "has depth %u", inpam1.depth, inpam2.depth);
+
+    computeOutputType(&outpam, inpam1, inpam2, cmdline.function);
+
+    pnm_writepaminit(&outpam);
+
+    switch (functionCategory(cmdline.function)) {    
+    case CATEGORY_FRACTIONAL_ARITH:
+        if (inpam1.maxval == inpam2.maxval)
+            doUnNormalizedArith(&inpam1, &inpam2, &outpam, cmdline.function);
+        else
+            doNormalizedArith(&inpam1, &inpam2, &outpam, cmdline.function);
+        break;
+    case CATEGORY_BITSTRING:
+    case CATEGORY_SHIFT:
+        doUnNormalizedArith(&inpam1, &inpam2, &outpam, cmdline.function);
+        break;
+    }
+
+    pm_close(if1P);
+    pm_close(if2P);
+    
+    return 0;
+}
diff --git a/other/pambayer.c b/other/pambayer.c
new file mode 100644
index 00000000..aa4beef1
--- /dev/null
+++ b/other/pambayer.c
@@ -0,0 +1,297 @@
+/*
+
+  Bayer matrix conversion tool
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published
+  by the Free Software Foundation; either version 2 of the License,
+  or (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
+  USA
+
+  Copyright Alexandre Becoulet <diaxen AT free DOT fr>
+  
+  Completely rewritten for Netpbm by Bryan Henderson August 2005.
+*/
+
+#include <unistd.h>
+#include <stdio.h>
+
+#include "pam.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+#include "nstring.h"
+
+
+enum bayerType {
+    BAYER1,
+    BAYER2,
+    BAYER3,
+    BAYER4
+};
+
+struct cmdlineInfo {
+    const char * inputFilespec;
+    enum bayerType bayerType;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const 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 typeSpec;
+    unsigned int type;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "type",     OPT_UINT, &type,
+            &typeSpec, 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 (argc-1 < 1)
+        cmdlineP->inputFilespec = "-";
+    else if (argc-1 > 1)
+        pm_error("There is at most one argument -- the input file.  "
+                 "You specified %u", argc-1);
+
+    if (!typeSpec)
+        pm_error("You must specify the -type option");
+    else {
+        switch (type) {
+        case 1: cmdlineP->bayerType = BAYER1; break;
+        case 2: cmdlineP->bayerType = BAYER2; break;
+        case 3: cmdlineP->bayerType = BAYER3; break;
+        case 4: cmdlineP->bayerType = BAYER4; break;
+        }
+    }
+}
+
+
+
+static void
+calc_4(const struct pam * const pamP,
+       tuple **           const intuples,
+       tuple **           const outtuples,
+       unsigned int       const plane,
+       unsigned int       const xoffset,
+       unsigned int       const yoffset) {
+/*----------------------------------------------------------------------------
+    X . X
+    . . .
+    X . X
+-----------------------------------------------------------------------------*/
+    unsigned int y;
+    
+    for (y = yoffset; y < pamP->height; y += 2) {
+        unsigned int x;
+        for (x = xoffset; x + 2 < pamP->width; x += 2) {
+            outtuples[y][x][plane] = intuples[y][x][0];
+            outtuples[y][x + 1][plane] =
+                (intuples[y][x][0] + intuples[y][x + 2][0]) / 2;
+        }
+    }
+    for (y = yoffset; y + 2 < pamP->height; y += 2) {
+        unsigned int x;
+        for (x = xoffset; x < pamP->width; ++x)
+            outtuples[y + 1][x][plane] =
+                (outtuples[y][x][plane] + outtuples[y + 2][x][plane]) / 2;
+    }
+}
+
+
+
+static void
+calc_5(const struct pam * const pamP,
+       tuple **           const intuples,
+       tuple **           const outtuples,
+       unsigned int       const plane,
+       unsigned int       const xoffset,
+       unsigned int       const yoffset) {
+/*----------------------------------------------------------------------------
+   . X .
+   X . X
+   . X .
+-----------------------------------------------------------------------------*/
+    unsigned int y;
+    unsigned int j;
+
+    j = 0;  /* initial value */
+
+    for (y = yoffset; y + 2 < pamP->height; ++y) {
+        unsigned int x;
+        for (x = xoffset + j; x + 2 < pamP->width; x += 2) {
+            outtuples[y][x + 1][plane] = intuples[y][x + 1][0];
+            outtuples[y + 1][x + 1][plane] = 
+                (intuples[y][x + 1][0] + intuples[y + 1][x][0] +
+                 intuples[y + 2][x + 1][0] + intuples[y + 1][x + 2][0]) / 4;
+        }
+        j = 1 - j;
+    }
+}
+
+
+
+struct compAction {
+    unsigned int xoffset;
+    unsigned int yoffset;
+    void (*calc)(const struct pam * const pamP,
+                 tuple **           const intuples,
+                 tuple **           const outtuples,
+                 unsigned int       const plane,
+                 unsigned int       const xoffset,
+                 unsigned int       const yoffset);
+};
+
+
+
+static struct compAction const comp_1[3] = {
+/*----------------------------------------------------------------------------
+  G B G B
+  R G R G
+  G B G B
+  R G R G
+-----------------------------------------------------------------------------*/
+
+    { 0, 1, calc_4 },
+    { 0, 1, calc_5 },
+    { 1, 0, calc_4 }
+};
+
+static struct compAction const comp_2[3] = {
+/*----------------------------------------------------------------------------
+  R G R G
+  G B G B
+  R G R G
+  G B G B
+-----------------------------------------------------------------------------*/
+    { 0, 0, calc_4 },
+    { 0, 0, calc_5 },
+    { 1, 1, calc_4 }
+};
+
+static struct compAction const comp_3[3] = {
+/*----------------------------------------------------------------------------
+  B G B G
+  G R G R
+  B G B G
+  G R G R
+-----------------------------------------------------------------------------*/
+    { 1, 1, calc_4 },
+    { 0, 0, calc_5 },
+    { 0, 0, calc_4 }
+};
+
+static struct compAction const comp_4[3] = {
+/*----------------------------------------------------------------------------
+  G R G R
+  B G B G
+  G R G R
+  B G B G
+-----------------------------------------------------------------------------*/
+    { 1, 0, calc_4 },
+    { 0, 1, calc_5 },
+    { 0, 1, calc_4 }
+};
+
+
+
+static void
+makeOutputPam(const struct pam * const inpamP,
+              struct pam *       const outpamP) {
+
+    outpamP->size   = sizeof(*outpamP);
+    outpamP->len    = PAM_STRUCT_SIZE(tuple_type);
+    outpamP->file   = stdout;
+    outpamP->format = PAM_FORMAT;
+    outpamP->plainformat = 0;
+    outpamP->width  = inpamP->width;
+    outpamP->height = inpamP->height;
+    outpamP->depth  = 3;
+    outpamP->maxval = inpamP->maxval;
+    outpamP->bytes_per_sample = inpamP->bytes_per_sample;
+    STRSCPY(outpamP->tuple_type, "RGB");
+}
+
+
+
+static const struct compAction *
+actionTableForType(enum bayerType const bayerType) {
+
+    const struct compAction * retval;
+
+    switch (bayerType) {
+    case BAYER1: retval = comp_1; break;
+    case BAYER2: retval = comp_2; break;
+    case BAYER3: retval = comp_3; break;
+    case BAYER4: retval = comp_4; break;
+    }
+    return retval;
+}
+
+
+
+int
+main(int argc, char **argv) {
+
+    struct cmdlineInfo cmdline;
+    FILE * ifP;
+    struct pam inpam;
+    struct pam outpam;
+    tuple ** intuples;
+    tuple ** outtuples;
+    const struct compAction * compActionTable;
+    unsigned int plane;
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+    
+    ifP = pm_openr(cmdline.inputFilespec);
+    
+    intuples = pnm_readpam(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
+
+    compActionTable = actionTableForType(cmdline.bayerType);
+
+    makeOutputPam(&inpam, &outpam);
+
+    outtuples = pnm_allocpamarray(&outpam);
+
+    for (plane = 0; plane < 3; ++plane) {
+        struct compAction const compAction = compActionTable[plane];
+
+        compAction.calc(&inpam, intuples, outtuples, plane,
+                        compAction.xoffset, compAction.yoffset);
+    }
+    pnm_writepam(&outpam, outtuples);
+
+    pnm_freepamarray(outtuples, &outpam);
+    pnm_freepamarray(intuples, &inpam);
+
+    return 0;
+}
diff --git a/other/pamchannel.c b/other/pamchannel.c
new file mode 100644
index 00000000..ac7bae65
--- /dev/null
+++ b/other/pamchannel.c
@@ -0,0 +1,199 @@
+/*----------------------------------------------------------------------------
+                               pamchannel
+------------------------------------------------------------------------------
+  Part of the Netpbm package.
+
+  Extract specified channels (planes) from a PAM image
+
+
+  By Bryan Henderson, San Jose CA 2000.08.05
+
+  Contributed to the public domain by its author 2000.08.05.
+-----------------------------------------------------------------------------*/
+
+#include <string.h>
+
+#include "pam.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+
+#define MAX_CHANNELS 16
+    /* The most channels we allow user to specify */
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char * inputFileName;  /* Filename of input files */
+    const char * tupletype;      /* Tuple type for output PAM */
+    int n_channel;
+        /* The number of channels to extract.  At least 1, at most 16. */
+    unsigned int channel_to_extract[MAX_CHANNELS];
+        /* The channel numbers to extract, in order. */
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const 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;
+    extern struct pam pam;  /* Just so we can look at field sizes */
+
+    unsigned int option_def_index;
+    unsigned int infileSpec, tupletypeSpec;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "infile",     OPT_STRING, &cmdlineP->inputFileName, 
+            &infileSpec, 0);
+    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 (!infileSpec)
+        cmdlineP->inputFileName = "-";
+
+    if (!tupletypeSpec)
+        cmdlineP->tupletype = "";
+    else
+        if (strlen(cmdlineP->tupletype)+1 > sizeof(pam.tuple_type))
+            pm_error("Tuple type name specified is too long.  Maximum of "
+                     "%u characters allowed.", sizeof(pam.tuple_type));
+
+    cmdlineP->n_channel = 0;  /* initial value */
+    { 
+        int argn;
+        for (argn = 1; argn < argc; argn++) {
+            int n;
+            char *endptr;
+
+            if (cmdlineP->n_channel >= MAX_CHANNELS) 
+                pm_error("You may not specify more than %d channels.",
+                         MAX_CHANNELS);
+            
+            n = strtol(argv[argn], &endptr, 10);
+            if (n < 0)
+                pm_error("Channel numbers cannot be negative.  "
+                         "You specified %d", n);
+            if (endptr == NULL)
+                pm_error("non-numeric channel number argument: '%s'",
+                         argv[argn]);
+            
+            cmdlineP->channel_to_extract[cmdlineP->n_channel++] = n;
+        }
+    }
+    if (cmdlineP->n_channel < 1)
+        pm_error("You must specify at least one channel to extract.");
+}
+
+
+
+static void
+validateChannels(int          const n_channel, 
+                 unsigned int const channels[], 
+                 int          const depth) {
+
+    int i;
+
+    for (i = 0; i < n_channel; i++) 
+        if (channels[i] > depth-1)
+            pm_error("You specified channel number %d.  The highest numbered\n"
+                     "channel in the input image is %d.",
+                     channels[i], depth-1);
+}
+
+
+
+static void
+doOneImage(FILE *       const ifP,
+           FILE *       const ofP,
+           unsigned int const nChannel,
+           unsigned int const channelToExtract[],
+           const char * const tupletype) {
+
+    struct pam inpam;   /* Input PAM image */
+    struct pam outpam;  /* Output PAM image */
+
+    pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
+
+    validateChannels(nChannel, channelToExtract, inpam.depth);
+
+    outpam = inpam;     /* Initial value */
+    outpam.file   = ofP;
+    outpam.depth  = nChannel;
+    outpam.format = PAM_FORMAT;
+    strcpy(outpam.tuple_type, tupletype);
+    
+    pnm_writepaminit(&outpam);
+
+    {
+        tuple * inrow;
+        tuple * outrow;
+        
+        inrow  = pnm_allocpamrow(&inpam);      
+        outrow = pnm_allocpamrow(&outpam);
+        { 
+            unsigned int row;
+            
+            for (row = 0; row < inpam.height; ++row) {
+                unsigned int col;
+
+                pnm_readpamrow(&inpam, inrow);
+
+                for (col = 0; col < inpam.width; ++col) {
+                    unsigned int plane;
+                    for (plane = 0; plane < nChannel; ++plane) 
+                        outrow[col][plane] = 
+                            inrow[col][channelToExtract[plane]];
+                }
+                pnm_writepamrow(&outpam, outrow);
+            }
+        }
+        pnm_freepamrow(outrow);
+        pnm_freepamrow(inrow);        
+    }
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+    FILE * ifP;
+    bool eof;
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+    
+    ifP = pm_openr(cmdline.inputFileName);
+
+    eof = FALSE;
+    while (!eof) {
+        doOneImage(ifP, stdout, cmdline.n_channel, cmdline.channel_to_extract,
+                   cmdline.tupletype);
+
+        pnm_nextimage(ifP, &eof);
+    }
+
+    pm_close(ifP);
+
+    return 0;
+}
+
diff --git a/other/pamdepth.c b/other/pamdepth.c
new file mode 100644
index 00000000..0c4490ed
--- /dev/null
+++ b/other/pamdepth.c
@@ -0,0 +1,175 @@
+/*=============================================================================
+                            pamdepth
+===============================================================================
+  Change the maxval in a Netpbm image.
+
+  This replaces Pnmdepth.
+
+  By Bryan Henderson January 2006.
+
+  Contributed to the public domain by its author.
+=============================================================================*/
+#include <assert.h>
+
+#include "shhopt.h"
+#include "mallocvar.h"
+#include "pam.h"
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char * inputFileName;
+    unsigned int newMaxval;
+    unsigned int verbose;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo *cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec strings we return are 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;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0, "verbose",  OPT_STRING, NULL, 
+            &cmdlineP->verbose, 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 (argc-1 < 1)
+        pm_error("You must specify at least one argument -- the new maxval");
+    else {
+        int const intval = atoi(argv[1]);
+
+        if (intval < 1)
+            pm_error("New maxval must be at least 1.  You specified %d",
+                     intval);
+        else if (intval > PNM_OVERALLMAXVAL)
+            pm_error("newmaxval (%d) is too large.\n"
+                     "The maximum allowed by the PNM formats is %d.",
+                     intval, PNM_OVERALLMAXVAL);
+        else
+            cmdlineP->newMaxval = intval;
+
+        if (argc-1 < 2)
+            cmdlineP->inputFileName = "-";
+        else
+            cmdlineP->inputFileName = argv[2];
+    }
+}
+
+
+
+static void
+createSampleMap(sample   const oldMaxval,
+                sample   const newMaxval,
+                sample** const sampleMapP) {
+
+    unsigned int i;
+
+    sample * sampleMap;
+
+    MALLOCARRAY_NOFAIL(sampleMap, oldMaxval+1);
+
+    for (i = 0; i <= oldMaxval; ++i)
+        sampleMap[i] = (i * newMaxval + oldMaxval / 2) / oldMaxval;
+
+    *sampleMapP = sampleMap;
+}
+
+
+
+static void
+transformRaster(struct pam * const inpamP,
+                struct pam * const outpamP) {
+                
+    tuple * tuplerow;
+    unsigned int row;
+    sample * sampleMap;  /* malloc'ed */
+
+    createSampleMap(inpamP->maxval, outpamP->maxval, &sampleMap);
+
+    assert(inpamP->height == outpamP->height);
+    assert(inpamP->width  == outpamP->width);
+    assert(inpamP->depth  == outpamP->depth);
+
+    tuplerow = pnm_allocpamrow(inpamP);
+
+    for (row = 0; row < inpamP->height; ++row) {
+        unsigned int col;
+        pnm_readpamrow(inpamP, tuplerow);
+
+        for (col = 0; col < inpamP->width; ++col) {
+            unsigned int plane;
+            for (plane = 0; plane < inpamP->depth; ++plane)
+                tuplerow[col][plane] = sampleMap[tuplerow[col][plane]];
+        }
+        pnm_writepamrow(outpamP, tuplerow);
+	}
+
+    pnm_freepamrow(tuplerow);
+
+    free(sampleMap);
+}
+
+
+
+int
+main(int    argc,
+     char * argv[]) {
+
+    struct cmdlineInfo cmdline;
+    FILE * ifP;
+    struct pam inpam;
+    struct pam outpam;
+    bool eof;
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFileName);
+
+    eof = FALSE;
+    while (!eof) {
+        pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
+
+        outpam = inpam;  /* initial value */
+        
+        outpam.file = stdout;
+        outpam.maxval = cmdline.newMaxval;
+        
+        if (PNM_FORMAT_TYPE(inpam.format) == PBM_TYPE) {
+            pm_message( "promoting from PBM to PGM" );
+            outpam.format = PGM_TYPE;
+        } else
+            outpam.format = inpam.format;
+        
+        pnm_writepaminit(&outpam);
+
+        transformRaster(&inpam, &outpam);
+
+        pnm_nextimage(ifP, &eof);
+    }
+    pm_close(ifP);
+    pm_close(stdout);
+
+    return 0;
+}
diff --git a/other/pamendian.c b/other/pamendian.c
new file mode 100644
index 00000000..16e1fe56
--- /dev/null
+++ b/other/pamendian.c
@@ -0,0 +1,70 @@
+/******************************************************************************
+                              pnmendian
+*******************************************************************************
+
+  Reverse the endianness of multi-byte samples in a Netpbm stream.
+  I.e. convert between the true format and the little endian variation of
+  it.
+******************************************************************************/
+  
+#include "pam.h"
+
+
+static sample
+reverseSample(sample const insample, unsigned int const bytesPerSample) {
+/*----------------------------------------------------------------------------
+  Return a sample whose value is the least significant
+  'bytes_per_sample' bytes, in reverse order.
+-----------------------------------------------------------------------------*/
+    unsigned int bytePos;
+    sample shiftedInsample;
+    sample outsample;
+    shiftedInsample = insample;  /* initial value */
+    outsample = 0;  /* initial value */
+    for (bytePos = 0; bytePos < bytesPerSample; ++bytePos) {
+        outsample = outsample * 256 + (shiftedInsample & 0xff);
+        shiftedInsample >>= 8;
+    }
+    return outsample;
+}
+
+
+
+int main(int argc, char *argv[]) {
+
+    struct pam inpam, outpam;
+    tuple * intuplerow;
+    tuple * outtuplerow;
+    unsigned int row;
+
+    pnm_init(&argc, argv);
+
+    pnm_readpaminit(stdin, &inpam, PAM_STRUCT_SIZE(tuple_type));
+
+    outpam = inpam;
+    outpam.file = stdout;
+
+    pnm_writepaminit(&outpam);
+
+    intuplerow = pnm_allocpamrow(&inpam);      
+    outtuplerow = pnm_allocpamrow(&outpam);
+
+    for (row = 0; row < inpam.height; row++) {
+        unsigned int col;
+        pnm_readpamrow(&inpam, intuplerow);
+        for (col = 0; col < inpam.width; col++) {
+            unsigned int plane;
+            for (plane = 0; plane < inpam.depth; plane++) 
+                outtuplerow[col][plane] = 
+                    reverseSample(intuplerow[col][plane], 
+                                  inpam.bytes_per_sample);
+        }
+        pnm_writepamrow(&outpam, outtuplerow);
+    }
+
+    pnm_freepamrow(outtuplerow);        
+    pnm_freepamrow(intuplerow);        
+
+    exit(0);
+}
+
diff --git a/other/pamlookup.c b/other/pamlookup.c
new file mode 100644
index 00000000..2651d596
--- /dev/null
+++ b/other/pamlookup.c
@@ -0,0 +1,299 @@
+/*============================================================================
+                               pamlookup
+==============================================================================
+
+  Look up integers or ordered pairs from an index image in a lookup table and
+  produce a corresponding image containing the results of the lookups.
+  
+  The index image and lookup table are PAM images.  The output image is
+  a PAM image with the width and height of the index image and tuples of
+  the kind in the lookup table.
+
+  By Bryan Henderson, San Jose CA 2002.11.10
+
+============================================================================*/
+
+#include "pam.h"
+#include "shhopt.h"
+#include "pm_system.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.
+    */
+    const char *indexFilespec;  
+    char *lookupFilespec;
+    char *missingcolor;  /* -missingcolor value.  null if not specified */
+    unsigned int fit;  /* -fit option */
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** const argv,
+                 struct cmdlineInfo * const 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;
+    
+    unsigned int lookupfileSpec, missingcolorSpec;
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0, "lookupfile",     OPT_STRING, &cmdlineP->lookupFilespec,  
+            &lookupfileSpec, 0);
+    OPTENT3(0,   "missingcolor", OPT_STRING, 
+            &cmdlineP->missingcolor,   &missingcolorSpec, 0);
+    OPTENT3(0,   "fit", OPT_FLAG, 
+            NULL,   &cmdlineP->fit, 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 (!lookupfileSpec)
+        pm_error("You must specify the -lookupfile option");
+
+    if (!missingcolorSpec)
+        cmdlineP->missingcolor = NULL;
+
+    if (argc-1 < 1)
+        cmdlineP->indexFilespec = "-";
+    else
+        cmdlineP->indexFilespec = argv[1];
+}        
+
+
+
+static void
+fitLookup(tuple **     const inputLookup, 
+          struct pam   const inputLookuppam,
+          tuple ***    const fitLookupP,
+          struct pam * const fitLookuppamP,
+          unsigned int const cols,
+          unsigned int const rows) {
+/*----------------------------------------------------------------------------
+  Scale the lookup table image so that it has dimensions 'cols' x 'rows'.
+-----------------------------------------------------------------------------*/
+    const char * pamscaleCommand;
+    struct pamtuples inPamtuples;
+    struct pamtuples outPamtuples;
+
+    *fitLookuppamP = inputLookuppam;
+    fitLookuppamP->width = cols;
+    fitLookuppamP->height = rows;
+
+    asprintfN(&pamscaleCommand, "pamscale -width=%u -height=%u", cols, rows);
+
+    inPamtuples.pamP = (struct pam *) &inputLookuppam;
+    inPamtuples.tuplesP = (tuple ***) &inputLookup;
+    outPamtuples.pamP = fitLookuppamP;
+    outPamtuples.tuplesP = fitLookupP;
+    
+    pm_system(&pm_feed_from_pamtuples, &inPamtuples,
+              &pm_accept_to_pamtuples, &outPamtuples,
+              pamscaleCommand);
+
+    strfree(pamscaleCommand);
+}
+
+
+
+static void
+getLookup(const char * const lookupFilespec, 
+          unsigned int const indexDegree,
+          unsigned int const indexMaxval,
+          tuple ***    const lookupP,
+          struct pam * const lookuppamP,
+          bool         const fit) {
+
+    FILE*  lookupfileP;
+
+    struct pam inputLookuppam;
+    tuple** inputLookup;
+
+    lookupfileP = pm_openr(lookupFilespec);
+    inputLookup = pnm_readpam(lookupfileP, 
+                              &inputLookuppam, PAM_STRUCT_SIZE(tuple_type));
+
+    pm_close(lookupfileP);
+    
+    if (fit) {
+        fitLookup(inputLookup, inputLookuppam, lookupP, lookuppamP,
+                  indexMaxval + 1, 
+                  indexDegree > 1 ? indexMaxval + 1 : 1);
+        pnm_freepamarray(inputLookup, &inputLookuppam);
+    } else {
+        *lookupP = inputLookup;
+        *lookuppamP = inputLookuppam;
+    }
+        
+    if (indexDegree == 1 && lookuppamP->height != 1)
+        pm_error("Your index image has integer indices, "
+                 "so the lookup table image must be one row.  "
+                 "Yours is %d rows.", 
+                 lookuppamP->height);
+
+    if (lookuppamP->width - 1 > indexMaxval)
+        pm_message("Warning:  your lookup table image is wider than "
+                   "the maxval of "
+                   "your index message, so the right end of the lookup "
+                   "table image will have no effect on the output.");
+    if (indexDegree == 2 && lookuppamP->height - 1 > indexMaxval)
+        pm_message("Warning: your lookup table image is taller than "
+                   "the maxval of "
+                   "your index message, so the bottom end of the lookup "
+                   "table image has no effect on the output.");
+}
+
+
+
+static void
+computeDefaultTuple(struct cmdlineInfo const cmdline, 
+                    tuple **           const lookup,
+                    struct pam *       const lookuppamP, 
+                    tuple *            const defaultTupleP) {
+
+    tuple retval;
+
+    retval = pnm_allocpamtuple(lookuppamP);
+
+    /* Note: "missing color" really makes sense only for a color
+       lookup, whereas this program allows an arbitrary PAM image as a
+       lookup table.  We should probably check here for a lookup file
+       that has a visual image tuple type, but we don't out of
+       laziness.  The program probably ought to have a generic
+       "missing tuple type" option too.  
+    */
+    if (cmdline.missingcolor) {
+        pixel const color = 
+            ppm_parsecolor(cmdline.missingcolor, lookuppamP->maxval);
+
+        if (lookuppamP->depth >= 3) {
+            retval[PAM_RED_PLANE] = PPM_GETR(color);
+            retval[PAM_GRN_PLANE] = PPM_GETG(color);
+            retval[PAM_BLU_PLANE] = PPM_GETB(color);
+        } else {
+            if (PPM_GETR(color) != PPM_GETG(color) ||
+                PPM_GETR(color) != PPM_GETB(color))
+                pm_error("You specified as a missing color something which "
+                         "is not monochrome, but your lookup table file, "
+                         "and thus your "
+                         "output file, can contain only monochrome values");
+            else
+                retval[0] = PPM_GETR(color);
+        }
+    } else 
+        pnm_assigntuple(lookuppamP, retval, lookup[0][0]);
+
+    *defaultTupleP = retval;
+}
+
+
+
+static void
+doLookup(struct pam const indexpam,
+         struct pam const outpamarg,
+         tuple      const defaultTuple,
+         tuple **   const lookup,
+         struct pam const lookuppam) {
+
+    struct pam outpam;
+    unsigned int row;
+
+    tuple* tuplerowIndex;
+    tuple* tuplerowOut;
+
+    outpam = outpamarg;
+
+    tuplerowIndex = pnm_allocpamrow(&indexpam);
+    tuplerowOut = pnm_allocpamrow(&outpam);
+
+    pnm_writepaminit(&outpam);
+
+    for (row = 0; row < outpam.height; ++row) {
+        unsigned int col;
+        pnm_readpamrow(&indexpam, tuplerowIndex);
+        
+        for (col = 0; col < outpam.width; ++col) {
+            unsigned int indexRow, indexCol;
+            tuple v;
+            
+            if (indexpam.depth < 2) {
+                indexRow = 0;
+                indexCol = tuplerowIndex[col][0];
+            } else {
+                indexRow = tuplerowIndex[col][0];
+                indexCol = tuplerowIndex[col][1];
+            }
+
+            if (indexRow >= lookuppam.height || indexCol >= lookuppam.width)
+                v = defaultTuple;
+            else
+                v = lookup[indexRow][indexCol];
+
+            pnm_assigntuple(&outpam, tuplerowOut[col], v);
+        }
+        pnm_writepamrow(&outpam, tuplerowOut);
+    }
+    pnm_freepamrow(tuplerowIndex);
+    pnm_freepamrow(tuplerowOut);
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+    struct pam indexpam;
+    struct pam outpam;
+    FILE*  ifP;
+    struct pam lookuppam;
+    tuple** lookup;
+
+    tuple defaultTuple;
+    
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.indexFilespec);
+
+    pnm_readpaminit(ifP, &indexpam, PAM_STRUCT_SIZE(tuple_type));
+
+    if (indexpam.depth != 1 && indexpam.depth != 2)
+        pm_error("The input (index) file must have depth 1 or 2.  "
+                 "Yours has depth %d",
+                 indexpam.depth);
+
+    getLookup(cmdline.lookupFilespec, indexpam.depth, indexpam.maxval, 
+              &lookup, &lookuppam, cmdline.fit);
+
+    computeDefaultTuple(cmdline, lookup, &lookuppam, &defaultTuple);
+
+    outpam = lookuppam;
+    outpam.height = indexpam.height;
+    outpam.width = indexpam.width;
+    outpam.file = stdout;
+    
+    doLookup(indexpam, outpam, defaultTuple, lookup, lookuppam);
+
+    pm_close(ifP);
+
+    pnm_freepamtuple(defaultTuple);
+    pnm_freepamarray(lookup, &lookuppam);
+    
+    exit(0);
+}
+
diff --git a/other/pampick.c b/other/pampick.c
new file mode 100644
index 00000000..7c2d4385
--- /dev/null
+++ b/other/pampick.c
@@ -0,0 +1,250 @@
+/******************************************************************************
+                               pampick
+*******************************************************************************
+  Select specified images from a Netpbm image stream and create a new
+  Netpbm image stream containing them.
+
+  By Bryan Henderson, San Jose CA; October 2005
+
+  Contributed to the public domain by its author.
+******************************************************************************/
+
+#include <string.h>
+#include <stdio.h>
+
+#include "pam.h"
+#include "shhopt.h"
+#include "nstring.h"
+#include "mallocvar.h"
+
+
+struct uintSet {
+    unsigned int allocSize;
+    unsigned int count;
+    unsigned int * list;
+};
+
+
+
+static void
+initUintSet(struct uintSet * const uintSetP,
+            unsigned int     const allocSize) {
+
+    uintSetP->allocSize = allocSize;
+    uintSetP->count     = 0;
+
+    MALLOCARRAY(uintSetP->list, allocSize);
+    if (uintSetP->list == NULL)
+        pm_error("Could not allocate memory for an array of %u numbers.",
+                 allocSize);
+}
+
+
+
+static void
+termUintSet(struct uintSet * const uintSetP) {
+
+    free(uintSetP->list);
+}
+
+
+
+static bool
+isMemberOfUintSet(const struct uintSet * const uintSetP,
+                  unsigned int           const searchValue) {
+
+    bool retval;
+    unsigned int i;
+
+    retval = FALSE;  /* initial assumption */
+    
+    for (i = 0; i < uintSetP->count; ++i) {
+        if (uintSetP->list[i] == searchValue)
+            retval = TRUE;
+    }
+    return retval;
+}
+
+
+
+static void
+addToUintSet(struct uintSet * const uintSetP,
+             unsigned int     const newValue) {
+
+    if (uintSetP->count >= uintSetP->allocSize)
+        pm_error("Overflow of number list");
+    else {
+        if (!isMemberOfUintSet(uintSetP, newValue))
+            uintSetP->list[uintSetP->count++] = newValue;
+    }
+}
+
+
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    struct uintSet imageSeqList;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the pointers we place into *cmdlineP are sometimes to storage
+   in the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def;
+        /* Instructions to OptParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+
+    /* We have no options.  We use the parser just to gripe if user
+       specifies an option.  But when we add an option in the future,
+       it will go right here with an OPTENT3() macro invocation.
+    */
+
+    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. */
+
+    initUintSet(&cmdlineP->imageSeqList, argc-1);
+
+    {
+        unsigned int i;
+
+        for (i = 0; i < argc-1; ++i) {
+            if (strlen(argv[i+1]) == 0)
+                pm_error("An image sequence argument is a null string!");
+            else {
+                char * endPtr;
+                int const strtolResult = strtol(argv[i+1], &endPtr, 10);
+
+                if (*endPtr != '\0')
+                    pm_error("Garbage in sequence number argument '%s': '%s'",
+                             argv[i+1], endPtr);
+                
+                if (strtolResult < 0)
+                    pm_error("Image sequence number cannot be negative.  "
+                             "You specified %d", strtolResult);
+
+                addToUintSet(&cmdlineP->imageSeqList, strtolResult);
+            }
+        }
+    }
+    free(option_def);
+}
+
+
+
+static void
+destroyCmdline(struct cmdlineInfo * const cmdlineP) {
+
+    termUintSet(&cmdlineP->imageSeqList);
+}
+
+
+
+static void
+extractOneImage(FILE * const infileP,
+                FILE * const outfileP) {
+/*----------------------------------------------------------------------------
+  Copy a complete image from input stream *infileP to output stream
+  *outfileP.
+
+  But if outfileP == NULL, just read the image and discard it.
+-----------------------------------------------------------------------------*/
+    struct pam inpam;
+    struct pam outpam;
+    enum pm_check_code checkRetval;
+    
+    unsigned int row;
+    tuple * tuplerow;
+
+    pnm_readpaminit(infileP, &inpam, PAM_STRUCT_SIZE(tuple_type));
+
+    pnm_checkpam(&inpam, PM_CHECK_BASIC, &checkRetval);
+
+    outpam = inpam;
+    outpam.file = outfileP;
+
+    if (outfileP)
+        pnm_writepaminit(&outpam);
+
+    tuplerow = pnm_allocpamrow(&inpam);
+    for (row = 0; row < inpam.height; ++row) {
+        pnm_readpamrow(&inpam, tuplerow);
+        if (outfileP)
+            pnm_writepamrow(&outpam, tuplerow);
+    }
+    pnm_freepamrow(tuplerow);
+}
+
+
+
+static void
+failIfUnpickedImages(const struct uintSet * const uintSetP,
+                     unsigned int           const imageCount) {
+/*----------------------------------------------------------------------------
+   Abort the program (pm_error()) if there are any image sequence numbers
+   in the set *uintSetP that are greater than or equal to 'imageCount'.
+-----------------------------------------------------------------------------*/
+    unsigned int i;
+
+    for (i = 0; i < uintSetP->count; ++i) {
+        if (uintSetP->list[i] >= imageCount)
+            pm_error("You asked for image sequence number %u "
+                     "(relative to 0).  "
+                     "The number of images in the input stream is only %u.",
+                     uintSetP->list[i], imageCount);
+    }
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+
+    bool eof;  /* No more images in input */
+    unsigned int imageSeq;  
+        /* Sequence of current image in input file.  First = 0 */
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+    
+    eof = FALSE;
+    for (imageSeq = 0; !eof; ++imageSeq) {
+        if (isMemberOfUintSet(&cmdline.imageSeqList, imageSeq)) {
+            pm_message("Extracting Image #%u", imageSeq);
+
+            extractOneImage(stdin, stdout);
+        } else
+            extractOneImage(stdin, NULL);
+
+        pnm_nextimage(stdin, &eof);
+    }
+
+    failIfUnpickedImages(&cmdline.imageSeqList, imageSeq);
+
+    destroyCmdline(&cmdline);
+
+    pm_close(stdin);
+    pm_close(stdout);
+    
+    return 0;
+}
diff --git a/other/pamsplit.c b/other/pamsplit.c
new file mode 100644
index 00000000..b9ff6247
--- /dev/null
+++ b/other/pamsplit.c
@@ -0,0 +1,187 @@
+/******************************************************************************
+                               pamsplit
+*******************************************************************************
+  Split a Netpbm format input file into multiple Netpbm format output files
+  with one image per output file.
+
+  By Bryan Henderson, Olympia WA; June 2000
+
+  Contributed to the public domain by its author.
+******************************************************************************/
+
+#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 <stdio.h>
+#include "pam.h"
+#include "shhopt.h"
+#include "nstring.h"
+#include "mallocvar.h"
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char * inputFileName;
+    const char * outputFilePattern;
+    unsigned int debug;
+    unsigned int padname;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the pointers we place into *cmdlineP are sometimes to storage
+   in the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def;
+        /* Instructions to OptParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int padnameSpec;
+
+    unsigned int option_def_index;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0,   "debug",   OPT_FLAG, NULL, &cmdlineP->debug, 0);
+    OPTENT3(0,   "padname", OPT_UINT, &cmdlineP->padname, &padnameSpec, 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 (!padnameSpec)
+        cmdlineP->padname = 0;
+
+    if (argc - 1 < 1) 
+        cmdlineP->inputFileName = "-";
+    else
+        cmdlineP->inputFileName = argv[1];
+    if (argc -1 < 2)
+        cmdlineP->outputFilePattern = "image%d";
+    else
+        cmdlineP->outputFilePattern = argv[2];
+
+    if (!strstr(cmdlineP->outputFilePattern, "%d"))
+        pm_error("output file spec pattern parameter must include the "
+                 "string '%%d',\n"
+                 "to stand for the image sequence number.\n"
+                 "You specified '%s'.", cmdlineP->outputFilePattern);
+}
+
+
+
+static void
+extractOneImage(FILE * const infileP,
+                FILE * const outfileP) {
+
+    struct pam inpam;
+    struct pam outpam;
+    enum pm_check_code checkRetval;
+    
+    unsigned int row;
+    tuple * tuplerow;
+
+    pnm_readpaminit(infileP, &inpam, PAM_STRUCT_SIZE(tuple_type));
+
+    pnm_checkpam(&inpam, PM_CHECK_BASIC, &checkRetval);
+
+    outpam = inpam;
+    outpam.file = outfileP;
+
+    pnm_writepaminit(&outpam);
+
+    tuplerow = pnm_allocpamrow(&inpam);
+    for (row = 0; row < inpam.height; ++row) {
+        pnm_readpamrow(&inpam, tuplerow);
+        pnm_writepamrow(&outpam, tuplerow);
+    }
+    pnm_freepamrow(tuplerow);
+}
+
+
+
+static void
+computeOutputName(char          const outputFilePattern[], 
+                  unsigned int  const padCount,
+                  unsigned int  const imageSeq,
+                  const char ** const outputNameP) {
+/*----------------------------------------------------------------------------
+   Compute the name of an output file given the pattern
+   outputFilePattern[] and the image sequence number 'imageSeq'.
+   outputFilePattern[] contains at least one instance of the string
+   "%d" and we substitute the ASCII decimal representation of
+   imageSeq for the firstone of them to generate the output file
+   name.  We add leading zeroes as necessary to bring the number up to
+   at least 'padCount' characters.
+-----------------------------------------------------------------------------*/
+    char * beforeSub;
+    const char * afterSub;
+    const char * filenameFormat;
+        /* A format string for asprintfN for the file name */
+
+    beforeSub = strdup(outputFilePattern);
+    *(strstr(beforeSub, "%d")) = '\0';
+
+    afterSub = strstr(outputFilePattern, "%d") + 2;
+
+    /* Make filenameFormat something like "%s%04u%s" */
+    asprintfN(&filenameFormat, "%%s%%0%ud%%s", padCount);
+
+    asprintfN(outputNameP, filenameFormat, beforeSub, imageSeq, afterSub);
+
+    strfree(filenameFormat);
+
+    free(beforeSub);
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+
+    FILE * ifP;
+    bool eof;  /* No more images in input */
+    unsigned int imageSeq;  
+        /* Sequence of current image in input file.  First = 0 */
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+    
+    ifP = pm_openr(cmdline.inputFileName);
+
+    eof = FALSE;
+    for (imageSeq = 0; !eof; ++imageSeq) {
+        FILE * ofP;
+        const char * outputFileName;  /* malloc'ed */
+
+        computeOutputName(cmdline.outputFilePattern, cmdline.padname, 
+                          imageSeq,
+                          &outputFileName);
+        pm_message("WRITING %s", outputFileName);
+
+        ofP = pm_openw(outputFileName);
+        extractOneImage(ifP, ofP);
+
+        pm_close(ofP);
+        strfree(outputFileName);
+
+        pnm_nextimage(ifP, &eof);
+    }
+    pm_close(ifP);
+    
+    return 0;
+}
diff --git a/other/pamstack.c b/other/pamstack.c
new file mode 100644
index 00000000..4f8d9945
--- /dev/null
+++ b/other/pamstack.c
@@ -0,0 +1,240 @@
+/*----------------------------------------------------------------------------
+                               pamstack
+------------------------------------------------------------------------------
+  Part of the Netpbm package.
+
+  Combine the channels (stack the planes) of multiple PAM images to create
+  a single PAM image.
+
+
+  By Bryan Henderson, San Jose CA 2000.08.05
+
+  Contributed to the public domain by its author 2002.05.05.
+-----------------------------------------------------------------------------*/
+
+#include <string.h>
+
+#include "mallocvar.h"
+#include "shhopt.h"
+#include "pam.h"
+
+#define MAX_INPUTS 16
+    /* The most input PAMs we allow user to specify */
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *tupletype;       /* Tuple type for output PAM */
+    unsigned int nInput;
+        /* The number of input PAMs.  At least 1, at most 16. */
+    const char * inputFileName[MAX_INPUTS];
+        /* The PAM files to combine, in order. */
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec strings we return are 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;
+    extern struct pam pam;  /* Just so we can look at field sizes */
+
+    unsigned int option_def_index;
+    unsigned int tupletypeSpec;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+    
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    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 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 (!tupletypeSpec)
+        cmdlineP->tupletype = "";
+    else
+        if (strlen(cmdlineP->tupletype)+1 > sizeof(pam.tuple_type))
+            pm_error("Tuple type name specified is too long.  Maximum of "
+                     "%u characters allowed.", sizeof(pam.tuple_type));
+
+    cmdlineP->nInput = 0;  /* initial value */
+    { 
+        int argn;
+        for (argn = 1; argn < argc; argn++) {
+            if (cmdlineP->nInput >= MAX_INPUTS) 
+                pm_error("You may not specify more than %d input images.",
+                         MAX_INPUTS);
+            cmdlineP->inputFileName[cmdlineP->nInput++] = argv[argn];
+        }
+    }
+    if (cmdlineP->nInput < 1)
+        pm_error("You must specify at least one input PAM image.");
+}
+
+
+
+static void
+openAllStreams(unsigned int  const nInput,
+               const char ** const inputFileName,
+               FILE **       const ifP) {
+
+    unsigned int inputSeq;
+
+    for (inputSeq = 0; inputSeq < nInput; ++inputSeq)
+        ifP[inputSeq] = pm_openr(inputFileName[inputSeq]);
+}
+
+
+
+static void
+outputRaster(const struct pam       inpam[], 
+             unsigned int     const nInput,
+             struct pam             outpam) {
+
+    tuple *inrow;
+    tuple *outrow;
+        
+    outrow = pnm_allocpamrow(&outpam);
+    inrow = pnm_allocpamrow(&outpam);      
+
+    { 
+        int row;
+        
+        for (row = 0; row < outpam.height; row++) {
+            unsigned int inputSeq;
+            int outplane;
+            outplane = 0;  /* initial value */
+            for (inputSeq = 0; inputSeq < nInput; ++inputSeq) {
+                struct pam thisInpam = inpam[inputSeq];
+                int col;
+
+                pnm_readpamrow(&thisInpam, inrow);
+
+                for (col = 0; col < outpam.width; col ++) {
+                    int inplane;
+                    for (inplane = 0; inplane < thisInpam.depth; ++inplane) 
+                        outrow[col][outplane+inplane] = inrow[col][inplane];
+                }
+                outplane += thisInpam.depth;
+            }
+            pnm_writepamrow(&outpam, outrow);
+        }
+    }
+    pnm_freepamrow(outrow);
+    pnm_freepamrow(inrow);        
+}
+
+
+
+static void
+processOneImageInAllStreams(unsigned int const nInput,
+                            FILE *       const ifP[],
+                            FILE *       const ofP,
+                            const char * const tupletype) {
+
+    struct pam inpam[MAX_INPUTS];   /* Input PAM images */
+    struct pam outpam;  /* Output PAM image */
+
+    unsigned int inputSeq;
+        /* The horizontal sequence -- i.e. the sequence of the
+           input stream, not the sequence of an image within a
+           stream.
+        */
+
+    unsigned int outputDepth;
+    outputDepth = 0;  /* initial value */
+    
+    for (inputSeq = 0; inputSeq < nInput; ++inputSeq) {
+
+        pnm_readpaminit(ifP[inputSeq], &inpam[inputSeq], 
+                        PAM_STRUCT_SIZE(tuple_type));
+
+        if (inputSeq > 0) {
+            /* All images, including this one, must be compatible with the 
+               first image.
+            */
+            if (inpam[inputSeq].width != inpam[0].width)
+                pm_error("Image no. %u does not have the same width as "
+                         "Image 0.", inputSeq);
+            if (inpam[inputSeq].height != inpam[0].height)
+                pm_error("Image no. %u does not have the same height as "
+                         "Image 0.", inputSeq);
+            if (inpam[inputSeq].maxval != inpam[0].maxval)
+                pm_error("Image no. %u does not have the same maxval as "
+                         "Image 0.", inputSeq);
+        }
+        outputDepth += inpam[inputSeq].depth;
+    }
+
+    outpam        = inpam[0];     /* Initial value */
+    outpam.depth  = outputDepth;
+    outpam.file   = ofP;
+    outpam.format = PAM_FORMAT;
+    strcpy(outpam.tuple_type, tupletype);
+
+    pm_message("Writing %u channel PAM image", outpam.depth);
+
+    pnm_writepaminit(&outpam);
+
+    outputRaster(inpam, nInput, outpam);
+}
+
+
+
+static void
+nextImageAllStreams(unsigned int const nInput,
+                    FILE *       const ifP[],
+                    bool *       const eofP) {
+/*----------------------------------------------------------------------------
+   Advance all the streams ifP[] to the next image.
+
+   Return *eofP == TRUE iff at least one stream has no next image.
+-----------------------------------------------------------------------------*/
+    unsigned int inputSeq;
+
+    for (inputSeq = 0; inputSeq < nInput; ++inputSeq) {
+        bool eof;
+        pnm_nextimage(ifP[inputSeq], &eof);
+        if (eof)
+            *eofP = eof;
+    }
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+    FILE * ifP[MAX_INPUTS];
+    bool eof;
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    openAllStreams(cmdline.nInput, cmdline.inputFileName, ifP);
+
+    eof = FALSE;
+    while (!eof) {
+        processOneImageInAllStreams(cmdline.nInput, ifP, stdout,
+                                    cmdline.tupletype);
+
+        nextImageAllStreams(cmdline.nInput, ifP, &eof);
+    }
+
+    return 0;
+}
diff --git a/other/pamsummcol.c b/other/pamsummcol.c
new file mode 100644
index 00000000..c2c3e46b
--- /dev/null
+++ b/other/pamsummcol.c
@@ -0,0 +1,257 @@
+/******************************************************************************
+                               pamsummcol
+*******************************************************************************
+  Summarize the columns of a PAM image with various functions.
+
+  By Bryan Henderson, San Jose CA 2004.02.07.
+
+  Contributed to the public domain
+
+
+******************************************************************************/
+
+#include "pam.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+
+enum function {FN_ADD, FN_MEAN, FN_MIN, FN_MAX};
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *inputFilespec;  /* Filespec of input file */
+    enum function function;
+    unsigned int verbose;
+};
+
+
+static void
+parseCommandLine(int argc, char ** const argv,
+                 struct cmdlineInfo * const 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;
+
+    unsigned int sumSpec, meanSpec, minSpec, maxSpec;
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0,   "sum",      OPT_FLAG,  NULL, &sumSpec,           0);
+    OPTENT3(0,   "mean",     OPT_FLAG,  NULL, &meanSpec,          0);
+    OPTENT3(0,   "min",      OPT_FLAG,  NULL, &minSpec,           0);
+    OPTENT3(0,   "max",      OPT_FLAG,  NULL, &maxSpec,           0);
+    OPTENT3(0,   "verbose",  OPT_FLAG,  NULL, &cmdlineP->verbose, 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 (sumSpec + minSpec + maxSpec > 1)
+        pm_error("You may specify at most one of -sum, -min, and -max");
+
+    if (sumSpec) {
+        cmdlineP->function = FN_ADD;
+    } else if (meanSpec) {
+        cmdlineP->function = FN_MEAN;
+    } else if (minSpec) {
+        cmdlineP->function = FN_MIN;
+    } else if (maxSpec) {
+        cmdlineP->function = FN_MAX;
+    } else 
+        pm_error("You must specify one of -sum, -min, or -max");
+        
+    if (argc-1 > 1)
+        pm_error("Too many arguments (%d).  File spec is the only argument.",
+                 argc-1);
+
+    if (argc-1 < 1)
+        cmdlineP->inputFilespec = "-";
+    else 
+        cmdlineP->inputFilespec = argv[1];
+    
+}
+
+
+struct accum {
+    union {
+        unsigned int sum;
+        unsigned int min;
+        unsigned int max;
+    } u;
+};
+
+
+
+static void
+createAccumulator(enum function    const function,
+                  unsigned int     const cols,
+                  unsigned int     const planes,
+                  struct accum *** const accumulatorP) {
+    
+    struct accum ** accumulator;
+    unsigned int col;
+
+    MALLOCARRAY_NOFAIL(accumulator, cols);
+
+    for (col = 0; col < cols; ++col) {
+        unsigned int plane;
+
+        MALLOCARRAY_NOFAIL(accumulator[col], planes);
+
+        for (plane = 0; plane < planes; ++plane) {
+            switch(function) {
+            case FN_ADD:  accumulator[col][plane].u.sum = 0;        break;
+            case FN_MEAN: accumulator[col][plane].u.sum = 0;        break;
+            case FN_MIN:  accumulator[col][plane].u.min = UINT_MAX; break;
+            case FN_MAX:  accumulator[col][plane].u.max = 0;        break;
+            } 
+        }
+    }
+    *accumulatorP = accumulator;
+}
+
+
+
+static void
+destroyAccumulator(struct accum **    accumulator,
+                   unsigned int const cols) {
+
+    unsigned int col;
+    for (col = 0; col < cols; ++col)
+        free(accumulator[col]);
+
+    free(accumulator);
+}
+
+
+
+static void
+aggregate(struct pam *    const inpamP,
+          tuple *         const tupleRow,
+          enum function   const function,
+          struct accum ** const accumulator) {
+
+    unsigned int col;
+
+    for (col = 0; col < inpamP->width; ++col) {
+        unsigned int plane;
+        for (plane = 0; plane < inpamP->depth; ++plane) {
+            switch(function) {
+            case FN_ADD:  
+            case FN_MEAN: 
+                if (accumulator[col][plane].u.sum > 
+                    UINT_MAX - tupleRow[col][plane])
+                    pm_error("Numerical overflow in Column %u", col);
+                accumulator[col][plane].u.sum += tupleRow[col][plane];
+            break;
+            case FN_MIN:  
+                if (tupleRow[col][plane] < accumulator[col][plane].u.min)
+                    accumulator[col][plane].u.min = tupleRow[col][plane];
+                break;
+            case FN_MAX:
+                if (tupleRow[col][plane] > accumulator[col][plane].u.min)
+                    accumulator[col][plane].u.min = tupleRow[col][plane];
+                break;
+            } 
+        }
+    }
+}
+
+
+
+static void
+makeSummaryRow(struct accum ** const accumulator,
+               unsigned int  const   count,
+               struct pam *  const   pamP,
+               enum function const   function,
+               tuple *       const   tupleRow) {
+    
+    unsigned int col;
+
+    for (col = 0; col < pamP->width; ++col) {
+        unsigned int plane;
+        for (plane = 0; plane < pamP->depth; ++plane) {
+            switch(function) {
+            case FN_ADD:  
+                tupleRow[col][plane] = 
+                    MIN(accumulator[col][plane].u.sum, pamP->maxval);
+                break;
+            case FN_MEAN: 
+                tupleRow[col][plane] = 
+                    ROUNDU((double)accumulator[col][plane].u.sum / count);
+                break;
+            case FN_MIN:  
+                tupleRow[col][plane] = 
+                    accumulator[col][plane].u.min;
+                break;
+            case FN_MAX:
+                tupleRow[col][plane] = 
+                    accumulator[col][plane].u.max;
+                break;
+            }
+        } 
+    }
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    FILE* ifP;
+    tuple* inputRow;   /* Row from input image */
+    tuple* outputRow;  /* Output row */
+    int row;
+    struct cmdlineInfo cmdline;
+    struct pam inpam;   /* Input PAM image */
+    struct pam outpam;  /* Output PAM image */
+    struct accum ** accumulator;  /* malloc'ed two-dimensional array */
+
+    pnm_init( &argc, argv );
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilespec);
+
+    pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
+
+    createAccumulator(cmdline.function, inpam.width, inpam.depth, 
+                      &accumulator);
+
+    inputRow = pnm_allocpamrow(&inpam);
+
+    outpam = inpam;    /* Initial value -- most fields should be same */
+    outpam.file = stdout;
+    outpam.height = 1;
+
+    pnm_writepaminit(&outpam);
+
+    outputRow = pnm_allocpamrow(&outpam);
+
+    for (row = 0; row < inpam.height; row++) {
+        pnm_readpamrow(&inpam, inputRow);
+
+        aggregate(&inpam, inputRow, cmdline.function, accumulator);
+    }
+    makeSummaryRow(accumulator, inpam.height, &outpam, cmdline.function, 
+                   outputRow);
+    pnm_writepamrow(&outpam, outputRow);
+
+    pnm_freepamrow(outputRow);
+    pnm_freepamrow(inputRow);
+    destroyAccumulator(accumulator, inpam.width);
+    pm_close(inpam.file);
+    pm_close(outpam.file);
+    
+    return 0;
+}
diff --git a/other/pamx/COPYRIGHT b/other/pamx/COPYRIGHT
new file mode 100644
index 00000000..3ad3b675
--- /dev/null
+++ b/other/pamx/COPYRIGHT
@@ -0,0 +1,72 @@
+Many of the source files for Pamx contain code written by or derived
+from code written by Jim Frost for his program Xloadimage.  The files
+in which Frost has copyright contain Frost's name at the top.  Frost
+licenses his copyright to the public as follows:
+
+    Copyright 1989, 1993 Jim Frost
+   
+    Permission to use, copy, modify, distribute, and sell this software
+    and its documentation for any purpose is hereby granted without fee,
+    provided that the above copyright notice appear in all copies and
+    that both that copyright notice and this permission notice appear
+    in supporting documentation.  The author makes no representations
+    about the suitability of this software for any purpose.  It is
+    provided "as is" without express or implied warranty.
+   
+    THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+    INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
+    NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+    CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+    OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+    OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
+    USE OR PERFORMANCE OF THIS SOFTWARE.
+
+
+The rest of the code was primarily written by Bryan Henderson,
+starting in 2006.  Bryan and other authors have contributed their work
+to the public domain.
+
+
+Here is a more or less complete list of people who contributed to
+the Xloadimage from which Pamx was derived.  The list is from the README
+file in the Xloadimage source code:
+
+Special thanks to the crew at the Boston University Graphics Lab for
+their assistance and sample images, and to bzs@std.com for his simple
+dithering algorithm (or what's left of it).  Real special thanks to
+Kirk L. Johnson (tuna@athena.mit.edu) for a very nice GIF loader and
+dithering routine, to Mark Snitily (zok!mark@apple.com) for 386/ix
+compatibility work, to Andreas Stolcke (stolcke@icsib12.berkeley.edu)
+for miscellaneous bug fixes, to Anthony A. Datri (datri@convex.com)
+for a number of things, to Mark Moraes (moraes@cs.toronto.edu) for
+the slideshow colormap fix, to Gregg Townsend (gmt@cs.arizona.edu) for
+a suggested dithering routine and other fixes, to Brian Frost
+(B1F5814@RIGEL.TAMU.EDU) for changes for VMS, to Chip Horstman for G3
+FAX support, to Deron Dann Johnson (dj@eng.sun.com) for fixing the
+RetainTemporary bug, to Tom Tatlow (tatlow@dash.enet.dec.com) for
+image rotation code, to Mark A. Horstman (mhorstm@sarek.sbc.com) for
+tilde expansion in .xloadimagerc files and virtual-root support in
+root.c, to Tim Roper (timr@labtam.labtam.oz.au), Graeme Gill
+(graeme@labtam.oz.au) for gamma correction and Utah RLE image support,
+Mark Majhor (uunet!sequent!markm) for FBM and MacPaint support, Ian
+MacPhedran (macphed@dvinci.usask.ca) for PGM and PPM support, Per
+Fogelstrom (pf@diab.se) for a fix to send.c, Hans J. Albertsson
+(hans@Sweden.Sun.COM) for cleaning up GIF aborting, Graham Hudspith
+(gwh@inmos.com) for a geometry patch, Glenn P. Davis
+(davis@unidata.ucar.edu) for McIDAS areafile support, Keith S. Pickens
+(maxwell.nde.swri.edu!ksp) for fixing the RLE loader to work with the
+updated zio package, Mike Douglas (douglas@wilbur.coyote.trw.com) for
+normalization, Rod Johnson (johnson@wrl.epi.com) for speedup
+suggestions, Hal Peterson (hrp@cray.com) for his Imakefile fix, Matt
+Caprile (Matthew.Caprile@ec.bull.fr) for slideshow delay code, Bob
+Deroy (rwd@bucrsb.bu.edu) for mondo 24-bit Sun Rasterfile images that
+broke everything, Christos S. Zoulas (christo@ee.cornell.edu) for a
+first-cut 24-bit implementation, Gerald James Barnes
+(gjb@oasis.icl.stc.co.uk) for a first-cut forced-visual
+implementation, Michael Campanella (campanella@cvg.enet.dec.com) for
+more VMS changes, Kee Hinckley (nazgul@alfalfa.com) for robustness
+changes to the g3 and MacPaint loaders and the ZIO package, Tim
+Northrup (tim@brspyr1.brs.com) for PC Paintbrush and GEM image
+formats, Richard Weidner (richard@elroy.jpl.nasa.gov) for lots of
+24-bit testing,  Eckhard Rueggeberg (erueg@cfgauss.uni-math.gwdg.de)
+for a better PCX loader, and any others whose names I've missed.
diff --git a/other/pamx/Makefile b/other/pamx/Makefile
new file mode 100644
index 00000000..a86a3331
--- /dev/null
+++ b/other/pamx/Makefile
@@ -0,0 +1,43 @@
+ifeq ($(SRCDIR)x,x)
+  SRCDIR = $(CURDIR)/../..
+  BUILDDIR = $(SRCDIR)
+endif
+SUBDIR = other/pamx
+VPATH=.:$(SRCDIR)/$(SUBDIR)
+
+include $(BUILDDIR)/Makefile.config
+
+ifneq ($(X11LIB),NONE)
+  ifneq ($(X11HDR_DIR),)
+    INCLUDES += -I$(X11HDR_DIR)
+  endif
+endif
+
+ifneq ($(X11LIB),NONE)
+  BINARIES += pamx
+endif
+
+PAMX_OBJECTS = \
+	pamx.o \
+	image.o \
+	send.o \
+	window.o \
+
+MERGE_OBJECTS = \
+	pamx.o2 \
+	image.o \
+	send.o \
+	window.o \
+
+OBJECTS = $(PAMX_OBJECTS)
+
+MERGEBINARIES = $(BINARIES)
+
+all: $(BINARIES)
+
+include $(SRCDIR)/Makefile.common
+
+pamx: $(PAMX_OBJECTS) $(NETPBMLIB) $(LIBOPT)
+	$(LD) $(LDFLAGS) -o $@ $(PAMX_OBJECTS) \
+	  $(shell $(LIBOPT) $(NETPBMLIB) $(X11LIB)) \
+	  $(LDLIBS) $(MATHLIB) $(RPATH) $(LADD)
diff --git a/other/pamx/Makefile2 b/other/pamx/Makefile2
new file mode 100644
index 00000000..f69e103f
--- /dev/null
+++ b/other/pamx/Makefile2
@@ -0,0 +1,51 @@
+# C compiler to use, including special flags.
+CC=gcc
+
+WARNINGS = -Wall -Wmissing-declarations -Wundef -Wimplicit -Wwrite-strings \
+	-Winline \
+	-Wstrict-prototypes -Wmissing-prototypes \
+	-Werror
+
+CFLAGS = $(WARNINGS) -fno-common -g
+INCLUDES = -I /home/bryanh/netpbm/other/importinc
+
+# X11 include and library information.
+X11_LIB_DIR=-L/subsysx/X11R6/lib
+X11_LIB_NAME=-lX11
+NETPBMLIB = /home/bryanh/netpbm/lib/libnetpbm.so
+
+LIBS=$(X11_LIB_DIR) $(X11_LIB_NAME) -lm
+
+default: pamx
+
+# files for the image library
+IMAGE_SRCS= image.c
+IMAGE_OBJS= ${IMAGE_SRCS:.c=.o}
+
+# files for the image processing library
+PROCESS_HDRS=
+# no image processing.
+PROCESS_SRCS= fill.c
+PROCESS_OBJS= ${PROCESS_SRCS:.c=.o}
+
+X_SRCS= send.c window.c pamx.c
+X_OBJS= ${X_SRCS:.c=.o}
+
+OBJS= $(IMAGE_OBJS) $(PROCESS_OBJS) $(X_OBJS) $(NETPBMLIB)
+
+.c.o: $*.c
+	$(CC) -c $(CFLAGS) $(INCLUDES) $*.c $(CADD)
+
+pamx: $(OBJS) $(OPTIONAL_LIBS)
+	$(CC) -o $@ $(OBJS) $(OPTIONAL_LIBS) $(LIBS)
+
+clean::
+	rm -f *.o pamx
+
+dep:
+	$(CC) -MM -MG $(INCLUDES) *.c >Makefile.depend
+
+include Makefile.depend
+
+Makefile.depend:
+	>$@
diff --git a/other/pamx/fill.c b/other/pamx/fill.c
new file mode 100644
index 00000000..13a2b21e
--- /dev/null
+++ b/other/pamx/fill.c
@@ -0,0 +1,82 @@
+/* 
+   fill an image area with a particular pixel value
+ 
+   By Jim Frost 1989.10.02, Bryan Henderson 2006.03.25.
+ 
+   See COPYRIGHT file for copyright information.
+*/
+
+#include "pm.h"
+#include "image.h"
+#include "valtomem.h"
+#include "fill.h"
+
+void
+fill(Image * const imageP,
+     unsigned int const fx,
+     unsigned int const fy,
+     unsigned int const fw,
+     unsigned int const fh,
+     Pixel        const pixval) {
+
+    assertGoodImage(imageP);
+    switch(imageP->type) {
+    case IBITMAP: {
+        unsigned int const linelen = (imageP->width +7)/ 8;
+        unsigned int const start = (fx +7) / 8;
+        unsigned char const startmask = 0x80 >> (fx % 8);
+
+        unsigned int y;
+        unsigned char * lineptr;
+
+        for (y = fy, lineptr = imageP->data + linelen * fy;
+             y < fy + fh;
+             ++y, lineptr += linelen) {
+
+            unsigned int x;
+            unsigned char mask;
+            unsigned char * pixptr;
+
+            mask = startmask;
+            pixptr = lineptr + start;
+
+            for (x = fx; x < fw; ++x) {
+                if (pixval)
+                    *pixptr |= mask;
+                else
+                    *pixptr &= ~mask;
+                if (!(mask >>= 1)) {
+                    mask = 0x80;
+                    ++pixptr;
+                }
+            }
+        }
+    } break;
+        
+  case IRGB:
+    case ITRUE: {
+        unsigned int const linelen= imageP->width * imageP->pixlen;
+        unsigned int const start = imageP->pixlen * fx;
+
+        unsigned int y;
+        unsigned char * lineptr;
+
+        for (y = fy, lineptr = imageP->data + (linelen * fy);
+             y < fy + fh;
+             ++y, lineptr += linelen) {
+
+            unsigned int x;
+            unsigned char * pixptr;
+
+            pixptr = lineptr + start;
+            for (x = fx, pixptr = lineptr + start;
+                 x < fw;
+                 ++x, pixptr += imageP->pixlen) {
+                valToMem(pixval, pixptr, imageP->pixlen);
+            }
+        }
+    } break;
+    default:
+        pm_error("INTERNAL ERROR: Impossible image type %u", imageP->type);
+    }
+}
diff --git a/other/pamx/fill.h b/other/pamx/fill.h
new file mode 100644
index 00000000..1f316d0b
--- /dev/null
+++ b/other/pamx/fill.h
@@ -0,0 +1,16 @@
+#ifndef FILL_H_INCLUDED
+#define FILL_H_INCLUDED
+
+#include "ximageinfo.h"
+
+struct Image;
+
+void
+fill(struct Image * const imageP,
+     unsigned int   const fx,
+     unsigned int   const fy,
+     unsigned int   const fw,
+     unsigned int   const fh,
+     Pixel          const pixval);
+
+#endif
diff --git a/other/pamx/image.c b/other/pamx/image.c
new file mode 100644
index 00000000..3aaa8478
--- /dev/null
+++ b/other/pamx/image.c
@@ -0,0 +1,331 @@
+/*
+   Functions to allocate and deallocate structures and structure data
+ 
+   By Jim Frost 1989.09.29, Bryan Henderson 2006.03.25.
+ 
+   See COPYRIGHT file for copyright information.
+*/
+
+#include <assert.h>
+
+#include "pm_c_util.h"
+#include "mallocvar.h"
+#include "nstring.h"
+#include "pam.h"
+#include "image.h"
+
+
+
+/* this table is useful for quick conversions between depth and ncolors */
+
+static unsigned long const DepthToColorsTable[] = {
+  /*  0 */ 1,
+  /*  1 */ 2,
+  /*  2 */ 4,
+  /*  3 */ 8,
+  /*  4 */ 16,
+  /*  5 */ 32,
+  /*  6 */ 64,
+  /*  7 */ 128,
+  /*  8 */ 256,
+  /*  9 */ 512,
+  /* 10 */ 1024,
+  /* 11 */ 2048,
+  /* 12 */ 4096,
+  /* 13 */ 8192,
+  /* 14 */ 16384,
+  /* 15 */ 32768,
+  /* 16 */ 65536,
+  /* 17 */ 131072,
+  /* 18 */ 262144,
+  /* 19 */ 524288,
+  /* 20 */ 1048576,
+  /* 21 */ 2097152,
+  /* 22 */ 4194304,
+  /* 23 */ 8388608,
+  /* 24 */ 16777216,
+  /* 25 */ 33554432,
+  /* 26 */ 67108864,
+  /* 27 */ 134217728,
+  /* 28 */ 268435456,
+  /* 29 */ 536870912,
+  /* 30 */ 1073741824,
+  /* 31 */ 2147483648u,
+  /* 32 */ 2147483648u /* bigger than unsigned int; this is good enough */
+};
+
+
+
+unsigned int
+depthToColors(unsigned int const depth) {
+    return DepthToColorsTable[MIN(depth,32)];
+}
+
+
+
+unsigned long
+colorsToDepth(unsigned long const ncolors) {
+
+    unsigned long a;
+
+    for (a = 0; (a < 32) && (DepthToColorsTable[a] < ncolors); ++a)
+        /* EMPTY */
+        ;
+    return a ;
+}
+
+
+
+void
+assertGoodImage(Image * const imageP) {
+
+    assert(imageP != NULL);
+
+    switch (imageP->type) {
+    case IBITMAP:
+    case IRGB:
+    case ITRUE:
+        break;
+    default:
+        assert(FALSE);  /* can't be */
+    }
+}
+
+
+
+static void
+newRGBMapData(RGBMap *     const rgbP,
+              unsigned int const size) {
+
+    rgbP->used = 0;
+    rgbP->size = size;
+    rgbP->compressed = FALSE;
+    MALLOCARRAY(rgbP->red, size);
+    MALLOCARRAY(rgbP->grn, size);
+    MALLOCARRAY(rgbP->blu, size);
+
+    if (rgbP->red == NULL || rgbP->grn == NULL || rgbP->blu == NULL)
+        pm_error("Out of memory allocating %u pixels", size);
+}
+
+
+
+static void
+freeRGBMapData(RGBMap * const rgbP) {
+
+    free(rgbP->red);
+    free(rgbP->grn);
+    free(rgbP->blu);
+}
+
+
+
+Image *
+newBitImage(unsigned int const width,
+            unsigned int const height) {
+
+    unsigned int const linelen = (width + 7) / 8;
+
+    Image * imageP;
+
+    MALLOCVAR_NOFAIL(imageP);
+
+    imageP->type = IBITMAP;
+    newRGBMapData(&imageP->rgb, 2);
+    imageP->rgb.red[0] = imageP->rgb.grn[0] = imageP->rgb.blu[0] = 65535;
+    imageP->rgb.red[1] = imageP->rgb.grn[1] = imageP->rgb.blu[1] = 0;
+    imageP->rgb.used = 2;
+    imageP->width = width;
+    imageP->height = height;
+    imageP->depth = 1;
+
+    if (UINT_MAX / linelen < height)
+        pm_error("Image dimensions too big to compute: %u x %u",
+                 linelen, height);
+    MALLOCARRAY(imageP->data, linelen * height);
+
+    if (imageP->data == NULL)
+        pm_error("Out of memory allocating array of %u x %u", linelen, height);
+
+    return imageP;
+}
+
+
+
+Image *
+newRGBImage(unsigned int const width,
+            unsigned int const height,
+            unsigned int const depth) {
+    
+    unsigned int const pixlen = pixlen > 0 ? (depth + 7) / 8 : 1;
+        /* Special case for "zero" depth image, which is sometimes
+           interpreted as "one color"
+        */
+    unsigned int const numcolors = depthToColors(depth);
+
+    Image * imageP;
+    
+    MALLOCVAR_NOFAIL(imageP);
+    imageP->type   = IRGB;
+    newRGBMapData(&imageP->rgb, numcolors);
+    imageP->width  = width;
+    imageP->height = height;
+    imageP->depth  = depth;
+    imageP->pixlen = pixlen;
+
+    if (UINT_MAX / width / height < pixlen)
+        pm_error("Image dimensions %u x %u x %u are too big to compute.",
+                 width, height, pixlen);
+    MALLOCARRAY(imageP->data, width * height * pixlen);
+    if (imageP->data == NULL)
+        pm_error("Unable to allocate %u x %u x %u raster array",
+                 width, height, pixlen);
+
+    return imageP;
+}
+
+
+
+Image *
+newTrueImage(unsigned int const width,
+             unsigned int const height) {
+
+    unsigned int const pixlen = 3;
+    
+    Image * imageP;
+
+    MALLOCVAR_NOFAIL(imageP);
+    imageP->type     = ITRUE;
+    imageP->rgb.used = 0;
+    imageP->rgb.size = 0;
+    imageP->width    = width;
+    imageP->height   = height;
+    imageP->depth    = 24;
+    imageP->pixlen   = 3;
+
+    if (UINT_MAX / width / height < pixlen)
+        pm_error("Image dimensions %u x %u x %u are too big to compute.",
+                 width, height, pixlen);
+    MALLOCARRAY(imageP->data, width * height * pixlen);
+    if (imageP->data == NULL)
+        pm_error("Unable to allocate %u x %u x %u raster array",
+                 width, height, pixlen);
+
+    return imageP;
+}
+
+
+
+static void
+freeImageData(Image * const imageP) {
+
+    if (!TRUEP(imageP))
+        freeRGBMapData(&imageP->rgb);
+    free(imageP->data);
+}
+
+
+
+void
+freeImage(Image * const imageP) {
+
+    assertGoodImage(imageP);
+
+    freeImageData(imageP);
+
+    imageP->type = IBAD;
+
+    free(imageP);
+}
+
+
+
+
+static void
+fillRow1(struct pam *     const pamP,
+         tuple *          const tuplerow,
+         unsigned char ** const pP) {
+
+    unsigned int col;
+    
+    for (col = 0; col < pamP->width; ++col) {
+        unsigned int plane;
+        for (plane = 0; plane < pamP->depth; ++plane)
+            *(*pP)++ =
+                pnm_scalesample(tuplerow[col][0], pamP->maxval, 255);
+    }
+}
+
+
+
+static void
+fillRow3(struct pam *     const pamP,
+         tuple *          const tuplerow,
+         unsigned char ** const pP) {
+
+    unsigned int col;
+    
+    for (col = 0; col < pamP->width; ++col) {
+        unsigned int plane;
+        for (plane = 0; plane < pamP->depth; ++plane)
+            *(*pP)++ =
+                pnm_scalesample(tuplerow[col][plane], pamP->maxval, 255);
+    }
+}
+
+
+
+Image *
+pbmLoad(const char * const fullname,
+        const char * const name,
+        bool         const verbose) {
+
+    FILE * ifP;
+    struct pam pam;
+    Image * imageP;
+    unsigned int row;
+    const char * filename;
+    tuple * tuplerow;
+    unsigned char * p;
+    enum {DEPTH_1, DEPTH_3} depth;
+
+    if (STREQ(fullname, "stdin"))
+        filename = "-";
+    else
+        filename = fullname;
+
+    ifP = pm_openr(filename);
+
+    pnm_readpaminit(ifP, &pam, PAM_STRUCT_SIZE(tuple_type));
+
+    if (strncmp(pam.tuple_type, "RGB", 3) == 0) {
+        depth = DEPTH_3;
+        if (pam.depth < 3)
+            pm_error("Invalid depth %u for RGB tuple type.", pam.depth);
+    } else
+        depth = DEPTH_1;
+
+    imageP = newTrueImage(pam.width, pam.height);
+
+    p = &imageP->data[0];  /* initial value */
+
+    tuplerow = pnm_allocpamrow(&pam);
+
+    for (row = 0; row < pam.height; ++row) {
+        pnm_readpamrow(&pam, tuplerow);
+        
+        switch (depth) {
+        case DEPTH_3:
+            fillRow3(&pam, tuplerow, &p);
+            break;
+        case DEPTH_1:
+            fillRow1(&pam, tuplerow, &p);
+            break;
+        }
+    }
+    pnm_freepamrow(tuplerow);
+    
+    pm_close(ifP);
+
+    return imageP;
+}
diff --git a/other/pamx/image.h b/other/pamx/image.h
new file mode 100644
index 00000000..9c9689ac
--- /dev/null
+++ b/other/pamx/image.h
@@ -0,0 +1,90 @@
+#ifndef IMAGE_H_INCLUDED
+#define IMAGE_H_INCLUDED
+#include <strings.h>
+
+#include "pm_c_util.h"
+
+#include "ximageinfo.h"
+
+typedef struct rgbmap {
+    unsigned int size;       /* size of RGB map */
+    unsigned int used;       /* number of colors used in RGB map */
+    bool         compressed; /* image uses colormap fully */
+    Intensity *  red;        /* color values in X style */
+    Intensity *  grn;
+    Intensity *  blu;
+} RGBMap;
+
+typedef struct Image {
+    unsigned int    type;   /* type of image */
+    RGBMap          rgb;    /* RGB map of image if IRGB type */
+    unsigned int    width;  /* width of image in pixels */
+    unsigned int    height; /* height of image in pixels */
+    unsigned int    depth;  /* depth of image in bits if IRGB type */
+    unsigned int    pixlen; /* length of pixel if IRGB type */
+    unsigned char * data;   /* data rounded to full byte for each row */
+} Image;
+
+#define IBAD    0 /* invalid image (used when freeing) */
+#define IBITMAP 1 /* image is a bitmap */
+#define IRGB    2 /* image is RGB */
+#define ITRUE   3 /* image is true color */
+
+#define BITMAPP(IMAGE) ((IMAGE)->type == IBITMAP)
+#define RGBP(IMAGE)    ((IMAGE)->type == IRGB)
+#define TRUEP(IMAGE)   ((IMAGE)->type == ITRUE)
+
+#define TRUE_RED(PIXVAL)   (((PIXVAL) & 0xff0000) >> 16)
+#define TRUE_GRN(PIXVAL) (((PIXVAL) & 0xff00) >> 8)
+#define TRUE_BLU(PIXVAL)  ((PIXVAL) & 0xff)
+#define RGB_TO_TRUE(R,G,B) \
+  (((unsigned int)((R) & 0xff00) << 8) | ((unsigned int)(G) & 0xff00) | \
+   ((unsigned int)(B) >> 8))
+
+unsigned long
+colorsToDepth(unsigned long const ncolors);
+
+void
+assertGoodImage(Image * const imageP);
+
+Image *
+newBitImage(unsigned int const width,
+            unsigned int const height);
+
+Image *
+newRGBImage(unsigned int const width,
+            unsigned int const height,
+            unsigned int const depth);
+
+Image *
+newTrueImage(unsigned int const width,
+             unsigned int const height);
+
+void
+freeImage(Image * const imageP);
+
+unsigned int
+depthToColors(unsigned int const depth);
+
+extern unsigned short RedIntensity[];
+extern unsigned short GreenIntensity[];
+extern unsigned short BlueIntensity[];
+
+static __inline__ unsigned int
+colorIntensity(unsigned int const red,
+               unsigned int const grn,
+               unsigned int const blu) {
+/*----------------------------------------------------------------------------
+  Return the (approximate) intensity of a color.
+-----------------------------------------------------------------------------*/
+    return (RedIntensity[red / 256] +
+            GreenIntensity[grn / 256] +
+            BlueIntensity[blu / 256]);
+}
+
+Image *
+pbmLoad(const char * const fullname,
+        const char * const name,
+        bool         const verbose);
+
+#endif
diff --git a/other/pamx/pamx.c b/other/pamx/pamx.c
new file mode 100644
index 00000000..8a64fec4
--- /dev/null
+++ b/other/pamx/pamx.c
@@ -0,0 +1,364 @@
+/* By Bryan Henderson 2006.03.25 
+
+   Copyright information is in the file COPYRIGHT
+*/
+
+#include <signal.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+
+#include "pam.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+#include "nstring.h"
+#include "filename.h"
+
+#include "ximageinfo.h"
+#include "image.h"
+#include "fill.h"
+#include "window.h"
+
+
+struct geometry {
+    bool specified;
+    const char * string;
+    unsigned int width;
+    unsigned int height;
+};
+
+struct cmdlineInfo {
+    const char * inputFileName;
+    const char * displayName;  /* NULL if none */
+    const char * display;
+    const char * title;
+    unsigned int fullscreen;
+    unsigned int verbose;
+    struct geometry geometry;
+    const char * foreground;
+    const char * background;
+    const char * border;
+    unsigned int install;
+    unsigned int private;
+    unsigned int pixmap;
+    unsigned int fit;
+    unsigned int visualSpec;
+    unsigned int visual;
+};
+
+
+
+static void 
+parseCommandLine(int argc, 
+                 char ** argv, 
+                 struct cmdlineInfo  * const 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 displaySpec, titleSpec, foregroundSpec, backgroundSpec,
+        borderSpec, geometrySpec;
+
+    const char * geometryOpt;
+    const char * visualOpt;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "fullscreen", OPT_FLAG,
+            NULL, &cmdlineP->fullscreen,     0);
+    OPTENT3(0, "install",    OPT_FLAG,
+            NULL, &cmdlineP->install,     0);
+    OPTENT3(0, "private",    OPT_FLAG,
+            NULL, &cmdlineP->private,     0);
+    OPTENT3(0, "fit",        OPT_FLAG,
+            NULL, &cmdlineP->fit,     0);
+    OPTENT3(0, "pixmap",     OPT_FLAG,
+            NULL, &cmdlineP->pixmap,     0);
+    OPTENT3(0, "verbose",    OPT_FLAG,
+            NULL, &cmdlineP->verbose,     0);
+    OPTENT3(0, "display",    OPT_STRING,
+            &cmdlineP->display, &displaySpec, 0);
+    OPTENT3(0, "title",      OPT_STRING,
+            &cmdlineP->title, &titleSpec,     0);
+    OPTENT3(0, "foreground", OPT_STRING,
+            &cmdlineP->foreground, &foregroundSpec,     0);
+    OPTENT3(0, "background", OPT_STRING,
+            &cmdlineP->background, &backgroundSpec,     0);
+    OPTENT3(0, "border",     OPT_STRING,
+            &cmdlineP->border, &borderSpec,     0);
+    OPTENT3(0, "geometry",   OPT_STRING,
+            &geometryOpt, &geometrySpec,     0);
+    OPTENT3(0, "visual",     OPT_STRING,
+            &visualOpt, &cmdlineP->visualSpec,     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 (geometrySpec) {
+        int rc;
+
+        cmdlineP->geometry.specified = true;
+        cmdlineP->geometry.string    = geometryOpt;
+
+        rc = sscanf(geometryOpt, "%ux%u",
+                    &cmdlineP->geometry.width, &cmdlineP->geometry.height);
+
+        if (rc != 2)
+            pm_error("Geometry value '%s' does not have the form WxH, "
+                     "where W and H are natural numbers", geometryOpt);
+
+        if (cmdlineP->geometry.width == 0)
+            pm_error("Width in -geometry option is zero");
+        if (cmdlineP->geometry.height == 0)
+            pm_error("Height in -geometry option is zero");
+    } else
+        cmdlineP->geometry.specified = false;
+
+    if (!displaySpec)
+        cmdlineP->display = NULL;
+
+    if (!titleSpec)
+        cmdlineP->title = NULL;
+
+    if (!foregroundSpec)
+        cmdlineP->foreground = NULL;
+
+    if (!backgroundSpec)
+        cmdlineP->background = NULL;
+
+    if (!borderSpec)
+        cmdlineP->border = NULL;
+
+    if (cmdlineP->visualSpec)
+        cmdlineP->visual = visualClassFromName(visualOpt);
+
+    if (argc-1 < 1)
+        cmdlineP->inputFileName = "-";
+    else if (argc-1 == 1)
+        cmdlineP->inputFileName = argv[1];
+    else
+        pm_error("Program takes at most one argument:  input file name");
+}
+
+
+
+static int
+errorHandler(Display *     const disp,
+             XErrorEvent * const error) {
+/*----------------------------------------------------------------------------
+   This is an X error handler
+-----------------------------------------------------------------------------*/
+    char errortext[BUFSIZ];
+
+    XGetErrorText(disp, error->error_code, errortext, sizeof(errortext));
+    pm_error("X Error: %s on resource 0x%x",
+             errortext, (unsigned)error->resourceid);
+    return 0;
+}
+
+
+
+static void
+fillRow1(struct pam *     const pamP,
+         tuple *          const tuplerow,
+         unsigned char ** const pP) {
+
+    unsigned int col;
+    
+    for (col = 0; col < pamP->width; ++col) {
+        unsigned int plane;
+        for (plane = 0; plane < pamP->depth; ++plane)
+            *(*pP)++ =
+                pnm_scalesample(tuplerow[col][0], pamP->maxval, 255);
+    }
+}
+
+
+
+static void
+fillRow3(struct pam *     const pamP,
+         tuple *          const tuplerow,
+         unsigned char ** const pP) {
+
+    unsigned int col;
+    
+    for (col = 0; col < pamP->width; ++col) {
+        unsigned int plane;
+        for (plane = 0; plane < pamP->depth; ++plane)
+            *(*pP)++ =
+                pnm_scalesample(tuplerow[col][plane], pamP->maxval, 255);
+    }
+}
+
+
+
+static void
+loadPamImage(FILE *   const ifP,
+             Image ** const imagePP) {
+
+    struct pam pam;
+    Image * imageP;
+    unsigned int row;
+    tuple * tuplerow;
+    unsigned char * p;
+    enum {DEPTH_1, DEPTH_3} depth;
+
+    pnm_readpaminit(ifP, &pam, PAM_STRUCT_SIZE(tuple_type));
+
+    if (strncmp(pam.tuple_type, "RGB", 3) == 0) {
+        depth = DEPTH_3;
+        if (pam.depth < 3)
+            pm_error("Invalid depth %u for RGB tuple type.", pam.depth);
+    } else
+        depth = DEPTH_1;
+
+    imageP = newTrueImage(pam.width, pam.height);
+
+    p = &imageP->data[0];  /* initial value */
+
+    tuplerow = pnm_allocpamrow(&pam);
+
+    for (row = 0; row < pam.height; ++row) {
+        pnm_readpamrow(&pam, tuplerow);
+        
+        switch (depth) {
+        case DEPTH_3:
+            fillRow3(&pam, tuplerow, &p);
+            break;
+        case DEPTH_1:
+            fillRow1(&pam, tuplerow, &p);
+            break;
+        }
+    }
+    pnm_freepamrow(tuplerow);
+
+    *imagePP = imageP;
+}
+
+
+#define BACKGROUND_IDX 0
+#define FOREGROUND_IDX 1
+
+
+static void
+processImage(Image *            const imageP,
+             struct cmdlineInfo const cmdline,
+             Display *          const dispP,
+             int                const scrn) {
+/*----------------------------------------------------------------------------
+   Modify image *imageP according to various command line options.
+-----------------------------------------------------------------------------*/
+    if (imageP->depth <= 1) {
+        if (cmdline.background) {
+            XColor color;
+            XParseColor(dispP, DefaultColormap(dispP, scrn),
+                        cmdline.background, &color);
+            imageP->rgb.red[BACKGROUND_IDX] = color.red;
+            imageP->rgb.grn[BACKGROUND_IDX] = color.green;
+            imageP->rgb.blu[BACKGROUND_IDX] = color.blue;
+        }
+        if (cmdline.foreground) {
+            XColor color;
+            XParseColor(dispP, DefaultColormap(dispP, scrn),
+                        cmdline.foreground, &color);
+            imageP->rgb.red[FOREGROUND_IDX] = color.red;
+            imageP->rgb.grn[FOREGROUND_IDX] = color.green;
+            imageP->rgb.blu[FOREGROUND_IDX] = color.blue;
+        }
+    }    
+}
+
+
+
+static void
+determineTitle(struct cmdlineInfo const cmdline,
+               const char **      const titleP) {
+    
+    const char * title;
+    
+    if (cmdline.title)
+        title = strdup(cmdline.title);
+    else {
+        if (STREQ(cmdline.inputFileName, "-"))
+            title = NULL;
+        else {
+            title = pm_basename(cmdline.inputFileName);
+        }
+    }
+    *titleP = title;
+}
+
+
+
+int
+main(int     argc,
+     char ** argv) {
+
+    struct cmdlineInfo cmdline;
+    FILE *       ifP;
+    Image *      imageP;
+    Display *    dispP;           /* display we're sending to */
+    int          scrn;           /* screen we're sending to */
+    int          retval;
+    const char * geometryString;
+    const char * title;
+    viewer * viewerP;
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFileName);
+    
+    dispP = XOpenDisplay(cmdline.display);
+    if (!dispP)
+        pm_error("Cannot open display '%s'", XDisplayName(cmdline.display));
+
+    scrn = DefaultScreen(dispP);
+    XSetErrorHandler(errorHandler);
+
+    geometryString =
+        cmdline.geometry.specified ? cmdline.geometry.string : NULL;
+
+    createViewer(&viewerP, dispP, scrn, geometryString, cmdline.fullscreen);
+
+    loadPamImage(ifP, &imageP);
+
+    processImage(imageP, cmdline, dispP, scrn);
+
+    determineTitle(cmdline, &title);
+
+    displayInViewer(viewerP, imageP,
+                    cmdline.install, cmdline.private, cmdline.fit,
+                    cmdline.pixmap, cmdline.visualSpec, cmdline.visual,
+                    title, cmdline.verbose,
+                    &retval);
+
+    freeImage(imageP);
+
+    destroyViewer(viewerP);
+
+    if (title)
+        strfree(title);
+
+    XCloseDisplay(dispP);
+
+    return retval;
+}
diff --git a/other/pamx/send.c b/other/pamx/send.c
new file mode 100644
index 00000000..4b1268a5
--- /dev/null
+++ b/other/pamx/send.c
@@ -0,0 +1,872 @@
+/*
+ 
+  Send an Image to an X pixmap
+
+
+  By Jim Frost 1989.10.02, Bryan Henderson 2006.03.25.
+ 
+  Copyright 1989, 1990, 1991 Jim Frost.
+  See COPYRIGHT file for copyright information.
+*/
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+
+#include "pm_c_util.h"
+#include "pm.h"
+#include "mallocvar.h"
+#include "ximageinfo.h"
+#include "valtomem.h"
+#include "image.h"
+#include "send.h"
+
+#define TRUE_TO_15BIT(PIXEL)     \
+    ((((PIXEL) & 0xf80000) >> 9) | \
+    (((PIXEL) & 0x00f800) >> 6) | \
+    (((PIXEL) & 0x0000f8) >> 3))
+
+#define RED_INTENSITY(P)   (((P) & 0x7c00) >> 10)
+#define GREEN_INTENSITY(P) (((P) & 0x03e0) >> 5)
+#define BLUE_INTENSITY(P)   ((P) & 0x001f)
+#define PM_SCALE(a, b, c) (long)((a) * (c))/(b)
+
+
+static bool GotError;
+
+static int
+pixmapErrorTrap(Display *     const disp,
+                XErrorEvent * const  pErrorEvent) {
+
+#define MAXERRORLEN 100
+    char buf[MAXERRORLEN+1];
+    GotError = 1;
+    XGetErrorText(disp, pErrorEvent->error_code, buf, MAXERRORLEN);
+    pm_message("serial #%ld (request code %d) Got Error '%s'",
+               pErrorEvent->serial,
+               pErrorEvent->request_code,
+               buf);
+    return 0;
+}
+
+
+
+Pixmap
+ximageToPixmap(Display *    const disp,
+               Window       const parent,
+               XImageInfo * const ximageinfoP) {
+
+    XErrorHandler old_handler;
+    Pixmap        pixmap;
+  
+    GotError = FALSE;
+    old_handler = XSetErrorHandler(pixmapErrorTrap);
+    XSync(disp, False);
+    pixmap= XCreatePixmap(disp, parent,
+                          ximageinfoP->ximageP->width,
+                          ximageinfoP->ximageP->height,
+                          ximageinfoP->depth);
+    XSetErrorHandler(old_handler);
+    if (GotError)
+        return None;
+    ximageinfoP->drawable = pixmap;
+    sendXImage(ximageinfoP, 0, 0, 0, 0,
+               ximageinfoP->ximageP->width, ximageinfoP->ximageP->height);
+
+    return pixmap;
+}
+
+
+
+/* find the best pixmap depth supported by the server for a particular
+ * visual and return that depth.
+ *
+ * this is complicated by R3's lack of XListPixmapFormats so we fake it
+ * by looking at the structure ourselves.
+ */
+
+static unsigned int
+bitsPerPixelAtDepth(Display *    const disp,
+                    int          const scrn,
+                    unsigned int const depth) {
+
+#if XlibSpecificationRelease < 4 /* the way things were */
+  unsigned int a;
+
+  for (a= 0; a < disp->nformats; a++)
+    if (disp->pixmap_format[a].depth == depth)
+      return(disp->pixmap_format[a].bits_per_pixel);
+
+#else /* the way things should be */
+  XPixmapFormatValues *xf;
+  unsigned int nxf, a;
+
+  xf = XListPixmapFormats(disp, (int *)&nxf);
+  for (a = 0; a < nxf; a++)
+    if (xf[a].depth == depth)
+      return(xf[a].bits_per_pixel);
+#endif
+
+  /* this should never happen; if it does, we're in trouble
+   */
+
+  fprintf(stderr, "bitsPerPixelAtDepth: Can't find pixmap depth info!\n");
+  exit(1);
+}
+     
+
+
+static Image *
+itrueToRGB(Image *      const imageP,
+           unsigned int const ddepth) {
+
+    int y, x, num_pixels, colors;
+    unsigned long pixel_counts[32786];
+    unsigned long pixel_array[32786];
+    Pixel pixval;
+    unsigned char * pixel;
+    unsigned char * dpixel;
+    Image * newImageP;
+    
+    newImageP = newRGBImage(imageP->width, imageP->height, ddepth);
+
+    colors = 1 << ddepth;
+  
+    bzero(pixel_counts, 32768 * sizeof(unsigned long));
+  
+    pixel= imageP->data;
+    for (y= 0; y < imageP->height; y++) {
+        unsigned int x;
+        for (x= 0; x < imageP->width; x++) {
+            unsigned int const z = TRUE_TO_15BIT(memToVal(pixel, 3));
+            pixel_counts[z]++;
+            pixel += 3;
+        }
+    }
+    num_pixels = 0;
+    for (x = 0; x < 32768; ++x) {
+        if (pixel_counts[x] > 0) {
+            unsigned long const red = RED_INTENSITY(x);
+            unsigned long const grn = GREEN_INTENSITY(x);
+            unsigned long const blu = BLUE_INTENSITY(x);
+            pixel_counts[x] = num_pixels;
+            *(newImageP->rgb.red + num_pixels) = red<<11;
+            *(newImageP->rgb.grn + num_pixels) = grn<<11; 
+            *(newImageP->rgb.blu + num_pixels) =  blu<<11;
+            pixel_array[num_pixels++] = (short)x;
+            if (num_pixels > colors)
+                break;
+        }
+    }    
+
+    pixel = imageP->data;
+    dpixel = newImageP->data;
+    
+    for (y = 0; y < imageP->height; ++y) {
+        unsigned int x;
+        for (x = 0; x < imageP->width; ++x) {
+            unsigned int const z = TRUE_TO_15BIT(memToVal(pixel, 3));
+            pixval = pixel_counts[z];
+            valToMem(pixval, dpixel, newImageP->pixlen);
+            pixel += 3;
+            dpixel += newImageP->pixlen;
+        }
+    }
+    newImageP->rgb.used = num_pixels;
+    newImageP->rgb.compressed = 1;
+
+    return newImageP;
+}
+
+
+
+static void
+makeUsableVisual(Image *      const origImageP,
+                 Visual *     const visualP,
+                 unsigned int const ddepth,
+                 Image **     const newImagePP) {
+
+    /* process image based on type of visual to which we're sending */
+
+    switch (origImageP->type) {
+    case ITRUE:
+        switch (visualP->class) {
+        case TrueColor:
+        case DirectColor:
+            /* goody goody */
+            *newImagePP = origImageP;
+            break;
+        case PseudoColor:
+            *newImagePP = itrueToRGB(origImageP, ddepth);
+            if (*newImagePP == NULL)
+                pm_error("Unable to convert for Pseudocolor.");
+            break;
+        default:
+            pm_error("INTERNAL ERROR: impossible visual class %u",
+                     visualP->class);
+        }
+        break;
+        
+    case IRGB:
+        switch(visualP->class) {
+        case TrueColor:
+        case DirectColor:
+            /* no problem, we handle this just fine */
+            *newImagePP = origImageP;
+            break;
+        default:
+            pm_error("INTERNAL ERROR: impossible visual class %u",
+                     visualP->class);
+        }
+        
+    case IBITMAP:
+        /* no processing ever needs to be done for bitmaps */
+        *newImagePP = origImageP;
+        break;
+    }
+}    
+
+
+
+static void
+makeColorMap1(Display *  const disp,
+              int        const scrn,
+              Visual *   const visualP,
+              Colormap * const cmapP,
+              Pixel **   const redvalueP,
+              Pixel **   const grnvalueP,
+              Pixel **   const bluvalueP) {
+    
+    Pixel * redvalue;
+    Pixel * grnvalue;
+    Pixel * bluvalue;
+    Pixel pixval;
+    unsigned int redcolors, grncolors, blucolors;
+    unsigned int redstep, grnstep, blustep;
+    unsigned int redbottom, grnbottom, blubottom;
+    unsigned int redtop, grntop, blutop;
+    unsigned int a;
+            
+    MALLOCARRAY_NOFAIL(redvalue, 256);
+    MALLOCARRAY_NOFAIL(grnvalue, 256);
+    MALLOCARRAY_NOFAIL(bluvalue, 256);
+            
+    if (visualP == DefaultVisual(disp, scrn))
+        *cmapP = DefaultColormap(disp, scrn);
+    else
+        *cmapP = XCreateColormap(disp, RootWindow(disp, scrn),
+                                 visualP, AllocNone);
+            
+ retry_direct: /* tag we hit if a DirectColor allocation fails on
+                * default colormap */
+            
+    /* calculate number of distinct colors in each band */
+            
+    redcolors = grncolors = blucolors = 1;
+    for (pixval = 1; pixval; pixval <<= 1) {
+        if (pixval & visualP->red_mask)
+            redcolors <<= 1;
+        if (pixval & visualP->green_mask)
+            grncolors <<= 1;
+        if (pixval & visualP->blue_mask)
+            blucolors <<= 1;
+    }
+            
+    /* sanity check */
+            
+    if ((redcolors > visualP->map_entries) ||
+        (grncolors > visualP->map_entries) ||
+        (blucolors > visualP->map_entries)) {
+        pm_message("Warning: inconsistency in color information "
+                   "(this may be ugly)");
+    }
+            
+    redstep= 256 / redcolors;
+    grnstep= 256 / grncolors;
+    blustep= 256 / blucolors;
+    redbottom = grnbottom = blubottom= 0;
+    for (a = 0; a < visualP->map_entries; ++a) {
+        XColor xcolor;
+        Status rc;
+        if (redbottom < 256)
+            redtop = redbottom + redstep;
+        if (grnbottom < 256)
+            grntop = grnbottom + grnstep;
+        if (blubottom < 256)
+            blutop = blubottom + blustep;
+                
+        xcolor.flags = DoRed | DoGreen | DoBlue;
+        xcolor.red   = (redtop - 1) << 8;
+        xcolor.green = (grntop - 1) << 8;
+        xcolor.blue  = (blutop - 1) << 8;
+        rc = XAllocColor(disp, *cmapP, &xcolor);
+        if (rc == 0) {
+            /* Allocation failed.  If it's for a DirectColor default
+               visual then we should create a private colormap
+               and try again.
+            */
+
+            if ((visualP->class == DirectColor) &&
+                (visualP == DefaultVisual(disp, scrn))) {
+                *cmapP = XCreateColormap(disp, RootWindow(disp, scrn),
+                                         visualP, AllocNone);
+                goto retry_direct;
+            }
+                    
+            /* something completely unexpected happened */
+                    
+            pm_error("INTERNAL ERROR: XAllocColor failed on a "
+                     "TrueColor/Directcolor visual");
+        }
+                
+        /* fill in pixel values for each band at this intensity */
+                
+        while ((redbottom < 256) && (redbottom < redtop))
+            redvalue[redbottom++] = xcolor.pixel & visualP->red_mask;
+        while ((grnbottom < 256) && (grnbottom < grntop))
+            grnvalue[grnbottom++] = xcolor.pixel & visualP->green_mask;
+        while ((blubottom < 256) && (blubottom < blutop))
+            bluvalue[blubottom++] = xcolor.pixel & visualP->blue_mask;
+    }
+    *redvalueP   = redvalue;
+    *grnvalueP   = grnvalue;
+    *bluvalueP   = bluvalue;
+}
+
+
+ 
+static void
+allocColorCells(Display *      const disp,
+                Colormap       const cmap,
+                Pixel *        const colorIndex,
+                unsigned int   const colorCount,
+                unsigned int * const cellCountP) {
+
+    bool outOfCells;
+    unsigned int cellCount;
+    
+    outOfCells = false;  /* initial value */
+    cellCount = 0;       /* initial value */
+    while (cellCount < colorCount && !outOfCells) {
+        Status rc;
+        rc = XAllocColorCells(disp, cmap, FALSE, NULL, 0,
+                              &colorIndex[cellCount++], 1);
+        if (rc == 0)
+            outOfCells = true;
+    }
+    *cellCountP = cellCount;
+}
+
+    
+
+
+static void
+makeColorMap2(Display *  const disp,
+              int        const scrn,
+              Visual *   const visualP,
+              RGBMap     const rgb,
+              bool       const userWantsPrivateCmap,
+              bool       const userWantsFit,
+              bool       const verbose,
+              Colormap * const cmapP,
+              Pixel **   const colorIndexP) {
+
+    bool privateCmap;
+    bool fit;
+    bool newmap;
+    Pixel * colorIndex;
+
+    MALLOCARRAY_NOFAIL(colorIndex, rgb.used);
+        
+    /* 'privateCmap' is invalid if not a dynamic visual */
+        
+    switch (visualP->class) {
+    case StaticColor:
+    case StaticGray:
+        privateCmap = TRUE;
+    default:
+        privateCmap = userWantsPrivateCmap;
+    }
+        
+    /* get the colormap to use. */
+        
+    if (privateCmap) { /* user asked us to use a private cmap */
+        newmap = TRUE;
+        fit = FALSE;
+    } else if ((visualP == DefaultVisual(disp, scrn)) ||
+               (visualP->class == StaticGray) ||
+               (visualP->class == StaticColor) ||
+               (visualP->class == TrueColor) ||
+               (visualP->class == DirectColor)) {
+            
+        unsigned int a;
+
+        fit = userWantsFit;
+
+        /* if we're using the default visual, try to alloc colors
+           shareable.  otherwise we're using a static visual and
+           should treat it accordingly.
+        */
+            
+        if (visualP == DefaultVisual(disp, scrn))
+            *cmapP = DefaultColormap(disp, scrn);
+        else
+            *cmapP = XCreateColormap(disp, RootWindow(disp, scrn),
+                                     visualP, AllocNone);
+        newmap = FALSE;
+            
+        /* allocate colors shareable (if we can) */
+            
+        for (a = 0; a < rgb.used; ++a) {
+            Status rc;
+            XColor  xcolor;
+
+            xcolor.flags = DoRed | DoGreen | DoBlue;
+            xcolor.red   = rgb.red[a];
+            xcolor.green = rgb.grn[a];
+            xcolor.blue  = rgb.blu[a];
+            rc = XAllocColor(disp, *cmapP, &xcolor);
+            if (rc == 0) {
+                if ((visualP->class == StaticColor) ||
+                    (visualP->class == StaticGray) ||
+                    (visualP->class == TrueColor) ||
+                    (visualP->class == DirectColor)) {
+                    pm_error("XAllocColor failed on a static visual");
+                } else {
+                    /* We can't allocate the colors shareable so
+                       free all the colors we had allocated and
+                       create a private colormap (or fit into the
+                       default cmap if `fit' is true).
+                    */
+                    XFreeColors(disp, *cmapP, colorIndex, a, 0);
+                    newmap = TRUE;
+                    break;
+                }
+            }
+            colorIndex[a] = xcolor.pixel;
+        }
+    } else {
+        newmap = TRUE;
+        fit    = FALSE;
+    }
+        
+    if (newmap) {
+        /* Either create a new colormap or fit the image into the
+           one we have.  To create a new one, we create a private
+           cmap and allocate the colors writable.  Fitting the
+           colors is harder; we have to:
+
+           1. grab the server so no one can goof with the colormap.
+           2. count the available colors using XAllocColorCells.
+           3. free the colors we just allocated.
+           4. reduce the depth of the image to fit.
+           5. allocate the colors again shareable.
+           6. ungrab the server and continue on our way.
+               
+           Someone should shoot the people who designed X color allocation.
+        */
+            
+        unsigned int a;
+
+        if (fit) {
+            if (verbose)
+                pm_message("Fitting image into default colormap");
+            XGrabServer(disp);
+        } else {
+            if (verbose)
+                pm_message("Using private colormap");
+                
+            /* create new colormap */
+                
+            *cmapP = XCreateColormap(disp, RootWindow(disp, scrn),
+                                     visualP, AllocNone);
+        }
+            
+        allocColorCells(disp, *cmapP, colorIndex, rgb.used, &a);
+
+        if (fit) {
+            if (a > 0)
+                XFreeColors(disp, *cmapP, colorIndex, a, 0);
+            if (a <= 2)
+                pm_error("Cannot fit into default colormap");
+        }
+            
+        if (a == 0)
+            pm_error("Color allocation failed!");
+            
+        if (fit) {
+            unsigned int a;
+            for (a = 0; a < rgb.used; ++a) {
+                XColor xcolor;
+                xcolor.flags = DoRed | DoGreen | DoBlue;
+                xcolor.red   = rgb.red[a];
+                xcolor.green = rgb.grn[a];
+                xcolor.blue  = rgb.blu[a];
+                
+                if (!XAllocColor(disp, *cmapP, &xcolor))
+                    pm_error("XAllocColor failed while fitting colormap!");
+                colorIndex[a] = xcolor.pixel;
+            }
+            XUngrabServer(disp);
+        } else {
+            unsigned int b;
+            for (b = 0; b < a; ++b) {
+                XColor xcolor;
+                xcolor.flags = DoRed | DoGreen | DoBlue;
+                xcolor.pixel = colorIndex[b];
+                xcolor.red   = rgb.red[b];
+                xcolor.green = rgb.grn[b];
+                xcolor.blue  = rgb.blu[b];
+                XStoreColor(disp, *cmapP, &xcolor);
+            }
+        }
+    }
+    *colorIndexP = colorIndex;
+}
+
+
+
+static void
+doColorAllocation(XImageInfo * const ximageinfoP,
+                  Display *    const disp,
+                  int          const scrn,
+                  Visual *     const visualP,
+                  Image *      const imageP,
+                  bool         const userWantsPrivateCmap,
+                  bool         const userWantsFit,
+                  bool         const verbose,
+                  Pixel **     const colorIndexP,
+                  Pixel **     const redvalP,
+                  Pixel **     const grnvalP,
+                  Pixel **     const bluvalP) {
+    
+    if ((visualP->class == TrueColor || visualP->class == DirectColor) &&
+        !BITMAPP(imageP)) {
+        makeColorMap1(disp, scrn, visualP, &ximageinfoP->cmap,
+                      redvalP, grnvalP, bluvalP);
+        *colorIndexP = NULL;
+    } else {
+        makeColorMap2(disp, scrn, visualP, imageP->rgb,
+                      userWantsPrivateCmap, userWantsFit, verbose,
+                      &ximageinfoP->cmap, colorIndexP);
+        
+        *redvalP = *grnvalP = *bluvalP = NULL;
+    }
+}
+    
+
+
+
+static void
+makeXImage(XImageInfo * const ximageinfoP,
+           Display *    const disp,
+           int          const scrn,
+           Visual *     const visualP,
+           unsigned int const ddepth,
+           Image *      const imageP,
+           Pixel        const colorIndex[],
+           Pixel        const redvalue[],
+           Pixel        const grnvalue[],
+           Pixel        const bluvalue[],
+           bool         const verbose) {
+/*----------------------------------------------------------------------------
+  Create an XImage and related colormap based on the image type we
+  have.
+-----------------------------------------------------------------------------*/
+    if (verbose)
+        pm_message("Building XImage...");
+
+    switch (imageP->type) {
+    case IBITMAP: {
+        unsigned int const byteCount =
+            (imageP->width + 7) / 8 * imageP->height;
+        unsigned char * data;
+
+        /* we copy the data to be more consistent */
+
+        MALLOCARRAY(data, byteCount);
+        if (data == NULL)
+            pm_error("Can't allocate space for %u byte image", byteCount);
+        bcopy(imageP->data, data, byteCount);
+
+        ximageinfoP->ximageP =
+            XCreateImage(disp, visualP, 1, XYBitmap,
+                         0, (char*)data, imageP->width, imageP->height, 8, 0);
+        ximageinfoP->depth = ddepth;
+        ximageinfoP->foreground = colorIndex[1];
+        ximageinfoP->background = colorIndex[0];
+        ximageinfoP->ximageP->bitmap_bit_order = MSBFirst;
+        ximageinfoP->ximageP->byte_order = MSBFirst;
+    } break;
+
+    case IRGB:
+    case ITRUE: {
+        /* Modify image data to match visual and colormap */
+        
+        unsigned int const dbits = bitsPerPixelAtDepth(disp, scrn, ddepth);
+        unsigned int const dpixlen = (dbits + 7) / 8;
+
+        ximageinfoP->depth = ddepth;
+        
+        switch (visualP->class) {
+        case DirectColor:
+        case TrueColor: {
+            unsigned char * data;
+            unsigned char * destptr;
+            unsigned char * srcptr;
+        
+            ximageinfoP->ximageP =
+                XCreateImage(disp, visualP, ddepth, ZPixmap, 0,
+                             NULL, imageP->width, imageP->height, 8, 0);
+            MALLOCARRAY(data, imageP->width * imageP->height * dpixlen);
+            if (data == NULL)
+                pm_error("Unable to allocate space for %u x %u x %u image",
+                         imageP->width, imageP->height, dpixlen);
+            ximageinfoP->ximageP->data = (char*)data;
+            destptr = data;
+            srcptr = imageP->data;
+            switch (imageP->type) {
+            case ITRUE: {
+                unsigned int y;
+                for (y= 0; y < imageP->height; ++y) {
+                    unsigned int x;
+                    for (x= 0; x < imageP->width; ++x) {
+                        Pixel const pixval = memToVal(srcptr, imageP->pixlen);
+                        Pixel const newpixval =
+                            redvalue[TRUE_RED(pixval)] |
+                            grnvalue[TRUE_GRN(pixval)] |
+                            bluvalue[TRUE_BLU(pixval)];
+                        valToMem(newpixval, destptr, dpixlen);
+                        srcptr += imageP->pixlen;
+                        destptr += dpixlen;
+                    }
+                }
+            } break;
+            case IRGB: {
+                unsigned int y;
+                for (y= 0; y < imageP->height; ++y) {
+                    unsigned int x;
+                    for (x = 0; x < imageP->width; ++x) {
+                        Pixel const pixval = memToVal(srcptr, imageP->pixlen);
+                        Pixel const newpixval =
+                            redvalue[imageP->rgb.red[pixval] >> 8] |
+                            grnvalue[imageP->rgb.grn[pixval] >> 8] |
+                            bluvalue[imageP->rgb.blu[pixval] >> 8];
+                        valToMem(newpixval, destptr, dpixlen);
+                        srcptr += imageP->pixlen;
+                        destptr += dpixlen;
+                    }
+                }
+            } break;
+            default: /* something's broken */
+                pm_error("INTERNAL ERROR: Unexpected image type %u for "
+                         "DirectColor/TrueColor visual!", imageP->type);
+            }
+            ximageinfoP->ximageP->byte_order = MSBFirst;
+                /* Trust me, I know what I'm talking about */
+        } break;
+
+        default: {
+            
+            /* only IRGB images make it this far. */
+
+            /* If our XImage doesn't have modulus 8 bits per pixel,
+               it's unclear how to pack bits so we instead use an
+               XYPixmap image.  This is slower.
+            */
+
+            if (dbits % 8) {
+                unsigned int const linelen = (imageP->width + 7) / 8;
+                unsigned int const size =
+                    imageP->width * imageP->height * dpixlen;
+                unsigned char * data;
+                unsigned char * destptr;
+                unsigned char * srcptr;
+                Pixel pixval;
+                unsigned int a;
+
+                ximageinfoP->ximageP =
+                    XCreateImage(disp, visualP, ddepth, XYPixmap, 0,
+                                 NULL, imageP->width, imageP->height, 8, 0);
+
+                MALLOCARRAY(data, size);
+                if (data == NULL)
+                    pm_error("Unable to allocate space for %u x %x x %u "
+                             "image", imageP->width, imageP->height, dpixlen);
+                ximageinfoP->ximageP->data = (char*)data;
+                bzero(data, size);
+                ximageinfoP->ximageP->bitmap_bit_order = MSBFirst;
+                ximageinfoP->ximageP->byte_order = MSBFirst;
+                for (a= 0; a < dbits; ++a) {
+                    Pixel const pixmask = 1 << a;
+                    unsigned char * const destdata = 
+                        data + ((ddepth - a - 1) * imageP->height * linelen);
+
+                    unsigned int y;
+
+                    srcptr = imageP->data;
+                    for (y= 0; y < imageP->height; ++y) {
+                        unsigned int x;
+                        unsigned char mask;
+                        destptr = destdata + (y * linelen);
+                        *destptr = 0;
+                        mask = 0x80;
+                        for (x= 0; x < imageP->width; ++x) {
+                            pixval = memToVal(srcptr, imageP->pixlen);
+                            srcptr += imageP->pixlen;
+                            if (colorIndex[pixval] & pixmask)
+                                *destptr |= mask;
+                            mask >>= 1;
+                            if (mask == 0) {
+                                mask = 0x80;
+                                ++destptr;
+                            }
+                        }
+                    }
+                }
+            } else {
+                unsigned int const dpixlen =
+                    (ximageinfoP->ximageP->bits_per_pixel + 7) / 8;
+
+                unsigned char * data;
+                unsigned char * srcptr;
+                unsigned char * destptr;
+                unsigned int y;
+
+                ximageinfoP->ximageP =
+                    XCreateImage(disp, visualP, ddepth, ZPixmap, 0,
+                                 NULL, imageP->width, imageP->height, 8, 0);
+
+                MALLOCARRAY(data, imageP->width * imageP->height * dpixlen);
+                if (data == NULL)
+                    pm_error("Failed to allocate space for %u x %u x %u image",
+                             imageP->width, imageP->height, dpixlen);
+                ximageinfoP->ximageP->data = (char*)data;
+                ximageinfoP->ximageP->byte_order= MSBFirst;
+                    /* Trust me, i know what I'm talking about */
+                srcptr = imageP->data;
+                destptr = data;
+                for (y= 0; y < imageP->height; ++y) {
+                    unsigned int x;
+                    for (x= 0; x < imageP->width; x++) {
+                        valToMem(colorIndex[memToVal(srcptr, imageP->pixlen)],
+                                 destptr, dpixlen);
+                        srcptr += imageP->pixlen;
+                        destptr += dpixlen;
+                    }
+                }
+            }
+        } break;
+        }   
+    } break;
+    }
+    if (verbose)
+        pm_message("done");
+}
+
+
+
+XImageInfo *
+imageToXImage(Display *    const disp,
+              int          const scrn,
+              Visual *     const visualP, /* visual to use */
+              unsigned int const ddepth, /* depth of the visual to use */
+              Image *      const origImageP,
+              bool         const userWantsPrivateCmap,
+              bool         const userWantsFit,
+              bool         const verbose) {
+
+    XImageInfo * ximageinfoP;
+    Image * imageP;
+    Pixel * colorIndex;
+    Pixel * redvalue;
+    Pixel * grnvalue;
+    Pixel * bluvalue;
+
+    assertGoodImage(origImageP);
+  
+    MALLOCVAR_NOFAIL(ximageinfoP);
+    ximageinfoP->disp = disp;
+    ximageinfoP->scrn = scrn;
+    ximageinfoP->depth = 0;
+    ximageinfoP->drawable = None;
+    ximageinfoP->foreground = ximageinfoP->background = 0;
+    ximageinfoP->gc = NULL;
+    ximageinfoP->ximageP = NULL;
+  
+    makeUsableVisual(origImageP, visualP, ddepth, &imageP);
+
+    assertGoodImage(imageP);
+
+    doColorAllocation(ximageinfoP, disp, scrn, visualP, imageP,
+                      userWantsPrivateCmap, userWantsFit, verbose,
+                      &colorIndex, &redvalue, &grnvalue, &bluvalue);
+
+    makeXImage(ximageinfoP, disp, scrn, visualP, ddepth, imageP,
+               colorIndex, redvalue, grnvalue, bluvalue, verbose);
+
+    if (colorIndex)
+        free(colorIndex);
+    if (redvalue) {
+        free(redvalue);
+        free(grnvalue);
+        free(bluvalue);
+    }
+    if (imageP != origImageP)
+        freeImage(imageP);
+    
+    return ximageinfoP;
+}
+
+
+
+void
+sendXImage(XImageInfo * const ximageinfoP,
+           int          const src_x,
+           int          const src_y,
+           int          const dst_x,
+           int          const dst_y,
+           unsigned int const w,
+           unsigned int const h) {
+/*----------------------------------------------------------------------------
+  Given an XImage and a drawable, move a rectangle from the Ximage
+  to the drawable.
+-----------------------------------------------------------------------------*/
+    XGCValues gcv;
+
+    /* build and cache the GC */
+    
+    if (!ximageinfoP->gc) {
+        gcv.function = GXcopy;
+        if (ximageinfoP->ximageP->depth == 1) {
+            gcv.foreground = ximageinfoP->foreground;
+            gcv.background= ximageinfoP->background;
+            ximageinfoP->gc =
+                XCreateGC(ximageinfoP->disp, ximageinfoP->drawable,
+                          GCFunction | GCForeground | GCBackground,
+                          &gcv);
+        }else
+            ximageinfoP->gc =
+                XCreateGC(ximageinfoP->disp, ximageinfoP->drawable,
+                          GCFunction, &gcv);
+    }
+    
+    XPutImage(ximageinfoP->disp, ximageinfoP->drawable, ximageinfoP->gc,
+              ximageinfoP->ximageP, src_x, src_y, dst_x, dst_y, w, h);
+}
+
+
+
+void
+freeXImage(Image *      const imageP,
+           XImageInfo * const ximageinfoP) {
+/*----------------------------------------------------------------------------
+  free up anything cached in the local Ximage structure.
+-----------------------------------------------------------------------------*/
+    if (ximageinfoP->gc)
+        XFreeGC(ximageinfoP->disp, ximageinfoP->gc);
+    free(ximageinfoP->ximageP->data);
+    ximageinfoP->ximageP->data = NULL;
+    XDestroyImage(ximageinfoP->ximageP);
+
+    free(ximageinfoP);
+}
diff --git a/other/pamx/send.h b/other/pamx/send.h
new file mode 100644
index 00000000..203b99c7
--- /dev/null
+++ b/other/pamx/send.h
@@ -0,0 +1,38 @@
+#ifndef SEND_H_INCLUDED
+#define SEND_H_INCLUDED
+
+#include <X11/Xlib.h>
+
+#include "pm_c_util.h"
+
+struct Image;
+
+void
+sendXImage(XImageInfo * const ximageinfoP,
+           int          const src_x,
+           int          const src_y,
+           int          const dst_x,
+           int          const dst_y,
+           unsigned int const w,
+           unsigned int const h);
+
+void
+freeXImage(struct Image * const imageP,
+           XImageInfo *   const ximageinfoP);
+
+XImageInfo *
+imageToXImage(Display *      const disp,
+              int            const scrn,
+              Visual *       const visualP, /* visual to use */
+              unsigned int   const ddepth, /* depth of the visual to use */
+              struct Image * const origImageP,
+              bool           const userWantsPrivateCmap,
+              bool           const userWantsFit,
+              bool           const verbose);
+
+Pixmap
+ximageToPixmap(Display *    const disp,
+               Window       const parent,
+               XImageInfo * const ximageinfo);
+
+#endif
diff --git a/other/pamx/valtomem.h b/other/pamx/valtomem.h
new file mode 100644
index 00000000..e27ce0df
--- /dev/null
+++ b/other/pamx/valtomem.h
@@ -0,0 +1,65 @@
+/*
+  By Jim Frost 1989.10.02.
+
+  Copyright 1989 Jim Frost.
+  See COPYRIGHT file for copyright information.
+*/
+#ifndef VALTOMEM_H_INCLUDED
+#define VALTOMEM_H_INCLUDED
+
+/* inline these functions for speed.  these only work for {len : 1,2,3,4}.
+ */
+
+#define memToVal(PTR,LEN) \
+  ((LEN) == 1 ? ((unsigned long)(*((unsigned char *)PTR))) : \
+   ((LEN) == 3 ? ((unsigned long) \
+          (*(unsigned char *)(PTR) << 16) | \
+          (*((unsigned char *)(PTR) + 1) << 8) | \
+          (*((unsigned char *)(PTR) + 2))) : \
+    ((LEN) == 2 ? ((unsigned long) \
+           (*(unsigned char *)(PTR) << 8) | \
+           (*((unsigned char *)(PTR) + 1))) : \
+     ((unsigned long)((*(unsigned char *)(PTR) << 24) | \
+              (*((unsigned char *)(PTR) + 1) << 16) | \
+              (*((unsigned char *)(PTR) + 2) << 8) | \
+              (*((unsigned char *)(PTR) + 3)))))))
+
+#define memToValLSB(PTR,LEN) \
+  ((LEN) == 1 ? ((unsigned long)(*(unsigned char *)(PTR))) : \
+   ((LEN) == 3 ? ((unsigned long) \
+          (*(unsigned char *)(PTR)) | \
+          (*((unsigned char *)(PTR) + 1) << 8) | \
+          (*((unsigned char *)(PTR) + 2) << 16)) : \
+    ((LEN) == 2 ? ((unsigned long) \
+           (*(unsigned char *)(PTR)) | (*((unsigned char *)(PTR) + 1) << 8)) : \
+     ((unsigned long)((*(unsigned char *)(PTR)) | \
+              (*((unsigned char *)(PTR) + 1) << 8) | \
+              (*((unsigned char *)(PTR) + 2) << 16) | \
+              (*((unsigned char *)(PTR) + 3) << 24))))))
+
+#define valToMem(VAL,PTR,LEN) \
+  ((LEN) == 1 ? (*(unsigned char *)(PTR) = ((unsigned int)(VAL) & 0xff)) : \
+   ((LEN) == 3 ? (((*(unsigned char *)(PTR)) = ((unsigned int)(VAL) & 0xff0000) >> 16), \
+          ((*((unsigned char *)(PTR) + 1)) = ((unsigned int)(VAL) & 0xff00) >> 8), \
+          ((*((unsigned char *)(PTR) + 2)) = ((unsigned int)(VAL) & 0xff))) : \
+    ((LEN) == 2 ? (((*(unsigned char *)(PTR)) = ((unsigned int)(VAL) & 0xff00) >> 8), \
+           ((*((unsigned char *)(PTR) + 1)) = ((unsigned int)(VAL) & 0xff))) : \
+     (((*(unsigned char *)(PTR)) = ((unsigned int)(VAL) & 0xff000000) >> 24), \
+      ((*((unsigned char *)(PTR) + 1)) = ((unsigned int)(VAL) & 0xff0000) >> 16), \
+      ((*((unsigned char *)(PTR) + 2)) = ((unsigned int)(VAL) & 0xff00) >> 8), \
+      ((*((unsigned char *)(PTR) + 3)) = ((unsigned int)(VAL) & 0xff))))))
+
+#define valToMemLSB(VAL,PTR,LEN) \
+  ((LEN) == 1 ? (*(unsigned char *)(PTR) = ((unsigned int)(VAL) & 0xff)) : \
+   ((LEN) == 3 ? (((*(unsigned char *)(PTR) + 2) = ((unsigned int)(VAL) & 0xff0000) >> 16), \
+          ((*((unsigned char *)(PTR) + 1)) = ((unsigned int)(VAL) & 0xff00) >> 8), \
+          ((*(unsigned char *)(PTR)) = ((unsigned int)(VAL) & 0xff))) : \
+    ((LEN) == 2 ? (((*((unsigned char *)(PTR) + 1) = ((unsigned int)(VAL) & 0xff00) >> 8), \
+            ((*(unsigned char *)(PTR)) = ((unsigned int)(VAL) & 0xff)))) : \
+     (((*((unsigned char *)(PTR) + 3)) = ((unsigned int)(VAL) & 0xff000000) >> 24), \
+      ((*((unsigned char *)(PTR) + 2)) = ((unsigned int)(VAL) & 0xff0000) >> 16), \
+      ((*((unsigned char *)(PTR) + 1)) = ((unsigned int)(VAL) & 0xff00) >> 8), \
+      ((*(unsigned char *)(PTR)) = ((unsigned int)(VAL) & 0xff))))))
+
+
+#endif
diff --git a/other/pamx/window.c b/other/pamx/window.c
new file mode 100644
index 00000000..1a6e510b
--- /dev/null
+++ b/other/pamx/window.c
@@ -0,0 +1,1209 @@
+/*
+   Functions to allocate and deallocate structures and structure data
+ 
+   By Jim Frost 1989.10.03, Bryan Henderson 2006.03.25.
+ 
+   See COPYRIGHT file for copyright information.
+*/
+
+#include <assert.h>
+#include <ctype.h>
+#include <signal.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <X11/cursorfont.h>
+#include <X11/Xatom.h>
+#include <X11/Xdefs.h>  /* Needed by Xutil.h */
+#include <X11/X.h>      /* Needed by Xutil.h */
+#include <X11/Xlib.h>   /* Needed by Xutil.h */
+#include <X11/Xutil.h>
+
+#include "mallocvar.h"
+#include "nstring.h"
+#include "pm.h"
+#include "ximageinfo.h"
+#include "send.h"
+#include "image.h"
+#include "window.h"
+
+/* A viewer object is something in which you can display an image.  You
+   can display multiple images in the same viewer, sequentially.
+
+   The viewer has a permanent window, called the viewport.  When you display
+   an image, it has a second window, called the image window, which is a
+   child of the viewport.
+*/
+
+struct viewer {
+    Display *    dispP;
+    int          scrn;
+    Window       imageWin;
+    Window       viewportWin;
+    Colormap     imageColormap;
+    Cursor       cursor;
+    unsigned int xpos, ypos;
+    unsigned int width, height;
+    bool         fullscreen;
+    bool         userChoseGeometry;
+    Atom         deleteAtom;
+    bool         blank;
+        /* I'm just guessing, but it seems that a "paint" operation is
+           necessary the first time a viewport is used, but not when
+           displaying a new image in an old viewport.  I assume that's
+           because the new viewport is blank, and that's what this
+           value means.
+        */
+    Pixmap       pixmap;
+};
+
+
+
+static void
+setXloadimageClassHint(Display * const dispP,
+                       Window    const window) {
+
+    XClassHint classhint;
+
+    classhint.res_class = (char*)"Xloadimage";
+    classhint.res_name  = (char*)"xloadimage";
+
+    XSetClassHint(dispP, window, &classhint);
+}
+
+
+
+static void
+setDeleteWindow(viewer * const viewerP) {
+
+    Atom const protoAtom = XInternAtom(viewerP->dispP, "WM_PROTOCOLS", False);
+    
+    viewerP->deleteAtom = XInternAtom(viewerP->dispP,
+                                      "WM_DELETE_WINDOW", False);
+    
+    if ((protoAtom != None) && (viewerP->deleteAtom != None))
+        XChangeProperty(viewerP->dispP, viewerP->viewportWin,
+                        protoAtom, XA_ATOM, 32, PropModeReplace,
+                        (unsigned char *)&viewerP->deleteAtom, 1);
+}
+
+
+
+static void
+getInitialViewerGeometry(const char *   const geometryString,
+                         bool           const fullscreen,
+                         Display *      const dispP,
+                         int            const scrn,
+                         unsigned int * const xposP,
+                         unsigned int * const yposP,
+                         unsigned int * const widthP,
+                         unsigned int * const heightP,
+                         bool *         const userChoseP) {
+
+    unsigned int const defaultWidth  = 10;
+    unsigned int const defaultHeight = 10;
+
+    if (fullscreen) {
+        *widthP  = DisplayWidth(dispP, scrn);
+        *heightP = DisplayHeight(dispP, scrn);
+        *xposP   = 0;
+        *yposP   = 0;
+        *userChoseP = TRUE;
+    } else if (geometryString) {
+        const char * defGeom;
+        asprintfN(&defGeom, "%ux%u+0+0", defaultWidth, defaultHeight);
+        XGeometry(dispP, scrn, geometryString, defGeom, 0, 1, 1, 0, 0,
+                  (int *)xposP, (int *)yposP,
+                  (int *)widthP, (int *)heightP);
+        strfree(defGeom);
+        *userChoseP = TRUE;
+    } else {
+        *widthP     = defaultWidth;
+        *heightP    = defaultHeight;
+        *xposP      = 0;
+        *yposP      = 0;
+        *userChoseP = FALSE;
+    }
+}
+
+
+
+void
+createViewer(viewer **     const viewerPP,
+             Display *     const dispP,
+             int           const scrn,
+             const char *  const geometryString,
+             bool          const fullscreen) {
+
+    viewer * viewerP;
+    
+    XSetWindowAttributes  swa_view;
+
+    MALLOCVAR_NOFAIL(viewerP);
+
+    viewerP->dispP       = dispP;
+    viewerP->scrn        = scrn;
+    viewerP->fullscreen  = fullscreen;
+    viewerP->imageWin    = 0;
+    viewerP->viewportWin = 0;
+
+    getInitialViewerGeometry(geometryString, fullscreen, dispP, scrn,
+                             &viewerP->xpos, &viewerP->ypos,
+                             &viewerP->width, &viewerP->height,
+                             &viewerP->userChoseGeometry);
+
+    viewerP->cursor = XCreateFontCursor(dispP, XC_watch);
+
+    swa_view.background_pixel = WhitePixel(dispP, scrn);
+    swa_view.backing_store    = NotUseful;
+    swa_view.cursor           = viewerP->cursor;
+    swa_view.event_mask       =
+        ButtonPressMask | Button1MotionMask | KeyPressMask |
+        StructureNotifyMask | EnterWindowMask | LeaveWindowMask;
+    swa_view.save_under       = FALSE;
+    
+    viewerP->viewportWin =
+        XCreateWindow(dispP, RootWindow(dispP, scrn),
+                      viewerP->xpos, viewerP->ypos,
+                      viewerP->width, viewerP->height, 0,
+                      DefaultDepth(dispP, scrn), InputOutput,
+                      DefaultVisual(dispP, scrn),
+                      CWBackingStore | CWBackPixel | CWCursor |
+                      CWEventMask | CWSaveUnder,
+                      &swa_view);
+
+    setXloadimageClassHint(viewerP->dispP, viewerP->viewportWin);
+    
+    setDeleteWindow(viewerP);
+
+    viewerP->blank = TRUE;
+
+    *viewerPP = viewerP;
+}
+
+
+
+static void
+determineRepaintStrategy(viewer  *    const viewerP,
+                         bool         const userWantsPixmap,
+                         bool         const verbose,
+                         XImageInfo * const ximageinfoP,
+                         Pixmap *     const pixmapP) {
+                        
+    /* Decide how we're going to handle repaints.  We have three modes:
+       use backing-store, use background pixmap, and use exposures.
+       If the server supports backing-store, we enable it and use it.
+       This really helps servers which are memory constrained.  If the
+       server does not have backing-store, we try to send the image to
+       a pixmap and use that as backing-store.  If that fails, we use
+       exposures to blit the image (which is ugly but it works).
+       
+       'use_pixmap' forces background pixmap mode, which may improve
+       performance.
+    */
+
+    ximageinfoP->drawable = viewerP->imageWin;
+    if ((DoesBackingStore(ScreenOfDisplay(viewerP->dispP, viewerP->scrn)) ==
+         NotUseful) ||
+        userWantsPixmap) {
+        *pixmapP = ximageToPixmap(viewerP->dispP, viewerP->imageWin,
+                                  ximageinfoP);
+        if (*pixmapP == None && verbose)
+            pm_message("Cannot create image in server; "
+                       "repaints will be ugly!");
+    } else
+        *pixmapP = None;
+}
+
+
+
+static void
+setImageWindowAttr(Display * const dispP,
+                   int       const scrn,
+                   Window    const imageWindow,
+                   Colormap  const cmap,
+                   Pixmap    const pixmap) {
+
+    /* build window attributes for the image window */
+
+    XSetWindowAttributes swa_img;
+    unsigned int         wa_mask_img;
+
+    swa_img.bit_gravity  = NorthWestGravity;
+    swa_img.save_under   = FALSE;
+    swa_img.colormap     = cmap;
+    swa_img.border_pixel = 0;
+
+    wa_mask_img = 0;  /* initial value */
+
+    if (pixmap == None) {
+        /* No pixmap.  Must paint over the wire.  Ask for BackingStore
+           to cut down on the painting.  But, ask for Exposures so we can
+           paint both viewables and backingstore.
+        */
+
+        swa_img.background_pixel = WhitePixel(dispP,scrn);
+        wa_mask_img |= CWBackPixel;
+        swa_img.event_mask = ExposureMask;
+        wa_mask_img |= CWEventMask;
+        swa_img.backing_store = WhenMapped;
+        wa_mask_img |= CWBackingStore;
+    } else {
+        /* We have a pixmap so tile the window.  to move the image we only
+           have to move the window and the server should do the rest.
+         */
+
+        swa_img.background_pixmap = pixmap;
+        wa_mask_img |= CWBackPixmap;
+        swa_img.event_mask = 0; /* no exposures please */
+        wa_mask_img |= CWEventMask;
+        swa_img.backing_store = NotUseful;
+        wa_mask_img |= CWBackingStore;
+    }
+    XChangeWindowAttributes(dispP, imageWindow, wa_mask_img, &swa_img);
+}
+
+
+
+static void
+createImageWindow(viewer *      const viewerP,
+                  XImageInfo *  const ximageInfoP,
+                  Image *       const imageP,
+                  Visual *      const visualP,
+                  bool          const userWantsPixmap,
+                  bool          const verbose) {
+
+    XSetWindowAttributes  swa_img;
+
+    swa_img.bit_gravity  = NorthWestGravity;
+    swa_img.save_under   = FALSE;
+    swa_img.colormap     = ximageInfoP->cmap;
+    swa_img.border_pixel = 0;
+    viewerP->imageWin = XCreateWindow(viewerP->dispP, viewerP->viewportWin,
+                                      viewerP->xpos, viewerP->ypos,
+                                      imageP->width, imageP->height, 0,
+                                      ximageInfoP->depth, InputOutput, visualP,
+                                      CWBitGravity | CWColormap | CWSaveUnder |
+                                      CWBorderPixel, &swa_img);
+    viewerP->imageColormap = ximageInfoP->cmap;
+    setXloadimageClassHint(viewerP->dispP, viewerP->imageWin);
+
+    determineRepaintStrategy(viewerP, userWantsPixmap, verbose, ximageInfoP,
+                             &viewerP->pixmap);
+
+    setImageWindowAttr(viewerP->dispP, viewerP->scrn,
+                       viewerP->imageWin, ximageInfoP->cmap,
+                       viewerP->pixmap);
+}
+
+
+
+static void
+destroyImageWindow(viewer * const viewerP) {
+    
+    if (viewerP->imageWin) {
+        if (viewerP->imageColormap &&
+            (viewerP->imageColormap != DefaultColormap(viewerP->dispP, viewerP->scrn)))
+            XFreeColormap(viewerP->dispP, viewerP->imageColormap);
+        XDestroyWindow(viewerP->dispP, viewerP->imageWin);
+    }
+}
+                       
+
+
+static void
+changeCursor(viewer *      const viewerP,
+             unsigned int  const imageWidth,
+             unsigned int  const imageHeight) {
+
+    Cursor cursor;
+    XSetWindowAttributes swa;
+
+    if ((viewerP->width >= imageWidth) && (viewerP->height >= imageHeight))
+        cursor = XCreateFontCursor(viewerP->dispP, XC_icon);
+    else if ((viewerP->width < imageWidth) && (viewerP->height >= imageHeight))
+        cursor = XCreateFontCursor(viewerP->dispP, XC_sb_h_double_arrow);
+    else if ((viewerP->width >= imageWidth) && (viewerP->height < imageHeight))
+        cursor = XCreateFontCursor(viewerP->dispP, XC_sb_v_double_arrow);
+    else
+        cursor = XCreateFontCursor(viewerP->dispP, XC_fleur);
+
+    swa.cursor = cursor;
+    XChangeWindowAttributes(viewerP->dispP, viewerP->viewportWin,
+                            CWCursor, &swa);
+
+    XFreeCursor(viewerP->dispP, viewerP->cursor);
+
+    viewerP->cursor = cursor;
+}
+
+
+
+static void
+placeImage(viewer * const viewerP,
+           int      const width,
+           int      const height,
+           int *    const rxP,     /* input and output */
+           int *    const ryP) {   /* input and output */
+
+    int pixx, pixy;
+    
+    pixx = *rxP;
+    pixy = *ryP;
+    
+    if (viewerP->width > width)
+        pixx = (viewerP->width - width) / 2;
+    else {
+        if ((pixx < 0) && (pixx + width < viewerP->width))
+            pixx = viewerP->width - width;
+        if (pixx > 0)
+            pixx = 0;
+    }
+    if (viewerP->height > height)
+        pixy = (viewerP->height - height) / 2;
+    else {
+        if ((pixy < 0) && (pixy + height < viewerP->height))
+            pixy = viewerP->height - viewerP->height;
+        if (pixy > 0)
+            pixy = 0;
+    }
+    XMoveWindow(viewerP->dispP, viewerP->imageWin, pixx, pixy);
+
+    *rxP = pixx;
+    *ryP = pixy;
+}
+
+
+
+static void
+blitImage(XImageInfo * const ximageinfoP,
+          unsigned int const width,
+          unsigned int const height,
+          int          const xArg,
+          int          const yArg,
+          int          const wArg,
+          int          const hArg) {
+
+    int w, h;
+    int x, y;
+
+    w = MIN(wArg, width);
+    h = MIN(hArg, height);
+
+    x = xArg;
+    y = yArg;
+
+    if (x < 0) {
+        XClearArea(ximageinfoP->disp, ximageinfoP->drawable,
+                   x, y, -x, h, False);
+        w -= (0 - x);
+        x = 0;
+    }
+    if (y < 0) {
+        XClearArea(ximageinfoP->disp, ximageinfoP->drawable,
+                   x, y, w, -y, False);
+        h -= (0 - y);
+        y = 0;
+    }
+    if (x + w > width) {
+        XClearArea(ximageinfoP->disp, ximageinfoP->drawable,
+                   x + width, y, x + w - width, h, False);
+        w -= x + w - width;
+    }
+    if (y + h > height) {
+        XClearArea(ximageinfoP->disp, ximageinfoP->drawable,
+                   x, y + height, w, y + h - height, False);
+        h -= y + h - height;
+    }
+    sendXImage(ximageinfoP, x, y, x, y, w, h);
+}
+
+
+
+void
+destroyViewer(viewer * const viewerP) {
+/*----------------------------------------------------------------------------
+   Clean up static window.
+-----------------------------------------------------------------------------*/
+    if (viewerP->pixmap != None)
+        XFreePixmap(viewerP->dispP, viewerP->pixmap);
+
+    if (viewerP->imageWin)
+        XDestroyWindow(viewerP->dispP, viewerP->imageWin);
+    viewerP->imageWin = 0;
+    
+    if (viewerP->viewportWin)
+        XDestroyWindow(viewerP->dispP, viewerP->viewportWin);
+    
+    viewerP->viewportWin = 0;
+
+    XFreeCursor(viewerP->dispP, viewerP->cursor);
+
+    free(viewerP);
+}
+
+
+
+static void
+setViewportColormap(viewer *  const viewerP,
+                    Visual *  const visualP) {
+/*----------------------------------------------------------------------------
+  Set the colormap and WM_COLORMAP_WINDOWS properly for the viewport.
+-----------------------------------------------------------------------------*/
+    static Atom cmap_atom = None;
+    XSetWindowAttributes swa;
+    Window cmap_windows[2];
+
+    if (cmap_atom == None)
+        cmap_atom = XInternAtom(viewerP->dispP, "WM_COLORMAP_WINDOWS", False);
+    
+    /* If the visual we're using is the same as the default visual (used by
+       the viewport window) then we can set the viewport window to use the
+       image's colormap.  This keeps most window managers happy.
+    */
+    
+    if (visualP == DefaultVisual(viewerP->dispP, viewerP->scrn)) {
+        swa.colormap = viewerP->imageColormap;
+        XChangeWindowAttributes(viewerP->dispP, viewerP->viewportWin,
+                                CWColormap, &swa);
+        XDeleteProperty(viewerP->dispP, viewerP->viewportWin, cmap_atom);
+    } else {
+        /* Smart window managers can handle it when we use a different colormap
+           in our subwindow so long as we set the WM_COLORMAP_WINDOWS property
+           ala ICCCM.
+        */
+        cmap_windows[0] = viewerP->imageWin;
+        cmap_windows[1] = viewerP->viewportWin;
+        XChangeProperty(viewerP->dispP, viewerP->viewportWin,
+                        cmap_atom, XA_WINDOW, 32,
+                        PropModeReplace, (unsigned char *)cmap_windows, 2);
+    }
+}
+
+
+
+static const char *
+iconName(const char * const s) {
+/*----------------------------------------------------------------------------
+   Return an icon name suitable for an image titled 's'.
+
+   s == NULL means untitled.
+-----------------------------------------------------------------------------*/
+    const char * retval;
+    char buf[BUFSIZ];
+
+    if (!s)
+        STRSCPY(buf, "Unnamed");
+    else {
+        char * t;
+
+        STRSCPY(buf, s);
+        /* strip off stuff following 1st word.  This strips
+           info added by processing functions too.
+        */
+        t = index(buf, ' ');
+        if (t)
+            *t = '\0';
+    
+        /* Strip off leading path.  if you don't use unix-style paths,
+           You might want to change this.
+        */
+    
+        t= rindex(buf, '/');
+        if (t) {
+            char * p;
+            for (p = buf, ++t; *t; ++p, ++t)
+                *p = *t;
+            *p = '\0';
+        }
+        /* look for an extension and strip it off */
+        t = index(buf, '.');
+        if (t)
+            *t = '\0';
+    }
+
+    retval = strdup(buf);
+    if (retval == NULL)
+        pm_error("Out of memory");
+    return retval;
+}
+
+
+
+/* visual class to name table  */
+
+static struct visual_class_name {
+    int          class; /* numerical value of class */
+    const char * name;  /* actual name of class */
+} const VisualClassName[] = {
+    {TrueColor,   "TrueColor"   } ,
+    {DirectColor, "DirectColor" } ,
+    {PseudoColor, "PseudoColor" } ,
+    {StaticColor, "StaticColor" } ,
+    {GrayScale,   "GrayScale"   } ,
+    {StaticGray,  "StaticGray"  } ,
+    {StaticGray,  "StaticGrey"  } ,
+    {-1,          NULL          }
+};
+
+
+
+int
+visualClassFromName(const char * const name) {
+
+    unsigned int a;
+    int class;
+    bool found;
+    
+    for (a = 0, found = FALSE; VisualClassName[a].name; ++a) {
+        if (STRCASEEQ(VisualClassName[a].name, name)) {
+            /* Check for uniqueness.  We special-case StaticGray
+               because we have two spellings but they are unique if
+               we find either.
+            */
+            if (found && class != StaticGray)
+                pm_error("'%s' does not uniquely describe a visual class",
+                         name);
+
+            class = VisualClassName[a].class;
+            found = TRUE;
+        }
+    }
+    if (!found)
+        pm_error("'%s' is not a visual class name", name);
+
+    return class;
+}
+
+
+
+static const char *
+nameOfVisualClass(int const class) {
+
+    unsigned int a;
+    const char * name;
+    bool found;
+
+    for (a = 0, found = FALSE; VisualClassName[a].name; ++a) {
+        if (VisualClassName[a].class == class) {
+            name = VisualClassName[a].name;
+            found = TRUE;
+        }
+    }
+    if (found)
+        return name;
+    else
+        return "[Unknown Visual Class]";
+}
+
+
+
+/* find the best visual of a particular class with a particular depth
+ */
+
+static Visual *
+bestVisualOfClassAndDepth(Display *    const disp,
+                          int          const scrn,
+                          int          const class,
+                          unsigned int const depth) {
+
+    long const vinfoMask =
+        VisualScreenMask | VisualClassMask | VisualDepthMask;
+
+    Visual * best;
+    XVisualInfo template;
+    XVisualInfo * infoP;
+    unsigned int nvisuals;
+
+    best = NULL;  /* initial value */
+
+    template.screen = scrn;
+    template.class  = class;
+    template.depth  = depth;
+    infoP = XGetVisualInfo(disp, vinfoMask, &template, (int*)&nvisuals);
+    if (infoP) {
+        /* There are visuals with this depth */
+
+        /* Not sure what to do if this gives more than one visual of a
+           particular class and depth, so just return the first one.
+        */
+
+        best = infoP->visual;
+        XFree((char *)infoP);
+    }
+    return best;
+}
+
+
+
+static void
+bestVisual(Display *      const disp,
+           int            const scrn,
+           Image *        const imageP,
+           Visual **      const rvisualPP,
+           unsigned int * const rdepthP) {
+/*----------------------------------------------------------------------------
+  Try to determine the best available visual to use for a particular
+  image
+-----------------------------------------------------------------------------*/
+    unsigned int  depth, a;
+    Screen * screen;
+    Visual * visualP;
+    Visual * default_visualP;
+
+    /* Figure out the best depth the server supports.  note that some servers
+       (such as the HP 11.3 server) actually say they support some depths but
+       have no visuals that support that depth.  Seems silly to me ...
+    */
+
+    depth = 0;
+    screen = ScreenOfDisplay(disp, scrn);
+    for (a = 0; a < screen->ndepths; ++a) {
+        if (screen->depths[a].nvisuals &&
+            ((!depth ||
+              ((depth < imageP->depth) && (screen->depths[a].depth > depth)) ||
+              ((screen->depths[a].depth >= imageP->depth) &&
+               (screen->depths[a].depth < depth)))))
+            depth = screen->depths[a].depth;
+    }
+    if (!depth) {
+        /* this shouldn't happen */
+        pm_message("bestVisual: didn't find any depths?!?");
+        depth = DefaultDepth(disp, scrn);
+    }
+    
+    /* given this depth, find the best possible visual
+     */
+    
+    default_visualP = DefaultVisual(disp, scrn);
+    switch (imageP->type) {
+    case ITRUE: {
+        /* If the default visual is DirectColor or TrueColor prioritize such
+           that we use the default type if it exists at this depth
+        */
+
+        if (default_visualP->class == TrueColor) {
+            visualP = bestVisualOfClassAndDepth(disp, scrn, TrueColor, depth);
+            if (!visualP)
+                visualP = bestVisualOfClassAndDepth(disp, scrn,
+                                                    DirectColor, depth);
+        } else {
+            visualP = bestVisualOfClassAndDepth(disp, scrn, DirectColor,
+                                                depth);
+            if (!visualP)
+                visualP = bestVisualOfClassAndDepth(disp, scrn, TrueColor,
+                                                    depth);
+        }
+        
+        if (!visualP || ((depth <= 8) &&
+                         bestVisualOfClassAndDepth(disp, scrn, PseudoColor,
+                                                   depth)))
+            visualP = bestVisualOfClassAndDepth(disp, scrn, PseudoColor,
+                                                depth);
+        if (!visualP)
+            visualP = bestVisualOfClassAndDepth(disp, scrn, StaticColor,
+                                                depth);
+        if (!visualP)
+            visualP = bestVisualOfClassAndDepth(disp, scrn, GrayScale, depth);
+        if (!visualP)
+            visualP = bestVisualOfClassAndDepth(disp, scrn, StaticGray, depth);
+    } break;
+        
+    case IRGB: {
+        /* if it's an RGB image, we want PseudoColor if we can get it */
+
+        visualP = bestVisualOfClassAndDepth(disp, scrn, PseudoColor, depth);
+        if (!visualP)
+            visualP = bestVisualOfClassAndDepth(disp, scrn, DirectColor,
+                                                depth);
+        if (!visualP)
+            visualP = bestVisualOfClassAndDepth(disp, scrn, TrueColor, depth);
+        if (!visualP)
+            visualP = bestVisualOfClassAndDepth(disp, scrn, StaticColor,
+                                                depth);
+        if (!visualP)
+            visualP = bestVisualOfClassAndDepth(disp, scrn, GrayScale, depth);
+        if (!visualP)
+            visualP = bestVisualOfClassAndDepth(disp, scrn, StaticGray, depth);
+    } break;
+
+    case IBITMAP: {
+        visualP = bestVisualOfClassAndDepth(disp, scrn, PseudoColor, depth);
+        if (!visualP)
+            visualP = bestVisualOfClassAndDepth(disp, scrn, StaticColor,
+                                                depth);
+        if (!visualP)
+            visualP = bestVisualOfClassAndDepth(disp, scrn, GrayScale, depth);
+        if (!visualP)
+            visualP = bestVisualOfClassAndDepth(disp, scrn, StaticGray, depth);
+
+        /* It seems pretty wasteful to use a TrueColor or DirectColor visual
+           to display a bitmap (2-color) image, so we look for those last
+        */
+
+        if (!visualP)
+            visualP = bestVisualOfClassAndDepth(disp, scrn, DirectColor,
+                                                depth);
+        if (!visualP)
+            visualP = bestVisualOfClassAndDepth(disp, scrn, TrueColor, depth);
+    } break;
+    }
+
+    if (!visualP) { /* this shouldn't happen */
+        pm_message("bestVisual: couldn't find one?!?");
+        depth = DefaultDepth(disp, scrn);
+        visualP = DefaultVisual(disp, scrn);
+    }
+    *rvisualPP = visualP;
+    *rdepthP   = depth;
+}
+
+
+
+static void
+bestVisualOfClass(Display *      const disp,
+                  int            const scrn,
+                  Image *        const image,
+                  int            const visual_class,
+                  Visual **      const rvisual,
+                  unsigned int * const rdepth) {
+/*----------------------------------------------------------------------------
+  Given a visual class, try to find the best visual of that class at
+  the best depth.  Return a null visual and depth if we couldn't find
+  any visual of that type at any depth.
+-----------------------------------------------------------------------------*/
+  Visual       *visual;
+  Screen       *screen;
+  unsigned int  a, b, depth;
+
+  /* loop through depths looking for a visual of a good depth which matches
+   * our visual class.
+   */
+
+  screen= ScreenOfDisplay(disp, scrn);
+  visual= (Visual *)NULL;
+  depth= 0;
+  for (a= 0; a < screen->ndepths; a++) {
+    for (b= 0; b < screen->depths[a].nvisuals; b++) {
+      if ((screen->depths[a].visuals[b].class == visual_class) &&
+      (!depth ||
+       ((depth < image->depth) && (screen->depths[a].depth > depth)) ||
+       ((screen->depths[a].depth >= image->depth) &&
+        (screen->depths[a].depth < depth)))) {
+    depth= screen->depths[a].depth;
+    visual= &(screen->depths[a].visuals[b]);
+      }
+    }
+  }
+  *rvisual= visual;
+  *rdepth= depth;
+}
+
+
+
+static void
+getImageDispDimensions(viewer *       const viewerP,
+                       Image *        const imageP,
+                       unsigned int * const widthP,
+                       unsigned int * const heightP) {
+
+    if (viewerP->userChoseGeometry) {
+        *widthP  = viewerP->width;
+        *heightP = viewerP->height;
+    } else {
+        unsigned int const displayWidth = 
+            DisplayWidth(viewerP->dispP, viewerP->scrn);
+        unsigned int const displayHeight =
+            DisplayHeight(viewerP->dispP, viewerP->scrn);
+
+        /* We don't use more than 90% of display real estate unless user
+           explicitly asked for it.
+        */
+        *widthP = MIN(imageP->width, (unsigned)(displayWidth * 0.9));
+        *heightP = MIN(imageP->height, (unsigned)(displayHeight * 0.9));
+    }
+}
+
+
+
+static void
+getVisualAndDepth(Image *        const imageP,
+                  Display *      const dispP,
+                  int            const scrn,
+                  bool           const fit,
+                  bool           const visualSpec,
+                  unsigned int   const visualClass,
+                  Visual **      const visualPP,
+                  unsigned int * const depthP) {
+
+
+    /* If the user told us to fit the colormap, we must use the default
+       visual.
+    */
+
+    if (fit) {
+        *visualPP = DefaultVisual(dispP, scrn);
+        *depthP   = DefaultDepth(dispP, scrn);
+    } else {
+        Visual * visualP;
+        unsigned int depth;
+
+        visualP = NULL;
+        if (!visualSpec) {
+            /* Try to pick the best visual for the image. */
+
+            bestVisual(dispP, scrn, imageP, &visualP, &depth);
+        } else {
+            /* Try to find a visual of the specified class */
+
+            bestVisualOfClass(dispP, scrn, imageP, visualClass,
+                              &visualP, &depth);
+            if (!visualP) {
+                bestVisual(dispP, scrn, imageP, &visualP, &depth);
+                pm_message("Server does not provide %s visual, using %s",
+                           nameOfVisualClass(visualClass),
+                           nameOfVisualClass(visualP->class));
+            }
+        }
+        *visualPP = visualP;
+        *depthP   = depth;
+    }
+}
+
+
+
+static void
+setNormalSizeHints(viewer *     const viewerP,
+                   Image *      const imageP) {
+    
+    XSizeHints sh;
+    
+    sh.width  = viewerP->width;
+    sh.height = viewerP->height;
+    if (viewerP->fullscreen) {
+        sh.min_width  = sh.max_width  = viewerP->width;
+        sh.min_height = sh.max_height = viewerP->height;
+    } else {
+        sh.min_width  = 1;
+        sh.min_height = 1;
+        sh.max_width  = imageP->width;
+        sh.max_height = imageP->height;
+    }
+    sh.width_inc  = 1;
+    sh.height_inc = 1;
+
+    sh.flags = PMinSize | PMaxSize | PResizeInc;
+    if (viewerP->userChoseGeometry)
+        sh.flags |= USSize;
+    else
+        sh.flags |= PSize;
+
+    if (viewerP->userChoseGeometry) {
+        sh.x = viewerP->xpos;
+        sh.y = viewerP->ypos;
+        sh.flags |= USPosition;
+    }
+    XSetNormalHints(viewerP->dispP, viewerP->viewportWin, &sh);
+
+    sh.min_width  = sh.max_width;
+    sh.min_height = sh.max_height;
+    XSetNormalHints(viewerP->dispP, viewerP->imageWin, &sh);
+        /* Image doesn't shrink */
+}
+
+
+
+static void
+setWMHints(viewer * const viewerP) {
+
+    XWMHints wmh;
+
+    wmh.input = TRUE;
+    wmh.flags = InputHint;
+    XSetWMHints(viewerP->dispP, viewerP->viewportWin, &wmh);
+}
+
+
+#define CTL_C '\003'
+
+typedef enum exitReason {
+    EXIT_NONE,
+    EXIT_QUIT,
+    EXIT_WM_KILL,
+    EXIT_DESTROYED
+} exitReason;
+
+
+
+static void
+run(viewer *     const viewerP,
+    Image *      const imageP,
+    XImageInfo * const ximageInfoP,
+    int          const initPixx,
+    int          const initPixy,
+    bool         const install,
+    exitReason * const exitReasonP) {
+
+    int lastx, lasty;
+    int pixx, pixy;
+    union {
+        XEvent              event;
+        XAnyEvent           any;
+        XButtonEvent        button;
+        XKeyEvent           key;
+        XConfigureEvent     configure;
+        XExposeEvent        expose;
+        XMotionEvent        motion;
+        XResizeRequestEvent resize;
+        XClientMessageEvent message;
+    } event;
+    exitReason exitReason;
+
+    lastx = lasty = -1;
+    pixx   = initPixx;
+    pixy   = initPixy;
+
+    exitReason = EXIT_NONE;  /* No reason to exit yet */
+
+    while (exitReason == EXIT_NONE) {
+        XNextEvent(viewerP->dispP, &event.event);
+
+        switch (event.any.type) {
+        case ButtonPress:
+            if (event.button.button == 1) {
+                lastx = event.button.x;
+                lasty = event.button.y;
+            }
+            break;
+
+        case KeyPress: {
+            char buf[128];
+            KeySym ks;
+            XComposeStatus status;
+            Status rc;
+            
+            rc = XLookupString(&event.key, buf, 128, &ks, &status);
+            if (rc == 1) {
+                char const ret = buf[0];
+                char const lowerRet = tolower(ret);
+
+                switch (lowerRet) {
+                case CTL_C:
+                case 'q':
+                    exitReason = EXIT_QUIT;
+                    break;
+                }
+            }
+        } break;
+
+        case MotionNotify: {
+            int mousex, mousey;
+
+            if (imageP->width  <= viewerP->width &&
+                imageP->height <= viewerP->height) {
+                /* we're AT&T */
+            } else {
+                mousex = event.button.x;
+                mousey = event.button.y;
+                while (XCheckTypedEvent(viewerP->dispP, MotionNotify,
+                                        (XEvent*)&event)) {
+                    mousex = event.button.x;
+                    mousey = event.button.y;
+                }
+                pixx -= (lastx - mousex);
+                pixy -= (lasty - mousey);
+                lastx = mousex;
+                lasty = mousey;
+                placeImage(viewerP, imageP->width, imageP->height,
+                           &pixx, &pixy);
+            }
+        } break;
+
+        case ConfigureNotify:
+            viewerP->width  = event.configure.width;
+            viewerP->height = event.configure.height;
+
+            placeImage(viewerP, imageP->width, imageP->height,
+                       &pixx, &pixy);
+
+            /* Configure the cursor to indicate which directions we can drag
+             */
+
+            changeCursor(viewerP, imageP->width, imageP->height);
+            break;
+
+        case DestroyNotify:
+            exitReason = EXIT_DESTROYED;
+            break;
+
+        case Expose:
+            blitImage(ximageInfoP, imageP->width, imageP->height,
+                      event.expose.x, event.expose.y,
+                      event.expose.width, event.expose.height);
+            break;
+
+        case EnterNotify:
+            if (install)
+                XInstallColormap(viewerP->dispP, ximageInfoP->cmap);
+            break;
+
+        case LeaveNotify:
+            if (install)
+                XUninstallColormap(viewerP->dispP, ximageInfoP->cmap);
+            break;
+
+        case ClientMessage:
+            /* If we get a client message for the viewport window
+               which has the value of the delete atom, it means the
+               window manager wants us to die.
+            */
+
+            if ((event.message.window == viewerP->viewportWin) &&
+                (event.message.data.l[0] == viewerP->deleteAtom)) {
+                exitReason = EXIT_WM_KILL;
+            }
+            break;
+        }
+    }
+    *exitReasonP = exitReason;
+}
+
+
+
+static int
+retvalueFromExitReason(exitReason const exitReason) {
+
+    int retval;
+
+    switch (exitReason) {
+    case EXIT_NONE:      assert(false); break;
+    case EXIT_QUIT:      retval =  0;     break;
+    case EXIT_WM_KILL:   retval = 10;     break;
+    case EXIT_DESTROYED: retval = 20;     break;
+    }
+    return retval;
+}
+
+
+
+static void
+resizeViewer(viewer *     const viewerP,
+             unsigned int const newWidth,
+             unsigned int const newHeight) {
+    
+    if (viewerP->width != newWidth || viewerP->height != newHeight) {
+        XResizeWindow(viewerP->dispP, viewerP->viewportWin,
+                      newWidth, newHeight);
+        viewerP->width  = newWidth;
+        viewerP->height = newHeight;
+    }
+}
+
+
+
+void
+displayInViewer(viewer *       const viewerP,
+                struct Image * const imageP,
+                bool           const install,
+                bool           const userWantsPrivateCmap,
+                bool           const fit,
+                bool           const userWantsPixmap,
+                bool           const visualSpec,
+                unsigned int   const visualClass,
+                const char *   const title,
+                bool           const verbose,
+                int *          const retvalP) {
+    
+    XImageInfo *     ximageInfoP;
+    Visual *         visual;
+    unsigned int     depth;
+    bool             privateCmap;
+    int              pixx, pixy;
+    exitReason       exitReason;
+    unsigned int     windowWidth, windowHeight;
+
+    pixx = -1; pixy = -1;  /* initial values */
+
+    getImageDispDimensions(viewerP, imageP, &windowWidth, &windowHeight);
+
+    resizeViewer(viewerP, windowWidth, windowHeight);
+
+    getVisualAndDepth(imageP, viewerP->dispP, viewerP->scrn,
+                      fit, visualSpec, visualClass,
+                      &visual, &depth);
+    
+    if (verbose && (visual != DefaultVisual(viewerP->dispP, viewerP->scrn)))
+        pm_message("Using %s visual", nameOfVisualClass(visual->class));
+
+    /* If we're in slideshow mode and the user told us to fit the colormap,
+       free it here.
+    */
+
+    if (viewerP->blank) {
+        /* For the first image we display we can use the default cmap.
+           subsequent images use a private colormap (unless they're
+           bitmaps) so we don't get color erosion when switching
+           images.
+        */
+
+        if (fit) {
+            XDestroyWindow(viewerP->dispP, viewerP->imageWin);
+            viewerP->imageWin = 0;
+            viewerP->imageColormap = 0;
+            privateCmap = userWantsPrivateCmap;
+        } else if (!BITMAPP(imageP))
+            privateCmap = TRUE;
+    } else
+        privateCmap = userWantsPrivateCmap;
+
+    ximageInfoP = imageToXImage(viewerP->dispP, viewerP->scrn, visual, depth,
+                                imageP, privateCmap, fit, verbose);
+    if (!ximageInfoP)
+        pm_error("INTERNAL ERROR: Cannot convert Image to XImage");
+
+    destroyImageWindow(viewerP);
+
+    createImageWindow(viewerP, ximageInfoP, imageP, visual,
+                      userWantsPixmap, verbose);
+
+    if (title)
+        XStoreName(viewerP->dispP, viewerP->viewportWin, title);
+    else
+        XStoreName(viewerP->dispP, viewerP->viewportWin, "Unnamed");
+
+    {
+        const char * const name = iconName(title);
+        XSetIconName(viewerP->dispP, viewerP->viewportWin, name);
+        strfree(name);
+    }
+    setNormalSizeHints(viewerP, imageP);
+
+    setWMHints(viewerP);
+
+    setViewportColormap(viewerP, visual);
+
+    /* Map (display) windows */
+
+    XMapWindow(viewerP->dispP, viewerP->imageWin);
+    XMapWindow(viewerP->dispP, viewerP->viewportWin);
+
+    /* Start displaying image */
+
+    placeImage(viewerP, imageP->width, imageP->height, &pixx, &pixy);
+    if (!viewerP->blank) {
+        XResizeWindow(viewerP->dispP, viewerP->imageWin,
+                      imageP->width, imageP->height);
+        /* Clear the image window.  Ask for exposure if there is no tile. */
+        XClearArea(viewerP->dispP, viewerP->imageWin, 0, 0, 0, 0,
+                   (viewerP->pixmap == None));
+        viewerP->blank = FALSE;
+    }
+
+    changeCursor(viewerP, imageP->width, imageP->height);
+
+    /* Process X events, continuously */
+    run(viewerP, imageP, ximageInfoP, pixx, pixy, install, &exitReason);
+
+    freeXImage(imageP, ximageInfoP);
+
+    *retvalP = retvalueFromExitReason(exitReason);
+}
diff --git a/other/pamx/window.h b/other/pamx/window.h
new file mode 100644
index 00000000..2efc54b5
--- /dev/null
+++ b/other/pamx/window.h
@@ -0,0 +1,38 @@
+#ifndef WINDOW_H_INCLUDED
+#define WINDOW_H_INCLUDED
+
+#include <X11/Xlib.h>
+
+#include "pm_c_util.h"
+
+struct Image;
+struct viewer;
+typedef struct viewer viewer;
+
+int
+visualClassFromName(const char * const name);
+
+void
+createViewer(viewer **     const viewerPP,
+             Display *     const dispP,
+             int           const scrn,
+             const char *  const geometryString,
+             bool          const fullscreen);
+
+void
+destroyViewer(viewer * const viewerP);
+
+void
+displayInViewer(viewer *       const viewerP,
+                struct Image * const imageP,
+                bool           const install,
+                bool           const userWantsPrivateCmap,
+                bool           const fit,
+                bool           const userWantsPixmap,
+                bool           const visualSpec,
+                unsigned int   const visualClass,
+                const char *   const title,
+                bool           const verbose,
+                int *          const retvalP);
+
+#endif
diff --git a/other/pamx/ximageinfo.h b/other/pamx/ximageinfo.h
new file mode 100644
index 00000000..b0b54ee1
--- /dev/null
+++ b/other/pamx/ximageinfo.h
@@ -0,0 +1,25 @@
+#ifndef XIMAGEINFO_H_INCLUDED
+#define XIMAGEINFO_H_INCLUDED
+
+#include <X11/Xlib.h>
+
+
+
+typedef unsigned long  Pixel;     /* what X thinks a pixel is */
+typedef unsigned short Intensity; /* what X thinks an RGB intensity is */
+
+typedef struct {
+    /* This struct holds the X-client side bits for a rendered image.  */
+    Display * disp;       /* destination display */
+    int       scrn;       /* destination screen */
+    int       depth;      /* depth of drawable we want/have */
+    Drawable  drawable;   /* drawable to send image to */
+    Pixel     foreground;
+        /* foreground and background pixels for mono images */
+    Pixel     background;
+    Colormap  cmap;       /* colormap used for image */
+    GC        gc;         /* cached gc for sending image */
+    XImage *  ximageP;     /* ximage structure */
+} XImageInfo;
+
+#endif
diff --git a/other/pnmcolormap.c b/other/pnmcolormap.c
new file mode 100644
index 00000000..c4776001
--- /dev/null
+++ b/other/pnmcolormap.c
@@ -0,0 +1,973 @@
+/******************************************************************************
+                               pnmcolormap.c
+*******************************************************************************
+
+  Create a colormap file (a PPM image containing one pixel of each of a set
+  of colors).  Base the set of colors on an input image.
+
+  For PGM input, do the equivalent for grayscale and produce a PGM graymap.
+
+  By Bryan Henderson, San Jose, CA 2001.12.17
+
+  Derived from ppmquant, originally by Jef Poskanzer.
+
+  Copyright (C) 1989, 1991 by Jef Poskanzer.
+  Copyright (C) 2001 by Bryan Henderson.
+
+  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 "pam.h"
+#include "pammap.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+#include "nstring.h"
+
+enum methodForLargest {LARGE_NORM, LARGE_LUM};
+
+enum methodForRep {REP_CENTER_BOX, REP_AVERAGE_COLORS, REP_AVERAGE_PIXELS};
+
+typedef struct box* boxVector;
+struct box {
+    int ind;
+    int colors;
+    int sum;
+};
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *inputFilespec;  /* Filespec of input file */
+    unsigned int allcolors;  /* boolean: select all colors from the input */
+    unsigned int newcolors;    
+        /* Number of colors argument; meaningless if allcolors true */
+    enum methodForLargest methodForLargest; 
+        /* -spreadintensity/-spreadluminosity options */
+    enum methodForRep methodForRep;
+        /* -center/-meancolor/-meanpixel options */
+    unsigned int sort;
+    unsigned int square;
+    unsigned int verbose;
+};
+
+
+
+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 spreadbrightness, spreadluminosity;
+    unsigned int center, meancolor, meanpixel;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0,   "spreadbrightness", OPT_FLAG,   
+            NULL,                       &spreadbrightness, 0);
+    OPTENT3(0,   "spreadluminosity", OPT_FLAG,   
+            NULL,                       &spreadluminosity, 0);
+    OPTENT3(0,   "center",           OPT_FLAG,   
+            NULL,                       &center,           0);
+    OPTENT3(0,   "meancolor",        OPT_FLAG,   
+            NULL,                       &meancolor,        0);
+    OPTENT3(0,   "meanpixel",        OPT_FLAG,   
+            NULL,                       &meanpixel,        0);
+    OPTENT3(0, "sort",     OPT_FLAG,   NULL,                  
+            &cmdlineP->sort,       0 );
+    OPTENT3(0, "square",   OPT_FLAG,   NULL,                  
+            &cmdlineP->square,       0 );
+    OPTENT3(0, "verbose",   OPT_FLAG,   NULL,                  
+            &cmdlineP->verbose,       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 (spreadbrightness && spreadluminosity) 
+        pm_error("You cannot specify both -spreadbrightness and "
+                 "spreadluminosity.");
+    if (spreadluminosity)
+        cmdlineP->methodForLargest = LARGE_LUM;
+    else
+        cmdlineP->methodForLargest = LARGE_NORM;
+
+    if (center + meancolor + meanpixel > 1)
+        pm_error("You can specify only one of -center, -meancolor, and "
+                 "-meanpixel.");
+    if (meancolor)
+        cmdlineP->methodForRep = REP_AVERAGE_COLORS;
+    else if (meanpixel) 
+        cmdlineP->methodForRep = REP_AVERAGE_PIXELS;
+    else
+        cmdlineP->methodForRep = REP_CENTER_BOX;
+
+    if (argc-1 > 2)
+        pm_error("Program takes at most two arguments: number of colors "
+                 "and input file specification.  "
+                 "You specified %d arguments.", argc-1);
+    else {
+        if (argc-1 < 2)
+            cmdlineP->inputFilespec = "-";
+        else
+            cmdlineP->inputFilespec = argv[2];
+
+        if (argc-1 < 1)
+            pm_error("You must specify the number of colors in the "
+                     "output as an argument.");
+        else {
+            if (strcmp(argv[1], "all") == 0)
+                cmdlineP->allcolors = TRUE;
+            else {
+                char * tail;
+                long int const newcolors = strtol(argv[1], &tail, 10);
+                if (*tail != '\0')
+                    pm_error("The number of colors argument '%s' is not "
+                             "a number or 'all'", argv[1]);
+                else if (newcolors < 1)
+                    pm_error("The number of colors must be positive");
+                else if (newcolors == 1)
+                    pm_error("The number of colors must be greater than 1.");
+                else {
+                    cmdlineP->newcolors = newcolors;
+                    cmdlineP->allcolors = FALSE;
+                }
+            }
+        }
+    }
+}
+
+
+typedef int qsort_comparison_fn(const void *, const void *);
+    /* A collation function to be used as argument to qsort() */
+
+static qsort_comparison_fn compareplane;
+
+static unsigned int compareplanePlane;
+    /* This is a parameter to compareplane().  We use this global variable
+       so that compareplane() can be called by qsort(), to compare two
+       tuples.  qsort() doesn't pass any arguments except the two tuples.
+    */
+static int
+compareplane(const void * const arg1, 
+             const void * const arg2) {
+
+    const struct tupleint * const * const comparandPP  = arg1;
+    const struct tupleint * const * const comparatorPP = arg2;
+
+    return (*comparandPP)->tuple[compareplanePlane] -
+        (*comparatorPP)->tuple[compareplanePlane];
+}
+
+
+
+static qsort_comparison_fn sumcompare;
+
+static int
+sumcompare(const void * const b1, const void * const b2) {
+    return(((boxVector)b2)->sum - ((boxVector)b1)->sum);
+}
+
+
+
+
+/*
+** Here is the fun part, the median-cut colormap generator.  This is based
+** on Paul Heckbert's paper "Color Image Quantization for Frame Buffer
+** Display", SIGGRAPH '82 Proceedings, page 297.
+*/
+
+static tupletable2
+newColorMap(unsigned int const newcolors,
+            unsigned int const depth) {
+
+    tupletable2 colormap;
+    unsigned int i;
+    struct pam pam;
+
+    pam.depth = depth;
+
+    colormap.table = pnm_alloctupletable(&pam, newcolors);
+
+    for (i = 0; i < newcolors; ++i) {
+        unsigned int plane;
+        for (plane = 0; plane < depth; ++plane) 
+            colormap.table[i]->tuple[plane] = 0;
+    }
+    colormap.size = newcolors;
+
+    return colormap;
+}
+
+
+
+static boxVector
+newBoxVector(int const colors, int const sum, int const newcolors) {
+
+    boxVector bv;
+    MALLOCARRAY(bv, newcolors);
+    if (bv == NULL)
+        pm_error("out of memory allocating box vector table");
+
+    /* Set up the initial box. */
+    bv[0].ind = 0;
+    bv[0].colors = colors;
+    bv[0].sum = sum;
+
+    return bv;
+}
+
+
+
+static void
+findBoxBoundaries(tupletable2  const colorfreqtable,
+                  unsigned int const depth,
+                  unsigned int const boxStart,
+                  unsigned int const boxSize,
+                  sample             minval[],
+                  sample             maxval[]) {
+/*----------------------------------------------------------------------------
+  Go through the box finding the minimum and maximum of each
+  component - the boundaries of the box.
+-----------------------------------------------------------------------------*/
+    unsigned int plane;
+    unsigned int i;
+
+    for (plane = 0; plane < depth; ++plane) {
+        minval[plane] = colorfreqtable.table[boxStart]->tuple[plane];
+        maxval[plane] = minval[plane];
+    }
+    
+    for (i = 1; i < boxSize; ++i) {
+        unsigned int plane;
+        for (plane = 0; plane < depth; ++plane) {
+            sample const v = colorfreqtable.table[boxStart + i]->tuple[plane];
+            if (v < minval[plane]) minval[plane] = v;
+            if (v > maxval[plane]) maxval[plane] = v;
+        } 
+    }
+}
+
+
+
+static unsigned int
+largestByNorm(sample minval[], sample maxval[], unsigned int const depth) {
+    
+    unsigned int largestDimension;
+    unsigned int plane;
+
+    sample largestSpreadSoFar = 0;  
+    largestDimension = 0;
+    for (plane = 0; plane < depth; ++plane) {
+        sample const spread = maxval[plane]-minval[plane];
+        if (spread > largestSpreadSoFar) {
+            largestDimension = plane;
+            largestSpreadSoFar = spread;
+        }
+    }
+    return largestDimension;
+}
+
+
+
+static unsigned int 
+largestByLuminosity(sample minval[], sample maxval[], 
+                    unsigned int const depth) {
+/*----------------------------------------------------------------------------
+   This subroutine presumes that the tuple type is either 
+   BLACKANDWHITE, GRAYSCALE, or RGB (which implies pamP->depth is 1 or 3).
+   To save time, we don't actually check it.
+-----------------------------------------------------------------------------*/
+    unsigned int retval;
+
+    if (depth == 1)
+        retval = 0;
+    else {
+        /* An RGB tuple */
+        unsigned int largestDimension;
+        unsigned int plane;
+        double largestSpreadSoFar;
+
+        largestSpreadSoFar = 0.0;
+
+        for (plane = 0; plane < 3; ++plane) {
+            double const spread = 
+                pnm_lumin_factor[plane] * (maxval[plane]-minval[plane]);
+            if (spread > largestSpreadSoFar) {
+                largestDimension = plane;
+                largestSpreadSoFar = spread;
+            }
+        }
+        retval = largestDimension;
+    }
+    return retval;
+}
+
+
+
+static void
+centerBox(int          const boxStart,
+          int          const boxSize,
+          tupletable2  const colorfreqtable, 
+          unsigned int const depth,
+          tuple        const newTuple) {
+
+    unsigned int plane;
+
+    for (plane = 0; plane < depth; ++plane) {
+        int minval, maxval;
+        unsigned int i;
+
+        minval = maxval = colorfreqtable.table[boxStart]->tuple[plane];
+        
+        for (i = 1; i < boxSize; ++i) {
+            int const v = colorfreqtable.table[boxStart + i]->tuple[plane];
+            minval = MIN( minval, v);
+            maxval = MAX( maxval, v);
+        }
+        newTuple[plane] = (minval + maxval) / 2;
+    }
+}
+
+
+
+static void
+averageColors(int          const boxStart,
+              int          const boxSize,
+              tupletable2  const colorfreqtable, 
+              unsigned int const depth,
+              tuple        const newTuple) {
+
+    unsigned int plane;
+
+    for (plane = 0; plane < depth; ++plane) {
+        sample sum;
+        int i;
+
+        sum = 0;
+
+        for (i = 0; i < boxSize; ++i) 
+            sum += colorfreqtable.table[boxStart+i]->tuple[plane];
+
+        newTuple[plane] = sum / boxSize;
+    }
+}
+
+
+
+static void
+averagePixels(int          const boxStart,
+              int          const boxSize,
+              tupletable2  const colorfreqtable, 
+              unsigned int const depth,
+              tuple        const newTuple) {
+
+    unsigned int n;
+        /* Number of tuples represented by the box */
+    unsigned int plane;
+    unsigned int i;
+
+    /* Count the tuples in question */
+    n = 0;  /* initial value */
+    for (i = 0; i < boxSize; ++i)
+        n += colorfreqtable.table[boxStart + i]->value;
+
+
+    for (plane = 0; plane < depth; ++plane) {
+        sample sum;
+        int i;
+
+        sum = 0;
+
+        for (i = 0; i < boxSize; ++i) 
+            sum += colorfreqtable.table[boxStart+i]->tuple[plane]
+                * colorfreqtable.table[boxStart+i]->value;
+
+        newTuple[plane] = sum / n;
+    }
+}
+
+
+
+static tupletable2
+colormapFromBv(unsigned int      const newcolors,
+               boxVector         const bv, 
+               unsigned int      const boxes,
+               tupletable2       const colorfreqtable, 
+               unsigned int      const depth,
+               enum methodForRep const methodForRep) {
+    /*
+    ** Ok, we've got enough boxes.  Now choose a representative color for
+    ** each box.  There are a number of possible ways to make this choice.
+    ** One would be to choose the center of the box; this ignores any structure
+    ** within the boxes.  Another method would be to average all the colors in
+    ** the box - this is the method specified in Heckbert's paper.  A third
+    ** method is to average all the pixels in the box. 
+    */
+    tupletable2 colormap;
+    unsigned int bi;
+
+    colormap = newColorMap(newcolors, depth);
+
+    for (bi = 0; bi < boxes; ++bi) {
+        switch (methodForRep) {
+        case REP_CENTER_BOX: 
+            centerBox(bv[bi].ind, bv[bi].colors, colorfreqtable, depth, 
+                      colormap.table[bi]->tuple);
+            break;
+        case REP_AVERAGE_COLORS:
+            averageColors(bv[bi].ind, bv[bi].colors, colorfreqtable, depth,
+                          colormap.table[bi]->tuple);
+            break;
+        case REP_AVERAGE_PIXELS:
+            averagePixels(bv[bi].ind, bv[bi].colors, colorfreqtable, depth,
+                          colormap.table[bi]->tuple);
+            break;
+        default:
+            pm_error("Internal error: invalid value of methodForRep: %d",
+                     methodForRep);
+        }
+    }
+    return colormap;
+}
+
+
+
+static unsigned int
+freqTotal(tupletable2 const freqtable) {
+    
+    unsigned int i;
+    unsigned int sum;
+
+    sum = 0;
+
+    for (i = 0; i < freqtable.size; ++i)
+        sum += freqtable.table[i]->value;
+
+    return sum;
+}
+
+
+static void
+splitBox(boxVector             const bv, 
+         unsigned int *        const boxesP, 
+         unsigned int          const bi,
+         tupletable2           const colorfreqtable, 
+         unsigned int          const depth,
+         enum methodForLargest const methodForLargest) {
+/*----------------------------------------------------------------------------
+   Split Box 'bi' in the box vector bv (so that bv contains one more box
+   than it did as input).  Split it so that each new box represents about
+   half of the pixels in the distribution given by 'colorfreqtable' for 
+   the colors in the original box, but with distinct colors in each of the
+   two new boxes.
+
+   Assume the box contains at least two colors.
+-----------------------------------------------------------------------------*/
+    unsigned int const boxStart = bv[bi].ind;
+    unsigned int const boxSize  = bv[bi].colors;
+    unsigned int const sm       = bv[bi].sum;
+
+    sample * minval;  /* malloc'ed array */
+    sample * maxval;  /* malloc'ed array */
+
+    unsigned int largestDimension;
+        /* number of the plane with the largest spread */
+    unsigned int medianIndex;
+    int lowersum;
+        /* Number of pixels whose value is "less than" the median */
+
+    MALLOCARRAY_NOFAIL(minval, depth);
+    MALLOCARRAY_NOFAIL(maxval, depth);
+
+    findBoxBoundaries(colorfreqtable, depth, boxStart, boxSize, 
+                      minval, maxval);
+
+    /* Find the largest dimension, and sort by that component.  I have
+       included two methods for determining the "largest" dimension;
+       first by simply comparing the range in RGB space, and second by
+       transforming into luminosities before the comparison.
+    */
+    switch (methodForLargest) {
+    case LARGE_NORM: 
+        largestDimension = largestByNorm(minval, maxval, depth);
+        break;
+    case LARGE_LUM: 
+        largestDimension = largestByLuminosity(minval, maxval, depth);
+        break;
+    }
+                                                    
+    /* TODO: I think this sort should go after creating a box,
+       not before splitting.  Because you need the sort to use
+       the REP_CENTER_BOX method of choosing a color to
+       represent the final boxes 
+    */
+
+    /* Set the gross global variable 'compareplanePlane' as a
+       parameter to compareplane(), which is called by qsort().
+    */
+    compareplanePlane = largestDimension;
+    qsort((char*) &colorfreqtable.table[boxStart], boxSize, 
+          sizeof(colorfreqtable.table[boxStart]), 
+          compareplane);
+            
+    {
+        /* Now find the median based on the counts, so that about half
+           the pixels (not colors, pixels) are in each subdivision.  */
+
+        unsigned int i;
+
+        lowersum = colorfreqtable.table[boxStart]->value; /* initial value */
+        for (i = 1; i < boxSize - 1 && lowersum < sm/2; ++i) {
+            lowersum += colorfreqtable.table[boxStart + i]->value;
+        }
+        medianIndex = i;
+    }
+    /* Split the box, and sort to bring the biggest boxes to the top.  */
+
+    bv[bi].colors = medianIndex;
+    bv[bi].sum = lowersum;
+    bv[*boxesP].ind = boxStart + medianIndex;
+    bv[*boxesP].colors = boxSize - medianIndex;
+    bv[*boxesP].sum = sm - lowersum;
+    ++(*boxesP);
+    qsort((char*) bv, *boxesP, sizeof(struct box), sumcompare);
+
+    free(minval); free(maxval);
+}
+
+
+
+static void
+mediancut(tupletable2           const colorfreqtable, 
+          unsigned int          const depth,
+          int                   const newcolors,
+          enum methodForLargest const methodForLargest,
+          enum methodForRep     const methodForRep,
+          tupletable2 *         const colormapP) {
+/*----------------------------------------------------------------------------
+   Compute a set of only 'newcolors' colors that best represent an
+   image whose pixels are summarized by the histogram
+   'colorfreqtable'.  Each tuple in that table has depth 'depth'.
+   colorfreqtable.table[i] tells the number of pixels in the subject image
+   have a particular color.
+
+   As a side effect, sort 'colorfreqtable'.
+-----------------------------------------------------------------------------*/
+    boxVector bv;
+    unsigned int bi;
+    unsigned int boxes;
+    bool multicolorBoxesExist;
+        /* There is at least one box that contains at least 2 colors; ergo,
+           there is more splitting we can do.
+        */
+
+    bv = newBoxVector(colorfreqtable.size, freqTotal(colorfreqtable),
+                      newcolors);
+    boxes = 1;
+    multicolorBoxesExist = (colorfreqtable.size > 1);
+
+    /* Main loop: split boxes until we have enough. */
+    while (boxes < newcolors && multicolorBoxesExist) {
+        /* Find the first splittable box. */
+        for (bi = 0; bi < boxes && bv[bi].colors < 2; ++bi);
+        if (bi >= boxes)
+            multicolorBoxesExist = FALSE;
+        else 
+            splitBox(bv, &boxes, bi, colorfreqtable, depth, methodForLargest);
+    }
+    *colormapP = colormapFromBv(newcolors, bv, boxes, colorfreqtable, depth,
+                                methodForRep);
+
+    free(bv);
+}
+
+
+
+
+static void
+validateCompatibleImage(struct pam * const inpamP,
+                        struct pam * const firstPamP,
+                        unsigned int const imageSeq) {
+
+    if (inpamP->depth != firstPamP->depth)
+        pm_error("Image %u depth (%u) is not the same as Image 0 (%u)",
+                 imageSeq, inpamP->depth, firstPamP->depth);
+    if (inpamP->maxval != firstPamP->maxval)
+        pm_error("Image %u maxval (%u) is not the same as Image 0 (%u)",
+                 imageSeq,
+                 (unsigned)inpamP->maxval, (unsigned)firstPamP->maxval);
+    if (inpamP->format != firstPamP->format)
+        pm_error("Image %u format (%d) is not the same as Image 0 (%d)",
+                 imageSeq, inpamP->format, firstPamP->format);
+    if (!STREQ(inpamP->tuple_type, firstPamP->tuple_type))
+        pm_error("Image %u tuple type (%s) is not the same as Image 0 (%s)",
+                 imageSeq, inpamP->tuple_type, firstPamP->tuple_type);
+}
+
+
+
+static void
+addImageColorsToHash(struct pam *   const pamP,
+                     tuplehash      const tuplehash,
+                     unsigned int * const colorCountP) {
+
+    tuple * tuplerow;
+    unsigned int row;
+    
+    tuplerow = pnm_allocpamrow(pamP);
+
+    for (row = 0; row < pamP->height; ++row) {
+        unsigned int col;
+
+        pnm_readpamrow(pamP, tuplerow);
+
+        for (col = 0; col < pamP->width; ++col) {
+            bool firstOccurrence;
+
+            pnm_addtuplefreqoccurrence(pamP, tuplerow[col], tuplehash,
+                                       &firstOccurrence);
+
+            if (firstOccurrence)
+                ++(*colorCountP);
+        }
+    }
+    pnm_freepamrow(tuplerow);
+}
+
+
+
+static void
+computeHistogram(FILE *         const ifP,
+                 int *          const formatP,
+                 struct pam *   const freqPamP,
+                 tupletable2 *  const colorfreqtableP) {
+/*----------------------------------------------------------------------------
+  Make a histogram of the colors in the image stream in the file '*ifP'.
+  
+  Return as *freqPamP a description of the tuple values in the histogram.
+  Only the fields of *freqPamP that describe individual tuples are
+  meaningful (depth, maxval, tuple type);
+
+  As a fringe benefit, also return the format of the input file as
+  *formatP.
+----------------------------------------------------------------------------*/
+    unsigned int imageSeq;
+    struct pam firstPam;
+    tuplehash tuplehash;
+    unsigned int colorCount;
+    bool eof;
+    
+    pm_message("making histogram...");
+
+    tuplehash = pnm_createtuplehash();
+    colorCount = 0;
+
+    eof = FALSE;
+
+    for (imageSeq = 0; !eof; ++imageSeq) {
+        struct pam inpam;
+        
+        pm_message("Scanning image %u", imageSeq);
+
+        pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
+
+        if (imageSeq == 0)
+            firstPam = inpam;
+        else
+            validateCompatibleImage(&inpam, &firstPam, imageSeq);
+    
+        addImageColorsToHash(&inpam, tuplehash, &colorCount);
+
+        pm_message("%u colors so far", colorCount);
+
+        pnm_nextimage(ifP, &eof);
+    }
+    colorfreqtableP->table =
+        pnm_tuplehashtotable(&firstPam, tuplehash, colorCount);
+    colorfreqtableP->size = colorCount;
+
+    pnm_destroytuplehash(tuplehash);
+
+    pm_message("%u colors found", colorfreqtableP->size);
+    
+    freqPamP->size   = sizeof(*freqPamP);
+    freqPamP->len    = PAM_STRUCT_SIZE(tuple_type);
+    freqPamP->maxval = firstPam.maxval;
+    freqPamP->bytes_per_sample = pnm_bytespersample(freqPamP->maxval);
+    freqPamP->depth  = firstPam.depth;
+    STRSCPY(freqPamP->tuple_type, firstPam.tuple_type);
+    
+    *formatP = firstPam.format;
+}
+
+
+
+static void
+computeColorMapFromInput(FILE *                const ifP,
+                         bool                  const allColors, 
+                         int                   const reqColors, 
+                         enum methodForLargest const methodForLargest,
+                         enum methodForRep     const methodForRep,
+                         int *                 const formatP,
+                         struct pam *          const freqPamP,
+                         tupletable2 *         const colormapP) {
+/*----------------------------------------------------------------------------
+   Produce a colormap containing the best colors to represent the
+   image stream in file 'ifP'.  Figure it out using the median cut
+   technique.
+
+   The colormap will have 'reqcolors' or fewer colors in it, unless
+   'allcolors' is true, in which case it will have all the colors that
+   are in the input.
+
+   The colormap has the same maxval as the input.
+
+   Put the colormap in newly allocated storage as a tupletable2 
+   and return its address as *colormapP.  Return the number of colors in
+   it as *colorsP and its maxval as *colormapMaxvalP.
+
+   Return the characteristics of the input file as
+   *formatP and *freqPamP.  (This information is not really
+   relevant to our colormap mission; just a fringe benefit).
+-----------------------------------------------------------------------------*/
+    tupletable2 colorfreqtable;
+
+    computeHistogram(ifP, formatP, freqPamP, &colorfreqtable);
+    
+    if (allColors) {
+        *colormapP = colorfreqtable;
+    } else {
+        if (colorfreqtable.size <= reqColors) {
+            pm_message("Image already has few enough colors (<=%d).  "
+                       "Keeping same colors.", reqColors);
+            *colormapP = colorfreqtable;
+        } else {
+            pm_message("choosing %d colors...", reqColors);
+            mediancut(colorfreqtable, freqPamP->depth,
+                      reqColors, methodForLargest, methodForRep,
+                      colormapP);
+            pnm_freetupletable2(freqPamP, colorfreqtable);
+        }
+    }
+}
+
+
+
+static void
+sortColormap(tupletable2  const colormap, 
+             sample       const depth) {
+/*----------------------------------------------------------------------------
+   Sort the colormap in place, in order of ascending Plane 0 value, 
+   the Plane 1 value, etc.
+
+   Use insertion sort.
+-----------------------------------------------------------------------------*/
+    int i;
+    
+    pm_message("Sorting %u colors...", colormap.size);
+
+    for (i = 0; i < colormap.size; ++i) {
+        int j;
+        for (j = i+1; j < colormap.size; ++j) {
+            unsigned int plane;
+            bool iIsGreater, iIsLess;
+
+            iIsGreater = FALSE; iIsLess = FALSE;
+            for (plane = 0; 
+                 plane < depth && !iIsGreater && !iIsLess; 
+                 ++plane) {
+                if (colormap.table[i]->tuple[plane] >
+                    colormap.table[j]->tuple[plane])
+                    iIsGreater = TRUE;
+                else if (colormap.table[i]->tuple[plane] <
+                         colormap.table[j]->tuple[plane])
+                    iIsLess = TRUE;
+            }            
+            if (iIsGreater) {
+                for (plane = 0; plane < depth; ++plane) {
+                    sample const temp = colormap.table[i]->tuple[plane];
+                    colormap.table[i]->tuple[plane] =
+                        colormap.table[j]->tuple[plane];
+                    colormap.table[j]->tuple[plane] = temp;
+                }
+            }
+        }    
+    }
+}
+
+
+
+static void 
+colormapToSquare(struct pam * const pamP,
+                 tupletable2  const colormap,
+                 tuple ***    const outputRasterP) {
+    {
+        unsigned int const intsqrt = (int)sqrt((float)colormap.size);
+        if (intsqrt * intsqrt == colormap.size) 
+            pamP->width = intsqrt;
+        else 
+            pamP->width = intsqrt + 1;
+    }
+    {
+        unsigned int const intQuotient = colormap.size / pamP->width;
+        if (pamP->width * intQuotient == colormap.size)
+            pamP->height = intQuotient;
+        else
+            pamP->height = intQuotient + 1;
+    }
+    {
+        tuple ** outputRaster;
+        unsigned int row;
+        unsigned int colormapIndex;
+        
+        outputRaster = pnm_allocpamarray(pamP);
+
+        colormapIndex = 0;  /* initial value */
+        
+        for (row = 0; row < pamP->height; ++row) {
+            unsigned int col;
+            for (col = 0; col < pamP->width; ++col) {
+                unsigned int plane;
+                for (plane = 0; plane < pamP->depth; ++plane) {
+                    outputRaster[row][col][plane] = 
+                        colormap.table[colormapIndex]->tuple[plane];
+                }
+                if (colormapIndex < colormap.size-1)
+                    ++colormapIndex;
+            }
+        }
+        *outputRasterP = outputRaster;
+    } 
+}
+
+
+
+static void 
+colormapToSingleRow(struct pam * const pamP,
+                    tupletable2  const colormap,
+                    tuple ***    const outputRasterP) {
+
+    tuple ** outputRaster;
+    unsigned int col;
+    
+    pamP->width = colormap.size;
+    pamP->height = 1;
+    
+    outputRaster = pnm_allocpamarray(pamP);
+    
+    for (col = 0; col < pamP->width; ++col) {
+        int plane;
+        for (plane = 0; plane < pamP->depth; ++plane)
+            outputRaster[0][col][plane] = colormap.table[col]->tuple[plane];
+    }
+    *outputRasterP = outputRaster;
+}
+
+
+
+static void
+colormapToImage(int                const format,
+                const struct pam * const colormapPamP,
+                tupletable2        const colormap,
+                bool               const sort,
+                bool               const square,
+                struct pam *       const outpamP, 
+                tuple ***          const outputRasterP) {
+/*----------------------------------------------------------------------------
+   Create a tuple array and pam structure for an image which includes
+   one pixel of each of the colors in the colormap 'colormap'.
+
+   May rearrange the contents of 'colormap'.
+-----------------------------------------------------------------------------*/
+    outpamP->size             = sizeof(*outpamP);
+    outpamP->len              = PAM_STRUCT_SIZE(tuple_type);
+    outpamP->format           = format,
+    outpamP->plainformat      = FALSE;
+    outpamP->depth            = colormapPamP->depth;
+    outpamP->maxval           = colormapPamP->maxval;
+    outpamP->bytes_per_sample = pnm_bytespersample(outpamP->maxval);
+    STRSCPY(outpamP->tuple_type, colormapPamP->tuple_type);
+
+    if (sort)
+        sortColormap(colormap, outpamP->depth);
+
+    if (square) 
+        colormapToSquare(outpamP, colormap, outputRasterP);
+    else 
+        colormapToSingleRow(outpamP, colormap, outputRasterP);
+}
+
+
+
+int
+main(int argc, char * argv[] ) {
+
+    struct cmdlineInfo cmdline;
+    FILE * ifP;
+    int format;
+    struct pam colormapPam;
+    struct pam outpam;
+    tuple ** colormapRaster;
+    tupletable2 colormap;
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilespec);
+
+    computeColorMapFromInput(ifP,
+                             cmdline.allcolors, cmdline.newcolors, 
+                             cmdline.methodForLargest, 
+                             cmdline.methodForRep,
+                             &format, &colormapPam, &colormap);
+
+    pm_close(ifP);
+
+    colormapToImage(format, &colormapPam, colormap,
+                    cmdline.sort, cmdline.square, &outpam, &colormapRaster);
+
+    if (cmdline.verbose)
+        pm_message("Generating %u x %u image", outpam.width, outpam.height);
+
+    outpam.file = stdout;
+    
+    pnm_writepam(&outpam, colormapRaster);
+    
+    pnm_freetupletable2(&colormapPam, colormap);
+
+    pnm_freepamarray(colormapRaster, &outpam);
+
+    pm_close(stdout);
+
+    return 0;
+}
diff --git a/other/ppmdcfont.c b/other/ppmdcfont.c
new file mode 100644
index 00000000..701277a9
--- /dev/null
+++ b/other/ppmdcfont.c
@@ -0,0 +1,200 @@
+#include <stdio.h>
+#include <assert.h>
+
+#include "ppm.h"
+#include "nstring.h"
+#include "ppmdfont.h"
+
+
+
+static void
+generateHeader(struct ppmd_fontHeader const fontHeader) {
+
+    fprintf(stdout, "  {/* .header */\n");
+    fprintf(stdout, "    {'p','p','m','d','f','o','n','t'},\n");
+    fprintf(stdout, "    0x%02x,\n", fontHeader.format);
+    fprintf(stdout, "    %u,\n", fontHeader.characterCount);
+    fprintf(stdout, "    %u\n", fontHeader.firstCodePoint);
+    fprintf(stdout, "  }\n");
+}
+
+
+
+static void
+generateGlyphCommand(struct ppmd_glyphCommand const glyphCommand) {
+
+    const char * verb;
+
+    switch (glyphCommand.verb) {
+    case CMD_NOOP:     verb = "CMD_NOOP";     break;
+    case CMD_DRAWLINE: verb = "CMD_DRAWLINE"; break;
+    case CMD_MOVEPEN:  verb = "CMD_MOVEPEN";  break;
+    }
+    
+    fprintf(stdout, "  {/* glyphCommand */ %s, %u, %u }\n",
+            verb, glyphCommand.x, glyphCommand.y);
+    
+}
+
+
+
+static void
+generateCommandTable(struct ppmd_glyph const glyph,
+                     const char *      const variableName) {
+
+    unsigned int commandNum;
+
+    fprintf(stdout, "struct ppmd_glyphCommand const %s[%u] = {\n",
+            variableName, glyph.header.commandCount);
+
+    for (commandNum = 0;
+         commandNum < glyph.header.commandCount;
+         ++commandNum) {
+
+        generateGlyphCommand(glyph.commandList[commandNum]);
+
+        if (commandNum < glyph.header.commandCount-1)
+            fprintf(stdout, "  ,\n");
+    }
+}
+
+
+
+static void
+generateCommandTables(const struct ppmd_font * const fontP,
+                      const char *             const glyphTableVariableName) {
+
+    unsigned int relativeCodePoint;
+
+    for (relativeCodePoint = 0;
+         relativeCodePoint < fontP->header.characterCount;
+         ++relativeCodePoint) {
+
+        if (fontP->glyphTable[relativeCodePoint].header.commandCount > 0) {
+            const char * commandTableVariableName;
+
+            asprintfN(&commandTableVariableName, "%s_cmd_%u",
+                      glyphTableVariableName,
+                      fontP->header.firstCodePoint + relativeCodePoint);
+            
+            generateCommandTable(fontP->glyphTable[relativeCodePoint],
+                                 commandTableVariableName);
+
+            strfree(commandTableVariableName);
+
+            fprintf(stdout, "};\n");
+            fprintf(stdout, "\n");
+        }
+    }
+}
+
+
+
+static void
+generateGlyph(
+    struct ppmd_glyph const glyph,
+    const char *      const commandTableVariableName) {
+
+    fprintf(stdout, "  { /* glyph */\n");
+    fprintf(stdout, "    { /* header */ %u, %u, %u}\n",
+            glyph.header.commandCount,
+            glyph.header.skipBefore,
+            glyph.header.skipAfter
+            );
+
+    fprintf(stdout, "    ,\n");
+    if (glyph.header.commandCount == 0)
+        fprintf(stdout, "    NULL\n");
+    else
+        fprintf(stdout, "    %s\n", commandTableVariableName);
+
+    fprintf(stdout, "  }\n");
+}
+
+
+
+static void
+generateGlyphTable(const struct ppmd_font * const fontP,
+                   const char *             const variableName) {
+
+    unsigned int relativeCodePoint;
+
+    generateCommandTables(fontP, variableName);
+
+    fprintf(stdout, "struct ppmd_glyph const %s[%u] = {\n",
+            variableName, fontP->header.characterCount);
+
+    for (relativeCodePoint = 0;
+         relativeCodePoint < fontP->header.characterCount;
+         ++relativeCodePoint) {
+
+        const char * commandTableVariableName;
+
+        asprintfN(&commandTableVariableName, "%s_cmd_%u",
+                  variableName,
+                  fontP->header.firstCodePoint + relativeCodePoint);
+        
+        generateGlyph(fontP->glyphTable[relativeCodePoint],
+                      commandTableVariableName);
+
+        strfree(commandTableVariableName);
+
+        if (relativeCodePoint < fontP->header.characterCount - 1)
+            fprintf(stdout, "  ,\n");
+    }
+    fprintf(stdout, "};\n");
+    fprintf(stdout, "\n");
+}
+
+
+
+static void
+generateFont(const struct ppmd_font * const fontP,
+             const char *             const fontVariableName,
+             const char *             const glyphTableVariableName) {
+
+    fprintf(stdout, "struct ppmd_font const %s = {\n", fontVariableName);
+    
+    generateHeader(fontP->header);
+
+    fprintf(stdout, "  ,\n");
+
+    fprintf(stdout, "  /* .glyphTable: */ %s\n", glyphTableVariableName);
+
+    fprintf(stdout, "};\n");
+}
+
+
+
+int
+main(int argc, char **argv) {
+
+    const char fontVariableName[] = "ppmd_standardfont";
+    const struct ppmd_font * fontP;
+    const char * glyphTableVariableName;
+
+    ppm_init(&argc, argv);
+
+    ppmd_read_font(stdin, &fontP);
+
+    fprintf(stdout, "/* THIS FILE WAS GENERATED BY 'ppmdcfont' from a "
+            "ppmdfont file. */\n");
+
+    fprintf(stdout, "\n");
+
+    fprintf(stdout, "#include \"ppmdfont.h\"\n\n");
+
+    asprintfN(&glyphTableVariableName, "%s_glyphTable", fontVariableName);
+
+    generateGlyphTable(fontP, glyphTableVariableName);
+
+    fprintf(stdout, "\n");
+        
+    generateFont(fontP, fontVariableName, glyphTableVariableName);
+
+    strfree(glyphTableVariableName);
+
+    ppmd_free_font(fontP);
+    
+    return 0;
+}
diff --git a/other/ppmddumpfont.c b/other/ppmddumpfont.c
new file mode 100644
index 00000000..3ab477ab
--- /dev/null
+++ b/other/ppmddumpfont.c
@@ -0,0 +1,89 @@
+#include <stdio.h>
+#include <assert.h>
+
+#include "ppm.h"
+#include "ppmdfont.h"
+
+
+
+static int
+untwos(unsigned char const arg) {
+
+    if (arg >= 128)
+        return arg - 256;
+    else
+        return arg;
+}
+
+
+
+static void
+dumpHeader(struct ppmd_fontHeader const fontHeader) {
+    
+    pm_message("Font has %u characters", fontHeader.characterCount);
+    pm_message("Font has code points %u through %u",
+               fontHeader.firstCodePoint,
+               fontHeader.firstCodePoint + fontHeader.characterCount - 1);
+}
+
+
+
+static void
+dumpGlyph(struct ppmd_glyph const glyph) {
+
+    unsigned int commandNum;
+
+    pm_message("  skip before: %u pixels; skip after: %u pixels; "
+               "%u commands:", 
+               glyph.header.skipBefore,
+               glyph.header.skipAfter,
+               glyph.header.commandCount);
+
+    for (commandNum = 0;
+         commandNum < glyph.header.commandCount;
+         ++commandNum) {
+         
+        struct ppmd_glyphCommand const glyphCommand =
+            glyph.commandList[commandNum];
+        
+        const char * verbDisp;
+
+        switch (glyphCommand.verb) {
+        case CMD_NOOP:     verbDisp = "NOOP";     break;
+        case CMD_DRAWLINE: verbDisp = "DRAWLINE"; break;
+        case CMD_MOVEPEN:  verbDisp = "MOVEPEN";  break;
+        }
+
+        pm_message("    %s %d %d",
+                   verbDisp, untwos(glyphCommand.x), untwos(glyphCommand.y));
+    }
+}
+
+
+
+int
+main(int argc, char **argv) {
+
+    const struct ppmd_font * fontP;
+    unsigned int relativeCodePoint;
+
+    ppm_init(&argc, argv);
+
+    ppmd_read_font(stdin, &fontP);
+
+    dumpHeader(fontP->header);
+
+    for (relativeCodePoint = 0;
+         relativeCodePoint < fontP->header.characterCount;
+         ++relativeCodePoint) {
+
+        pm_message("Code point %u:",
+                   fontP->header.firstCodePoint + relativeCodePoint);
+
+        dumpGlyph(fontP->glyphTable[relativeCodePoint]);
+    }
+
+    ppmd_free_font(fontP);
+    
+    return 0;
+}
diff --git a/other/ppmdmkfont.c b/other/ppmdmkfont.c
new file mode 100644
index 00000000..7cf1256f
--- /dev/null
+++ b/other/ppmdmkfont.c
@@ -0,0 +1,705 @@
+#include <stdio.h>
+#include <assert.h>
+
+#include "ppm.h"
+#include "mallocvar.h"
+#include "nstring.h"
+#include "ppmdfont.h"
+
+
+/*        Stroke character definitions
+
+   The  following  character  definitions are derived from the (public
+   domain) Hershey plotter  font  database,  using  the  single-stroke
+   Roman font.
+
+   Each  character  definition  begins  with 3 bytes which specify the
+   number of X, Y plot pairs which follow, the negative  of  the  skip
+   before  starting  to  draw  the  characters, and the skip after the
+   character.  The first plot pair moves the pen to that location  and
+   subsequent  pairs  draw  to  the  location given.  A pair of 192, 0
+   raises the pen, moves to the location given by the following  pair,
+   and resumes drawing with the pair after that.
+
+   The  values  in  the  definition  tables are 8-bit two's complement
+   signed numbers.  We  declare  the  table  as  "unsigned  char"  and
+   manually  sign-extend  the  values because C compilers differ as to
+   whether the type "char" is signed or unsigned, and  some  compilers
+   don't  accept the qualifier "signed" which we would like to use for
+   these items.  We specify negative numbers as their  unsigned  two's
+   complements  to  avoid  complaints  from compilers which don't like
+   initialising unsigned data with signed values.  Ahhh,  portability.
+*/
+
+static unsigned char char32[] =
+{ 0, 0, 21 };
+
+static unsigned char char33[] =
+{ 8, 251, 5,
+  0, 244, 0, 2, 192, 0, 0, 7, 255, 8, 0, 9, 1, 8, 0, 7 };
+
+static unsigned char char34[] =
+{ 17, 253, 15,
+  2, 244, 1, 245, 0, 244, 1, 243, 2, 244, 2, 246, 1,
+  248, 0, 249, 192, 0, 10, 244, 9, 245, 8, 244, 9, 243, 10, 244,
+  10, 246, 9, 248, 8, 249, };
+
+static unsigned char char35[] =
+{ 11, 246, 11,
+  1, 240, 250, 16, 192, 0, 7, 240, 0, 16, 192, 0, 250,
+  253, 8, 253, 192, 0, 249, 3, 7, 3 };
+
+static unsigned char char36[] =
+{ 26, 246, 10,
+  254, 240, 254, 13, 192, 0, 2, 240, 2, 13, 192, 0, 7,
+  247, 5, 245, 2, 244, 254, 244, 251, 245, 249, 247, 249, 249, 250,
+  251, 251, 252, 253, 253, 3, 255, 5, 0, 6, 1, 7, 3, 7, 6, 5, 8, 2,
+  9, 254, 9, 251, 8, 249, 6 };
+
+static unsigned char char37[] =
+{ 31, 244, 12,
+  9, 244, 247, 9, 192, 0, 252, 244, 254, 246, 254,
+  248, 253, 250, 251, 251, 249, 251, 247, 249, 247, 247, 248, 245,
+  250, 244, 252, 244, 254, 245, 1, 246, 4, 246, 7, 245, 9, 244,
+  192, 0, 5, 2, 3, 3, 2, 5, 2, 7, 4, 9, 6, 9, 8, 8, 9, 6, 9, 4, 7,
+  2, 5, 2 };
+  
+static unsigned char char38[] =
+{ 34, 243, 13,
+  10, 253, 10, 252, 9, 251, 8, 251, 7, 252, 6, 254, 4,
+  3, 2, 6, 0, 8, 254, 9, 250, 9, 248, 8, 247, 7, 246, 5, 246, 3,
+  247, 1, 248, 0, 255, 252, 0, 251, 1, 249, 1, 247, 0, 245, 254,
+  244, 252, 245, 251, 247, 251, 249, 252, 252, 254, 255, 3, 6, 5,
+  8, 7, 9, 9, 9, 10, 8, 10, 7 };
+
+static unsigned char char39[] =
+{ 7, 251, 5,
+  0, 246, 255, 245, 0, 244, 1, 245, 1, 247, 0, 249, 255,
+  250 };
+
+static unsigned char char40[] =
+{ 10, 249, 7,
+  4, 240, 2, 242, 0, 245, 254, 249, 253, 254, 253, 2,
+  254, 7, 0, 11, 2, 14, 4, 16 };
+
+static unsigned char char41[] =
+{ 10, 249, 7,
+  252, 240, 254, 242, 0, 245, 2, 249, 3, 254, 3, 2, 2,
+  7, 0, 11, 254, 14, 252, 16 };
+
+static unsigned char char42[] =
+{ 8, 248, 8,
+  0, 250, 0, 6, 192, 0, 251, 253, 5, 3, 192, 0, 5, 253,
+  251, 3 };
+
+static unsigned char char43[] =
+{ 5, 243, 13,
+  0, 247, 0, 9, 192, 0, 247, 0, 9, 0 };
+
+static unsigned char char44[] =
+{ 8, 251, 5,
+  1, 8, 0, 9, 255, 8, 0, 7, 1, 8, 1, 10, 0, 12, 255, 13
+};
+
+static unsigned char char45[] =
+{ 2, 243, 13,
+  247, 0, 9, 0 };
+
+static unsigned char char46[] =
+{ 5, 251, 5,
+  0, 7, 255, 8, 0, 9, 1, 8, 0, 7 };
+
+static unsigned char char47[] =
+{ 2, 245, 11,
+  9, 240, 247, 16 };
+
+static unsigned char char48[] =
+{ 17, 246, 10,
+  255, 244, 252, 245, 250, 248, 249, 253, 249, 0, 250,
+  5, 252, 8, 255, 9, 1, 9, 4, 8, 6, 5, 7, 0, 7, 253, 6, 248, 4,
+  245, 1, 244, 255, 244 };
+
+static unsigned char char49[] =
+{ 4, 246, 10,
+  252, 248, 254, 247, 1, 244, 1, 9 };
+
+static unsigned char char50[] =
+{ 14, 246, 10,
+  250, 249, 250, 248, 251, 246, 252, 245, 254, 244, 2,
+  244, 4, 245, 5, 246, 6, 248, 6, 250, 5, 252, 3, 255, 249, 9, 7, 9
+};
+
+static unsigned char char51[] =
+{ 15, 246, 10,
+  251, 244, 6, 244, 0, 252, 3, 252, 5, 253, 6, 254, 7,
+  1, 7, 3, 6, 6, 4, 8, 1, 9, 254, 9, 251, 8, 250, 7, 249, 5 };
+
+
+static unsigned char char52[] =
+{ 6, 246, 10,
+  3, 244, 249, 2, 8, 2, 192, 0, 3, 244, 3, 9 };
+
+static unsigned char char53[] =
+{ 17, 246, 10,
+  5, 244, 251, 244, 250, 253, 251, 252, 254, 251, 1,
+  251, 4, 252, 6, 254, 7, 1, 7, 3, 6, 6, 4, 8, 1, 9, 254, 9, 251,
+  8, 250, 7, 249, 5 };
+
+static unsigned char char54[] =
+{ 23, 246, 10,
+  6, 247, 5, 245, 2, 244, 0, 244, 253, 245, 251, 248,
+  250, 253, 250, 2, 251, 6, 253, 8, 0, 9, 1, 9, 4, 8, 6, 6, 7, 3,
+  7, 2, 6, 255, 4, 253, 1, 252, 0, 252, 253, 253, 251, 255, 250, 2
+};
+
+static unsigned char char55[] =
+{ 5, 246, 10,
+  7, 244, 253, 9, 192, 0, 249, 244, 7, 244 };
+    
+static unsigned char char56[] =
+{ 29, 246, 10,
+  254, 244, 251, 245, 250, 247, 250, 249, 251, 251,
+  253, 252, 1, 253, 4, 254, 6, 0, 7, 2, 7, 5, 6, 7, 5, 8, 2, 9,
+  254, 9, 251, 8, 250, 7, 249, 5, 249, 2, 250, 0, 252, 254, 255,
+  253, 3, 252, 5, 251, 6, 249, 6, 247, 5, 245, 2, 244, 254, 244 };
+
+static unsigned char char57[] =
+{ 23, 246, 10,
+  6, 251, 5, 254, 3, 0, 0, 1, 255, 1, 252, 0, 250,
+  254, 249, 251, 249, 250, 250, 247, 252, 245, 255, 244, 0, 244, 3,
+  245, 5, 247, 6, 251, 6, 0, 5, 5, 3, 8, 0, 9, 254, 9, 251, 8, 250,
+  6 };
+      
+static unsigned char char58[] =
+{ 11, 251, 5,
+  0, 251, 255, 252, 0, 253, 1, 252, 0, 251, 192, 0, 0,
+  7, 255, 8, 0, 9, 1, 8, 0, 7 };
+                                
+static unsigned char char59[] =
+{ 14, 251, 5,
+  0, 251, 255, 252, 0, 253, 1, 252, 0, 251, 192, 0, 1,
+  8, 0, 9, 255, 8, 0, 7, 1, 8, 1, 10, 0, 12, 255, 13 };
+
+static unsigned char char60[] =
+{ 3, 244, 12,
+  8, 247, 248, 0, 8, 9 };
+
+static unsigned char char61[] =
+{ 5, 243, 13,
+  247, 253, 9, 253, 192, 0, 247, 3, 9, 3 };
+
+static unsigned char char62[] =
+{ 3, 244, 12,
+  248, 247, 8, 0, 248, 9 };
+
+static unsigned char char63[] =
+{ 20, 247, 9,
+  250, 249, 250, 248, 251, 246, 252, 245, 254, 244, 2,
+  244, 4, 245, 5, 246, 6, 248, 6, 250, 5, 252, 4, 253, 0, 255, 0,
+  2, 192, 0, 0, 7, 255, 8, 0, 9, 1, 8, 0, 7 };
+
+static unsigned char char64[] =
+{ 55, 243, 14,
+  5, 252, 4, 250, 2, 249, 255, 249, 253, 250, 252,
+  251, 251, 254, 251, 1, 252, 3, 254, 4, 1, 4, 3, 3, 4, 1, 192, 0,
+  255, 249, 253, 251, 252, 254, 252, 1, 253, 3, 254, 4, 192, 0, 5,
+  249, 4, 1, 4, 3, 6, 4, 8, 4, 10, 2, 11, 255, 11, 253, 10, 250, 9,
+  248, 7, 246, 5, 245, 2, 244, 255, 244, 252, 245, 250, 246, 248,
+  248, 247, 250, 246, 253, 246, 0, 247, 3, 248, 5, 250, 7, 252, 8,
+  255, 9, 2, 9, 5, 8, 7, 7, 8, 6, 192, 0, 6, 249, 5, 1, 5, 3, 6, 4
+};
+  
+static unsigned char char65[] =
+{ 8, 247, 9,
+  0, 244, 248, 9, 192, 0, 0, 244, 8, 9, 192, 0, 251, 2,
+  5, 2 };
+
+static unsigned char char66[] =
+{ 23, 245, 10,
+  249, 244, 249, 9, 192, 0, 249, 244, 2, 244, 5, 245,
+  6, 246, 7, 248, 7, 250, 6, 252, 5, 253, 2, 254, 192, 0, 249, 254,
+  2, 254, 5, 255, 6, 0, 7, 2, 7, 5, 6, 7, 5, 8, 2, 9, 249, 9 };
+
+static unsigned char char67[] =
+{ 18, 246, 11,
+  8, 249, 7, 247, 5, 245, 3, 244, 255, 244, 253, 245,
+  251, 247, 250, 249, 249, 252, 249, 1, 250, 4, 251, 6, 253, 8,
+  255, 9, 3, 9, 5, 8, 7, 6, 8, 4 };
+
+static unsigned char char68[] =
+{ 15, 245, 10,
+  249, 244, 249, 9, 192, 0, 249, 244, 0, 244, 3, 245,
+  5, 247, 6, 249, 7, 252, 7, 1, 6, 4, 5, 6, 3, 8, 0, 9, 249, 9 };
+
+static unsigned char char69[] =
+{ 11, 246, 9,
+  250, 244, 250, 9, 192, 0, 250, 244, 7, 244, 192, 0,
+  250, 254, 2, 254, 192, 0, 250, 9, 7, 9 };
+
+static unsigned char char70[] =
+{ 8, 246, 8,
+  250, 244, 250, 9, 192, 0, 250, 244, 7, 244, 192, 0,
+  250, 254, 2, 254 };
+
+static unsigned char char71[] =
+{ 22, 246, 11,
+  8, 249, 7, 247, 5, 245, 3, 244, 255, 244, 253, 245,
+  251, 247, 250, 249, 249, 252, 249, 1, 250, 4, 251, 6, 253, 8,
+  255, 9, 3, 9, 5, 8, 7, 6, 8, 4, 8, 1, 192, 0, 3, 1, 8, 1 };
+
+static unsigned char char72[] =
+{ 8, 245, 11,
+  249, 244, 249, 9, 192, 0, 7, 244, 7, 9, 192, 0, 249,
+  254, 7, 254 };
+
+static unsigned char char73[] =
+{ 2, 252, 4,
+  0, 244, 0, 9 };
+
+static unsigned char char74[] =
+{ 10, 248, 8,
+  4, 244, 4, 4, 3, 7, 2, 8, 0, 9, 254, 9, 252, 8, 251,
+  7, 250, 4, 250, 2 };
+
+static unsigned char char75[] =
+{ 8, 245, 10,
+  249, 244, 249, 9, 192, 0, 7, 244, 249, 2, 192, 0,
+  254, 253, 7, 9 };
+
+static unsigned char char76[] = 
+{ 3, 246, 7,
+  250, 244, 250, 9, 6, 9 };
+
+static unsigned char char77[] =
+{ 11, 244, 12,
+  248, 244, 248, 9, 192, 0, 248, 244, 0, 9, 192, 0, 8,
+  244, 0, 9, 192, 0, 8, 244, 8, 9 };
+                                               
+static unsigned char char78[] =
+{ 8, 245, 11,
+  249, 244, 249, 9, 192, 0, 249, 244, 7, 9, 192, 0, 7,
+  244, 7, 9 };
+                         
+static unsigned char char79[] =
+{ 21, 245, 11,
+  254, 244, 252, 245, 250, 247, 249, 249, 248, 252,
+  248, 1, 249, 4, 250, 6, 252, 8, 254, 9, 2, 9, 4, 8, 6, 6, 7, 4,
+  8, 1, 8, 252, 7, 249, 6, 247, 4, 245, 2, 244, 254, 244 };
+
+static unsigned char char80[] =
+{ 13, 245, 10,
+  249, 244, 249, 9, 192, 0, 249, 244, 2, 244, 5, 245,
+  6, 246, 7, 248, 7, 251, 6, 253, 5, 254, 2, 255, 249, 255 };
+
+static unsigned char char81[] =
+{ 24, 245, 11,
+  254, 244, 252, 245, 250, 247, 249, 249, 248, 252,
+  248, 1, 249, 4, 250, 6, 252, 8, 254, 9, 2, 9, 4, 8, 6, 6, 7, 4,
+  8, 1, 8, 252, 7, 249, 6, 247, 4, 245, 2, 244, 254, 244, 192, 0,
+  1, 5, 7, 11 };
+                           
+static unsigned char char82[] =
+{ 16, 245, 10,
+  249, 244, 249, 9, 192, 0, 249, 244, 2, 244, 5, 245,
+  6, 246, 7, 248, 7, 250, 6, 252, 5, 253, 2, 254, 249, 254, 192, 0,
+  0, 254, 7, 9 };
+
+static unsigned char char83[] =
+{ 20, 246, 10,
+  7, 247, 5, 245, 2, 244, 254, 244, 251, 245, 249,
+  247, 249, 249, 250, 251, 251, 252, 253, 253, 3, 255, 5, 0, 6, 1,
+  7, 3, 7, 6, 5, 8, 2, 9, 254, 9, 251, 8, 249, 6 };
+
+static unsigned char char84[] =
+{ 5, 248, 8,
+  0, 244, 0, 9, 192, 0, 249, 244, 7, 244 };
+
+static unsigned char char85[] =
+{ 10, 245, 11,
+  249, 244, 249, 3, 250, 6, 252, 8, 255, 9, 1, 9, 4,
+  8, 6, 6, 7, 3, 7, 244 };
+
+static unsigned char char86[] =
+{ 5, 247, 9,
+  248, 244, 0, 9, 192, 0, 8, 244, 0, 9 };
+
+static unsigned char char87[] =
+{ 11, 244, 12,
+  246, 244, 251, 9, 192, 0, 0, 244, 251, 9, 192, 0, 0,
+  244, 5, 9, 192, 0, 10, 244, 5, 9 };
+
+static unsigned char char88[] =
+{ 5, 246, 10,
+  249, 244, 7, 9, 192, 0, 7, 244, 249, 9 };
+
+static unsigned char char89[] =
+{ 6, 247, 9,
+  248, 244, 0, 254, 0, 9, 192, 0, 8, 244, 0, 254 };
+
+static unsigned char char90[] =
+{ 8, 246, 10,
+  7, 244, 249, 9, 192, 0, 249, 244, 7, 244, 192, 0,
+  249, 9, 7, 9 };
+
+static unsigned char char91[] =
+{ 11, 249, 7,
+  253, 240, 253, 16, 192, 0, 254, 240, 254, 16, 192, 0,
+  253, 240, 4, 240, 192, 0, 253, 16, 4, 16 };
+
+static unsigned char char92[] =
+{ 2, 245, 11,
+  9, 16, 247, 240 };
+
+static unsigned char char93[] =
+{ 11, 249, 7,
+  2, 240, 2, 16, 192, 0, 3, 240, 3, 16, 192, 0, 252,
+  240, 3, 240, 192, 0, 252, 16, 3, 16 };
+
+static unsigned char char94[] =
+{ 7, 245, 11,
+  248, 2, 0, 253, 8, 2, 192, 0, 248, 2, 0, 254, 8, 2 };
+
+static unsigned char char95[] =
+{ 2, 253, 22,
+  0, 9, 20, 9 };
+
+static unsigned char char96[] =
+{ 7, 251, 5,
+  1, 244, 0, 245, 255, 247, 255, 249, 0, 250, 1, 249, 0, 248 };
+
+static unsigned char char97[] =
+{ 17, 247, 10,
+  6, 251, 6, 9, 192, 0, 6, 254, 4, 252, 2, 251, 255,
+  251, 253, 252, 251, 254, 250, 1, 250, 3, 251, 6, 253, 8, 255, 9,
+  2, 9, 4, 8, 6, 6 };
+                                
+static unsigned char char98[] =
+{ 17, 246, 9,
+  250, 244, 250, 9, 192, 0, 250, 254, 252, 252, 254,
+  251, 1, 251, 3, 252, 5, 254, 6, 1, 6, 3, 5, 6, 3, 8, 1, 9, 254,
+  9, 252, 8, 250, 6 };
+                                 
+static unsigned char char99[] =
+{ 14, 247, 9,
+  6, 254, 4, 252, 2, 251, 255, 251, 253, 252, 251, 254,
+  250, 1, 250, 3, 251, 6, 253, 8, 255, 9, 2, 9, 4, 8, 6, 6 };
+
+static unsigned char char100[] =
+{ 17, 247, 10,
+  6, 244, 6, 9, 192, 0, 6, 254, 4, 252, 2, 251, 255,
+  251, 253, 252, 251, 254, 250, 1, 250, 3, 251, 6, 253, 8, 255, 9,
+  2, 9, 4, 8, 6, 6 };
+                                 
+static unsigned char char101[] =
+{ 17, 247, 9,
+  250, 1, 6, 1, 6, 255, 5, 253, 4, 252, 2, 251, 255,
+  251, 253, 252, 251, 254, 250, 1, 250, 3, 251, 6, 253, 8, 255, 9,
+  2, 9, 4, 8, 6, 6 };
+
+static unsigned char char102[] =
+{ 8, 251, 7,
+  5, 244, 3, 244, 1, 245, 0, 248, 0, 9, 192, 0, 253,
+  251, 4, 251 };
+
+static unsigned char char103[] =
+{ 22, 247, 10,
+  6, 251, 6, 11, 5, 14, 4, 15, 2, 16, 255, 16, 253,
+  15, 192, 0, 6, 254, 4, 252, 2, 251, 255, 251, 253, 252, 251,
+  254, 250, 1, 250, 3, 251, 6, 253, 8, 255, 9, 2, 9, 4, 8, 6, 6 };
+
+static unsigned char char104[] =
+{ 10, 247, 10,
+  251, 244, 251, 9, 192, 0, 251, 255, 254, 252, 0,
+  251, 3, 251, 5, 252, 6, 255, 6, 9 };
+
+static unsigned char char105[] =
+{ 8, 252, 4,
+  255, 244, 0, 245, 1, 244, 0, 243, 255, 244, 192, 0,
+  0, 251, 0, 9 };
+
+static unsigned char char106[] =
+{ 11, 251, 5,
+  0, 244, 1, 245, 2, 244, 1, 243, 0, 244, 192, 0, 1,
+  251, 1, 12, 0, 15, 254, 16, 252, 16 };
+
+static unsigned char char107[] =
+{ 8, 247, 8,
+  251, 244, 251, 9, 192, 0, 5, 251, 251, 5, 192, 0, 255, 1, 6, 9 };
+
+static unsigned char char108[] =
+{ 2, 252, 4,
+  0, 244, 0, 9 };
+                                        
+static unsigned char char109[] =
+{ 18, 241, 15,
+  245, 251, 245, 9, 192, 0, 245, 255, 248, 252, 250,
+  251, 253, 251, 255, 252, 0, 255, 0, 9, 192, 0, 0, 255, 3, 252,
+  5, 251, 8, 251, 10, 252, 11, 255, 11, 9 };
+
+static unsigned char char110[] =
+{ 10, 247, 10,
+  251, 251, 251, 9, 192, 0, 251, 255, 254, 252, 0,
+  251, 3, 251, 5, 252, 6, 255, 6, 9 };
+
+static unsigned char char111[] =
+{ 17, 247, 10,
+ 255, 251, 253, 252, 251, 254, 250, 1, 250, 3, 251,
+  6, 253, 8, 255, 9, 2, 9, 4, 8, 6, 6, 7, 3, 7, 1, 6, 254, 4, 252,
+  2, 251, 255, 251 };
+
+static unsigned char char112[] =
+{ 17, 246, 9,
+  250, 251, 250, 16, 192, 0, 250, 254, 252, 252, 254,
+  251, 1, 251, 3, 252, 5, 254, 6, 1, 6, 3, 5, 6, 3, 8, 1, 9, 254,
+  9, 252, 8, 250, 6 };
+
+static unsigned char char113[] =
+{ 17, 247, 10,
+  6, 251, 6, 16, 192, 0, 6, 254, 4, 252, 2, 251, 255,
+  251, 253, 252, 251, 254, 250, 1, 250, 3, 251, 6, 253, 8, 255, 9,
+  2, 9, 4, 8, 6, 6 };
+                                 
+static unsigned char char114[] =
+{ 8, 249, 6,
+  253, 251, 253, 9, 192, 0, 253, 1, 254, 254, 0, 252,
+  2, 251, 5, 251 };
+                               
+static unsigned char char115[] =
+{ 17, 248, 9,
+  6, 254, 5, 252, 2, 251, 255, 251, 252, 252, 251,
+  254, 252, 0, 254, 1, 3, 2, 5, 3, 6, 5, 6, 6, 5, 8, 2, 9, 255, 9,
+  252, 8, 251, 6 };
+
+static unsigned char char116[] =
+{ 8, 251, 7,
+  0, 244, 0, 5, 1, 8, 3, 9, 5, 9, 192, 0, 253, 251, 4, 251 };
+
+static unsigned char char117[] =
+{ 10, 247, 10,
+  251, 251, 251, 5, 252, 8, 254, 9, 1, 9, 3, 8, 6, 5,
+  192, 0, 6, 251, 6, 9 };
+
+static unsigned char char118[] =
+{ 5, 248, 8,
+  250, 251, 0, 9, 192, 0, 6, 251, 0, 9 };
+                                                                
+static unsigned char char119[] =
+{ 11, 245, 11,
+  248, 251, 252, 9, 192, 0, 0, 251, 252, 9, 192, 0,
+  0, 251, 4, 9, 192, 0, 8, 251, 4, 9 };
+
+static unsigned char char120[] =
+{ 5, 248, 9,
+  251, 251, 6, 9, 192, 0, 6, 251, 251, 9 };
+
+static unsigned char char121[] =
+{ 9, 248, 8,
+  250, 251, 0, 9, 192, 0, 6, 251, 0, 9, 254, 13, 252,
+  15, 250, 16, 249, 16 };
+                                     
+static unsigned char char122[] =
+{ 8, 248, 9,
+  6, 251, 251, 9, 192, 0, 251, 251, 6, 251, 192, 0,
+  251, 9, 6, 9 };
+
+static unsigned char char123[] =
+{ 39, 249, 7,
+  2, 240, 0, 241, 255, 242, 254, 244, 254, 246, 255,
+  248, 0, 249, 1, 251, 1, 253, 255, 255, 192, 0, 0, 241, 255, 243,
+  255, 245, 0, 247, 1, 248, 2, 250, 2, 252, 1, 254, 253, 0, 1, 2,
+  2, 4, 2, 6, 1, 8, 0, 9, 255, 11, 255, 13, 0, 15, 192, 0, 255, 1,
+  1, 3, 1, 5, 0, 7, 255, 8, 254, 10, 254, 12, 255, 14, 0, 15, 2, 16 };
+
+static unsigned char char124[] =
+{ 2, 252, 4,
+  0, 240, 0, 16 };
+
+static unsigned char char125[] =
+{ 39, 249, 7,
+  254, 240, 0, 241, 1, 242, 2, 244, 2, 246, 1, 248, 0,
+  249, 255, 251, 255, 253, 1, 255, 192, 0, 0, 241, 1, 243, 1, 245,
+  0, 247, 255, 248, 254, 250, 254, 252, 255, 254, 3, 0, 255, 2,
+  254, 4, 254, 6, 255, 8, 0, 9, 1, 11, 1, 13, 0, 15, 192, 0, 1, 1,
+  255, 3, 255, 5, 0, 7, 1, 8, 2, 10, 2, 12, 1, 14, 0, 15, 254, 16 };
+
+static unsigned char char126[] =
+{ 23, 255, 21,
+  2, 1, 0, 255, 1, 253, 3, 251, 5, 251, 7, 252,
+  11, 255, 13, 0, 15, 0, 17, 255, 18, 254, 192, 0, 2, 0, 1,
+  254, 3, 253, 5, 253, 7, 254, 11, 1, 13, 2, 15, 2, 17, 1, 18,
+  255, 18, 252 };
+
+/* Pointers to character definition tables. */
+
+static unsigned char * fontData[] = {
+    char32, char33, char34, char35, char36, char37, char38, char39, char40,
+    char41, char42, char43, char44, char45, char46, char47, char48, char49,
+    char50, char51, char52, char53, char54, char55, char56, char57, char58,
+    char59, char60, char61, char62, char63, char64, char65, char66, char67,
+    char68, char69, char70, char71, char72, char73, char74, char75, char76,
+    char77, char78, char79, char80, char81, char82, char83, char84, char85,
+    char86, char87, char88, char89, char90, char91, char92, char93, char94,
+    char95, char96, char97, char98, char99, char100, char101, char102,
+    char103, char104, char105, char106, char107, char108, char109, char110,
+    char111, char112, char113, char114, char115, char116, char117, char118,
+    char119, char120, char121, char122, char123, char124, char125, char126
+};
+
+
+
+static void
+writeGlyphCommand(FILE *                   const ofP,
+                  struct ppmd_glyphCommand const glyphCommand) {
+
+    fputc(glyphCommand.verb, ofP);
+    fputc(glyphCommand.x, ofP);
+    fputc(glyphCommand.y, ofP);
+}    
+
+
+
+static void
+writeMovePen(FILE *                const ofP,
+             const unsigned char * const glyphData) {
+
+    struct ppmd_glyphCommand glyphCommand;
+            
+    glyphCommand.verb = CMD_MOVEPEN;
+    glyphCommand.x = glyphData[0];
+    glyphCommand.y = glyphData[1];
+    
+    writeGlyphCommand(ofP, glyphCommand);
+}
+
+
+
+static void
+writeMovePenNoop(FILE *                const ofP,
+                 const unsigned char * const glyphData) {
+
+    struct ppmd_glyphCommand glyphCommand;
+            
+    glyphCommand.verb = CMD_MOVEPEN;
+    glyphCommand.x = glyphData[0];
+    glyphCommand.y = glyphData[1];
+    
+    writeGlyphCommand(ofP, glyphCommand);
+                
+    glyphCommand.verb = CMD_NOOP;
+    glyphCommand.x = 0;
+    glyphCommand.y = 0;
+
+    writeGlyphCommand(ofP, glyphCommand);
+}
+
+
+
+static void
+writeDrawLine(FILE *                const ofP,
+              const unsigned char * const glyphData) {
+
+    struct ppmd_glyphCommand glyphCommand;
+
+    glyphCommand.verb = CMD_DRAWLINE;
+    glyphCommand.x = glyphData[0];
+    glyphCommand.y = glyphData[1];
+    
+    writeGlyphCommand(ofP, glyphCommand);
+}
+            
+
+
+static void
+writeGlyphHeader(FILE *                  const ofP,
+                 struct ppmd_glyphHeader const glyphHeader) {
+
+    fputc(glyphHeader.commandCount, ofP);
+    fputc(glyphHeader.skipBefore, ofP);
+    fputc(glyphHeader.skipAfter, ofP);
+}    
+
+
+
+static void
+writeBuiltinCharacter(FILE *       const ofP,
+                      unsigned int const relativeCodePoint) {
+
+    const unsigned char * const glyphData = fontData[relativeCodePoint];
+
+    struct ppmd_glyphHeader glyphHeader;
+    unsigned int commandNum;
+
+    glyphHeader.commandCount = glyphData[0];
+    glyphHeader.skipBefore   = glyphData[1];
+    glyphHeader.skipAfter    = glyphData[2];
+
+    writeGlyphHeader(ofP, glyphHeader);
+
+    commandNum = 0;
+
+    while (commandNum < glyphHeader.commandCount) {
+            
+        if (commandNum == 0) {
+            writeMovePen(ofP, &glyphData[3 + commandNum * 2]);
+            commandNum += 1;
+        } else if (glyphData[3 + commandNum*2] == 192) {
+
+            assert(commandNum + 1 < glyphHeader.commandCount);
+
+            writeMovePenNoop(ofP, &glyphData[3 + (commandNum + 1) * 2]);
+
+            commandNum += 2;
+        } else {
+            writeDrawLine(ofP, &glyphData[3 + commandNum * 2]);
+            commandNum += 1;
+        }
+    }
+}
+
+
+
+static void
+writeFontHeader(FILE *                 const ofP,
+                struct ppmd_fontHeader const fontHeader) {
+
+    fwrite(fontHeader.signature, 1, sizeof(fontHeader.signature), ofP);
+    fputc(fontHeader.format, ofP);
+    fputc(fontHeader.characterCount, ofP);
+    fputc(fontHeader.firstCodePoint, ofP);
+}
+
+
+
+static void
+writeBuiltinFont(FILE * const ofP) {
+
+    unsigned int relativeCodePoint;
+
+    struct ppmd_fontHeader fontHeader;
+
+    memcpy(fontHeader.signature, "ppmdfont", sizeof(fontHeader.signature));
+    fontHeader.format         = 0x01;
+    fontHeader.characterCount = 95;
+    fontHeader.firstCodePoint = 32;
+
+    writeFontHeader(ofP, fontHeader);
+
+    for (relativeCodePoint = 0;
+         relativeCodePoint < fontHeader.characterCount;
+         ++relativeCodePoint) {
+
+        writeBuiltinCharacter(ofP,relativeCodePoint);
+    }
+}
+
+
+
+int
+main(int argc, char **argv) {
+
+    ppm_init(&argc, argv);
+
+    writeBuiltinFont(stdout);
+    
+    return 0;
+}
diff --git a/other/ppmsvgalib.c b/other/ppmsvgalib.c
new file mode 100644
index 00000000..67cc2b1a
--- /dev/null
+++ b/other/ppmsvgalib.c
@@ -0,0 +1,283 @@
+/******************************************************************************
+                               ppmsvgalib
+*******************************************************************************
+   Display a PPM image on a Linux console using Svgalib.
+
+   By Bryan Henderson, San Jose CA 2002.01.06.
+
+   Contributed to the public domain.
+   
+******************************************************************************/
+
+#define _XOPEN_SOURCE    /* Make sure modern signal stuff is in signal.h */
+#include <stdio.h>
+#include <vga.h>
+#include <string.h>
+#include <errno.h>
+#include <signal.h>
+#include <unistd.h>
+#include "ppm.h"
+#include "shhopt.h"
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *inputFilespec;  /* Filespec of input file */
+    unsigned int mode;
+    unsigned int verbose;
+};
+
+
+
+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 = malloc( 100*sizeof( optEntry ) );
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    unsigned int modeSpec;
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0,   "mode",         OPT_UINT,
+            &cmdlineP->mode,   &modeSpec, 0);
+    OPTENT3(0,   "verbose",      OPT_FLAG,
+            NULL,   &cmdlineP->verbose,   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 (!modeSpec)
+        pm_error("You must specify the -mode option.");
+
+    if (argc-1 > 1)
+        pm_error("Program takes at most one argument: the input file "
+                 "specification.  "
+                 "You specified %d arguments.", argc-1);
+    if (argc-1 < 1)
+        cmdlineP->inputFilespec = "-";
+    else
+        cmdlineP->inputFilespec = argv[1];
+}
+
+
+
+static void
+displayImage(FILE * const ifP, 
+             int    const cols, 
+             int    const rows,
+             pixval const maxval, 
+             int    const format,
+             int    const originCol,
+             int    const originRow) {
+/*----------------------------------------------------------------------------
+   Draw the PPM image which is in file 'ifP', which is positioned after the
+   PPM header on the screen with its upper left corner (originCol, originRow).
+
+   The image is 'cols' x 'rows' with maxval 'maxval' and PNM format 'format'.
+
+   Svgalib is initialized and the mode selected.
+
+   The image fits on the screen.
+-----------------------------------------------------------------------------*/
+    unsigned int svgalibMaxval = 255;
+        /* This is the maxval for intensity values passed to Svgalib */
+    unsigned int row;
+    pixel * pixelrow;
+
+    pixelrow = ppm_allocrow(cols);
+
+    /* Implementation note:  It might be faster to use 
+       vga_drawscansegment() instead of vga_drawpixel()
+    */
+
+    for (row = 0; row < rows; ++row) {
+        unsigned int col;
+        ppm_readppmrow(ifP, pixelrow, cols, maxval, format);
+        for (col = 0; col < cols; ++col) {
+            pixel const p = pixelrow[col];
+            int const red = PPM_GETR(p) * svgalibMaxval / maxval;
+            int const grn = PPM_GETG(p) * svgalibMaxval / maxval;
+            int const blu = PPM_GETB(p) * svgalibMaxval / maxval;
+
+            vga_setrgbcolor(red, grn, blu);
+            vga_drawpixel(originCol + col, originRow + row);
+        }
+    }
+    ppm_freerow(pixelrow);
+}
+
+
+
+static void
+sigintHandler(int const signal) {
+/*----------------------------------------------------------------------------
+   This is a signal handler for the SIGINT signal (Control-C).
+
+   It does nothing; The handler exists only to replace the default action,
+   which is to terminate the process.  Though the handler does nothing,
+   the signal still causes the wait() system call, assuming it's in progress,
+   to terminate so that this program can terminate.
+-----------------------------------------------------------------------------*/
+}
+
+
+
+static void 
+waitforSigint(void) {
+
+    struct sigaction oldsigaction;
+    struct sigaction newsigaction;
+    int rc;
+    
+    newsigaction.sa_handler = &sigintHandler;
+    sigemptyset(&newsigaction.sa_mask);
+    newsigaction.sa_flags = 0;
+    rc = sigaction(SIGINT, &newsigaction, &oldsigaction);
+    if (rc != 0)
+        pm_error("Unable to set up SIGINTR signal handler.  Errno=%d (%s)",
+                 errno, strerror(errno));
+
+    pause();  /* Wait for a signal, e.g. control-C */
+
+    sigaction(SIGINT, &oldsigaction, NULL);
+}
+
+
+
+static void
+display(FILE * const ifP, 
+        int    const cols, 
+        int    const rows, 
+        pixval const maxval, 
+        int    const format, 
+        int    const videoMode, 
+        bool   const verbose) {
+
+    int xmax, ymax;
+    vga_modeinfo *modeinfo;
+
+    modeinfo = vga_getmodeinfo(videoMode);
+    
+    if (verbose) {
+        pm_message("Screen Width: %d  Height: %d  Colors: %d",
+                   modeinfo->width,
+                   modeinfo->height,
+                   modeinfo->colors);
+        pm_message("DisplayStartRange: %xh  Maxpixels: %d  Blit: %s",
+                   modeinfo->startaddressrange,
+                   modeinfo->maxpixels,
+                   modeinfo->haveblit ? "YES" : "NO");
+    }
+
+    if (modeinfo->colors <= 256)
+        pm_error("This video mode has %d or fewer colors, which means "
+                 "it is colormapped (aka paletted, aka pseudocolor).  "
+                 "This program cannot drive colormapped modes.", 
+                 modeinfo->colors);
+
+    if (cols > modeinfo->width)
+        pm_error("Image is too wide (%d columns) for screen (%d columns).  "
+                 "Use Pamcut to select part to display.", 
+                 cols, modeinfo->width);
+    if (rows > modeinfo->height)
+        pm_error("Image is too tall (%d rows) for screen (%d rows).  "
+                 "Use Pamcut to select part to display.",
+                 rows, modeinfo->height);
+    
+    /* The program must not terminate after we set the video mode and before
+       we reset it to text mode.  Note that vga_setmode() sets up handlers
+       for signals such as SIGINT that attempt to restore modes and then exit
+       the program.
+    */
+
+    vga_setmode(videoMode);
+
+    vga_screenoff();
+
+    xmax = vga_getxdim() - 1;
+    ymax = vga_getydim() - 1;
+
+    /* Draw white border */
+
+    vga_setcolor(vga_white());
+    vga_drawline(0, 0, xmax, 0);
+    vga_drawline(xmax, 0, xmax, ymax);
+    vga_drawline(xmax, ymax, 0, ymax);
+    vga_drawline(0, ymax, 0, 0);
+
+    vga_screenon();
+
+    {
+        int const originCol = (modeinfo->width - cols) / 2;
+        int const originRow = (modeinfo->height - rows) / 2;
+        displayImage(ifP, cols, rows, maxval, format, originCol, originRow);
+    }
+
+    waitforSigint();
+
+    vga_setmode(TEXT);
+}
+
+
+
+int 
+main(int argc, char *argv[]) {
+
+    FILE * ifP;
+    struct cmdlineInfo cmdline;
+    int cols, rows;
+    pixval maxval;
+    int format;
+    int rc;
+
+    ppm_init( &argc, argv );
+
+    rc = vga_init();         /* Initialize. */
+    if (rc < 0)
+        pm_error("Svgalib unable to allocate a virtual console.");
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilespec);
+
+    ppm_readppminit(ifP, &cols, &rows, &maxval, &format);
+
+    {
+        enum pm_check_code checkResult;
+        ppm_check(ifP, PM_CHECK_BASIC, format, cols, rows, maxval, 
+                  &checkResult);
+    }
+
+    if (vga_hasmode(cmdline.mode))
+        display(ifP, cols, rows, maxval, format, 
+                cmdline.mode, cmdline.verbose);
+    else {
+        pm_error("Svgalib video mode #%d not available.  Either the "
+                 "video controller isn't capable of that mode or the "
+                 "Svgalib video driver doesn't know how to use it.",
+                 cmdline.mode);
+    }
+
+    pm_close(ifP);
+
+    return 0;
+}
diff --git a/other/ppmtomap b/other/ppmtomap
new file mode 100755
index 00000000..1d7ed940
--- /dev/null
+++ b/other/ppmtomap
@@ -0,0 +1,5 @@
+#! /bin/sh
+
+# This program exists for backward compatibility.
+
+pnmcolormap all $@