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/Makefile55
-rw-r--r--editor/specialty/pamdeinterlace.c133
-rw-r--r--editor/specialty/pammixinterlace.c345
-rw-r--r--editor/specialty/pamoil.c137
-rw-r--r--editor/specialty/pampop9.c108
-rw-r--r--editor/specialty/pbmlife.c114
-rw-r--r--editor/specialty/pgmabel.c316
-rw-r--r--editor/specialty/pgmbentley.c74
-rw-r--r--editor/specialty/pgmmorphconv.c253
-rw-r--r--editor/specialty/pnmindex.c640
-rw-r--r--editor/specialty/ppm3d.c308
-rw-r--r--editor/specialty/ppmglobe.c173
-rw-r--r--editor/specialty/ppmntsc.c501
-rw-r--r--editor/specialty/ppmrelief.c90
-rw-r--r--editor/specialty/ppmshift.c132
-rw-r--r--editor/specialty/ppmspread.c117
-rw-r--r--editor/specialty/ppmtv.c105
17 files changed, 3601 insertions, 0 deletions
diff --git a/editor/specialty/Makefile b/editor/specialty/Makefile
new file mode 100644
index 00000000..eda54882
--- /dev/null
+++ b/editor/specialty/Makefile
@@ -0,0 +1,55 @@
+ifeq ($(SRCDIR)x,x)
+  SRCDIR = $(CURDIR)/../..
+  BUILDDIR = $(SRCDIR)
+endif
+SUBDIR = editor/specialty
+VPATH=.:$(SRCDIR)/$(SUBDIR)
+
+include $(BUILDDIR)/config.mk
+
+PORTBINARIES = pamdeinterlace \
+	       pammixinterlace \
+	       pamoil \
+	       pampop9 \
+	       pbmlife \
+	       pgmabel \
+	       pgmbentley \
+	       pgmmorphconv \
+	       pnmindex \
+	       ppm3d \
+	       ppmglobe \
+	       ppmntsc \
+	       ppmrelief \
+	       ppmshift \
+	       ppmspread \
+	       ppmtv \
+
+# We don't include programs that have special library dependencies in the
+# merge scheme, because we don't want those dependencies to prevent us
+# from building all the other programs.
+
+NOMERGEBINARIES = 
+MERGEBINARIES = $(PORTBINARIES)
+
+
+BINARIES = $(MERGEBINARIES) $(NOMERGEBINARIES)
+SCRIPTS =
+
+OBJECTS = $(BINARIES:%=%.o)
+
+MERGE_OBJECTS = $(MERGEBINARIES:%=%.o2)
+
+.PHONY: all
+all: $(BINARIES)
+
+include $(SRCDIR)/common.mk
+
+install.bin: install.bin.local
+
+.PHONY: install.bin.local
+install.bin.local: $(PKGDIR)/bin
+# Remember that $(SYMLINK) might just be a copy command.
+# pamoil replaced pgmoil in June 2001.
+	cd $(PKGDIR)/bin ; \
+	rm -f pgmoil ; \
+	$(SYMLINK) pamoil$(EXE) pgmoil
diff --git a/editor/specialty/pamdeinterlace.c b/editor/specialty/pamdeinterlace.c
new file mode 100644
index 00000000..f158fbac
--- /dev/null
+++ b/editor/specialty/pamdeinterlace.c
@@ -0,0 +1,133 @@
+/******************************************************************************
+                             pamdeinterlace
+*******************************************************************************
+  De-interlace an image, i.e. select every 2nd row.
+   
+  By Bryan Henderson, San Jose, CA 2001.11.11.
+
+  Contributed to the public domain.
+******************************************************************************/
+
+#include "pm_c_util.h"
+#include "pam.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+
+enum evenodd {EVEN, ODD};
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *inputFilespec;  /* Filespecs of input files */
+    enum evenodd rowsToTake;
+};
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo *cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optStruct3 opt;  /* set by OPTENT3 */
+    optEntry *option_def;
+    unsigned int option_def_index;
+
+    unsigned int takeeven, takeodd;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0,   "takeeven", OPT_FLAG, NULL, &takeeven, 0);
+    OPTENT3(0,   "takeodd",  OPT_FLAG, NULL, &takeodd,  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 (takeeven && takeodd)
+        pm_error("You cannot specify both -takeeven and -takeodd options.");
+
+    if (takeodd)
+        cmdlineP->rowsToTake = ODD;
+    else
+        cmdlineP->rowsToTake = EVEN;
+
+    if (argc-1 < 1)
+        cmdlineP->inputFilespec = "-";
+    else if (argc-1 == 1)
+        cmdlineP->inputFilespec = argv[1];
+    else
+        pm_error("You specified too many arguments (%d).  The only "
+                 "argument is the optional input file specification.",
+                 argc-1);
+}
+
+
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    FILE * ifP;
+    tuple * tuplerow;   /* Row from input image */
+    unsigned int row;
+    struct cmdlineInfo cmdline;
+    struct pam inpam;  
+    struct pam outpam;
+
+    pnm_init( &argc, argv );
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilespec);
+    
+    pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
+
+    tuplerow = pnm_allocpamrow(&inpam);
+
+    outpam = inpam;    /* Initial value -- most fields should be same */
+    outpam.file = stdout;
+    if (inpam.height % 2 == 0)
+        outpam.height = inpam.height / 2;
+    else {
+        if (cmdline.rowsToTake == ODD)
+            outpam.height = inpam.height / 2;
+        else
+            outpam.height = inpam.height / 2 + 1;
+    }
+
+    pnm_writepaminit(&outpam);
+
+    {
+        unsigned int modulusToTake;
+            /* The row number mod 2 of the rows that are supposed to go into
+               the output.
+            */
+
+        switch (cmdline.rowsToTake) {
+        case EVEN: modulusToTake = 0; break;
+        case ODD:  modulusToTake = 1; break;
+        default: pm_error("INTERNAL ERROR: invalid rowsToTake");
+        }
+
+        /* Read input and write out rows extracted from it */
+        for (row = 0; row < inpam.height; row++) {
+            pnm_readpamrow(&inpam, tuplerow);
+            if (row % 2 == modulusToTake)
+                pnm_writepamrow(&outpam, tuplerow);
+        }
+    }
+    pnm_freepamrow(tuplerow);
+    pm_close(inpam.file);
+    pm_close(outpam.file);
+    
+    return 0;
+}
+
diff --git a/editor/specialty/pammixinterlace.c b/editor/specialty/pammixinterlace.c
new file mode 100644
index 00000000..9f98b406
--- /dev/null
+++ b/editor/specialty/pammixinterlace.c
@@ -0,0 +1,345 @@
+/******************************************************************************
+                             pammixinterlace
+*******************************************************************************
+  De-interlace an image by merging adjacent rows.
+   
+  Copyright (C) 2007 Bruce Guenter, FutureQuest, Inc.
+
+  Permission to use, copy, modify, and distribute this software and its
+  documentation for any purpose and without fee is hereby granted,
+  provided that the above copyright notice appear in all copies and that
+  both that copyright notice and this permission notice appear in
+  supporting documentation.  This software is provided "as is" without
+  express or implied warranty.
+
+******************************************************************************/
+
+#define _BSD_SOURCE    /* Make sure strcaseeq() is in nstring.h */
+
+#include <string.h>
+
+#include "pm_c_util.h"
+#include "mallocvar.h"
+#include "nstring.h"
+#include "shhopt.h"
+#include "pam.h"
+
+
+
+static sample
+clamp(sample const val,
+      sample const maxval) {
+
+    return val < 0 ? 0 : val > maxval ? maxval : val;
+}
+
+
+
+static bool
+distant(long const above,
+        long const mid,
+        long const below) {
+
+    return abs(mid - (above + below) / 2) > abs(above - below);
+}
+
+
+
+static void
+filterLinearBlend(tuple *      const outputrow,
+                  tuple **     const tuplerowWindow,
+                  unsigned int const width,
+                  unsigned int const depth,
+                  bool         const adaptive,
+                  sample       const maxval) {
+
+    unsigned int col;
+
+    for (col = 0; col < width; ++col) {
+        unsigned int plane;
+
+        for (plane = 0; plane < depth; ++plane) {
+            long const above = tuplerowWindow[0][col][plane];
+            long const mid   = tuplerowWindow[1][col][plane];
+            long const below = tuplerowWindow[2][col][plane];
+
+            sample out;
+
+            if (!adaptive || distant(above, mid, below))
+                out = (above + mid * 2 + below) / 4;
+            else
+                out = mid;
+            
+            outputrow[col][plane] = out;
+        }
+    }
+}
+
+
+
+static void
+filterFfmpeg(tuple *      const outputrow,
+             tuple **     const tuplerowWindow,
+             unsigned int const width,
+             unsigned int const depth,
+             bool         const adaptive,
+             sample       const maxval) {
+
+    unsigned int col;
+    
+    for (col = 0; col < width; ++col) {
+        unsigned int plane;
+        
+        for (plane = 0; plane < depth; ++plane) {
+            long const above = tuplerowWindow[1][col][plane];
+            long const mid   = tuplerowWindow[2][col][plane];
+            long const below = tuplerowWindow[3][col][plane];
+
+            sample out;
+            
+            if (!adaptive || distant(above, mid, below)) {
+                long const a = (- (long)tuplerowWindow[0][col][plane]
+                                + above * 4
+                                + mid * 2
+                                + below * 4
+                                - (long)tuplerowWindow[4][col][plane]) / 8;
+                out = clamp(a, maxval);
+            } else
+                out = mid;
+
+            outputrow[col][plane] = out;
+        }
+    }
+}
+
+
+
+static void
+filterFIR(tuple *      const outputrow,
+          tuple **     const tuplerowWindow,
+          unsigned int const width,
+          unsigned int const depth,
+          bool         const adaptive,
+          sample       const maxval) {
+
+    unsigned int col;
+
+    for (col = 0; col < width; ++col) {
+        unsigned int plane;
+
+        for (plane = 0; plane < depth; ++plane) {
+
+            long const above = tuplerowWindow[1][col][plane];
+            long const mid   = tuplerowWindow[2][col][plane];
+            long const below = tuplerowWindow[3][col][plane];
+
+            sample out;
+
+            if (!adaptive || distant(above, mid, below)) {
+                long const a = (- (long)tuplerowWindow[0][col][plane]
+                                + above * 2
+                                + mid * 6
+                                + below * 2
+                                - (long)tuplerowWindow[4][col][plane]) / 8;
+                out = clamp(a, maxval);
+            } else
+                out = mid;
+            
+            outputrow[col][plane] = out;
+        }
+    }
+}
+
+
+
+struct filter {
+    const char * name;          /* The command-line name of the filter */
+    unsigned int rows;   /* The number of rows the filter operates on */
+    void (*filter)(tuple *, tuple **,
+                   unsigned int width, unsigned int depth,
+                   bool adaptive, sample maxval);
+};
+
+static struct filter filters[] = {
+    { "fir", 5, filterFIR }, /* FIR is cleanest and default filter */
+    { "ffmpeg", 5, filterFfmpeg },
+    { "linear", 3, filterLinearBlend },
+};
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char * inputFileName;  /* Names of input files */
+    struct filter * filterP;
+    unsigned int adaptive;
+};
+
+
+
+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.
+-----------------------------------------------------------------------------*/
+    optStruct3 opt;  /* set by OPTENT3 */
+    optEntry * option_def;
+    unsigned int option_def_index;
+    const char * filterName;
+    unsigned int filterSpec;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "filter",   OPT_STRING, &filterName, &filterSpec, 0);
+    OPTENT3(0, "adaptive", OPT_FLAG, NULL, &cmdlineP->adaptive, 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 (!filterSpec)
+        cmdlineP->filterP = &filters[0];
+    else {
+        unsigned int i;
+        cmdlineP->filterP = NULL;
+        for (i = 0; i < sizeof filters / sizeof(struct filter); ++i) {
+            if (strcaseeq(filterName, filters[i].name)) {
+                cmdlineP->filterP = &filters[i];
+                break;
+            }
+        }
+        if (!cmdlineP->filterP)
+            pm_error("The filter name '%s' is not known.", filterName);
+    }
+    
+    if (argc-1 < 1)
+        cmdlineP->inputFileName = "-";
+    else if (argc-1 == 1)
+        cmdlineP->inputFileName = argv[1];
+    else
+        pm_error("You specified too many arguments (%d).  The only "
+                 "argument is the optional input file specification.",
+                 argc-1);
+}
+
+
+
+static void
+allocateRowWindowBuffer(struct pam * const pamP,
+                        tuple **     const tuplerowWindow,
+                        unsigned int const rows) {
+
+    unsigned int row;
+
+    for (row = 0; row < rows; ++row)
+        tuplerowWindow[row] = pnm_allocpamrow(pamP);
+}
+
+
+
+static void
+freeRowWindowBuffer(tuple **     const tuplerowWindow,
+                    unsigned int const rows) {
+
+    unsigned int row;
+
+    for (row = 0; row < rows; ++row)
+        pnm_freepamrow(tuplerowWindow[row]);
+}
+
+
+
+static void
+slideWindowDown(tuple **     const tuplerowWindow,
+                unsigned int const rows) {
+/*----------------------------------------------------------------------------
+  Slide the rows-line tuple row window tuplerowWindow[] down one by moving
+  pointers.
+-----------------------------------------------------------------------------*/
+    tuple * const oldrow0 = tuplerowWindow[0];
+
+    unsigned int i;
+
+    for (i = 0; i < rows-1; ++i)
+        tuplerowWindow[i] = tuplerowWindow[i+1];
+
+    tuplerowWindow[i] = oldrow0;
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    FILE * ifP;
+    struct cmdlineInfo cmdline;
+    struct pam inpam;  
+    struct pam outpam;
+    tuple * tuplerowWindow[5];
+    tuple * outputrow;
+    unsigned int rows;
+    
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    rows = cmdline.filterP->rows;
+
+    ifP = pm_openr(cmdline.inputFileName);
+    
+    pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
+
+    outpam = inpam;    /* Initial value -- most fields should be same */
+    outpam.file = stdout;
+
+    pnm_writepaminit(&outpam);
+
+    allocateRowWindowBuffer(&inpam, tuplerowWindow, rows);
+    outputrow = pnm_allocpamrow(&outpam);
+
+    if (inpam.height < rows) {
+        unsigned int row;
+        pm_message("WARNING: Image height less than %d.  No mixing done.",
+                   rows);
+        for (row = 0; row < inpam.height; ++row) {
+            pnm_readpamrow(&inpam, tuplerowWindow[0]);
+            pnm_writepamrow(&outpam, tuplerowWindow[0]);
+        }
+    } else {
+        unsigned int row;
+
+    for (row = 0; row < rows-1; ++row)
+        pnm_readpamrow(&inpam, tuplerowWindow[row]);
+
+    /* Pass through first unfilterable rows */
+    for (row = 0; row < rows/2; ++row)
+        pnm_writepamrow(&outpam, tuplerowWindow[row]);
+
+    for (row = rows / 2 + 1; row < inpam.height - rows / 2 + 1; ++row) {
+        pnm_readpamrow(&inpam, tuplerowWindow[rows-1]);
+        cmdline.filterP->filter(outputrow, tuplerowWindow,
+                                inpam.width, inpam.depth,
+                                cmdline.adaptive, inpam.maxval);
+        pnm_writepamrow(&outpam, outputrow);
+        
+        slideWindowDown(tuplerowWindow, rows);
+    }
+    
+    /* Pass through last rows */
+    for (row = rows/2; row < rows-1; ++row)
+        pnm_writepamrow(&outpam, tuplerowWindow[row]);
+    }
+
+    freeRowWindowBuffer(tuplerowWindow, rows);
+    pnm_freepamrow(outputrow);
+    pm_close(inpam.file);
+    pm_close(outpam.file);
+    
+    return 0;
+}
diff --git a/editor/specialty/pamoil.c b/editor/specialty/pamoil.c
new file mode 100644
index 00000000..6cb8d3ac
--- /dev/null
+++ b/editor/specialty/pamoil.c
@@ -0,0 +1,137 @@
+/* pgmoil.c - read a portable pixmap and turn into an oil painting
+**
+** Copyright (C) 1990 by Wilson Bent (whb@hoh-2.att.com)
+** Shamelessly butchered into a color version by Chris Sheppard
+** 2001
+**
+** 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 <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include "pam.h"
+#include "mallocvar.h"
+
+static void 
+convertRow(struct pam const inpam, tuple ** const tuples,
+           tuple * const tuplerow, int const row, int const smearFactor,
+           int * const hist) {
+
+    int sample;
+    for (sample = 0; sample < inpam.depth; sample++) {
+        int col;
+        for (col = 0; col < inpam.width; ++col)  {
+            int i;
+            int drow;
+            int modalval;
+                /* The sample value that occurs most often in the neighborhood
+                   of the pixel being examined
+                */
+
+            /* Compute hist[] - frequencies, in the neighborhood, of each 
+               sample value
+            */
+            for (i = 0; i <= inpam.maxval; ++i) hist[i] = 0;
+
+            for (drow = row - smearFactor; drow <= row + smearFactor; ++drow) {
+                if (drow >= 0 && drow < inpam.height) {
+                    int dcol;
+                    for (dcol = col - smearFactor; 
+                         dcol <= col + smearFactor; 
+                         ++dcol) {
+                        if ( dcol >= 0 && dcol < inpam.width )
+                            ++hist[tuples[drow][dcol][sample]];
+                    }
+                }
+            }
+            {
+                /* Compute modalval */
+                int sampleval;
+                int maxfreq;
+
+                maxfreq = 0;
+                modalval = 0;
+
+                for (sampleval = 0; sampleval <= inpam.maxval; ++sampleval) {
+                    if (hist[sampleval] > maxfreq) {
+                        maxfreq = hist[sampleval];
+                        modalval = sampleval;
+                    }
+                }
+            }
+            tuplerow[col][sample] = modalval;
+        }
+    }
+}
+
+
+
+int
+main(int argc, char *argv[] ) {
+    struct pam inpam, outpam;
+    FILE* ifp;
+    tuple ** tuples;
+    tuple * tuplerow;
+    int * hist;
+        /* A buffer for the convertRow subroutine to use */
+    int argn;
+    int row;
+    int smearFactor;
+    const char* const usage = "[-n <n>] [ppmfile]";
+
+    ppm_init( &argc, argv );
+
+    argn = 1;
+    smearFactor = 3;       /* DEFAULT VALUE */
+
+    /* Check for options. */
+    if ( argn < argc && argv[argn][0] == '-' ) {
+        if ( argv[argn][1] == 'n' ) {
+            ++argn;
+            if ( argn == argc || sscanf(argv[argn], "%d", &smearFactor) != 1 )
+                pm_usage( usage );
+        } else
+            pm_usage( usage );
+        ++argn;
+    }
+    if ( argn < argc ) {
+        ifp = pm_openr( argv[argn] );
+        ++argn;
+    } else
+        ifp = stdin;
+
+    if ( argn != argc )
+        pm_usage( usage );
+
+    tuples = pnm_readpam(ifp, &inpam, PAM_STRUCT_SIZE(tuple_type));
+    pm_close(ifp);
+
+    MALLOCARRAY(hist, inpam.maxval + 1);
+    if (hist == NULL)
+        pm_error("Unable to allocate memory for histogram.");
+
+    outpam = inpam; outpam.file = stdout;
+
+    pnm_writepaminit(&outpam);
+
+    tuplerow = pnm_allocpamrow(&inpam);
+
+    for (row = 0; row < inpam.height; ++row) {
+        convertRow(inpam, tuples, tuplerow, row, smearFactor, hist);
+        pnm_writepamrow(&outpam, tuplerow);
+    }
+
+    pnm_freepamrow(tuplerow);
+    free(hist);
+    pnm_freepamarray(tuples, &inpam);
+
+    pm_close(stdout);
+    exit(0);
+}
+
diff --git a/editor/specialty/pampop9.c b/editor/specialty/pampop9.c
new file mode 100644
index 00000000..d6c61e4f
--- /dev/null
+++ b/editor/specialty/pampop9.c
@@ -0,0 +1,108 @@
+/* 
+ *
+ * (c) Robert Tinsley, 2003 (http://www.thepoacher.net/contact)
+ *
+ * Released under the GPL (http://www.fsf.org/licenses/gpl.txt)
+ *
+ *
+ * CHANGES
+ *
+ * v1.00 2003-02-28 Original version
+ *
+ * v1.10 2003-03-02
+ *  + changed to use pam_* routines rather than ppm_*
+ *  + changed to use pm_* rather than fopen()/fclose()
+ *  + renamed from ppmgrid to pampup9
+ *  + wrote a man-page (actually, html)
+ *
+ * Changes by Bryan Henderson for inclusion in the Netpbm package (fully
+ * exploiting Netpbm library).  Renamed to Pampop9.  March 2003.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h> /* atoi() */
+
+#include "pam.h"
+
+static const char * const copyright = 
+  "(c) Robert Tinsley 2003 (http://www.thepoacher.net/contact)";
+
+static const char *usagestr = "pnmfile|- xtiles ytiles xdelta ydelta";
+
+int main(int argc, char *argv[])
+{
+    const char *filename = "-";
+    int xtiles, ytiles, xdelta, ydelta;
+    FILE *fp;
+
+    struct pam spam, dpam;
+    tuple **spix, *dpix;
+    int xtilesize, ytilesize, sx, sy, dx, dy, p;
+
+
+
+    pnm_init(&argc, argv);
+
+    if (argc-1 != 5) {
+        pm_error("Wrong number of arguments.  Program requires 5 arguments; "
+                 "you supplied %d.  Usage: %s", argc-1, usagestr);
+    }
+
+    filename = argv[1];
+    xtiles = atoi(argv[2]);
+    ytiles = atoi(argv[3]);
+    xdelta = atoi(argv[4]);
+    ydelta = atoi(argv[5]);
+
+    if (filename == NULL || *filename == '\0'
+        || xtiles <= 0 || ytiles <= 0 || xdelta < 0 || ydelta < 0) 
+        pm_error("invalid argument");
+
+    /* read src pam */
+
+    fp = pm_openr(filename);
+
+    spix = pnm_readpam(fp, &spam, PAM_STRUCT_SIZE(tuple_type));
+
+    pm_close(fp);
+
+    /* init dst pam */
+
+    xtilesize = spam.width  - (xtiles - 1) * xdelta;
+    ytilesize = spam.height - (ytiles - 1) * ydelta;
+
+    if (xtilesize <= 0)
+        pm_error("xtilesize must be positive.  You specified %d", xtilesize);
+    if (ytilesize <= 0)
+        pm_error("ytilesize must be positive.  You specified %d", ytilesize);
+
+    dpam = spam;
+    dpam.file = stdout;
+    dpam.width  = xtiles * xtilesize;
+    dpam.height = ytiles * ytilesize;
+
+    dpix = pnm_allocpamrow(&dpam);
+
+    pnm_writepaminit(&dpam);
+
+    /* generate dst pam */
+
+    for (dy = 0; dy < dpam.height; dy++) {
+        sy = ((int) (dy / ytilesize)) * ydelta + (dy % ytilesize);
+        for (dx = 0; dx < dpam.width; dx++) {
+            sx = ((int) (dx / xtilesize)) * xdelta + (dx % xtilesize);
+                for (p = 0; p < spam.depth; ++p) {
+                    dpix[dx][p] = spix[sy][sx][p];
+            }
+        }
+        pnm_writepamrow(&dpam, dpix);
+    }
+
+    /* all done */
+
+    pnm_freepamarray(spix, &spam);
+    pnm_freepamrow(dpix);
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/editor/specialty/pbmlife.c b/editor/specialty/pbmlife.c
new file mode 100644
index 00000000..be34cc69
--- /dev/null
+++ b/editor/specialty/pbmlife.c
@@ -0,0 +1,114 @@
+/* pbmlife.c - read a portable bitmap and apply Conway's rules of Life to it
+**
+** Copyright (C) 1988,1 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "pbm.h"
+
+int
+main( argc, argv )
+int argc;
+char* argv[];
+    {
+    FILE* ifp;
+    bit* prevrow;
+    bit* thisrow;
+    bit* nextrow;
+    bit* temprow;
+    register bit* newrow;
+    int rows, cols, row;
+    register int col, count;
+    int format;
+
+
+    pbm_init( &argc, argv );
+
+    if ( argc > 2 )
+	pm_usage( "[pbmfile]" );
+
+    if ( argc == 2 )
+	ifp = pm_openr( argv[1] );
+    else
+	ifp = stdin;
+
+    pbm_readpbminit( ifp, &cols, &rows, &format );
+    prevrow = pbm_allocrow( cols );
+    thisrow = pbm_allocrow( cols );
+    nextrow = pbm_allocrow( cols );
+
+    pbm_writepbminit( stdout, cols, rows, 0 );
+    newrow = pbm_allocrow( cols );
+
+    pbm_readpbmrow( ifp, nextrow, cols, format );
+
+    for ( row = 0; row < rows; ++row )
+	{
+	temprow = prevrow;
+	prevrow = thisrow;
+	thisrow = nextrow;
+	nextrow = temprow;
+	if ( row < rows - 1 )
+	    pbm_readpbmrow( ifp, nextrow, cols, format );
+
+        for ( col = 0; col < cols; ++col )
+	    {
+	    /* Check the neighborhood, with an unrolled double loop. */
+	    count = 0;
+	    if ( row > 0 )
+		{
+		/* upper left */
+		if ( col > 0 && prevrow[col - 1] == PBM_WHITE )
+		    ++count;
+		/* upper center */
+		if ( prevrow[col] == PBM_WHITE )
+		    ++count;
+		/* upper right */
+		if ( col < cols - 1 && prevrow[col + 1] == PBM_WHITE )
+		    ++count;
+		}
+	    /* left */
+	    if ( col > 0 && thisrow[col - 1] == PBM_WHITE )
+		++count;
+	    /* right */
+	    if ( col < cols - 1 && thisrow[col + 1] == PBM_WHITE )
+		++count;
+	    if ( row < rows - 1 )
+		{
+		/* lower left */
+		if ( col > 0 && nextrow[col - 1] == PBM_WHITE )
+		    ++count;
+		/* lower center */
+		if ( nextrow[col] == PBM_WHITE )
+		    ++count;
+		/* lower right */
+		if ( col < cols - 1 && nextrow[col + 1] == PBM_WHITE )
+		    ++count;
+		}
+
+	    /* And compute the new value. */
+	    if ( thisrow[col] == PBM_WHITE )
+		if ( count == 2 || count == 3 )
+		    newrow[col] = PBM_WHITE;
+		else
+		    newrow[col] = PBM_BLACK;
+	    else
+		if ( count == 3 )
+		    newrow[col] = PBM_WHITE;
+		else
+		    newrow[col] = PBM_BLACK;
+	    }
+	pbm_writepbmrow( stdout, newrow, cols, 0 );
+	}
+
+    pm_close( ifp );
+    pm_close( stdout );
+
+    exit( 0 );
+    }
diff --git a/editor/specialty/pgmabel.c b/editor/specialty/pgmabel.c
new file mode 100644
index 00000000..4914c4be
--- /dev/null
+++ b/editor/specialty/pgmabel.c
@@ -0,0 +1,316 @@
+/* pgmabel.c - read a portable graymap and making the deconvolution
+**
+**      Deconvolution of an axial-symmetric image of an rotation symmetrical
+**      process by solving the linear equation system with y-Axis as
+**      symmetry-line
+**
+** Copyright (C) 1997-2006 by German Aerospace Research establishment
+**
+** Author: Volker Schmidt
+**         lefti@voyager.boerde.de
+**
+** 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.
+**
+** $HISTORY:
+**
+** 24 Jan 2002 : 001.009 :  some optimzization
+** 22 Jan 2002 : 001.008 :  some stupid calculations changed
+** 08 Aug 2001 : 001.007 :  new usage (netpbm-conform)
+** 27 Jul 1998 : 001.006 :  First try of error correction
+** 26 Mar 1998 : 001.005 :  Calculating the dl's before transformation
+** 06 Feb 1998 : 001.004 :  Include of a logo in the upper left edge
+** 26 Nov 1997 : 001.003 :  Some bug fixes and reading only lines
+** 25 Nov 1997 : 001.002 :  include of pixsize for getting scale invariant
+** 03 Sep 1997 : 001.001 :  only define for PID2
+** 03 Sep 1997 : 001.000 :  First public release
+** 21 Aug 1997 : 000.909 :  Recalculate the streching-factor
+** 20 Aug 1997 : 000.908 :  -left and -right for calculating only one side
+** 20 Aug 1997 : 000.906 :  correction of divisor, include of -factor
+** 15 Aug 1997 : 000.905 :  Include of -help and -axis
+*/
+
+static const char* const version="$VER: pgmabel 1.009 (24 Jan 2002)";
+
+#include <math.h>
+#include <stdlib.h>   /* for calloc */
+#include "pgm.h"
+#include "mallocvar.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 */
+
+/* ----------------------------------------------------------------------------
+** procedure for calculating the sum of the calculated surfaces with the
+** weight of the surface
+**      n     <-  index of end point of the summation
+**      N     <-  width of the calculated row
+**      xr    <-  array of the calculated elements of the row
+**      adl   <-  pre-calculated surface coefficient for each segment
+*/
+static double 
+Sum ( int n, double *xr, int N, double *adl)
+{
+    int k;
+    double result=0.0;
+
+    if (n==0) return(0.0);             /* outer ring  is 0 per definition    */
+    for (k=0 ; k<=(n-1) ; k++)
+    {
+         result += xr[k] * ( adl[k*N+n] - adl[(k+1)*N+n]);
+/*       result += xr[k] * ( dr(k,n+0.5,N) - dr(k+1,n+0.5,N));   */
+    }
+    return(result);
+}
+
+/* ----------------------------------------------------------------------------
+** procedure for calculating the surface coefficient for the Integration
+**      R, N  <-  indizes of the coefficient
+**      r     <-  radial position of the center of the surface
+*/
+static double 
+dr ( int R, double r,  int N)
+{
+    double a;
+    double b;
+    a=(double) N-R ;
+    b=(double) N-r ;
+    return(sqrt(a*a-b*b));
+}
+
+/* ----------------------------------------------------------------------------
+** procedure for making the Abel integration for deconvolution of the image
+**        y    <-> array with values for deconvolution and results
+**        N    <-  width of the array
+**        adl  <-  array with pre-calculated weighting factors
+*/
+static void 
+abel ( float *y, int N, double *adl)
+{
+    register int n;
+    double *rho, *rhop;       /* results and new index                       */
+    float  *yp;               /* new indizes for the y-array                 */
+
+    MALLOCARRAY(rho, N);
+    if( !rho )
+        pm_error( "out of memory" );
+    rhop = rho;
+    yp  = y;
+
+    for (n=0 ; n<N ; n++)
+    {
+        *(rhop++) = ((*yp++) - Sum(n,rho,N,adl))/(adl[n*N+n]);
+/*    *(rhop++) = ((*yp++) - Sum(n,rho,N))/(dr(n,n+0.5,N));  old version */
+        if ( *rhop < 0.0 ) *rhop = 0.0;         /*  error correction !       */
+/*   if (n > 2) rhop[n-1] = (rho[n-2]+rho[n-1]+rho[n])/3.0;  stabilization*/
+    }
+    for (n=0 ; n<N ; n++)
+        {
+            if (( n>=1 )&&( n<N-1 ))
+	       (*y++) = ((rho[n-1]*0.5+rho[n]+rho[n+1]*0.5)/2.0);/*1D median filter*/
+            else (*y++) = rho[n];
+        }
+    free(rho);
+}
+
+/* ----------------------------------------------------------------------------
+** printing a help message if Option -h(elp) is chosen
+*/
+static void 
+help()
+{
+    pm_message("-----------------------------------------------------------------");
+    pm_message("| pgmabel                                                       |");
+    pm_message("| make a deconvolution with vertical axis as symmetry-line      |");
+    pm_message("| usage:                                                        |");
+    pm_message("| pgmabel [-help] [-axis N] [-factor N] [-left|-right]          |");
+    pm_message("|         [-pixsize] [-verbose] [pgmfile]                       |");
+    pm_message("|   axis    : horizontal position of the axis                   |");
+    pm_message("|   factor  : user defines stretch-factor for the gray levels   |");
+    pm_message("|   pixsize : size of one pixel in mm (default = 0.1)           |");
+    pm_message("|   left    : calculating only the left (or right) side         |");
+    pm_message("|   verbose : output of useful data                             |");
+    pm_message("|   pgmfile : Name of a pgmfile (optional)                      |");
+    pm_message("|                                                               |");
+    pm_message("| for further information please contact the manpage            |"); 
+    pm_message("-----------------------------------------------------------------");
+    pm_message("%s",version);     /* telling the version      */
+    exit(-1);                     /* retur-code for no result */
+}
+
+
+
+
+
+/* ----------------------------------------------------------------------------
+** main program
+*/
+int main( argc, argv )
+    int    argc;
+    char*  argv[];
+{
+    FILE*  ifp;
+    gray maxval;                            /* maximum gray-level            */
+    gray* grayorig;
+    gray* grayrow;                          /* one line in the image         */
+    int argn, rows, cols, row, format;
+    int col, midcol=0, temp, tc;
+    float *trow;                          /* temporary row for deconvolution */
+    float l_div, r_div, fac=1.0, cfac=4.0;  /* factor for scaling gray-level */
+    float pixsize=0.1;
+    /* no verbose, calculating both sides                                */
+    int verb = FALSE, left = TRUE, right = TRUE;
+    int nologo = FALSE;
+    const char* const usage = "[-help] [-axis N] [-factor N] [-pixsize N] [-left|-right] [-verbose] [pgmfile]";
+
+    pgm_init( &argc, argv );
+    argn = 1;
+
+    /* Check for flags. */
+    while ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' )
+        {
+        if ( pm_keymatch( argv[argn], "-help", 1 ) ) help();
+        else if ( pm_keymatch( argv[argn], "-axis", 1 ) )
+            {
+            ++argn;
+            if ( argn == argc || sscanf( argv[argn], "%i", &midcol ) !=1 )
+                pm_usage( usage );
+            }
+        else if ( pm_keymatch( argv[argn], "-factor", 1 ) )
+            {
+            ++argn;
+            if ( argn == argc || sscanf( argv[argn], "%f", &fac ) !=1 )
+                pm_usage( usage );
+            }
+        else if ( pm_keymatch( argv[argn], "-pixsize", 1 ) )
+            {
+            ++argn;
+            if ( argn == argc || sscanf( argv[argn], "%f", &pixsize ) !=1 )
+                pm_usage( usage );
+            }
+        else if ( pm_keymatch( argv[argn], "-verbose", 1 ) )
+            {
+                verb = TRUE;
+            }
+        else if ( pm_keymatch( argv[argn], "-left", 1 ) )
+            {
+                if ( left ) right = FALSE;
+                else pm_usage( usage );
+            }
+        else if ( pm_keymatch( argv[argn], "-right", 1 ) )
+           {
+                if ( right ) left = FALSE;
+                else pm_usage( usage );
+            }
+        else if ( pm_keymatch( argv[argn], "-nologo", 4 ) )
+            {
+                nologo = TRUE;
+            }
+        else
+            pm_usage( usage );
+        ++ argn;
+        }
+    if ( argn < argc )
+        {
+        ifp = pm_openr( argv[argn] );                    /* open the picture */
+        ++argn;
+        }
+    else
+        ifp = stdin;                                /* or reading from STDIN */
+    if ( argn != argc )
+        pm_usage( usage );
+
+    pgm_readpgminit( ifp, &cols, &rows, &maxval, &format );  /* read picture  */
+    pgm_writepgminit( stdout, cols, rows, maxval, 0 );  /* write the header  */
+    grayorig = pgm_allocrow(cols);
+    grayrow = pgm_allocrow( cols );                     /* allocate a row    */
+
+    if (midcol == 0) midcol = cols/2;     /* if no axis set take the center */
+    if (left ) l_div = (float)(PID2*pixsize)/(cfac*fac);
+    else l_div=1.0;                              /* weighting the left side  */
+    if (right) r_div = (float)(PID2*pixsize)/(cfac*fac);
+    else r_div=1.0;                              /* weighting the right side */
+
+    if (verb)
+    {
+        pm_message("%s",version);
+        pm_message("Calculating a portable graymap with %i rows and %i cols",rows,cols);
+        pm_message("  resuming a pixelsize of %f mm",pixsize);
+        if ( !right ) pm_message("     only the left side!");
+        if ( !left ) pm_message("     only the right side!");
+        pm_message("  axis = %i, stretching factor = %f",midcol,cfac*fac);
+        if ( left ) pm_message("  left side weighting = %f",l_div);
+        if ( right ) pm_message(" right side weighting = %f",r_div);
+    }
+
+    /* allocating the memory for the arrays aldl and ardl                    */
+    aldl = calloc ( midcol*midcol, sizeof(double));
+    if( !aldl )
+        pm_error( "out of memory" );
+    ardl = calloc ( (cols-midcol)*(cols-midcol), sizeof(double));
+    if( !ardl )
+        pm_error( "out of memory" );
+
+    MALLOCARRAY(trow, cols);
+    if( !trow )
+        pm_error( "out of memory" );
+
+    /* now precalculating the weighting-factors for the abel-transformation  */
+    for (col = 0; col < midcol; ++col)             /* factors for left side  */
+    {
+        for (tc = 0; tc < midcol; ++tc) aldl[col*midcol+tc] = dr(col,tc+0.5,midcol);
+    }
+    for (col = 0; col < (cols-midcol); ++col)      /* factors for right side */
+    {
+        for (tc = 0; tc < (cols-midcol); ++tc) 
+            ardl[col*(cols-midcol)+tc] = dr(col,tc+0.5,cols-midcol);
+    }
+
+    /* abel-transformation for each row splitted in right and left side      */
+    for ( row = 0; row < rows ; ++row )
+    {
+        pgm_readpgmrow( ifp, grayorig, cols, maxval, format );
+        for ( col = 0; col < midcol; ++col)          /* left side            */
+        {
+            trow[col] = (float) (grayorig[col]);
+        }
+        if (left ) abel(trow, midcol, aldl);         /* deconvolution        */
+        for ( col = 0; col < midcol; ++col)          /* writing left side    */
+        {
+            temp = (int)(trow[col]/l_div);
+            grayrow[col] = (temp>0?temp:0);
+        }
+        for ( col = midcol; col < cols; ++col )      /* right side           */
+        {
+            trow[cols-col-1] = (float) (grayorig[col]);
+        }
+        if ( right ) abel(trow,(cols-midcol),ardl);  /* deconvolution        */
+        for ( col = midcol; col < cols; ++col)       /* writing right side   */
+        {
+            temp = (int)(trow[cols-col-1]/r_div);
+            temp = (temp>0?temp:0);
+            grayrow[col] = temp;
+        }
+        pgm_writepgmrow( stdout, grayrow, cols, maxval, 0 );  /* saving row  */
+    }
+    pm_close( ifp );
+    pm_close( stdout );               /* closing output                      */
+    free( trow );                     /* deconvolution is done, clear memory */
+    pgm_freerow( grayorig );
+    pgm_freerow( grayrow );
+    free(aldl);
+    free(ardl);                      /* all used memory freed (i hope)       */
+    exit( 0 );                       /* end of procedure                     */
+}
+
diff --git a/editor/specialty/pgmbentley.c b/editor/specialty/pgmbentley.c
new file mode 100644
index 00000000..aed92074
--- /dev/null
+++ b/editor/specialty/pgmbentley.c
@@ -0,0 +1,74 @@
+/* pgmbentley.c - read a portable graymap and smear it according to brightness
+**
+** Copyright (C) 1990 by Wilson Bent (whb@hoh-2.att.com)
+**
+** 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 <stdio.h>
+
+#include "pm_c_util.h"
+#include "pgm.h"
+
+
+static unsigned int const N = 4;
+
+
+int
+main(int argc, const char * argv[]) {
+
+    FILE * ifP;
+    int rows, cols;
+    gray maxval;
+    gray ** gin;
+    gray ** gout;
+    unsigned int row;
+    const char * inputFileName;
+
+    pm_proginit(&argc, argv);
+
+    if (argc-1 < 1)
+        inputFileName = "-";
+    else {
+        inputFileName = argv[1];
+
+        if (argc-1 > 1)
+            pm_error("There are no options and only one argument.  "
+                     "You specified %u", argc-1);
+    }
+	ifP = pm_openr(inputFileName);
+
+    gin = pgm_readpgm(ifP, &cols, &rows, &maxval);
+
+    pm_close(ifP);
+
+    gout = pgm_allocarray(cols, rows);
+
+    for (row = 0; row < rows; ++row) {
+        unsigned int col;
+        for (col = 0; col < cols; ++col)
+            gout[row][col] = 0;
+    }
+
+    for (row = 0; row < rows; ++row) {
+        unsigned int col;
+
+        for (col = 0; col < cols; ++col) {
+            unsigned int const brow = MIN(rows-1, row + gin[row][col] / N);
+
+            gout[brow][col] = gin[row][col];
+	    }
+    }
+     
+    pgm_writepgm(stdout, gout, cols, rows, maxval, 0);
+
+    pm_close(stdout);
+    pgm_freearray(gout, rows);
+
+    return 0;
+}
diff --git a/editor/specialty/pgmmorphconv.c b/editor/specialty/pgmmorphconv.c
new file mode 100644
index 00000000..abc4e718
--- /dev/null
+++ b/editor/specialty/pgmmorphconv.c
@@ -0,0 +1,253 @@
+/* pgmmorphconv.c - morphological convolutions on a graymap: dilation and 
+** erosion
+**
+** Copyright (C) 2000 by Luuk van Dijk/Mind over Matter
+**
+** Based on 
+** pnmconvol.c - general MxN convolution on a portable anymap
+**
+** 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.
+*/
+
+#include "pm_c_util.h"
+#include "pgm.h"
+
+
+/************************************************************
+ * Dilate 
+ ************************************************************/
+
+static int 
+dilate( bit** template, int trowso2, int tcolso2, 
+        gray** in_image, gray** out_image, 
+        int rows, int cols ){
+
+  int c, r, tc, tr;
+  int templatecount;
+  gray source;
+
+  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 ){
+
+      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 ){
+          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 */
+
+  return templatecount;
+
+} /* dilate */
+
+
+
+/************************************************************
+ * Erode: same as dilate except !!!!
+ ************************************************************/
+
+static int 
+erode( bit** template, int trowso2, int tcolso2, 
+       gray** in_image, gray** out_image, 
+       int rows, int cols ){
+
+  int c, r, tc, tr;
+  int templatecount;
+  gray source;
+
+  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
+   */
+
+  templatecount=0;
+
+  for( tr=-trowso2; tr<=trowso2; ++tr ){
+    for( tc=-tcolso2; tc<=tcolso2; ++tc ){
+
+      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 ){
+
+      source = in_image[r+tr][c+tc];
+      out_image[r][c] = MIN(source, out_image[r][c]);
+      
+    } /* for c */
+      } /* for r */
+
+
+
+    } /* for tr */
+  } /* for tc */
+
+  return templatecount;
+
+} /* erode */
+
+
+
+/************************************************************
+ *  Main
+ ************************************************************/
+
+
+int main( int argc, char* argv[] ){
+
+  int argn;
+  char operation;
+  const char* usage = "-dilate|-erode|-open|-close <templatefile> [pgmfile]";
+
+  FILE* tifp;   /* template */
+  int tcols, trows;
+  int tcolso2, trowso2;
+  bit** template;
+
+
+  FILE*  ifp;   /* input image */
+  int cols, rows;
+  gray maxval;
+
+  gray** in_image;
+  gray** out_image;
+
+  int templatecount=0;
+
+  pgm_init( &argc, argv );
+
+  /*
+   *  parse arguments
+   */ 
+  
+  ifp = stdin;
+  operation = 'd';
+
+  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.
+   */
+
+  template = pbm_readpbm( tifp, &tcols, &trows );
+  pm_close( tifp );
+
+  if( tcols % 2 != 1 || trows % 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 */
+  tcolso2 = tcols / 2; /* template coords run from -tcols/2 .. 0 .. +tcols/2 */
+  trowso2 = trows / 2;
+
+#if 0
+  fprintf(stderr, "template: %d  x %d\n", trows, tcols);
+  fprintf(stderr, "half: %d  x %d\n", trowso2, tcolso2);
+#endif
+
+  /*
+   * Read in the image
+   */
+  
+  in_image = pgm_readpgm( ifp, &cols, &rows, &maxval);
+
+  if( cols < tcols || rows < trows )
+    pm_error("the image is smaller than the convolution matrix" );
+  
+#if 0
+  fprintf(stderr, "image: %d  x %d (%d)\n", rows, cols, maxval);
+#endif
+
+  /* 
+   * Allocate  output buffer and initialize with min or max value 
+   */
+
+  out_image = pgm_allocarray( cols, rows );
+  
+  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 );
+  }
+  
+  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 */
+
diff --git a/editor/specialty/pnmindex.c b/editor/specialty/pnmindex.c
new file mode 100644
index 00000000..ca1da18c
--- /dev/null
+++ b/editor/specialty/pnmindex.c
@@ -0,0 +1,640 @@
+/*============================================================================
+                              pnmindex   
+==============================================================================
+
+  build a visual index of a bunch of PNM images
+
+  This used to be a C shell program, and then a BASH program.  Neither
+  were portable enough, and the program is too complex for either of
+  those languages anyway, so now it's in C.
+
+  By Bryan Henderson 2005.04.24.
+
+  Contributed to the public domain by its author.
+
+============================================================================*/
+
+#define _BSD_SOURCE   /* Make sure strdup is in string.h */
+
+#include <assert.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <sys/stat.h>
+
+
+#include "pm_config.h"
+#include "pm_c_util.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+#include "nstring.h"
+#include "pnm.h"
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    unsigned int  inputFileCount;
+    const char ** inputFileName;
+    unsigned int  size;
+    unsigned int  across;
+    unsigned int  colors;
+    unsigned int  black;
+    unsigned int  noquant;
+    const char *  title;
+    unsigned int  verbose;
+};
+
+static bool verbose;
+
+
+
+static void PM_GNU_PRINTF_ATTR(1,2)
+systemf(const char * const fmt,
+        ...) {
+
+    va_list varargs;
+    
+    size_t dryRunLen;
+    
+    va_start(varargs, fmt);
+    
+    vsnprintfN(NULL, 0, fmt, varargs, &dryRunLen);
+
+    va_end(varargs);
+
+    if (dryRunLen + 1 < dryRunLen)
+        /* arithmetic overflow */
+        pm_error("Command way too long");
+    else {
+        size_t const allocSize = dryRunLen + 1;
+        char * shellCommand;
+        shellCommand = malloc(allocSize);
+        if (shellCommand == NULL)
+            pm_error("Can't get storage for %u-character command",
+                     allocSize);
+        else {
+            va_list varargs;
+            size_t realLen;
+            int rc;
+
+            va_start(varargs, fmt);
+
+            vsnprintfN(shellCommand, allocSize, fmt, varargs, &realLen);
+                
+            assert(realLen == dryRunLen);
+            va_end(varargs);
+
+            if (verbose)
+                pm_message("shell cmd: %s", shellCommand);
+
+            rc = system(shellCommand);
+            if (rc != 0)
+                pm_error("shell command '%s' failed.  rc %d",
+                         shellCommand, rc);
+            
+            strfree(shellCommand);
+        }
+    }
+}
+        
+
+
+static void
+parseCommandLine(int argc, char ** argv, 
+                 struct cmdlineInfo * const cmdlineP) {
+
+    unsigned int option_def_index;
+    optEntry *option_def;
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int quant;
+    unsigned int sizeSpec, colorsSpec, acrossSpec, titleSpec;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "black",       OPT_FLAG,   NULL,                  
+            &cmdlineP->black,         0);
+    OPTENT3(0, "noquant",     OPT_FLAG,   NULL,                  
+            &cmdlineP->noquant,       0);
+    OPTENT3(0, "quant",       OPT_FLAG,   NULL,                  
+            &quant,                   0);
+    OPTENT3(0, "verbose",     OPT_FLAG,   NULL,                  
+            &cmdlineP->verbose,       0);
+    OPTENT3(0, "size",        OPT_UINT,   &cmdlineP->size,
+            &sizeSpec,                0);
+    OPTENT3(0, "colors",      OPT_UINT,   &cmdlineP->colors,
+            &colorsSpec,              0);
+    OPTENT3(0, "across",      OPT_UINT,   &cmdlineP->across,
+            &acrossSpec,              0);
+    OPTENT3(0, "title",       OPT_STRING, &cmdlineP->title,
+            &titleSpec,               0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE; 
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdline_p and others. */
+
+    if (quant && cmdlineP->noquant)
+        pm_error("You can't specify both -quant and -noquat");
+
+    if (!colorsSpec)
+        cmdlineP->colors = 256;
+    
+    if (!sizeSpec)
+        cmdlineP->size = 100;
+
+    if (!acrossSpec)
+        cmdlineP->across = 6;
+
+    if (!titleSpec)
+        cmdlineP->title = NULL;
+
+    if (colorsSpec && cmdlineP->noquant)
+        pm_error("-colors doesn't make any sense with -noquant");
+
+    if (argc-1 < 1)
+        pm_error("You must name at least one file that contains an image "
+                 "to go into the index");
+
+    cmdlineP->inputFileCount = argc-1;
+
+    MALLOCARRAY_NOFAIL(cmdlineP->inputFileName, cmdlineP->inputFileCount);
+
+    {
+        unsigned int i;
+        for (i = 0; i < cmdlineP->inputFileCount; ++i) {
+            cmdlineP->inputFileName[i] = strdup(argv[i+1]);
+            if (cmdlineP->inputFileName[i] == NULL)
+                pm_error("Unable to allocate memory for a file name");
+        }
+    }
+}
+
+
+
+static void
+freeCmdline(struct cmdlineInfo const cmdline) {
+
+    unsigned int i;
+
+    for (i = 0; i < cmdline.inputFileCount; ++i)
+        strfree(cmdline.inputFileName[i]);
+
+    free(cmdline.inputFileName);
+
+}
+
+
+
+static void
+makeTempDir(const char ** const tempDirP) {
+
+    const char * const tmpdir = getenv("TMPDIR") ? getenv("TMPDIR") : "/tmp";
+
+    const char * mytmpdir;
+    int rc;
+
+    asprintfN(&mytmpdir, "%s/pnmindex_%d", tmpdir, getpid());
+
+    rc = pm_mkdir(mytmpdir, 0700);
+    if (rc != 0)
+        pm_error("Unable to create temporary file directory '%s'.  mkdir() "
+                 "fails with errno %d (%s)",
+                 mytmpdir, errno, strerror(errno));
+
+    *tempDirP = mytmpdir;
+}
+
+
+
+static void
+removeTempDir(const char * const tempDir) {
+
+    int rc;
+
+    rc = rmdir(tempDir);
+    if (rc != 0)
+        pm_error("Failed to remove temporary file directory '%s'.  "
+                 "rmdir() fails with errno %d (%s)",
+                 tempDir, errno, strerror(errno));
+}
+
+
+static const char *
+rowFileName(const char * const dirName,
+            unsigned int const row) {
+
+    const char * fileName;
+    
+    asprintfN(&fileName, "%s/pi.%u", dirName, row);
+    
+    return fileName;
+}
+
+
+
+static void
+makeTitle(const char * const title,
+          unsigned int const rowNumber,
+          bool         const blackBackground,
+          const char * const tempDir) {
+
+    const char * const invertStage = blackBackground ? "| pnminvert " : "";
+
+    const char * fileName;
+
+    fileName = rowFileName(tempDir, rowNumber);
+
+    /* This quoting is not adequate.  We really should do this without
+       a shell at all.
+    */
+    systemf("pbmtext \"%s\" "
+            "%s"
+            "> %s", 
+            title, invertStage, fileName);
+
+    strfree(fileName);
+}
+
+
+
+static void
+copyImage(const char * const inputFileName,
+          const char * const outputFileName) {
+
+    systemf("cat %s > %s", inputFileName, outputFileName);
+} 
+
+
+
+static void
+copyScaleQuantImage(const char * const inputFileName,
+                    const char * const outputFileName,
+                    int          const format,
+                    unsigned int const size,
+                    unsigned int const quant,
+                    unsigned int const colors) {
+
+    const char * scaleCommand;
+
+    switch (PNM_FORMAT_TYPE(format)) {
+    case PBM_TYPE:
+        asprintfN(&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);
+        break;
+        
+    case PPM_TYPE:
+        if (quant)
+            asprintfN(&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);
+        break;
+    default:
+        pm_error("Unrecognized Netpbm format: %d", format);
+    }
+
+    systemf("%s", scaleCommand);
+
+    strfree(scaleCommand);
+}
+
+
+
+static int
+formatTypeMax(int const typeA,
+              int const typeB) {
+
+    if (typeA == PPM_TYPE || typeB == PPM_TYPE)
+        return PPM_TYPE; 
+    else if (typeA == PGM_TYPE || typeB == PGM_TYPE)
+        return PGM_TYPE;
+    else
+        return PBM_TYPE;
+}
+
+
+
+static const char *
+thumbnailFileName(const char * const dirName,
+                  unsigned int const row,
+                  unsigned int const col) {
+
+    const char * fileName;
+    
+    asprintfN(&fileName, "%s/pi.%u.%u", dirName, row, col);
+    
+    return fileName;
+}
+
+
+
+static const char *
+thumbnailFileList(const char * const dirName,
+                  unsigned int const row,
+                  unsigned int const cols) {
+
+    unsigned int const maxListSize = 4096;
+
+    char * list;
+    unsigned int col;
+
+    list = malloc(maxListSize);
+    if (list == NULL)
+        pm_error("Unable to allocate %u bytes for file list", maxListSize);
+
+    list[0] = '\0';
+    
+    for (col = 0; col < cols; ++col) {
+        const char * const fileName = thumbnailFileName(dirName, row, col);
+
+        if (strlen(list) + strlen(fileName) + 1 > maxListSize - 1)
+            pm_error("File name list too long for this program to handle.");
+        else {
+            strcat(list, " ");
+            strcat(list, fileName);
+        }
+        strfree(fileName);
+    }
+
+    return list;
+}
+
+
+
+static void
+makeImageFile(const char * const thumbnailFileName,
+              const char * const inputFileName,
+              bool         const blackBackground,
+              const char * const outputFileName) {
+
+    const char * const blackWhiteOpt = blackBackground ? "-black" : "-white";
+    const char * const invertStage = blackBackground ? "| pnminvert " : "";
+
+    systemf("pbmtext \"%s\" "
+            "%s"
+            "| pnmcat %s -topbottom %s - "
+            "> %s",
+            inputFileName, invertStage, blackWhiteOpt, 
+            thumbnailFileName, outputFileName);
+}    
+
+
+
+static void
+makeThumbnail(const char *  const inputFileName,
+              unsigned int  const size,
+              bool          const black,
+              bool          const quant,
+              unsigned int  const colors,
+              const char *  const tempDir,
+              unsigned int  const row,
+              unsigned int  const col,
+              int *         const formatP) {
+
+    FILE * ifP;
+    int imageCols, imageRows, format;
+    xelval maxval;
+    const char * tmpfile;
+    const char * fileName;
+        
+    ifP = pm_openr(inputFileName);
+    pnm_readpnminit(ifP, &imageCols, &imageRows, &maxval, &format);
+    pm_close(ifP);
+    
+    asprintfN(&tmpfile, "%s/pi.tmp", tempDir);
+
+    if (imageCols < size && imageRows < size)
+        copyImage(inputFileName, tmpfile);
+    else
+        copyScaleQuantImage(inputFileName, tmpfile, format, 
+                            size, quant, colors);
+
+    fileName = thumbnailFileName(tempDir, row, col);
+        
+    makeImageFile(tmpfile, inputFileName, black, fileName);
+
+    unlink(tmpfile);
+
+    strfree(fileName);
+    strfree(tmpfile);
+
+    *formatP = format;
+}
+        
+
+
+static void
+unlinkThumbnailFiles(const char * const dirName,
+                     unsigned int const row,
+                     unsigned int const cols) {
+
+    unsigned int col;
+    
+    for (col = 0; col < cols; ++col) {
+        const char * const fileName = thumbnailFileName(dirName, row, col);
+
+        unlink(fileName);
+
+        strfree(fileName);
+    }
+}
+
+
+
+static void
+unlinkRowFiles(const char * const dirName,
+               unsigned int const rows) {
+
+    unsigned int row;
+    
+    for (row = 0; row < rows; ++row) {
+        const char * const fileName = rowFileName(dirName, row);
+
+        unlink(fileName);
+
+        strfree(fileName);
+    }
+}
+
+
+
+static void
+combineIntoRowAndDelete(unsigned int const row,
+                        unsigned int const cols,
+                        int          const maxFormatType,
+                        bool         const blackBackground,
+                        bool         const quant,
+                        unsigned int const colors,
+                        const char * const tempDir) {
+
+    const char * const blackWhiteOpt = blackBackground ? "-black" : "-white";
+
+    const char * fileName;
+    const char * quantStage;
+    const char * fileList;
+    
+    fileName = rowFileName(tempDir, row);
+
+    unlink(fileName);
+
+    if (maxFormatType == PPM_TYPE && quant)
+        asprintfN(&quantStage, "| pnmquant -quiet %u ", colors);
+    else
+        quantStage = strdup("");
+
+    fileList = thumbnailFileList(tempDir, row, cols);
+
+    systemf("pnmcat %s -leftright -jbottom %s "
+            "%s"
+            ">%s",
+            blackWhiteOpt, fileList, quantStage, fileName);
+
+    strfree(fileList);
+    strfree(quantStage);
+    strfree(fileName);
+
+    unlinkThumbnailFiles(tempDir, row, cols);
+}
+
+
+
+static const char *
+rowFileList(const char * const dirName,
+            unsigned int const rows) {
+
+    unsigned int const maxListSize = 4096;
+
+    unsigned int row;
+    char * list;
+
+    list = malloc(maxListSize);
+    if (list == NULL)
+        pm_error("Unable to allocate %u bytes for file list", maxListSize);
+
+    list[0] = '\0';
+
+    for (row = 0; row < rows; ++row) {
+        const char * const fileName = rowFileName(dirName, row);
+
+        if (strlen(list) + strlen(fileName) + 1 > maxListSize - 1)
+            pm_error("File name list too long for this program to handle.");
+        
+        else {
+            strcat(list, " ");
+            strcat(list, fileName);
+        }
+        strfree(fileName);
+    }
+
+    return list;
+}
+
+
+
+static void
+writeRowsAndDelete(unsigned int const rows,
+                   int          const maxFormatType,
+                   bool         const blackBackground,
+                   bool         const quant,
+                   unsigned int const colors,
+                   const char * const tempDir) {
+
+    const char * const blackWhiteOpt = blackBackground ? "-black" : "-white";
+
+    const char * quantStage;
+    const char * fileList;
+    
+    if (maxFormatType == PPM_TYPE && quant)
+        asprintfN(&quantStage, "| pnmquant -quiet %u ", colors);
+    else
+        quantStage = strdup("");
+
+    fileList = rowFileList(tempDir, rows);
+
+    systemf("pnmcat %s -topbottom %s %s",
+            blackWhiteOpt, fileList, quantStage);
+
+    strfree(fileList);
+    strfree(quantStage);
+
+    unlinkRowFiles(tempDir, rows);
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+    struct cmdlineInfo cmdline;
+    const char * tempDir;
+    int maxFormatType;
+    unsigned int colsInRow;
+    unsigned int rowsDone;
+    unsigned int i;
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    verbose = cmdline.verbose;
+    
+    makeTempDir(&tempDir);
+
+    maxFormatType = PBM_TYPE;
+    colsInRow = 0;
+    rowsDone = 0;
+
+    if (cmdline.title)
+        makeTitle(cmdline.title, rowsDone++, cmdline.black, tempDir);
+
+    for (i = 0; i < cmdline.inputFileCount; ++i) {
+        const char * const inputFileName = cmdline.inputFileName[i];
+
+        int format;
+
+        makeThumbnail(inputFileName, cmdline.size, cmdline.black, 
+                      !cmdline.noquant, cmdline.colors, tempDir,
+                      rowsDone, colsInRow, &format);
+
+        maxFormatType = formatTypeMax(maxFormatType, PNM_FORMAT_TYPE(format));
+
+        ++colsInRow;
+        if (colsInRow >= cmdline.across || i == cmdline.inputFileCount-1) {
+            combineIntoRowAndDelete(
+                rowsDone, colsInRow, maxFormatType,
+                cmdline.black, !cmdline.noquant, cmdline.colors,
+                tempDir);
+            ++rowsDone;
+            colsInRow = 0;
+        }
+    }
+
+    writeRowsAndDelete(rowsDone, maxFormatType, cmdline.black,
+                       !cmdline.noquant, cmdline.colors, tempDir);
+
+    removeTempDir(tempDir);
+
+    freeCmdline(cmdline);
+
+    pm_close(stdout);
+
+    return 0;
+}
diff --git a/editor/specialty/ppm3d.c b/editor/specialty/ppm3d.c
new file mode 100644
index 00000000..d9ada365
--- /dev/null
+++ b/editor/specialty/ppm3d.c
@@ -0,0 +1,308 @@
+/*=============================================================================
+                                   ppmto3d
+===============================================================================
+  This program converts two PPM images into an anaglyph stereogram image PPM.
+  (for viewing with red/blue 3D glasses).
+
+=============================================================================*/
+
+#include <assert.h>
+
+#include "pm_c_util.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+#include "ppm.h"
+#include "lum.h"
+
+
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char * leftInputFileName;  /* '-' if stdin */
+    const char * rghtInputFileName;  /* '-' if stdin */
+    int offset;
+    unsigned int color;
+};
+
+
+
+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 offsetSpec;
+    const char * offsetArg;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "color",   OPT_FLAG,   NULL,
+            &cmdlineP->color,   0);
+    OPTENT3(0, "offset",  OPT_INT,    &cmdlineP->offset,
+            &offsetSpec,  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 (argc-1 < 2)
+        pm_error("You must specify at least two arguments: left and right "
+                 "input file names.  You specified %u", argc-1);
+    else {
+        cmdlineP->leftInputFileName = argv[1];
+        cmdlineP->rghtInputFileName = argv[2];
+
+        if (argc-1 > 2) {
+            offsetArg = argv[3];
+
+            if (argc-1 > 3)
+                pm_error("Program takes at most 3 arguments:  left and "
+                         "right input file names and offset.  "
+                         "You specified %u", argc-1);
+        } else
+            offsetArg = NULL;
+    }
+
+    if (offsetArg && offsetSpec)
+        pm_error("You cannot specify both -offset and the offset "
+                 "argument (i.e. with -offset, there is at most "
+                 "two arguments: left and right input file names");
+    else if (!offsetArg && !offsetSpec)
+        cmdlineP->offset = 0;
+    else if (offsetArg)
+        cmdlineP->offset = atoi(offsetArg);
+}
+
+
+
+static void
+computeGrayscaleRow(const pixel * const inputRow,
+                    gray *        const outputRow,
+                    pixval        const maxval,
+                    unsigned int  const cols) {
+
+    if (maxval <= 255) {
+        unsigned int col;
+        /* Use fast approximation to 0.299 r + 0.587 g + 0.114 b. */
+        for (col = 0; col < cols; ++col)
+            outputRow[col] = ppm_fastlumin(inputRow[col]);
+    } else {
+        unsigned int col;
+        /* Can't use fast approximation, so fall back on floats. */
+        for (col = 0; col < cols; ++col)
+            outputRow[col] = PPM_LUMIN(inputRow[col]) + 0.5;
+    }
+}
+
+
+
+static void
+compute3dRowMono(gray *       const lGrayrow,
+                 gray *       const rGrayrow,
+                 pixel *      const pixelrow,
+                 unsigned int const cols,
+                 int          const offset) {
+    
+    unsigned int col;
+    gray *  lgP;
+    gray *  rgP;
+    pixel * pP;
+
+    assert(abs(offset) <= cols);
+
+    for (col = 0, pP = pixelrow, lgP = lGrayrow, rgP = rGrayrow;
+         col < cols + offset;
+         ++col) {
+            
+        if ((int)col < offset/2)
+            ++lgP;
+        else if ((int)col < offset) {
+            PPM_ASSIGN(*pP, 0, *lgP, *lgP);
+            ++lgP;
+            ++pP;
+        } else if (col < cols) {
+            PPM_ASSIGN(*pP, *rgP, *lgP, *lgP);
+            ++lgP;
+            ++rgP;
+            ++pP;
+        } else if (col < cols + offset/2) {
+            PPM_ASSIGN(*pP, *rgP, 0, 0);
+            ++rgP;
+            ++pP;
+        } else {
+            assert(col < cols + offset);
+            ++rgP;
+        }
+    }
+}    
+
+
+
+static void
+compute3dRowColor(pixel *      const lPixelrow,
+                  pixel *      const rPixelrow,
+                  pixel *      const pixelrow,
+                  unsigned int const cols,
+                  unsigned int const offset) {
+    
+    unsigned int col;
+    pixel * lP;
+    pixel * rP;
+    pixel * pP;
+
+    assert(abs(offset) <= cols);
+
+    for (col = 0, pP = pixelrow, lP = lPixelrow, rP = rPixelrow;
+         col < cols + offset;
+         ++col) {
+            
+        if ((int)col < offset/2)
+            ++lP;
+        else if ((int)col < offset) {
+            PPM_ASSIGN(*pP, 0, PPM_GETG(*lP), PPM_GETB(*lP));
+            ++lP;
+            ++pP;
+        } else if (col < cols) {
+            PPM_ASSIGN(*pP, PPM_GETR(*rP), PPM_GETG(*lP), PPM_GETB(*lP));
+            ++lP;
+            ++rP;
+            ++pP;
+        } else if (col < cols + offset/2) {
+            PPM_ASSIGN(*pP, PPM_GETR(*rP), 0, 0);
+            ++rP;
+            ++pP;
+        } else {
+            assert(col < cols + offset);
+            ++rP;
+        }
+    }
+}    
+
+
+
+static void
+write3dRaster(FILE *       const ofP,
+              FILE *       const lIfP,
+              FILE *       const rIfP,
+              unsigned int const cols,
+              unsigned int const rows,
+              pixval       const maxval,
+              int          const lFormat,
+              int          const rFormat,
+              int          const offset,
+              bool         const color) {
+
+    pixel * lPixelrow;
+    gray *  lGrayrow;
+    pixel * rPixelrow;
+    gray *  rGrayrow;
+    pixel * pixelrow;
+
+    unsigned int row;
+
+    assert(abs(offset) < cols);
+
+    lPixelrow = ppm_allocrow (cols);
+    lGrayrow  = pgm_allocrow (cols);
+    rPixelrow = ppm_allocrow (cols);
+    rGrayrow  = pgm_allocrow (cols);
+    pixelrow  = ppm_allocrow (cols);
+
+    for (row = 0; row < rows; ++row) {
+        ppm_readppmrow(lIfP, lPixelrow, cols, maxval, lFormat);
+        ppm_readppmrow(rIfP, rPixelrow, cols, maxval, rFormat);
+
+        computeGrayscaleRow(lPixelrow, lGrayrow, maxval, cols);
+        computeGrayscaleRow(rPixelrow, rGrayrow, maxval, cols);
+
+        if (color)
+            compute3dRowColor(lPixelrow, rPixelrow, pixelrow, cols, offset);
+        else
+            compute3dRowMono(lGrayrow, rGrayrow, pixelrow, cols, offset);
+
+        ppm_writeppmrow(ofP, pixelrow, cols, maxval, 0);
+    }
+
+    ppm_freerow(pixelrow);
+    pgm_freerow(rGrayrow);
+    ppm_freerow(rPixelrow);
+    pgm_freerow(lGrayrow);
+    ppm_freerow(lPixelrow);
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+    FILE * lIfP;
+    FILE * rIfP;
+
+    int cols, rows;
+    pixval maxval;
+
+    int lRows, lCols;
+    int lFormat;
+    pixval lMaxval;
+   
+    int rRows, rCols;
+    int rFormat;
+    pixval rMaxval;
+   
+    ppm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    lIfP = pm_openr(cmdline.leftInputFileName);
+    rIfP = pm_openr(cmdline.rghtInputFileName);
+
+    ppm_readppminit(lIfP, &lCols, &lRows, &lMaxval, &lFormat);
+    ppm_readppminit(rIfP, &rCols, &rRows, &rMaxval, &rFormat);
+    
+    if ((lCols != rCols) || (lRows != rRows) || 
+        (lMaxval != rMaxval) || 
+        (PPM_FORMAT_TYPE(lFormat) != PPM_FORMAT_TYPE(rFormat)))
+        pm_error ("Pictures are not of same size and format");
+    
+    cols   = lCols;
+    rows   = lRows;
+    maxval = lMaxval;
+
+    if (abs(cmdline.offset) >= cols)
+        pm_error("Magnitude of -offset (%u columns) is not less than "
+                 "width of images "
+                 "(%u columns)", abs(cmdline.offset), cols);
+   
+    ppm_writeppminit(stdout, cols, rows, maxval, 0);
+
+    write3dRaster(stdout, lIfP, rIfP, cols, rows, maxval,
+                  lFormat, rFormat, cmdline.offset, cmdline.color);
+
+    pm_close(lIfP);
+    pm_close(rIfP);
+    pm_close(stdout);
+
+    return 0;
+}
+
diff --git a/editor/specialty/ppmglobe.c b/editor/specialty/ppmglobe.c
new file mode 100644
index 00000000..82fae5fb
--- /dev/null
+++ b/editor/specialty/ppmglobe.c
@@ -0,0 +1,173 @@
+/*
+ * This code written 2003
+ * by Max Gensthaler <Max@Gensthaler.de>
+ * Distributed under the Gnu Public License (GPL)
+ *
+ * Gensthaler called it 'ppmglobemap'.
+ *
+ * Translations of comments and C dialect by Bryan Henderson May 2003.
+ */
+
+
+#define _XOPEN_SOURCE  /* get M_PI in math.h */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <math.h>
+
+#include "pm_c_util.h"
+#include "ppm.h"
+#include "colorname.h"
+#include "shhopt.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;  /* Filename of input files */
+    unsigned int stripcount;
+    const char * background;
+    unsigned int closeok;
+};
+
+
+
+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 backgroundSpec;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "background",     OPT_STRING, &cmdlineP->background, 
+            &backgroundSpec, 0);
+    OPTENT3(0, "closeok",        OPT_FLAG, NULL,
+            &cmdlineP->closeok, 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 (!backgroundSpec)
+        cmdlineP->background = NULL;
+
+    if (argc - 1 < 1) 
+        pm_error("You must specify at least one argument:  the strip count");
+    else {
+        int const stripcount = atoi(argv[1]);
+        if (stripcount <= 0)
+            pm_error("The strip count must be positive.  You specified %d",
+                     stripcount);
+            
+        cmdlineP->stripcount = stripcount;
+
+        if (argc-1 < 2)
+            cmdlineP->inputFileName = "-";
+        else
+            cmdlineP->inputFileName = argv[2];
+    
+        if (argc - 1 > 2)
+            pm_error("There are at most two arguments: strip count "
+                     "and input file name.  "
+                     "You specified %u", argc-1);
+    }
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+    FILE * ifP;
+    pixel ** srcPixels;
+    pixel ** dstPixels;
+    int srcCols, srcRows;
+    unsigned int dstCols, dstRows;
+    pixval srcMaxval, dstMaxval;
+    unsigned int stripWidth;
+        /* Width in pixels of each strip.  In the output image, this means
+           the rectangular strip in which the lens-shaped foreground strip
+           is placed..
+        */
+    unsigned int row;
+    pixel backgroundColor;
+
+    ppm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+    
+    ifP = pm_openr(cmdline.inputFileName);
+    
+    srcPixels = ppm_readppm(ifP, &srcCols, &srcRows, &srcMaxval);
+
+    pm_close(ifP);
+
+    stripWidth = srcCols / cmdline.stripcount;
+
+    if (stripWidth < 1)
+        pm_error("You asked for %u strips, but the image is only "
+                 "%u pixels wide, so that is impossible.",
+                 cmdline.stripcount, srcCols);
+
+    dstCols   = stripWidth * cmdline.stripcount;
+    dstRows   = srcRows;
+    dstMaxval = srcMaxval;
+
+    if (cmdline.background == NULL)
+        PPM_ASSIGN(backgroundColor, 0, 0, 0);
+    else
+        pm_parse_dictionary_name(cmdline.background,
+                                 dstMaxval, cmdline.closeok,
+                                 &backgroundColor);
+
+    dstPixels = ppm_allocarray(dstCols, dstRows);
+    
+    for (row = 0; row < dstRows; ++row) {
+        double const factor = sin(M_PI * row / dstRows);
+            /* Amount by which we squeeze the foreground image of each
+               strip in this row.
+            */
+        int const stripBorder = (int)((stripWidth*(1.0-factor)/2.0) + 0.5);
+            /* Distance from the edge (either one) of a strip to the
+               foreground image within that strip -- i.e. number of pixels
+               of background color, which User will cut out with scissors
+               after he prints the image.
+            */
+        unsigned int dstCol;
+
+        for (dstCol = 0; dstCol < dstCols; ++dstCol) {
+            if (dstCol % stripWidth < stripBorder
+                || dstCol % stripWidth >= stripWidth - stripBorder)
+                dstPixels[row][dstCol] = backgroundColor;
+            else {
+                unsigned int const leftEdge =
+                    (dstCol / stripWidth) * stripWidth;
+                unsigned int const srcCol = leftEdge +
+                    (int)((dstCol % stripWidth - stripBorder) / factor + 0.5);
+                dstPixels[row][dstCol] = srcPixels[row][srcCol];
+            }
+        }
+    }
+
+    ppm_writeppm(stdout, dstPixels, dstCols, dstRows, dstMaxval, 0);
+
+    return 0;
+}
diff --git a/editor/specialty/ppmntsc.c b/editor/specialty/ppmntsc.c
new file mode 100644
index 00000000..ae3bcfe9
--- /dev/null
+++ b/editor/specialty/ppmntsc.c
@@ -0,0 +1,501 @@
+/* This is ppmntsc.c, a program to adjust saturation values in an image
+   so they are legal for NTSC or PAL.
+
+   It is derived from the program rlelegal.c, dated June 5, 1995,
+   which is described below and propagates that program's copyright.
+   The derivation was done by Bryan Henderson on 2000.04.21 to convert
+   it from operating on the RLE format to operating on the PPM format
+   and to rewrite it in a cleaner style, taking advantage of modern C
+   compiler technology.  
+*/
+
+
+/*
+ * This software is copyrighted as noted below.  It may be freely copied,
+ * modified, and redistributed, provided that the copyright notice is 
+ * preserved on all copies.
+ * 
+ * There is no warranty or other guarantee of fitness for this software,
+ * it is provided solely "as is".  Bug reports or fixes may be sent
+ * to the author, who may or may not act on them as he desires.
+ *
+ * You may not include this software in a program or other software product
+ * without supplying the source, or without informing the end-user that the 
+ * source is available for no extra charge.
+ *
+ * If you modify this software, you should include a notice giving the
+ * name of the person performing the modification, the date of modification,
+ * and the reason for such modification.
+ */
+
+/* 
+ * rlelegal.c - Make RGB colors legal in the YIQ or YUV color systems.
+ * 
+ * Author:	Wes Barris
+ * 		Minnesota Supercomputer Center, Inc.
+ * Date:	Fri Oct 15, 1993
+ * @Copyright, Research Equipment Inc., d/b/a Minnesota Supercomputer
+ * Center, Inc., 1993
+
+ */
+
+#define _BSD_SOURCE 1      /* Make sure strdup() is in string.h */
+#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+
+#include "pm_c_util.h"
+#include "ppm.h"
+#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 */
+
+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 verbose;
+    unsigned int debug;
+    unsigned int pal;
+    enum {ALL, LEGAL_ONLY, ILLEGAL_ONLY, CORRECTED_ONLY} output;
+};
+
+
+
+
+static void 
+rgbtoyiq(const int r, const int g, const int b, 
+         double * const y_p, 
+         double * const i_p, 
+         double * const q_p) {
+    
+    *y_p = .299*(r/255.0) + .587*(g/255.0) + .114*(b/255.0);
+    *i_p = .596*(r/255.0) - .274*(g/255.0) - .322*(b/255.0);
+    *q_p = .211*(r/255.0) - .523*(g/255.0) + .312*(b/255.0);
+}
+
+
+
+static void 
+yiqtorgb(const double y, const double i, const double q, 
+         int * const r_p, int * const g_p, int * const b_p) {
+    *r_p = 255.0*(1.00*y + .9562*i + .6214*q);
+    *g_p = 255.0*(1.00*y - .2727*i - .6468*q);
+    *b_p = 255.0*(1.00*y -1.1037*i +1.7006*q);
+}
+
+
+
+static void 
+rgbtoyuv(const int r, const int g, const int b, 
+         double * const y_p, 
+         double * const u_p, 
+         double * const v_p) {
+    *y_p =  .299*(r/255.0) + .587*(g/255.0) + .114*(b/255.0);
+    *u_p = -.147*(r/255.0) - .289*(g/255.0) + .437*(b/255.0);
+    *v_p =  .615*(r/255.0) - .515*(g/255.0) - .100*(b/255.0);
+}
+
+
+
+static void 
+yuvtorgb(const double y, const double u, const double v, 
+         int * const r_p, int * const g_p, int * const b_p) {
+    
+    *r_p = 255.0*(1.00*y + .0000*u +1.1398*v);
+    *g_p = 255.0*(1.00*y - .3938*u - .5805*v);
+    *b_p = 255.0*(1.00*y +2.0279*u + .0000*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
+    ) {
+    
+    double sat_old, sat_new;
+    /*
+     * 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;
+    } else {
+        *y_new_p = y;
+        *i_new_p = i;
+        *q_new_p = q;
+        *action_p = ALREADY_LEGAL;
+    }
+    return;
+}
+
+
+
+static void
+make_legal_yuv(const double y, const double u, const double v, 
+               double * const y_new_p, 
+               double * const u_new_p, 
+               double * const v_new_p,
+               enum legalize * const action_p
+    ) {
+    
+    double sat_old, sat_new;
+    /*
+     * U and V are legs of a right triangle.  Saturation is the hypotenuse.
+     */
+    sat_old = sqrt(u*u + v*v);
+    if (y+sat_old >= 1.334) {
+        const double diff = 0.5*((y+sat_old) - 1.334);
+        *y_new_p = y - diff;
+        sat_new = 1.333 - *y_new_p;
+        *u_new_p = u*(sat_new/sat_old);
+        *v_new_p = v*(sat_new/sat_old);
+        *action_p = LOWER_SAT;
+    } else if (y-sat_old <= -0.339) {
+        const double diff = 0.5*((sat_old-y) - 0.339);
+        *y_new_p = y + diff;
+        sat_new = 0.338 + *y_new_p;
+        *u_new_p = u*(sat_new/sat_old);
+        *v_new_p = v*(sat_new/sat_old);
+        *action_p = RAISE_SAT;
+    } else {
+        *u_new_p = u;
+        *v_new_p = v;
+        *action_p = ALREADY_LEGAL;
+    }
+    return;
+}
+
+
+
+static void
+make_legal_yiq_i(const int r_in, const int g_in, const int b_in, 
+                 int * const r_out_p, 
+                 int * const g_out_p, 
+                 int * const b_out_p,
+                 enum legalize * const action_p
+    ) {
+    
+    double y, i, q;
+    double y_new, i_new, q_new;
+    /*
+     * 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);
+    if (*action_p != ALREADY_LEGAL)
+        /*
+         * Given the new I and Q, compute new RGB values.
+        */
+        yiqtorgb(y_new, i_new, q_new, r_out_p, g_out_p, b_out_p);
+    else {
+        *r_out_p = r_in;
+        *g_out_p = g_in;
+        *b_out_p = b_in;
+      }
+    return;
+}
+
+
+
+static void
+make_legal_yuv_i(const int r_in, const int g_in, const int b_in, 
+                 int * const r_out_p, 
+                 int * const g_out_p, 
+                 int * const b_out_p,
+                 enum legalize * const action_p
+    ){
+    
+    double y, u, v;
+    double y_new, u_new, v_new;  
+    /*
+     * Convert to YUV and compute the new saturation.
+     */
+    rgbtoyuv(r_in, g_in, b_in, &y, &u, &v);
+    make_legal_yuv(y, u, v, &y_new, &u_new, &v_new, action_p);
+    if (*action_p != ALREADY_LEGAL)
+        /*
+         * Given the new U and V, compute new RGB values.
+         */
+        yuvtorgb(y_new, u_new, v_new, r_out_p, g_out_p, b_out_p);
+    else {
+        *r_out_p = r_in;
+        *g_out_p = g_in;
+        *b_out_p = b_in;
+    }
+    return;
+}
+
+
+
+static void 
+make_legal_yiq_b(const pixel input,
+                 pixel * const output_p,
+                 enum legalize * const action_p) {
+
+
+    int ir_in, ig_in, ib_in;
+    int ir_out, ig_out, ib_out;
+    
+    ir_in = (int)PPM_GETR(input);
+    ig_in = (int)PPM_GETG(input);
+    ib_in = (int)PPM_GETB(input);
+
+    make_legal_yiq_i(ir_in, ig_in, ib_in, &ir_out, &ig_out, &ib_out, action_p);
+
+    PPM_ASSIGN(*output_p, ir_out, ig_out, ib_out);
+
+    return;
+}
+
+
+
+static void 
+make_legal_yuv_b(const pixel input,
+                 pixel * const output_p,
+                 enum legalize * const action_p) {
+
+    int ir_in, ig_in, ib_in;
+    int ir_out, ig_out, ib_out;
+    
+    ir_in = (int)PPM_GETR(input);
+    ig_in = (int)PPM_GETG(input);
+    ib_in = (int)PPM_GETB(input);
+    make_legal_yuv_i(ir_in, ig_in, ib_in, &ir_out, &ig_out, &ib_out, action_p);
+
+    PPM_ASSIGN(*output_p, ir_out, ig_out, ib_out);
+
+    return;
+}
+
+
+
+static void 
+report_mapping(const pixel old_pixel, const pixel new_pixel) {
+/*----------------------------------------------------------------------------
+  Assuming old_pixel and new_pixel 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)
+            );
+
+        last_changed_pixel = old_pixel;
+        first_time = FALSE;
+    }    
+}
+
+
+
+static void
+convert_one_image(FILE * const ifp, struct cmdlineInfo const cmdline, 
+                  bool * const eofP, 
+                  int * const hicountP, 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);
+    {
+        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 black;
+        /* A constant - black pixel */
+
+        PPM_ASSIGN(black, 0, 0, 0);
+
+        PPM_ASSIGN(last_illegal_pixel, 0, 0, 0);  /* initial value */
+        {
+            int row;
+
+            *hicountP = 0; *locountP = 0;  /* initial values */
+
+            for (row = 0; row < rows; ++row) {
+                int col;
+                ppm_readppmrow(ifp, input_row, cols, maxval, format);
+                for (col = 0; col < cols; ++col) {
+                    pixel corrected;
+                    /* Corrected or would-be corrected value for pixel */
+                    enum legalize action;
+                    /* What action was used to make pixel legal */
+                    if (cmdline.pal)
+                        make_legal_yuv_b(input_row[col],
+                                         &corrected,
+                                         &action);
+                    else
+                        make_legal_yiq_b(input_row[col],
+                                         &corrected,
+                                         &action);
+                        
+                    if (action == LOWER_SAT) 
+                        (*hicountP)++;
+                    if (action == RAISE_SAT)
+                        (*locountP)++;
+                    if (cmdline.debug) report_mapping(input_row[col],
+                                                      corrected);
+                    switch (cmdline.output) {
+                    case ALL:
+                        output_row[col] = corrected;
+                        break;
+                    case LEGAL_ONLY:
+                        output_row[col] = (action == ALREADY_LEGAL) ?
+                            input_row[col] : black;
+                        break;
+                    case ILLEGAL_ONLY:
+                        output_row[col] = (action != ALREADY_LEGAL) ?
+                            input_row[col] : black;
+                        break;
+                    case CORRECTED_ONLY:
+                        output_row[col] = (action != ALREADY_LEGAL) ?
+                            corrected : black;
+                        break;
+                    }
+                }
+                ppm_writeppmrow(stdout, output_row, cols, maxval, FALSE);
+            }
+        }
+        ppm_freerow(output_row);
+        ppm_freerow(input_row);
+    }
+}
+
+
+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) {
+    
+    struct cmdlineInfo cmdline;
+    FILE * ifP;
+    int total_hicount, total_locount;
+    int image_count;
+
+    bool eof;
+
+    ppm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilename);
+
+    image_count = 0;    /* initial value */
+    total_hicount = 0;  /* initial value */
+    total_locount = 0;  /* initial value */
+
+    eof = FALSE;
+    while (!eof) {
+        int hicount, locount;
+        convert_one_image(ifP, cmdline, &eof, &hicount, &locount);
+        image_count++;
+        total_hicount += hicount;
+        total_locount += 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_close(ifP);
+
+    return 0;
+}
diff --git a/editor/specialty/ppmrelief.c b/editor/specialty/ppmrelief.c
new file mode 100644
index 00000000..5e0669c3
--- /dev/null
+++ b/editor/specialty/ppmrelief.c
@@ -0,0 +1,90 @@
+/* ppmrelief.c - generate a relief map of a portable pixmap
+**
+** Copyright (C) 1990 by Wilson H. Bent, Jr.
+**
+** 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 <stdio.h>
+#include "ppm.h"
+
+int
+main(int argc, char * argv[]) {
+
+    FILE* ifp;
+    pixel** inputbuf;
+    pixel* outputrow;
+    int argn, rows, cols, format, row;
+    register int col;
+    pixval maxval, mv2;
+    const char* const usage = "[ppmfile]";
+
+    ppm_init( &argc, argv );
+
+    argn = 1;
+
+    if ( argn != argc ) {
+        ifp = pm_openr( argv[argn] );
+        ++argn;
+    } else
+        ifp = stdin;
+
+    if ( argn != argc )
+        pm_usage( usage );
+    
+    ppm_readppminit( ifp, &cols, &rows, &maxval, &format );
+    mv2 = maxval / 2;
+
+    /* Allocate space for 3 input rows, plus an output row. */
+    inputbuf = ppm_allocarray( cols, 3 );
+    outputrow = ppm_allocrow( cols );
+
+    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 );
+
+    /* Write out the first row, all zeros. */
+    for ( col = 0; col < cols; ++col )
+        PPM_ASSIGN( outputrow[col], 0, 0, 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.
+    */
+    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 = PPM_GETR( inputbuf[rowa][col] ) +
+                ( mv2 - PPM_GETR( inputbuf[rowb][col + 2] ) );
+            g = PPM_GETG( inputbuf[rowa][col] ) +
+                ( mv2 - PPM_GETG( inputbuf[rowb][col + 2] ) );
+            b = PPM_GETB( inputbuf[rowa][col] ) +
+                ( mv2 - PPM_GETB( inputbuf[rowb][col + 2] ) );
+            PPM_ASSIGN( outputrow[col + 1], r, g, b );
+        }
+        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 );
+
+    pm_close( ifp );
+    pm_close( stdout );
+
+    exit( 0 );
+}
diff --git a/editor/specialty/ppmshift.c b/editor/specialty/ppmshift.c
new file mode 100644
index 00000000..a765daa5
--- /dev/null
+++ b/editor/specialty/ppmshift.c
@@ -0,0 +1,132 @@
+
+/*********************************************************************/
+/* ppmshift -  shift lines of a picture left or right by x pixels    */
+/* Frank Neumann, October 1993                                       */
+/* V1.1 16.11.1993                                                   */
+/*                                                                   */
+/* version history:                                                  */
+/* V1.0    11.10.1993  first version                                 */
+/* V1.1    16.11.1993  Rewritten to be NetPBM.programming conforming */
+/*********************************************************************/
+
+#include "ppm.h"
+
+/**************************/
+/* start of main function */
+/**************************/
+int
+main(int    argc,
+     char * argv[]) {
+
+    FILE * ifP;
+    unsigned int row;
+    int argn, rows, cols, format;
+    pixel * srcrow;
+    pixel * destrow;
+    pixval maxval;
+    int shift, nowshift;
+    int shiftArg;
+
+    const char * const usage = "shift [ppmfile]\n        shift: maximum number of pixels to shift a line by\n";
+
+    /* parse in 'default' parameters */
+    ppm_init(&argc, argv);
+
+    argn = 1;
+
+    /* parse in shift number */
+    if (argn == argc)
+        pm_usage(usage);
+    if (sscanf(argv[argn], "%d", &shiftArg) != 1)
+        pm_usage(usage);
+    if (shiftArg < 0)
+        pm_error("shift factor must be 0 or more");
+    ++argn;
+
+    /* parse in filename (if present, stdin otherwise) */
+    if (argn != argc)
+    {
+        ifP = pm_openr(argv[argn]);
+        ++argn;
+    }
+    else
+        ifP = stdin;
+
+    if (argn != argc)
+        pm_usage(usage);
+
+    /* read first data from file */
+    ppm_readppminit(ifP, &cols, &rows, &maxval, &format);
+
+    if (shiftArg > cols) {
+        shift = cols;
+        pm_message("shift amount is larger than picture width - reset to %d",
+                   shift);
+    } else
+        shift = shiftArg;
+
+    srcrow = ppm_allocrow(cols);
+
+    destrow = ppm_allocrow(cols);
+
+    ppm_writeppminit(stdout, cols, rows, maxval, 0);
+
+    srand(pm_randseed());
+
+    /** now do the shifting **/
+    /* the range by which a line is shifted lays in the range from */
+    /* -shift/2 .. +shift/2 pixels; however, within this range it is */
+    /* randomly chosen */
+    for (row = 0; row < rows; ++row) {
+        pixel * pP;
+        pixel * pP2;
+
+        if (shift != 0)
+            nowshift = (rand() % (shift+1)) - ((shift+1) / 2);
+        else
+            nowshift = 0;
+
+        ppm_readppmrow(ifP, srcrow, cols, maxval, format);
+
+        pP  = &srcrow[0];
+        pP2 = &destrow[0];
+
+        /* if the shift value is less than zero, we take the original
+           pixel line and copy it into the destination line translated
+           to the left by x pixels. The empty pixels on the right end
+           of the destination line are filled up with the pixel that
+           is the right-most in the original pixel line.
+        */
+        if (nowshift < 0) {
+            unsigned int col;
+            pP += abs(nowshift);
+            for (col = 0; col < cols; ++col) {
+                PPM_ASSIGN(*pP2, PPM_GETR(*pP), PPM_GETG(*pP), PPM_GETB(*pP));
+                ++pP2;
+                if (col < (cols + nowshift) - 1)
+                    ++pP;
+            }
+        } else {
+            unsigned int col;
+            /* The shift value is 0 or positive, so fill the first
+               <nowshift> pixels of the destination line with the
+               first pixel from the source line, and copy the rest of
+               the source line to the dest line
+            */
+            for (col = 0; col < cols; ++col) {
+                PPM_ASSIGN(*pP2, PPM_GETR(*pP), PPM_GETG(*pP), PPM_GETB(*pP));
+                ++pP2;
+                if (col >= nowshift)
+                    ++pP;
+            }
+        }
+
+        ppm_writeppmrow(stdout, destrow, cols, maxval, 0);
+    }
+
+    pm_close(ifP);
+    ppm_freerow(srcrow);
+    ppm_freerow(destrow);
+
+    return 0;
+}
diff --git a/editor/specialty/ppmspread.c b/editor/specialty/ppmspread.c
new file mode 100644
index 00000000..6753f4fe
--- /dev/null
+++ b/editor/specialty/ppmspread.c
@@ -0,0 +1,117 @@
+/*********************************************************************/
+/* ppmspread -  randomly displace a PPM's pixels by a certain amount */
+/* Frank Neumann, October 1993                                       */
+/* V1.1 16.11.1993                                                   */
+/*                                                                   */
+/* version history:                                                  */
+/* V1.0 12.10.1993    first version                                  */
+/* V1.1 16.11.1993    Rewritten to be NetPBM.programming conforming  */
+/*********************************************************************/
+
+#include <string.h>
+
+#include "ppm.h"
+
+
+
+int
+main(int    argc,
+     char * argv[]) {
+
+    FILE * ifP;
+    int argn, rows, cols;
+    unsigned int row;
+    pixel ** destarray, ** srcarray;
+    pixel * pP;
+    pixel * pP2;
+    pixval maxval;
+    pixval r1, g1, b1;
+    int amount;
+    const char * const usage = "amount [ppmfile]\n        amount: # of pixels to displace a pixel by at most\n";
+
+    /* parse in 'default' parameters */
+    ppm_init(&argc, argv);
+
+    argn = 1;
+
+    /* parse in amount & seed */
+    if (argn == argc)
+        pm_usage(usage);
+    if (sscanf(argv[argn], "%d", &amount) != 1)
+        pm_usage(usage);
+    if (amount < 0)
+        pm_error("amount should be a positive number");
+    ++argn;
+
+    /* parse in filename (if present, stdin otherwise) */
+    if (argn != argc)
+    {
+        ifP = pm_openr(argv[argn]);
+        ++argn;
+    }
+    else
+        ifP = stdin;
+
+    if (argn != argc)
+        pm_usage(usage);
+
+    srcarray = ppm_readppm(ifP, &cols, &rows, &maxval);
+
+    destarray = ppm_allocarray(cols, rows);
+
+    /* clear out the buffer */
+    for (row = 0; row < rows; ++row)
+        memset(destarray[row], 0, cols * sizeof(pixel));
+
+    srand(pm_randseed());
+
+    /* start displacing pixels */
+    for (row = 0; row < rows; ++row) {
+        unsigned int col;
+        pP = &srcarray[row][0];
+
+        for (col = 0; col < cols; ++col) {
+            int const xdis = (rand() % (amount+1)) - ((amount+1) / 2);
+            int const ydis = (rand() % (amount+1)) - ((amount+1) / 2);
+
+            int const xnew = col + xdis;
+            int const ynew = row + ydis;
+
+            /* only set the displaced pixel if it's within the bounds
+               of the image
+            */
+            if (xnew >= 0 && xnew < cols && ynew >= 0 && ynew < rows) {
+                /* displacing a pixel is accomplished by swapping it
+                   with another pixel in its vicinity - so, first
+                   store other pixel's RGB
+                */
+                pP2 = &srcarray[ynew][xnew];
+                r1 = PPM_GETR(*pP2);
+                g1 = PPM_GETG(*pP2);
+                b1 = PPM_GETB(*pP2);
+                /* set second pixel to new value */
+                pP2 = &destarray[ynew][xnew];
+                PPM_ASSIGN(*pP2, PPM_GETR(*pP), PPM_GETG(*pP), PPM_GETB(*pP));
+                
+                /* now, set first pixel to (old) value of second */
+                pP2 = &destarray[row][col];
+                PPM_ASSIGN(*pP2, r1, g1, b1);
+            } else {
+                /* displaced pixel is out of bounds; leave the old
+                   pixel there
+                */
+                pP2 = &destarray[row][col];
+                PPM_ASSIGN(*pP2, PPM_GETR(*pP), PPM_GETG(*pP), PPM_GETB(*pP));
+            }
+            ++pP;
+        }
+    }
+
+    ppm_writeppm(stdout, destarray, cols, rows, maxval, 0);
+
+    pm_close(ifP);
+    ppm_freearray(srcarray, rows);
+    ppm_freearray(destarray, rows);
+
+    return 0;
+}
diff --git a/editor/specialty/ppmtv.c b/editor/specialty/ppmtv.c
new file mode 100644
index 00000000..da25102a
--- /dev/null
+++ b/editor/specialty/ppmtv.c
@@ -0,0 +1,105 @@
+
+/*********************************************************************/
+/* ppmtv -  make a 'look-alike ntsc' picture from a PPM file       */
+/* Frank Neumann, October 1993                                       */
+/* V1.1 16.11.1993                                                   */
+/*                                                                   */
+/* version history:                                                  */
+/* V1.0 12.10.1993    first version                                  */
+/* V1.1 16.11.1993    Rewritten to be NetPBM.programming conforming  */
+/*********************************************************************/
+
+#include "ppm.h"
+
+/**************************/
+/* start of main function */
+/**************************/
+int main(argc, argv)
+int argc;
+char *argv[];
+{
+	FILE* ifp;
+	int argn, rows, cols, format, i = 0, j = 0;
+	pixel *srcrow, *destrow;
+	pixel *pP = NULL, *pP2 = NULL;
+	pixval maxval;
+	double dimfactor;
+	long longfactor;
+	const char * const usage = "dimfactor [ppmfile]\n        dimfactor: 0.0 = total blackness, 1.0 = original picture\n";
+
+	/* parse in 'default' parameters */
+	ppm_init(&argc, argv);
+
+	argn = 1;
+
+	/* parse in dim factor */
+	if (argn == argc)
+		pm_usage(usage);
+	if (sscanf(argv[argn], "%lf", &dimfactor) != 1)
+		pm_usage(usage);
+	if (dimfactor < 0.0 || dimfactor > 1.0)
+		pm_error("dim factor must be in the range from 0.0 to 1.0 ");
+	++argn;
+
+	/* parse in filename (if present, stdin otherwise) */
+	if (argn != argc)
+	{
+		ifp = pm_openr(argv[argn]);
+		++argn;
+	}
+	else
+		ifp = stdin;
+
+	if (argn != argc)
+		pm_usage(usage);
+
+	/* read first data from file */
+	ppm_readppminit(ifp, &cols, &rows, &maxval, &format);
+
+	/* no error checking required here, ppmlib does it all for us */
+	srcrow = ppm_allocrow(cols);
+
+	longfactor = (long)(dimfactor * 65536);
+
+	/* allocate a row of pixel data for the new pixels */
+	destrow = ppm_allocrow(cols);
+
+	ppm_writeppminit(stdout, cols, rows, maxval, 0);
+
+	/** now do the ntsc'ing (actually very similar to ppmdim) **/
+	for (i = 0; i < rows; i++)
+	{
+		ppm_readppmrow(ifp, srcrow, cols, maxval, format);
+
+		pP = srcrow;
+		pP2 = destrow;
+
+		for (j = 0; j < cols; j++)
+		{
+			/* every alternating row is left in unchanged condition */
+			if (i & 1)
+			{
+				PPM_ASSIGN(*pP2, PPM_GETR(*pP), PPM_GETG(*pP), PPM_GETB(*pP));
+			}
+			/* and the other lines are dimmed to the specified factor */
+			else
+			{
+				PPM_ASSIGN(*pP2, (PPM_GETR(*pP) * longfactor) >> 16,
+								 (PPM_GETG(*pP) * longfactor) >> 16,
+								 (PPM_GETB(*pP) * longfactor) >> 16);
+			}
+			pP++;
+			pP2++;
+		}
+
+		/* write out one line of graphic data */
+		ppm_writeppmrow(stdout, destrow, cols, maxval, 0);
+	}
+
+	pm_close(ifp);
+	ppm_freerow(srcrow);
+	ppm_freerow(destrow);
+
+	exit(0);
+}
+