about summary refs log tree commit diff
path: root/converter
diff options
context:
space:
mode:
authorgiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2015-03-29 23:07:03 +0000
committergiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2015-03-29 23:07:03 +0000
commitdd2bedb00517b296115cc56a66300194ab9a25d9 (patch)
tree7a68e60481770e1d0dd7d03c229d96d42cd93e13 /converter
parent04c4c8a3ce3ccb391877c25039c52a1a9cc07bd4 (diff)
downloadnetpbm-mirror-dd2bedb00517b296115cc56a66300194ab9a25d9.tar.gz
netpbm-mirror-dd2bedb00517b296115cc56a66300194ab9a25d9.tar.xz
netpbm-mirror-dd2bedb00517b296115cc56a66300194ab9a25d9.zip
Release 10.70.00
git-svn-id: http://svn.code.sf.net/p/netpbm/code/advanced@2442 9d0c8265-081b-0410-96cb-a4ca84ce46f8
Diffstat (limited to 'converter')
-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
17 files changed, 1232 insertions, 297 deletions
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;
+}
+
+