about summary refs log tree commit diff
diff options
context:
space:
mode:
authorgiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2007-03-29 02:36:03 +0000
committergiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2007-03-29 02:36:03 +0000
commit3283c876c0383ca4a20bca845c0f07c9a8818d87 (patch)
treea156b0d4879f3b63a19939e1943741a62a7aa463
parentaf2df42c34088c6689bbe178a4317b55bce6b533 (diff)
downloadnetpbm-mirror-3283c876c0383ca4a20bca845c0f07c9a8818d87.tar.gz
netpbm-mirror-3283c876c0383ca4a20bca845c0f07c9a8818d87.tar.xz
netpbm-mirror-3283c876c0383ca4a20bca845c0f07c9a8818d87.zip
Fix for at least some direct color 24/32 images. Add pm_drain()
git-svn-id: http://svn.code.sf.net/p/netpbm/code/trunk@260 9d0c8265-081b-0410-96cb-a4ca84ce46f8
-rw-r--r--converter/other/xwdtopnm.c496
-rw-r--r--doc/HISTORY203
2 files changed, 398 insertions, 301 deletions
diff --git a/converter/other/xwdtopnm.c b/converter/other/xwdtopnm.c
index 28c38cfc..40317e2e 100644
--- a/converter/other/xwdtopnm.c
+++ b/converter/other/xwdtopnm.c
@@ -14,6 +14,12 @@
 
    The file X11/XWDFile.h from the X Window System is an authority for the
    format of an XWD file.  Netpbm uses its own declaration, though.
+
+   It has been a real challenge trying to reverse engineer the XWD
+   format.  This program is almost always broken as people find XWD images
+   with which it does not work and we update the program in response.
+
+   We consider an XWD file correct if Xwud displays it properly.
 */
 
 
@@ -32,11 +38,18 @@
 #include "x10wd.h"
 #include "x11wd.h"
 
+struct compMask {
+    unsigned long red;
+    unsigned long grn;
+    unsigned long blu;
+};
+
+
 struct cmdlineInfo {
     /* All the information the user supplied in the command line,
        in a form easy for the program to use.
     */
-    char *input_filename;
+    const char * inputFilename;
     unsigned int verbose;
     unsigned int debug;
     unsigned int headerdump;
@@ -119,12 +132,12 @@ parseCommandLine(int argc, char ** argv,
         /* Uses and sets argc, argv, and some of *cmdlineP and others. */
 
     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\n"
                  "is the input file specification");
@@ -137,16 +150,14 @@ processX10Header(X10WDFileHeader *  const h10P,
                  FILE *             const file,
                  int *              const colsP, 
                  int *              const rowsP, 
-                 int *              const padrightP, 
+                 unsigned int *     const padrightP, 
                  xelval *           const maxvalP, 
                  enum visualclass * const visualclassP, 
                  int *              const formatP, 
                  xel **             const colorsP, 
                  int *              const bits_per_pixelP, 
                  int *              const bits_per_itemP, 
-                 unsigned long *    const red_maskP, 
-                 unsigned long *    const green_maskP, 
-                 unsigned long *    const blue_maskP,
+                 struct compMask *  const compMaskP,
                  enum byteorder *   const byte_orderP,
                  enum byteorder *   const bit_orderP) {
 
@@ -215,7 +226,7 @@ processX10Header(X10WDFileHeader *  const h10P,
         PNM_ASSIGN1( (*colorsP)[0], 0 );
         PNM_ASSIGN1( (*colorsP)[1], *maxvalP );
         *padrightP =
-            ( ( h10P->pixmap_width + 15 ) / 16 ) * 16 - h10P->pixmap_width;
+            (((h10P->pixmap_width + 15) / 16) * 16 - h10P->pixmap_width) * 8;
         *bits_per_itemP = 16;
         *bits_per_pixelP = 1;
     } else if ( h10P->window_ncolors == 0 ) { 
@@ -227,7 +238,7 @@ processX10Header(X10WDFileHeader *  const h10P,
         for ( i = 0; i <= *maxvalP; ++i )
             PNM_ASSIGN1( (*colorsP)[i], i );
         *padrightP =
-            ( ( h10P->pixmap_width + 15 ) / 16 ) * 16 - h10P->pixmap_width;
+            (((h10P->pixmap_width + 15) / 16) * 16 - h10P->pixmap_width) * 8;
         *bits_per_itemP = 16;
         *bits_per_pixelP = 1;
     } else {
@@ -245,7 +256,7 @@ processX10Header(X10WDFileHeader *  const h10P,
                     x10colors[i].blue);
         }
 
-        *padrightP = h10P->pixmap_width & 1;
+        *padrightP = (h10P->pixmap_width & 1) * 8;
         *bits_per_itemP = 8;
         *bits_per_pixelP = 8;
     }
@@ -426,21 +437,65 @@ dumpX11Header(X11WDFileHeader * const h11P) {
 
 
 
+static unsigned long
+reverseBits(unsigned long arg,
+            unsigned int nSigBits) {
+
+    unsigned long input;
+    unsigned long output;
+    unsigned int i;
+
+    for (i = 0, input = arg, output = 0; i < nSigBits; ++i) {
+        output <<= 1;
+
+        output |= (input & 0x1);
+
+        input >>= 1;
+    }
+    return output;
+}
+
+
+
+static void
+computeComponentMasks(X11WDFileHeader * const h11P,
+                      struct compMask * const compMaskP) {
+/*----------------------------------------------------------------------------
+   You'd think the component (red, green, blue) masks in the header
+   would just be right.  But we've seen a direct color image which has
+   BGR layout even though the masks say RGB.  It also says bit order
+   is LSB first, even though the pixels within the items are arranged
+   MSB first.  So we're guessing that LSB first bit order in that
+   particular case means the bits within each the pixel are backwards.
+   So we reverse the masks to compensate.
+-----------------------------------------------------------------------------*/
+    if (h11P->visual_class == DirectColor &&
+        h11P->bits_per_pixel == 24 && h11P->bitmap_bit_order == LSBFirst) {
+        compMaskP->red = reverseBits(h11P->red_mask, 24);
+        compMaskP->grn = reverseBits(h11P->green_mask, 24);
+        compMaskP->blu = reverseBits(h11P->blue_mask, 24);
+    } else {
+        compMaskP->red = h11P->red_mask;
+        compMaskP->grn = h11P->green_mask;
+        compMaskP->blu = h11P->blue_mask;
+    }
+}
+
+
+
 static void
 processX11Header(X11WDFileHeader *  const h11P, 
                  FILE *             const file,
                  int *              const colsP, 
                  int *              const rowsP, 
-                 int *              const padrightP, 
+                 unsigned int *     const padrightP, 
                  xelval *           const maxvalP, 
                  enum visualclass * const visualclassP, 
                  int *              const formatP, 
                  xel **             const colorsP, 
                  int *              const bits_per_pixelP, 
                  int *              const bits_per_itemP, 
-                 unsigned long *    const red_maskP, 
-                 unsigned long *    const green_maskP, 
-                 unsigned long *    const blue_maskP,
+                 struct compMask *  const compMaskP,
                  enum byteorder *   const byte_orderP,
                  enum byteorder *   const bit_orderP) {
 
@@ -460,34 +515,31 @@ processX11Header(X11WDFileHeader *  const h11P,
             pm_error("couldn't read rest of X11 XWD file header");
 
     /* Check whether we can handle this dump. */
-    if ( h11FixedP->pixmap_depth > 24 )
-        pm_error( "can't handle X11 pixmap_depth > 24" );
-    if ( h11FixedP->bits_per_rgb > 24 )
-        pm_error( "can't handle X11 bits_per_rgb > 24" );
-    if ( h11FixedP->pixmap_format != ZPixmap && h11FixedP->pixmap_depth != 1 )
-        pm_error(
-            "can't handle X11 pixmap_format %d with depth != 1",
-            h11FixedP->pixmap_format );
-    if ( h11FixedP->bitmap_unit != 8 && h11FixedP->bitmap_unit != 16 &&
-         h11FixedP->bitmap_unit != 32 )
-        pm_error(
-            "X11 bitmap_unit (%d) is non-standard - can't handle",
-            h11FixedP->bitmap_unit );
+    if (h11FixedP->pixmap_depth > 24)
+        pm_error( "can't handle X11 pixmap_depth > 24");
+    if (h11FixedP->bits_per_rgb > 24)
+        pm_error("can't handle X11 bits_per_rgb > 24");
+    if (h11FixedP->pixmap_format != ZPixmap && h11FixedP->pixmap_depth != 1)
+        pm_error("can't handle X11 pixmap_format %d with depth != 1",
+                 h11FixedP->pixmap_format);
+    if (h11FixedP->bitmap_unit != 8 && h11FixedP->bitmap_unit != 16 &&
+        h11FixedP->bitmap_unit != 32)
+        pm_error("X11 bitmap_unit (%d) is non-standard - can't handle",
+                 h11FixedP->bitmap_unit);
     /* The following check was added in 10.19 (November 2003) */
-    if ( h11FixedP->bitmap_pad != 8 && h11FixedP->bitmap_pad != 16 &&
-         h11FixedP->bitmap_pad != 32 )
-        pm_error(
-            "X11 bitmap_pad (%d) is non-standard - can't handle",
-            h11FixedP->bitmap_unit );
-
-    if ( h11FixedP->ncolors > 0 ) {
-        readX11Colormap( file, h11FixedP->ncolors, byte_swap, &x11colors );
-        grayscale = colormapAllGray( x11colors, h11FixedP->ncolors );
+    if (h11FixedP->bitmap_pad != 8 && h11FixedP->bitmap_pad != 16 &&
+        h11FixedP->bitmap_pad != 32)
+        pm_error("X11 bitmap_pad (%d) is non-standard - can't handle",
+                 h11FixedP->bitmap_unit);
+
+    if (h11FixedP->ncolors > 0) {
+        readX11Colormap(file, h11FixedP->ncolors, byte_swap, &x11colors);
+        grayscale = colormapAllGray(x11colors, h11FixedP->ncolors);
     } else
         grayscale = TRUE;
 
     *visualclassP = (enum visualclass) h11FixedP->visual_class;
-    if ( *visualclassP == DirectColor ) {
+    if (*visualclassP == DirectColor) {
         unsigned int i;
         *formatP = PPM_TYPE;
         *maxvalP = 65535;
@@ -497,12 +549,12 @@ processX11Header(X11WDFileHeader *  const h11P,
           is composed of 3 separate indices.
         */
 
-        *colorsP = pnm_allocrow( h11FixedP->ncolors );
-        for ( i = 0; i < h11FixedP->ncolors; ++i )
+        *colorsP = pnm_allocrow(h11FixedP->ncolors);
+        for (i = 0; i < h11FixedP->ncolors; ++i)
             PPM_ASSIGN(
                 (*colorsP)[i], x11colors[i].red, x11colors[i].green,
                 x11colors[i].blue);
-    } else if ( *visualclassP == TrueColor ) {
+    } else if (*visualclassP == TrueColor) {
         *formatP = PPM_TYPE;
 
         *maxvalP = pm_lcm(pm_bitstomaxval(one_bits(h11FixedP->red_mask)),
@@ -510,31 +562,30 @@ processX11Header(X11WDFileHeader *  const h11P,
                           pm_bitstomaxval(one_bits(h11FixedP->blue_mask)),
                           PPM_OVERALLMAXVAL
             );
-    }
-    else if ( *visualclassP == StaticGray && h11FixedP->bits_per_pixel == 1 ) {
+    } else if (*visualclassP == StaticGray && h11FixedP->bits_per_pixel == 1) {
         *formatP = PBM_TYPE;
         *maxvalP = 1;
         *colorsP = pnm_allocrow( 2 );
-        PNM_ASSIGN1( (*colorsP)[0], *maxvalP );
-        PNM_ASSIGN1( (*colorsP)[1], 0 );
-    } else if ( *visualclassP == StaticGray ) {
+        PNM_ASSIGN1((*colorsP)[0], *maxvalP);
+        PNM_ASSIGN1((*colorsP)[1], 0);
+    } else if (*visualclassP == StaticGray) {
         unsigned int i;
         *formatP = PGM_TYPE;
-        *maxvalP = ( 1 << h11FixedP->bits_per_pixel ) - 1;
-        *colorsP = pnm_allocrow( *maxvalP + 1 );
-        for ( i = 0; i <= *maxvalP; ++i )
-            PNM_ASSIGN1( (*colorsP)[i], i );
+        *maxvalP = (1 << h11FixedP->bits_per_pixel) - 1;
+        *colorsP = pnm_allocrow(*maxvalP + 1);
+        for (i = 0; i <= *maxvalP; ++i)
+            PNM_ASSIGN1((*colorsP)[i], i);
     } else {
-        *colorsP = pnm_allocrow( h11FixedP->ncolors );
-        if ( grayscale ) {
+        *colorsP = pnm_allocrow(h11FixedP->ncolors);
+        if (grayscale) {
             unsigned int i;
             *formatP = PGM_TYPE;
-            for ( i = 0; i < h11FixedP->ncolors; ++i )
-                PNM_ASSIGN1( (*colorsP)[i], x11colors[i].red );
+            for (i = 0; i < h11FixedP->ncolors; ++i)
+                PNM_ASSIGN1((*colorsP)[i], x11colors[i].red);
         } else {
             unsigned int i;
             *formatP = PPM_TYPE;
-            for ( i = 0; i < h11FixedP->ncolors; ++i )
+            for (i = 0; i < h11FixedP->ncolors; ++i)
                 PPM_ASSIGN(
                     (*colorsP)[i], x11colors[i].red, x11colors[i].green,
                     x11colors[i].blue);
@@ -545,13 +596,14 @@ processX11Header(X11WDFileHeader *  const h11P,
     *colsP = h11FixedP->pixmap_width;
     *rowsP = h11FixedP->pixmap_height;
     *padrightP =
-        h11FixedP->bytes_per_line * 8 / h11FixedP->bits_per_pixel -
-        h11FixedP->pixmap_width;
+        h11FixedP->bytes_per_line * 8 -
+        h11FixedP->pixmap_width * h11FixedP->bits_per_pixel;
+
     /* According to X11/XWDFile.h, the item size is 'bitmap_pad' for some
        images and 'bitmap_unit' for others.  This is strange, so there may
        be some subtlety of their definitions that we're missing.
 
-       See comments in getpix() about what an item is.
+       See comments in pixelReader_getpix() about what an item is.
 
        Ben Kelley in January 2002 had a 32 bits-per-pixel xwd file
        from a truecolor 32 bit window on a Hummingbird Exceed X server
@@ -561,22 +613,38 @@ processX11Header(X11WDFileHeader *  const h11P,
        bit-per-pixel direct color window that had bitmap_unit = 32 and
        bitmap_pad = 8.  This was made by Xwd in Red Hat Xfree86 4.3.0-2.
 
+       In March 2007, Darren Frith present an xwd file like this:
+       Header says direct color, bits_per_pixel = 24, bitmap_unit =
+       32, bitmap_pad = 8, byte order and bit order LSB first.  The
+       bytes in each item are in fact MSB first and the pixels spread
+       across the items MSB first.  The raster is consecutive 24 bit
+       pixel units, but each row is padded on the right with enough
+       bits to make the total line size 32 x width.  Really strange.
+       The header says the bits within each pixel are one byte red,
+       one byte green, one byte blue.  But they are actually blue,
+       green, red.  Xwud, ImageMagick, and Gimp render this image
+       correctly, so it's not broken.
+
        Before Netpbm 9.23 (January 2002), we used bitmap_unit as the
        item size always.  Then, until 10.19 (November 2003), we used
        bitmap_pad when pixmap_depth > 1 and pixmap_format == ZPixmap.
        We still don't see any logic in these fields at all, but we
        figure whichever one is greater (assuming both are meaningful)
-       has to be the item size.  
-    */
-    *bits_per_itemP = MAX(h11FixedP->bitmap_pad, h11FixedP->bitmap_unit);
-
+       has to be the item size.  */
+    *bits_per_itemP  = MAX(h11FixedP->bitmap_pad, h11FixedP->bitmap_unit);
     *bits_per_pixelP = h11FixedP->bits_per_pixel;
 
-    *byte_orderP = (enum byteorder) h11FixedP->byte_order;
-    *bit_orderP = (enum byteorder) h11FixedP->bitmap_bit_order;
-    *red_maskP = h11FixedP->red_mask;
-    *green_maskP = h11FixedP->green_mask;
-    *blue_maskP = h11FixedP->blue_mask;
+    if (*visualclassP == DirectColor) {
+        /* Strange, but we've seen a Direct Color 24/32 image that
+           says LSBFirst and it's a lie.  And Xwud renders it correctly.
+        */
+        *byte_orderP = MSBFirst;
+        *bit_orderP = MSBFirst;
+    } else {
+        *byte_orderP = (enum byteorder) h11FixedP->byte_order;
+        *bit_orderP  = (enum byteorder) h11FixedP->bitmap_bit_order;
+    }
+    computeComponentMasks(h11FixedP, compMaskP);
 
     free(h11FixedP);
 } 
@@ -587,16 +655,14 @@ static void
 getinit(FILE *             const ifP, 
         int *              const colsP, 
         int *              const rowsP, 
-        int *              const padrightP, 
+        unsigned int *     const padrightP, 
         xelval *           const maxvalP, 
         enum visualclass * const visualclassP, 
         int *              const formatP, 
         xel **             const colorsP,
         int *              const bits_per_pixelP, 
         int *              const bits_per_itemP, 
-        unsigned long *    const red_maskP, 
-        unsigned long *    const green_maskP,
-        unsigned long *    const blue_maskP,
+        struct compMask *  const compMaskP,
         enum byteorder *   const byte_orderP,
         enum byteorder *   const bit_orderP,
         bool               const headerDump) {
@@ -606,10 +672,10 @@ getinit(FILE *             const ifP,
 
    Return various fields from the header.
 
-   Return as *padrightP the number of additional pixels of padding are
+   Return as *padrightP the number of additional bits of padding are
    at the end of each line of input.  This says the input stream
-   contains *colsP pixels of image data plus *padrightP pixels of
-   padding.
+   contains *colsP pixels of image data (at *bits_per_pixelP bits each)
+   plus *padrightP bits of padding.
 -----------------------------------------------------------------------------*/
     /* Assume X11 headers are larger than X10 ones. */
     unsigned char header[sizeof(X11WDFileHeader)];
@@ -643,8 +709,7 @@ getinit(FILE *             const ifP,
         processX10Header(h10P, ifP, colsP, rowsP, padrightP, maxvalP, 
                          visualclassP, formatP, 
                          colorsP, bits_per_pixelP, bits_per_itemP, 
-                         red_maskP, green_maskP, blue_maskP, 
-                         byte_orderP, bit_orderP);
+                         compMaskP, byte_orderP, bit_orderP);
     } else if (h11P->file_version == X11WD_FILE_VERSION ||
                pm_bs_long(h11P->file_version) == X11WD_FILE_VERSION) {
         
@@ -665,8 +730,7 @@ getinit(FILE *             const ifP,
         processX11Header(h11P, ifP, colsP, rowsP, padrightP, maxvalP, 
                          visualclassP, formatP, 
                          colorsP, bits_per_pixelP, bits_per_itemP, 
-                         red_maskP, green_maskP, blue_maskP, 
-                         byte_orderP, bit_orderP);
+                         compMaskP, byte_orderP, bit_orderP);
     } else
         pm_error("unknown XWD file version: %u", h11P->file_version);
 }
@@ -719,13 +783,10 @@ getinit(FILE *             const ifP,
    one pixel at a time from it.
 
    It consists of a structure of type 'pixelReader' and the
-   getpix() and pixelReaderInit() subroutines.
+   pixelReader_*() subroutines.
 -----------------------------------------------------------------------------*/
 
 typedef struct {
-    /* This structure contains the state of the getpix() reader as it
-       reads across a row in the input image.
-       */
     FILE * fileP;
     unsigned long itemBuffer;
         /* The item buffer.  This contains what's left of the item
@@ -770,12 +831,12 @@ typedef struct {
 
 
 static void
-pixelReaderInit(pixelReader *  const pixelReaderP,
-                FILE *         const fileP,
-                int            const bitsPerPixel,
-                int            const bitsPerItem, 
-                enum byteorder const byteOrder,
-                enum byteorder const bitOrder) {
+pixelReader_init(pixelReader *  const pixelReaderP,
+                 FILE *         const fileP,
+                 int            const bitsPerPixel,
+                 int            const bitsPerItem, 
+                 enum byteorder const byteOrder,
+                 enum byteorder const bitOrder) {
     
     pixelReaderP->fileP           = fileP;
     pixelReaderP->bitsPerPixel    = bitsPerPixel;
@@ -789,6 +850,33 @@ pixelReaderInit(pixelReader *  const pixelReaderP,
 
 
 static void
+pixelReader_term(pixelReader * const pixelReaderP) {
+
+    uint remainingByteCount;
+
+    if (pixelReaderP->nBitsLeft > 0)
+        pm_message("Warning: %u unused bits left in the pixel reader "
+                   "buffer after full image converted.  XWD file may be "
+                   "corrupted or Xwdtopnm may have misinterpreted it",
+                   pixelReaderP->nBitsLeft);
+
+
+    pm_drain(pixelReaderP->fileP, 4096, &remainingByteCount);
+
+    if (remainingByteCount >= 4096)
+        pm_message("Warning: at least 4K additional bytes in XWD input stream "
+                   "after full image converted.  XWD file may be corrupted "
+                   "or Xwdtopnm may have misinterpreted it.");
+    else if (remainingByteCount > 0)
+        pm_message("Warning: %u additional bytes in XWD input stream "
+                   "after full image converted.  XWD file may be corrupted "
+                   "or Xwdtopnm may have misinterpreted it.",
+                   remainingByteCount);
+}
+
+
+
+static void
 readItem(pixelReader * const rdrP) {
 /*----------------------------------------------------------------------------
    Read one item from the XWD raster associated with pixel reader *rdrP.
@@ -861,55 +949,16 @@ static unsigned long const lsbmask[] = {
 
 
 static unsigned long
-getpix(pixelReader * const rdrP) {
+pixelReader_getbits(pixelReader * const rdrP,
+                    unsigned int  const nBits) {
 /*----------------------------------------------------------------------------
-   Get a pixel from the input image.
-
-   A pixel is a bit string.  It may be either an rgb triplet or an index
-   into the colormap (or even an rgb triplet of indices into the colormaps!).
-   We don't care -- it's just a bit string.
-
-   We return an integer.  It's the integer that the pixel represents as
-   pure binary cipher, with the first bit the most significant bit.
-   
-   The basic unit of storage in the input file is an "item."  An item
-   can be 1, 2, or 4 bytes, and 'bits_per_item' tells us which.  Each
-   item can have its bytes stored in forward or reverse order, and
-   'byte_order' tells us which.
-
-   Each item can contain one or more pixels, and may contain
-   fractional pixels.  'bits_per_pixel' tells us how many bits each
-   pixel has, and 'bits_per_pixel' is always less than or equal to
-   'bits_per_item', but not necessarily a factor of it.  Within an item,
-   after taking care of the endianness of its storage format, the pixels
-   may be arranged from left to right or right to left.  'bit_order' tells
-   us which.
-
-   But it's not that simple.  Sometimes dummy pixels are added to the
-   right edge of the image in order to make an integral number of
-   items in each row of the raster.  getpix() doesn't know anything
-   about that, though -- it gets the dummy pixels the same as any
-   other pixel.  (This program is written as if it is always whole
-   pixels that get added for padding, but I wonder.  When pixels are 3
-   or 4 bytes and items are 4 bytes, sub-pixel padding would make
-   sense.  But then, maybe in formats with 3 or 4 bytes pixels,
-   there's never padding.  That seems to be the case so far...).  The
-   XWD header has a field that tells how many bytes there are per XWD
-   raster line, so that's the final word on how much padding there is.
-   
-   The most difficult part of getting the pixel is the ones that span
-   items.  We detect when an item has part, but not all, of the next
-   pixel (after the one we return) in it and store the fragment as
-   "carryover" bits for use in the next call to this function.
-
-   All this state information (carryover bits, etc.) is kept in 
-   *row_controlP.
-
+  Get the next 'nBits' bits from the stream, and return the last 32
+  of them.
 -----------------------------------------------------------------------------*/
     unsigned long pixel;
         /* Accumulator for the value we ultimately return.  We shift in
            bits from the right end.  The number of bits presently in the
-           accumulator is rdrP->bitsPerPixel - bitsStillNeeded .
+           accumulator is rdrP->bitsPerPixel - nBitsStillNeeded .
         */
     
     unsigned int nBitsStillNeeded;
@@ -919,10 +968,8 @@ getpix(pixelReader * const rdrP) {
            it -- additional bits will shift in from the right.
         */
 
-    assert(rdrP->bitsPerPixel <= 32);
-
     pixel = 0;
-    nBitsStillNeeded = rdrP->bitsPerPixel;
+    nBitsStillNeeded = nBits;
 
     while (nBitsStillNeeded > 0) {
         if (rdrP->nBitsLeft == 0)
@@ -964,18 +1011,65 @@ getpix(pixelReader * const rdrP) {
 
 
 
+static unsigned long
+pixelReader_getpix(pixelReader * const rdrP) {
+/*----------------------------------------------------------------------------
+   Get a pixel from the input image.
+
+   A pixel is a bit string.  It may be either an rgb triplet or an index
+   into the colormap (or even an rgb triplet of indices into the colormaps!).
+   We don't care -- it's just a bit string.
+
+   We return an integer.  It's the integer that the pixel represents as
+   pure binary cipher, with the first bit the most significant bit.
+   
+   The basic unit of storage in the input file is an "item."  An item
+   can be 1, 2, or 4 bytes, and 'bits_per_item' tells us which.  Each
+   item can have its bytes stored in forward or reverse order, and
+   'byte_order' tells us which.  We have seen a Direct Color 24 bpp/32 bpi
+   image which said 'byte_order' == LSBFirst, but the byte order is
+   nonetheless MSB first.
+
+   Each item can contain one or more pixels, and may contain
+   fractional pixels.  'bits_per_pixel' tells us how many bits each
+   pixel has, and 'bits_per_pixel' is always less than or equal to
+   'bits_per_item', but not necessarily a factor of it.  Within an item,
+   after taking care of the endianness of its storage format, the pixels
+   may be arranged from left to right or right to left.  'bit_order' tells
+   us which.  We have also seen images in which the pixels are arranged
+   from left to right within the items, but the RGB components within
+   each pixel are right to left and 'bit_order' is LSBFirst.
+
+   But it's not that simple.  Sometimes dummy bits are added to the
+   right edge of the image in order to make an integral number of
+   items in each row of the raster.  And we've even seen images where
+   there are a ridiculous number of padding bits on the right so as to
+   make the number of items per line equal the number of pixels per
+   line, even though items are 32 bits and pixels are 24 bits!  The
+   XWD header has a field that tells how many bytes there are per XWD
+   raster line, so that's the final word on how much padding there is.
+
+   We maintain a 32 bit buffer to decouple reading of whole items from
+   the file and reading of an arbitrary number of bits from the
+   pixelReader.
+-----------------------------------------------------------------------------*/
+    assert(rdrP->bitsPerPixel <= 32);
+
+    return pixelReader_getbits(rdrP, rdrP->bitsPerPixel);
+}
+
+
+
 static void
 reportInfo(int              const cols, 
            int              const rows, 
-           int              const padright, 
+           unsigned int     const padright, 
            xelval           const maxval, 
            enum visualclass const visualclass,
            int              const format, 
            int              const bits_per_pixel,
            int              const bits_per_item, 
-           int              const red_mask, 
-           int              const green_mask, 
-           int              const blue_mask,
+           struct compMask  const compMask,
            enum byteorder   const byte_order, 
            enum byteorder   const bit_order) {
     
@@ -1003,15 +1097,15 @@ reportInfo(int              const cols,
     }
     pm_message("%d rows of %d columns with maxval %d",
                rows, cols, maxval);
-    pm_message("padright=%d.  visualclass = %s.  format=%d (%c%c)",
+    pm_message("padright=%u bits.  visualclass = %s.  format=%d (%c%c)",
                padright, visualclass_name, 
                format, format/256, format%256);
     pm_message("bits_per_pixel=%d; bits_per_item=%d",
                bits_per_pixel, bits_per_item);
     pm_message("byte_order=%s; bit_order=%s",
                byte_order_name, bit_order_name);
-    pm_message("red_mask=0x%.8x; green_mask=0x%.8x; blue_mask=0x%.8x",
-               red_mask, green_mask, blue_mask);
+    pm_message("component mask: red=0x%.8lx; grn=0x%.8lx; blu=0x%.8lx",
+               compMask.red, compMask.grn, compMask.blu);
 }
 
 
@@ -1024,36 +1118,34 @@ convertRowSimpleIndex(pixelReader *  const pixelReaderP,
     
     unsigned int col;
     for (col = 0; col < cols; ++col)
-        xelrow[col] = colors[getpix(pixelReaderP)];
+        xelrow[col] = colors[pixelReader_getpix(pixelReaderP)];
 }
 
 
 
 static void
-convertRowDirect(pixelReader *  const pixelReaderP,
-                 int            const cols,
-                 const xel *    const colors,
-                 unsigned long  const red_mask,
-                 unsigned long  const grn_mask,
-                 unsigned long  const blu_mask,
-                 xel *          const xelrow) {
+convertRowDirect(pixelReader *   const pixelReaderP,
+                 int             const cols,
+                 const xel *     const colors,
+                 struct compMask const compMask,
+                 xel *           const xelrow) {
         
     unsigned int col;
 
     for (col = 0; col < cols; ++col) {
         unsigned long pixel;
             /* This is a triplet of indices into the color map, packed
-               into this bit string according to red_mask, etc.
+               into this bit string according to compMask
             */
         unsigned int red_index, grn_index, blu_index;
             /* These are indices into the color map, unpacked from 'pixel'.
              */
             
-        pixel = getpix(pixelReaderP);
+        pixel = pixelReader_getpix(pixelReaderP);
 
-        red_index = (pixel & red_mask) >> zero_bits(red_mask);
-        grn_index = (pixel & grn_mask) >> zero_bits(grn_mask); 
-        blu_index = (pixel & blu_mask) >> zero_bits(blu_mask);
+        red_index = (pixel & compMask.red) >> zero_bits(compMask.red);
+        grn_index = (pixel & compMask.grn) >> zero_bits(compMask.grn); 
+        blu_index = (pixel & compMask.blu) >> zero_bits(compMask.blu);
 
         PPM_ASSIGN(xelrow[col],
                    PPM_GETR(colors[red_index]),
@@ -1066,39 +1158,37 @@ convertRowDirect(pixelReader *  const pixelReaderP,
 
 
 static void
-convertRowTrueColor(pixelReader *  const pixelReaderP,
-                    int                  const cols,
-                    pixval               const maxval,
-                    const xel *          const colors,
-                    unsigned long        const red_mask,
-                    unsigned long        const grn_mask,
-                    unsigned long        const blu_mask,
-                    xel *                const xelrow) {
+convertRowTrueColor(pixelReader *   const pixelReaderP,
+                    int             const cols,
+                    pixval          const maxval,
+                    const xel *     const colors,
+                    struct compMask const compMask,
+                    xel *           const xelrow) {
 
     unsigned int col;
     unsigned int red_shift, grn_shift, blu_shift;
     unsigned int red_maxval, grn_maxval, blu_maxval;
 
-    red_shift = zero_bits(red_mask);
-    grn_shift = zero_bits(grn_mask);
-    blu_shift = zero_bits(blu_mask);
+    red_shift = zero_bits(compMask.red);
+    grn_shift = zero_bits(compMask.grn);
+    blu_shift = zero_bits(compMask.blu);
 
-    red_maxval = red_mask >> red_shift;
-    grn_maxval = grn_mask >> grn_shift;
-    blu_maxval = blu_mask >> blu_shift;
+    red_maxval = compMask.red >> red_shift;
+    grn_maxval = compMask.grn >> grn_shift;
+    blu_maxval = compMask.blu >> blu_shift;
 
     for (col = 0; col < cols; ++col) {
         unsigned long pixel;
 
-        pixel = getpix(pixelReaderP);
+        pixel = pixelReader_getpix(pixelReaderP);
 
         /* The parsing of 'pixel' used to be done with hardcoded layout
            parameters.  See comments at end of this file.
         */
         PPM_ASSIGN(xelrow[col],
-                   ((pixel & red_mask) >> red_shift) * maxval / red_maxval,
-                   ((pixel & grn_mask) >> grn_shift) * maxval / grn_maxval,
-                   ((pixel & blu_mask) >> blu_shift) * maxval / blu_maxval
+                   ((pixel & compMask.red) >> red_shift) * maxval / red_maxval,
+                   ((pixel & compMask.grn) >> grn_shift) * maxval / grn_maxval,
+                   ((pixel & compMask.blu) >> blu_shift) * maxval / blu_maxval
             );
 
     }
@@ -1109,13 +1199,11 @@ convertRowTrueColor(pixelReader *  const pixelReaderP,
 static void
 convertRow(pixelReader *    const pixelReaderP,
            FILE *           const ofP,
-           int              const padright, 
+           unsigned int     const padright, 
            int              const cols, 
            xelval           const maxval,
            int              const format, 
-           unsigned long    const red_mask, 
-           unsigned long    const green_mask, 
-           unsigned long    const blue_mask, 
+           struct compMask  const compMask,
            const xel*       const colors, 
            enum visualclass const visualclass) {
 /*----------------------------------------------------------------------------
@@ -1125,7 +1213,7 @@ convertRow(pixelReader *    const pixelReaderP,
    The row is 'cols' pixels.
 
    After reading the 'cols' pixels, we read and discard an additional
-   'padright' pixels from the input stream, so as to read the entire
+   'padright' bits from the input stream, so as to read the entire
    input line.
 -----------------------------------------------------------------------------*/
     xel* xelrow;
@@ -1139,25 +1227,19 @@ convertRow(pixelReader *    const pixelReaderP,
         convertRowSimpleIndex(pixelReaderP, cols, colors, xelrow);
         break;
     case DirectColor: 
-        convertRowDirect(pixelReaderP, cols, colors,
-                         red_mask, green_mask, blue_mask,
-                         xelrow);
+        convertRowDirect(pixelReaderP, cols, colors, compMask, xelrow);
         
         break;
     case TrueColor: 
         convertRowTrueColor(pixelReaderP, cols, maxval, colors,
-                            red_mask, green_mask, blue_mask,
-                            xelrow);
+                            compMask, xelrow);
         break;
             
     default:
         pm_error("unknown visual class");
     }
-    {
-        unsigned int col;
-        for (col = 0; col < padright; ++col)
-            getpix(pixelReaderP);
-    }
+    pixelReader_getbits(pixelReaderP, padright);
+    
     pnm_writepnmrow(ofP, xelrow, cols, maxval, format, 0);
     pnm_freerow(xelrow);
 }
@@ -1192,15 +1274,17 @@ main(int argc, char *argv[]) {
 
     struct cmdlineInfo cmdline;
     FILE * ifP;
-    int rows, cols, format, padright;
+    int rows, cols, format;
+    unsigned int padright;
+        /* Number of bits of padding on the right of each row */
     unsigned int row;
-    int bits_per_pixel;
-    int bits_per_item;
-    unsigned long red_mask, green_mask, blue_mask;
+    int bitsPerPixel;
+    int bitsPerItem;
+    struct compMask compMask;
     xelval maxval;
     enum visualclass visualclass;
-    enum byteorder byte_order, bit_order;
-    xel *colors;  /* the color map */
+    enum byteorder byteOrder, bitOrder;
+    xel * colors;  /* the color map */
     pixelReader pixelReader;
 
     pnm_init(&argc, argv);
@@ -1210,24 +1294,23 @@ main(int argc, char *argv[]) {
     debug = cmdline.debug;
     verbose = cmdline.verbose;
 
-    if (cmdline.input_filename != NULL) 
-        ifP = pm_openr(cmdline.input_filename);
+    if (cmdline.inputFilename != NULL) 
+        ifP = pm_openr(cmdline.inputFilename);
     else
         ifP = stdin;
 
     getinit(ifP, &cols, &rows, &padright, &maxval, &visualclass, &format, 
-            &colors, &bits_per_pixel, &bits_per_item, 
-            &red_mask, &green_mask, &blue_mask, &byte_order, &bit_order,
+            &colors, &bitsPerPixel, &bitsPerItem, 
+            &compMask, &byteOrder, &bitOrder,
             cmdline.headerdump);
     
     if (verbose) 
         reportInfo(cols, rows, padright, maxval, visualclass,
-                   format, bits_per_pixel, bits_per_item,
-                   red_mask, green_mask, blue_mask, 
-                   byte_order, bit_order);
+                   format, bitsPerPixel, bitsPerItem, compMask,
+                   byteOrder, bitOrder);
 
-    pixelReaderInit(&pixelReader, ifP, bits_per_pixel, bits_per_item,
-                    byte_order, bit_order);
+    pixelReader_init(&pixelReader, ifP, bitsPerPixel, bitsPerItem,
+                     byteOrder, bitOrder);
 
     pnm_writepnminit(stdout, cols, rows, maxval, format, 0);
 
@@ -1235,9 +1318,11 @@ main(int argc, char *argv[]) {
 
     for (row = 0; row < rows; ++row) {
         convertRow(&pixelReader, stdout,
-                   padright, cols, maxval, format,
-                   red_mask, green_mask, blue_mask, colors, visualclass);
+                   padright, cols, maxval, format, compMask,
+                   colors, visualclass);
     }
+
+    pixelReader_term(&pixelReader);
     
     pm_close(ifP);
     pm_close(stdout);
@@ -1246,6 +1331,7 @@ main(int argc, char *argv[]) {
 }
 
 
+
 /*
    This used to be the way we parsed a direct/true color pixel.  I'm 
    keeping it here in case we find out some application needs it this way.
diff --git a/doc/HISTORY b/doc/HISTORY
index 58d44633..5336dc30 100644
--- a/doc/HISTORY
+++ b/doc/HISTORY
@@ -5,6 +5,15 @@ CHANGE HISTORY
 --------------
 
 not yet  BJH  Release 10.38.0
+    
+              xwdtopnm: fix right edge padding for 24 bit per pixel,
+              32 bit per item images.
+
+              xwdtopnm: update assumptions about format for direct color
+              images to match an image we found.
+
+              xwdtopnm: use pm_drain() to catch some format
+              misinterpretations.
 
               pamtogif: Speed up for monochrome images.  Thanks
               Prophet of the Way <afu@wta.att.ne.jp>.
@@ -38,6 +47,8 @@ not yet  BJH  Release 10.38.0
 
               giftopnm: Add -repair option.
 
+              libnetpbm: add pm_drain().
+              
               libnetpbm: shhopt: reject signed number as value for
               OPT_UINT option.
 
@@ -3064,41 +3075,41 @@ Minor bug fixes and compatibility fixes are not documented in this file.
 
 PBM
 
-libpbm1.c	strstr() added to libpbm1.c.
-libpbm5.c	BDF font support added.
-pbmtext		BDF font support added.
-pbmto4425	New filter.
-pbmtoln03	Command line parsing changed to Pbmplus standard.
+libpbm1.c       strstr() added to libpbm1.c.
+libpbm5.c       BDF font functions added.
+pbmtext         Ability to use BDF fonts added.
+pbmto4425       New filter.
+pbmtoln03       Command line parsing changed to Pbmplus standard.
 
 
 PGM
 
-pgmnoise	New filter.
+pgmnoise        New filter.
 
 
 PPM
 
-picttoppm	Updated
-ppm3d		New facility.
-ppmchange	New filter.
-ppmdim		New filter.
-ppmflash	New filter.
-ppmmix		New filter.
-ppmntsc		New filter.
-ppmqvga		Option parsing changed to Pbmplus standard.
-ppmshift	New filter.
-ppmspread	New filter.
-ppmtoxpm	Prototypes added.
-xpmtoppm	Prototypes added.
-ilbmtoppm	Updated.
-ppmtoilbm	Updated.
+picttoppm       Updated
+ppm3d           New facility.
+ppmchange       New filter.
+ppmdim          New filter.
+ppmflash        New filter.
+ppmmix          New filter.
+ppmntsc         New filter.
+ppmqvga         Option parsing changed to Pbmplus standard.
+ppmshift        New filter.
+ppmspread       New filter.
+ppmtoxpm        Prototypes added.
+xpmtoppm        Prototypes added.
+ilbmtoppm       Updated.
+ppmtoilbm       Updated.
 
 
 PNM
 
-pnmtoddif	New filter.
-pnmhistmap	New facility.
-pnmtops		New option (-nocenter) added.
+pnmtoddif       New filter.
+pnmhistmap      New facility.
+pnmtops         New option (-nocenter) added.
 
 
 Functional changes to Netpbm since 7 December 1993.
@@ -3106,25 +3117,25 @@ Minor bug fixes and compatibility fixes are not documented in this file.
 
 PGM
 
-asciitopgm	New filter.
-fitstopgm	Replaced by fitstopnm.
-pgmtofits	Replaced by pnmtofits.
-pgmtopbm	Upgraded.
-pgmkernel	New filter.
+asciitopgm      New filter.
+fitstopgm       Replaced by fitstopnm.
+pgmtofits       Replaced by pnmtofits.
+pgmtopbm        Upgraded.
+pgmkernel       New filter.
 
 PPM
 
-ppmchange	Upgraded.
-xvminitoppm	New filter.
+ppmchange       Upgraded.
+xvminitoppm     New filter.
 
 PNM
 
-pnmalias	New filter.
-pnmtofits	Replacement for pgmtofits.
-fitstopnm	Replacement for fitstopgm.
-pnmtosgi	New filter.
-sgitopnm	New filter.
-pstopnm		New filter.
+pnmalias        New filter.
+pnmtofits       Replacement for pgmtofits.
+fitstopnm       Replacement for fitstopgm.
+pnmtosgi        New filter.
+sgitopnm        New filter.
+pstopnm         New filter.
 
 
 
@@ -3135,126 +3146,126 @@ The following is new in Netpbm (compared to Pbmplus):
 
 PBM
 
-pbmtext		BDF font support added.
+pbmtext         Ability to use BDF fonts added.
 
-pbmto4425	Display on an AT&T 4425 Ascii terminal.
+pbmto4425       Display on an AT&T 4425 Ascii terminal.
 
-pbmtoascii	A new improved version.
+pbmtoascii      A new improved version.
 
-pbmtoln03	Convert to DEC LN03+.
+pbmtoln03       Convert to DEC LN03+.
 
-pbmtolps	Fast PostScript creator.
+pbmtolps        Fast PostScript creator.
 
-pbmtopk		Conversion to/from a packed (PK) format font.
+pbmtopk         Conversion to/from a packed (PK) format font.
 pktopbm
 
-pbmclean	Flip isolated pixels.
+pbmclean        Flip isolated pixels.
 
-pbmpscale	Enlarge pbm image with edge smoothing.
+pbmpscale       Enlarge pbm image with edge smoothing.
 
 
 PGM
 
-asciitopgm	Convert an ascii image into pgm.
+asciitopgm      Convert an ascii image into pgm.
 
-pbmtopgm	Convert pbm to pgm by averaging areas.
+pbmtopgm        Convert pbm to pgm by averaging areas.
 
-rawtopgm	Handles input files without specification of the file size,
-		assuming the input image is quadratic. It also supports a
-		-tb (top bottom flip) option.
+rawtopgm        Handles input files without specification of the file size,
+                assuming the input image is quadratic. It also has a
+                -tb (top bottom flip) option.
 
-bioradtopgm	Conversion utility for files created by Biorad confocal
-		microscopes.
+bioradtopgm     Conversion utility for files created by Biorad confocal
+                microscopes.
 
-spottopgm	Convert SPOT satellite images to pgm.
+spottopgm       Convert SPOT satellite images to pgm.
 
-pgmkernel	Generate a convolution kernel.
+pgmkernel       Generate a convolution kernel.
 
-pgmnoise	Create a pgm file with random pixels.
+pgmnoise        Create a pgm file with random pixels.
 
 
 PPM
 
-bmptoppm	Conversion to/from windows bitmap format.
+bmptoppm        Conversion to/from windows bitmap format.
 ppmtobmp
 
-ppmtogif	Updated version.
-giftoppm	Removed (see giftopnm).
+ppmtogif        Updated version.
+giftoppm        Removed (see giftopnm).
 
-ppmtoilbm	Updated version.
+ppmtoilbm       Updated version.
 ilbmtoppm
 
-picttoppm	Updated version.
+picttoppm       Updated version.
 ppmtopict
 
-ppmtoxpm	Updated version, which supports xpm version 3.
+ppmtoxpm        Updated version, which understands xpm version 3.
 xpmtoppm
 
-ppmtomap	Extract all colors from a ppm file.
+ppmtomap        Extract all colors from a ppm file.
 
-ppmtomitsu	Convert to Mitsubishi S340-10 printer format.
+ppmtomitsu      Convert to Mitsubishi S340-10 printer format.
 
-xvminitoppm	Convert an XV thumbnail picture to ppm.
+xvminitoppm     Convert an XV thumbnail picture to ppm.
 
-ppmtoyuvsplit	Conversion to/from YUV triplets. (MPEG / JPEG).
+ppmtoyuvsplit   Conversion to/from YUV triplets. (MPEG / JPEG).
 yuvsplittoppm
 
-ppm3d		Create a red/blue stereo image.
+ppm3d           Create a red/blue stereo image.
 
-ppmbrighten	Change image saturation and value on an HSV map.
+ppmbrighten     Change image saturation and value on an HSV map.
 
-ppmchange	Change all pixels of one color to another in a portable pixmap
+ppmchange       Change all pixels of one color to another in a portable pixmap
 
-ppmdim		Dim a ppm file down to total blackness.
+ppmdim          Dim a ppm file down to total blackness.
 
-ppmdist		Simplistic grayscale assignment for machine generated
-		color images.
+ppmdist         Simplistic grayscale assignment for machine generated
+                color images.
 
-ppmflash	Brighten a picture up to complete white-out
+ppmflash        Brighten a picture up to complete white-out
 
-ppmmix		Blend together two portable pixmaps.
+ppmmix          Blend together two portable pixmaps.
 
-ppmnorm		Normalize the contrast in a portable pixmap.
+ppmnorm         Normalize the contrast in a portable pixmap.
 
-ppmntsc		Make a portable pixmap look like taken from an American TV.
+ppmntsc         Make a portable pixmap look like taken from an American TV.
 
-ppmqvga		Eight plane quantization.
+ppmqvga         Eight plane quantization.
 
-ppmshift	Shift lines of a portable pixmap left or right by a random amount.
+ppmshift        Shift lines of a portable pixmap left or right by a random amount.
 
-ppmspread	Displace a portable pixmap's pixels by a random amount.
+ppmspread       Displace a portable pixmap's pixels by a random amount.
 
-ppmtopjxl	Convert a ppm file into an HP PaintJet XL PCL file.
+ppmtopjxl       Convert a ppm file into an HP PaintJet XL PCL file.
 
 
 PNM
 
-pnmtops		New option (-nocenter) added.
+pnmtops         New option (-nocenter) added.
 
-pnmtofits	Replacement for pgmtofits/fitstopgm
+pnmtofits       Replacement for pgmtofits/fitstopgm
 fitstopnm
 
-pnmtosgi	Conversion to/from sgi image format.
+pnmtosgi        Conversion to/from sgi image format.
 sgitopnm
 
-pnmtosir	Conversion to/from Solitaire image recorder format.
+pnmtosir        Conversion to/from Solitaire image recorder format.
 sirtopnm
 
-giftopnm	Replaces giftoppm. Examines the input image and produces
-		a pbm, pgm, or ppm output.
+giftopnm        Replaces giftoppm. Examines the input image and produces
+                a pbm, pgm, or ppm output.
 
-pstopnm		Convert PostScript to pnm. Requires Ghostscript.
+pstopnm         Convert PostScript to pnm. Requires Ghostscript.
 
-zeisstopnm	Conversion utility for files created by Zeiss confocal
-		microscopes (the old standard).
+zeisstopnm      Conversion utility for files created by Zeiss confocal
+                microscopes (the old standard).
 
-pnmalias	Anti aliasing filter.
+pnmalias        Anti aliasing filter.
 
-pnmcomp		Composite two portable anymaps together.
+pnmcomp         Composite two portable anymaps together.
 
-pnmcrop		New options added.
+pnmcrop         New options added.
 
-pnmpad		Add borders to anymap.
+pnmpad          Add borders to anymap.
 
 
 LIBTIFF
@@ -3338,7 +3349,7 @@ Changes during the extended beta test period, starting on 15jan91:
     Added a -pseudodepth flag to pnmtoxwd.
     Updated tifftopnm for libtiff 2.4.
     Added many option flags to pnmtotiff.  (J.T. Conklin)
-    Added support for X11R5's new color specifiers rgb: and rgbi:.
+    Added recognition of X11R5's new color specifiers rgb: and rgbi:.
     Added pgmtexture.  (James Darrell McCauley)
     Added ppmtopj, pjtoppm, and ppmdither.  (Christos Zoulas)
     Added ppmtotga.  (Mark Shand)
@@ -3346,7 +3357,7 @@ Changes during the extended beta test period, starting on 15jan91:
     Added pbmtoatk and atktopbm.  (Bill Janssen)
     Added ppmtoyuv and yuvtoppm.  (Marc Boucher)
     Fixes to picttoppm.  (George Phillips)
-    Added 24-bit support to ilbmtoppm.  (Mark Thompson)
+    Added recognition of 24-bit images to ilbmtoppm.  (Mark Thompson)
 
 Changes since the X.V11R4 / comp.sources.misc distribution of 22nov89:
 
@@ -3387,7 +3398,7 @@ Changes since the X.V11R4 / comp.sources.misc distribution of 22nov89:
     Added -expand flag to pbmmask.
     Speedup to pnmflip - don't buffer if possible.
     Added color-name-to-value routine to ppm - uses X11's rgb.txt if present.
-    Updated Imakefile support to reflect X.V11R4.
+    Updated Imakefile function to reflect X.V11R4.
     Removed picttopbm.
     Improved pnmcut argument syntax so that negative coords work like pnmpaste.
     Added "magic" file, for use with the "file" program.
@@ -3411,7 +3422,7 @@ Changes since the X.V11R4 / comp.sources.misc distribution of 22nov89:
     Added -map flag to ppmquant - user-specifiable colormap.  Also, the
       Floyd-Steinberg error diffusion finally works right.
     Added -map flag to pgmtoppm.
-    Added DirectColor support to xwdtopnm and pnmtoxwd.
+    Added DirectColor capability to xwdtopnm and pnmtoxwd.
     Speedup to pgmtolj from Arthur David Olson: avoid sending whitespace.
     Fix to pbmtogo from Bo Thide': 2D compression now works.
 
@@ -3495,7 +3506,7 @@ Changes since the comp.sources.misc distribution of 31oct88:
 
 Changes since the X.V11R3 distribution of 31aug88:
 
-    The cbm format has been revised to support run-length encoding.
+    The cbm format has been revised to include run-length encoding.
     Pbmtops now does run-length encoding.
 
 Major changes since the X.V11R2 distribution of 28mar88: