about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--GNUmakefile27
-rw-r--r--analyzer/pamfile.c14
-rw-r--r--common.mk7
-rw-r--r--converter/other/Makefile2
-rw-r--r--converter/other/cameratopam/Makefile4
-rw-r--r--converter/other/cameratopam/cameratopam.c2
-rw-r--r--converter/other/giftopnm.c214
-rw-r--r--converter/other/jbig/Makefile2
-rw-r--r--converter/other/jpeg2000/libjasper/base/jas_stream.c2
-rw-r--r--converter/other/pamtotiff.c10
-rw-r--r--converter/other/pamtowinicon.c12
-rw-r--r--converter/other/rletopnm.c152
-rw-r--r--converter/other/tifftopnm.c41
-rw-r--r--converter/other/winicontopam.c20
-rw-r--r--converter/other/yuy2topam.c266
-rw-r--r--converter/pgm/Makefile4
-rw-r--r--converter/pgm/pgmtosbig.c130
-rw-r--r--converter/pgm/pgmtost4.c104
-rw-r--r--converter/pgm/sbigtopgm.c304
-rw-r--r--converter/pgm/st4topgm.c260
-rw-r--r--doc/HISTORY73
-rw-r--r--doc/INSTALL58
-rw-r--r--editor/pamcut.c10
-rw-r--r--editor/pgmdeshadow.c2
-rw-r--r--editor/pnmgamma.c10
-rw-r--r--editor/pnmhisteq.c193
-rw-r--r--editor/specialty/pgmmorphconv.c520
-rw-r--r--lib/Makefile1
-rw-r--r--lib/colorname.c5
-rw-r--r--lib/libpam.c11
-rw-r--r--lib/libpamcolor.c4
-rw-r--r--lib/libpamd.c7
-rw-r--r--lib/libpammap.c7
-rw-r--r--lib/libpamn.c7
-rw-r--r--lib/libpamread.c5
-rw-r--r--lib/libpamwrite.c5
-rw-r--r--lib/libpbm1.c7
-rw-r--r--lib/libpbm3.c3
-rw-r--r--lib/libpbmfont.c7
-rw-r--r--lib/libpgm1.c5
-rw-r--r--lib/libpgm2.c4
-rw-r--r--lib/libpm.c2
-rw-r--r--lib/libpnm1.c4
-rw-r--r--lib/libpnm2.c4
-rw-r--r--lib/libppm1.c4
-rw-r--r--lib/libppm2.c4
-rw-r--r--lib/libppmcmap.c8
-rw-r--r--lib/libppmcolor.c6
-rw-r--r--lib/libppmd.c6
-rw-r--r--lib/libppmfloyd.c2
-rw-r--r--lib/libppmfuzzy.c4
-rw-r--r--lib/libsystem.c4
-rw-r--r--lib/pam.h3
-rw-r--r--lib/path.c4
-rw-r--r--lib/pmfileio.c143
-rw-r--r--lib/ppmdfont.c2
-rw-r--r--lib/util/io.c5
-rw-r--r--netpbm.c33
-rw-r--r--pm_config.in.h15
-rw-r--r--test/Test-Order2
-rw-r--r--test/all-in-place.ok4
-rwxr-xr-xtest/all-in-place.test6
-rw-r--r--test/sbig-roundtrip.ok1
-rwxr-xr-xtest/sbig-roundtrip.test12
-rw-r--r--test/st4-roundtrip.ok1
-rwxr-xr-xtest/st4-roundtrip.test17
-rw-r--r--version.mk4
67 files changed, 2160 insertions, 661 deletions
diff --git a/GNUmakefile b/GNUmakefile
index 4be3c7c8..1e933347 100644
--- a/GNUmakefile
+++ b/GNUmakefile
@@ -354,14 +354,31 @@ netpbm.o: mergetrylist
 install.merge: local.install.merge
 .PHONY: local.install.merge
 local.install.merge:
-	cd $(PKGDIR)/bin; $(SYMLINKEXE) netpbm pnmnoraw
-	cd $(PKGDIR)/bin; $(SYMLINKEXE) netpbm gemtopbm
-	cd $(PKGDIR)/bin; $(SYMLINKEXE) netpbm pnminterp
-	cd $(PKGDIR)/bin; $(SYMLINKEXE) netpbm pgmoil
-	cd $(PKGDIR)/bin; $(SYMLINKEXE) netpbm ppmtojpeg
 	cd $(PKGDIR)/bin; $(SYMLINKEXE) netpbm bmptoppm
+	cd $(PKGDIR)/bin; $(SYMLINKEXE) netpbm gemtopbm
+	cd $(PKGDIR)/bin; $(SYMLINKEXE) netpbm icontopbm
+	cd $(PKGDIR)/bin; $(SYMLINKEXE) netpbm pgmedge
 	cd $(PKGDIR)/bin; $(SYMLINKEXE) netpbm pgmnorm
+	cd $(PKGDIR)/bin; $(SYMLINKEXE) netpbm pgmoil
+	cd $(PKGDIR)/bin; $(SYMLINKEXE) netpbm pgmslice
+	cd $(PKGDIR)/bin; $(SYMLINKEXE) netpbm pngtopnm
+	cd $(PKGDIR)/bin; $(SYMLINKEXE) netpbm pnmarith
+	cd $(PKGDIR)/bin; $(SYMLINKEXE) netpbm pnmcomp
+	cd $(PKGDIR)/bin; $(SYMLINKEXE) netpbm pnmcut
+	cd $(PKGDIR)/bin; $(SYMLINKEXE) netpbm pnmdepth
+	cd $(PKGDIR)/bin; $(SYMLINKEXE) netpbm pnmenlarge
 	cd $(PKGDIR)/bin; $(SYMLINKEXE) netpbm pnmfile
+	cd $(PKGDIR)/bin; $(SYMLINKEXE) netpbm pnminterp
+	cd $(PKGDIR)/bin; $(SYMLINKEXE) netpbm pnmnoraw
+	cd $(PKGDIR)/bin; $(SYMLINKEXE) netpbm pnmscale
+	cd $(PKGDIR)/bin; $(SYMLINKEXE) netpbm pnmsplit
+	cd $(PKGDIR)/bin; $(SYMLINKEXE) netpbm pnmtofits
+	cd $(PKGDIR)/bin; $(SYMLINKEXE) netpbm pnmnoraw
+	cd $(PKGDIR)/bin; $(SYMLINKEXE) netpbm pnmtopnm
+	cd $(PKGDIR)/bin; $(SYMLINKEXE) netpbm ppmnorm
+	cd $(PKGDIR)/bin; $(SYMLINKEXE) netpbm ppmtojpeg
+	cd $(PKGDIR)/bin; $(SYMLINKEXE) netpbm pnmtotga
+	cd $(PKGDIR)/bin; $(SYMLINKEXE) netpbm pnmtouil
 
 ifneq ($(NETPBMLIBTYPE),unixstatic)
 install.lib: lib/install.lib
diff --git a/analyzer/pamfile.c b/analyzer/pamfile.c
index 9c5b2c33..bb55ecce 100644
--- a/analyzer/pamfile.c
+++ b/analyzer/pamfile.c
@@ -16,7 +16,7 @@
 #include "shhopt.h"
 #include "pam.h"
 
-struct cmdlineInfo {
+struct CmdlineInfo {
     /* All the information the user supplied in the command line,
        in a form easy for the program to use.
     */
@@ -30,8 +30,8 @@ struct cmdlineInfo {
 
 
 static void
-parseCommandLine(int argc, char ** argv,
-                 struct cmdlineInfo * const cmdlineP) {
+parseCommandLine(int argc, const char ** argv,
+                 struct CmdlineInfo * const cmdlineP) {
 /*----------------------------------------------------------------------------
    Note that the file spec array we return is stored in the storage that
    was passed to as as the argv array.
@@ -54,7 +54,7 @@ parseCommandLine(int argc, char ** argv,
     opt.short_allowed = FALSE; /* We have no short (old-fashioned) options */
     opt.allowNegNum   = FALSE; /* We have no parms that are negative numbers */
     
-    pm_optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+    pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
         /* Uses and sets argc, argv, and some of *cmdlineP and others */
 
     cmdlineP->inputFilespec = (const char **)&argv[1];
@@ -221,11 +221,11 @@ describeOneFile(const char * const name,
 
 
 int
-main(int argc, char *argv[]) {
+main(int argc, const char *argv[]) {
 
-    struct cmdlineInfo cmdline;
+    struct CmdlineInfo cmdline;
 
-    pnm_init(&argc, argv);
+    pm_proginit(&argc, argv);
 
     parseCommandLine(argc, argv, &cmdline);
 
diff --git a/common.mk b/common.mk
index a4fed0f0..48c63c69 100644
--- a/common.mk
+++ b/common.mk
@@ -86,8 +86,13 @@ include $(SRCDIR)/version.mk
 # importinc/pam.h (as it did originally) is that it lives on a user's system
 # as <netpbm/pam.h>, and therefore all _exported_ header files do say
 # "<netpbm/pam.h>.
+ifneq ($(ALL_INTERNAL_HEADER_FILES_ARE_QUALIFIED),Y)
+  LEGACY_NETPBM_INCLUDE = -Iimportinc/netpbm
+else
+  LEGACY_NETPBM_INCLUDE =
+endif
 
-NETPBM_INCLUDES := -Iimportinc -Iimportinc/netpbm -I$(SRCDIR)/$(SUBDIR)
+NETPBM_INCLUDES := -Iimportinc $(LEGACY_NETPBM_INCLUDE) -I$(SRCDIR)/$(SUBDIR)
 
 # -I. is needed when builddir != srcdir
 INCLUDES = -I. $(COMP_INCLUDES) $(NETPBM_INCLUDES) $(EXTERN_INCLUDES)
diff --git a/converter/other/Makefile b/converter/other/Makefile
index db185faf..e76373ef 100644
--- a/converter/other/Makefile
+++ b/converter/other/Makefile
@@ -124,7 +124,7 @@ PORTBINARIES =  avstopam bmptopnm fitstopnm \
 		pnmtopclxl pnmtorast \
 		pnmtosgi pnmtosir pamtotga pnmtoxwd \
 		rasttopnm rlatopam sgitopnm sirtopnm srftopam sunicontopnm \
-		winicontopam xwdtopnm zeisstopnm
+		winicontopam xwdtopnm yuy2topam zeisstopnm
 
 ifneq ($(DONT_HAVE_PROCESS_MGMT),Y)
   PORTBINARIES += pstopnm pnmtops
diff --git a/converter/other/cameratopam/Makefile b/converter/other/cameratopam/Makefile
index 11ca4e1d..d6207aea 100644
--- a/converter/other/cameratopam/Makefile
+++ b/converter/other/cameratopam/Makefile
@@ -26,11 +26,11 @@ OBJECTS = cameratopam.o $(ADDL_OBJECTS)
 
 camera.o camera.o2: CFLAGS_TARGET = $(HAVE_JPEG_DEFINE)
 
-MERGE_OBJECTS =
+MERGE_OBJECTS = cameratopam.o2 $(ADDL_OBJECTS)
 
 PORTBINARIES = cameratopam
 BINARIES = $(PORTBINARIES)
-MERGEBINARIES = 
+MERGEBINARIES = cameratopam
 SCRIPTS = 
 
 include $(SRCDIR)/common.mk
diff --git a/converter/other/cameratopam/cameratopam.c b/converter/other/cameratopam/cameratopam.c
index 40d8207f..ec33dd31 100644
--- a/converter/other/cameratopam/cameratopam.c
+++ b/converter/other/cameratopam/cameratopam.c
@@ -26,7 +26,7 @@
 #include <stdlib.h>
 #include <string.h>
 
-#ifdef __CYGWIN__
+#ifdef HAVE_IO_H
   #include <io.h>
 #endif
 #if !MSVCRT
diff --git a/converter/other/giftopnm.c b/converter/other/giftopnm.c
index 239e5100..2e6ae1d9 100644
--- a/converter/other/giftopnm.c
+++ b/converter/other/giftopnm.c
@@ -39,10 +39,6 @@
 
 #define MAX_LZW_BITS  12
 
-#define INTERLACE      0x40
-#define LOCALCOLORMAP  0x80
-#define BitSet(byte, bit)      (((byte) & (bit)) == (bit))
-
 #if !defined(BYTE_ORDER) || !defined(LITTLE_ENDIAN)
   /* make sure (BYTE_ORDER == LITTLE_ENDIAN) is FALSE */ 
   #define BYTE_ORDER    0
@@ -90,7 +86,7 @@ readFile(FILE *          const ifP,
 
 
 
-struct cmdlineInfo {
+struct CmdlineInfo {
     /* All the information the user supplied in the command line,
        in a form easy for the program to use.
     */
@@ -111,7 +107,7 @@ struct cmdlineInfo {
 
 static void
 parseCommandLine(int argc, char ** argv,
-                 struct cmdlineInfo * const cmdlineP) {
+                 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.
@@ -240,13 +236,21 @@ typedef struct {
 -----------------------------------------------------------------------*/
 
 
-struct gifScreen {
-    unsigned int    Width;
-    unsigned int    Height;
-    GifColorMap     ColorMap;
-    unsigned int    ColorResolution;
-    unsigned int    Background;
-    unsigned int    AspectRatio;
+struct GifScreen {
+    unsigned int    width;
+    unsigned int    height;
+    bool            hasGlobalColorMap;
+        /* The stream has a global color map, to wit 'colorMap'.
+           (If the stream doesn't have a global color map, the individual
+           images must each have a local color map)
+        */
+    GifColorMap     colorMap;
+        /* The global color map for the stream.  Meaningful only if
+           'hasGlobalColorMap' is true.
+        */
+    unsigned int    colorResolution;
+    unsigned int    background;
+    unsigned int    aspectRatio;
         /* Aspect ratio of each pixel, times 64, minus 15.  (i.e. 1 => 1:4).
            But Zero means 1:1.
         */
@@ -260,7 +264,7 @@ struct gifScreen {
         */
 };
 
-struct gif89 {
+struct Gif89 {
     bool         haveTransColor;
         /* The GIF specifies a transparent background color */
     unsigned int transparentIndex;
@@ -278,7 +282,7 @@ struct gif89 {
 };
 
 static void
-initGif89(struct gif89 * const gif89P) {
+initGif89(struct Gif89 * const gif89P) {
     gif89P->haveTransColor = false;
     gif89P->haveDelayTime  = false;
     gif89P->haveInputFlag  = false;
@@ -297,7 +301,16 @@ readColorMap(FILE *        const ifP,
              GifColorMap * const cmapP,
              bool *        const hasGrayP,
              bool *        const hasColorP) {
+/*----------------------------------------------------------------------------
+   Read a color map from a GIF stream, where the stream is on *ifP,
+   which is positioned to a color map, which is 'cmapSize' bytes long.
 
+   Return as *cmapP that color map.
+
+   Furthermore, analyze that color map and return *hasGrayP == true iff it
+   contains any gray (black and white don't count) and *hasColorP == true iff
+   it contains anything that is not gray or black or white.
+-----------------------------------------------------------------------------*/
     unsigned int  i;
     unsigned char rgb[3];
 
@@ -494,7 +507,7 @@ LM_to_uint(unsigned char const a,
 
 static void 
 doGraphicControlExtension(FILE *         const ifP,
-                          struct gif89 * const gif89P) {
+                          struct Gif89 * const gif89P) {
 
     bool eof;
     unsigned int length;
@@ -531,7 +544,7 @@ doGraphicControlExtension(FILE *         const ifP,
 static void
 doExtension(FILE *         const ifP,
             unsigned char  const label,
-            struct gif89 * const gif89P) {
+            struct Gif89 * const gif89P) {
 
     const char * str;
     
@@ -566,7 +579,7 @@ doExtension(FILE *         const ifP,
 
 
 
-struct getCodeState {
+struct GetCodeState {
     unsigned char buf[280];
         /* This is the buffer through which we read the data from the 
            stream.  We must buffer it because we have to read whole data
@@ -592,7 +605,7 @@ struct getCodeState {
 
 static void
 getAnotherBlock(FILE *                const ifP, 
-                struct getCodeState * const gsP,
+                struct GetCodeState * const gsP,
                 const char **         const errorP) {
 
     unsigned int count;
@@ -633,10 +646,10 @@ getAnotherBlock(FILE *                const ifP,
 
 
 
-static struct getCodeState getCodeState;
+static struct GetCodeState getCodeState;
 
 static void
-getCode_init(struct getCodeState * const getCodeStateP) {
+getCode_init(struct GetCodeState * const getCodeStateP) {
     
     /* Fake a previous data block */
     getCodeStateP->buf[0] = 0;
@@ -688,7 +701,7 @@ bitsOfLeBuffer(const unsigned char * const buf,
 
 
 static void
-getCode_get(struct getCodeState * const gsP,
+getCode_get(struct GetCodeState * const gsP,
             FILE *                const ifP, 
             int                   const codeSize,
             bool *                const eofP,
@@ -752,7 +765,7 @@ getCode_get(struct getCodeState * const gsP,
 
 
 
-struct stack {
+struct Stack {
     /* Stack grows from low addresses to high addresses */
     unsigned char * stack;  /* malloc'ed array */
     unsigned char * sp;     /* stack pointer */
@@ -762,7 +775,7 @@ struct stack {
 
 
 static void 
-initStack(struct stack * const stackP, unsigned int const size) {
+initStack(struct Stack * const stackP, unsigned int const size) {
 
     MALLOCARRAY(stackP->stack, size);
     if (stackP->stack == NULL)
@@ -774,7 +787,7 @@ initStack(struct stack * const stackP, unsigned int const size) {
 
 
 static void
-pushStack(struct stack * const stackP, unsigned char const value) {
+pushStack(struct Stack * const stackP, unsigned char const value) {
 
     if (stackP->sp >= stackP->top)
         pm_error("stack overflow");
@@ -785,14 +798,14 @@ pushStack(struct stack * const stackP, unsigned char const value) {
 
 
 static bool
-stackIsEmpty(const struct stack * const stackP) {
+stackIsEmpty(const struct Stack * const stackP) {
     return stackP->sp == stackP->stack;
 }
 
 
 
 static unsigned char
-popStack(struct stack * const stackP) {
+popStack(struct Stack * const stackP) {
 
     if (stackP->sp <= stackP->stack)
         pm_error("stack underflow");
@@ -803,7 +816,7 @@ popStack(struct stack * const stackP) {
 
 
 static void
-termStack(struct stack * const stackP) {
+termStack(struct Stack * const stackP) {
     free(stackP->stack);
     stackP->stack = NULL;
 }
@@ -851,8 +864,8 @@ termStack(struct stack * const stackP) {
 
 static int const maxLzwCodeCt = (1<<MAX_LZW_BITS);
 
-struct decompressor {
-    struct stack stack;
+struct Decompressor {
+    struct Stack stack;
     bool fresh;
         /* The stream is right after a clear code or at the very beginning */
     unsigned int codeSize;
@@ -891,7 +904,7 @@ struct decompressor {
 
 
 static void
-resetDecompressor(struct decompressor * const decompP) {
+resetDecompressor(struct Decompressor * const decompP) {
 
     decompP->codeSize      = decompP->initCodeSize+1;
     decompP->maxCodeCt     = 1 << decompP->codeSize;
@@ -927,7 +940,7 @@ validateTransparentIndex(unsigned int const transparentIndex,
 
 
 static void
-lzwInit(struct decompressor * const decompP, 
+lzwInit(struct Decompressor * const decompP, 
         FILE *                const ifP,
         int                   const initCodeSize,
         unsigned int          const cmapSize,
@@ -988,7 +1001,7 @@ lzwInit(struct decompressor * const decompP,
 
 
 static void
-lzwAdjustForPBM(struct decompressor * const decompP,
+lzwAdjustForPBM(struct Decompressor * const decompP,
                 GifColorMap           const cmap) {
 /*----------------------------------------------------------------------------
   In the PBM case we use the table index value directly instead of looking up
@@ -1006,7 +1019,7 @@ lzwAdjustForPBM(struct decompressor * const decompP,
 
 
 static void
-lzwTerm(struct decompressor * const decompP) {
+lzwTerm(struct Decompressor * const decompP) {
 
     termStack(&decompP->stack);
 }
@@ -1014,7 +1027,7 @@ lzwTerm(struct decompressor * const decompP) {
 
 
 static void
-pushWholeStringOnStack(struct decompressor * const decompP,
+pushWholeStringOnStack(struct Decompressor * const decompP,
                        unsigned int          const code0) {
 /*----------------------------------------------------------------------------
   Get the whole string that compression code 'code0' represents and push
@@ -1038,7 +1051,7 @@ pushWholeStringOnStack(struct decompressor * const decompP,
 
 
 static void
-expandCodeOntoStack(struct decompressor * const decompP,
+expandCodeOntoStack(struct Decompressor * const decompP,
                     unsigned int          const incode,
                     const char **         const errorP) {
 /*----------------------------------------------------------------------------
@@ -1113,8 +1126,8 @@ expandCodeOntoStack(struct decompressor * const decompP,
 
 
 static void
-lzwReadByteFresh(struct getCodeState * const getCodeStateP,
-                 struct decompressor * const decompP,
+lzwReadByteFresh(struct GetCodeState * const getCodeStateP,
+                 struct Decompressor * const decompP,
                  bool *                const endOfImageP,
                  unsigned char *       const dataReadP,
                  const char **         const errorP) {
@@ -1171,7 +1184,7 @@ lzwReadByteFresh(struct getCodeState * const getCodeStateP,
 
 
 static void
-lzwReadByte(struct decompressor * const decompP,
+lzwReadByte(struct Decompressor * const decompP,
             unsigned char *       const dataReadP,
             bool *                const endOfImageP,
             const char **         const errorP) {
@@ -1398,7 +1411,7 @@ pnmFormat(bool const hasGray,
 
 
 static void
-makePnmRow(struct decompressor * const decompP,
+makePnmRow(struct Decompressor * const decompP,
            unsigned int          const cols,
            unsigned int          const rows,
            bool                  const fillWithZero,
@@ -1447,7 +1460,7 @@ makePnmRow(struct decompressor * const decompP,
 
 
 static void
-convertRaster(struct decompressor * const decompP,
+convertRaster(struct Decompressor * const decompP,
               unsigned int          const cols,
               unsigned int          const rows,
               GifColorMap           const cmap, 
@@ -1550,7 +1563,7 @@ convertRaster(struct decompressor * const decompP,
 
 
 static void
-skipExtraneousData(struct decompressor * const decompP) {
+skipExtraneousData(struct Decompressor * const decompP) {
 
     unsigned char byteRead;
     bool endOfImage;
@@ -1638,7 +1651,7 @@ readImageData(FILE *       const ifP,
               bool         const tolerateBadInput) {
 
     unsigned char lzwMinCodeSize;      
-    struct decompressor decomp;
+    struct Decompressor decomp;
     const char * error;
 
     readFile(ifP, &lzwMinCodeSize, sizeof(lzwMinCodeSize), &error);
@@ -1697,12 +1710,14 @@ warnUserNotSquare(unsigned int const aspectRatio) {
 
 static void
 readGifHeader(FILE *             const gifFileP,
-              struct gifScreen * const gifScreenP) {
+              struct GifScreen * const gifScreenP) {
 /*----------------------------------------------------------------------------
    Read the GIF stream header off the file *gifFileP, which is present
    positioned to the beginning of a GIF stream.  Return the info from it
    as *gifScreenP.
 -----------------------------------------------------------------------------*/
+#define GLOBALCOLORMAP  0x80
+
     unsigned char buf[16];
     char version[4];
     unsigned int cmapSize;
@@ -1729,44 +1744,46 @@ readGifHeader(FILE *             const gifFileP,
     if (error)
         pm_error("Failed to read screen descriptor.  %s", error);
     
-    gifScreenP->Width           = LM_to_uint(buf[0],buf[1]);
-    gifScreenP->Height          = LM_to_uint(buf[2],buf[3]);
+    gifScreenP->width           = LM_to_uint(buf[0],buf[1]);
+    gifScreenP->height          = LM_to_uint(buf[2],buf[3]);
     cmapSize                    = 1 << ((buf[4] & 0x07) + 1);
-    gifScreenP->ColorResolution = (buf[4] & 0x70 >> 3) + 1;
-    gifScreenP->Background      = buf[5];
-    gifScreenP->AspectRatio     = buf[6];
+    gifScreenP->colorResolution = (buf[4] & 0x70 >> 3) + 1;
+    gifScreenP->background      = buf[5];
+    gifScreenP->aspectRatio     = buf[6];
 
     if (verbose) {
-        pm_message("GIF Width = %d GIF Height = %d "
-                   "Pixel aspect ratio = %d (%f:1)",
-                   gifScreenP->Width, gifScreenP->Height, 
-                   gifScreenP->AspectRatio, 
-                   gifScreenP->AspectRatio == 0 ? 
-                   1 : (gifScreenP->AspectRatio + 15) / 64.0);
-        pm_message("Colors = %d   Color Resolution = %d",
-                   cmapSize, gifScreenP->ColorResolution);
+        pm_message("GIF Width = %u GIF Height = %u "
+                   "Pixel aspect ratio = %u (%f:1)",
+                   gifScreenP->width, gifScreenP->height, 
+                   gifScreenP->aspectRatio, 
+                   gifScreenP->aspectRatio == 0 ? 
+                   1 : (gifScreenP->aspectRatio + 15) / 64.0);
+        pm_message("Global color count = %u   Color Resolution = %u",
+                   cmapSize, gifScreenP->colorResolution);
     }           
-    if (BitSet(buf[4], LOCALCOLORMAP)) {    /* Global Colormap */
-        readColorMap(gifFileP, cmapSize, &gifScreenP->ColorMap,
+    if (buf[4] & GLOBALCOLORMAP) {
+        gifScreenP->hasGlobalColorMap = true;
+        readColorMap(gifFileP, cmapSize, &gifScreenP->colorMap,
                      &gifScreenP->hasGray, &gifScreenP->hasColor);
         if (verbose) {
-            pm_message("Color map %s grays, %s colors", 
+            pm_message("Global color map %s grays, %s colors", 
                        gifScreenP->hasGray ? "contains" : "doesn't contain",
                        gifScreenP->hasColor ? "contains" : "doesn't contain");
         }
-    } else {
-        gifScreenP->ColorMap.size = 0;
-    }
+    } else
+        gifScreenP->hasGlobalColorMap = false;
     
-    if (gifScreenP->AspectRatio != 0 && gifScreenP->AspectRatio != 49)
-        warnUserNotSquare(gifScreenP->AspectRatio);
+    if (gifScreenP->aspectRatio != 0 && gifScreenP->aspectRatio != 49)
+        warnUserNotSquare(gifScreenP->aspectRatio);
+
+#undef GLOBALCOLORMAP
 }
 
 
 
 static void
 readExtensions(FILE*          const ifP, 
-               struct gif89 * const gif89P,
+               struct Gif89 * const gif89P,
                bool *         const eodP,
                const char **  const errorP) {
 /*----------------------------------------------------------------------------
@@ -1832,7 +1849,13 @@ struct GifImageHeader {
 /*----------------------------------------------------------------------------
    Information in the header (first 9 bytes) of a GIF image.
 -----------------------------------------------------------------------------*/
-    bool useGlobalColormap;
+    bool hasLocalColormap;
+        /* The image has its own color map.  Its size is 'localColorMapSize' */
+        /* (If an image does not have its own color map, the image uses the 
+           global color map for the GIF stream)
+        */
+    unsigned int localColorMapSize;
+        /* Meaningful only if 'hasLocalColormap' is true. */
 
     /* Position of the image (max 65535) */
     unsigned int lpos;
@@ -1841,7 +1864,7 @@ struct GifImageHeader {
     /* Dimensions of the image (max 65535) */
     unsigned int cols;
     unsigned int rows;
-    unsigned int localColorMapSize;
+
     bool interlaced;
 };
 
@@ -1858,11 +1881,11 @@ reportImageHeader(struct GifImageHeader const imageHeader) {
         pm_message("  Image left position: %u top position: %u",
                    imageHeader.lpos, imageHeader.tpos);
     
-    if (imageHeader.useGlobalColormap)
-        pm_message("  Uses global colormap");
-    else
+    if (imageHeader.hasLocalColormap)
         pm_message("  Uses local colormap of %u colors",
                    imageHeader.localColorMapSize);
+    else
+        pm_message("  Uses global colormap");
 }
 
 
@@ -1871,6 +1894,9 @@ static void
 readImageHeader(FILE *                  const ifP,
                 struct GifImageHeader * const imageHeaderP) {
 
+#define LOCALCOLORMAP  0x80
+#define INTERLACE      0x40
+
     unsigned char buf[16];
     const char * error;
 
@@ -1878,34 +1904,37 @@ readImageHeader(FILE *                  const ifP,
     if (error)
         pm_error("couldn't read left/top/width/height.  %s", error);
 
-    imageHeaderP->useGlobalColormap = ! BitSet(buf[8], LOCALCOLORMAP);
+    imageHeaderP->hasLocalColormap  = !!(buf[8] & LOCALCOLORMAP);
     imageHeaderP->localColorMapSize = 1u << ((buf[8] & 0x07) + 1);
     imageHeaderP->lpos              = LM_to_uint(buf[0], buf[1]);
     imageHeaderP->tpos              = LM_to_uint(buf[2], buf[3]);
     imageHeaderP->cols              = LM_to_uint(buf[4], buf[5]);
     imageHeaderP->rows              = LM_to_uint(buf[6], buf[7]);
-    imageHeaderP->interlaced        = !!BitSet(buf[8], INTERLACE);
+    imageHeaderP->interlaced        = !!(buf[8] & INTERLACE);
 
     if (verbose)
         reportImageHeader(*imageHeaderP);
+
+#undef INTERLACE
+#undef LOCALCOLORMAP
 }
 
 
 
 static void
 validateWithinGlobalScreen(struct GifImageHeader const imageHeader,
-                           struct gifScreen      const gifScreen) {
+                           struct GifScreen      const gifScreen) {
 
     unsigned long int const rpos = imageHeader.lpos + imageHeader.cols;
     unsigned long int const bpos = imageHeader.tpos + imageHeader.rows; 
 
-    if (rpos > gifScreen.Width)
+    if (rpos > gifScreen.width)
         pm_error("Image right end (%lu) is outside global screen: %u x %u",
-                 rpos, gifScreen.Width, gifScreen.Height);
-    if (bpos > gifScreen.Height)
+                 rpos, gifScreen.width, gifScreen.height);
+    if (bpos > gifScreen.height)
         pm_error("Image bottom end (%lu) is outside global screen: "
                  "%u x %u",
-                 bpos, gifScreen.Width, gifScreen.Height);
+                 bpos, gifScreen.width, gifScreen.height);
 }
 
 
@@ -1930,8 +1959,8 @@ convertImage(FILE *           const ifP,
              bool             const skipIt, 
              FILE *           const imageoutFileP, 
              FILE *           const alphafileP, 
-             struct gifScreen const gifScreen,
-             struct gif89     const gif89,
+             struct GifScreen const gifScreen,
+             struct Gif89     const gif89,
              bool             const tolerateBadInput) {
 /*----------------------------------------------------------------------------
    Read a single GIF image from the current position of file 'ifP'.
@@ -1949,19 +1978,18 @@ convertImage(FILE *           const ifP,
 
     validateWithinGlobalScreen(imageHeader, gifScreen);
 
-    if (imageHeader.useGlobalColormap) {
-        if (gifScreen.ColorMap.size == 0) {
-            pm_error("Invalid GIF: "
-                     "Image has no local color map and stream has no global "
-                     "color map either.");
-        }
-        currentColorMapP = &gifScreen.ColorMap;
-        hasGray  = gifScreen.hasGray;
-        hasColor = gifScreen.hasColor;
-    } else {
+    if (imageHeader.hasLocalColormap) {
         readColorMap(ifP, imageHeader.localColorMapSize, &localColorMap, 
                      &hasGray, &hasColor);
         currentColorMapP = &localColorMap;
+    } else if (gifScreen.hasGlobalColorMap) {
+        currentColorMapP = &gifScreen.colorMap;
+        hasGray  = gifScreen.hasGray;
+        hasColor = gifScreen.hasColor;
+    } else {
+        pm_error("Invalid GIF: "
+                 "Image has no local color map and stream has no global "
+                 "color map either.");
     }
 
     if (!skipIt) {
@@ -2027,8 +2055,8 @@ convertImages(FILE *       const ifP,
         /* Sequence within GIF stream of image we are currently processing.
            First is 0.
         */
-    struct gifScreen gifScreen;
-    struct gif89 gif89;
+    struct GifScreen gifScreen;
+    struct Gif89 gif89;
     bool eod;
         /* We've read through the GIF terminator character */
 
@@ -2072,7 +2100,7 @@ convertImages(FILE *       const ifP,
 int
 main(int argc, char **argv) {
 
-    struct cmdlineInfo cmdline;
+    struct CmdlineInfo cmdline;
     FILE * ifP;
     FILE * alphaFileP;
     FILE * imageOutFileP;
diff --git a/converter/other/jbig/Makefile b/converter/other/jbig/Makefile
index 0625edd3..946cb732 100644
--- a/converter/other/jbig/Makefile
+++ b/converter/other/jbig/Makefile
@@ -29,6 +29,8 @@ endif
 
 BINARIES = $(PORTBINARIES)
 
+MERGEBINARIES = $(BINARIES)
+
 SCRIPTS =
 
 ifeq ($(JBIGLIB),$(INTERNAL_JBIGLIB))
diff --git a/converter/other/jpeg2000/libjasper/base/jas_stream.c b/converter/other/jpeg2000/libjasper/base/jas_stream.c
index 7c6050fb..16c948eb 100644
--- a/converter/other/jpeg2000/libjasper/base/jas_stream.c
+++ b/converter/other/jpeg2000/libjasper/base/jas_stream.c
@@ -127,7 +127,7 @@
 #if defined(HAVE_UNISTD_H)
 #include <unistd.h>
 #endif
-#if MSVCRT
+#if HAVE_IO_H
 #include <io.h>
 #endif
 
diff --git a/converter/other/pamtotiff.c b/converter/other/pamtotiff.c
index f2cc0e2b..7b645b23 100644
--- a/converter/other/pamtotiff.c
+++ b/converter/other/pamtotiff.c
@@ -960,6 +960,12 @@ copyBufferToStdout(int const tmpfileFd) {
             fwrite(buffer, 1, bytesReadCt, stdout);
     }
 
+    /* POSIX lets us create a FILE from an existing file descriptor, but
+       does not provide a way to destroy the FILE and keep the file
+       descriptor.  The following fclose() closes the file.  Caller
+       must not access the file again, and if he attempts to close it,
+       must ignore the failure of close
+    */
     fclose(tmpfileP);
 }
 
@@ -975,6 +981,10 @@ destroyTiffGenerator(WriteMethod const writeMethod,
     if (writeMethod == TMPFILE)
         copyBufferToStdout(ofd);
 
+    /* If we copied the buffer above, the buffer file is already closed
+       (copyBufferToStdout closes it), TIFFClose appears to tolerate that -
+       all it does is a close() and doesn't mind that it fails.
+    */
     TIFFClose(tifP);
 }
 
diff --git a/converter/other/pamtowinicon.c b/converter/other/pamtowinicon.c
index c67267e4..7e2c9e86 100644
--- a/converter/other/pamtowinicon.c
+++ b/converter/other/pamtowinicon.c
@@ -36,12 +36,12 @@ parseCommandLine(int argc, const char **argv,
 
     option_def_index = 0;
 
-    OPTENT3 (0, "verbose",         OPT_FLAG, NULL,
-             &cmdlineP->verbose,         0);
-    OPTENT3 (0, "pngthreshold",    OPT_UINT, &cmdlineP->pngthreshold,
-             &pngthresholdSpec,          0);
-    OPTENT3 (0, "truetransparent", OPT_FLAG, NULL,
-             &cmdlineP->truetransparent, 0);
+    OPTENT3(0, "verbose",         OPT_FLAG, NULL,
+            &cmdlineP->verbose,         0);
+    OPTENT3(0, "pngthreshold",    OPT_UINT, &cmdlineP->pngthreshold,
+            &pngthresholdSpec,          0);
+    OPTENT3(0, "truetransparent", OPT_FLAG, NULL,
+            &cmdlineP->truetransparent, 0);
 
     opt3.opt_table     = option_def;
     opt3.short_allowed = false;
diff --git a/converter/other/rletopnm.c b/converter/other/rletopnm.c
index 71f4e284..99959141 100644
--- a/converter/other/rletopnm.c
+++ b/converter/other/rletopnm.c
@@ -61,19 +61,19 @@
 /*
  * Utah type declarations.
  */
-rle_hdr     hdr;
-rle_map     *colormap;
+static rle_hdr hdr;
+static rle_map * colormap;
 
 
-struct cmdlineInfo {
+struct CmdlineInfo {
     /* All the information the user supplied in the command line,
        in a form easy for the program to use.
     */
-    char *input_filename;
+    char *       inputFilename;
     unsigned int headerdump;
     unsigned int verbose;
-    char *alpha_filename;
-    bool alpha_stdout;
+    char *       alphaout;
+    bool         alphaStdout;
 };
 
 
@@ -81,15 +81,14 @@ struct cmdlineInfo {
 /*
  * Other declarations.
  */
-int     visual, maplen;
-int     width, height;
-
+static int visual, maplen;
+static int width, height;
 
 
 
 static void
 parseCommandLine(int argc, char ** argv,
-                   struct cmdlineInfo * const cmdlineP) {
+                 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
@@ -110,7 +109,7 @@ parseCommandLine(int argc, char ** argv,
     OPTENT3('v', "verbose",    OPT_FLAG,   
             NULL,                      &cmdlineP->verbose,        0);
     OPTENT3(0,   "alphaout",   OPT_STRING, 
-            &cmdlineP->alpha_filename, &alphaoutSpec,             0);
+            &cmdlineP->alphaout, &alphaoutSpec,                   0);
 
     opt.opt_table = option_def;
     opt.short_allowed = TRUE;  /* We have short (old-fashioned) options */
@@ -120,24 +119,24 @@ parseCommandLine(int argc, char ** argv,
         /* Uses and sets argc, argv, and all of *cmdlineP. */
 
     if (!alphaoutSpec)
-        cmdlineP->alpha_filename = NULL;
+        cmdlineP->alphaout = NULL;
 
     if (argc - 1 == 0)
-        cmdlineP->input_filename = NULL;  /* he wants stdin */
+        cmdlineP->inputFilename = NULL;  /* he wants stdin */
     else if (argc - 1 == 1) {
         if (streq(argv[1], "-"))
-            cmdlineP->input_filename = NULL;  /* he wants stdin */
+            cmdlineP->inputFilename = NULL;  /* he wants stdin */
         else 
-            cmdlineP->input_filename = strdup(argv[1]);
+            cmdlineP->inputFilename = strdup(argv[1]);
     } else 
         pm_error("Too many arguments.  The only argument accepted "
                  "is the input file specification");
 
-    if (cmdlineP->alpha_filename && 
-        streq(cmdlineP->alpha_filename, "-"))
-        cmdlineP->alpha_stdout = TRUE;
+    if (cmdlineP->alphaout && 
+        streq(cmdlineP->alphaout, "-"))
+        cmdlineP->alphaStdout = TRUE;
     else 
-        cmdlineP->alpha_stdout = FALSE;
+        cmdlineP->alphaStdout = FALSE;
 }
 
 
@@ -167,15 +166,14 @@ reportRleGetSetupError(int const rleGetSetupRc) {
 }
 
 
-/*-----------------------------------------------------------------------------
- *                                                         Read the rle header.
- */
+
 static void 
-read_rle_header(FILE * const ifp,
-                bool   const headerDump) {
+readRleHeader(FILE * const ifP,
+              bool   const headerDump) {
+
     int rc;
     int  i;
-    hdr.rle_file = ifp;
+    hdr.rle_file = ifP;
     rc = rle_get_setup(&hdr);
     if (rc != 0)
         reportRleGetSetupError(rc);
@@ -254,12 +252,12 @@ read_rle_header(FILE * const ifp,
         for (i = 0; hdr.comments[i] != NULL; i++)
             HMSG("%s", hdr.comments[i]);
 }
-/*-----------------------------------------------------------------------------
- *                                                    Write the ppm image data.
- */
+
+
+
 static void 
-write_ppm_data(FILE * const imageout_file,
-               FILE * const alpha_file) {
+writePpmRaster(FILE * const imageoutFileP,
+               FILE * const alphaFileP) {
 
     rle_pixel ***scanlines, **scanline;
     pixval r, g, b;
@@ -341,17 +339,17 @@ write_ppm_data(FILE * const imageout_file,
         /*
          * Write the scan line.
          */
-        if (imageout_file ) 
-            ppm_writeppmrow(imageout_file, pixelrow, width, RLE_MAXVAL, 0);
-        if (alpha_file)
-            pgm_writepgmrow(alpha_file, alpharow, width, RLE_MAXVAL, 0);
+        if (imageoutFileP) 
+            ppm_writeppmrow(imageoutFileP, pixelrow, width, RLE_MAXVAL, 0);
+        if (alphaFileP)
+            pgm_writepgmrow(alphaFileP, alpharow, width, RLE_MAXVAL, 0);
 
     } /* end of for scan = 0 to height */
 
     /* Free scanline memory. */
-    for ( scan = 0; scan < height; scan++ )
-        rle_row_free( &hdr, scanlines[scan] );
-    free( scanlines );
+    for (scan = 0; scan < height; ++scan)
+        rle_row_free(&hdr, scanlines[scan]);
+    free (scanlines);
     ppm_freerow(pixelrow);
     pgm_freerow(alpharow);
 }
@@ -359,8 +357,8 @@ write_ppm_data(FILE * const imageout_file,
 
 
 static void 
-write_pgm_data(FILE * const imageout_file,
-               FILE * const alpha_file) {
+writePgmRaster(FILE * const imageoutFileP,
+               FILE * const alphaFileP) {
 /*----------------------------------------------------------------------------
    Write the PGM image data
 -----------------------------------------------------------------------------*/
@@ -397,10 +395,10 @@ write_pgm_data(FILE * const imageout_file,
             else
                 alpharow[x] = 0;
         }
-        if (imageout_file) 
-            pgm_writepgmrow(imageout_file, pixelrow, width, RLE_MAXVAL, 0);
-        if (alpha_file)
-            pgm_writepgmrow(alpha_file, alpharow, width, RLE_MAXVAL, 0);
+        if (imageoutFileP) 
+            pgm_writepgmrow(imageoutFileP, pixelrow, width, RLE_MAXVAL, 0);
+        if (alphaFileP)
+            pgm_writepgmrow(alphaFileP, alpharow, width, RLE_MAXVAL, 0);
 
         }   /* end of for scan = 0 to height */
 
@@ -414,59 +412,59 @@ write_pgm_data(FILE * const imageout_file,
 
 
 
-/*-----------------------------------------------------------------------------
- *                               Convert a Utah rle file to a pbmplus pnm file.
- */
 int
 main(int argc, char ** argv) {
 
-    struct cmdlineInfo cmdline;
-    FILE        *ifp;
-    FILE *imageout_file, *alpha_file;
-    char *fname = NULL;
+    struct CmdlineInfo cmdline;
+    FILE * ifP;
+    FILE * imageoutFileP;
+    FILE * alphaFileP;
+    char * fname;
 
     pnm_init( &argc, argv );
 
     parseCommandLine(argc, argv, &cmdline);
 
-    if ( cmdline.input_filename != NULL ) 
-        ifp = pm_openr( cmdline.input_filename );
+    fname = NULL;  /* initial value */
+
+    if (cmdline.inputFilename != NULL ) 
+        ifP = pm_openr(cmdline.inputFilename);
     else
-        ifp = stdin;
+        ifP = stdin;
 
-    if (cmdline.alpha_stdout)
-        alpha_file = stdout;
-    else if (cmdline.alpha_filename == NULL) 
-        alpha_file = NULL;
+    if (cmdline.alphaStdout)
+        alphaFileP = stdout;
+    else if (cmdline.alphaout == NULL) 
+        alphaFileP = NULL;
     else {
-        alpha_file = pm_openw(cmdline.alpha_filename);
+        alphaFileP = pm_openw(cmdline.alphaout);
     }
 
-    if (cmdline.alpha_stdout) 
-        imageout_file = NULL;
+    if (cmdline.alphaStdout) 
+        imageoutFileP = NULL;
     else
-        imageout_file = stdout;
+        imageoutFileP = stdout;
 
 
     /*
      * Open the file.
      */
     /* Initialize header. */
-    hdr = *rle_hdr_init( (rle_hdr *)NULL );
-    rle_names( &hdr, cmd_name( argv ), fname, 0 );
+    hdr = *rle_hdr_init(NULL);
+    rle_names(&hdr, cmd_name( argv ), fname, 0);
 
     /*
      * Read the rle file header.
      */
-    read_rle_header(ifp, cmdline.headerdump || cmdline.verbose);
+    readRleHeader(ifP, cmdline.headerdump || cmdline.verbose);
     if (cmdline.headerdump)
         exit(0);
 
     /* 
      * Write the alpha file header
      */
-    if (alpha_file)
-        pgm_writepgminit(alpha_file, width, height, RLE_MAXVAL, 0);
+    if (alphaFileP)
+        pgm_writepgminit(alphaFileP, width, height, RLE_MAXVAL, 0);
 
     /*
      * Write the pnm file header.
@@ -475,24 +473,24 @@ main(int argc, char ** argv) {
     case GRAYSCALE:   /* 8 bits without colormap -> pgm */
         if (cmdline.verbose)
             pm_message("Writing pgm file.");
-        if (imageout_file)
-            pgm_writepgminit(imageout_file, width, height, RLE_MAXVAL, 0);
-        write_pgm_data(imageout_file, alpha_file);
+        if (imageoutFileP)
+            pgm_writepgminit(imageoutFileP, width, height, RLE_MAXVAL, 0);
+        writePgmRaster(imageoutFileP, alphaFileP);
         break;
     default:      /* anything else -> ppm */
         if (cmdline.verbose)
             pm_message("Writing ppm file.");
-        if (imageout_file)
-            ppm_writeppminit(imageout_file, width, height, RLE_MAXVAL, 0);
-        write_ppm_data(imageout_file, alpha_file);
+        if (imageoutFileP)
+            ppm_writeppminit(imageoutFileP, width, height, RLE_MAXVAL, 0);
+        writePpmRaster(imageoutFileP, alphaFileP);
         break;
     }
    
-    pm_close(ifp);
-    if (imageout_file) 
-        pm_close( imageout_file );
-    if (alpha_file)
-        pm_close( alpha_file );
+    pm_close(ifP);
+    if (imageoutFileP) 
+        pm_close(imageoutFileP);
+    if (alphaFileP)
+        pm_close(alphaFileP);
 
     return 0;
 }
diff --git a/converter/other/tifftopnm.c b/converter/other/tifftopnm.c
index 595d4937..0d6494f9 100644
--- a/converter/other/tifftopnm.c
+++ b/converter/other/tifftopnm.c
@@ -151,6 +151,31 @@ parseCommandLine(int argc, const char ** const argv,
 
 
 
+static TIFF *
+newTiffImageObject(const char * const inputFileName) {
+/*----------------------------------------------------------------------------
+   Create a TIFF library object for accessing the TIFF input in file
+   named 'inputFileName'.  If 'inputFileName' is "-", that means
+   Standard Input.
+-----------------------------------------------------------------------------*/
+    const char * const tiffSourceName =
+        streq(inputFileName, "-") ? "Standard Input" : inputFileName;
+
+    TIFF * retval;
+
+    retval = TIFFFdOpen(fileno(pm_openr_seekable(inputFileName)),
+                        tiffSourceName,
+                        "r");
+
+    if (retval == NULL)
+        pm_error("Failed to access input file.  The OS opened the file fine, "
+                 "but the TIFF library's TIFFFdOpen rejected the open file.");
+
+    return retval;
+}
+
+
+
 static void
 getBps(TIFF *           const tif,
        unsigned short * const bpsP) {
@@ -1623,7 +1648,7 @@ int
 main(int argc, const char * argv[]) {
 
     struct CmdlineInfo cmdline;
-    TIFF * tif;
+    TIFF * tiffP;
     FILE * alphaFile;
     FILE * imageoutFile;
 
@@ -1631,15 +1656,7 @@ main(int argc, const char * argv[]) {
 
     parseCommandLine(argc, argv, &cmdline);
 
-    if (!streq(cmdline.inputFilename, "-")) {
-        tif = TIFFOpen(cmdline.inputFilename, "r");
-        if (tif == NULL)
-            pm_error("error opening TIFF file %s", cmdline.inputFilename);
-    } else {
-        tif = TIFFFdOpen(0, "Standard Input", "r");
-        if (tif == NULL)
-            pm_error("error opening standard input as TIFF file");
-    }
+    tiffP = newTiffImageObject(cmdline.inputFilename);
 
     if (cmdline.alphaStdout)
         alphaFile = stdout;
@@ -1653,13 +1670,15 @@ main(int argc, const char * argv[]) {
     else
         imageoutFile = stdout;
 
-    convertIt(tif, alphaFile, imageoutFile, cmdline);
+    convertIt(tiffP, alphaFile, imageoutFile, cmdline);
 
     if (imageoutFile != NULL) 
         pm_close( imageoutFile );
     if (alphaFile != NULL)
         pm_close( alphaFile );
 
+    TIFFClose(tiffP);
+
     pm_strfree(cmdline.inputFilename);
 
     /* If the program failed, it previously aborted with nonzero completion
diff --git a/converter/other/winicontopam.c b/converter/other/winicontopam.c
index f41a8db3..664b4ef9 100644
--- a/converter/other/winicontopam.c
+++ b/converter/other/winicontopam.c
@@ -49,16 +49,16 @@ parseCommandLine(int argc, const char **argv,
 
     option_def_index = 0;
 
-    OPTENT3 (0, "allimages",   OPT_FLAG,   NULL,
-             &cmdlineP->allimages,         0);
-    OPTENT3 (0, "image",     OPT_UINT,   &cmdlineP->image,
-             &cmdlineP->imageSpec,         0);
-    OPTENT3 (0, "andmasks",  OPT_FLAG,   NULL,
-             &cmdlineP->andmasks,          0);
-    OPTENT3 (0, "headerdump",   OPT_FLAG,   NULL,
-             &cmdlineP->headerdump,        0);
-    OPTENT3 (0, "verbose",   OPT_FLAG,   NULL,
-             &cmdlineP->verbose,           0);
+    OPTENT3(0, "allimages",   OPT_FLAG,   NULL,
+            &cmdlineP->allimages,         0);
+    OPTENT3(0, "image",     OPT_UINT,   &cmdlineP->image,
+            &cmdlineP->imageSpec,         0);
+    OPTENT3(0, "andmasks",  OPT_FLAG,   NULL,
+            &cmdlineP->andmasks,          0);
+    OPTENT3(0, "headerdump",   OPT_FLAG,   NULL,
+            &cmdlineP->headerdump,        0);
+    OPTENT3(0, "verbose",   OPT_FLAG,   NULL,
+            &cmdlineP->verbose,           0);
 
     opt3.opt_table     = option_def;
     opt3.short_allowed = false;
diff --git a/converter/other/yuy2topam.c b/converter/other/yuy2topam.c
new file mode 100644
index 00000000..40ab98b3
--- /dev/null
+++ b/converter/other/yuy2topam.c
@@ -0,0 +1,266 @@
+/* Convert an YUY2 image to a PAM image
+ *
+ * See
+ * http://msdn.microsoft.com/en-us/library/aa904813%28VS.80%29.aspx#yuvformats_2
+ * and http://www.digitalpreservation.gov/formats/fdd/fdd000364.shtml for
+ * details.
+ *
+ * By Michael Haardt 2014.
+ *
+ * Contributed to the public domain by its author.
+ *
+ * Recoded in Netpbm style by Bryan Henderson
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include "pm_c_util.h"
+#include "mallocvar.h"
+#include "pm.h"
+#include "pam.h"
+#include "shhopt.h"
+
+
+
+struct CmdlineInfo {
+    const char * inputFileName;
+    unsigned int width;
+    unsigned int height;
+};
+
+
+
+static void 
+parseCommandLine(int argc, const 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 pm_optParseOptions3 on how to parse our options. */
+    optStruct3 opt;
+
+    unsigned int widthSpec, heightSpec;
+    unsigned int option_def_index;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "width",    OPT_UINT,
+            &cmdlineP->width,   &widthSpec,                             0);
+    OPTENT3(0, "height",   OPT_UINT,
+            &cmdlineP->height,  &heightSpec,                            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 */
+
+    pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (!widthSpec)
+        pm_error("You must specify the image width with -width");
+    if (cmdlineP->width == 0)
+        pm_error("-width cannot be zero");
+
+    if (cmdlineP->width % 2 != 0)
+        pm_error("-width %u is odd, but YUY2 images must have an even width.",
+                 cmdlineP->width);
+
+    if (!heightSpec)
+        pm_error("You must specify the image height with -height");
+    if (cmdlineP->height == 0)
+        pm_error("-height cannot be zero");
+
+    if (argc-1 < 1)
+        cmdlineP->inputFileName = "-";
+    else {
+        cmdlineP->inputFileName = argv[1];
+        
+        if (argc-1 > 1)
+            pm_error("Too many arguments (%u).  The only non-option argument "
+                     "is the input file name.", argc-1);
+    }
+}
+
+
+
+typedef struct {
+    int y0;
+    int y1;
+    int u;
+    int v;
+} Yuy2Pixel;
+
+
+
+static Yuy2Pixel
+readPixel(FILE * const ifP) {
+/*----------------------------------------------------------------------------
+   Read one pixel from the YUY2 input.  YUY2 represents a pixel in 4 bytes.
+-----------------------------------------------------------------------------*/
+    Yuy2Pixel retval;
+    unsigned char c;
+
+    pm_readcharu(ifP, &c); retval.y0 = c -  16;
+    pm_readcharu(ifP, &c); retval.u  = c - 128;
+    pm_readcharu(ifP, &c); retval.y1 = c -  16;
+    pm_readcharu(ifP, &c); retval.v  = c - 128;
+
+    return retval;
+}
+
+
+
+typedef struct {
+    int a1;
+    int a2;
+    int a3;
+    int a4;
+} UvCoeff;
+
+typedef struct {
+    int a0a;
+    int a0b;
+    UvCoeff uv;
+} Coeff;
+
+
+
+static Coeff
+coeffFromYuy2(Yuy2Pixel const yuy2) {
+
+    Coeff retval;
+
+    retval.a0a   = 298 * yuy2.y0;
+    retval.a0b   = 298 * yuy2.y1;
+    retval.uv.a1 = 409 * yuy2.v;
+    retval.uv.a2 = 100 * yuy2.u;
+    retval.uv.a3 = 208 * yuy2.v;
+    retval.uv.a4 = 516 * yuy2.u;
+
+    return retval;
+}
+
+
+
+typedef struct {
+    int r;
+    int g;
+    int b;
+} Rgb;
+
+
+
+static Rgb
+rgbFromCoeff(int     const a0,
+             UvCoeff const uv) {
+
+    Rgb retval;
+
+    retval.r = (a0 + uv.a1 + 128) >> 8;
+    retval.g = (a0 - uv.a2 - uv.a3 + 128) >> 8;
+    retval.b = (a0 + uv.a4 + 128) >> 8;
+
+    return retval;
+}
+
+
+
+static Rgb
+rgbFromCoeff0(Coeff const coeff) {
+
+    return rgbFromCoeff(coeff.a0a, coeff.uv);
+}
+
+
+
+static Rgb
+rgbFromCoeff1(Coeff const coeff) {
+
+    return rgbFromCoeff(coeff.a0b, coeff.uv);
+}
+
+
+
+static void
+rgbToTuple(Rgb   const rgb,
+           tuple const out) {
+
+    out[PAM_RED_PLANE] = MIN(255, MAX(0, rgb.r));
+    out[PAM_GRN_PLANE] = MIN(255, MAX(0, rgb.g));
+    out[PAM_BLU_PLANE] = MIN(255, MAX(0, rgb.b));
+}
+
+
+
+static void
+yuy2topam(const char * const fileName,
+          unsigned int const width,
+          unsigned int const height) {
+
+    FILE * ifP;
+    struct pam outpam;
+    tuple * tuplerow;
+    unsigned int row;
+
+    outpam.size             = sizeof(struct pam);
+    outpam.len              = PAM_STRUCT_SIZE(allocation_depth);
+    outpam.file             = stdout;
+    outpam.format           = PAM_FORMAT;
+    outpam.plainformat      = 0;
+    outpam.width            = width;
+    outpam.height           = height;
+    outpam.depth            = 3;
+    outpam.maxval           = 255;
+    outpam.bytes_per_sample = 1;
+    strcpy(outpam.tuple_type, PAM_PPM_TUPLETYPE);
+    outpam.allocation_depth = 3;
+
+    ifP = pm_openr(fileName);
+
+    pnm_writepaminit(&outpam);
+
+    tuplerow = pnm_allocpamrow(&outpam);
+
+    for (row = 0; row < outpam.height; ++row) {
+        unsigned int col;
+
+        for (col = 0; col < outpam.width; col += 2) {
+            Yuy2Pixel const yuy2 = readPixel(ifP);
+
+            Coeff const coeff = coeffFromYuy2(yuy2);
+
+            rgbToTuple(rgbFromCoeff0(coeff), tuplerow[col]);
+            rgbToTuple(rgbFromCoeff1(coeff), tuplerow[col+1]);
+        }
+        pnm_writepamrow(&outpam, tuplerow);
+    }
+    pnm_freepamrow(tuplerow);
+
+    pm_closer(ifP);
+}
+
+
+
+int
+main(int argc, const char *argv[]) {
+
+    struct CmdlineInfo cmdline;
+
+    pm_proginit(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    yuy2topam(cmdline.inputFileName, cmdline.width, cmdline.height);
+
+    return 0;
+}
diff --git a/converter/pgm/Makefile b/converter/pgm/Makefile
index b109683b..f7ff341e 100644
--- a/converter/pgm/Makefile
+++ b/converter/pgm/Makefile
@@ -8,8 +8,8 @@ VPATH=.:$(SRCDIR)/$(SUBDIR)
 include $(BUILDDIR)/config.mk
 
 PORTBINARIES =	asciitopgm bioradtopgm fstopgm hipstopgm \
-		lispmtopgm pgmtofs pgmtolispm pgmtopgm \
-	        psidtopgm spottopgm sbigtopgm
+		lispmtopgm pgmtofs pgmtolispm pgmtopgm pgmtosbig pgmtost4 \
+	        psidtopgm spottopgm sbigtopgm st4topgm
 MATHBINARIES =	rawtopgm
 BINARIES =	$(PORTBINARIES) $(MATHBINARIES)
 
diff --git a/converter/pgm/pgmtosbig.c b/converter/pgm/pgmtosbig.c
new file mode 100644
index 00000000..0a302dd8
--- /dev/null
+++ b/converter/pgm/pgmtosbig.c
@@ -0,0 +1,130 @@
+/*=============================================================================
+                                 pgmtosbig
+===============================================================================
+
+  This program converts from PGM to a simple subset of SBIG.
+
+  By Bryan Henderson January 19, 2015.
+
+  Contributed to the public domain by its author.
+=============================================================================*/
+#include <string.h>
+
+#include "pm.h"
+#include "nstring.h"
+#include "pgm.h"
+
+
+
+#define SBIG_HEADER_LENGTH  2048      /* File header length */
+
+#define CTLZ "\x1A"
+
+
+struct SbigHeader {
+/*----------------------------------------------------------------------------
+   The information in an SBIG file header.
+
+   This is only the information this program cares about; the header
+   may have much more information in it.
+-----------------------------------------------------------------------------*/
+    unsigned int height;
+    unsigned int width;
+    unsigned int saturationLevel;
+};
+
+
+
+static void
+addUintParm(char *       const buffer,
+            const char * const name,
+            unsigned int const value) {
+
+    const char * line;
+
+    pm_asprintf(&line, "%s=%u\n\r", name, value);
+
+    strcat(buffer, line);
+
+    pm_strfree(line);
+}
+
+
+
+static void
+writeSbigHeader(FILE *            const ofP,
+                struct SbigHeader const hdr) {
+
+    char buffer[SBIG_HEADER_LENGTH];
+
+    memset(&buffer[0], 0x00, sizeof(buffer));
+
+    buffer[0] = '\0';
+
+    /* N.B. LF-CR instead of CRLF.  That's what the spec says. */
+
+    strcat(buffer, "ST-6 Image\n\r" );
+
+    addUintParm(buffer, "Height", hdr.height);
+
+    addUintParm(buffer, "Width", hdr.width);
+
+    addUintParm(buffer, "Sat_level", hdr.saturationLevel);
+
+    strcat(buffer, "End\n\r" CTLZ);
+
+    fwrite(buffer, 1, sizeof(buffer), ofP);
+}
+
+
+
+int
+main(int argc, const char * argv[]) {
+
+    FILE * ifP;
+    gray * grayrow;
+    int rows;
+    int cols;
+    int format;
+    struct SbigHeader hdr;
+    unsigned int row;
+    gray maxval;
+    const char * inputFile;
+
+    pm_proginit(&argc, argv);
+
+    if (argc-1 < 1)
+        inputFile = "-";
+    else {
+        inputFile = argv[1];
+
+        if (argc-1 > 2)
+            pm_error("Too many arguments.  The only argument is the optional "
+                     "input file name");
+    }
+
+    ifP = pm_openr(inputFile);
+
+    pgm_readpgminit(ifP, &cols, &rows, &maxval, &format);
+
+    grayrow = pgm_allocrow(cols);
+
+    hdr.height = rows;
+    hdr.width = cols;
+    hdr.saturationLevel = maxval;
+
+    writeSbigHeader(stdout, hdr);
+
+    for (row = 0; row < rows; ++row) {
+        unsigned int col;
+
+        pgm_readpgmrow(ifP, grayrow, cols, maxval, format);
+
+        for (col = 0; col < cols; ++col)
+            pm_writelittleshort(stdout, grayrow[col]);
+    }
+
+    pm_close(ifP);
+
+    return 0;
+}
diff --git a/converter/pgm/pgmtost4.c b/converter/pgm/pgmtost4.c
new file mode 100644
index 00000000..fa101ac9
--- /dev/null
+++ b/converter/pgm/pgmtost4.c
@@ -0,0 +1,104 @@
+/*=============================================================================
+                                 pgmtost4
+===============================================================================
+
+  This program converts from PGM to a simple subset of SBIG ST-4.
+
+  By Bryan Henderson January 19, 2015.
+
+  Contributed to the public domain by its author.
+=============================================================================*/
+#include <string.h>
+
+#include "pm.h"
+#include "nstring.h"
+#include "pam.h"
+
+
+
+static unsigned int const st4Height = 165;
+static unsigned int const st4Width  = 192;
+static unsigned int const st4Maxval = 255;
+
+
+
+static void
+writeSt4Footer(FILE * const ofP) {
+
+    const char * const comment = "This was created by Pgmtost4";
+    char buffer[192];
+
+    memset(buffer, ' ', sizeof(buffer));  /* initial value */
+
+    buffer[0] = 'v';
+
+    memcpy(&buffer[  0], "v", 1);
+    memcpy(&buffer[  1], comment, strlen(comment));
+    memcpy(&buffer[ 79], "         7", 10);
+    memcpy(&buffer[ 89], "         8", 10);
+    memcpy(&buffer[ 99], "         9", 10);
+    memcpy(&buffer[109], "        10", 10);
+
+    fwrite(buffer, 1, sizeof(buffer), ofP);
+}
+
+
+
+int
+main(int argc, const char * argv[]) {
+
+    FILE * ifP;
+    tuple * tuplerow;
+    struct pam inpam;
+    unsigned int row;
+    const char * inputFile;
+
+    pm_proginit(&argc, argv);
+
+    if (argc-1 < 1)
+        inputFile = "-";
+    else {
+        inputFile = argv[1];
+
+        if (argc-1 > 2)
+            pm_error("Too many arguments.  The only argument is the optional "
+                     "input file name");
+    }
+
+    ifP = pm_openr(inputFile);
+
+    pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
+
+    if (inpam.height != st4Height)
+        pm_error("Image is wrong height for ST-4 SBIG: %u pixels.  "
+                 "Must be %u", inpam.height, st4Height);
+
+    if (inpam.width != st4Width)
+        pm_error("Image is wrong width for ST-4 SBIG: %u pixels.  "
+                 "Must be %u", inpam.width, st4Width);
+    
+    /* Really, we should just scale to maxval 255.  There are library routines
+       for that, but we're too lazy even for that, since nobody is really
+       going to use this program.
+    */
+    if (inpam.maxval != st4Maxval)
+        pm_error("Image is wrong maxval for ST-4 SBIG: %u.  "
+                 "Must be %u", (unsigned)inpam.maxval, st4Maxval);
+
+    tuplerow = pnm_allocpamrow(&inpam);
+
+    for (row = 0; row < inpam.height; ++row) {
+        unsigned int col;
+
+        pnm_readpamrow(&inpam, tuplerow);
+
+        for (col = 0; col < inpam.width; ++col)
+            pm_writechar(stdout, (char)tuplerow[col][0]);
+    }
+
+    writeSt4Footer(stdout);
+
+    pm_close(ifP);
+
+    return 0;
+}
diff --git a/converter/pgm/sbigtopgm.c b/converter/pgm/sbigtopgm.c
index c49a4165..2e8b4586 100644
--- a/converter/pgm/sbigtopgm.c
+++ b/converter/pgm/sbigtopgm.c
@@ -1,42 +1,102 @@
 /*
-
     sbigtopgm.c - read a Santa Barbara Instruments Group CCDOPS file
 
-    Note: All SBIG CCD astronomical cameras produce 14 bits or
-	  (the ST-4 and ST-5) or 16 bits (ST-6 and later) per pixel.
-
-		  Copyright (C) 1998 by John Walker
-		       http://www.fourmilab.ch/
+    Note: All SBIG CCD astronomical cameras produce 14 bits
+    (the ST-4 and ST-5) or 16 bits (ST-6 and later) per pixel.
 
     If you find yourself having to add functionality included subsequent
     to the implementation of this program, you can probably find
     documentation of any changes to the SBIG file format on their
     Web site: http://www.sbig.com/
 
+    Copyright (C) 1998 by John Walker
+    http://www.fourmilab.ch/
+
     Permission to use, copy, modify, and distribute this software and
     its documentation for any purpose and without fee is hereby
     granted, 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
+    notice appear in supporting documentation.  This software is
     provided "as is" without express or implied warranty.
-
 */
 
 #include <string.h>
 
-#include "pgm.h"
+#include "pm_c_util.h"
+#include "mallocvar.h"
 #include "nstring.h"
+#include "shhopt.h"
+#include "pm.h"
+#include "pgm.h"
+
+struct CmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char * inputFileName;
+};
+
+
+
+static void
+parseCommandLine(int argc, const char ** argv,
+                 struct CmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to as as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry * option_def;
+        /* Instructions to pm_optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+    
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENTINIT;
+
+    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 */
+    
+    pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others */
+
+    if (argc-1 < 1)
+        cmdlineP->inputFileName = "-";
+    else {
+        cmdlineP->inputFileName = argv[1];
+
+        if (argc-1 > 1)
+            pm_error("Too many arguments.  The only possible argument is the "
+                     "optional input file name");
+    }
+}
+
+
 
 #define SBIG_HEADER_LENGTH  2048      /* File header length */
 
-/*  looseCanon	--  Canonicalize a line from the file header so
-    items more sloppily formatted than those
-    written by CCDOPS are still accepted.
-*/
+
 
 static void
 looseCanon(char * const cpArg) {
+/*----------------------------------------------------------------------------
+  Canonicalize a line from the file header so items more sloppily formatted
+  than those written by CCDOPS are still accepted.
+
+  Remove all whitespace and make all letters lowercase.
 
+  Note that the SBIG Type 3 format specification at www.sbig.com in January
+  2015 says header parameter names are capitalized like 'Height'; we change
+  that to "height".
+
+  The spec also says the line ends with LF, then CR (yes, really).  Assuming
+  Caller separates lines at LF, that means we see CR at the beginning of all
+  lines but the first.  We remove that.
+-----------------------------------------------------------------------------*/
     char * cp;
     char * op;
     char c;
@@ -56,43 +116,41 @@ looseCanon(char * const cpArg) {
 
 
 
-int
-main(int argc, char ** argv) {
+struct SbigHeader {
+/*----------------------------------------------------------------------------
+   The information in an SBIG file header.
 
-    FILE * ifP;
-    gray * grayrow;
-    gray * gP;
-    int argn, row;
-    int col;
-    int maxval;
-    int comp, rows, cols;
-    char header[SBIG_HEADER_LENGTH];
-    char * hdr;
-    static char camera[80] = "ST-?";
-
-    pgm_init(&argc, argv);
+   This is only the information this program cares about; the header
+   may have much more information in it.
+-----------------------------------------------------------------------------*/
+    unsigned int rows;
+    unsigned int cols;
+    unsigned int maxval;
+    bool isCompressed;
+    bool haveCameraType;
+    char cameraType[80];
+};
 
-    argn = 1;
 
-    if (argn < argc) {
-        ifP = pm_openr(argv[argn]);
-        argn++;
-    } else
-        ifP = stdin;
 
-    if (argn != argc)
-        pm_usage( "[sbigfile]" );
+static void
+readSbigHeader(FILE *              const ifP,
+               struct SbigHeader * const sbigHeaderP) {
 
-    if (fread(header, SBIG_HEADER_LENGTH, 1, ifP) < 1)
-        pm_error("error reading SBIG file header");
+    size_t rc;
+    bool gotCompression;
+    bool gotWidth;
+    bool gotHeight;
+    char buffer[SBIG_HEADER_LENGTH];
+    char * cursor;
+    bool endOfHeader;
 
-    /*	Walk through the header and parse relevant parameters.	*/
+    rc = fread(buffer, SBIG_HEADER_LENGTH, 1, ifP);
 
-    comp = -1;
-    cols = -1;
-    rows = -1;
+    if (rc < 1)
+        pm_error("error reading SBIG file header");
 
-    /*	The SBIG header specification equivalent to maxval is
+    /*  The SBIG header specification equivalent to maxval is
         "Sat_level", the saturation level of the image.  This
         specification is optional, and was not included in files
         written by early versions of CCDOPS. It was introduced when it
@@ -107,106 +165,166 @@ main(int argc, char ** argv) {
         65535 as the default because the overwhelming majority of
         cameras in use today are 16 bit, and it's possible some
         non-SBIG software may omit the "optional" Sat_level
-        specification.	Also, no harm is done if a larger maxval is
+        specification.  Also, no harm is done if a larger maxval is
         specified than appears in the image--a simple contrast stretch
         will adjust pixels to use the full 0 to maxval range.  The
         converse, pixels having values greater than maxval, results in
         an invalid file which may cause problems in programs which
         attempt to process it.
-	*/
+    */
 
-    maxval = 65535;
+    gotCompression = false;  /* initial value */
+    gotWidth       = false;  /* initial value */
+    gotHeight      = false;  /* initial value */
 
-    hdr = header;
+    sbigHeaderP->maxval = 65535;  /* initial assumption */
+    sbigHeaderP->haveCameraType = false;  /* initial assumption */
 
-    for (;;) {
-        char *cp = strchr(hdr, '\n');
+    for (cursor = &buffer[0], endOfHeader = false; !endOfHeader;) {
+        char * const cp = strchr(cursor, '\n');
 
         if (cp == NULL) {
             pm_error("malformed SBIG file header at character %u",
-                     (unsigned)(hdr - header));
+                     (unsigned)(cursor - &buffer[0]));
         }
         *cp = '\0';
-        if (strncmp(hdr, "ST-", 3) == 0) {
-            char * const ep = strchr(hdr + 3, ' ');
+        if (strneq(cursor, "ST-", 3)) {
+            char * const ep = strchr(cursor + 3, ' ');
 
             if (ep != NULL) {
                 *ep = '\0';
-                strcpy(camera, hdr);
+                strcpy(sbigHeaderP->cameraType, cursor);
+                sbigHeaderP->haveCameraType = true;
                 *ep = ' ';
             }
         }
-        looseCanon(hdr);
-        if (strncmp(hdr, "st-", 3) == 0) {
-            comp = strstr(hdr, "compressed") != NULL;
-        } else if (strncmp(hdr, "height=", 7) == 0) {
-            rows = atoi(hdr + 7);
-        } else if (strncmp(hdr, "width=", 6) == 0) {
-            cols = atoi(hdr + 6);
-        } else if (strncmp(hdr, "sat_level=", 10) == 0) {
-            maxval = atoi(hdr + 10);
-        } else if (streq(hdr, "end")) {
-            break;
+        
+        looseCanon(cursor);
+            /* Convert from standard SBIG to an internal format */
+
+        if (strneq(cursor, "st-", 3)) {
+            sbigHeaderP->isCompressed = (strstr("compressed", cursor) != NULL);
+            gotCompression = true;
+        } else if (strneq(cursor, "height=", 7)) {
+            sbigHeaderP->rows = atoi(cursor + 7);
+            gotHeight = true;
+        } else if (strneq(cursor, "width=", 6)) {
+            sbigHeaderP->cols = atoi(cursor + 6);
+            gotWidth = true;
+        } else if (strneq(cursor, "sat_level=", 10)) {
+            sbigHeaderP->maxval = atoi(cursor + 10);
+        } else if (streq("end", cursor)) {
+            endOfHeader = true;
         }
-        hdr = cp + 1;
+        cursor = cp + 1;
     }
 
-    if (comp == -1 || rows == -1 || cols == -1)
-        pm_error("required specification missing from SBIG file header");
+    if (!gotCompression)
+        pm_error("Required 'ST-*' specification missing "
+                 "from SBIG file header");
+    if (!gotHeight)
+        pm_error("required 'height=' specification missing"
+                 "from SBIG file header");
+    if (!gotWidth)
+        pm_error("required 'width=' specification missing "
+                 "from SBIG file header");
+}
+
 
-    pm_message("SBIG %s %dx%d %s image, saturation level = %d",
-               camera, cols, rows, comp ? "compressed" : "uncompressed",
-               maxval);
 
-    if (maxval > PGM_OVERALLMAXVAL) {
-        pm_error("Saturation level (%d levels) is too large"
-                 "This program's limit is %d.", maxval, PGM_OVERALLMAXVAL);
-    }
+static void
+writeRaster(FILE *            const ifP,
+            struct SbigHeader const hdr,
+            FILE *            const ofP) {
 
-    pgm_writepgminit(stdout, cols, rows, maxval, 0);
-    grayrow = pgm_allocrow(cols);
+    gray * grayrow;
+    unsigned int row;
 
-#define DOSINT(fp) ((getc(fp) & 0xFF) | (getc(fp) << 8))
+    grayrow = pgm_allocrow(hdr.cols);
 
-    for (row = 0; row < rows; ++row) {
-        int compthis;
+    for (row = 0; row < hdr.rows; ++row) {
+        bool compthis;
+        unsigned int col;
 
-        compthis = comp;  /* initial value */
+        if (hdr.isCompressed) {
+            unsigned short rowlen;        /* Compressed row length */
 
-        if (comp) {
-            int const rowlen = DOSINT(ifP); /* Compressed row length */
+            pm_readlittleshortu(ifP, &rowlen);
             
-            /*	If compression results in a row length >= the uncompressed
+            /*  If compression results in a row length >= the uncompressed
                 row length, that row is output uncompressed.  We detect this
                 by observing that the compressed row length is equal to
                 that of an uncompressed row.
             */
 
-            if (rowlen == cols * 2)
-                compthis = 0;
-        }
-        for (col = 0, gP = grayrow; col < cols; ++col, ++gP) {
-            gray g;
+            if (rowlen == hdr.cols * 2)
+                compthis = false;
+            else
+                compthis = hdr.isCompressed;
+        } else
+            compthis = hdr.isCompressed;
+
+        for (col = 0; col < hdr.cols; ++col) {
+            unsigned short g;
 
             if (compthis) {
                 if (col == 0) {
-                    g = DOSINT(ifP);
+                    pm_readlittleshortu(ifP, &g);
                 } else {
-                    int delta = getc(ifP);
+                    int const delta = getc(ifP);
 
                     if (delta == 0x80)
-                        g = DOSINT(ifP);
+                        pm_readlittleshortu(ifP, &g);
                     else
                         g += ((signed char) delta);
                 }
             } else
-                g = DOSINT(ifP);
-            *gP = g;
+                pm_readlittleshortu(ifP, &g);
+            grayrow[col] = g;
         }
-        pgm_writepgmrow(stdout, grayrow, cols, (gray) maxval, 0);
+        pgm_writepgmrow(ofP, grayrow, hdr.cols, hdr.maxval, 0);
     }
+
+    pgm_freerow(grayrow);
+}
+
+
+
+int
+main(int argc, const char ** argv) {
+
+    FILE * ifP;
+    struct CmdlineInfo cmdline;
+    struct SbigHeader hdr;
+
+    pm_proginit(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFileName);
+
+    readSbigHeader(ifP, &hdr);
+
+    pm_message("SBIG '%s' %ux%u %s image, saturation level = %u",
+               (hdr.haveCameraType ? hdr.cameraType : "ST-?"),
+               hdr.cols, hdr.rows,
+               hdr.isCompressed ? "compressed" : "uncompressed",
+               hdr.maxval);
+
+    if (hdr.maxval > PGM_OVERALLMAXVAL) {
+        pm_error("Saturation level (%u levels) is too large"
+                 "This program's limit is %u.", hdr.maxval, PGM_OVERALLMAXVAL);
+    }
+
+    pgm_writepgminit(stdout, hdr.cols, hdr.rows, hdr.maxval, 0);
+
+    writeRaster(ifP, hdr, stdout);
+
     pm_close(ifP);
     pm_close(stdout);
 
     return 0;
 }
+
+
+
diff --git a/converter/pgm/st4topgm.c b/converter/pgm/st4topgm.c
new file mode 100644
index 00000000..e763852c
--- /dev/null
+++ b/converter/pgm/st4topgm.c
@@ -0,0 +1,260 @@
+/*=============================================================================
+                               st4topgm
+===============================================================================
+
+  Convert an SBIG ST-4 image (not to be confused with the more sophisticated
+  SBIG format that every other SBIG camera produces) to PGM.
+
+  By Bryan Henderson January 2015.
+
+  Contributed to the public domain by its author.
+
+  This program was intended to substitute for the program of the same name in
+  the Debian version of Netpbm, by Justin Pryzby <justinpryzby@users.sf.net>
+  in December 2003.
+
+=============================================================================*/
+#include <string.h>
+
+#include "pm_config.h"
+#include "pm_c_util.h"
+#include "pm.h"
+#include "pam.h"
+
+
+
+static unsigned int const st4Height = 165;
+static unsigned int const st4Width  = 192;
+static unsigned int const st4Maxval = 255;
+
+
+
+static void
+validateFileSize(FILE * const ifP) {
+/*----------------------------------------------------------------------------
+   Abort program if *ifP is not the proper size for an ST-4 SBIG file.
+
+   Don't change file position.
+-----------------------------------------------------------------------------*/
+    pm_filepos const st4FileSize = (st4Height+1) * st4Width;
+
+    pm_filepos oldFilePos;
+    pm_filepos endFilePos;
+
+    pm_tell2(ifP, &oldFilePos, sizeof(endFilePos));
+
+    fseek(ifP, 0, SEEK_END);
+
+    pm_tell2(ifP, &endFilePos, sizeof(endFilePos));
+
+    pm_seek2(ifP, &oldFilePos, sizeof(oldFilePos));
+
+    if (endFilePos != st4FileSize)
+        pm_error("File is the wrong size for an ST-4 SBIG file.  "
+                 "It is %u bytes; it should be %u bytes",
+                 (unsigned)endFilePos, (unsigned)st4FileSize);
+}
+
+
+static void
+writeRaster(FILE *       const ifP,
+            struct pam * const pamP) {
+
+    tuple * tuplerow;
+    unsigned int row;
+
+    tuplerow = pnm_allocpamrow(pamP);
+
+    for (row = 0; row < st4Height; ++row) {
+        unsigned int col;
+
+        for (col = 0; col < st4Width; ++col) {
+            char c;
+
+            pm_readchar(ifP, &c);
+
+            tuplerow[col][0] = (unsigned char)c;
+        }
+        pnm_writepamrow(pamP, tuplerow);
+    }
+
+    pnm_freepamrow(tuplerow);
+}
+
+
+
+struct St4Footer {
+/*----------------------------------------------------------------------------
+   The information in an ST-4 SBIG footer.
+-----------------------------------------------------------------------------*/
+    /* Note that numerical information is in decimal text, because we're lazy.
+    */
+
+    char comment[78+1];
+    char exposureTime[10+1];
+    char focalLength[10+1];
+    char apertureArea[10+1];
+    char calibrationFactor[10+1];
+};
+
+
+
+static void
+stripTrailing(char * const arg) {
+
+    if (strlen(arg) > 0) {
+        char * p;
+        for (p = arg + strlen(arg); p > arg && *(p-1) == ' '; --p);
+
+        *p = '\0';
+    }
+}
+
+
+
+static void
+stripLeading(char * const arg) {
+
+    const char * p;
+
+    for (p = &arg[0]; *p == ' '; ++p);
+
+    if (p > arg)
+        memmove(arg, p, strlen(p) + 1);
+}
+
+
+
+static void
+readFooter(FILE *             const ifP,
+           struct St4Footer * const footerP) {
+/*----------------------------------------------------------------------------
+   Read the footer of the ST-4 image from *ifP, assuming *ifP is positioned
+   to the footer.
+
+   Return its contents as *footerP.
+-----------------------------------------------------------------------------*/
+    char buffer[192];
+    size_t bytesReadCt;
+
+    /* The footer is laid out as follows.
+
+       off len description
+       --- --- -----------
+       000   1 Signature: 'v'
+       001  78 Freeform comment
+       079  10 Exposure time in 1/100s of a second
+       089  10 Focal length in inches
+       099  10 Aperture area in square inches
+       109  10 Calibration factor
+       119  73 Reserved
+
+       Note tha the footer is the same length as a raster row.
+    */
+
+    bytesReadCt = fread(buffer, 1, sizeof(buffer), ifP);
+
+    if (bytesReadCt != 192)
+        pm_error("Failed to read footer of image");
+
+    if (buffer[0] != 'v')
+        pm_error("Input is not an ST-4 file.  We know because the "
+                 "signature byte (first byte of the footer) is not 'v'");
+
+    buffer[191] = '\0';
+    memmove(footerP->comment, &buffer[1], 78);
+    footerP->comment[78] = '\0';
+    stripTrailing(footerP->comment);
+
+    memmove(footerP->exposureTime, &buffer[79], 10);
+    footerP->exposureTime[10] = '\0';
+    stripLeading(footerP->exposureTime);
+
+    memmove(footerP->focalLength, &buffer[89], 10);
+    footerP->focalLength[10] = '\0';
+    stripLeading(footerP->focalLength);
+
+    memmove(footerP->apertureArea, &buffer[99], 10);
+    footerP->apertureArea[10] = '\0';
+    stripLeading(footerP->apertureArea);
+
+    memmove(footerP->calibrationFactor, &buffer[109], 10);
+    footerP->calibrationFactor[10] = '\0';
+    stripLeading(footerP->calibrationFactor);
+}
+
+
+
+static void
+reportFooter(struct St4Footer const footer) {
+
+	pm_message("Comment:                 %s", footer.comment);
+
+	pm_message("Exposure time (1/100 s): %s", footer.exposureTime);
+
+	pm_message("Focal length (in):       %s", footer.focalLength);
+
+	pm_message("Aperture area (sq in):   %s", footer.apertureArea);
+
+	pm_message("Calibration factor:      %s", footer.calibrationFactor);
+}
+
+
+
+int
+main(int argc, const char **argv) {
+
+    FILE * ifP;
+    const char * inputFileName;
+    struct pam outpam;
+    struct St4Footer footer;
+
+    pm_proginit(&argc, argv);
+
+    if (argc-1 < 1)
+        inputFileName = "'";
+    else {
+        inputFileName = argv[1];
+        if (argc-1 > 1)
+            pm_error("Too many arguments: %u.  "
+                     "The only possible argument is the "
+                     "optional input file name", argc-1);
+    }        
+
+    /* We check the file size to catch the common problem of the input not
+       being valid ST-4 SBIG input.  Unlike most formats, this one does not
+       have any signature at the head of the file.
+
+       More checks on the validity of the format happens when we process
+       the image footer.
+    */
+
+    ifP = pm_openr_seekable(inputFileName);
+
+    validateFileSize(ifP);
+
+    outpam.size = sizeof(outpam);
+    outpam.len = PAM_STRUCT_SIZE(maxval);
+    outpam.file = stdout;
+    outpam.format = PGM_FORMAT;
+    outpam.plainformat = false;
+    outpam.height = st4Height;
+    outpam.width = st4Width;
+    outpam.depth = 1;
+    outpam.maxval = st4Maxval;
+
+    pnm_writepaminit(&outpam);
+
+    writeRaster(ifP, &outpam);
+
+    readFooter(ifP, &footer);
+
+    reportFooter(footer);
+
+    pm_close(ifP);
+    pm_close(stdout);
+
+    return 0;
+}
+
+
diff --git a/doc/HISTORY b/doc/HISTORY
index 7126c1f9..ec46a56c 100644
--- a/doc/HISTORY
+++ b/doc/HISTORY
@@ -4,49 +4,57 @@ Netpbm.
 CHANGE HISTORY 
 --------------
 
-15.03.29 BJH  Release 10.69.07
+15.03.29 BJH  Release 10.70.00
 
-              giftopnm: Fix bug: crashes if purported GIF has neither a global
-              color map nor a local one.
+              Add st4topgm, pgmtost4.
 
-15.03.03 BJH  Release 10.69.06
+              Add pgmtosbig.  Mainly a test tool for sbigtopgm.
 
-              pamtosvg: fix use of unset variable; probably results in a
-              crash.  Always present (pamtosvg was new in Netpbm 10.33 (March
-              2006).
+              Add yuy2topam.  Thanks Michael Haardt.
 
-              cameratopam: fix bug: variable used before set; unknown impact.
-              Introduced in Netpbm 10.66 (March 2014).
+              tifftopnm: allow input file to be nonseekable.
+              Thanks Ludolf Holzheid <ludolf.holzheid@gmx.de>.
 
-              build: fix incompatible type compilation error in giftopnm.
-              Broken in Netpbm 10.38 (March 2007) (but obviously manifests
-              only in recent build environments).
+              pnmhisteq: add -noblack and -nowhite.  Idea from Andrew Brooks
+              <arb@sat.dundee.ac.uk>.
 
-15.01.25 BJH  Release 10.69.05
+              pgmmorphconv: add -gradient.  Thanks Michael Haardt
+              <michael@moria.de>.
 
-              pamtilt: fix bug: unconditional crash.  Broken in Netpbm 10.63
-              (June 2013).
+              giftopnm: Fix bug: crashes if purported GIF has neither a global
+              color map nor a local one.
 
-15.01.23 BJH  Release 10.69.04
+              pgmmorphconv: fix bug: always produces PGM Plain format.
 
-              build: fix compile failure in wordint_access_be.h with Bigendian
-              target platforms.  Broken in Netpbm 10.63 (June 2013).
-
-15.01.22 BJH  Release 10.69.03
+              pamtilt: fix bug: unconditional crash.  Broken in Netpbm 10.63
+              (June 2013).
 
               pnmgamma -srgbtobt709, -bt709tosrgb: fix bug; incorrect output
               nearly always.  Always broken (These options were new in
               Netpbm 10.32 (February 2006)).
 
-              Install: make backward compatibility link ppmtotga -> pamtotga .
+              pamtosvg: fix use of unset variable; probably results in a
+              crash.  Always present (pamtosvg was new in Netpbm 10.33 (March
+              2006).
 
-15.01.01 BJH  Release 10.69.02
+              cameratopam: fix bug: variable used before set; unknown impact.
+              Introduced in Netpbm 10.66 (March 2014).
+
+              On Windows, don't leave temporary files around (previous code
+              did so because unlink of an open file fails in Windows; new
+              code deletes temporary files via atexit).  Thanks
+              Ludolf Holzheid <ludolf.holzheid@gmx.de>.
 
               Libnetpbm: fix external header file pm.h so it does not include
               internal header file pm_c_util.h.  Introduced in Netpbm
-              10.69.00 (December 2014).
+              10.69 (December 2014).
+
+              build: fix incompatible type compilation error in giftopnm.
+              Broken in Netpbm 10.38 (March 2007) (but obviously manifests
+              only in recent build environments).
 
-15.01.01 BJH  Release 10.69.01
+              build: fix compile failure in wordint_access_be.h with Bigendian
+              target platforms.  Broken in Netpbm 10.63 (June 2013).
 
               build: fix compile failure in pbmtomacp, ppmtoacad, pgmabel:
               TRUE redefined.  Introduced in Netpbm 10.69 (December 2014).
@@ -3287,7 +3295,7 @@ CHANGE HISTORY
 
               pnmtojpeg: Add -density option.
 
-              pamtotga: Add alpha capability, via "RGBA" tuple type.
+              pamtotga: Fix bugs with images with alpha planes.
 
               libnetpbm: Add ppm_readcolornamefile().
 
@@ -3621,9 +3629,17 @@ CHANGE HISTORY
               pnmmontage: add -data option.  Thanks Ben
               <bem@mad.scientist.com>.
 
-              pnmtotga: put "image ID" in TGA output.
+              pamtotga: Add alpha capability, via "RGBA" tuple type.
+
+              pamtotga: put "image ID" in TGA output.
+
+              ppmtotga: take PAM input; rename dot 'pamtotga'.  Former name
+              was a misnomer anyway; it was always a PNM program because it
+              distinguished between PGM and PPM input.
+
+              pamtotga: Add alpha capability, via "RGBA" tuple type.
 
-              ppmtotga: Correct name to pnmtotga.
+              pamtotga: put "image ID" in TGA output.
 
               pnmcomp:  Add -opacity option.
 
@@ -3952,7 +3968,8 @@ CHANGE HISTORY
 
               pnmtops: Accept maxval > 255 input.
          
-              Rename pnminterp to the more informative pnmstretch.
+              Rename pnminterp to the more informative pnmstretch and
+              pnminterp-gen to pamstretch-gen.
 
               pnmstretch: convert from pnm to pam - pamstretch.
 
diff --git a/doc/INSTALL b/doc/INSTALL
index f933722d..f0b3e87b 100644
--- a/doc/INSTALL
+++ b/doc/INSTALL
@@ -372,3 +372,61 @@ DOCUMENTATION
 
 Documentation is not packaged with the program source code.  See the
 file doc/USERDOC for information on installing documentation.
+
+
+COMMON PROBLEMS
+---------------
+
+Improper -config files
+----------------------
+
+The most common problem building Netpbm is one of improperly installed
+prerequisite libraries, such as Libpng.  Such a library is designed to be
+installed along with a -config program (e.g. libpng-config) that tells
+builders of dependent packages (such as Netpbm) how to use it.  When the
+-config program is wrong, you get Netpbm build failures with messages about
+undefined references.
+
+The exact nature of the problems with -config programs can be quite
+involved, especially since there is no guarantee that a -config
+program can do what's required of it in every situation.  But I'll
+explain the basic problem.  For simplicity, I'll talk specifically
+about Libpng, but the principles apply to any library that has a -config
+program.
+
+The point of libpng-config is to describe how Libpng is installed on your
+particular system.  You have choices of where to install the various parts
+and what prerequisites to build into them, and libpng-config is how you
+communicate those choices to the Netpbm make files.
+
+Libpng's builder automatically creates a libpng-config program for you,
+but you should not think of it as part of Libpng.  It's really a
+configuration file -- something that tells how your particular system
+is put together.  The Libpng builder is not smart enough to know exactly
+what to put in libpng-config; it just builds one that works for most
+people.  The local system administrator is actually responsible for
+the contents of libpng-config.
+
+One rather complex way in which the libpng-config that the Libpng builder
+builds can be wrong is that it often indicates that to link to the
+Libpng library, you need a "-L /usr/lib" option (or something like that
+-- an option that adds to the linker's search path a directory that is
+already in it).  This is usually unnecessary because the directory is
+already in the search path, and often breaks things because it puts
+the directory too early in the search path.  If your libpng-config says to
+link with -L /usr/lib, you should normally edit it to remove that.
+
+As an example of how -L /usr/lib breaks things, here is a problem that is
+often reported: The user has Netpbm installed on his system, but wants to
+build a new one to replace it, or to use for a particular project instead of
+the system version.  But the build of the new version fails with undefined
+references to symbol "pm_foo".  pm_foo is a new symbol - it was added to
+Netpbm in a recent release.  The version of Netpbm installed on the system is
+too old to have it.  The make file obviously specifies the path to the current
+libraries that the user just built in the link library search order, but the
+link is picking up the old system version instead.  Why?  Because the link
+options say to search /usr/lib _before_ the local build directory.  And they
+do that because libpng-config erroneously says that you need a -L /usr/lib
+link option to find the Libpng library.
+
+
diff --git a/editor/pamcut.c b/editor/pamcut.c
index 03573796..7c41af38 100644
--- a/editor/pamcut.c
+++ b/editor/pamcut.c
@@ -24,11 +24,11 @@
        but we hope not.
        */
 
-struct cmdlineInfo {
+struct CmdlineInfo {
     /* All the information the user supplied in the command line,
        in a form easy for the program to use.
     */
-    const char *inputFileName;  /* File name of input file */
+    const char * inputFileName;  /* File name of input file */
 
     /* The following describe the rectangle the user wants to cut out. 
        the value UNSPEC for any of them indicates that value was not
@@ -52,7 +52,7 @@ struct cmdlineInfo {
 
 static void
 parseCommandLine(int argc, const char ** const argv,
-                 struct cmdlineInfo * const cmdlineP) {
+                 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.
@@ -628,7 +628,7 @@ extractRowsPBM(const struct pam * const inpamP,
 
 static void
 cutOneImage(FILE *             const ifP,
-            struct cmdlineInfo const cmdline,
+            struct CmdlineInfo const cmdline,
             FILE *             const ofP) {
 
     int leftcol, rightcol, toprow, bottomrow;
@@ -673,7 +673,7 @@ main(int argc, const char *argv[]) {
 
     FILE * const ofP = stdout;
 
-    struct cmdlineInfo cmdline;
+    struct CmdlineInfo cmdline;
     FILE * ifP;
     int eof;
 
diff --git a/editor/pgmdeshadow.c b/editor/pgmdeshadow.c
index 17531327..2c3a90f8 100644
--- a/editor/pgmdeshadow.c
+++ b/editor/pgmdeshadow.c
@@ -21,7 +21,7 @@
 */
 
 /*
- * Algorithm reference: Luc Vincent, "Morphological Grayscale Reruction
+ * Algorithm reference: Luc Vincent, "Morphological Grayscale Reconstruction
  * in Image Analysis: Applications and Efficient Algorithms," IEEE
  * Transactions on Image Processing, vol. 2, no. 2, April 1993, pp. 176-201.
  *
diff --git a/editor/pnmgamma.c b/editor/pnmgamma.c
index 96068bfe..b357b0d8 100644
--- a/editor/pnmgamma.c
+++ b/editor/pnmgamma.c
@@ -10,6 +10,7 @@
 ** implied warranty.
 */
 
+#include <assert.h>
 #include <math.h>
 #include <ctype.h>
 
@@ -308,6 +309,7 @@ buildPowGamma(xelval       table[],
         double const normalized = ((double) i) / maxval;
             /* Xel sample value normalized to 0..1 */
         double const v = pow(normalized, oneOverGamma);
+
         table[i] = MIN((xelval)(v * newMaxval + 0.5), newMaxval);  
             /* denormalize, round and clip */
     }
@@ -509,11 +511,15 @@ buildBt709ToSrgbGamma(xelval       table[],
         else
             radiance = pow((normalized + 0.099) / 1.099, gamma709);
 
+        assert(radiance <= 1.0);
+
         if (radiance < linearCutoffSrgb * normalizer)
             srgb = radiance * linearExpansionSrgb;
         else
             srgb = 1.055 * pow(normalized, oneOverGammaSrgb) - 0.055;
 
+        assert(srgb <= 1.0);
+
         table[i] = srgb * newMaxval + 0.5;
     }
 }
@@ -563,11 +569,15 @@ buildSrgbToBt709Gamma(xelval       table[],
         else
             radiance = pow((normalized + 0.099) / 1.099, gammaSrgb);
 
+        assert(radiance <= 1.0);
+
         if (radiance < linearCutoff709 * normalizer)
             bt709 = radiance * linearExpansion709;
         else
             bt709 = 1.055 * pow(normalized, oneOverGamma709) - 0.055;
 
+        assert(bt709 <= 1.0);
+
         table[i] = bt709 * newMaxval + 0.5;
     }
 }
diff --git a/editor/pnmhisteq.c b/editor/pnmhisteq.c
index 0d315227..8af42019 100644
--- a/editor/pnmhisteq.c
+++ b/editor/pnmhisteq.c
@@ -24,6 +24,8 @@ struct cmdlineInfo {
     */
     const char * inputFileName;
     unsigned int gray;
+    unsigned int noblack;
+    unsigned int nowhite;
     const char * wmap;
     const char * rmap;
     unsigned int verbose;
@@ -55,6 +57,10 @@ parseCommandLine(int argc, char ** argv,
             &wmapSpec,          0);
     OPTENT3(0, "gray",     OPT_FLAG,   NULL,
             &cmdlineP->gray,    0);
+    OPTENT3(0, "noblack",     OPT_FLAG,   NULL,
+            &cmdlineP->noblack,    0);
+    OPTENT3(0, "nowhite",     OPT_FLAG,   NULL,
+            &cmdlineP->nowhite,    0);
     OPTENT3(0, "verbose",  OPT_FLAG,   NULL,
             &cmdlineP->verbose, 0);
 
@@ -91,8 +97,6 @@ computeLuminosityHistogram(xel * const *   const xels,
                            int             const format,
                            bool            const monoOnly,
                            unsigned int ** const lumahistP,
-                           xelval *        const lminP,
-                           xelval *        const lmaxP,
                            unsigned int *  const pixelCountP) {
 /*----------------------------------------------------------------------------
   Scan the image and build the luminosity histogram.  If the input is
@@ -162,25 +166,6 @@ computeLuminosityHistogram(xel * const *   const xels,
 
     *lumahistP = lumahist;
     *pixelCountP = pixelCount;
-    *lminP = lmin;
-    *lmaxP = lmax;
-}
-
-
-
-static void
-findMaxLuma(const xelval * const lumahist,
-            xelval         const maxval,
-            xelval *       const maxLumaP) {
-
-    xelval maxluma;
-    unsigned int i;
-
-    for (i = 0, maxluma = 0; i <= maxval; ++i)
-        if (lumahist[i] > 0)
-            maxluma = i;
-
-    *maxLumaP = maxluma;
 }
 
 
@@ -216,53 +201,159 @@ readMapFile(const char * const rmapFileName,
 
 
 
+static xelval
+maxLumaPresent(const xelval * const lumahist,
+               xelval         const darkestCandidate,
+               xelval         const brightestCandidate) {
+/*----------------------------------------------------------------------------
+    The maximum luminosity in the image, in the range ['darkestCandidate',
+   'brightestCandidate'], given that the luminosity histogram for the image is
+   'lumahist' (lumahist[N] is the number of pixels in the image with
+   luminosity N).
+-----------------------------------------------------------------------------*/
+    xelval maxluma;
+    xelval i;
+
+    for (i = darkestCandidate, maxluma = darkestCandidate;
+         i <= brightestCandidate;
+         ++i) {
+
+        if (lumahist[i] > 0)
+            maxluma = i;
+    }
+    return maxluma;
+}
+
+
+
 static void
-computeMap(const unsigned int * const lumahist,
-           xelval               const maxval,
-           unsigned int         const pixelCount,
-           gray *               const lumamap) {
+equalize(const unsigned int * const lumahist,
+         xelval               const darkestRemap,
+         xelval               const brightestRemap,
+         unsigned int         const remapPixelCount,
+         gray *               const lumamap) {
+/*----------------------------------------------------------------------------
+   Fill in the mappings of luminosities from 'darkestRemap' through
+   'brightestRemap' in 'lumamap', to achieve an equalization based on the
+   histogram 'lumahist'.  lumahist[N] is the number of pixels in the original
+   image of luminosity N.
 
-    /* Calculate initial histogram equalization curve. */
+   'remapPixelCount' is the number of pixels in the given luminosity range.
+   It is redundant with 'lumahist'; we get it for computational convenience.
+-----------------------------------------------------------------------------*/
+    xelval const maxluma =
+        maxLumaPresent(lumahist, darkestRemap, brightestRemap);
+
+    unsigned int const range = brightestRemap - darkestRemap;
     
-    unsigned int i;
-    unsigned int pixsum;
-    xelval maxluma;
+    {
+        xelval origLum;
+        unsigned int pixsum;
 
-    for (i = 0, pixsum = 0; i <= maxval; ++i) {
+        for (origLum = darkestRemap, pixsum = 0;
+             origLum <= brightestRemap;
+             ++origLum) {
             
-        /* With 16 bit grays, the following calculation can
-           overflow a 32 bit long.  So, we do it in floating
-           point.
-        */
+            /* With 16 bit grays, the following calculation can overflow a 32
+               bit long.  So, we do it in floating point.
+            */
 
-        lumamap[i] = ROUNDU((((double) pixsum * maxval)) / pixelCount);
+            lumamap[origLum] =
+                darkestRemap +
+                ROUNDU((((double) pixsum * range)) / remapPixelCount);
+            
+            pixsum += lumahist[origLum];
+        }
 
-        pixsum += lumahist[i];
     }
-
-    findMaxLuma(lumahist, maxval, &maxluma);
-
     {
-        double const lscale = (double)maxval /
-            ((lumahist[maxluma] > 0) ?
-             (double) lumamap[maxluma] : (double) maxval);
+        double const lscale = (double)range /
+            ((lumamap[maxluma] > darkestRemap) ?
+             (double) lumamap[maxluma] - darkestRemap : (double) range);
 
-        unsigned int i;
+        xelval origLum;
 
         /* Normalize so that the brightest pixels are set to maxval. */
 
-        for (i = 0; i <= maxval; ++i)
-            lumamap[i] = MIN(maxval, ROUNDU(lumamap[i] * lscale));
+        for (origLum = darkestRemap; origLum <= brightestRemap; ++origLum)
+            lumamap[origLum] =
+                MIN(brightestRemap, 
+                    darkestRemap + ROUNDU(lumamap[origLum] * lscale));
     }
 }
 
 
 
 static void
+computeMap(const unsigned int * const lumahist,
+           xelval               const maxval,
+           unsigned int         const pixelCount,
+           bool                 const noblack,
+           bool                 const nowhite,
+           gray *               const lumamap) {
+/*----------------------------------------------------------------------------
+  Calculate initial histogram equalization curve.
+
+  'lumahist' is the luminosity histogram for the image; lumahist[N] is
+  the number of pixels that have luminosity N.
+
+  'maxval' is the maxval of the image (ergo the maximum luminosity).
+
+  'pixelCount' is the number of pixels in the image, which is redundant
+  with 'lumahist' but provided for computational convenience.
+   
+  'noblack' means don't include the black pixels in the equalization and
+  make the black pixels in the output the same ones as in the input.
+
+  'nowhite' is equivalent for the white pixels.
+
+  We return the map as *lumamap, where lumamap[N] is the luminosity in the
+  output of a pixel with luminosity N in the input.  Its storage size must
+  be at least 'maxval' + 1.
+-----------------------------------------------------------------------------*/
+    xelval darkestRemap, brightestRemap;
+        /* The lowest and highest luminosity values that we will remap
+           according to the equalization strategy.  They're just 0 and maxval
+           unless modified by 'noblack' and 'nowhite'.
+        */
+    unsigned int remapPixelCount;
+        /* The number of pixels we map according to the equalization
+           strategy; it doesn't include black pixels and white pixels that
+           are excluded from the equalization because of 'noblack' and
+           'nowhite'
+        */
+
+    remapPixelCount = pixelCount;  /* Initial assumption */
+
+    if (noblack) {
+        lumamap[0] = 0;
+        darkestRemap = 1;
+        remapPixelCount -= lumahist[0];
+    } else {
+        darkestRemap = 0;
+    }
+
+    if (nowhite) {
+        lumamap[maxval] = maxval;
+        brightestRemap = maxval - 1;
+        remapPixelCount -= lumahist[maxval];
+    } else {
+        brightestRemap = maxval;
+    }
+
+    equalize(lumahist, darkestRemap, brightestRemap, remapPixelCount,
+             lumamap);
+}
+
+
+
+static void
 getMapping(const char *         const rmapFileName,
            const unsigned int * const lumahist,
            xelval               const maxval,
            unsigned int         const pixelCount,
+           bool                 const noblack,
+           bool                 const nowhite,
            gray **              const lumamapP) {
 /*----------------------------------------------------------------------------
   Calculate the luminosity mapping table which gives the
@@ -275,7 +366,7 @@ getMapping(const char *         const rmapFileName,
     if (rmapFileName)
         readMapFile(rmapFileName, maxval, lumamap);
     else
-        computeMap(lumahist, maxval, pixelCount, lumamap);
+        computeMap(lumahist, maxval, pixelCount, noblack, nowhite, lumamap);
 
     *lumamapP = lumamap;
 }
@@ -410,7 +501,6 @@ main(int argc, char * argv[]) {
 
     struct cmdlineInfo cmdline;
     FILE * ifP;
-    xelval lmin, lmax;
     gray * lumamap;           /* Luminosity map */
     unsigned int * lumahist;  /* Histogram of luminosity values */
     int rows, cols;           /* Rows, columns of input image */
@@ -429,11 +519,12 @@ main(int argc, char * argv[]) {
 
     pm_close(ifP);
 
-    computeLuminosityHistogram(xels, rows, cols, maxval, format,
-                               cmdline.gray, &lumahist, &lmin, &lmax,
-                               &pixelCount);
+    computeLuminosityHistogram(xels, rows, cols, maxval, format, cmdline.gray,
+                               &lumahist, &pixelCount);
 
-    getMapping(cmdline.rmap, lumahist, maxval, pixelCount, &lumamap);
+    getMapping(cmdline.rmap, lumahist, maxval, pixelCount,
+               cmdline.noblack > 0, cmdline.nowhite > 0,
+               &lumamap);
 
     if (cmdline.verbose)
         reportMap(lumahist, maxval, lumamap);
diff --git a/editor/specialty/pgmmorphconv.c b/editor/specialty/pgmmorphconv.c
index abc4e718..2ba2d62d 100644
--- a/editor/specialty/pgmmorphconv.c
+++ b/editor/specialty/pgmmorphconv.c
@@ -17,237 +17,403 @@
 */
 
 #include "pm_c_util.h"
+#include "shhopt.h"
+#include "mallocvar.h"
 #include "pgm.h"
 
 
-/************************************************************
- * Dilate 
- ************************************************************/
 
-static int 
-dilate( bit** template, int trowso2, int tcolso2, 
-        gray** in_image, gray** out_image, 
-        int rows, int cols ){
+enum Operation { ERODE, DILATE, OPEN, CLOSE, GRADIENT };
+
+
+
+struct CmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char * inputFileName;  /* File name of input file */
+    const char * templateFileName;  /* File name of template file */
 
-  int c, r, tc, tr;
-  int templatecount;
-  gray source;
+    enum Operation operation;
+};
 
-  for( c=0; c<cols; ++c)
-    for( r=0; r<rows; ++r )
-      out_image[r][c] = 0;   /* only difference with erode is here and below */
-  
-  /* 
-   *  for each non-black pixel of the template
-   *  add in to out
-   */
 
-  templatecount=0;
 
-  for( tr=-trowso2; tr<=trowso2; ++tr ){
-    for( tc=-tcolso2; tc<=tcolso2; ++tc ){
+static void
+parseCommandLine(int argc, const char ** const argv,
+                 struct CmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry * option_def;
+        /* Instructions to OptParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+    unsigned int option_def_index;
+    unsigned int erode, dilate, open, close, gradient;
 
-      if( template[trowso2+tr][tcolso2+tc] == PBM_BLACK ) continue;
+    MALLOCARRAY_NOFAIL(option_def, 100);
 
-      ++templatecount;
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0,   "erode",        OPT_FLAG,   NULL, &erode,           0);
+    OPTENT3(0,   "dilate",       OPT_FLAG,   NULL, &dilate,          0);
+    OPTENT3(0,   "open",         OPT_FLAG,   NULL, &open,            0);
+    OPTENT3(0,   "close",        OPT_FLAG,   NULL, &close,           0);
+    OPTENT3(0,   "gradient",     OPT_FLAG,   NULL, &gradient,        0);
 
-      for( r= ((tr>0)?0:-tr) ; r< ((tr>0)?(rows-tr):rows) ; ++r ){
-        for( c= ((tc>0)?0:-tc) ; c< ((tc>0)?(cols-tc):cols) ; ++c ){
-          source = in_image[r+tr][c+tc];
-          out_image[r][c] = MAX(source, out_image[r][c]);
-        } /* for c */
-      } /* for r */
-    } /* for tr */
-  } /* for tc */
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = TRUE;  /* We may have parms that are negative numbers */
 
-  return templatecount;
+    pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
 
-} /* dilate */
+    if (erode + dilate + open + close + gradient > 1)
+        pm_error("You may specify at most one of -erode, -dilate, "
+                 "-open, -close, or -gradient");
 
+    if (erode)
+        cmdlineP->operation = ERODE;
+    else if (dilate)
+        cmdlineP->operation = DILATE;
+    else if (open)
+        cmdlineP->operation = OPEN;
+    else if (close)
+        cmdlineP->operation = CLOSE;
+    else if (gradient)
+        cmdlineP->operation = GRADIENT;
+    else
+        cmdlineP->operation = DILATE;
+
+    if (argc-1 < 1)
+        pm_error("You must specify the template file name as an argument");
+    else {
+        cmdlineP->templateFileName = argv[1];
+
+        if (argc-1 < 2)
+            cmdlineP->inputFileName = "-";
+        else {
+            cmdlineP->inputFileName = argv[2];
+            
+            if (argc-1 > 2)
+                pm_error("Too many arguments: %u.  "
+                         "The only possible arguments "
+                         "are the template file name and the input file name",
+                         argc-1);
+        }
+    }
+}
+
+
+
+static void
+readTemplateMatrix(const char *   const fileName,
+                   bit ***        const templateP,
+                   unsigned int * const rowsP,
+                   unsigned int * const colsP) {
+/*----------------------------------------------------------------------------
+  Read in the template matrix.
+-----------------------------------------------------------------------------*/
+    FILE * templateFileP;
+    int cols, rows;
+
+    templateFileP = pm_openr(fileName);
+
+    *templateP = pbm_readpbm(templateFileP, &cols, &rows);
+
+    pm_close(templateFileP);
+
+    if (cols % 2 != 1 || rows % 2 != 1)
+        pm_error("the template matrix must have an odd number of "
+                 "rows and columns" );
+
+        /* the reason is that we want the middle pixel to be the origin */
+    *rowsP = rows;
+    *colsP = cols;
+}
+
+
+
+static void
+setAllPixel(gray **      const image,
+            unsigned int const rows,
+            unsigned int const cols,
+            gray         const value) {
+
+    unsigned int col;
+
+    for (col = 0; col < cols; ++col) {
+        unsigned int row;
+        for (row = 0; row < rows; ++row)
+            image[row][col] = value; 
+    }
+}
+
+
+
+static void
+dilate(bit **         const template,
+       int            const trowso2,
+       int            const tcolso2, 
+       gray **        const inImage,
+       gray **        const outImage, 
+       unsigned int   const rows,
+       unsigned int   const cols,
+       unsigned int * const templateCountP) {
+
+    unsigned int templateCount;
+    int tr;
+
+    setAllPixel(outImage, rows, cols, 0);
+
+    /* for each non-black pixel of the template add in to out */
+
+    for (tr = -trowso2, templateCount = 0; tr <= trowso2; ++tr) {
+        int tc;
+        for (tc = -tcolso2; tc <= tcolso2; ++tc) {
+            int r;
+            if (template[trowso2+tr][tcolso2+tc] != PBM_BLACK) {
+                ++templateCount;
+
+                for (r = ((tr > 0) ? 0 : -tr);
+                     r < ((tr > 0) ? (rows-tr) : rows);
+                     ++r) {
+                    int c;
+                    for (c = ((tc > 0) ? 0 : -tc);
+                         c < ((tc > 0) ? (cols-tc) : cols);
+                         ++c) {
+                        gray const source = inImage[r+tr][c+tc];
+                        outImage[r][c] = MAX(source, outImage[r][c]);
+                    }
+                }
+            }
+        }
+    }
+    *templateCountP = templateCount;
+}
+
+
+
+static void
+erode(bit **         const template,
+      int            const trowso2,
+      int            const tcolso2, 
+      gray **        const inImage,
+      gray **        const outImage, 
+      unsigned int   const rows,
+      unsigned int   const cols,
+      unsigned int * const templateCountP) {
+
+    unsigned int templateCount;
+    int tr;
+
+    setAllPixel(outImage, rows, cols, PGM_MAXMAXVAL);
+
+    /* For each non-black pixel of the template add in to out */
+
+    for (tr = -trowso2, templateCount = 0; tr <= trowso2; ++tr) {
+        int tc;
+        for (tc = -tcolso2; tc <= tcolso2; ++tc) {
+            if (template[trowso2+tr][tcolso2+tc] != PBM_BLACK) {
+                int r;
+                ++templateCount;
+
+                for (r = ((tr > 0) ? 0 : -tr);
+                     r < ((tr > 0) ? (rows-tr) : rows);
+                     ++r){
+                    int c;
+
+                    for (c = ((tc > 0) ? 0 : -tc);
+                         c < ((tc > 0) ? (cols-tc) : cols);
+                         ++c) {
+                        
+                        gray const source = inImage[r+tr][c+tc];
+                        outImage[r][c] = MIN(source, outImage[r][c]);
+      
+                    }
+                }
+            }
+        }
+    }
+    *templateCountP = templateCount;
+}
 
 
-/************************************************************
- * Erode: same as dilate except !!!!
- ************************************************************/
 
-static int 
-erode( bit** template, int trowso2, int tcolso2, 
-       gray** in_image, gray** out_image, 
-       int rows, int cols ){
+static void
+openMorph(bit **         const template,
+          int            const trowso2,
+          int            const tcolso2, 
+          gray **        const inputImage,
+          gray **        const outputImage, 
+          unsigned int   const rows,
+          unsigned int   const cols,
+          unsigned int * const templateCountP) {
 
-  int c, r, tc, tr;
-  int templatecount;
-  gray source;
+    gray ** erodedImage;
+    unsigned int erodedTemplateCount;
 
-  for( c=0; c<cols; ++c)
-    for( r=0; r<rows; ++r )
-      out_image[r][c] = PGM_MAXMAXVAL; /* !!!! */
-  
-  /* 
-   *  for each non-black pixel of the template
-   *  add in to out
-   */
+    erodedImage = pgm_allocarray(cols, rows);
+    
+    erode(template, trowso2, tcolso2, 
+          inputImage, erodedImage, rows, cols, &erodedTemplateCount);
 
-  templatecount=0;
+    dilate(template, trowso2, tcolso2, 
+           erodedImage, outputImage, rows, cols, templateCountP);
 
-  for( tr=-trowso2; tr<=trowso2; ++tr ){
-    for( tc=-tcolso2; tc<=tcolso2; ++tc ){
+    pgm_freearray(erodedImage, rows);
+}
 
-      if( template[trowso2+tr][tcolso2+tc] == PBM_BLACK ) continue;
 
-      ++templatecount;
 
-      for( r= ((tr>0)?0:-tr) ; r< ((tr>0)?(rows-tr):rows) ; ++r ){
-    for( c= ((tc>0)?0:-tc) ; c< ((tc>0)?(cols-tc):cols) ; ++c ){
+static void
+closeMorph(bit **         const template,
+           int            const trowso2,
+           int            const tcolso2, 
+           gray **        const inputImage,
+           gray **        const outputImage, 
+           unsigned int   const rows,
+           unsigned int   const cols,
+           unsigned int * const templateCountP) {
 
-      source = in_image[r+tr][c+tc];
-      out_image[r][c] = MIN(source, out_image[r][c]);
-      
-    } /* for c */
-      } /* for r */
+    gray ** dilatedImage;
+    unsigned int dilatedTemplateCount;
 
+    dilatedImage = pgm_allocarray(cols, rows);
 
+    dilate(template, trowso2, tcolso2, 
+           inputImage, dilatedImage, rows, cols, &dilatedTemplateCount);
 
-    } /* for tr */
-  } /* for tc */
+    erode(template, trowso2, tcolso2, 
+          dilatedImage, outputImage, rows, cols, templateCountP);
 
-  return templatecount;
+    pgm_freearray(dilatedImage, rows);
+}
 
-} /* erode */
 
 
+static void
+subtract(gray **      const subtrahendImage,
+         gray **      const subtractorImage,
+         gray **      const outImage, 
+         unsigned int const rows,
+         unsigned int const cols ) {
 
-/************************************************************
- *  Main
- ************************************************************/
+    /* (I call the minuend the subtrahend and the subtrahend the subtractor,
+       to be consistent with other arithmetic terminology).
+    */
 
+    unsigned int c;
 
-int main( int argc, char* argv[] ){
+    for (c = 0; c < cols; ++c) {
+        unsigned int r;
+        for (r = 0; r < rows; ++r)
+            outImage[r][c] = subtrahendImage[r][c] - subtractorImage[r][c];
+    }
+}
 
-  int argn;
-  char operation;
-  const char* usage = "-dilate|-erode|-open|-close <templatefile> [pgmfile]";
 
-  FILE* tifp;   /* template */
-  int tcols, trows;
-  int tcolso2, trowso2;
-  bit** template;
 
+static void
+gradient(bit **         const template,
+         int            const trowso2,
+         int            const tcolso2, 
+         gray **        const inputImage,
+         gray **        const outputImage, 
+         unsigned int   const rows,
+         unsigned int   const cols,
+         unsigned int * const templateCountP) {
 
-  FILE*  ifp;   /* input image */
-  int cols, rows;
-  gray maxval;
+    gray ** dilatedImage;
+    gray ** erodedImage;
+    unsigned int dilatedTemplateCount;
+    
+    dilatedImage = pgm_allocarray(cols, rows);
+    erodedImage = pgm_allocarray(cols, rows);
 
-  gray** in_image;
-  gray** out_image;
+    dilate(template, trowso2, tcolso2, 
+           inputImage, dilatedImage, rows, cols, &dilatedTemplateCount);
 
-  int templatecount=0;
+    erode(template, trowso2, tcolso2, 
+          inputImage, erodedImage, rows, cols, templateCountP);
 
-  pgm_init( &argc, argv );
+    subtract(dilatedImage, erodedImage, outputImage, rows, cols);
 
-  /*
-   *  parse arguments
-   */ 
-  
-  ifp = stdin;
-  operation = 'd';
+    pgm_freearray(erodedImage, rows );
+    pgm_freearray(dilatedImage, rows );
+}
 
-  argn=1;
-  
-  if( argn == argc ) pm_usage( usage );
-  
-  if( pm_keymatch( argv[argn], "-erode", 2  )) { operation='e'; argn++; }
-  else
-  if( pm_keymatch( argv[argn], "-dilate", 2 )) { operation='d'; argn++; }
-  else
-  if( pm_keymatch( argv[argn], "-open", 2   )) { operation='o'; argn++; }
-  else
-  if( pm_keymatch( argv[argn], "-close", 2  )) { operation='c'; argn++; }
-  
-  if( argn == argc ) pm_usage( usage );
-  
-  tifp = pm_openr( argv[argn++] );
-  
-  if( argn != argc ) ifp = pm_openr( argv[argn++] );
 
-  if( argn != argc ) pm_usage( usage );
 
-  
-  /* 
-   * Read in the template matrix.
-   */
+int
+main(int argc, const char ** argv) {
 
-  template = pbm_readpbm( tifp, &tcols, &trows );
-  pm_close( tifp );
+    struct CmdlineInfo cmdline;
+    FILE * ifP;
+    bit ** template;
+    unsigned int templateCols, templateRows;
+    int cols, rows;
+    gray maxval;
+    gray ** inputImage;
+    gray ** outputImage;
+    unsigned int templateCount;
 
-  if( tcols % 2 != 1 || trows % 2 != 1 )
-    pm_error("the template matrix must have an odd number of "
-             "rows and columns" );
+    pm_proginit(&argc, argv);
 
-  /* the reason is that we want the middle pixel to be the origin */
-  tcolso2 = tcols / 2; /* template coords run from -tcols/2 .. 0 .. +tcols/2 */
-  trowso2 = trows / 2;
+    parseCommandLine(argc, argv, &cmdline);
 
-#if 0
-  fprintf(stderr, "template: %d  x %d\n", trows, tcols);
-  fprintf(stderr, "half: %d  x %d\n", trowso2, tcolso2);
-#endif
+    ifP = pm_openr(cmdline.inputFileName);
 
-  /*
-   * Read in the image
-   */
-  
-  in_image = pgm_readpgm( ifp, &cols, &rows, &maxval);
+    readTemplateMatrix(cmdline.templateFileName,
+                       &template, &templateRows, &templateCols);
 
-  if( cols < tcols || rows < trows )
-    pm_error("the image is smaller than the convolution matrix" );
+    /* Template coords run from -templateCols/2 .. 0 .. + templateCols/2 */
   
-#if 0
-  fprintf(stderr, "image: %d  x %d (%d)\n", rows, cols, maxval);
-#endif
-
-  /* 
-   * Allocate  output buffer and initialize with min or max value 
-   */
+    inputImage = pgm_readpgm(ifP, &cols, &rows, &maxval);
 
-  out_image = pgm_allocarray( cols, rows );
+    if (cols < templateCols || rows < templateRows)
+        pm_error("the image is smaller than the convolution matrix" );
   
-  if( operation == 'd' ){
-    templatecount = dilate(template, trowso2, tcolso2, 
-               in_image, out_image, rows, cols);
-  } 
-  else if( operation == 'e' ){
-    templatecount = erode(template, trowso2, tcolso2, 
-              in_image, out_image, rows, cols);
-  }
-  else if( operation == 'o' ){
-    gray ** eroded_image;
-    eroded_image = pgm_allocarray( cols, rows );
-    templatecount = erode(template, trowso2, tcolso2, 
-                          in_image, eroded_image, rows, cols);
-    templatecount = dilate(template, trowso2, tcolso2, 
-                           eroded_image, out_image, rows, cols);
-    pgm_freearray( eroded_image, rows );
-  }
-  else if( operation == 'c' ){
-    gray ** dilated_image;
-    dilated_image = pgm_allocarray( cols, rows );
-    templatecount = dilate(template, trowso2, tcolso2, 
-                           in_image, dilated_image, rows, cols);
-    templatecount = erode(template, trowso2, tcolso2, 
-                          dilated_image, out_image, rows, cols);
-    pgm_freearray( dilated_image, rows );
-  }
+    outputImage = pgm_allocarray(cols, rows);
   
-  if(templatecount == 0 ) pm_error( "The template was empty!" );
-
-  pgm_writepgm( stdout, out_image, cols, rows, maxval, 1 );
-
-  pgm_freearray( out_image, rows );
-  pgm_freearray( in_image, rows );
-  pm_close( ifp );
-
-  exit( 0 );
-
-} /* main */
+    switch (cmdline.operation) {
+    case DILATE:
+        dilate(template, templateRows/2, templateCols/2,
+               inputImage, outputImage, rows, cols,
+               &templateCount);
+        break;
+    case ERODE:
+        erode(template, templateRows/2, templateCols/2,
+              inputImage, outputImage, rows, cols,
+              &templateCount);
+        break;
+    case OPEN:
+        openMorph(template, templateRows/2, templateCols/2,
+              inputImage, outputImage, rows, cols,
+              &templateCount);
+        break;
+    case CLOSE:
+        closeMorph(template, templateRows/2, templateCols/2,
+                   inputImage, outputImage, rows, cols,
+                   &templateCount);
+        break;
+    case GRADIENT:
+        gradient(template, templateRows/2, templateCols/2,
+                 inputImage, outputImage, rows, cols,
+                 &templateCount);
+        break;
+    }
+
+    if (templateCount == 0)
+        pm_error( "The template was empty!" );
+
+    pgm_writepgm(stdout, outputImage, cols, rows, maxval, 0);
+
+    pgm_freearray(outputImage, rows);
+    pgm_freearray(inputImage, rows);
+    pm_close(ifP);
+
+    return 0;
+}
 
diff --git a/lib/Makefile b/lib/Makefile
index b47c3aae..19557909 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -68,6 +68,7 @@ SCRIPTS =
 BINARIES = 
 
 OMIT_LIBRARY_RULE = 1
+ALL_INTERNAL_HEADER_FILES_ARE_QUALIFIED = Y
 include $(SRCDIR)/common.mk
 
 # The following must go after common.mk because $(LIBNETPBM) may 
diff --git a/lib/colorname.c b/lib/colorname.c
index c23eb13f..123de75e 100644
--- a/lib/colorname.c
+++ b/lib/colorname.c
@@ -15,13 +15,14 @@
 #define _BSD_SOURCE 1      /* Make sure strdup() is in string.h */
 #define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
 
-#include "pm_c_util.h"
+#include "netpbm/pm_c_util.h"
 #include <ctype.h>
 #include <string.h>
 #include <stdlib.h>
 #include <errno.h>
 
-#include "nstring.h"
+#include "netpbm/nstring.h"
+
 #include "colorname.h"
 
 static int lineNo;
diff --git a/lib/libpam.c b/lib/libpam.c
index 2d82ffef..cc6368e1 100644
--- a/lib/libpam.c
+++ b/lib/libpam.c
@@ -23,9 +23,10 @@
 
 #include <math.h>
 
-#include "pm_c_util.h"
-#include "mallocvar.h"
-#include "nstring.h"
+#include "netpbm/pm_c_util.h"
+#include "netpbm/mallocvar.h"
+#include "netpbm/nstring.h"
+
 #include "pam.h"
 #include "ppm.h"
 #include "libpbm.h"
@@ -783,8 +784,8 @@ validateMinDepth(const struct pam * const pamP,
 static void
 interpretTupleType(struct pam * const pamP) {
 /*----------------------------------------------------------------------------
-   Fill in redundant convenience fields in *pamP with information implied by
-   the pamP->tuple_type implies:
+   Fill in redundant convenience fields in *pamP with information the
+   pamP->tuple_type value implies:
 
      visual
      colorDepth
diff --git a/lib/libpamcolor.c b/lib/libpamcolor.c
index f945c0a5..f3ca9a86 100644
--- a/lib/libpamcolor.c
+++ b/lib/libpamcolor.c
@@ -20,11 +20,13 @@
 #include <string.h>
 #include <limits.h>
 
-#include "pm_c_util.h"
+#include "netpbm/pm_c_util.h"
+
 #include "pam.h"
 #include "ppm.h"
 
 
+
 tuple
 pnm_parsecolor(const char * const colorname,
                sample       const maxval) {
diff --git a/lib/libpamd.c b/lib/libpamd.c
index 77c7fc54..952150b4 100644
--- a/lib/libpamd.c
+++ b/lib/libpamd.c
@@ -20,9 +20,10 @@
 #include <assert.h>
 #include <stdlib.h>
 
-#include "pm_config.h"
-#include "pm_c_util.h"
-#include "mallocvar.h"
+#include "netpbm/pm_config.h"
+#include "netpbm/pm_c_util.h"
+#include "netpbm/mallocvar.h"
+
 #include "pam.h"
 #include "ppmdfont.h"
 
diff --git a/lib/libpammap.c b/lib/libpammap.c
index ef373e81..55e1d3ff 100644
--- a/lib/libpammap.c
+++ b/lib/libpammap.c
@@ -17,9 +17,10 @@
 
 #include <assert.h>
 
-#include "pm_c_util.h"
-#include "mallocvar.h"
-#include "nstring.h"
+#include "netpbm/pm_c_util.h"
+#include "netpbm/mallocvar.h"
+#include "netpbm/nstring.h"
+
 #include "pam.h"
 #include "pammap.h"
 
diff --git a/lib/libpamn.c b/lib/libpamn.c
index b30bde53..26dbbfe3 100644
--- a/lib/libpamn.c
+++ b/lib/libpamn.c
@@ -11,9 +11,10 @@
 
 #include <assert.h>
 
-#include "pm_c_util.h"
-#include "mallocvar.h"
-#include "nstring.h"
+#include "netpbm/pm_c_util.h"
+#include "netpbm/mallocvar.h"
+#include "netpbm/nstring.h"
+
 #include "pam.h"
 #include "fileio.h"
 #include "pm_gamma.h"
diff --git a/lib/libpamread.c b/lib/libpamread.c
index cbac18c1..74b1ab83 100644
--- a/lib/libpamread.c
+++ b/lib/libpamread.c
@@ -19,9 +19,10 @@
 #include <limits.h>
 #include <assert.h>
 
-#include "pm_config.h"
+#include "netpbm/pm_config.h"
+#include "netpbm/nstring.h"
+
 #include "fileio.h"
-#include "nstring.h"
 #include "pam.h"
 
 
diff --git a/lib/libpamwrite.c b/lib/libpamwrite.c
index 9f86e359..29ddeaa2 100644
--- a/lib/libpamwrite.c
+++ b/lib/libpamwrite.c
@@ -21,8 +21,9 @@
 #include <assert.h>
 #include <math.h>
 
-#include "pm_config.h"
-#include "pm_c_util.h"
+#include "netpbm/pm_config.h"
+#include "netpbm/pm_c_util.h"
+
 #include "pam.h"
 
 
diff --git a/lib/libpbm1.c b/lib/libpbm1.c
index c96779ed..49ab7fdf 100644
--- a/lib/libpbm1.c
+++ b/lib/libpbm1.c
@@ -18,9 +18,10 @@
 
 #include <stdio.h>
 
-#include "pm_c_util.h"
-#include "mallocvar.h"
-#include "shhopt.h"
+#include "netpbm/pm_c_util.h"
+#include "netpbm/mallocvar.h"
+#include "netpbm/shhopt.h"
+
 #include "pbm.h"
 
 
diff --git a/lib/libpbm3.c b/lib/libpbm3.c
index 020e1558..c8389824 100644
--- a/lib/libpbm3.c
+++ b/lib/libpbm3.c
@@ -12,7 +12,8 @@
 
 #include <assert.h>
 
-#include "pm_c_util.h"
+#include "netpbm/pm_c_util.h"
+
 #include "pbm.h"
 
 #ifndef PACKBITS_SSE
diff --git a/lib/libpbmfont.c b/lib/libpbmfont.c
index 68b1ffb4..86a713aa 100644
--- a/lib/libpbmfont.c
+++ b/lib/libpbmfont.c
@@ -18,9 +18,10 @@
 #include <string.h>
 #include <ctype.h>
 
-#include "pm_c_util.h"
-#include "mallocvar.h"
-#include "nstring.h"
+#include "netpbm/pm_c_util.h"
+#include "netpbm/mallocvar.h"
+#include "netpbm/nstring.h"
+
 #include "pbmfont.h"
 #include "pbm.h"
 
diff --git a/lib/libpgm1.c b/lib/libpgm1.c
index de5aedde..9ef5d2c1 100644
--- a/lib/libpgm1.c
+++ b/lib/libpgm1.c
@@ -20,6 +20,9 @@
 #include <stdio.h>
 #include <errno.h>
 
+#include "netpbm/mallocvar.h"
+#include "netpbm/nstring.h"
+
 #include "pgm.h"
 #include "libpgm.h"
 #include "pbm.h"
@@ -27,8 +30,6 @@
 #include "pam.h"
 #include "libpam.h"
 #include "fileio.h"
-#include "mallocvar.h"
-#include "nstring.h"
 
 
 gray *
diff --git a/lib/libpgm2.c b/lib/libpgm2.c
index 21e5ef9a..80b5cf42 100644
--- a/lib/libpgm2.c
+++ b/lib/libpgm2.c
@@ -13,8 +13,8 @@
 #include <string.h>
 #include <errno.h>
 
-#include "pm_c_util.h"
-#include "mallocvar.h"
+#include "netpbm/pm_c_util.h"
+#include "netpbm/mallocvar.h"
 #include "pgm.h"
 
 
diff --git a/lib/libpm.c b/lib/libpm.c
index cf5ff17b..4374bbe3 100644
--- a/lib/libpm.c
+++ b/lib/libpm.c
@@ -34,7 +34,7 @@
 #include "netpbm/shhopt.h"
 #include "compile.h"
 
-#include "netpbm/pm.h"
+#include "pm.h"
 
 /* The following are set by pm_init(), then used by subsequent calls to other
    pm_xxx() functions.
diff --git a/lib/libpnm1.c b/lib/libpnm1.c
index 045b7afc..adfacd03 100644
--- a/lib/libpnm1.c
+++ b/lib/libpnm1.c
@@ -13,6 +13,8 @@
 #include <string.h>
 #include <errno.h>
 
+#include "netpbm/mallocvar.h"
+
 #include "pnm.h"
 
 #include "ppm.h"
@@ -27,8 +29,6 @@
 #include "pam.h"
 #include "libpam.h"
 
-#include "mallocvar.h"
-
 
 
 xel *
diff --git a/lib/libpnm2.c b/lib/libpnm2.c
index 01ffa389..fa4bb8be 100644
--- a/lib/libpnm2.c
+++ b/lib/libpnm2.c
@@ -10,7 +10,7 @@
 ** implied warranty.
 */
 
-#include "pm_c_util.h"
+#include "netpbm/pm_c_util.h"
 
 #include "pnm.h"
 
@@ -20,6 +20,8 @@
 
 #include "pbm.h"
 
+
+
 void
 pnm_writepnminit(FILE * const fileP, 
                  int    const cols, 
diff --git a/lib/libppm1.c b/lib/libppm1.c
index 2e17e894..a3be5d3b 100644
--- a/lib/libppm1.c
+++ b/lib/libppm1.c
@@ -20,6 +20,8 @@
 #include <stdio.h>
 #include <errno.h>
 
+#include "netpbm/mallocvar.h"
+#include "netpbm/nstring.h"
 #include "ppm.h"
 #include "libppm.h"
 #include "pgm.h"
@@ -29,8 +31,6 @@
 #include "pam.h"
 #include "libpam.h"
 #include "fileio.h"
-#include "mallocvar.h"
-#include "nstring.h"
 
 
 pixel *
diff --git a/lib/libppm2.c b/lib/libppm2.c
index fd26d2d8..694ecdd7 100644
--- a/lib/libppm2.c
+++ b/lib/libppm2.c
@@ -14,8 +14,8 @@
 #include <errno.h>
 
 
-#include "pm_c_util.h"
-#include "mallocvar.h"
+#include "netpbm/pm_c_util.h"
+#include "netpbm/mallocvar.h"
 #include "ppm.h"
 
 void
diff --git a/lib/libppmcmap.c b/lib/libppmcmap.c
index 67a85ebf..f78d0516 100644
--- a/lib/libppmcmap.c
+++ b/lib/libppmcmap.c
@@ -12,10 +12,10 @@
 ** implied warranty.
 */
 
-#include "pm_config.h"
-#include "pm_c_util.h"
-#include "nstring.h"
-#include "mallocvar.h"
+#include "netpbm/pm_config.h"
+#include "netpbm/pm_c_util.h"
+#include "netpbm/nstring.h"
+#include "netpbm/mallocvar.h"
 #include "ppm.h"
 #include "ppmcmap.h"
 
diff --git a/lib/libppmcolor.c b/lib/libppmcolor.c
index 3d98f5e7..aee8fd83 100644
--- a/lib/libppmcolor.c
+++ b/lib/libppmcolor.c
@@ -17,9 +17,9 @@
 #include <string.h>
 #include <math.h>
 
-#include "pm_c_util.h"
-#include "mallocvar.h"
-#include "nstring.h"
+#include "netpbm/pm_c_util.h"
+#include "netpbm/mallocvar.h"
+#include "netpbm/nstring.h"
 #include "ppm.h"
 #include "colorname.h"
 
diff --git a/lib/libppmd.c b/lib/libppmd.c
index d6f08b37..262679ec 100644
--- a/lib/libppmd.c
+++ b/lib/libppmd.c
@@ -18,9 +18,9 @@
 #include <assert.h>
 #include <stdlib.h>
 
-#include "pm_config.h"
-#include "pm_c_util.h"
-#include "mallocvar.h"
+#include "netpbm/pm_config.h"
+#include "netpbm/pm_c_util.h"
+#include "netpbm/mallocvar.h"
 #include "ppm.h"
 #include "ppmdfont.h"
 #include "ppmdraw.h"
diff --git a/lib/libppmfloyd.c b/lib/libppmfloyd.c
index ec6256ff..d2b9c7b1 100644
--- a/lib/libppmfloyd.c
+++ b/lib/libppmfloyd.c
@@ -22,9 +22,9 @@ do Floyd-Steinberg.
 ** implied warranty.
 */
 
+#include "netpbm/mallocvar.h"
 #include "ppm.h"
 #include "ppmfloyd.h"
-#include "mallocvar.h"
 
 
 
diff --git a/lib/libppmfuzzy.c b/lib/libppmfuzzy.c
index 2a54e896..f1573c68 100644
--- a/lib/libppmfuzzy.c
+++ b/lib/libppmfuzzy.c
@@ -5,8 +5,8 @@
   <kenan@unix.ba> in 2006.
 =============================================================================*/
 
-#include "pm_c_util.h"
-#include "nstring.h"
+#include "netpbm/pm_c_util.h"
+#include "netpbm/nstring.h"
 #include "ppm.h"
 
 typedef double fzLog;
diff --git a/lib/libsystem.c b/lib/libsystem.c
index d21d0403..77f874cb 100644
--- a/lib/libsystem.c
+++ b/lib/libsystem.c
@@ -24,8 +24,8 @@
 #include <signal.h>
 #include <sys/wait.h>
 
-#include "pm_c_util.h"
-#include "mallocvar.h"
+#include "netpbm/pm_c_util.h"
+#include "netpbm/mallocvar.h"
 #include "pm.h"
 #include "pm_system.h"
 
diff --git a/lib/pam.h b/lib/pam.h
index 2726092b..c58e36a2 100644
--- a/lib/pam.h
+++ b/lib/pam.h
@@ -163,6 +163,9 @@ struct pam {
 #define PAM_PBM_TUPLETYPE "BLACKANDWHITE"
 #define PAM_PGM_TUPLETYPE "GRAYSCALE"
 #define PAM_PPM_TUPLETYPE "RGB"
+#define PAM_PBM_ALPHA_TUPLETYPE "BLACKANDWHITE_ALPHA"
+#define PAM_PGM_ALPHA_TUPLETYPE "GRAYSCALE_ALPHA"
+#define PAM_PPM_ALPHA_TUPLETYPE "RGB_ALPHA"
 
 #define PAM_PBM_BLACK PAM_BLACK
 #define PAM_PBM_WHITE PAM_BW_WHITE
diff --git a/lib/path.c b/lib/path.c
index 79985109..82fd874c 100644
--- a/lib/path.c
+++ b/lib/path.c
@@ -10,8 +10,8 @@
 
 #include <assert.h>
 
-#include "pm_c_util.h"
-#include "mallocvar.h"
+#include "netpbm/pm_c_util.h"
+#include "netpbm/mallocvar.h"
 #include "ppm.h"
 #include "ppmdfont.h"
 #include "ppmdraw.h"
diff --git a/lib/pmfileio.c b/lib/pmfileio.c
index b629fd23..8176ae6a 100644
--- a/lib/pmfileio.c
+++ b/lib/pmfileio.c
@@ -29,6 +29,7 @@
 #define _LARGE_FILE_API
     /* This makes the the x64() functions available on AIX */
 
+#include "netpbm/pm_config.h"
 #include <unistd.h>
 #include <assert.h>
 #include <stdio.h>
@@ -36,13 +37,13 @@
 #include <stdarg.h>
 #include <string.h>
 #include <errno.h>
-#ifdef __DJGPP__
-  #include <io.h>
+#if HAVE_IO_H
+  #include <io.h>  /* For mktemp */
 #endif
 
-#include "pm_c_util.h"
-#include "mallocvar.h"
-#include "nstring.h"
+#include "netpbm/pm_c_util.h"
+#include "netpbm/mallocvar.h"
+#include "netpbm/nstring.h"
 
 #include "pm.h"
 
@@ -249,13 +250,10 @@ pm_make_tmpfile_fd(int *         const fdP,
                    const char ** const filenameP) {
 
     const char * filenameTemplate;
-    unsigned int fnamelen;
     const char * tmpdir;
     const char * dirseparator;
     const char * error;
 
-    fnamelen = strlen(pm_progname) + 10; /* "/" + "_XXXXXX\0" */
-
     tmpdir = tmpDir();
 
     if (tmpdir[strlen(tmpdir) - 1] == '/')
@@ -306,17 +304,130 @@ pm_make_tmpfile(FILE **       const filePP,
 
 
 
+bool const canUnlinkOpen = 
+#if CAN_UNLINK_OPEN
+    1
+#else
+    0
+#endif
+    ;
+
+
+
+typedef struct UnlinkListEntry {
+/*----------------------------------------------------------------------------
+   This is an entry in the linked list of files to close and unlink as the
+   program exits.
+-----------------------------------------------------------------------------*/
+    struct UnlinkListEntry * next;
+    int                      fd;
+    char                     fileName[1];  /* Actually variable length */
+} UnlinkListEntry;
+
+static UnlinkListEntry * unlinkListP;
+
+
+
+static void
+unlinkTempFiles(void) {
+/*----------------------------------------------------------------------------
+  Close and unlink (so presumably delete) the files in the list
+  *unlinkListP.
+
+  This is an atexit function.
+-----------------------------------------------------------------------------*/
+    while (unlinkListP) {
+        UnlinkListEntry * const firstEntryP = unlinkListP;
+
+        unlinkListP = unlinkListP->next;
+
+        close(firstEntryP->fd);
+        unlink(firstEntryP->fileName);
+
+        free(firstEntryP);
+    }
+}
+
+
+
+static UnlinkListEntry *
+newUnlinkListEntry(const char * const fileName,
+                   int          const fd) {
+
+    UnlinkListEntry * const unlinkListEntryP =
+        malloc(sizeof(*unlinkListEntryP) + strlen(fileName) + 1);
+
+    if (unlinkListEntryP) {
+        strcpy(unlinkListEntryP->fileName, fileName);
+        unlinkListEntryP->fd   = fd;
+        unlinkListEntryP->next = NULL;
+    }
+    return unlinkListEntryP;
+}
+
+
+
+static void
+addUnlinkListEntry(const char * const fileName,
+                   int          const fd) {
+
+    UnlinkListEntry * const unlinkListEntryP =
+        newUnlinkListEntry(fileName, fd);
+
+    if (unlinkListEntryP) {
+        unlinkListEntryP->next = unlinkListP;
+        unlinkListP = unlinkListEntryP;
+    }
+}
+
+
+
+static void
+scheduleUnlinkAtExit(const char * const fileName,
+                     int          const fd) {
+/*----------------------------------------------------------------------------
+   Set things up to have the file unlinked as the program exits.
+
+   This is messy and probably doesn't work in all situations; it is a hack
+   to get Unix code essentially working on Windows, without messing up the
+   code too badly for Unix.
+-----------------------------------------------------------------------------*/
+    static bool unlinkListEstablished = false;
+    
+    if (!unlinkListEstablished) {
+        atexit(unlinkTempFiles);
+        unlinkListP = NULL;
+        unlinkListEstablished = true;
+    }
+
+    addUnlinkListEntry(fileName, fd);
+}
+
+
+
+static void
+arrangeUnlink(const char * const fileName,
+              int          const fd) {
+
+    if (canUnlinkOpen)
+        unlink(fileName);
+    else
+        scheduleUnlinkAtExit(fileName, fd);
+}
+
+
+
 FILE * 
 pm_tmpfile(void) {
 
     FILE * fileP;
-    const char * tmpfile;
+    const char * tmpfileNm;
 
-    pm_make_tmpfile(&fileP, &tmpfile);
+    pm_make_tmpfile(&fileP, &tmpfileNm);
 
-    unlink(tmpfile);
+    arrangeUnlink(tmpfileNm, fileno(fileP));
 
-    pm_strfree(tmpfile);
+    pm_strfree(tmpfileNm);
 
     return fileP;
 }
@@ -327,13 +438,13 @@ int
 pm_tmpfile_fd(void) {
 
     int fd;
-    const char * tmpfile;
+    const char * tmpfileNm;
 
-    pm_make_tmpfile_fd(&fd, &tmpfile);
+    pm_make_tmpfile_fd(&fd, &tmpfileNm);
 
-    unlink(tmpfile);
+    arrangeUnlink(tmpfileNm, fd);
 
-    pm_strfree(tmpfile);
+    pm_strfree(tmpfileNm);
 
     return fd;
 }
diff --git a/lib/ppmdfont.c b/lib/ppmdfont.c
index c0db3f51..f64cd10f 100644
--- a/lib/ppmdfont.c
+++ b/lib/ppmdfont.c
@@ -3,8 +3,8 @@
 #include <errno.h>
 #include <string.h>
 
+#include "netpbm/mallocvar.h"
 #include "pm.h"
-#include "mallocvar.h"
 #include "ppmdfont.h"
 
 
diff --git a/lib/util/io.c b/lib/util/io.c
index 5fe959a9..54ecb6a8 100644
--- a/lib/util/io.c
+++ b/lib/util/io.c
@@ -15,8 +15,7 @@ pm_freadline(FILE *        const fileP,
              const char ** const errorP) {
 /*----------------------------------------------------------------------------
    Read a line (assuming the file is text with lines delimited by newlines)
-   for file *fileP.  Return that line in newly malloced storage as
-   *lineP.
+   from file *fileP.  Return that line in newly malloced storage as *lineP.
 
    The newline delimiter is not part of the line.
 
@@ -89,3 +88,5 @@ pm_freadline(FILE *        const fileP,
         }
     }
 }
+
+
diff --git a/netpbm.c b/netpbm.c
index 716af766..f647ad15 100644
--- a/netpbm.c
+++ b/netpbm.c
@@ -56,26 +56,41 @@ main(int argc, char *argv[]) {
 #include "mergetrylist"
 
     /* Add the obsolete names for some programs */
+    TRY("bmptoppm", main_bmptopnm);
     TRY("gemtopbm", main_gemtopnm);
-    TRY("pnminterp", main_pamstretch);
+    TRY("icontopbm", main_sunicontopnm);
+    TRY("pbmtoicon", main_pbmtosunicon);
+    TRY("pgmedge", main_pamedge);
+    TRY("pgmnorm", main_pnmnorm);
     TRY("pgmoil", main_pamoil);
+    TRY("pgmslice", main_pamslice);
+    TRY("pnmarith", main_pamarith);
+    TRY("pngtopnm", main_pngtopam);
+    TRY("pnmarith", main_pamarith);
+    TRY("pnmcomp", main_pamcomp);
+    TRY("pnmcut", main_pamcut);
+    TRY("pnmdepth", main_pamdepth);
+    TRY("pnmfile", main_pamfile);
+    TRY("pnminterp", main_pamstretch);
+    TRY("pnmenlarge", main_pamenlarge);
+    TRY("pnmscale", main_pamscale);
+    TRY("pnmsplit", main_pamsplit);
+    TRY("pnmtofits", main_pamtofits);
+    TRY("pnmtopnm", main_pamtopnm);
+    TRY("ppmnorm", main_pnmnorm);
+    TRY("ppmtotga", main_pamtotga);
+    TRY("ppmtouil", main_pamtouil);
 
     /* We don't do the ppmtojpeg alias because if user doesn't have a JPEG
        library, there is no main_pnmtojpeg library.  The right way to do
        this is to have these TRY's generated by the subdirectory makes,
        which would know whether pnmtojpeg was built into the merged binary
-       or not.  But that's too much work.
+       or not.  But that's too much work.  Same with TIFF converters.
 
     TRY("ppmtojpeg", main_pnmtojpeg); 
     TRY("pngtopnm", main_pngtopam); 
+    TRY("pnmtotiff", main_pamtotiff);
     */
-    TRY("bmptoppm", main_bmptopnm);
-    TRY("pgmnorm", main_pnmnorm);
-    TRY("ppmnorm", main_pnmnorm);
-    TRY("ppmtotga", main_pamtotga);
-    TRY("pnmarith", main_pamarith);
-    TRY("pnmfile", main_pamfile);
-    TRY("pgmedge", main_pamedge);
 
     fprintf(stderr,"'%s' is an unknown Netpbm program name \n", cp );
     exit(1);
diff --git a/pm_config.in.h b/pm_config.in.h
index 1e54f06e..c64fc093 100644
--- a/pm_config.in.h
+++ b/pm_config.in.h
@@ -137,7 +137,11 @@
 #define HAVE_SETMODE
 #endif
 
-/* #define HAVE_SETMODE */
+#if MSVCRT || defined(__CYGWIN__) || defined(DJGPP)
+#define HAVE_IO_H 1
+#else
+#define HAVE_IO_H 0
+#endif
 
 #if (defined(__GLIBC__) || defined(__GNU_LIBRARY__) || defined(__APPLE__)) || defined(__NetBSD__)
   #define HAVE_VASPRINTF 1
@@ -145,6 +149,15 @@
   #define HAVE_VASPRINTF 0
 #endif
 
+/* On Windows, unlinking a file is deleting it, and you can't delete an open
+   file, so unlink of an open file fails.  The errno is (incorrectly) EACCES.
+*/
+#if MSVCRT || defined(__CYGWIN__) || defined(DJGPP)
+  #define CAN_UNLINK_OPEN 0
+#else
+  #define CAN_UNLINK_OPEN 1
+#endif
+
 #ifdef __amigaos__
 #include <clib/exec_protos.h>
 #define getpid() ((pid_t)FindTask(NULL))
diff --git a/test/Test-Order b/test/Test-Order
index 7467ba0b..3ab897ca 100644
--- a/test/Test-Order
+++ b/test/Test-Order
@@ -128,6 +128,8 @@ png-roundtrip.test
 ps-roundtrip.test
 ps-alt-roundtrip.test
 sgi-roundtrip.test
+sbig-roundtrip.test
+st4-roundtrip.test
 sunrast-roundtrip.test
 targa-roundtrip.test
 tiff-roundtrip.test
diff --git a/test/all-in-place.ok b/test/all-in-place.ok
index d37fd561..63a0f232 100644
--- a/test/all-in-place.ok
+++ b/test/all-in-place.ok
@@ -182,6 +182,8 @@ pgmtolispm: ok
 pgmtopbm: ok
 pgmtopgm: ok
 pgmtoppm: ok
+pgmtosbig: ok
+pgmtost4: ok
 pi1toppm: ok
 pi3topbm: ok
 picttoppm: ok
@@ -318,6 +320,7 @@ spctoppm: ok
 spottopgm: ok
 sputoppm: ok
 srftopam: ok
+st4topgm: ok
 sunicontopnm: ok
 svgtopam: ok
 tgatoppm: ok
@@ -334,6 +337,7 @@ xwdtopnm: ok
 ybmtopbm: ok
 yuvsplittoppm: ok
 yuvtoppm: ok
+yuy2topam: ok
 zeisstopnm: ok
 fiascotopnm: ok
 manweb: ok
diff --git a/test/all-in-place.test b/test/all-in-place.test
index d6ccdb3c..d307459a 100755
--- a/test/all-in-place.test
+++ b/test/all-in-place.test
@@ -221,6 +221,8 @@ ordinary_testprogs="\
   pgmtopbm \
   pgmtopgm \
   pgmtoppm \
+  pgmtosbig \
+  pgmtost4 \
   pi1toppm \
   pi3topbm \
   picttoppm \
@@ -357,6 +359,7 @@ ordinary_testprogs="\
   spottopgm \
   sputoppm \
   srftopam \
+  st4topgm \
   sunicontopnm \
   svgtopam \
   tgatoppm \
@@ -373,6 +376,7 @@ ordinary_testprogs="\
   ybmtopbm \
   yuvsplittoppm \
   yuvtoppm \
+  yuy2topam \
   zeisstopnm \
 "
 
@@ -452,7 +456,7 @@ ${PBM_TESTPREFIX}ppmfade -f /dev/zero -base /dev/null > /dev/null 2> /dev/null
 
 for i in pamstretch-gen pcdovtoppm
   do
-  ${PBM_TESTPREFIX}$i > /dev/zero
+  ${PBM_TESTPREFIX}$i > /dev/null
      testExitStatus $i 1 $?
   done
 
diff --git a/test/sbig-roundtrip.ok b/test/sbig-roundtrip.ok
new file mode 100644
index 00000000..430f3f5f
--- /dev/null
+++ b/test/sbig-roundtrip.ok
@@ -0,0 +1 @@
+1571496937 33838
diff --git a/test/sbig-roundtrip.test b/test/sbig-roundtrip.test
new file mode 100755
index 00000000..7a339470
--- /dev/null
+++ b/test/sbig-roundtrip.test
@@ -0,0 +1,12 @@
+#! /bin/bash
+# This script tests: pgmtosbig sbigtopgm
+# Also requires: pamchannel
+
+  alias sbigtopgm="${PBM_TESTPREFIX}sbigtopgm"
+  alias pgmtosbig="${PBM_TESTPREFIX}pgmtosbig"
+  shopt -s expand_aliases
+
+# Should produce 1571496937 33838, cksum of testimg.red
+
+pamchannel -infile=testimg.ppm -tupletype="GRAYSCALE" 0 | pamtopnm | \
+  pgmtosbig | sbigtopgm | cksum
diff --git a/test/st4-roundtrip.ok b/test/st4-roundtrip.ok
new file mode 100644
index 00000000..e43cd037
--- /dev/null
+++ b/test/st4-roundtrip.ok
@@ -0,0 +1 @@
+185194654 31695
diff --git a/test/st4-roundtrip.test b/test/st4-roundtrip.test
new file mode 100755
index 00000000..bd3da5f1
--- /dev/null
+++ b/test/st4-roundtrip.test
@@ -0,0 +1,17 @@
+#! /bin/bash
+# This script tests: pgmtost4 st4topgm
+# Also requires: pamchannel pamtopnm pamcut
+
+  alias st4topgm="${PBM_TESTPREFIX}st4topgm"
+  alias pgmtost4="${PBM_TESTPREFIX}pgmtost4"
+  shopt -s expand_aliases
+
+# Input image of pgmtost4 must by 192x165
+
+# Should produce 185194654 31695 which is the output of:
+# pamchannel -infile=testimg.ppm -tupletype="GRAYSCALE" 0 | \
+#   pamtopnm | pamcut -pad 0 0 192 165 | cksum
+
+pamchannel -infile=testimg.ppm -tupletype="GRAYSCALE" 0 | \
+  pamtopnm | pamcut -pad 0 0 192 165 | \
+  pgmtost4 | st4topgm - | cksum
diff --git a/version.mk b/version.mk
index 9c587ca4..948133da 100644
--- a/version.mk
+++ b/version.mk
@@ -1,3 +1,3 @@
 NETPBM_MAJOR_RELEASE = 10
-NETPBM_MINOR_RELEASE = 69
-NETPBM_POINT_RELEASE = 7
+NETPBM_MINOR_RELEASE = 70
+NETPBM_POINT_RELEASE = 0