about summary refs log tree commit diff
path: root/converter/other/pnmtopalm/pnmtopalm.c
diff options
context:
space:
mode:
Diffstat (limited to 'converter/other/pnmtopalm/pnmtopalm.c')
-rw-r--r--converter/other/pnmtopalm/pnmtopalm.c1235
1 files changed, 1235 insertions, 0 deletions
diff --git a/converter/other/pnmtopalm/pnmtopalm.c b/converter/other/pnmtopalm/pnmtopalm.c
new file mode 100644
index 00000000..3b9eec8f
--- /dev/null
+++ b/converter/other/pnmtopalm/pnmtopalm.c
@@ -0,0 +1,1235 @@
+/* pnmtopalm.c - read a PNM image and write a Palm Bitmap file
+ *
+ * Inspired by and using methods from ppmtoTbmp.c by Ian Goldberg
+ * <iang@cs.berkeley.edu>, which was based on ppmtopuzz.c by Jef
+ * Poskanzer, from the netpbm-1mar1994 package.
+ *
+ * Mods for multiple bits per pixel were added to ppmtoTbmp.c by
+ * George Caswell <tetsujin@sourceforge.net> and Bill Janssen
+ * <bill@janssen.org>.
+ *
+ * Major fixes and new capability added by Paul Bolle <pebolle@tiscali.nl>
+ * in late 2004 / early 2005.
+ *
+ * See LICENSE file for licensing information.
+ *
+ *  
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <assert.h>
+#include <limits.h>
+
+#include "pnm.h"
+#include "palm.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+
+enum compressionType {COMP_NONE, COMP_SCANLINE, COMP_RLE, COMP_PACKBITS};
+
+struct cmdline_info {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *inputFilespec;  /* Filespecs of input files */
+    char *transparent;          /* -transparent value.  Null if unspec */
+    unsigned int depth;         /* -depth value.  0 if unspec */
+    unsigned int maxdepth;      /* -maxdepth value.  0 if unspec */
+    enum compressionType compression;
+    unsigned int verbose;
+    unsigned int colormap;
+    unsigned int offset;        /* -offset specified */
+    unsigned int density;       /* screen density */
+    unsigned int withdummy;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv, struct cmdline_info *cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optStruct3 opt;  /* set by OPTENT3 */
+    optEntry *option_def;
+    unsigned int option_def_index;
+
+    unsigned int transSpec, depthSpec, maxdepthSpec, densitySpec;
+    unsigned int scanline_compression, rle_compression, packbits_compression;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0, "transparent",      OPT_STRING, 
+            &cmdlineP->transparent, &transSpec, 0);
+    OPTENT3(0, "depth",            OPT_UINT, 
+            &cmdlineP->depth,       &depthSpec, 0);
+    OPTENT3(0, "maxdepth",         OPT_UINT, 
+            &cmdlineP->maxdepth,    &maxdepthSpec, 0);
+    OPTENT3(0, "scanline_compression", OPT_FLAG, 
+            NULL,                   &scanline_compression, 0);
+    OPTENT3(0, "rle_compression",  OPT_FLAG, 
+            NULL,                   &rle_compression, 0);
+    OPTENT3(0, "packbits_compression", OPT_FLAG, 
+            NULL,                   &packbits_compression, 0);
+    OPTENT3(0, "verbose",          OPT_FLAG, 
+            NULL,                   &cmdlineP->verbose, 0);
+    OPTENT3(0, "colormap",         OPT_FLAG, 
+            NULL,                   &cmdlineP->colormap, 0);
+    OPTENT3(0, "offset",           OPT_FLAG, 
+            NULL,                   &cmdlineP->offset, 0);
+    OPTENT3(0, "density",          OPT_UINT, 
+            &cmdlineP->density,     &densitySpec, 0);
+    OPTENT3(0, "withdummy",        OPT_FLAG, 
+            NULL,                   &cmdlineP->withdummy, 0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE; /* We have some short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdline_p and others. */
+
+    if (depthSpec) {
+        if (cmdlineP->depth != 1 && cmdlineP->depth != 2 
+            && cmdlineP->depth != 4 && cmdlineP->depth != 8
+            && cmdlineP->depth != 16)
+            pm_error("invalid value for -depth: %u.  Valid values are "
+                     "1, 2, 4, 8, and 16", cmdlineP->depth);
+    } else
+        cmdlineP->depth = 0;
+
+    if (maxdepthSpec) {
+        if (cmdlineP->maxdepth != 1 && cmdlineP->maxdepth != 2 
+            && cmdlineP->maxdepth != 4 && cmdlineP->maxdepth != 8
+            && cmdlineP->maxdepth != 16)
+            pm_error("invalid value for -maxdepth: %u.  Valid values are "
+                     "1, 2, 4, 8, and 16", cmdlineP->maxdepth);
+    } else
+        cmdlineP->maxdepth = 0;
+
+    if (depthSpec && maxdepthSpec && 
+        cmdlineP->depth > cmdlineP->maxdepth)
+        pm_error("-depth value (%u) is greater than -maxdepth (%u) value.",
+                 cmdlineP->depth, cmdlineP->maxdepth);
+
+    if (!transSpec)
+        cmdlineP->transparent = NULL;
+
+    if (densitySpec) {
+        if (cmdlineP->density != PALM_DENSITY_LOW &&
+            cmdlineP->density != PALM_DENSITY_ONEANDAHALF &&
+            cmdlineP->density != PALM_DENSITY_DOUBLE &&
+            cmdlineP->density != PALM_DENSITY_TRIPLE &&
+            cmdlineP->density != PALM_DENSITY_QUADRUPLE)
+            pm_error("Invalid value for -density: %d.  Valid values are "
+                     "%d, %d, %d, %d and %d.", cmdlineP->density, 
+                     PALM_DENSITY_LOW, PALM_DENSITY_ONEANDAHALF,
+                     PALM_DENSITY_DOUBLE, PALM_DENSITY_TRIPLE,
+                     PALM_DENSITY_QUADRUPLE);
+    } else
+        cmdlineP->density = PALM_DENSITY_LOW;
+
+    if (cmdlineP->density != PALM_DENSITY_LOW && cmdlineP->withdummy)
+            pm_error("You can't specify -withdummy with -density value %u.  "
+                     "It is valid only with low density (%u)",
+                     cmdlineP->density, PALM_DENSITY_LOW);
+
+    if (cmdlineP->withdummy && !cmdlineP->offset)
+        pm_error("-withdummy does not make sense without -offset");
+
+    if (scanline_compression + rle_compression + packbits_compression > 1)
+        pm_error("You may specify only one of -scanline_compression, "
+                 "-rle_compression, and -packbits_compression");
+    else {
+        if (scanline_compression)
+            cmdlineP->compression = COMP_SCANLINE;
+        else if (rle_compression)
+            cmdlineP->compression = COMP_RLE;
+        else if (packbits_compression)
+            cmdlineP->compression = COMP_PACKBITS;
+        else
+            cmdlineP->compression = COMP_NONE;
+    }
+        
+    if (argc-1 > 1)
+        pm_error("This program takes at most 1 argument: the file name.  "
+                 "You specified %d", argc-1);
+    else if (argc-1 > 0) 
+        cmdlineP->inputFilespec = argv[1];
+    else
+        cmdlineP->inputFilespec = "-";
+}
+
+
+
+static void
+determinePalmFormat(unsigned int   const cols, 
+                    unsigned int   const rows, 
+                    xelval         const maxval, 
+                    int            const format, 
+                    xel **         const xels,
+                    unsigned int   const specified_bpp,
+                    unsigned int   const max_bpp, 
+                    bool           const custom_colormap,
+                    bool           const verbose,
+                    unsigned int * const bppP, 
+                    bool *         const directColorP, 
+                    Colormap *     const colormapP) {
+
+    if (PNM_FORMAT_TYPE(format) == PBM_TYPE) {
+        if (custom_colormap)
+            pm_error("You specified -colormap with a black and white input "
+                     "image.  -colormap is valid only with color.");
+        if (specified_bpp)
+            *bppP = specified_bpp;
+        else
+            *bppP = 1;    /* no point in wasting bits */
+        *directColorP = FALSE;
+        *colormapP = NULL;
+        if (verbose)
+            pm_message("output is black and white");
+    } else if (PNM_FORMAT_TYPE(format) == PGM_TYPE) {
+        /* we can usually handle this one, but may not have enough
+           pixels.  So check... */
+        if (custom_colormap)
+            pm_error("You specified -colormap with a black and white input"
+                     "image.  -colormap is valid only with color.");
+        if (specified_bpp)
+            *bppP = specified_bpp;
+        else if (max_bpp && (maxval >= (1 << max_bpp)))
+            *bppP = max_bpp;
+        else if (maxval > 16)
+            *bppP = 4;
+        else {
+            /* scale to minimum number of bpp needed */
+            for (*bppP = 1;  (1 << *bppP) < maxval;  *bppP *= 2)
+                ;
+        }
+        if (*bppP > 4)
+            *bppP = 4;
+        if (verbose)
+            pm_message("output is grayscale %d bits-per-pixel", *bppP);
+        *directColorP = FALSE;
+        *colormapP = NULL;
+    } else if (PNM_FORMAT_TYPE(format) == PPM_TYPE) {
+
+        /* We assume that we only get a PPM if the image cannot be
+           represented as PBM or PGM.  There are two options here: either
+           8-bit with a colormap, either the standard one or a custom one,
+           or 16-bit direct color.  In the 8-bit case, if "custom_colormap"
+           is specified (not recommended by Palm) we will put in our own
+           colormap; otherwise we will assume that the colors have been
+           mapped to the default Palm colormap by appropriate use of
+           pnmquant.  We try for 8-bit color first, since it works on
+           more PalmOS devices. 
+        */
+        if ((specified_bpp == 16) || 
+            (specified_bpp == 0 && max_bpp == 16)) {
+            /* we do the 16-bit direct color */
+            *directColorP = TRUE;
+            *colormapP = NULL;
+            *bppP = 16;
+        } else if (!custom_colormap) {
+            /* standard indexed 8-bit color */
+            *colormapP = palmcolor_build_default_8bit_colormap();
+            *bppP = 8;
+            if (((specified_bpp != 0) && (specified_bpp != 8)) ||
+                ((max_bpp != 0) && (max_bpp < 8)))
+                pm_error("Must use depth of 8 for color Palm Bitmap without "
+                         "custom color table.");
+            *directColorP = FALSE;
+            if (verbose)
+                pm_message("Output is color with default colormap at 8 bpp");
+        } else {
+            /* indexed 8-bit color with a custom colormap */
+            *colormapP = 
+                palmcolor_build_custom_8bit_colormap(rows, cols, xels);
+            for (*bppP = 1; (1 << *bppP) < (*colormapP)->ncolors; *bppP *= 2);
+            if (specified_bpp != 0) {
+                if (specified_bpp >= *bppP)
+                    *bppP = specified_bpp;
+                else
+                    pm_error("Too many colors for specified depth.  "
+                             "Use pnmquant to reduce.");
+            } else if ((max_bpp != 0) && (max_bpp < *bppP)) {
+                pm_error("Too many colors for specified max depth.  "
+                         "Use pnmquant to reduce.");
+            }
+            *directColorP = FALSE;
+            if (verbose)
+                pm_message("Output is color with custom colormap "
+                           "with %d colors at %d bpp", 
+                           (*colormapP)->ncolors, *bppP);
+        }
+    } else {
+        pm_error("unknown format 0x%x on input file", (unsigned) format);
+    }
+}
+
+
+
+static const char * 
+formatName(int const format) {
+    
+    const char * retval;
+
+    switch(PNM_FORMAT_TYPE(format)) {
+    case PBM_TYPE: retval = "black and white"; break;
+    case PGM_TYPE: retval = "grayscale";       break;
+    case PPM_TYPE: retval = "color";           break;
+    default:       retval = "???";             break;
+    }
+    return retval;
+}
+
+        
+
+static void
+findTransparentColor(char *         const colorSpec, 
+                     pixval         const newMaxval,
+                     bool           const directColor, 
+                     pixval         const maxval, 
+                     Colormap       const colormap,
+                     xel *          const transcolorP, 
+                     unsigned int * const transindexP) {
+
+    *transcolorP = ppm_parsecolor(colorSpec, maxval);
+    if (!directColor) {
+        Color_s const temp_color = 
+            ((((PPM_GETR(*transcolorP)*newMaxval) / maxval) << 16) 
+             | (((PPM_GETG(*transcolorP)*newMaxval) / maxval) << 8)
+             | ((PPM_GETB(*transcolorP)*newMaxval) / maxval));
+        Color const found = 
+            (bsearch(&temp_color,
+                     colormap->color_entries, colormap->ncolors,
+                     sizeof(Color_s), palmcolor_compare_colors));
+        if (!found) {
+            pm_error("Specified transparent color %s not found "
+                     "in colormap.", colorSpec);
+        } else
+            *transindexP = (*found >> 24) & 0xFF;
+    }
+}
+
+
+
+static unsigned int
+bitmapVersion(unsigned int         const bpp,
+              bool                 const colormap,
+              bool                 const transparent,
+              enum compressionType const compression,
+              unsigned int         const density) {
+/*----------------------------------------------------------------------------
+   Return the version number of the oldest version that can represent
+   the specified attributes.
+-----------------------------------------------------------------------------*/
+    unsigned int version;
+    /* we need Version 1 if we use more than 1 bpp,
+       Version 2 if we use compression or transparency,
+       Version 3 if density is 108 or higher
+    */
+    if (density > PALM_DENSITY_LOW)
+        version = 3;
+    else if (transparent || compression != COMP_NONE)
+        version = 2;
+    else if (bpp > 1 || colormap)
+        version = 1;
+    else
+        version = 0;
+
+    return version;
+}
+
+
+
+static void
+writeCommonHeader(unsigned int         const cols,
+                  unsigned int         const rows,
+                  unsigned int         const rowbytes,
+                  enum compressionType const compression,
+                  bool                 const colormap,
+                  bool                 const transparent,
+                  bool                 const directColor,
+                  unsigned int         const bpp,
+                  unsigned int         const version) {
+/*----------------------------------------------------------------------------
+   Write the first 10 bytes of the Palm Bitmap header.
+   These are common to all encodings (versions 0, 1, 2 and 3).
+-----------------------------------------------------------------------------*/
+    unsigned short flags;
+
+    if (cols > USHRT_MAX)
+        pm_error("Too many columns for Palm Bitmap: %u", cols);
+    pm_writebigshort(stdout, cols);    /* width */
+    if (rows > USHRT_MAX)
+        pm_error("Too many columns for Palm Bitmap: %u", rows);
+    pm_writebigshort(stdout, rows);    /* height */
+    if (rowbytes > USHRT_MAX)
+        pm_error("Too many bytes per row for Palm Bitmap: %u", rowbytes);
+    pm_writebigshort(stdout, rowbytes);
+
+    flags = 0;  /* initial value */
+    if (compression != COMP_NONE)
+        flags |= PALM_IS_COMPRESSED_FLAG;
+    if (colormap)
+        flags |= PALM_HAS_COLORMAP_FLAG;
+    if (transparent)
+        flags |= PALM_HAS_TRANSPARENCY_FLAG;
+    if (directColor)
+        flags |= PALM_DIRECT_COLOR_FLAG;
+    pm_writebigshort(stdout, flags);
+    assert(bpp <= UCHAR_MAX);
+    fputc(bpp, stdout);
+
+    fputc(version, stdout);
+}
+
+
+
+static unsigned char 
+compressionFieldValue(enum compressionType const compression) {
+
+    unsigned char retval;
+
+    switch (compression) {
+    case COMP_SCANLINE:
+        retval = PALM_COMPRESSION_SCANLINE;
+        break;
+    case COMP_RLE:
+        retval = PALM_COMPRESSION_RLE;
+        break;
+    case COMP_PACKBITS:
+        retval = PALM_COMPRESSION_PACKBITS;
+        break;
+    case COMP_NONE:
+        retval = 0x00;  /* empty */
+        break;
+    }
+    return retval;
+}
+
+
+
+static void
+writeRemainingHeaderLow(unsigned int         const nextDepthOffset,
+                        unsigned int         const transindex,
+                        enum compressionType const compression,
+                        unsigned int         const bpp) {
+/*----------------------------------------------------------------------------
+   Write last 6 bytes of a low density Palm Bitmap header. 
+-----------------------------------------------------------------------------*/
+    if (nextDepthOffset > USHRT_MAX)
+        pm_error("Image too large for Palm Bitmap");
+
+    pm_writebigshort(stdout, nextDepthOffset);
+
+    if (bpp != 16) {
+        assert(transindex <= UCHAR_MAX);
+        fputc(transindex, stdout);    /* transparent index */
+    } else
+        fputc(0, stdout);    /* the DirectInfoType will hold this info */
+    
+    fputc(compressionFieldValue(compression), stdout);
+
+    pm_writebigshort(stdout, 0);  /* reserved by Palm */
+}
+
+
+
+static void
+writeRemainingHeaderHigh(unsigned int         const bpp,
+                         enum compressionType const compression,
+                         unsigned int         const density,
+                         xelval               const maxval,
+                         bool                 const transparent,
+                         xel                  const transcolor,
+                         unsigned int         const transindex,
+                         unsigned int         const nextBitmapOffset) {
+/*----------------------------------------------------------------------------
+   Write last 16 bytes of a high density Palm Bitmap header. 
+-----------------------------------------------------------------------------*/
+    if ((nextBitmapOffset >> 31) > 1)
+        pm_error("Image too large for Palm Bitmap.  nextBitmapOffset "
+            "value doesn't fit in 4 bytes");
+
+    fputc(0x18, stdout); /* size of this high density header */
+
+    if (bpp != 16)
+        fputc(PALM_FORMAT_INDEXED, stdout);
+    else
+        fputc(PALM_FORMAT_565, stdout);
+
+    fputc(0x00, stdout); /* unused */
+
+    fputc(compressionFieldValue(compression), stdout);
+
+    pm_writebigshort(stdout, density);
+
+    if (transparent) {
+        if (bpp == 16) {
+            /* Blind guess here */
+            fputc(0, stdout);
+            fputc((PPM_GETR(transcolor) * 255) / maxval, stdout);
+            fputc((PPM_GETG(transcolor) * 255) / maxval, stdout);
+            fputc((PPM_GETB(transcolor) * 255) / maxval, stdout);
+        } else {
+            assert(transindex <= UCHAR_MAX);
+            fputc(0, stdout);
+            fputc(0, stdout);
+            fputc(0, stdout);
+            fputc(transindex, stdout);   /* transparent index */
+        }
+    } else
+        pm_writebiglong(stdout, 0);
+
+    pm_writebiglong(stdout, nextBitmapOffset);
+}
+
+
+
+static void
+writeDummy() {
+/*----------------------------------------------------------------------------
+   Write a dummy Palm Bitmap header.  This is a 16 byte header, of
+   type version 1 and with (only) pixelSize set to 0xFF.
+
+   An old viewer will see this as invalid due to the pixelSize, and stop
+   reading the stream.  A new viewer will recognize this for what it is
+   (a dummy header designed to stop old viewers from reading further in
+   the stream) and continue reading the stream.  Presumably, what follows
+   in the stream is understandable by a new viewer, but would confuse an
+   old one.
+-----------------------------------------------------------------------------*/
+    pm_writebiglong(stdout, 0x00);
+    pm_writebiglong(stdout, 0x00);
+    fputc(0xFF, stdout);               /* pixelSize */
+    fputc(0x01, stdout);               /* version */
+    pm_writebigshort(stdout, 0x00); 
+    pm_writebiglong(stdout, 0x00);
+}
+
+
+
+static void
+writeColormap(bool         const explicitColormap,
+              Colormap     const colormap,
+              bool         const directColor,
+              unsigned int const bpp,
+              bool         const transparent,
+              xel          const transcolor,
+              xelval       const maxval,
+              unsigned int const version) {
+              
+    /* if there's a colormap, write it out */
+    if (explicitColormap) {
+        unsigned int row;
+        if (!colormap)
+            pm_error("Internal error: user specified -colormap, but we did "
+                     "not generate a colormap.");
+        qsort (colormap->color_entries, colormap->ncolors,
+               sizeof(Color_s), palmcolor_compare_indices);
+        pm_writebigshort( stdout, colormap->ncolors );
+        for (row = 0;  row < colormap->ncolors; ++row)
+            pm_writebiglong (stdout, colormap->color_entries[row]);
+    }
+
+    if (directColor && (version < 3)) {
+        /* write the DirectInfoType (8 bytes) */
+        if (bpp == 16) {
+            fputc(5, stdout);   /* # of bits of red */
+            fputc(6, stdout);   /* # of bits of green */    
+            fputc(5, stdout);   /* # of bits of blue */
+            fputc(0, stdout);   /* reserved by Palm */
+        } else
+            pm_error("Don't know how to create %d bit DirectColor bitmaps.", 
+                     bpp);
+        if (transparent) {
+            fputc(0, stdout);
+            fputc((PPM_GETR(transcolor) * 255) / maxval, stdout);
+            fputc((PPM_GETG(transcolor) * 255) / maxval, stdout);
+            fputc((PPM_GETB(transcolor) * 255) / maxval, stdout);
+        } else
+            pm_writebiglong(stdout, 0);     /* no transparent color */
+    }
+}
+
+
+
+static void
+computeRawRowDirectColor(const xel *     const xelrow,
+                         unsigned int    const cols,
+                         xelval          const maxval,
+                         unsigned char * const rowdata) {
+
+    unsigned int col;
+    unsigned char *outptr;
+    
+    for (col = 0, outptr = rowdata; col < cols; ++col) {
+        unsigned int const color = 
+            ((((PPM_GETR(xelrow[col])*31)/maxval) << 11) |
+             (((PPM_GETG(xelrow[col])*63)/maxval) << 5) |
+             ((PPM_GETB(xelrow[col])*31)/maxval));
+        *outptr++ = (color >> 8) & 0xFF;
+        *outptr++ = color & 0xFF;
+    }
+}
+
+
+
+static void
+computeRawRowNonDirect(const xel *     const xelrow,
+                       unsigned int    const cols,
+                       xelval          const maxval,
+                       unsigned int    const bpp,
+                       Colormap        const colormap,
+                       unsigned int    const newMaxval,
+                       unsigned char * const rowdata) {
+
+    unsigned int col;
+    unsigned char *outptr;
+    unsigned char outbyte;
+        /* Accumulated bits to be output */
+    unsigned char outbit;
+        /* The lowest bit number we want to access for this pixel */
+
+    outbyte = 0x00;
+    outptr = rowdata;
+
+    for (outbit = 8 - bpp, col = 0; col < cols; ++col) {
+        unsigned int color;
+        if (!colormap) {
+            /* we assume grayscale, and use simple scaling */
+            color = (PNM_GET1(xelrow[col]) * newMaxval)/maxval;
+            if (color > newMaxval)
+                pm_error("oops.  Bug in color re-calculation code.  "
+                         "color of %u.", color);
+            color = newMaxval - color; /* note grayscale maps are inverted */
+        } else {
+            Color_s const temp_color =
+                ((((PPM_GETR(xelrow[col])*newMaxval)/maxval)<<16) 
+                 | (((PPM_GETG(xelrow[col])*newMaxval)/maxval)<<8)
+                 | (((PPM_GETB(xelrow[col])*newMaxval)/maxval)));
+            Color const found = (bsearch (&temp_color,
+                                          colormap->color_entries, 
+                                          colormap->ncolors,
+                                          sizeof(Color_s), 
+                                          palmcolor_compare_colors));
+            if (!found) {
+                pm_error("Color %d:%d:%d not found in colormap.  "
+                         "Try using pnmquant to reduce the "
+                         "number of colors.",
+                         PPM_GETR(xelrow[col]), 
+                         PPM_GETG(xelrow[col]), 
+                         PPM_GETB(xelrow[col]));
+            }
+            color = (*found >> 24) & 0xFF;
+        }
+
+        if (color > newMaxval)
+            pm_error("oops.  Bug in color re-calculation code.  "
+                     "color of %u.", color);
+        outbyte |= (color << outbit);
+        if (outbit == 0) {
+            /* Bit buffer is full.  Flush to to rowdata. */
+            *outptr++ = outbyte;
+            outbyte = 0x00;
+            outbit = 8 - bpp;
+        } else
+            outbit -= bpp;
+    }
+    if ((cols % (8 / bpp)) != 0) {
+        /* Flush bits remaining in the bit buffer to rowdata */
+        *outptr++ = outbyte;
+    }
+}
+
+
+struct seqBuffer {
+/*----------------------------------------------------------------------------
+   A buffer to which one can write bytes sequentially.
+-----------------------------------------------------------------------------*/
+    char * buffer;
+    unsigned int allocatedSize;
+    unsigned int occupiedSize;
+};
+
+
+static void
+createBuffer(struct seqBuffer ** const bufferPP) {
+
+    struct seqBuffer * bufferP;
+
+    MALLOCVAR_NOFAIL(bufferP);
+
+    bufferP->allocatedSize = 4096;
+    MALLOCARRAY(bufferP->buffer, bufferP->allocatedSize);
+    if (bufferP == NULL)
+        pm_error("Unable to allocate %u bytes of buffer", 
+                 bufferP->allocatedSize);
+    bufferP->occupiedSize = 0;
+
+    *bufferPP = bufferP;
+}
+
+
+
+static void
+destroyBuffer(struct seqBuffer * const bufferP) {
+
+    free(bufferP->buffer);
+    free(bufferP);
+}
+
+
+
+static void
+addByteToBuffer(struct seqBuffer * const bufferP,
+                unsigned char      const newByte) {
+
+    assert(bufferP->allocatedSize >= bufferP->occupiedSize);
+
+    if (bufferP->allocatedSize == bufferP->occupiedSize) {
+        bufferP->allocatedSize *= 2;
+        REALLOCARRAY(bufferP->buffer, bufferP->allocatedSize);
+        if (bufferP->buffer == NULL)
+            pm_error("Couldn't (re)allocate %u bytes of memory "
+                     "for buffer.", bufferP->allocatedSize);
+    }
+    bufferP->buffer[bufferP->occupiedSize++] = newByte;
+}
+
+
+
+static unsigned int
+bufferLength(struct seqBuffer * const bufferP) {
+    return bufferP->occupiedSize;
+}
+
+
+
+static void
+writeOutBuffer(struct seqBuffer * const bufferP,
+               FILE *             const fileP) {
+
+    size_t bytesWritten;
+
+    bytesWritten = fwrite(bufferP->buffer, sizeof(char), 
+                          bufferP->occupiedSize, fileP);
+
+    if (bytesWritten != bufferP->occupiedSize)
+        pm_error("fwrite() failed to write out the buffer.");
+}
+
+
+
+static void
+copyRowToBuffer(const unsigned char * const rowdata,
+                unsigned int          const rowbytes,
+                struct seqBuffer *    const rasterBufferP) {
+
+    unsigned int pos;
+    for (pos = 0; pos < rowbytes; ++pos)
+        addByteToBuffer(rasterBufferP, rowdata[pos]);
+} 
+
+
+
+static void
+scanlineCompressAndBufferRow(const unsigned char * const rowdata,
+                             unsigned int          const rowbytes,
+                             struct seqBuffer *    const rasterBufferP,
+                             const unsigned char * const lastrow) {
+/*----------------------------------------------------------------------------
+   Take the raw Palm Bitmap row 'rowdata', which is 'rowbytes'
+   columns, and add the scanline-compressed representation of it to
+   the buffer with handle 'rasterBufferP'.
+
+   'lastrow' is the raw contents of the row immediately before the one
+   we're compressing -- i.e. we compress with respect to that row.  This
+   function does not work on the first row of an image.
+-----------------------------------------------------------------------------*/
+    unsigned int pos;
+
+    for (pos = 0;  pos < rowbytes;  pos += 8) {
+        unsigned int const limit = MIN(rowbytes - pos, 8);
+
+        unsigned char map;
+            /* mask indicating which of the next 8 pixels are
+               different from the previous row, and therefore present
+               in the file immediately following the map byte.  
+            */
+        unsigned char differentPixels[8];
+        unsigned char *outptr;
+        unsigned char outbit;
+            
+        for (outbit = 0, map = 0x00, outptr = differentPixels;
+             outbit < limit;  
+             ++outbit) {
+            if (!lastrow 
+                || (lastrow[pos + outbit] != rowdata[pos + outbit])) {
+                map |= (1 << (7 - outbit));
+                *outptr++ = rowdata[pos + outbit];
+            }
+        }
+
+        addByteToBuffer(rasterBufferP, map);
+        {
+            unsigned int j;
+            for (j = 0; j < (outptr - differentPixels); ++j)
+                addByteToBuffer(rasterBufferP, differentPixels[j]);
+        }
+    }
+}
+
+
+
+static void
+rleCompressAndBufferRow(const unsigned char * const rowdata,
+                        unsigned int          const rowbytes,
+                        struct seqBuffer *    const rasterBufferP) {
+/*----------------------------------------------------------------------------
+   Take the raw Palm Bitmap row 'rowdata', which is 'rowbytes' bytes,
+   and add the rle-compressed representation of it to the buffer with
+   handle 'rasterBufferP'.
+-----------------------------------------------------------------------------*/
+    unsigned int pos;
+
+    /* we output a count of the number of bytes a value is
+       repeated, followed by that byte value 
+    */
+    pos = 0;
+    while (pos < rowbytes) {
+        unsigned int repeatcount;
+        for (repeatcount = 1;  
+             repeatcount < (rowbytes - pos) && repeatcount  < 255;  
+             ++repeatcount)
+            if (rowdata[pos + repeatcount] != rowdata[pos])
+                break;
+
+        addByteToBuffer(rasterBufferP, repeatcount);
+        addByteToBuffer(rasterBufferP, rowdata[pos]);
+        pos += repeatcount;
+    }
+}
+
+
+
+static void
+computeNextPackbitsRun(const unsigned char * const rowdata,
+                       unsigned int          const rowbytes,
+                       unsigned int          const startPos,
+                       unsigned int *        const nextPosP,
+                       unsigned char *       const output,
+                       int *                 const countP) {
+
+    unsigned int pos;
+    int count;
+    
+    pos = startPos;
+    count = 0;
+    
+    if (rowdata[pos] == rowdata[pos + 1]) {
+        ++pos;
+        --count;
+        while ((count > -127) && (pos < (rowbytes - 1)) &&
+               (rowdata[pos] == rowdata[pos + 1])) {
+            ++pos;
+            --count;
+        }
+        ++pos;  /* push pos past end of this run */
+    } else {
+        output[count] = rowdata[pos];
+        ++pos;
+        while ((count < 127) && (pos < (rowbytes - 1)) && 
+               (rowdata[pos] != rowdata[pos + 1])) {
+            ++count;
+            output[count] = rowdata[pos];
+            ++pos;
+        }
+        /* trailing literal */
+        if ((count < 127) && (pos == (rowbytes - 1)) &&
+            (rowdata[pos - 1] != rowdata[pos])) {
+            ++count;
+            output[count] = rowdata[pos];
+            ++pos;
+        }
+    }
+    *nextPosP = pos;
+    *countP = count;
+}
+
+
+
+static void
+addPackbitsRunToBuffer(const unsigned char * const rowdata,
+                       unsigned int          const rowbytes,
+                       unsigned int          const pos,
+                       unsigned char *       const output,
+                       int                   const count,
+                       struct seqBuffer *    const rasterBufferP) {
+
+    addByteToBuffer(rasterBufferP, (unsigned char)(signed char)count);
+    if (count < 0) {
+        addByteToBuffer(rasterBufferP, rowdata[pos - 1]);
+    } else {
+        unsigned int j;
+        for (j = 0; j <= count; j++)
+            addByteToBuffer(rasterBufferP, output[j]);
+    }
+    
+    if (pos == (rowbytes - 1) && (rowdata[pos - 1] != rowdata[pos])) {
+        /* orphaned byte, treat as literal */
+        addByteToBuffer(rasterBufferP, 0);
+        addByteToBuffer(rasterBufferP, rowdata[pos]);
+    }
+}
+
+
+
+static void
+packbitsCompressAndBufferRow(const unsigned char * const rowdata,
+                             unsigned int          const rowbytes,
+                             struct seqBuffer *    const rasterBufferP) {
+/*----------------------------------------------------------------------------
+   Take the raw Palm Bitmap row 'rowdata', which is 'rowbytess' bytes, and
+   add the packbits-compressed representation of it to the buffer 
+   with handle 'rasterBufferP'.
+-----------------------------------------------------------------------------*/
+    unsigned int position;
+        /* byte position within the row */
+
+    position = 0;  /* Start at beginning of row */
+
+    while (position < rowbytes - 1) {
+        unsigned char output[128];
+        int count;
+
+        computeNextPackbitsRun(rowdata, rowbytes, position, 
+                               &position, output, &count);
+
+        addPackbitsRunToBuffer(rowdata, rowbytes, position, output, count,
+                               rasterBufferP);
+    }
+}
+
+
+
+static void
+bufferRowFromRawRowdata(const unsigned char *  const rowdata,
+                        unsigned int           const rowbytes,
+                        enum compressionType   const compression,
+                        const unsigned char *  const lastrow,
+                        struct seqBuffer *     const rasterBufferP) {
+/*----------------------------------------------------------------------------
+   Starting with a raw (uncompressed) Palm raster line, do the
+   compression identified by 'compression' and add the compressed row
+   to the buffer with handle 'rasterBufferP'.
+
+   If 'compression' indicates scanline compression, 'lastrow' is the
+   row immediately preceding this one in the image (and this function
+   doesn't work on the first row of an image).  Otherwise, 'lastrow'
+   is meaningless.
+-----------------------------------------------------------------------------*/
+    switch (compression) {
+    case COMP_NONE:
+        copyRowToBuffer(rowdata, rowbytes, rasterBufferP);
+        break;
+    case COMP_SCANLINE:
+        scanlineCompressAndBufferRow(rowdata, rowbytes, rasterBufferP, 
+                                     lastrow);
+        break;
+    case COMP_RLE:
+        rleCompressAndBufferRow(rowdata, rowbytes, rasterBufferP);
+        break;
+    case COMP_PACKBITS:
+        packbitsCompressAndBufferRow(rowdata, rowbytes, rasterBufferP);
+        break;
+    }
+}
+
+
+
+static void
+bufferRow(const xel *          const xelrow,
+          unsigned int         const cols,
+          xelval               const maxval,
+          unsigned int         const rowbytes,
+          unsigned int         const bpp,
+          unsigned int         const newMaxval,
+          enum compressionType const compression,
+          bool                 const directColor,
+          Colormap             const colormap,
+          unsigned char *      const rowdata,
+          unsigned char *      const lastrow,
+          struct seqBuffer *   const rasterBufferP) {
+/*----------------------------------------------------------------------------
+   Add a row of the Palm Bitmap raster to buffer 'rasterBufferP'.
+   
+   'xelrow' is the image contents of row.  It is 'cols' columns wide.
+
+   If 'compression' indicates scanline compression, 'lastrow' is the
+   row immediately preceding this one in the image (and this function
+   doesn't work on the first row of an image).  Otherwise, 'lastrow'
+   is meaningless.
+
+   'rowdata' is a work buffer 'rowbytes' in size.
+-----------------------------------------------------------------------------*/
+    if (directColor)
+        computeRawRowDirectColor(xelrow, cols, maxval, rowdata);
+    else 
+        computeRawRowNonDirect(xelrow, cols, maxval, bpp, colormap, newMaxval,
+                               rowdata);
+
+    bufferRowFromRawRowdata(rowdata, rowbytes, compression,
+                            lastrow, rasterBufferP);
+}
+
+
+
+static void 
+bufferRaster(xel **               const xels,
+             unsigned int         const cols,
+             unsigned int         const rows,
+             xelval               const maxval,
+             unsigned int         const rowbytes,
+             unsigned int         const bpp,
+             unsigned int         const newMaxval,
+             enum compressionType const compression,
+             bool                 const directColor,
+             Colormap             const colormap,
+             struct seqBuffer **  const rasterBufferPP) {
+    
+    unsigned char * rowdata;
+    unsigned char * lastrow;
+    unsigned int row;
+
+    createBuffer(rasterBufferPP);
+    
+    MALLOCARRAY_NOFAIL(rowdata, rowbytes);
+    MALLOCARRAY_NOFAIL(lastrow, rowbytes);
+
+    /* And write out the data. */
+    for (row = 0; row < rows; ++row) {
+        bufferRow(xels[row], cols, maxval, rowbytes, bpp, newMaxval,
+                  compression,
+                  directColor, colormap, rowdata, row > 0 ? lastrow : NULL,
+                  *rasterBufferPP);
+
+        if (compression == COMP_SCANLINE)
+            memcpy(lastrow, rowdata, rowbytes);
+    }
+    free(lastrow);
+    free(rowdata);
+}
+
+
+
+static void
+computeOffsetStuff(bool                 const offsetWanted,
+                   unsigned int         const version,
+                   bool                 const directColor,
+                   enum compressionType const compression,
+                   bool                 const colormap,
+                   unsigned int         const colormapColorCount,
+                   unsigned int         const sizePlusRasterSize,
+                   unsigned int *       const nextDepthOffsetP,
+                   unsigned int *       const nextBitmapOffsetP,
+                   unsigned int *       const padBytesRequiredP) {
+    
+    if (offsetWanted) {
+        /* Offset is measured in 4-byte words (double words in
+           Intel/Microsoft terminology).  Account for header,
+           colormap, and raster size and round up 
+        */
+        unsigned int const headerSize = ((version < 3) ? 16 : 24);
+        unsigned int const colormapSize =
+            (colormap ? (2 + colormapColorCount * 4) : 0);
+        if (version < 3) {
+            unsigned int const directSize = 
+                (directColor && version < 3) ? 8 : 0; 
+            if (compression != COMP_NONE && sizePlusRasterSize > USHRT_MAX)
+                pm_error("Oversized compressed bitmap: %u bytes",
+                         sizePlusRasterSize);
+            *padBytesRequiredP = 4 - (sizePlusRasterSize + headerSize + 
+                                      directSize + colormapSize) % 4;
+            *nextDepthOffsetP = 
+                (sizePlusRasterSize + headerSize + 
+                 directSize + colormapSize + *padBytesRequiredP) / 4;
+        } else {
+            if (compression != COMP_NONE && (sizePlusRasterSize >> 31) > 1)
+                pm_error("Oversized compressed bitmap: %u bytes",
+                         sizePlusRasterSize);
+            /* Does version 3 need padding? Probably won't hurt */
+            *padBytesRequiredP = 4 - (sizePlusRasterSize + headerSize +
+                                      colormapSize) % 4;
+            *nextBitmapOffsetP = sizePlusRasterSize + headerSize + 
+                colormapSize + *padBytesRequiredP;
+        }
+    } else {
+        *padBytesRequiredP = 0;
+        *nextDepthOffsetP = 0;
+        *nextBitmapOffsetP = 0;
+    }
+}
+
+
+
+static void
+writeRasterSize(unsigned int const sizePlusRasterSize,
+                unsigned int const version,
+                FILE *       const fileP) {
+/*----------------------------------------------------------------------------
+   Write to file 'fileP' a raster size field for a Palm Bitmap version
+   'version' header, indicating 'sizePlusRasterSize' bytes.
+-----------------------------------------------------------------------------*/
+    if (version < 3) 
+        pm_writebigshort(fileP, sizePlusRasterSize);
+    else
+        pm_writebiglong(fileP, sizePlusRasterSize);
+}
+
+
+
+static void
+writeBitmap(xel **               const xels,
+            unsigned int         const cols,
+            unsigned int         const rows,
+            xelval               const maxval,
+            unsigned int         const rowbytes,
+            unsigned int         const bpp,
+            unsigned int         const newMaxval,
+            enum compressionType const compression,
+            bool                 const transparent,
+            bool                 const directColor,
+            bool                 const offsetWanted,
+            bool                 const hasColormap,
+            Colormap             const colormap,
+            unsigned int         const transindex,
+            xel                  const transcolor,
+            unsigned int         const version,
+            unsigned int         const density,
+            bool                 const withdummy) {
+    
+    unsigned int sizePlusRasterSize;
+    unsigned int nextDepthOffset;
+    unsigned int nextBitmapOffset;
+        /* Offset from the beginning of the image we write to the beginning
+           of the next one, assuming user writes another one following this
+           one.
+           nextDepthOffset is used in encodings 1, 2 and is in 4 byte words 
+           nextBitmapOffset is used in encoding 3, is in 4 bytes 
+        */
+    unsigned int padBytesRequired;
+        /* Number of bytes of padding we need to put after the image in
+           order to align properly for User to add the next image to the
+           stream.
+        */
+    struct seqBuffer * rasterBufferP;
+
+    writeCommonHeader(cols, rows, rowbytes, compression, hasColormap, 
+                      transparent, directColor, bpp, version);
+    
+    bufferRaster(xels, cols, rows, maxval, rowbytes, bpp, newMaxval,
+                 compression, directColor, colormap, &rasterBufferP);
+
+    /* rasterSize itself takes 2 or 4 bytes */
+    if (version < 3)
+        sizePlusRasterSize = 2 + bufferLength(rasterBufferP);
+    else
+        sizePlusRasterSize = 4 + bufferLength(rasterBufferP);
+    
+    computeOffsetStuff(offsetWanted, version, directColor, compression,
+                       hasColormap, hasColormap ? colormap->ncolors : 0, 
+                       sizePlusRasterSize,
+                       &nextDepthOffset, &nextBitmapOffset,
+                       &padBytesRequired);
+
+    if (version < 3)
+        writeRemainingHeaderLow(nextDepthOffset, transindex, compression, bpp);
+    else
+        writeRemainingHeaderHigh(bpp, compression, density,
+                                 maxval, transparent, transcolor,
+                                 transindex, nextBitmapOffset);
+
+    writeColormap(hasColormap, colormap, directColor, bpp, 
+                  transparent, transcolor, maxval, version);
+
+    if (compression != COMP_NONE)
+        writeRasterSize(sizePlusRasterSize, version, stdout);
+
+    writeOutBuffer(rasterBufferP, stdout);
+
+    destroyBuffer(rasterBufferP);
+
+    {
+        unsigned int i;
+        for (i = 0; i < padBytesRequired; ++i)
+            fputc(0x00, stdout);
+    }
+
+    if (withdummy)
+        writeDummy();
+}        
+
+
+
+int 
+main( int argc, char **argv ) {
+    struct cmdline_info cmdline;
+    unsigned int version;
+    FILE* ifP;
+    xel** xels;
+    xel transcolor;
+    unsigned int transindex;
+    int rows, cols;
+    unsigned int rowbytes;
+    xelval maxval;
+    int format;
+    unsigned int bpp;
+    bool directColor;
+    unsigned int newMaxval;
+    Colormap colormap;
+    
+    /* Parse default params */
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilespec);
+
+    xels = pnm_readpnm(ifP, &cols, &rows, &maxval, &format);
+    pm_close(ifP);
+
+    if (cmdline.verbose)
+        pm_message("Input is %dx%d %s, maxval %d", 
+                   cols, rows, formatName(format), maxval);
+    
+    determinePalmFormat(cols, rows, maxval, format, xels, cmdline.depth,
+                        cmdline.maxdepth, cmdline.colormap, cmdline.verbose,
+                        &bpp, &directColor, &colormap);
+
+    newMaxval = (1 << bpp) - 1;
+
+    if (cmdline.transparent) 
+        findTransparentColor(cmdline.transparent, newMaxval, directColor,
+                             maxval, colormap, &transcolor, &transindex);
+    else 
+        transindex = 0;
+
+    rowbytes = ((cols + (16 / bpp -1)) / (16 / bpp)) * 2;    
+        /* bytes per row - always a word boundary */
+
+    version = bitmapVersion(bpp, cmdline.colormap, !!cmdline.transparent, 
+                            cmdline.compression, cmdline.density);
+
+    writeBitmap(xels, cols, rows, maxval,
+                rowbytes, bpp, newMaxval, cmdline.compression,
+                !!cmdline.transparent, directColor, cmdline.offset, 
+                cmdline.colormap, colormap, transindex, transcolor,
+                version, cmdline.density, cmdline.withdummy);
+    
+    return 0;
+}