about summary refs log tree commit diff
path: root/editor/specialty
diff options
context:
space:
mode:
Diffstat (limited to 'editor/specialty')
-rw-r--r--editor/specialty/Makefile2
-rw-r--r--editor/specialty/pamdeinterlace.c2
-rw-r--r--editor/specialty/pammixinterlace.c2
-rw-r--r--editor/specialty/pampaintspill.c500
-rw-r--r--editor/specialty/pgmabel.c6
-rw-r--r--editor/specialty/pgmmorphconv.c520
-rw-r--r--editor/specialty/pnmindex.c81
-rw-r--r--editor/specialty/pnmmercator.c430
-rw-r--r--editor/specialty/ppm3d.c4
-rw-r--r--editor/specialty/ppmglobe.c4
-rw-r--r--editor/specialty/ppmntsc.c311
-rw-r--r--editor/specialty/ppmrelief.c112
12 files changed, 1547 insertions, 427 deletions
diff --git a/editor/specialty/Makefile b/editor/specialty/Makefile
index 76befbb4..427c2c8f 100644
--- a/editor/specialty/Makefile
+++ b/editor/specialty/Makefile
@@ -11,11 +11,13 @@ PORTBINARIES = pamdeinterlace \
 	       pammixinterlace \
 	       pamoil \
 	       pampop9 \
+	       pampaintspill \
 	       pbmlife \
 	       pgmabel \
 	       pgmbentley \
 	       pgmmorphconv \
 	       pnmindex \
+	       pnmmercator \
 	       ppm3d \
 	       ppmglobe \
 	       ppmntsc \
diff --git a/editor/specialty/pamdeinterlace.c b/editor/specialty/pamdeinterlace.c
index 7c6b123c..d6f6aee1 100644
--- a/editor/specialty/pamdeinterlace.c
+++ b/editor/specialty/pamdeinterlace.c
@@ -47,7 +47,7 @@ parseCommandLine(int argc, char ** argv,
     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);
+    pm_optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
         /* Uses and sets argc, argv, and some of *cmdlineP and others. */
 
     free(option_def);
diff --git a/editor/specialty/pammixinterlace.c b/editor/specialty/pammixinterlace.c
index 9f98b406..7410a8f1 100644
--- a/editor/specialty/pammixinterlace.c
+++ b/editor/specialty/pammixinterlace.c
@@ -200,7 +200,7 @@ parseCommandLine(int argc, char ** argv,
     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);
+    pm_optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
     /* Uses and sets argc, argv, and some of *cmdlineP and others. */
 
     if (!filterSpec)
diff --git a/editor/specialty/pampaintspill.c b/editor/specialty/pampaintspill.c
new file mode 100644
index 00000000..745c9b68
--- /dev/null
+++ b/editor/specialty/pampaintspill.c
@@ -0,0 +1,500 @@
+/* ----------------------------------------------------------------------
+ *
+ * Bleed colors from non-background colors into the background
+ *
+ * By Scott Pakin <scott+pbm@pakin.org>
+ *
+ * ----------------------------------------------------------------------
+ *
+ * Copyright (C) 2010 Scott Pakin <scott+pbm@pakin.org>
+ *
+ * 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 3 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, see http://www.gnu.org/licenses/.
+ *
+ * ----------------------------------------------------------------------
+ */
+
+/*
+  This program contains code to work with Openmp, so that it can process
+  multiple columns at once, using multiple threads on multiple CPU cores,
+  and thus take less elapsed time to run.
+
+  But that code is dead in a normal Netpbm build, as it does not use the
+  required compiler options or link with the required library in any
+  conventional environment we know of.  One can exploit this code with a
+  modified build, e.g. with CADD and LADD make variables.
+
+  10.04.14
+*/
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <time.h>
+
+#include "mallocvar.h"
+#include "nstring.h"
+#include "shhopt.h"
+#include "pam.h"
+#include "pammap.h"
+
+
+static time_t const timeUpdateDelta = 30;
+    /* Seconds between progress updates */
+static int const    minUpdates = 4;
+    /* Minimum number of progress updates to output */
+
+
+struct cmdlineInfo {
+    /* This structure represents all of the information the user
+       supplied in the command line but in a form that's easy for the
+       program to use.
+    */
+    const char * inputFilename;  /* '-' if stdin */
+    const char * bgcolor;
+    unsigned int wrap;
+    unsigned int all;
+    float        power;
+    unsigned int downsample;
+};
+
+struct coords {
+    /* This structure represents an (x,y) coordinate within an image and
+       the color at that coordinate. */
+    unsigned int x;
+    unsigned int y;
+    tuple        color;
+};
+
+typedef double distFunc_t(const struct coords * const p0,
+                          const struct coords * const p1,
+                          unsigned int          const width,
+                          unsigned int          const height);
+    /* Distance function */
+
+
+
+static void
+parseCommandLine(int argc, const char ** const argv,
+                 struct cmdlineInfo * const cmdlineP ) {
+
+    optEntry     * option_def;
+        /* Instructions to OptParseOptions3 on how to parse our options */
+    optStruct3     opt;
+    unsigned int   option_def_index;
+    unsigned int bgcolorSpec, powerSpec, downsampleSpec;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+    option_def_index = 0;          /* Incremented by OPTENTRY */
+
+    OPTENT3(0, "bgcolor",    OPT_STRING, &cmdlineP->bgcolor,    
+            &bgcolorSpec, 0);
+    OPTENT3(0, "wrap",       OPT_FLAG,   NULL,
+            &cmdlineP->wrap,       0);
+    OPTENT3(0, "all",        OPT_FLAG,   NULL,
+            &cmdlineP->all,        0);
+    OPTENT3(0, "power",      OPT_FLOAT,  &cmdlineP->power,      
+            &powerSpec, 0);
+    OPTENT3(0, "downsample", OPT_UINT,   &cmdlineP->downsample, 
+            &downsampleSpec, 0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = 0;
+    opt.allowNegNum = 1;
+
+    pm_optParseOptions3( &argc, (char **)argv, opt, sizeof(opt), 0 );
+
+    if (!bgcolorSpec)
+        cmdlineP->bgcolor = NULL;
+
+    if (!powerSpec)
+        cmdlineP->power = -2.0;
+
+    if (!downsampleSpec)
+        cmdlineP->downsample = 0;
+
+    if (argc-1 < 1)
+        cmdlineP->inputFilename = "-";
+    else {
+        cmdlineP->inputFilename = argv[1];
+        if (argc-1 > 1)
+            pm_error("Too many arguments: %u.  The only argument is the "
+                     "optional input file name", argc-1);
+    }
+}
+
+
+static bool
+tupleEqualColor(const struct pam * const pamP,
+                tuple              const comparand,
+                tuple              const comparator) {
+/*----------------------------------------------------------------------------
+  Report whether two tuples have equal color, regardless of alpha.
+----------------------------------------------------------------------------*/
+    unsigned int const nColorPlanes = pamP->depth >= 3 ? 3 : 1;
+
+    unsigned int plane;
+
+    for (plane = 0; plane < nColorPlanes; ++plane)
+        if (comparand[plane] != comparator[plane])
+            return false;
+
+    return true;
+}
+
+
+
+struct paintSourceSet {
+    struct coords * list;  /* malloc'ed */
+        /* The list of places in the image from which paint comes */
+    unsigned int size;
+        /* Number of entries in sources[] */
+    unsigned int alloc;
+        /* Number of slots for entries of allocated memory */
+};
+
+
+
+static void
+setPaintSourceColors(struct pam *          const pamP,
+                     tuple **              const tuples,
+                     struct paintSourceSet const paintSources) {
+/*----------------------------------------------------------------------------
+   Set the 'color' member of each source in 'paintSources'.
+
+   Set it to the color of the source pixel in tuples[][], indicated by
+   'paintSources'.
+
+   Malloc memory to store these colors -- a contiguous block of memory for all
+   of them.
+-----------------------------------------------------------------------------*/
+    struct pam pamPaint;
+        /* numPaintSources-wide PAM for use by pnm_allocpamrow() */
+    tuple * paintColor;
+        /* Points to storage for the color tuples */
+    unsigned int    i;
+
+    pamPaint = *pamP;
+    pamPaint.width = paintSources.size;
+    paintColor = pnm_allocpamrow(&pamPaint);
+
+    for (i = 0; i < paintSources.size; ++i) {
+        struct coords * const thisSourceP = &paintSources.list[i];
+
+        thisSourceP->color = paintColor[i];
+        pnm_assigntuple(pamP, thisSourceP->color,
+                        tuples[thisSourceP->y][thisSourceP->x]);
+    }
+}
+
+
+
+static void
+addPaintSource(unsigned int            const row,
+               unsigned int            const col,
+               struct paintSourceSet * const paintSourcesP) {
+
+    if (paintSourcesP->size == paintSourcesP->alloc) {
+        paintSourcesP->alloc += 1024;
+        REALLOCARRAY(paintSourcesP->list, paintSourcesP->alloc);
+        if (!paintSourcesP->list)
+            pm_error("Out of memory");
+    }
+    paintSourcesP->list[paintSourcesP->size].x = col;
+    paintSourcesP->list[paintSourcesP->size].y = row;
+    ++paintSourcesP->size;
+}
+
+
+
+static void
+locatePaintSources(struct pam *            const pamP,
+                   tuple **                const tuples,
+                   tuple                   const bgColor,
+                   unsigned int            const downsample,
+                   struct paintSourceSet * const paintSourcesP) {
+/*--------------------------------------------------------------------
+  Construct a list of all pixel coordinates in the input image that
+  represent a non-background color.
+  ----------------------------------------------------------------------*/
+    struct paintSourceSet paintSources;
+    int row;  /* signed so it works with Openmp */
+
+    paintSources.list  = NULL;
+    paintSources.size  = 0;
+    paintSources.alloc = 0;
+
+    #pragma omp parallel for
+    for (row = 0; row < pamP->height; ++row) {
+        unsigned int col;
+        for (col = 0; col < pamP->width; ++col) {
+            if (!tupleEqualColor(pamP, tuples[row][col], bgColor))
+                #pragma omp critical (addPaintSource)
+                addPaintSource(row, col, &paintSources);
+        }
+    }
+
+    pm_message("Image contains %u background + %u non-background pixels",
+               pamP->width * pamP->height - paintSources.size,
+               paintSources.size);
+    
+    /* Reduce the number of paint sources to reduce execution time. */
+    if (downsample > 0 && downsample < paintSources.size) {
+        unsigned int i;
+
+        srand(time(NULL));
+
+        for (i = 0; i < downsample; ++i) {
+            unsigned int const swapIdx =
+                i + rand() % (paintSources.size - i);
+            struct coords const swapVal = paintSources.list[i];
+
+            paintSources.list[i] = paintSources.list[swapIdx];
+            paintSources.list[swapIdx] = swapVal;
+        }
+        paintSources.size = downsample;
+    }
+
+    setPaintSourceColors(pamP, tuples, paintSources);
+
+    *paintSourcesP    = paintSources;
+}
+
+
+
+static distFunc_t euclideanDistanceSqr;
+
+static double
+euclideanDistanceSqr(const struct coords * const p0,
+                     const struct coords * const p1,
+                     unsigned int          const width,
+                     unsigned int          const height) {
+/*----------------------------------------------------------------------------
+   Return the square of the Euclidian distance between p0 and p1.
+-----------------------------------------------------------------------------*/
+    double const deltax = (double) (int) (p1->x - p0->x);
+    double const deltay = (double) (int) (p1->y - p0->y);
+
+    return SQR(deltax) + SQR(deltay);
+}
+
+
+
+static distFunc_t euclideanDistanceTorusSqr;
+
+static double
+euclideanDistanceTorusSqr(const struct coords * const p0,
+                          const struct coords * const p1,
+                          unsigned int          const width,
+                          unsigned int          const height) {
+/*----------------------------------------------------------------------------
+   Return the square of the Euclidian distance between p0 and p1, assuming
+   it's a toroidal surface on which the top row curves around to meet the
+   bottom and the left column to the right.
+-----------------------------------------------------------------------------*/
+    struct coords p0Adj, p1Adj;
+
+    if (p1->x >= p0->x + width / 2) {
+        p0Adj.x = p0->x + width;
+        p1Adj.x = p1->x;
+    } else if (p0->x >= p1->x + width / 2) {
+        p0Adj.x = p0->x;
+        p1Adj.x = p1->x + width;
+    } else {
+        p0Adj.x = p0->x;
+        p1Adj.x = p1->x;
+    }
+    if (p1->y >= p0->y + height / 2) {
+        p0Adj.y = p0->y + height;
+        p1Adj.y = p1->y;
+    } else if (p0->y >= p1->y + height / 2) {
+        p0Adj.y = p0->y;
+        p1Adj.y = p1->y + height;
+    } else {
+        p0Adj.y = p0->y;
+        p1Adj.y = p1->y;
+    }
+
+    return euclideanDistanceSqr(&p0Adj, &p1Adj, 0, 0);
+}
+
+
+
+static void
+reportProgress(unsigned int const rowsComplete,
+               unsigned int const height) {
+
+    static time_t prevOutputTime = 0;
+    time_t        now;                  /* Current time in seconds */
+
+    if (prevOutputTime == 0)
+        prevOutputTime = time(NULL);
+
+    /* Output our progress only every timeUpdateDelta seconds. */
+    now = time(NULL);
+    if (prevOutputTime) {
+        if (now - prevOutputTime >= timeUpdateDelta
+            || rowsComplete % (height/minUpdates) == 0) {
+            pm_message("%5.1f%% complete",
+                       rowsComplete * 100.0 / height);
+            prevOutputTime = now;
+        }
+    } else
+        prevOutputTime = now;
+}
+
+
+
+static void
+spillOnePixel(struct pam *          const pamP,
+              struct coords         const target,
+              struct paintSourceSet const paintSources,
+              distFunc_t *          const distFunc,
+              double                const distPower,
+              tuple                 const outTuple,
+              double *              const newColor) {
+
+    unsigned int plane;
+    unsigned int ps;
+    double       totalWeight;
+
+    for (plane = 0; plane < pamP->depth; ++plane)
+        newColor[plane] = 0.0;
+    totalWeight = 0.0;
+    for (ps = 0; ps < paintSources.size; ++ps) {
+        struct coords const source = paintSources.list[ps];
+        double const distSqr =
+            (*distFunc)(&target, &source,
+                        pamP->width, pamP->height);
+
+        if (distSqr > 0.0) {
+            /* We do special cases for some common cases with code
+               that is much faster than pow().
+            */
+            double const weight =
+                distPower == -2.0 ? 1.0 / distSqr :
+                distPower == -1.0 ? 1.0 / sqrt(distSqr):
+                pow(distSqr, distPower/2);
+
+            unsigned int plane;
+
+            for (plane = 0; plane < pamP->depth; ++plane)
+                newColor[plane] += weight * source.color[plane];
+
+            totalWeight += weight;
+        }
+    }
+    for (plane = 0; plane < pamP->depth; ++plane)
+        outTuple[plane] = (sample) (newColor[plane] / totalWeight);
+}
+
+
+
+static void
+produceOutputImage(struct pam *          const pamP,
+                   tuple **              const intuples,
+                   tuple                 const bgColor,
+                   struct paintSourceSet const paintSources,
+                   distFunc_t *          const distFunc,
+                   double                const distPower,
+                   bool                  const all,
+                   tuple ***             const outtuplesP) {
+/*--------------------------------------------------------------------
+  Color each background pixel (or, if allPixels is 1, all pixels)
+  using a fraction of each paint source as determined by its distance
+  to the background pixel.
+----------------------------------------------------------------------*/
+    int row;   /* signed so it works with Openmp */
+    unsigned int rowsComplete;
+    tuple ** outtuples;
+
+    outtuples = pnm_allocpamarray(pamP);
+
+    rowsComplete = 0;
+    #pragma omp parallel for
+    for (row = 0; row < pamP->height; ++row) {
+        struct coords   target;
+        double        * newColor;
+        
+        MALLOCARRAY(newColor, pamP->depth);
+
+        target.y = row;
+        for (target.x = 0; target.x < pamP->width; ++target.x) {
+            tuple const targetTuple = intuples[target.y][target.x];
+            tuple const outputTuple = outtuples[target.y][target.x];
+
+            if (all || tupleEqualColor(pamP, targetTuple, bgColor))
+                spillOnePixel(pamP, target, paintSources, distFunc, distPower,
+                              outputTuple, newColor);
+            else
+                pnm_assigntuple(pamP,  outputTuple, targetTuple);
+        }
+        #pragma omp critical (rowTally)
+        reportProgress(++rowsComplete, pamP->height);
+
+        free(newColor);
+    }
+    *outtuplesP = outtuples;
+}
+
+
+
+int
+main(int argc, const char *argv[]) {
+    FILE *             ifP;
+    struct cmdlineInfo cmdline;          /* Command-line parameters */
+    tuple              bgColor;          /* Input image's background color */
+    struct paintSourceSet paintSources;
+        /* The set of paint-source indexes into 'tuples' */
+    distFunc_t *       distFunc;         /* The distance function */
+    struct pam inPam;
+    struct pam outPam;
+    tuple ** inTuples;
+    tuple ** outTuples;
+
+    pm_proginit(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilename);
+
+    inTuples = pnm_readpam(ifP, &inPam, PAM_STRUCT_SIZE(allocation_depth));
+
+    pm_close(ifP);
+
+    distFunc = cmdline.wrap ? euclideanDistanceTorusSqr : euclideanDistanceSqr;
+
+    if (cmdline.bgcolor)
+        bgColor = pnm_parsecolor(cmdline.bgcolor, inPam.maxval) ;
+    else
+        bgColor = pnm_backgroundtuple(&inPam, inTuples);
+
+    pm_message("Treating %s as the background color",
+               pnm_colorname(&inPam, bgColor, PAM_COLORNAME_HEXOK));
+
+    locatePaintSources(&inPam, inTuples, bgColor, cmdline.downsample,
+                       &paintSources);
+
+    produceOutputImage(&inPam, inTuples, bgColor, paintSources, distFunc,
+                       cmdline.power, cmdline.all, &outTuples);
+
+    outPam = inPam;
+    outPam.file = stdout;
+    pnm_writepam(&outPam, outTuples);
+
+    pnm_freepamarray(outTuples, &inPam);
+    pnm_freepamarray(inTuples, &outPam);
+
+    return 0;
+}
diff --git a/editor/specialty/pgmabel.c b/editor/specialty/pgmabel.c
index 4914c4be..1764c5d7 100644
--- a/editor/specialty/pgmabel.c
+++ b/editor/specialty/pgmabel.c
@@ -38,15 +38,15 @@ static const char* const version="$VER: pgmabel 1.009 (24 Jan 2002)";
 
 #include <math.h>
 #include <stdlib.h>   /* for calloc */
-#include "pgm.h"
+
+#include "pm_c_util.h"
 #include "mallocvar.h"
+#include "pgm.h"
 
 #ifndef PID2          /*  PI/2 (on AMIGA always defined) */
 #define PID2    1.57079632679489661923  
 #endif
 
-#define TRUE 1
-#define FALSE 0
 
 /* some global variables */
 static double *aldl, *ardl;                /* pointer for weighting factors */
diff --git a/editor/specialty/pgmmorphconv.c b/editor/specialty/pgmmorphconv.c
index abc4e718..2ba2d62d 100644
--- a/editor/specialty/pgmmorphconv.c
+++ b/editor/specialty/pgmmorphconv.c
@@ -17,237 +17,403 @@
 */
 
 #include "pm_c_util.h"
+#include "shhopt.h"
+#include "mallocvar.h"
 #include "pgm.h"
 
 
-/************************************************************
- * Dilate 
- ************************************************************/
 
-static int 
-dilate( bit** template, int trowso2, int tcolso2, 
-        gray** in_image, gray** out_image, 
-        int rows, int cols ){
+enum Operation { ERODE, DILATE, OPEN, CLOSE, GRADIENT };
+
+
+
+struct CmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char * inputFileName;  /* File name of input file */
+    const char * templateFileName;  /* File name of template file */
 
-  int c, r, tc, tr;
-  int templatecount;
-  gray source;
+    enum Operation operation;
+};
 
-  for( c=0; c<cols; ++c)
-    for( r=0; r<rows; ++r )
-      out_image[r][c] = 0;   /* only difference with erode is here and below */
-  
-  /* 
-   *  for each non-black pixel of the template
-   *  add in to out
-   */
 
-  templatecount=0;
 
-  for( tr=-trowso2; tr<=trowso2; ++tr ){
-    for( tc=-tcolso2; tc<=tcolso2; ++tc ){
+static void
+parseCommandLine(int argc, const 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;
+        /* Instructions to OptParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+    unsigned int option_def_index;
+    unsigned int erode, dilate, open, close, gradient;
 
-      if( template[trowso2+tr][tcolso2+tc] == PBM_BLACK ) continue;
+    MALLOCARRAY_NOFAIL(option_def, 100);
 
-      ++templatecount;
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0,   "erode",        OPT_FLAG,   NULL, &erode,           0);
+    OPTENT3(0,   "dilate",       OPT_FLAG,   NULL, &dilate,          0);
+    OPTENT3(0,   "open",         OPT_FLAG,   NULL, &open,            0);
+    OPTENT3(0,   "close",        OPT_FLAG,   NULL, &close,           0);
+    OPTENT3(0,   "gradient",     OPT_FLAG,   NULL, &gradient,        0);
 
-      for( r= ((tr>0)?0:-tr) ; r< ((tr>0)?(rows-tr):rows) ; ++r ){
-        for( c= ((tc>0)?0:-tc) ; c< ((tc>0)?(cols-tc):cols) ; ++c ){
-          source = in_image[r+tr][c+tc];
-          out_image[r][c] = MAX(source, out_image[r][c]);
-        } /* for c */
-      } /* for r */
-    } /* for tr */
-  } /* for tc */
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = TRUE;  /* We may have parms that are negative numbers */
 
-  return templatecount;
+    pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
 
-} /* dilate */
+    if (erode + dilate + open + close + gradient > 1)
+        pm_error("You may specify at most one of -erode, -dilate, "
+                 "-open, -close, or -gradient");
 
+    if (erode)
+        cmdlineP->operation = ERODE;
+    else if (dilate)
+        cmdlineP->operation = DILATE;
+    else if (open)
+        cmdlineP->operation = OPEN;
+    else if (close)
+        cmdlineP->operation = CLOSE;
+    else if (gradient)
+        cmdlineP->operation = GRADIENT;
+    else
+        cmdlineP->operation = DILATE;
+
+    if (argc-1 < 1)
+        pm_error("You must specify the template file name as an argument");
+    else {
+        cmdlineP->templateFileName = argv[1];
+
+        if (argc-1 < 2)
+            cmdlineP->inputFileName = "-";
+        else {
+            cmdlineP->inputFileName = argv[2];
+            
+            if (argc-1 > 2)
+                pm_error("Too many arguments: %u.  "
+                         "The only possible arguments "
+                         "are the template file name and the input file name",
+                         argc-1);
+        }
+    }
+}
+
+
+
+static void
+readTemplateMatrix(const char *   const fileName,
+                   bit ***        const templateP,
+                   unsigned int * const rowsP,
+                   unsigned int * const colsP) {
+/*----------------------------------------------------------------------------
+  Read in the template matrix.
+-----------------------------------------------------------------------------*/
+    FILE * templateFileP;
+    int cols, rows;
+
+    templateFileP = pm_openr(fileName);
+
+    *templateP = pbm_readpbm(templateFileP, &cols, &rows);
+
+    pm_close(templateFileP);
+
+    if (cols % 2 != 1 || rows % 2 != 1)
+        pm_error("the template matrix must have an odd number of "
+                 "rows and columns" );
+
+        /* the reason is that we want the middle pixel to be the origin */
+    *rowsP = rows;
+    *colsP = cols;
+}
+
+
+
+static void
+setAllPixel(gray **      const image,
+            unsigned int const rows,
+            unsigned int const cols,
+            gray         const value) {
+
+    unsigned int col;
+
+    for (col = 0; col < cols; ++col) {
+        unsigned int row;
+        for (row = 0; row < rows; ++row)
+            image[row][col] = value; 
+    }
+}
+
+
+
+static void
+dilate(bit **         const template,
+       int            const trowso2,
+       int            const tcolso2, 
+       gray **        const inImage,
+       gray **        const outImage, 
+       unsigned int   const rows,
+       unsigned int   const cols,
+       unsigned int * const templateCountP) {
+
+    unsigned int templateCount;
+    int tr;
+
+    setAllPixel(outImage, rows, cols, 0);
+
+    /* for each non-black pixel of the template add in to out */
+
+    for (tr = -trowso2, templateCount = 0; tr <= trowso2; ++tr) {
+        int tc;
+        for (tc = -tcolso2; tc <= tcolso2; ++tc) {
+            int r;
+            if (template[trowso2+tr][tcolso2+tc] != PBM_BLACK) {
+                ++templateCount;
+
+                for (r = ((tr > 0) ? 0 : -tr);
+                     r < ((tr > 0) ? (rows-tr) : rows);
+                     ++r) {
+                    int c;
+                    for (c = ((tc > 0) ? 0 : -tc);
+                         c < ((tc > 0) ? (cols-tc) : cols);
+                         ++c) {
+                        gray const source = inImage[r+tr][c+tc];
+                        outImage[r][c] = MAX(source, outImage[r][c]);
+                    }
+                }
+            }
+        }
+    }
+    *templateCountP = templateCount;
+}
+
+
+
+static void
+erode(bit **         const template,
+      int            const trowso2,
+      int            const tcolso2, 
+      gray **        const inImage,
+      gray **        const outImage, 
+      unsigned int   const rows,
+      unsigned int   const cols,
+      unsigned int * const templateCountP) {
+
+    unsigned int templateCount;
+    int tr;
+
+    setAllPixel(outImage, rows, cols, PGM_MAXMAXVAL);
+
+    /* For each non-black pixel of the template add in to out */
+
+    for (tr = -trowso2, templateCount = 0; tr <= trowso2; ++tr) {
+        int tc;
+        for (tc = -tcolso2; tc <= tcolso2; ++tc) {
+            if (template[trowso2+tr][tcolso2+tc] != PBM_BLACK) {
+                int r;
+                ++templateCount;
+
+                for (r = ((tr > 0) ? 0 : -tr);
+                     r < ((tr > 0) ? (rows-tr) : rows);
+                     ++r){
+                    int c;
+
+                    for (c = ((tc > 0) ? 0 : -tc);
+                         c < ((tc > 0) ? (cols-tc) : cols);
+                         ++c) {
+                        
+                        gray const source = inImage[r+tr][c+tc];
+                        outImage[r][c] = MIN(source, outImage[r][c]);
+      
+                    }
+                }
+            }
+        }
+    }
+    *templateCountP = templateCount;
+}
 
 
-/************************************************************
- * Erode: same as dilate except !!!!
- ************************************************************/
 
-static int 
-erode( bit** template, int trowso2, int tcolso2, 
-       gray** in_image, gray** out_image, 
-       int rows, int cols ){
+static void
+openMorph(bit **         const template,
+          int            const trowso2,
+          int            const tcolso2, 
+          gray **        const inputImage,
+          gray **        const outputImage, 
+          unsigned int   const rows,
+          unsigned int   const cols,
+          unsigned int * const templateCountP) {
 
-  int c, r, tc, tr;
-  int templatecount;
-  gray source;
+    gray ** erodedImage;
+    unsigned int erodedTemplateCount;
 
-  for( c=0; c<cols; ++c)
-    for( r=0; r<rows; ++r )
-      out_image[r][c] = PGM_MAXMAXVAL; /* !!!! */
-  
-  /* 
-   *  for each non-black pixel of the template
-   *  add in to out
-   */
+    erodedImage = pgm_allocarray(cols, rows);
+    
+    erode(template, trowso2, tcolso2, 
+          inputImage, erodedImage, rows, cols, &erodedTemplateCount);
 
-  templatecount=0;
+    dilate(template, trowso2, tcolso2, 
+           erodedImage, outputImage, rows, cols, templateCountP);
 
-  for( tr=-trowso2; tr<=trowso2; ++tr ){
-    for( tc=-tcolso2; tc<=tcolso2; ++tc ){
+    pgm_freearray(erodedImage, rows);
+}
 
-      if( template[trowso2+tr][tcolso2+tc] == PBM_BLACK ) continue;
 
-      ++templatecount;
 
-      for( r= ((tr>0)?0:-tr) ; r< ((tr>0)?(rows-tr):rows) ; ++r ){
-    for( c= ((tc>0)?0:-tc) ; c< ((tc>0)?(cols-tc):cols) ; ++c ){
+static void
+closeMorph(bit **         const template,
+           int            const trowso2,
+           int            const tcolso2, 
+           gray **        const inputImage,
+           gray **        const outputImage, 
+           unsigned int   const rows,
+           unsigned int   const cols,
+           unsigned int * const templateCountP) {
 
-      source = in_image[r+tr][c+tc];
-      out_image[r][c] = MIN(source, out_image[r][c]);
-      
-    } /* for c */
-      } /* for r */
+    gray ** dilatedImage;
+    unsigned int dilatedTemplateCount;
 
+    dilatedImage = pgm_allocarray(cols, rows);
 
+    dilate(template, trowso2, tcolso2, 
+           inputImage, dilatedImage, rows, cols, &dilatedTemplateCount);
 
-    } /* for tr */
-  } /* for tc */
+    erode(template, trowso2, tcolso2, 
+          dilatedImage, outputImage, rows, cols, templateCountP);
 
-  return templatecount;
+    pgm_freearray(dilatedImage, rows);
+}
 
-} /* erode */
 
 
+static void
+subtract(gray **      const subtrahendImage,
+         gray **      const subtractorImage,
+         gray **      const outImage, 
+         unsigned int const rows,
+         unsigned int const cols ) {
 
-/************************************************************
- *  Main
- ************************************************************/
+    /* (I call the minuend the subtrahend and the subtrahend the subtractor,
+       to be consistent with other arithmetic terminology).
+    */
 
+    unsigned int c;
 
-int main( int argc, char* argv[] ){
+    for (c = 0; c < cols; ++c) {
+        unsigned int r;
+        for (r = 0; r < rows; ++r)
+            outImage[r][c] = subtrahendImage[r][c] - subtractorImage[r][c];
+    }
+}
 
-  int argn;
-  char operation;
-  const char* usage = "-dilate|-erode|-open|-close <templatefile> [pgmfile]";
 
-  FILE* tifp;   /* template */
-  int tcols, trows;
-  int tcolso2, trowso2;
-  bit** template;
 
+static void
+gradient(bit **         const template,
+         int            const trowso2,
+         int            const tcolso2, 
+         gray **        const inputImage,
+         gray **        const outputImage, 
+         unsigned int   const rows,
+         unsigned int   const cols,
+         unsigned int * const templateCountP) {
 
-  FILE*  ifp;   /* input image */
-  int cols, rows;
-  gray maxval;
+    gray ** dilatedImage;
+    gray ** erodedImage;
+    unsigned int dilatedTemplateCount;
+    
+    dilatedImage = pgm_allocarray(cols, rows);
+    erodedImage = pgm_allocarray(cols, rows);
 
-  gray** in_image;
-  gray** out_image;
+    dilate(template, trowso2, tcolso2, 
+           inputImage, dilatedImage, rows, cols, &dilatedTemplateCount);
 
-  int templatecount=0;
+    erode(template, trowso2, tcolso2, 
+          inputImage, erodedImage, rows, cols, templateCountP);
 
-  pgm_init( &argc, argv );
+    subtract(dilatedImage, erodedImage, outputImage, rows, cols);
 
-  /*
-   *  parse arguments
-   */ 
-  
-  ifp = stdin;
-  operation = 'd';
+    pgm_freearray(erodedImage, rows );
+    pgm_freearray(dilatedImage, rows );
+}
 
-  argn=1;
-  
-  if( argn == argc ) pm_usage( usage );
-  
-  if( pm_keymatch( argv[argn], "-erode", 2  )) { operation='e'; argn++; }
-  else
-  if( pm_keymatch( argv[argn], "-dilate", 2 )) { operation='d'; argn++; }
-  else
-  if( pm_keymatch( argv[argn], "-open", 2   )) { operation='o'; argn++; }
-  else
-  if( pm_keymatch( argv[argn], "-close", 2  )) { operation='c'; argn++; }
-  
-  if( argn == argc ) pm_usage( usage );
-  
-  tifp = pm_openr( argv[argn++] );
-  
-  if( argn != argc ) ifp = pm_openr( argv[argn++] );
 
-  if( argn != argc ) pm_usage( usage );
 
-  
-  /* 
-   * Read in the template matrix.
-   */
+int
+main(int argc, const char ** argv) {
 
-  template = pbm_readpbm( tifp, &tcols, &trows );
-  pm_close( tifp );
+    struct CmdlineInfo cmdline;
+    FILE * ifP;
+    bit ** template;
+    unsigned int templateCols, templateRows;
+    int cols, rows;
+    gray maxval;
+    gray ** inputImage;
+    gray ** outputImage;
+    unsigned int templateCount;
 
-  if( tcols % 2 != 1 || trows % 2 != 1 )
-    pm_error("the template matrix must have an odd number of "
-             "rows and columns" );
+    pm_proginit(&argc, argv);
 
-  /* the reason is that we want the middle pixel to be the origin */
-  tcolso2 = tcols / 2; /* template coords run from -tcols/2 .. 0 .. +tcols/2 */
-  trowso2 = trows / 2;
+    parseCommandLine(argc, argv, &cmdline);
 
-#if 0
-  fprintf(stderr, "template: %d  x %d\n", trows, tcols);
-  fprintf(stderr, "half: %d  x %d\n", trowso2, tcolso2);
-#endif
+    ifP = pm_openr(cmdline.inputFileName);
 
-  /*
-   * Read in the image
-   */
-  
-  in_image = pgm_readpgm( ifp, &cols, &rows, &maxval);
+    readTemplateMatrix(cmdline.templateFileName,
+                       &template, &templateRows, &templateCols);
 
-  if( cols < tcols || rows < trows )
-    pm_error("the image is smaller than the convolution matrix" );
+    /* Template coords run from -templateCols/2 .. 0 .. + templateCols/2 */
   
-#if 0
-  fprintf(stderr, "image: %d  x %d (%d)\n", rows, cols, maxval);
-#endif
-
-  /* 
-   * Allocate  output buffer and initialize with min or max value 
-   */
+    inputImage = pgm_readpgm(ifP, &cols, &rows, &maxval);
 
-  out_image = pgm_allocarray( cols, rows );
+    if (cols < templateCols || rows < templateRows)
+        pm_error("the image is smaller than the convolution matrix" );
   
-  if( operation == 'd' ){
-    templatecount = dilate(template, trowso2, tcolso2, 
-               in_image, out_image, rows, cols);
-  } 
-  else if( operation == 'e' ){
-    templatecount = erode(template, trowso2, tcolso2, 
-              in_image, out_image, rows, cols);
-  }
-  else if( operation == 'o' ){
-    gray ** eroded_image;
-    eroded_image = pgm_allocarray( cols, rows );
-    templatecount = erode(template, trowso2, tcolso2, 
-                          in_image, eroded_image, rows, cols);
-    templatecount = dilate(template, trowso2, tcolso2, 
-                           eroded_image, out_image, rows, cols);
-    pgm_freearray( eroded_image, rows );
-  }
-  else if( operation == 'c' ){
-    gray ** dilated_image;
-    dilated_image = pgm_allocarray( cols, rows );
-    templatecount = dilate(template, trowso2, tcolso2, 
-                           in_image, dilated_image, rows, cols);
-    templatecount = erode(template, trowso2, tcolso2, 
-                          dilated_image, out_image, rows, cols);
-    pgm_freearray( dilated_image, rows );
-  }
+    outputImage = pgm_allocarray(cols, rows);
   
-  if(templatecount == 0 ) pm_error( "The template was empty!" );
-
-  pgm_writepgm( stdout, out_image, cols, rows, maxval, 1 );
-
-  pgm_freearray( out_image, rows );
-  pgm_freearray( in_image, rows );
-  pm_close( ifp );
-
-  exit( 0 );
-
-} /* main */
+    switch (cmdline.operation) {
+    case DILATE:
+        dilate(template, templateRows/2, templateCols/2,
+               inputImage, outputImage, rows, cols,
+               &templateCount);
+        break;
+    case ERODE:
+        erode(template, templateRows/2, templateCols/2,
+              inputImage, outputImage, rows, cols,
+              &templateCount);
+        break;
+    case OPEN:
+        openMorph(template, templateRows/2, templateCols/2,
+              inputImage, outputImage, rows, cols,
+              &templateCount);
+        break;
+    case CLOSE:
+        closeMorph(template, templateRows/2, templateCols/2,
+                   inputImage, outputImage, rows, cols,
+                   &templateCount);
+        break;
+    case GRADIENT:
+        gradient(template, templateRows/2, templateCols/2,
+                 inputImage, outputImage, rows, cols,
+                 &templateCount);
+        break;
+    }
+
+    if (templateCount == 0)
+        pm_error( "The template was empty!" );
+
+    pgm_writepgm(stdout, outputImage, cols, rows, maxval, 0);
+
+    pgm_freearray(outputImage, rows);
+    pgm_freearray(inputImage, rows);
+    pm_close(ifP);
+
+    return 0;
+}
 
diff --git a/editor/specialty/pnmindex.c b/editor/specialty/pnmindex.c
index ca1da18c..4ec9edaa 100644
--- a/editor/specialty/pnmindex.c
+++ b/editor/specialty/pnmindex.c
@@ -14,6 +14,7 @@
 
 ============================================================================*/
 
+#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
 #define _BSD_SOURCE   /* Make sure strdup is in string.h */
 
 #include <assert.h>
@@ -59,7 +60,7 @@ systemf(const char * const fmt,
     
     va_start(varargs, fmt);
     
-    vsnprintfN(NULL, 0, fmt, varargs, &dryRunLen);
+    pm_vsnprintf(NULL, 0, fmt, varargs, &dryRunLen);
 
     va_end(varargs);
 
@@ -72,7 +73,7 @@ systemf(const char * const fmt,
         shellCommand = malloc(allocSize);
         if (shellCommand == NULL)
             pm_error("Can't get storage for %u-character command",
-                     allocSize);
+                     (unsigned)allocSize);
         else {
             va_list varargs;
             size_t realLen;
@@ -80,7 +81,7 @@ systemf(const char * const fmt,
 
             va_start(varargs, fmt);
 
-            vsnprintfN(shellCommand, allocSize, fmt, varargs, &realLen);
+            pm_vsnprintf(shellCommand, allocSize, fmt, varargs, &realLen);
                 
             assert(realLen == dryRunLen);
             va_end(varargs);
@@ -93,7 +94,7 @@ systemf(const char * const fmt,
                 pm_error("shell command '%s' failed.  rc %d",
                          shellCommand, rc);
             
-            strfree(shellCommand);
+            pm_strfree(shellCommand);
         }
     }
 }
@@ -106,7 +107,7 @@ parseCommandLine(int argc, char ** argv,
 
     unsigned int option_def_index;
     optEntry *option_def;
-        /* Instructions to optParseOptions3 on how to parse our options.
+        /* Instructions to pm_optParseOptions3 on how to parse our options.
          */
     optStruct3 opt;
 
@@ -137,7 +138,7 @@ parseCommandLine(int argc, char ** argv,
     opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
     opt.allowNegNum = FALSE; 
 
-    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+    pm_optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
         /* Uses and sets argc, argv, and some of *cmdline_p and others. */
 
     if (quant && cmdlineP->noquant)
@@ -184,7 +185,7 @@ freeCmdline(struct cmdlineInfo const cmdline) {
     unsigned int i;
 
     for (i = 0; i < cmdline.inputFileCount; ++i)
-        strfree(cmdline.inputFileName[i]);
+        pm_strfree(cmdline.inputFileName[i]);
 
     free(cmdline.inputFileName);
 
@@ -200,7 +201,7 @@ makeTempDir(const char ** const tempDirP) {
     const char * mytmpdir;
     int rc;
 
-    asprintfN(&mytmpdir, "%s/pnmindex_%d", tmpdir, getpid());
+    pm_asprintf(&mytmpdir, "%s/pnmindex_%d", tmpdir, getpid());
 
     rc = pm_mkdir(mytmpdir, 0700);
     if (rc != 0)
@@ -232,7 +233,7 @@ rowFileName(const char * const dirName,
 
     const char * fileName;
     
-    asprintfN(&fileName, "%s/pi.%u", dirName, row);
+    pm_asprintf(&fileName, "%s/pi.%u", dirName, row);
     
     return fileName;
 }
@@ -259,7 +260,7 @@ makeTitle(const char * const title,
             "> %s", 
             title, invertStage, fileName);
 
-    strfree(fileName);
+    pm_strfree(fileName);
 }
 
 
@@ -285,28 +286,28 @@ copyScaleQuantImage(const char * const inputFileName,
 
     switch (PNM_FORMAT_TYPE(format)) {
     case PBM_TYPE:
-        asprintfN(&scaleCommand, 
-                  "pamscale -quiet -xysize %u %u %s "
-                  "| pgmtopbm > %s",
-                  size, size, inputFileName, outputFileName);
+        pm_asprintf(&scaleCommand, 
+                    "pamscale -quiet -xysize %u %u %s "
+                    "| pgmtopbm > %s",
+                    size, size, inputFileName, outputFileName);
         break;
         
     case PGM_TYPE:
-        asprintfN(&scaleCommand, 
-                  "pamscale -quiet -xysize %u %u %s >%s",
-                  size, size, inputFileName, outputFileName);
+        pm_asprintf(&scaleCommand, 
+                    "pamscale -quiet -xysize %u %u %s >%s",
+                    size, size, inputFileName, outputFileName);
         break;
         
     case PPM_TYPE:
         if (quant)
-            asprintfN(&scaleCommand, 
-                      "pamscale -quiet -xysize %u %u %s "
-                      "| pnmquant -quiet %u > %s",
-                      size, size, inputFileName, colors, outputFileName);
+            pm_asprintf(&scaleCommand, 
+                        "pamscale -quiet -xysize %u %u %s "
+                        "| pnmquant -quiet %u > %s",
+                        size, size, inputFileName, colors, outputFileName);
         else
-            asprintfN(&scaleCommand, 
-                      "pamscale -quiet -xysize %u %u %s >%s",
-                      size, size, inputFileName, outputFileName);
+            pm_asprintf(&scaleCommand, 
+                        "pamscale -quiet -xysize %u %u %s >%s",
+                        size, size, inputFileName, outputFileName);
         break;
     default:
         pm_error("Unrecognized Netpbm format: %d", format);
@@ -314,7 +315,7 @@ copyScaleQuantImage(const char * const inputFileName,
 
     systemf("%s", scaleCommand);
 
-    strfree(scaleCommand);
+    pm_strfree(scaleCommand);
 }
 
 
@@ -340,7 +341,7 @@ thumbnailFileName(const char * const dirName,
 
     const char * fileName;
     
-    asprintfN(&fileName, "%s/pi.%u.%u", dirName, row, col);
+    pm_asprintf(&fileName, "%s/pi.%u.%u", dirName, row, col);
     
     return fileName;
 }
@@ -372,7 +373,7 @@ thumbnailFileList(const char * const dirName,
             strcat(list, " ");
             strcat(list, fileName);
         }
-        strfree(fileName);
+        pm_strfree(fileName);
     }
 
     return list;
@@ -420,7 +421,7 @@ makeThumbnail(const char *  const inputFileName,
     pnm_readpnminit(ifP, &imageCols, &imageRows, &maxval, &format);
     pm_close(ifP);
     
-    asprintfN(&tmpfile, "%s/pi.tmp", tempDir);
+    pm_asprintf(&tmpfile, "%s/pi.tmp", tempDir);
 
     if (imageCols < size && imageRows < size)
         copyImage(inputFileName, tmpfile);
@@ -434,8 +435,8 @@ makeThumbnail(const char *  const inputFileName,
 
     unlink(tmpfile);
 
-    strfree(fileName);
-    strfree(tmpfile);
+    pm_strfree(fileName);
+    pm_strfree(tmpfile);
 
     *formatP = format;
 }
@@ -454,7 +455,7 @@ unlinkThumbnailFiles(const char * const dirName,
 
         unlink(fileName);
 
-        strfree(fileName);
+        pm_strfree(fileName);
     }
 }
 
@@ -471,7 +472,7 @@ unlinkRowFiles(const char * const dirName,
 
         unlink(fileName);
 
-        strfree(fileName);
+        pm_strfree(fileName);
     }
 }
 
@@ -497,7 +498,7 @@ combineIntoRowAndDelete(unsigned int const row,
     unlink(fileName);
 
     if (maxFormatType == PPM_TYPE && quant)
-        asprintfN(&quantStage, "| pnmquant -quiet %u ", colors);
+        pm_asprintf(&quantStage, "| pnmquant -quiet %u ", colors);
     else
         quantStage = strdup("");
 
@@ -508,9 +509,9 @@ combineIntoRowAndDelete(unsigned int const row,
             ">%s",
             blackWhiteOpt, fileList, quantStage, fileName);
 
-    strfree(fileList);
-    strfree(quantStage);
-    strfree(fileName);
+    pm_strfree(fileList);
+    pm_strfree(quantStage);
+    pm_strfree(fileName);
 
     unlinkThumbnailFiles(tempDir, row, cols);
 }
@@ -542,7 +543,7 @@ rowFileList(const char * const dirName,
             strcat(list, " ");
             strcat(list, fileName);
         }
-        strfree(fileName);
+        pm_strfree(fileName);
     }
 
     return list;
@@ -564,7 +565,7 @@ writeRowsAndDelete(unsigned int const rows,
     const char * fileList;
     
     if (maxFormatType == PPM_TYPE && quant)
-        asprintfN(&quantStage, "| pnmquant -quiet %u ", colors);
+        pm_asprintf(&quantStage, "| pnmquant -quiet %u ", colors);
     else
         quantStage = strdup("");
 
@@ -573,8 +574,8 @@ writeRowsAndDelete(unsigned int const rows,
     systemf("pnmcat %s -topbottom %s %s",
             blackWhiteOpt, fileList, quantStage);
 
-    strfree(fileList);
-    strfree(quantStage);
+    pm_strfree(fileList);
+    pm_strfree(quantStage);
 
     unlinkRowFiles(tempDir, rows);
 }
diff --git a/editor/specialty/pnmmercator.c b/editor/specialty/pnmmercator.c
new file mode 100644
index 00000000..cd9ff19b
--- /dev/null
+++ b/editor/specialty/pnmmercator.c
@@ -0,0 +1,430 @@
+/* pammercator.c - convert a map in PNM image format from 360x180 degrees
+**                 to Mercator projection or vice versa
+**
+** This program borrowed a lot of code from PnmScale which again was derived
+** from PpmScale.
+**
+** Copyright (C) 2009 by Willem van Schaik <willem@schaik.com>
+** Copyright (C) 1989, 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+**
+*/
+
+#define _XOPEN_SOURCE  /* Make sure M_PI is in <math.h> */
+#include <math.h>
+#include <string.h>
+
+#include "pm_c_util.h"
+#include "mallocvar.h"
+#include "shhopt.h"
+#include "pnm.h"
+
+/* The pnm library allows us to code this program without branching cases for
+   PGM and PPM, but we do the branch anyway to speed up processing of PGM
+   images. 
+*/
+
+
+
+struct cmdlineInfo 
+{
+    /*
+    All the information the user supplied in the command line,
+    in a form easy for the program to use. 
+    */
+
+    const char * input_filespec;  /* Filespecs of input files */
+    const char * inputFileName;  /* Filespec of input file */
+    unsigned int inverse; /* from Mercator to Degrees */
+    unsigned int nomix;
+    unsigned int verbose;
+    unsigned int vverbose;
+};
+
+
+
+static void
+parseCommandLine(int                        argc, 
+                 const char **              argv,
+                 struct cmdlineInfo * const cmdlineP ) {
+
+    optEntry * option_def;
+    optStruct3 opt;
+        /* Instructions to pm_optParseOptions3 on how to parse our options. */
+    
+    unsigned int option_def_index;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "inverse",   OPT_FLAG,    NULL,       &cmdlineP->inverse,   0);
+    OPTENT3(0, "nomix",     OPT_FLAG,    NULL,       &cmdlineP->nomix,     0);
+    OPTENT3(0, "verbose",   OPT_FLAG,    NULL,       &cmdlineP->verbose,   0);
+    OPTENT3(0, "vverbose",  OPT_FLAG,    NULL,       &cmdlineP->vverbose,  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 */
+
+    /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+    pm_optParseOptions3( &argc, (char **)argv, opt, sizeof(opt), 0 );
+
+    /* Only parameter allowed is optional filespec */
+    if (argc-1 < 1)
+        cmdlineP->input_filespec = "-";
+    else
+        cmdlineP->input_filespec = argv[1];
+}
+
+
+
+static void
+computeOutputDimensions(const struct cmdlineInfo cmdline, 
+                        const int rows, const int cols,
+                        int * newrowsP, int * newcolsP) 
+{
+    *newcolsP = cols;
+    if (!cmdline.inverse)
+        *newrowsP = 2 * rows;
+    else
+        *newrowsP = rows / 2;
+
+    if (*newcolsP < 1) *newcolsP = 1;
+    if (*newrowsP < 1) *newrowsP = 1;
+
+    if (cmdline.verbose) {
+        if (!cmdline.inverse)
+            pm_message("Creating Mercator map, new size is %dx%d",
+                       *newcolsP, *newrowsP);
+        else
+            pm_message("Creating Degrees map, new size is %dx%d",
+                       *newcolsP, *newrowsP);
+    }
+}        
+
+
+
+static void
+transformWithMixing(FILE * const ifP,
+                    int const cols, int const rows,
+                    xelval const maxval, int const format,
+                    int const newcols, int const newrows,
+                    xelval const newmaxval, int const newformat,
+                    bool const inverse,
+                    bool const verbose, bool const vverbose) 
+{
+    /* 
+    Transform the map image on input file 'ifP' (which is described by 
+    'cols', 'rows', 'format', and 'maxval') to a Mercator projection
+    and write the result to standard output as format 'newformat' and 
+    with maxval 'newmaxval'.
+
+    We'll blend colors from subsequent rows in the output pixels. 
+    */
+
+    xel* oddxelrow;  /* an input row */
+    xel* evenxelrow;
+    xel* newxelrow;  /* the output row */
+
+    int row;
+    double fRow;
+    int rowInXelrow;
+    int inputRow;
+    int col;
+
+    double fFract;
+    double fLatRad;
+    double fLatMerc;
+
+    oddxelrow = pnm_allocrow(cols); 
+    evenxelrow = pnm_allocrow(cols); 
+    rowInXelrow = 0;
+
+    newxelrow = pnm_allocrow(newcols);
+
+    for (row = 0; row < newrows; ++row) {
+        
+        fRow = (double)row + 0.5; /* center on half the pixel */
+        if (!inverse) {
+            /* the result is mercator, calculate back to degrees */
+            fLatMerc = 2.0 * M_PI * (fRow / (double)newrows - 0.5);
+                /* merc goes from -pi to +pi */
+            fLatRad = 2.0 * (atan(exp(fLatMerc)) - M_PI / 4.0);
+            fRow = ((fLatRad / M_PI) + 0.5) * (double)rows;
+                /* lat goes from -pi/2 to +pi/2 */
+        } else {
+            /* the result is in degrees, calculate back to mercator */
+            fLatRad = M_PI * (fRow / (double)newrows - 0.5);
+                /* lat goes from -pi/2 to +pi/2 */
+            fLatMerc = log(tan((M_PI / 4.0 + fLatRad / 2.0)));
+            fRow = ((fLatMerc / M_PI / 2.0) + 0.5) * (double)rows;
+                /* merc goes from -pi to +pi */
+        }
+        fRow -= 0.5;
+
+        inputRow = (int) floor(fRow);
+        if (inputRow < 0)
+            inputRow = 0;
+        if (inputRow > rows - 1)
+            inputRow = rows - 1;
+
+        /* calculate the fraction */
+        fFract = 1.0 - ((fRow) - (double) inputRow);
+
+        if (vverbose) {
+            if (!inverse) {
+#ifdef DBG_EDGES
+                if ((row < 10) || (row > (newrows - 10)))
+#else
+  #ifdef DBG_MIDDLE
+                if ((row > (newrows/2 - 10)) && (row < (newrows/2 + 10)))
+  #else
+                if (row % 20 == 0)
+  #endif
+#endif
+                    pm_message("outputRow=%d latMerc=%f latRad=%f inputRow=%d "
+                               "fRow=%f fFract=%f",
+                               row, fLatMerc, fLatRad, inputRow, fRow, fFract);
+            } else {
+#ifdef DBG_EDGES
+                if ((row < 10) || (row > (newrows - 10)))
+#else
+  #ifdef DBG_MIDDLE
+                if ((row > (newrows/2 - 10)) && (row < (newrows/2 + 10)))
+  #else
+                if (row % 10 == 0)
+  #endif
+#endif
+                    pm_message("outputRow=%d latRad=%f latMerc=%f inputRow=%d"
+                               "fRow=%f fFract=%f",
+                               row, fLatRad, fLatMerc, inputRow, fRow, fFract);
+            }
+        }
+
+        while ((rowInXelrow <= inputRow + 1) && 
+               (rowInXelrow < rows)) {
+            /* we need to read one row ahead */
+            if (rowInXelrow % 2 == 0) {
+#ifdef DBG_EDGES
+                if ((row < 10) || (row > (newrows - 10)))
+                    pm_message("read even row - rowInXelrow=%d inputRow=%d",
+                               rowInXelrow, inputRow);
+#endif
+                pnm_readpnmrow(ifP, evenxelrow, cols, newmaxval, format);
+            } else {
+#ifdef DBG_EDGES
+                if ((row < 10) || (row > (newrows - 10)))
+                    pm_message("read odd row - rowInXelrow=%d inputRow=%d",
+                               rowInXelrow, inputRow);
+#endif
+                pnm_readpnmrow(ifP, oddxelrow, cols, newmaxval, format);
+            }
+            ++rowInXelrow;
+        }
+
+        for (col = 0; col < newcols; ++col) {
+            if (inputRow == 0)
+                newxelrow[col] = evenxelrow[col];
+            else if (inputRow == rows - 1)
+                newxelrow[col] = oddxelrow[col];
+            else if (inputRow % 2 == 0) {
+                /* the even row is the low one, the odd the high one */
+                newxelrow[col].r = fFract * evenxelrow[col].r +
+                    (1.0 - fFract) * oddxelrow[col].r;
+                newxelrow[col].g = fFract * evenxelrow[col].g +
+                    (1.0 - fFract) * oddxelrow[col].g;
+                newxelrow[col].b = fFract * evenxelrow[col].b +
+                    (1.0 - fFract) * oddxelrow[col].b;
+            } else {
+                newxelrow[col].r = fFract * oddxelrow[col].r +
+                    (1.0 - fFract) * evenxelrow[col].r;
+                newxelrow[col].g = fFract * oddxelrow[col].g +
+                    (1.0 - fFract) * evenxelrow[col].g;
+                newxelrow[col].b = fFract * oddxelrow[col].b +
+                    (1.0 - fFract) * evenxelrow[col].b;
+            }
+        }
+
+        pnm_writepnmrow(stdout, newxelrow, newcols, newmaxval, newformat, 0 );
+    }
+
+    pnm_freerow(oddxelrow);
+    pnm_freerow(evenxelrow);
+    pnm_freerow(newxelrow);
+}
+
+
+
+static void
+transformNoneMixing(FILE * const ifP,
+                   int const cols, int const rows,
+                   xelval const maxval, int const format,
+                   int const newcols, int const newrows,
+                   xelval const newmaxval, int const newformat,
+                   bool const inverse,
+                   bool const verbose, bool const vverbose) 
+{
+    /*
+    Transform the map image on input file 'ifP' (which is described by 
+    'cols', 'rows', 'format', and 'maxval') to a Mercator projection and
+    write the result to standard output as format 'newformat' and with 
+    maxval 'newmaxval'.
+
+    Don't mix colors from different input pixels together in the output
+    pixels.  Each output pixel is an exact copy of some corresponding 
+    input pixel.
+    */
+
+    xel* xelrow;          /* an input row */
+    xel* newxelrow;         /* the output row */
+
+    int row;
+    double fRow;
+    int rowInXelrow;
+    int inputRow;
+    int col;
+
+    double fLatRad;
+    double fLatMerc;
+
+    xelrow = pnm_allocrow(cols); 
+    rowInXelrow = 0;
+
+    newxelrow = pnm_allocrow(newcols);
+
+    for (row = 0; row < newrows; ++row) {
+        
+        fRow = (double)row + 0.5; /* center on half the pixel */
+        if (!inverse) {
+            /* the result is mercator, calculate back to degrees */
+            fLatMerc = 2.0 * M_PI * (fRow / (double)newrows - 0.5);
+                /* merc goes from -pi to +pi */
+            fLatRad = 2.0 * (atan(exp(fLatMerc)) - M_PI / 4.0);
+            fRow = ((fLatRad / M_PI) + 0.5) * (double)rows;
+                /* lat goes from -pi/2 to +pi/2 */
+        } else {
+            /* the result is in degrees, calculate back to mercator */
+            fLatRad = M_PI * (fRow / (double)newrows - 0.5);
+                /* lat goes from -pi/2 to +pi/2 */
+            fLatMerc = log(tan((M_PI / 4.0 + fLatRad / 2.0)));
+            fRow = ((fLatMerc / M_PI / 2.0) + 0.5) * (double)rows;
+                /* merc goes from -pi to +pi */
+        }
+        /* fRow -= 0.5; */        /* it's weird that this isn't needed */
+
+        inputRow = (int) floor(fRow);
+        if (inputRow < 0)
+            inputRow = 0;
+        if (inputRow > rows - 1)
+            inputRow = rows - 1;
+
+        if (vverbose) {
+            if (!inverse) {
+#ifdef DBG_EDGES
+                if ((row < 10) || (row > (newrows - 10)))
+#else
+  #ifdef DBG_MIDDLE
+                if ((row > (newrows/2 - 10)) && (row < (newrows/2 + 10)))
+  #else
+                if (row % 20 == 0)
+  #endif
+#endif
+                    pm_message("outputRow=%d latMerc=%f latRad=%f inputRow=%d"
+                               "fRow=%f",
+                               row, fLatMerc, fLatRad, inputRow, fRow);
+            } else {
+#ifdef DBG_EDGES
+                if ((row < 10) || (row > (newrows - 10)))
+#else
+  #ifdef DBG_MIDDLE
+                if ((row > (newrows/2 - 10)) && (row < (newrows/2 + 10)))
+  #else
+                if (row % 10 == 0)
+  #endif
+#endif
+                    pm_message("outputRow=%d latRad=%f latMerc=%f inputRow=%d"
+                               "fRow=%f",
+                               row, fLatRad, fLatMerc, inputRow, fRow);
+            }
+        }
+
+        while ((rowInXelrow <= inputRow) && (rowInXelrow < rows)) {
+#ifdef DBG_EDGES
+            if ((row < 10) || (row > (newrows - 10)))
+                pm_message("read row - rowInXelrow=%d inputRow=%d",
+                           rowInXelrow, inputRow);
+#endif
+            pnm_readpnmrow(ifP, xelrow, cols, newmaxval, format);
+            ++rowInXelrow;
+        }
+        for (col = 0; col < newcols; ++col) {
+            newxelrow[col] = xelrow[col];
+        }
+
+        pnm_writepnmrow(stdout, newxelrow, newcols, newmaxval, newformat, 0 );
+    }
+
+    pnm_freerow(xelrow);
+    pnm_freerow(newxelrow);
+}
+
+
+
+int
+main(int argc, const char ** argv ) 
+{
+    struct cmdlineInfo cmdline;
+    FILE* ifP;
+    int rows, cols, format, newformat, newrows, newcols;
+    xelval maxval, newmaxval;
+    bool verbose;
+
+    pm_proginit( &argc, argv );
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    verbose = cmdline.verbose || cmdline.vverbose;
+
+    ifP = pm_openr(cmdline.input_filespec);
+
+    pnm_readpnminit( ifP, &cols, &rows, &maxval, &format );
+
+    /* Promote PBM file to PGM. */
+    if ( PNM_FORMAT_TYPE(format) == PBM_TYPE ) {
+        newformat = PGM_TYPE;
+        newmaxval = PGM_MAXMAXVAL;
+        pm_message( "promoting from PBM to PGM" );
+    } else {
+        newformat = format;
+        newmaxval = maxval;
+    }
+
+    computeOutputDimensions(cmdline, rows, cols, &newrows, &newcols);
+
+    pnm_writepnminit(stdout, newcols, newrows, newmaxval, newformat, 0);
+
+    if (cmdline.nomix) {
+        if (verbose)
+            pm_message("Transforming map without mixing/blending colors");
+        transformNoneMixing(ifP, cols, rows, maxval, format,
+                            newcols, newrows, newmaxval, newformat, 
+                            cmdline.inverse, verbose, cmdline.vverbose);
+    } else {
+        if (verbose)
+            pm_message("Transforming map while using intermediate colors");
+        transformWithMixing(ifP, cols, rows, maxval, format,
+                            newcols, newrows, newmaxval, newformat, 
+                            cmdline.inverse, verbose, cmdline.vverbose);
+    }
+
+    pm_close(ifP);
+    pm_close(stdout);
+    
+    return 0;
+}
diff --git a/editor/specialty/ppm3d.c b/editor/specialty/ppm3d.c
index d9ada365..a6faa341 100644
--- a/editor/specialty/ppm3d.c
+++ b/editor/specialty/ppm3d.c
@@ -42,7 +42,7 @@ parseCommandLine(int argc, char ** argv,
    was passed to us as the argv array.  We also trash *argv.
 -----------------------------------------------------------------------------*/
     optEntry * option_def;
-        /* Instructions to optParseOptions3 on how to parse our options.
+        /* Instructions to pm_optParseOptions3 on how to parse our options.
          */
     optStruct3 opt;
 
@@ -62,7 +62,7 @@ parseCommandLine(int argc, char ** argv,
     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);
+    pm_optParseOptions3( &argc, argv, opt, sizeof(opt), 0);
         /* Uses and sets argc, argv, and some of *cmdlineP and others. */
     
     if (argc-1 < 2)
diff --git a/editor/specialty/ppmglobe.c b/editor/specialty/ppmglobe.c
index 82fae5fb..92e22746 100644
--- a/editor/specialty/ppmglobe.c
+++ b/editor/specialty/ppmglobe.c
@@ -42,7 +42,7 @@ parseCommandLine(int argc, char ** argv,
    was passed to us as the argv array.
 -----------------------------------------------------------------------------*/
     optEntry *option_def;
-        /* Instructions to optParseOptions3 on how to parse our options.
+        /* Instructions to pm_optParseOptions3 on how to parse our options.
          */
     optStruct3 opt;
 
@@ -62,7 +62,7 @@ parseCommandLine(int argc, char ** argv,
     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);
+    pm_optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
         /* Uses and sets argc, argv, and some of *cmdlineP and others. */
 
     if (!backgroundSpec)
diff --git a/editor/specialty/ppmntsc.c b/editor/specialty/ppmntsc.c
index ae3bcfe9..a721b891 100644
--- a/editor/specialty/ppmntsc.c
+++ b/editor/specialty/ppmntsc.c
@@ -51,9 +51,6 @@
 #include "mallocvar.h"
 #include "shhopt.h"
 
-#define TRUE 1
-#define FALSE 0
-
 enum legalize {RAISE_SAT, LOWER_SAT, ALREADY_LEGAL};
    /* The actions that make a legal pixel */
 
@@ -70,6 +67,61 @@ struct cmdlineInfo {
 
 
 
+static void
+parseCommandLine(int argc, const char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that many of the strings that this function returns in the
+   *cmdlineP structure are actually in the supplied argv array.  And
+   sometimes, one of these strings is actually just a suffix of an entry
+   in argv!
+-----------------------------------------------------------------------------*/
+    optStruct3 opt;
+    optEntry *option_def;
+        /* Instructions to OptParseOptions on how to parse our options.
+         */
+    unsigned int option_def_index;
+    unsigned int legalonly, illegalonly, correctedonly;
+
+    MALLOCARRAY(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3('v', "verbose",        OPT_FLAG, NULL,  &cmdlineP->verbose,  0);
+    OPTENT3('V', "debug",          OPT_FLAG, NULL,  &cmdlineP->debug,    0);
+    OPTENT3('p', "pal",            OPT_FLAG, NULL,  &cmdlineP->pal,      0);
+    OPTENT3('l', "legalonly",      OPT_FLAG, NULL,  &legalonly,           0);
+    OPTENT3('i', "illegalonly",    OPT_FLAG, NULL,  &illegalonly,         0);
+    OPTENT3('c', "correctedonly",  OPT_FLAG, NULL,  &correctedonly,       0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = true;
+    opt.allowNegNum = false;
+
+    pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
+
+    if (argc - 1 == 0)
+        cmdlineP->inputFilename = "-";  /* he wants stdin */
+    else if (argc - 1 == 1)
+        cmdlineP->inputFilename = argv[1];
+    else 
+        pm_error("Too many arguments.  The only arguments accepted "
+                 "are the mask color and optional input file specification");
+
+    if (legalonly + illegalonly + correctedonly > 1)
+        pm_error("--legalonly, --illegalonly, and --correctedonly are "
+                 "conflicting options.  Specify at most one of these.");
+        
+    if (legalonly) 
+        cmdlineP->output = LEGAL_ONLY;
+    else if (illegalonly) 
+        cmdlineP->output = ILLEGAL_ONLY;
+    else if (correctedonly) 
+        cmdlineP->output = CORRECTED_ONLY;
+    else 
+        cmdlineP->output = ALL;
+}
+
+
 
 static void 
 rgbtoyiq(const int r, const int g, const int b, 
@@ -118,39 +170,39 @@ yuvtorgb(const double y, const double u, const double v,
 
 
 static void
-make_legal_yiq(const double y, const double i, const double q, 
-               double * const y_new_p, 
-               double * const i_new_p, 
-               double * const q_new_p,
-               enum legalize * const action_p
-    ) {
+makeLegalYiq(double          const y,
+             double          const i,
+             double          const q, 
+             double *        const yNewP, 
+             double *        const iNewP, 
+             double *        const qNewP,
+             enum legalize * const actionP) {
     
-    double sat_old, sat_new;
+    double satOld, satNew;
     /*
      * I and Q are legs of a right triangle.  Saturation is the hypotenuse.
      */
-    sat_old = sqrt(i*i + q*q);
-    if (y+sat_old > 1.0) {
-        const double diff = 0.5*((y+sat_old) - 1.0);
-        *y_new_p = y - diff;
-        sat_new = 1.0 - *y_new_p;
-        *i_new_p = i*(sat_new/sat_old);
-        *q_new_p = q*(sat_new/sat_old);
-        *action_p = LOWER_SAT;
-    } else if (y-sat_old <= -0.251) {
-        const double diff = 0.5*((sat_old-y) - 0.251);
-        *y_new_p = y + diff;
-        sat_new = 0.250 + *y_new_p;
-        *i_new_p = i*(sat_new/sat_old);
-        *q_new_p = q*(sat_new/sat_old);
-        *action_p = RAISE_SAT;
+    satOld = sqrt(SQR(i) + SQR(q));
+    if (y+satOld > 1.0) {
+        const double diff = 0.5*((y + satOld) - 1.0);
+        *yNewP = y - diff;
+        satNew = 1.0 - *yNewP;
+        *iNewP = i * (satNew/satOld);
+        *qNewP = q * (satNew/satOld);
+        *actionP = LOWER_SAT;
+    } else if (y - satOld <= -0.251) {
+        const double diff = 0.5*((satOld - y) - 0.251);
+        *yNewP = y + diff;
+        satNew = 0.250 + *yNewP;
+        *iNewP = i * (satNew/satOld);
+        *qNewP = q * (satNew/satOld);
+        *actionP = RAISE_SAT;
     } else {
-        *y_new_p = y;
-        *i_new_p = i;
-        *q_new_p = q;
-        *action_p = ALREADY_LEGAL;
+        *yNewP = y;
+        *iNewP = i;
+        *qNewP = q;
+        *actionP = ALREADY_LEGAL;
     }
-    return;
 }
 
 
@@ -206,7 +258,7 @@ make_legal_yiq_i(const int r_in, const int g_in, const int b_in,
      * Convert to YIQ and compute the new saturation.
      */
     rgbtoyiq(r_in, g_in, b_in, &y, &i, &q);
-    make_legal_yiq(y, i, q, &y_new, &i_new, &q_new, action_p);
+    makeLegalYiq(y, i, q, &y_new, &i_new, &q_new, action_p);
     if (*action_p != ALREADY_LEGAL)
         /*
          * Given the new I and Q, compute new RGB values.
@@ -295,204 +347,155 @@ make_legal_yuv_b(const pixel input,
 
 
 static void 
-report_mapping(const pixel old_pixel, const pixel new_pixel) {
+reportMapping(pixel const oldPixel,
+              pixel const newPixel) {
 /*----------------------------------------------------------------------------
-  Assuming old_pixel and new_pixel are input and output pixels,
+  Assuming oldPixel and newPixel are input and output pixels,
   tell the user that we changed a pixel to make it legal, if in fact we
   did and it isn't the same change that we just reported.
 -----------------------------------------------------------------------------*/
-    static pixel last_changed_pixel;
-    static int first_time = TRUE;
-
-    if (!PPM_EQUAL(old_pixel, new_pixel) && 
-        (first_time || PPM_EQUAL(old_pixel, last_changed_pixel))) {
-        pm_message("Mapping %d %d %d -> %d %d %d\n",
-                   PPM_GETR(old_pixel),
-                   PPM_GETG(old_pixel),
-                   PPM_GETB(old_pixel),
-                   PPM_GETR(new_pixel),
-                   PPM_GETG(new_pixel),
-                   PPM_GETB(new_pixel)
+    static pixel lastChangedPixel;
+    static bool firstTime = true;
+
+    if (!PPM_EQUAL(oldPixel, newPixel) && 
+        (firstTime || PPM_EQUAL(oldPixel, lastChangedPixel))) {
+        pm_message("Mapping %u %u %u -> %u %u %u\n",
+                   PPM_GETR(oldPixel),
+                   PPM_GETG(oldPixel),
+                   PPM_GETB(oldPixel),
+                   PPM_GETR(newPixel),
+                   PPM_GETG(newPixel),
+                   PPM_GETB(newPixel)
             );
 
-        last_changed_pixel = old_pixel;
-        first_time = FALSE;
+        lastChangedPixel = oldPixel;
+        firstTime = false;
     }    
 }
 
 
 
 static void
-convert_one_image(FILE * const ifp, struct cmdlineInfo const cmdline, 
-                  bool * const eofP, 
-                  int * const hicountP, int * const locountP) {
+convertOneImage(FILE *             const ifP,
+                struct cmdlineInfo const cmdline, 
+                unsigned int *     const hiCountP,
+                unsigned int *     const loCountP) {
 
     /* Parameters of input image: */
     int rows, cols;
     pixval maxval;
     int format;
 
-    ppm_readppminit(ifp, &cols, &rows, &maxval, &format);
-    ppm_writeppminit(stdout, cols, rows, maxval, FALSE);
+    ppm_readppminit(ifP, &cols, &rows, &maxval, &format);
+    ppm_writeppminit(stdout, cols, rows, maxval, 0);
     {
-        pixel* const input_row = ppm_allocrow(cols);
-        pixel* const output_row = ppm_allocrow(cols);
-        pixel last_illegal_pixel;
-        /* Value of the illegal pixel we most recently processed */
+        pixel * const inputRow = ppm_allocrow(cols);
+        pixel * const outputRow = ppm_allocrow(cols);
+
+        pixel lastIllegalPixel;
+            /* Value of the illegal pixel we most recently processed */
         pixel black;
-        /* A constant - black pixel */
+            /* A constant - black pixel */
 
         PPM_ASSIGN(black, 0, 0, 0);
 
-        PPM_ASSIGN(last_illegal_pixel, 0, 0, 0);  /* initial value */
+        PPM_ASSIGN(lastIllegalPixel, 0, 0, 0);  /* initial value */
         {
-            int row;
+            unsigned int row;
 
-            *hicountP = 0; *locountP = 0;  /* initial values */
+            *hiCountP = 0; *loCountP = 0;  /* initial values */
 
             for (row = 0; row < rows; ++row) {
-                int col;
-                ppm_readppmrow(ifp, input_row, cols, maxval, format);
+                unsigned int col;
+                ppm_readppmrow(ifP, inputRow, cols, maxval, format);
                 for (col = 0; col < cols; ++col) {
                     pixel corrected;
-                    /* Corrected or would-be corrected value for pixel */
+                        /* Corrected or would-be corrected value for pixel */
                     enum legalize action;
-                    /* What action was used to make pixel legal */
+                        /* What action was used to make pixel legal */
                     if (cmdline.pal)
-                        make_legal_yuv_b(input_row[col],
+                        make_legal_yuv_b(inputRow[col],
                                          &corrected,
                                          &action);
                     else
-                        make_legal_yiq_b(input_row[col],
+                        make_legal_yiq_b(inputRow[col],
                                          &corrected,
                                          &action);
                         
                     if (action == LOWER_SAT) 
-                        (*hicountP)++;
+                        ++*hiCountP;
                     if (action == RAISE_SAT)
-                        (*locountP)++;
-                    if (cmdline.debug) report_mapping(input_row[col],
-                                                      corrected);
+                        ++*loCountP;
+                    if (cmdline.debug)
+                        reportMapping(inputRow[col], corrected);
                     switch (cmdline.output) {
                     case ALL:
-                        output_row[col] = corrected;
+                        outputRow[col] = corrected;
                         break;
                     case LEGAL_ONLY:
-                        output_row[col] = (action == ALREADY_LEGAL) ?
-                            input_row[col] : black;
+                        outputRow[col] = (action == ALREADY_LEGAL) ?
+                            inputRow[col] : black;
                         break;
                     case ILLEGAL_ONLY:
-                        output_row[col] = (action != ALREADY_LEGAL) ?
-                            input_row[col] : black;
+                        outputRow[col] = (action != ALREADY_LEGAL) ?
+                            inputRow[col] : black;
                         break;
                     case CORRECTED_ONLY:
-                        output_row[col] = (action != ALREADY_LEGAL) ?
+                        outputRow[col] = (action != ALREADY_LEGAL) ?
                             corrected : black;
                         break;
                     }
                 }
-                ppm_writeppmrow(stdout, output_row, cols, maxval, FALSE);
+                ppm_writeppmrow(stdout, outputRow, cols, maxval, 0);
             }
         }
-        ppm_freerow(output_row);
-        ppm_freerow(input_row);
+        ppm_freerow(outputRow);
+        ppm_freerow(inputRow);
     }
 }
 
 
-static void
-parseCommandLine(int argc, char ** argv,
-                 struct cmdlineInfo * const cmdlineP) {
-/*----------------------------------------------------------------------------
-   Note that many of the strings that this function returns in the
-   *cmdlineP structure are actually in the supplied argv array.  And
-   sometimes, one of these strings is actually just a suffix of an entry
-   in argv!
------------------------------------------------------------------------------*/
-    optStruct3 opt;
-    optEntry *option_def;
-        /* Instructions to OptParseOptions on how to parse our options.
-         */
-    unsigned int option_def_index;
-    unsigned int legalonly, illegalonly, correctedonly;
-
-    MALLOCARRAY(option_def, 100);
-
-    option_def_index = 0;   /* incremented by OPTENTRY */
-    OPTENT3('v', "verbose",        OPT_FLAG, NULL,  &cmdlineP->verbose,  0);
-    OPTENT3('V', "debug",          OPT_FLAG, NULL,  &cmdlineP->debug,    0);
-    OPTENT3('p', "pal",            OPT_FLAG, NULL,  &cmdlineP->pal,      0);
-    OPTENT3('l', "legalonly",      OPT_FLAG, NULL,  &legalonly,           0);
-    OPTENT3('i', "illegalonly",    OPT_FLAG, NULL,  &illegalonly,         0);
-    OPTENT3('c', "correctedonly",  OPT_FLAG, NULL,  &correctedonly,       0);
-
-    opt.opt_table = option_def;
-    opt.short_allowed = TRUE;
-    opt.allowNegNum = FALSE;
-
-    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
-
-    if (argc - 1 == 0)
-        cmdlineP->inputFilename = "-";  /* he wants stdin */
-    else if (argc - 1 == 1)
-        cmdlineP->inputFilename = argv[1];
-    else 
-        pm_error("Too many arguments.  The only arguments accepted "
-                 "are the mask color and optional input file specification");
-
-    if (legalonly + illegalonly + correctedonly > 1)
-        pm_error("--legalonly, --illegalonly, and --correctedonly are "
-                 "conflicting options.  Specify at most one of these.");
-        
-    if (legalonly) 
-        cmdlineP->output = LEGAL_ONLY;
-    else if (illegalonly) 
-        cmdlineP->output = ILLEGAL_ONLY;
-    else if (correctedonly) 
-        cmdlineP->output = CORRECTED_ONLY;
-    else 
-        cmdlineP->output = ALL;
-}
-
-
 
 int
-main(int argc, char **argv) {
+main(int argc, const char **argv) {
     
     struct cmdlineInfo cmdline;
     FILE * ifP;
-    int total_hicount, total_locount;
-    int image_count;
+    unsigned int totalHiCount, totalLoCount;
+    unsigned int imageCount;
 
-    bool eof;
+    int eof;
 
-    ppm_init(&argc, argv);
+    pm_proginit(&argc, argv);
 
     parseCommandLine(argc, argv, &cmdline);
 
     ifP = pm_openr(cmdline.inputFilename);
+    
+    imageCount = 0;    /* initial value */
+    totalHiCount = 0;  /* initial value */
+    totalLoCount = 0;  /* initial value */
 
-    image_count = 0;    /* initial value */
-    total_hicount = 0;  /* initial value */
-    total_locount = 0;  /* initial value */
-
-    eof = FALSE;
+    eof = false;
     while (!eof) {
-        int hicount, locount;
-        convert_one_image(ifP, cmdline, &eof, &hicount, &locount);
-        image_count++;
-        total_hicount += hicount;
-        total_locount += locount;
+        unsigned int hiCount, loCount;
+
+        convertOneImage(ifP, cmdline, &hiCount, &loCount);
+
+        ++imageCount;
+        totalHiCount += hiCount;
+        totalLoCount += loCount;
+
         ppm_nextimage(ifP, &eof);
     }
 
 
 	if (cmdline.verbose) {
-        pm_message("%d images processed.", image_count);
-        pm_message("%d pixels were above the saturation limit.", 
-                   total_hicount);
-        pm_message("%d pixels were below the saturation limit.", 
-                   total_locount);
+        pm_message("%u images processed.", imageCount);
+        pm_message("%u pixels were above the saturation limit.", 
+                   totalHiCount);
+        pm_message("%u pixels were below the saturation limit.", 
+                   totalLoCount);
     }
     
     pm_close(ifP);
diff --git a/editor/specialty/ppmrelief.c b/editor/specialty/ppmrelief.c
index 1c408aec..14a6d0a8 100644
--- a/editor/specialty/ppmrelief.c
+++ b/editor/specialty/ppmrelief.c
@@ -1,4 +1,4 @@
-/* ppmrelief.c - generate a relief map of a portable pixmap
+/* ppmrelief.c - generate a relief map of a PPM image
 **
 ** Copyright (C) 1990 by Wilson H. Bent, Jr.
 **
@@ -11,86 +11,104 @@
 */
 
 #include <stdio.h>
+
 #include "pm_c_util.h"
 #include "ppm.h"
 
+
+
+static pixval
+clip(int    const p,
+     pixval const maxval) {
+
+    return MAX(0, MIN(maxval, p));
+}
+
+
+
 int
-main(int argc, char * argv[]) {
-
-    FILE* ifp;
-    pixel** inputbuf;
-    pixel* outputrow;
-    int argn, rows, cols, format, row;
-    register int col;
-    pixval maxval, mv2;
+main(int argc, const char * argv[]) {
+
+    FILE * ifP;
+    pixel ** inputbuf;
+    pixel * outputrow;
+    int argn, format, rows, cols;
+    unsigned int row, col;
+    pixval maxval;
     const char* const usage = "[ppmfile]";
 
-    ppm_init( &argc, argv );
+    pm_proginit(&argc, argv);
 
     argn = 1;
 
     if ( argn != argc ) {
-        ifp = pm_openr( argv[argn] );
+        ifP = pm_openr( argv[argn] );
         ++argn;
     } else
-        ifp = stdin;
+        ifP = stdin;
 
     if ( argn != argc )
         pm_usage( usage );
-    
-    ppm_readppminit( ifp, &cols, &rows, &maxval, &format );
+
+    ppm_readppminit(ifP, &cols, &rows, &maxval, &format );
 
     if (cols < 3 || rows < 3 )
         pm_error("Input image too small: %u x %u.  Must be at least 3x3",
                   cols, rows);
 
-    mv2 = maxval / 2;
-
     /* Allocate space for 3 input rows, plus an output row. */
-    inputbuf = ppm_allocarray( cols, 3 );
-    outputrow = ppm_allocrow( cols );
+    inputbuf  = ppm_allocarray(cols, 3);
+    outputrow = ppm_allocrow(cols);
 
-    ppm_writeppminit( stdout, cols, rows, maxval, 0 );
+    ppm_writeppminit(stdout, cols, rows, maxval, 0);
 
     /* Read in the first two rows. */
-    ppm_readppmrow( ifp, inputbuf[0], cols, maxval, format );
-    ppm_readppmrow( ifp, inputbuf[1], cols, maxval, format );
+    ppm_readppmrow(ifP, inputbuf[0], cols, maxval, format);
+    ppm_readppmrow(ifP, inputbuf[1], cols, maxval, format);
 
     /* Write out the first row, all zeros. */
-    for ( col = 0; col < cols; ++col )
+    for (col = 0; col < cols; ++col)
         PPM_ASSIGN( outputrow[col], 0, 0, 0 );
-    ppm_writeppmrow( stdout, outputrow, cols, maxval, 0 );
+
+    ppm_writeppmrow(stdout, outputrow, cols, maxval, 0);
 
     /* Now the rest of the image - read in the 3rd row of inputbuf,
-    ** and convolve with the first row into the output buffer.
+       and convolve with the first row into the output buffer.
     */
-    for ( row = 2 ; row < rows; ++row ) {
-        pixval r, g, b;
-        int rowa, rowb;
-
-        rowa = row % 3;
-        rowb = (row + 2) % 3;
-        ppm_readppmrow( ifp, inputbuf[rowa], cols, maxval, format );
-        
-        for ( col = 0; col < cols - 2; ++col ) {
-            r = MAX(0, MIN(maxval, PPM_GETR( inputbuf[rowa][col] ) +
-                           ( mv2 - PPM_GETR( inputbuf[rowb][col + 2] ) )));
-            g = MAX(0, MIN(maxval, PPM_GETG( inputbuf[rowa][col] ) +
-                           ( mv2 - PPM_GETG( inputbuf[rowb][col + 2] ) )));
-            b = MAX(0, MIN(maxval, PPM_GETB( inputbuf[rowa][col] ) +
-                           ( mv2 - PPM_GETB( inputbuf[rowb][col + 2] ) )));
-            PPM_ASSIGN( outputrow[col + 1], r, g, b );
+    for (row = 2 ; row < rows; ++row) {
+        pixval       const mv2 = maxval / 2;
+        unsigned int const rowa = row % 3;
+        unsigned int const rowb = (rowa + 2) % 3;
+
+        ppm_readppmrow(ifP, inputbuf[rowa], cols, maxval, format);
+
+        for (col = 0; col < cols - 2; ++col) {
+            pixel const inputA = inputbuf[rowa][col];
+            pixel const inputB = inputbuf[rowb][col + 2];
+            
+            pixval const r =
+                clip(PPM_GETR(inputA) + (mv2 - PPM_GETR(inputB)), maxval);
+            pixval const g =
+                clip(PPM_GETG(inputA) + (mv2 - PPM_GETG(inputB)), maxval);
+            pixval const b =
+                clip(PPM_GETB(inputA) + (mv2 - PPM_GETB(inputB)), maxval);
+
+            PPM_ASSIGN(outputrow[col + 1], r, g, b);
         }
-        ppm_writeppmrow( stdout, outputrow, cols, maxval, 0 );
+        ppm_writeppmrow(stdout, outputrow, cols, maxval, 0);
     }
 
     /* And write the last row, zeros again. */
-    for ( col = 0; col < cols; ++col )
-        PPM_ASSIGN( outputrow[col], 0, 0, 0 );
-    ppm_writeppmrow( stdout, outputrow, cols, maxval, 0 );
+    for (col = 0; col < cols; ++col)
+        PPM_ASSIGN(outputrow[col], 0, 0, 0);
+
+    ppm_writeppmrow(stdout, outputrow, cols, maxval, 0);
+
+    ppm_freerow(outputrow);
+    ppm_freearray(inputbuf, 3);
 
-    pm_close( ifp );
-    pm_close( stdout );
+    pm_close(ifP);
+    pm_close(stdout);
 
-    exit( 0 );
+    return 0;
 }