about summary refs log tree commit diff
diff options
context:
space:
mode:
authorgiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2010-09-25 20:41:29 +0000
committergiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2010-09-25 20:41:29 +0000
commit1511ebe5ed9943e31a66214b720544cdbf515eaa (patch)
treec2c1d586c20d0d20c4a93e3591bc45e1712dbd0b
parent423b5d086c5cdec480bd9fb7d97ed3067d371b14 (diff)
downloadnetpbm-mirror-1511ebe5ed9943e31a66214b720544cdbf515eaa.tar.gz
netpbm-mirror-1511ebe5ed9943e31a66214b720544cdbf515eaa.tar.xz
netpbm-mirror-1511ebe5ed9943e31a66214b720544cdbf515eaa.zip
Get pamtopdbimg, pdbimgtopam working
git-svn-id: http://svn.code.sf.net/p/netpbm/code/trunk@1302 9d0c8265-081b-0410-96cb-a4ca84ce46f8
-rw-r--r--converter/other/Makefile4
-rw-r--r--converter/other/ipdb.c1248
-rw-r--r--converter/other/ipdb.h68
-rw-r--r--converter/other/pamtopdbimg.c613
-rw-r--r--converter/other/pdbimgtopam.c774
-rw-r--r--doc/HISTORY2
6 files changed, 1518 insertions, 1191 deletions
diff --git a/converter/other/Makefile b/converter/other/Makefile
index 64c69b1c..cded8f33 100644
--- a/converter/other/Makefile
+++ b/converter/other/Makefile
@@ -93,7 +93,7 @@ ifneq ($(DONT_HAVE_PROCESS_MGMT),Y)
   PORTBINARIES += pstopnm
 endif
 
-BINARIES = $(PORTBINARIES) pnmtorast rasttopnm pamtopdbimg
+BINARIES = $(PORTBINARIES) pnmtorast rasttopnm pamtopdbimg pdbimgtopam
 
 ifeq ($(HAVE_PNGLIB),Y)
   BINARIES += pnmtopng pngtopam pamrgbatopng
@@ -210,7 +210,7 @@ pnmtorast rasttopnm: %: %.o rast.o $(NETPBMLIB) $(LIBOPT)
 	  $(shell $(LIBOPT) $(NETPBMLIB)) \
 	  $(MATHLIB) $(LDFLAGS) $(LDLIBS) $(RPATH) $(LADD)
 
-pamtopdbimg: %: %.o ipdb.o $(NETPBMLIB) $(LIBOPT)
+pdbimgtopam pamtopdbimg: %: %.o ipdb.o $(NETPBMLIB) $(LIBOPT)
 	$(LD) -o $@ $@.o ipdb.o \
 	  $(shell $(LIBOPT) $(NETPBMLIB)) \
 	  $(LDFLAGS) $(LDLIBS) $(RPATH) $(LADD)
diff --git a/converter/other/ipdb.c b/converter/other/ipdb.c
index 39e65b35..5c1fc314 100644
--- a/converter/other/ipdb.c
+++ b/converter/other/ipdb.c
@@ -19,6 +19,7 @@
  *   Authors:  Eric A. Howe (mu@trends.net)
  *             Bryan Henderson, 2010
  */
+#include <assert.h>
 #include <time.h>
 #include <string.h>
 
@@ -28,143 +29,153 @@
 
 typedef uint32_t pilot_time_t;
 
-/*
- * Pixel setting macros.
- */
-#define setg16pixel(b,v,o)  ((b) |= ((v) << (4 - 4*(o))))
-#define getg16pixel(b,o)    (((b) >> (4 - 4*(o))) & 0x0f)
-#define setgpixel(b,v,o)    ((b) |= ((v) << (6 - 2*(o))))
-#define getgpixel(b,o)      (((b) >> (6 - 2*(o))) & 0x03)
-#define setmpixel(b,v,o)    ((b) |= ((v) << (7 - (o))))
-#define getmpixel(b,o)      (((b) >> (7 - (o))) & 0x01)
 
-/*
- * Pixels/byte.
- */
-#define img_ppb(i) (                            \
-        (i)->type == IMG_GRAY   ? 4 :           \
-        (i)->type == IMG_GRAY16 ? 2 :           \
-        8                                       \
-        )
 
-/*
- * Size (in bytes) of an image's data.
- */
-#define img_size(i) (size_t)((i)->width/img_ppb(i)*(i)->height)
+
+static unsigned int
+imgPpb(IMAGE * const imgP) {
+/*----------------------------------------------------------------------------
+   Pixels per byte
+-----------------------------------------------------------------------------*/
+    return
+        imgP->type == IMG_GRAY   ? 4 :
+        imgP->type == IMG_GRAY16 ? 2 :
+        8;
+}
+
+
+
+unsigned int
+ipdb_img_ppb(IMAGE * const imgP) {
+/*----------------------------------------------------------------------------
+   Pixels per byte
+-----------------------------------------------------------------------------*/
+    return imgPpb(imgP);
+}
+
+
+
+size_t
+ipdb_img_size(IMAGE * const imgP) {
+/*----------------------------------------------------------------------------
+  Size (in bytes) of an image's data.
+-----------------------------------------------------------------------------*/
+    return (size_t)(imgP->width / imgPpb(imgP) * imgP->height);
+}
+
+
 
 /*
  * Return the start of row `r'.
  */
-#define img_row(i, r)   (&(i)->data[(r)*(i)->width/img_ppb(i)])
+ uint8_t *
+ ipdb_img_row(IMAGE *      const imgP,
+              unsigned int const row) {
 
-/*
- * Only use four bytes of these.
- */
-#define IPDB_vIMG   "vIMG"
-#define IPDB_View   "View"
+     return &imgP->data[(row) * imgP->width / imgPpb(imgP)];
+ }
 
-/*
- * Only use three bytes of this.
- */
-#define IPDB_MYST   "\x40\x6f\x80"
 
-static pilot_time_t const unixepoch = (66*365+17)*24*3600;
-    /* The unix epoch in Mac time (the Mac epoch is 00:00 UTC 1904.01.01).
-       The 17 is the number of leap years.
-    */
 
-static const char * const errorDesc[] = {
-    /* E_BADCOLORS      */
-    "Invalid palette, only {0x00, 0x55, 0xAA, 0xFF} allowed.",
+ #define img_row(i, r)   
 
-    /* E_NOTIMAGE       */
-    "Not an image file.",
+ static pilot_time_t const unixepoch = (66*365+17)*24*3600;
+     /* The unix epoch in Mac time (the Mac epoch is 00:00 UTC 1904.01.01).
+        The 17 is the number of leap years.
+     */
 
-    /* E_IMAGETHERE     */
-    "Image record already present, logic error.",
+ static const char * const errorDesc[] = {
+     /* E_BADCOLORS      */
+     "Invalid palette, only {0x00, 0x55, 0xAA, 0xFF} allowed.",
 
-    /* E_IMAGENOTTHERE  */
-    "Image record required before text record, logic error.",
+     /* E_NOTIMAGE       */
+     "Not an image file.",
 
-    /* E_TEXTTHERE      */
-    "Text record already present, logic error.",
+     /* E_IMAGETHERE     */
+     "Image record already present, logic error.",
 
-    /* E_NOTRECHDR      */
-    "Invalid record header encountered.",
+     /* E_IMAGENOTTHERE  */
+     "Image record required before text record, logic error.",
 
-    /* E_UNKNOWNRECHDR  */
-    "Unknown record header.",
+     /* E_TEXTTHERE      */
+     "Text record already present, logic error.",
 
-    /* E_TOOBIGG        */
-    "Image too big, maximum size approx. 640*400 gray pixels.",
+     /* E_NOTRECHDR      */
+     "Invalid record header encountered.",
 
-    /* E_TOOBIGM        */
-    "Image too big, maximum size approx. 640*800 monochrome pixels.",
-};
+     /* E_UNKNOWNRECHDR  */
+     "Unknown record header.",
 
+     /* E_TOOBIGG        */
+     "Image too big, maximum size approx. 640*400 gray pixels.",
 
+     /* E_TOOBIGM        */
+     "Image too big, maximum size approx. 640*800 monochrome pixels.",
+ };
 
-const char *
-ipdb_err(int const e) {
 
-    if (e < 0)
-        return e >= E_LAST ? errorDesc[-e - 1] : "unknown error";
-    else
-        return strerror(e);
-}
 
+ const char *
+ ipdb_err(int const e) {
 
+     if (e < 0)
+         return e >= E_LAST ? errorDesc[-e - 1] : "unknown error";
+     else
+         return strerror(e);
+ }
 
-static void
-rechdr_free(RECHDR * const recP) {
 
-    if (recP) {
-        free(recP->extra);
-        free(recP);
-    }
-}
 
+ static void
+ rechdr_free(RECHDR * const recP) {
 
+     if (recP) {
+         free(recP->extra);
+         free(recP);
+     }
+ }
 
-static void
-image_free(IMAGE * const imgP) {
 
-    if (imgP) {
-        rechdr_free(imgP->r);
-        free(imgP->data);
-        free(imgP);
-    }
-}
 
+ void
+ ipdb_image_free(IMAGE * const imgP) {
 
+     if (imgP) {
+         rechdr_free(imgP->r);
+         free(imgP->data);
+         free(imgP);
+     }
+ }
 
-static void
-text_free(TEXT * const textP) {
 
-    if (textP) {
-        rechdr_free(textP->r);
-        free(textP->data);
-        free(textP);
-    }
-}
 
+ void
+ ipdb_text_free(TEXT * const textP) {
 
+     if (textP) {
+         rechdr_free(textP->r);
+         free(textP->data);
+         free(textP);
+     }
+ }
 
-static void
-pdbhead_free(PDBHEAD * const headP) {
 
-    free(headP);
-}
 
+ void
+ ipdb_pdbhead_free(PDBHEAD * const headP) {
 
+     free(headP);
+ }
 
-static void
-ipdb_clear(IPDB * const pdbP) {
 
-    if (pdbP) {
-        image_free(pdbP->i);
-        text_free(pdbP->t);
-        pdbhead_free(pdbP->p);
+
+ void
+ ipdb_clear(IPDB * const pdbP) {
+     
+     if (pdbP) {
+         ipdb_image_free(pdbP->i);
+         ipdb_text_free(pdbP->t);
+         ipdb_pdbhead_free(pdbP->p);
     }
 }
 
@@ -179,8 +190,8 @@ ipdb_free(IPDB * const pdbP) {
 
 
 
-static PDBHEAD *
-pdbhead_alloc(const char * const name) {
+PDBHEAD *
+ipdb_pdbhead_alloc(const char * const name) {
 
     PDBHEAD * pdbHeadP;
 
@@ -240,8 +251,8 @@ rechdr_alloc(int      const type,
 
 
 
-static IMAGE *
-image_alloc(const char * const name,
+IMAGE *
+ipdb_image_alloc(const char * const name,
             int          const type,
             int          const w,
             int          const h) {
@@ -280,7 +291,7 @@ image_alloc(const char * const name,
             failed = true;
         
         if (failed)
-            image_free(imgP);
+            ipdb_image_free(imgP);
     } else 
         failed = true;
 
@@ -289,8 +300,8 @@ image_alloc(const char * const name,
 
 
 
-static TEXT *
-text_alloc(const char * const content) {
+TEXT *
+ipdb_text_alloc(const char * const content) {
 
     TEXT * textP;
     bool failed;
@@ -321,7 +332,7 @@ text_alloc(const char * const content) {
             failed = true;
 
         if (failed)
-            text_free(textP);
+            ipdb_text_free(textP);
     } else
         failed = true;
 
@@ -344,7 +355,7 @@ ipdb_alloc(const char * const name) {
         MEMSZERO(pdbP);
 
         if (name) {
-            pdbP->p = pdbhead_alloc(name);
+            pdbP->p = ipdb_pdbhead_alloc(name);
 
             if (!pdbP->p)
                 failed = true;
@@ -359,1028 +370,13 @@ ipdb_alloc(const char * const name) {
 
 
 
-static uint8_t *
-decompress(const uint8_t * const buffer,
-           int             const byteCount) {
-/*
- * This will *always* free `buffer'.
- *
- * The compression scheme used is a simple RLE; the control codes,
- * CODE, are one byte and have the following meanings:
- *
- *  CODE >  0x80    Insert (CODE + 1 - 0x80) copies of the next byte.
- *  CODE <= 0x80    Insert the next (CODE + 1) literal bytes.
- *
- * Compressed pieces can (and do) cross row boundaries.
- */
-    uint8_t * data;
-
-    MALLOCARRAY(data, byteCount);
-
-    if (data) {
-        const uint8_t * inP;
-        uint8_t * outP;
-        int bytesLeft;
-        
-        for (bytesLeft = byteCount, inP  = &buffer[0], outP = &data[0];
-             bytesLeft > 0;
-            ) {
-
-            int got, put;
-
-            if (*inP > 0x80) {
-                put = *inP++ + 1 - 0x80;
-                memset(outP, *inP, put);
-                got = 1;
-            } else {
-                put = *inP++ + 1;
-                memcpy(outP, inP, put);
-                got = put;
-            }
-            inP       += got;
-            outP      += put;
-            bytesLeft -= put;
-        }
-    }
-    free((void *)buffer);
-    return data;
-}
-
-
-
-#define UNKNOWN_OFFSET  (uint32_t)-1
-
-static int
-image_read_data(IMAGE *  const imgP,
-                uint32_t const end_offset,
-                FILE *   const fP) {
-
-    int retval;
-    size_t data_size;
-    uint8_t * buffer;
-
-    data_size = 0;  /* initial value */
-
-    if (end_offset == UNKNOWN_OFFSET) {
-        /*
-         * Read until EOF. Some of them have an extra zero byte
-         * dangling off the end.  I originally thought this was
-         * an empty note record (even though there was no record
-         * header for it); however, the release notes for Image
-         * Compression Manager 1.1 on http://www.pilotgear.com
-         * note this extra byte as a bug in Image Compression
-         * Manager 1.0 which 1.1 fixes.  We'll just blindly read
-         * this extra byte and ignore it by paying attention to
-         * the image dimensions.
-         */
-        MALLOCARRAY(buffer, img_size(imgP));
-
-        if (buffer == NULL)
-            retval = ENOMEM;
-        else {
-            data_size = fread(buffer, 1, img_size(imgP), fP);
-            if (data_size <= 0)
-                retval = EIO;
-            else
-                retval = 0;
-
-            if (retval != 0)
-                free(buffer);
-        }
-    } else {
-        /*
-         * Read to the indicated offset.
-         */
-        data_size = end_offset - ftell(fP) + 1;
-        
-        MALLOCARRAY(buffer, data_size);
-
-        if (buffer == NULL)
-            retval = ENOMEM;
-        else {
-            ssize_t rc;
-            rc = fread(buffer, 1, data_size, fP);
-            if (rc != data_size)
-                retval = EIO;
-            else
-                retval = 0;
-
-            if (retval != 0)
-                free(buffer);
-        }
-    }
-    if (retval == 0) {
-        /*
-         * Compressed data can cross row boundaries so we decompress
-         * the data here to avoid messiness in the row access functions.
-         */
-        if (data_size != img_size(imgP)) {
-            imgP->data = decompress(buffer, img_size(imgP));
-            if (imgP->data == NULL)
-                retval = ENOMEM;
-            else
-                imgP->compressed = true;
-        } else {
-            imgP->compressed = false;
-            imgP->data       = buffer;
-        }
-
-        if (retval != 0)
-            free(buffer);
-    }
-    return retval;
-}
-
-
-
-static int
-image_read(IMAGE *  const imgP,
-           uint32_t const end_offset,
-           FILE *   const fP) {
-
-    if (imgP) {
-        imgP->r->offset = (uint32_t)ftell(fP);
-
-        fread(&imgP->name, 1, 32, fP);
-        pm_readcharu(fP, &imgP->version);
-        pm_readcharu(fP, &imgP->type);
-        fread(&imgP->reserved1, 1, 4, fP);
-        fread(&imgP->note, 1, 4, fP);
-        pm_readbigshortu(fP, &imgP->x_last);
-        pm_readbigshortu(fP, &imgP->y_last);
-        fread(&imgP->reserved2, 1, 4, fP);
-        pm_readbigshortu(fP, &imgP->x_anchor);
-        pm_readbigshortu(fP, &imgP->y_anchor);
-        pm_readbigshortu(fP, &imgP->width);
-        pm_readbigshortu(fP, &imgP->height);
-        
-        image_read_data(imgP, end_offset, fP);
-    }
-    return 0;
-}
-
-
-
-static int
-text_read(TEXT * const textP,
-          FILE * const fP) {
-
-    int retval;
-    char    * s;
-    char    buf[128];
-    int used, alloced, len;
-
-    if (textP == NULL)
-        return 0;
-
-    textP->r->offset = (uint32_t)ftell(fP);
-    
-    /*
-     * What a pain in the ass!  Why the hell isn't there a length
-     * attached to the text record?  I suppose the designer wasn't
-     * concerned about non-seekable (i.e. pipes) input streams.
-     * Perhaps I'm being a little harsh, the lack of a length probably
-     * isn't much of an issue on the Pilot.
-     */
-    used    = 0;
-    alloced = 0;
-    s       = NULL;
-    retval = 0;  /* initial value */
-    while ((len = fread(buf, 1, sizeof(buf), fP)) != 0 && retval == 0) {
-        if (buf[len - 1] == '\0')
-            --len;
-        if (used + len > alloced) {
-            alloced += 2 * sizeof(buf);
-            REALLOCARRAY(s, alloced);
-
-            if (s == NULL)
-                retval = ENOMEM;
-        }
-        if (retval == 0) {
-            memcpy(s + used, buf, len);
-            used += len;
-        }
-    }
-    if (retval == 0) {
-        textP->data = calloc(1, used + 1);
-        if (textP->data == NULL)
-            retval = ENOMEM;
-        else
-            memcpy(textP->data, s, used);
-    }
-    if (s)
-        free(s);
-
-    return retval;
-}
-
-
-
-static int
-pdbhead_read(PDBHEAD * const pdbHeadP,
-             FILE *    const fP) {
-
-    int retval;
-
-    fread(pdbHeadP->name, 1, 32, fP);
-    pm_readbigshortu(fP, &pdbHeadP->flags);
-    pm_readbigshortu(fP, &pdbHeadP->version);
-    pm_readbiglongu2(fP, &pdbHeadP->ctime);
-    pm_readbiglongu2(fP, &pdbHeadP->mtime);
-    pm_readbiglongu2(fP, &pdbHeadP->btime);
-    pm_readbiglongu2(fP, &pdbHeadP->mod_num);
-    pm_readbiglongu2(fP, &pdbHeadP->app_info);
-    pm_readbiglongu2(fP, &pdbHeadP->sort_info);
-    fread(pdbHeadP->type, 1, 4,  fP);
-    fread(pdbHeadP->id,   1, 4,  fP);
-    pm_readbiglongu2(fP, &pdbHeadP->uniq_seed);
-    pm_readbiglongu2(fP, &pdbHeadP->next_rec);
-    pm_readbigshortu(fP, &pdbHeadP->num_recs);
-
-    if (!memeq(pdbHeadP->type, IPDB_vIMG, 4) 
-        || !memeq(pdbHeadP->id, IPDB_View, 4))
-        retval = E_NOTIMAGE;
-    else
-        retval = 0;
-
-    return retval;
-}
-
-
-
-static int
-rechdr_read(RECHDR * const rechdrP,
-            FILE *   const fP) {
-
-    int retval;
-    off_t   len;
-
-    pm_readbiglongu2(fP, &rechdrP->offset);
-
-    len = (off_t)rechdrP->offset - ftell(fP);
-    switch(len) {
-    case 4:
-    case 12:
-        /*
-         * Version zero (eight bytes of record header) or version
-         * two with a note (two chunks of eight record header bytes).
-         */
-        fread(&rechdrP->unknown[0], 1, 3, fP);
-        fread(&rechdrP->rec_type,   1, 1, fP);
-        rechdrP->n_extra = 0;
-        rechdrP->extra   = NULL;
-        retval = 0;
-        break;
-    case 6:
-        /*
-         * Version one (ten bytes of record header).
-         */
-        fread(&rechdrP->unknown[0], 1, 3, fP);
-        fread(&rechdrP->rec_type,   1, 1, fP);
-        rechdrP->n_extra = 2;
-        MALLOCARRAY(rechdrP->extra, rechdrP->n_extra);
-        if (rechdrP->extra == NULL)
-            retval = ENOMEM;
-        else {
-            fread(rechdrP->extra, 1, rechdrP->n_extra, fP);
-            retval = 0;
-        }
-        break;
-    default:
-        /*
-         * hmmm.... I'll assume this is the record header
-         * for a text record.
-         */
-        fread(&rechdrP->unknown[0], 1, 3, fP);
-        fread(&rechdrP->rec_type,   1, 1, fP);
-        rechdrP->n_extra = 0;
-        rechdrP->extra   = NULL;
-        retval = 0;
-        break;
-    }
-    if (retval == 0) {
-        if ((rechdrP->rec_type != IMG_REC && rechdrP->rec_type != TEXT_REC)
-            || !memeq(rechdrP->unknown, IPDB_MYST, 3))
-            retval = E_NOTRECHDR;
-    }
-    return retval;
-}
-
-
-
-int
-ipdb_read(IPDB * const pdbP,
-          FILE * const fP) {
-
-    int retval;
-
-    ipdb_clear(pdbP);
-
-    pdbP->p = pdbhead_alloc(NULL);
-
-    if (pdbP->p == NULL)
-        retval = ENOMEM;
-    else {
-        int status;
-
-        status = pdbhead_read(pdbP->p, fP);
-
-        if (status != 0)
-            retval = status;
-        else {
-            pdbP->i = image_alloc(pdbP->p->name, IMG_GRAY, 0, 0);
-            if (pdbP->i == NULL)
-                retval = ENOMEM;
-            else {
-                int status;
-                status = rechdr_read(pdbP->i->r, fP);
-                if (status != 0)
-                    retval = status;
-                else {
-                    if (pdbP->p->num_recs > 1) {
-                        pdbP->t = text_alloc(NULL);
-                        if (pdbP->t == NULL)
-                            retval = ENOMEM;
-                        else {
-                            int status;
-                            status = rechdr_read(pdbP->t->r, fP);
-                            if (status != 0)
-                                retval = status;
-                            else
-                                retval = 0;
-                        }
-                    } else
-                        retval = 0;
-                    
-                    if (retval == 0) {
-                        uint32_t const offset =
-                            pdbP->t == NULL ?
-                            UNKNOWN_OFFSET : pdbP->t->r->offset - 1;
-
-                        int status;
-
-                        status = image_read(pdbP->i, offset, fP);
-                        if (status != 0)
-                            retval = status;
-                        else {
-                            if (pdbP->t != NULL) {
-                                int status;
-                                
-                                status = text_read(pdbP->t, fP);
-                                if (status != 0)
-                                    retval = status;
-                            }
-                        }
-                    }
-                }
-            }
-        }
-    }
-    return retval;
-}
-
-
-
-static const uint8_t *
-g16unpack(const uint8_t * const p,
-          uint8_t *       const g,
-          int             const w) {
-
-    static const uint8_t pal[] =
-        {0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88,
-         0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x00};
-    const uint8_t * seg;
-    unsigned int i;
-
-    for (i = 0, seg = p; i < w; i += 2, ++seg) {
-        g[i + 0] = pal[getg16pixel(*seg, 0)];
-        g[i + 1] = pal[getg16pixel(*seg, 1)];
-    }
-    return g;
-}
-
-
-
-static const uint8_t *
-gunpack(const uint8_t * const p,
-        uint8_t *       const g,
-        int             const w) {
-
-    static const uint8_t pal[] = {0xff, 0xaa, 0x55, 0x00};
-    const uint8_t * seg;
-    unsigned int i;
-
-    for (i = 0, seg = p; i < w; i += 4, ++seg) {
-        g[i + 0] = pal[getgpixel(*seg, 0)];
-        g[i + 1] = pal[getgpixel(*seg, 1)];
-        g[i + 2] = pal[getgpixel(*seg, 2)];
-        g[i + 3] = pal[getgpixel(*seg, 3)];
-    }
-    return g;
-}
-
-
-
-static const uint8_t *
-munpack(const uint8_t * const p,
-        uint8_t *       const b,
-        int             const w) {
-
-    static const uint8_t pal[] = {0x00, 0x01};
-    const uint8_t * seg;
-    unsigned int i;
-
-    for (i = 0, seg = p; i < w; i += 8, ++seg) {
-        b[i + 0] = pal[getmpixel(*seg, 0)];
-        b[i + 1] = pal[getmpixel(*seg, 1)];
-        b[i + 2] = pal[getmpixel(*seg, 2)];
-        b[i + 3] = pal[getmpixel(*seg, 3)];
-        b[i + 4] = pal[getmpixel(*seg, 4)];
-        b[i + 5] = pal[getmpixel(*seg, 5)];
-        b[i + 6] = pal[getmpixel(*seg, 6)];
-        b[i + 7] = pal[getmpixel(*seg, 7)];
-    }
-    return b;
-}
-
-
-
-const uint8_t *
-ipdb_g16row(IPDB *       const pdbP,
-            unsigned int const row,
-            uint8_t *    const buffer) {
-
-    return g16unpack(img_row(pdbP->i, row), buffer, ipdb_width(pdbP));
-}
-
-
-
-const uint8_t *
-ipdb_grow(IPDB *       const pdbP,
-          unsigned int const row,
-          uint8_t *       const buffer) {
-
-    return gunpack(img_row(pdbP->i, row), buffer, ipdb_width(pdbP));
-}
-
-
-
-const uint8_t *
-ipdb_mrow(IPDB *       const pdbP,
-          unsigned int const row,
-          uint8_t *    const buffer) {
-
-    return munpack(img_row(pdbP->i, row), buffer, ipdb_width(pdbP));
-}
-
-
-
-int
-ipdb_remove_image(IPDB * const pdbP) {
-    
-    int retval;
-    /*
-     * There's no point in fiddling with pdbP->t->r->offset here since we
-     * never know what it really should be until write-time anyway.
-     */
-
-    if (pdbP->i == NULL)
-        retval = 0;
-    else {
-        image_free(pdbP->i);
-        --pdbP->p->num_recs;
-        retval = 0;
-    }
-    return retval;
-}
-
-
-
-int
-ipdb_remove_text(IPDB * const pdbP) {
-
-    int retval;
-
-    if (pdbP->t == NULL)
-        retval = 0;
-    else {
-        text_free(pdbP->t);
-        if (pdbP->i)
-            pdbP->i->r->offset -= 8;
-        --pdbP->p->num_recs;
-        retval = 0;
-    }
-    return retval;
-}
-
-
-
-static int
-pdbhead_write(PDBHEAD * const pdbheadP,
-              FILE *    const fP) {
-
-    fwrite(pdbheadP->name, 1, 32, fP);
-    pm_writebigshort(fP, pdbheadP->flags);
-    pm_writebigshort(fP, pdbheadP->version);
-    pm_writebiglong(fP, pdbheadP->ctime);
-    pm_writebiglong(fP, pdbheadP->mtime);
-    pm_writebiglong(fP, pdbheadP->btime);
-    pm_writebiglong(fP, pdbheadP->mod_num);
-    pm_writebiglong(fP, pdbheadP->app_info);
-    pm_writebiglong(fP, pdbheadP->sort_info);
-    fwrite(pdbheadP->type, 1, 4,  fP);
-    fwrite(pdbheadP->id,   1, 4,  fP);
-    pm_writebiglong(fP, pdbheadP->uniq_seed);
-    pm_writebiglong(fP, pdbheadP->next_rec);
-    pm_writebigshort(fP, pdbheadP->num_recs);
-
-    return 0;
-}
-
-
-
-static int
-rechdr_write(RECHDR * const rechdrP,
-             FILE *   const fP) {
-
-    if (rechdrP) {
-        pm_writebiglong(fP, rechdrP->offset);
-        fwrite(rechdrP->unknown,   1, 3, fP);
-        fwrite(&rechdrP->rec_type, 1, 1, fP);
-
-        if (rechdrP->n_extra != 0)
-            fwrite(rechdrP->extra, 1, rechdrP->n_extra, fP);
-    }
-    return 0;
-}
-
-
-
-static int
-image_write(IMAGE *   const imgP,
-            uint8_t * const data,
-            size_t    const n,
-            FILE *    const fP) {
-
-    fwrite(imgP->name,      1, 32, fP);
-    fwrite(&imgP->version,   1,  1, fP);
-    fwrite(&imgP->type,      1,  1, fP);
-    fwrite(imgP->reserved1, 1,  4, fP);
-    fwrite(imgP->note,      1,  4, fP);
-    pm_writebigshort(fP, imgP->x_last);
-    pm_writebigshort(fP, imgP->y_last);
-    fwrite(imgP->reserved2, 1, 2, fP);
-    pm_writebigshort(fP, imgP->x_anchor);
-    pm_writebigshort(fP, imgP->y_anchor);
-    pm_writebigshort(fP, imgP->width);
-    pm_writebigshort(fP, imgP->height);
-    fwrite(data, 1,  n, fP);
-
-    return 0;
-}
-
-
-
-static int
-text_write(TEXT * const textP,
-           FILE * const fP) {
-
-    if (textP)
-        fwrite(textP->data, 1, strlen(textP->data), fP);
-
-    return 0;
-}
-
-
-
-typedef struct {
-    unsigned int match;
-    uint8_t      buf[128];
-    int          mode;
-    size_t       len;
-    size_t       used;
-    uint8_t *    p;
-} RLE;
-#define MODE_MATCH  0
-#define MODE_LIT    1
-#define MODE_NONE   2
-
-#define reset(r) {                              \
-        (r)->match = 0xffff;                    \
-        (r)->mode  = MODE_NONE;                 \
-        (r)->len   = 0;                         \
-    }
-
-
-
-static void
-put_match(RLE *  const rleP,
-          size_t const n) {
-
-    *rleP->p++ = 0x80 + n - 1;
-    *rleP->p++ = rleP->match;
-    rleP->used += 2;
-    reset(rleP);
-}
-
-
-
-static void
-put_lit(RLE *  const rleP,
-        size_t const n) {
-
-    *rleP->p++ = n - 1;
-    rleP->p = (uint8_t *)memcpy(rleP->p, rleP->buf, n) + n;
-    rleP->used += n + 1;
-    reset(rleP);
-}
-
-
-
-static size_t
-compress(const uint8_t * const inData,
-         size_t          const n_in,
-         uint8_t *       const out) {
-
-    static void (*put[])(RLE *, size_t) = {put_match, put_lit};
-    RLE rle;
-    size_t  i;
-    const uint8_t * p;
-
-    MEMSZERO(&rle);
-    rle.p = out;
-    reset(&rle);
-
-    for (i = 0, p = &inData[0]; i < n_in; ++i, ++p) {
-        if (*p == rle.match) {
-            if (rle.mode == MODE_LIT && rle.len > 1) {
-                put_lit(&rle, rle.len - 1);
-                ++rle.len;
-                rle.match = *p;
-            }
-            rle.mode = MODE_MATCH;
-            ++rle.len;
-        } else {
-            if (rle.mode == MODE_MATCH)
-                put_match(&rle, rle.len);
-            rle.mode         = MODE_LIT;
-            rle.match        = *p;
-            rle.buf[rle.len++] = *p;
-        }
-        if (rle.len == 128)
-            put[rle.mode](&rle, rle.len);
-    }
-    if (rle.len != 0)
-        put[rle.mode](&rle, rle.len);
-
-    return rle.used;
-}
-
-
-
-int
-ipdb_write(IPDB * const pdbP,
-           int    const comp,
-           FILE * const fP) {
-
-    int retval;
-
-    if (pdbP->i == NULL)
-        retval = E_IMAGENOTTHERE;
-    else {
-        RECHDR * const trP = pdbP->t == NULL ? NULL : pdbP->t->r;
-        RECHDR * const irP= pdbP->i->r;
-
-        int n;
-        uint8_t * data;
-
-        n    = img_size(pdbP->i);  /* initial value */
-        data = pdbP->i->data;  /* initial value */
-
-        if (comp == IPDB_NOCOMPRESS)
-            retval = 0;
-        else {
-            /* Allocate for the worst case. */
-            int const allocSz = (3*n + 2)/2;
-            
-            MALLOCARRAY(data, allocSz);
-            
-            if (data == NULL)
-                retval = ENOMEM;
-            else {
-                int sz;
-                sz = compress(pdbP->i->data, n, data);
-                if (comp == IPDB_COMPMAYBE && sz >= n) {
-                    free(data);
-                    data = pdbP->i->data;
-                } else {
-                    pdbP->i->compressed = TRUE;
-                    if (pdbP->i->type == IMG_GRAY16)
-                        pdbP->i->version = 9;
-                    else
-                        pdbP->i->version = 1;
-                    if (pdbP->t != NULL)
-                        pdbP->t->r->offset -= n - sz;
-                    n = sz;
-                }
-                retval = 0;
-            }
-
-            if (retval == 0) {
-                retval = pdbhead_write(pdbP->p, fP);
-                if (retval == 0) {
-                    retval = rechdr_write(irP, fP);
-                    if (retval == 0) {
-                        retval = rechdr_write(trP, fP);
-                        if (retval == 0) {
-                            retval = image_write(pdbP->i, data, n, fP);
-                            if (retval == 0) {
-                                retval = text_write(pdbP->t, fP);
-                            }
-                        }
-                    }
-                }
-                if (data != pdbP->i->data)
-                    free(data);
-            }
-        }
-    }
-    return retval;
-}
-
-
-
-static int
-g16pack(const uint8_t * const inData,
-        uint8_t *       const p,
-        int             const w) {
-
-    int off, i;
-    uint8_t * seg;
-    const uint8_t * g;
-
-    for(i = off = 0, seg = p, g=inData; i < w; ++i, ++g) {
-        switch(*g) {
-        case 0xff:  setg16pixel(*seg, 0x00, off);   break;
-        case 0xee:  setg16pixel(*seg, 0x01, off);   break;
-        case 0xdd:  setg16pixel(*seg, 0x02, off);   break;
-        case 0xcc:  setg16pixel(*seg, 0x03, off);   break;
-        case 0xbb:  setg16pixel(*seg, 0x04, off);   break;
-        case 0xaa:  setg16pixel(*seg, 0x05, off);   break;
-        case 0x99:  setg16pixel(*seg, 0x06, off);   break;
-        case 0x88:  setg16pixel(*seg, 0x07, off);   break;
-        case 0x77:  setg16pixel(*seg, 0x08, off);   break;
-        case 0x66:  setg16pixel(*seg, 0x09, off);   break;
-        case 0x55:  setg16pixel(*seg, 0x0a, off);   break;
-        case 0x44:  setg16pixel(*seg, 0x0b, off);   break;
-        case 0x33:  setg16pixel(*seg, 0x0c, off);   break;
-        case 0x22:  setg16pixel(*seg, 0x0d, off);   break;
-        case 0x11:  setg16pixel(*seg, 0x0e, off);   break;
-        case 0x00:  setg16pixel(*seg, 0x0f, off);   break;
-        default:    return E_BADCOLORS;
-        }
-        if (++off == 2) {
-            ++seg;
-            off = 0;
-        }
-    }
-    return w/2;
-}
-
-
-
-static int
-gpack(const uint8_t * const inData,
-      uint8_t *       const p,
-      int             const w) {
-
-    int off, i;
-    uint8_t * seg;
-    const uint8_t * g;
-
-    for (i = off = 0, seg = p, g = inData; i < w; ++i, ++g) {
-        switch(*g) {
-        case 0xff:  setgpixel(*seg, 0x00, off); break;
-        case 0xaa:  setgpixel(*seg, 0x01, off); break;
-        case 0x55:  setgpixel(*seg, 0x02, off); break;
-        case 0x00:  setgpixel(*seg, 0x03, off); break;
-        default: return E_BADCOLORS;
-        }
-        if (++off == 4) {
-            ++seg;
-            off = 0;
-        }
-    }
-    return w/4;
-}
-
-
-
-static int
-mpack(const uint8_t * const inData,
-      uint8_t *       const p,
-      int             const w) {
-
-    int off, i;
-    uint8_t * seg;
-    const uint8_t * b;
-
-    for (i = off = 0, seg = p, b = inData; i < w; ++i, ++b) {
-        setmpixel(*seg, *b == 0, off);
-        if (++off == 8) {
-            ++seg;
-            off = 0;
-        }
-    }
-    return w/8;
-}
-
-
-
-static int
-adjust_dims(unsigned int   const w,
-            unsigned int   const h,
-            unsigned int * const awP,
-            unsigned int * const ahP) {
-
-    unsigned int provW, provH;
-
-    provW = w;
-    provH = h;
-    if (provW % 16 != 0)
-        provW += 16 - (provW % 16);
-    if (provW < 160)
-        provW = 160;
-    if (provH < 160)
-        provH = 160;
-
-    *awP = provW;
-    *ahP = provH;
-
-    return w == provW && h == provH;
-}
-
-
-
-/*
- * You can allocate only 64k chunks of memory on the pilot and that
- * supplies an image size limit.
- */
-#define MAX_SIZE(t) ((1 << 16)*((t) == IMG_GRAY ? 4 : 8))
-#define MAX_ERROR(t)    ((t) == IMG_GRAY ? E_TOOBIGG : E_TOOBIGM)
-
-static int
-image_insert_init(IPDB * const pdbP,
-                  int    const uw,
-                  int    const uh,
-                  int    const type) {
-
-    char * const name = pdbP->p->name;
-    unsigned int w, h;
-    int retval;
-
-    if (pdbP->p->num_recs != 0)
-        retval = E_IMAGETHERE;
-    else {
-        adjust_dims(uw, uh, &w, &h);
-        if (w*h > MAX_SIZE(type))
-            retval = MAX_ERROR(type);
-        else {
-            pdbP->i = image_alloc(name, type, w, h);
-            if(pdbP->i == NULL)
-                retval = ENOMEM;
-            else {
-                pdbP->p->num_recs = 1;
-
-                retval =0;
-            }
-        }
-    }
-    return retval;
-}
-
-
-
-int
-ipdb_insert_g16image(IPDB *          const pdbP,
-                     int             const w,
-                     int             const h,
-                     const uint8_t * const gArg) {
-
-    int retval;
-    int i;
-
-    i = image_insert_init(pdbP, w, h, IMG_GRAY16);
-    if (i != 0)
-        retval = i;
-    else {
-        int const incr = ipdb_width(pdbP)/2;
-        unsigned int i;
-        uint8_t * p;
-        const uint8_t * g;
-
-        for (i = 0, p = pdbP->i->data, g = gArg, retval = 0;
-             i < h && retval == 0;
-             ++i, p += incr, g += w) {
-
-            int const len = g16pack(g, p, w);
-            if (len  < 0)
-                retval = len;
-        }
-    } 
-    return retval;
-}
-
-
-
-int
-ipdb_insert_gimage(IPDB *          const pdbP,
-                   int             const w,
-                   int             const h,
-                   const uint8_t * const gArg) {
-
-    int i;
-    int retval;
-
-    i = image_insert_init(pdbP, w, h, IMG_GRAY);
-
-    if (i != 0)
-        retval = i;
-    else {
-        int const incr = ipdb_width(pdbP)/4;
-        unsigned int i;
-        uint8_t * p;
-        const uint8_t * g;
-
-        for (i = 0, p = pdbP->i->data, g = gArg, retval = 0;
-             i < h && retval == 0;
-             ++i, p += incr, g += w) {
-
-            int const len = gpack(g, p, w);
-            if (len < 0)
-                retval = len;
-        }
-    }
-    return retval;
-}
-
-
-
-int
-ipdb_insert_mimage(IPDB *          const pdbP,
-                   int             const w,
-                   int             const h,
-                   const uint8_t * const bArg) {
-
-    int retval;
-    int i;
-
-    i = image_insert_init(pdbP, w, h, IMG_MONO);
-    if (i != 0)
-        retval = i;
-    else {
-        int const incr = ipdb_width(pdbP)/8;
-        unsigned int i;
-        uint8_t  * p;
-        const uint8_t * b;
-
-        for (i = 0, p = pdbP->i->data, b = bArg, retval = 0;
-             i < h && retval == 0;
-             ++i, p += incr, b += w) {
-
-            int const len = mpack(b, p, w);
-            if (len < 0)
-                retval = len;
-        }
-    } 
-    return retval;
-}
-
-
-
-int
-ipdb_insert_text(IPDB *       const pdbP,
-                 const char * const s) {
-
-    int retval;
-
-    if (pdbP->i == NULL)
-        retval = E_IMAGENOTTHERE;
-    else if (pdbP->p->num_recs == 2)
-        retval = E_TEXTTHERE;
-    else {
-        pdbP->t = text_alloc(s);
-        if (pdbP->t == NULL)
-            retval = ENOMEM;
-        else {
-            pdbP->p->num_recs = 2;
-
-            pdbP->i->r->offset += 8;
-            pdbP->t->r->offset =
-                pdbP->i->r->offset + IMAGESIZE + img_size(pdbP->i);
+const char *
+ipdb_typeName(uint8_t const type) {
 
-            retval = 0;
-        }
+    switch (type) {
+    case IMG_GRAY16: return "16 Bit Grayscale"; break;
+    case IMG_GRAY: return "Grayscale"; break;
+    case IMG_MONO: return "Monochrome"; break;
+    default: return "???";
     }
-    return retval;
 }
diff --git a/converter/other/ipdb.h b/converter/other/ipdb.h
index d00fde1a..6af5fc44 100644
--- a/converter/other/ipdb.h
+++ b/converter/other/ipdb.h
@@ -127,6 +127,7 @@ typedef struct {
      */
     uint8_t  * data;
 } IMAGE;
+
 #define IMAGESIZE   (32 + 1 + 1 + 4 + 4 + 2*2 + 4 + 2*2 + 2*2)
 
 /*
@@ -136,6 +137,10 @@ typedef struct {
 #define IMG_GRAY    ((uint8_t)0)
 #define IMG_MONO    ((uint8_t)0xff)
 
+const char *
+ipdb_typeName(uint8_t const type);
+
+
 /*
  * Compression constants for IMAGE.version.
  */
@@ -160,6 +165,16 @@ typedef struct {
 } IPDB;
 
 /*
+ * Only use four bytes of these.
+ */
+#define IPDB_vIMG   "vIMG"
+#define IPDB_View   "View"
+/*
+ * Only use three bytes of this.
+ */
+#define IPDB_MYST   "\x40\x6f\x80"
+
+/*
  * Flags for ipdb_write().
  */
 #define IPDB_COMPMAYBE  0       /* compress if it does any good */
@@ -185,43 +200,44 @@ typedef struct {
 const char *
 ipdb_err(int error);
 
-int
-ipdb_read(IPDB *, FILE *);
-
-int
-ipdb_write(IPDB *, int, FILE *);
+size_t
+ipdb_img_size(IMAGE * const imgP);
 
-int
-ipdb_insert_g16image(IPDB *, int, int, const uint8_t *);
+unsigned int
+ipdb_img_ppb(IMAGE * const imgP);
 
-int
-ipdb_insert_gimage(IPDB *, int, int, const uint8_t*);
+uint8_t *
+ipdb_img_row(IMAGE *      const imgP,
+             unsigned int const row);
 
-int
-ipdb_insert_mimage(IPDB *, int, int, const uint8_t *);
+void
+ipdb_free(IPDB *);
 
-int
-ipdb_insert_text(IPDB *, const char *);
+IPDB *
+ipdb_alloc(const char *);
 
-int
-ipdb_remove_image(IPDB *);
+void
+ipdb_clear(IPDB * const pdbP);
 
-int
-ipdb_remove_text(IPDB *);
+PDBHEAD *
+ipdb_pdbhead_alloc(const char * const name);
 
-const uint8_t *
-ipdb_g16row(IPDB *, unsigned int, uint8_t *);
+void
+ipdb_pdbhead_free(PDBHEAD * const headP);
 
-const uint8_t *
-ipdb_grow(IPDB *, unsigned int, uint8_t *);
+IMAGE *
+ipdb_image_alloc(const char * const name,
+                 int          const type,
+                 int          const w,
+                 int          const h);
 
-const uint8_t *
-ipdb_mrow(IPDB *, unsigned int, uint8_t *);
+void
+ipdb_image_free(IMAGE * const imgP);
 
 void
-ipdb_free(IPDB *);
+ipdb_text_free(TEXT * const textP);
 
-IPDB *
-ipdb_alloc(const char *);
+TEXT *
+ipdb_text_alloc(const char * const content);
 
 #endif
diff --git a/converter/other/pamtopdbimg.c b/converter/other/pamtopdbimg.c
index dcd03450..53868aa8 100644
--- a/converter/other/pamtopdbimg.c
+++ b/converter/other/pamtopdbimg.c
@@ -30,6 +30,7 @@
  */
 
 #include <stdlib.h>
+#include <assert.h>
 #include <string.h>
 #include <sys/stat.h>
 
@@ -131,51 +132,595 @@ parseCommandLine(int argc, const char ** argv,
 
 
 
+/*
+ * Pixel setting macros.
+ */
+#define setg16pixel(b,v,o)  ((b) |= ((v) << (4 - 4*(o))))
+#define setgpixel(b,v,o)    ((b) |= ((v) << (6 - 2*(o))))
+#define setmpixelblack(b,o)    ((b) |= (1 << (7 - (o))))
+
+
+
+static int
+pdbheadWrite(PDBHEAD * const pdbheadP,
+             FILE *    const fileP) {
+
+    fwrite(pdbheadP->name, 1, 32, fileP);
+    pm_writebigshort(fileP, pdbheadP->flags);
+    pm_writebigshort(fileP, pdbheadP->version);
+    pm_writebiglong(fileP, pdbheadP->ctime);
+    pm_writebiglong(fileP, pdbheadP->mtime);
+    pm_writebiglong(fileP, pdbheadP->btime);
+    pm_writebiglong(fileP, pdbheadP->mod_num);
+    pm_writebiglong(fileP, pdbheadP->app_info);
+    pm_writebiglong(fileP, pdbheadP->sort_info);
+    fwrite(pdbheadP->type, 1, 4,  fileP);
+    fwrite(pdbheadP->id,   1, 4,  fileP);
+    pm_writebiglong(fileP, pdbheadP->uniq_seed);
+    pm_writebiglong(fileP, pdbheadP->next_rec);
+    pm_writebigshort(fileP, pdbheadP->num_recs);
+
+    return 0;
+}
+
+
+
+static int
+rechdrWrite(RECHDR * const rechdrP,
+            FILE *   const fileP) {
+
+    if (rechdrP) {
+        pm_writebiglong(fileP, rechdrP->offset);
+        fwrite(rechdrP->unknown,   1, 3, fileP);
+        fwrite(&rechdrP->rec_type, 1, 1, fileP);
+
+        if (rechdrP->n_extra != 0)
+            fwrite(rechdrP->extra, 1, rechdrP->n_extra, fileP);
+    }
+    return 0;
+}
+
+
+
 static void
-readimg(IPDB * const pdbP,
-        FILE * const ifP,
-        bool   const depth4) {
+imageWriteHeader(IMAGE * const imgP,
+                 FILE *  const fileP) {
+
+    fwrite(imgP->name,       1, 32, fileP);
+    fwrite(&imgP->version,   1,  1, fileP);
+    fwrite(&imgP->type,      1,  1, fileP);
+    fwrite(imgP->reserved1,  1,  4, fileP);
+    fwrite(imgP->note,       1,  4, fileP);
+    pm_writebigshort(fileP, imgP->x_last);
+    pm_writebigshort(fileP, imgP->y_last);
+    fwrite(imgP->reserved2,  1,  4, fileP);
+    pm_writebigshort(fileP, imgP->x_anchor);
+    pm_writebigshort(fileP, imgP->y_anchor);
+    pm_writebigshort(fileP, imgP->width);
+    pm_writebigshort(fileP, imgP->height);
+}
 
-    struct pam inpam;
-    tuple * tuplerow;
-    int status;
-    uint8_t * imgRaster;
-    unsigned int row;
 
-    pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
 
-    if (strneq(inpam.tuple_type, "RGB", 3))
-        pm_error("Input image is color.  Cannot make a Palm color image.");
+static void
+imageWriteData(IMAGE *         const imgP,
+               const uint8_t * const data,
+               size_t          const dataSize,
+               FILE *          const fileP) {
 
-    MALLOCARRAY(imgRaster, inpam.width * inpam.height);
+    fwrite(data, 1,  dataSize, fileP);
+}
 
-    tuplerow = pnm_allocpamrow(&inpam);
 
-    for (row = 0; row < inpam.height; ++row) {
-        unsigned int col;
 
-        pnm_readpamrow(&inpam, tuplerow);
+static void
+imageWrite(IMAGE *   const imgP,
+           uint8_t * const data,
+           size_t    const dataSize,
+           FILE *    const fileP) {
 
-        for (col = 0; col < inpam.width; ++col)
-            imgRaster[row * inpam.width + col] = tuplerow[col][0];
+    imageWriteHeader(imgP, fileP);
+
+    imageWriteData(imgP, data, dataSize, fileP);
+}
+
+
+
+static int
+textWrite(TEXT * const textP,
+          FILE * const fileP) {
+    
+    if (textP)
+        fwrite(textP->data, 1, strlen(textP->data), fileP);
+
+    return 0;
+}
+
+
+
+typedef struct {
+    unsigned int match;
+    uint8_t      buf[128];
+    int          mode;
+    size_t       len;
+    size_t       used;
+    uint8_t *    p;
+} RLE;
+#define MODE_MATCH  0
+#define MODE_LIT    1
+#define MODE_NONE   2
+
+#define reset(r) {                              \
+        (r)->match = 0xffff;                    \
+        (r)->mode  = MODE_NONE;                 \
+        (r)->len   = 0;                         \
     }
 
+
+
+static void
+putMatch(RLE *  const rleP,
+          size_t const n) {
+
+    *rleP->p++ = 0x80 + n - 1;
+    *rleP->p++ = rleP->match;
+    rleP->used += 2;
+    reset(rleP);
+}
+
+
+
+static void
+putLit(RLE *  const rleP,
+       size_t const n) {
+
+    *rleP->p++ = n - 1;
+    rleP->p = (uint8_t *)memcpy(rleP->p, rleP->buf, n) + n;
+    rleP->used += n + 1;
+    reset(rleP);
+}
+
+
+
+static size_t
+compress(const uint8_t * const inData,
+         size_t          const n_in,
+         uint8_t *       const out) {
+
+    static void (*put[])(RLE *, size_t) = {putMatch, putLit};
+    RLE rle;
+    size_t  i;
+    const uint8_t * p;
+
+    MEMSZERO(&rle);
+    rle.p = out;
+    reset(&rle);
+
+    for (i = 0, p = &inData[0]; i < n_in; ++i, ++p) {
+        if (*p == rle.match) {
+            if (rle.mode == MODE_LIT && rle.len > 1) {
+                putLit(&rle, rle.len - 1);
+                ++rle.len;
+                rle.match = *p;
+            }
+            rle.mode = MODE_MATCH;
+            ++rle.len;
+        } else {
+            if (rle.mode == MODE_MATCH)
+                putMatch(&rle, rle.len);
+            rle.mode         = MODE_LIT;
+            rle.match        = *p;
+            rle.buf[rle.len++] = *p;
+        }
+        if (rle.len == 128)
+            put[rle.mode](&rle, rle.len);
+    }
+    if (rle.len != 0)
+        put[rle.mode](&rle, rle.len);
+
+    return rle.used;
+}
+
+
+
+static void
+compressIfRequired(IPDB *     const pdbP,
+                   int        const comp,
+                   uint8_t ** const compressedDataP,
+                   size_t *   const compressedSizeP) {
+
+    if (comp == IPDB_NOCOMPRESS) {
+        *compressedDataP = pdbP->i->data;
+        *compressedSizeP = ipdb_img_size(pdbP->i);
+    } else {
+        int const uncompressedSz = ipdb_img_size(pdbP->i);
+        
+        /* Allocate for the worst case. */
+        size_t const allocSz = (3 * uncompressedSz + 2)/2;
+
+        uint8_t * data;
+            
+        data = pdbP->i->data;
+
+        MALLOCARRAY(data, allocSz);
+            
+        if (data == NULL)
+            pm_error("Could not get %lu bytes of memory to decompress",
+                     (unsigned long)allocSz);
+        else {
+            size_t compressedSz;
+            compressedSz = compress(pdbP->i->data, uncompressedSz, data);
+            if (comp == IPDB_COMPMAYBE && compressedSz >= uncompressedSz) {
+                /* Return the uncompressed data */
+                free(data);
+                *compressedDataP = pdbP->i->data;
+                *compressedSizeP = uncompressedSz;
+            } else {
+                pdbP->i->compressed = TRUE;
+                if (pdbP->i->type == IMG_GRAY16)
+                    pdbP->i->version = 9;
+                else
+                    pdbP->i->version = 1;
+                if (pdbP->t != NULL)
+                    pdbP->t->r->offset -= uncompressedSz - compressedSz;
+                *compressedDataP = data;
+                *compressedSizeP = compressedSz;
+            }
+        }
+    }
+}
+
+
+
+static void
+ipdbWrite(IPDB * const pdbP,
+          int    const comp,
+          FILE * const fileP) {
+
+    RECHDR * const trP = pdbP->t == NULL ? NULL : pdbP->t->r;
+    RECHDR * const irP = pdbP->i->r;
+
+    int rc;
+    uint8_t * compressedData;
+        /* This is the image raster, compressed as required.
+           (I.e. if it doesn't have to be compressed, it isn't).
+        */
+    size_t compressedSize;
+
+    assert(pdbP->i);
+
+    compressIfRequired(pdbP, comp, &compressedData, &compressedSize);
+
+    rc = pdbheadWrite(pdbP->p, fileP);
+    if (rc != 0)
+        pm_error("Failed to write PDB header.  %s", ipdb_err(rc));
+            
+    rc = rechdrWrite(irP, fileP);
+    if (rc != 0)
+        pm_error("Failed to write image record header.  %s", ipdb_err(rc));
+
+    rc = rechdrWrite(trP, fileP);
+    if (rc != 0)
+        pm_error("Failed to write text record header.  %s", ipdb_err(rc));
+
+    imageWrite(pdbP->i, compressedData, compressedSize, fileP);
+
+    rc = textWrite(pdbP->t, fileP);
+    if (rc != 0)
+        pm_error("Failed to write text.  %s", ipdb_err(rc));
+
+    /* Oh, gross.  compressIfRequired() might have returned a pointer to
+       storage that was already allocated, or it might have returned a
+       pointer to newly malloc'ed storage.  In the latter case, we have
+       to free the storage.
+    */
+    if (compressedData != pdbP->i->data)
+        free(compressedData);
+}
+
+
+
+static void
+g16pack(tuple *         const tupleRow,
+        struct pam *    const pamP,
+        uint8_t *       const outData,
+        unsigned int    const paddedWidth) {
+/*----------------------------------------------------------------------------
+   Pack a row of 16-level graysacle pixels 'tupleRow', described by *pamP into
+   'outData', padding it to 'paddedWidth' with white.
+
+   We pack 2 input pixels into one output byte.
+-----------------------------------------------------------------------------*/
+    unsigned int col;
+    unsigned int off;
+    uint8_t * seg;
+
+    for (col = 0, off = 0, seg = &outData[0]; col < paddedWidth; ++col) {
+        if (col < pamP->width)
+            setg16pixel(*seg, 16 - tupleRow[col][0] * 16 / pamP->maxval, off);
+        else
+            /* Pad on the right with white */
+            setgpixel(*seg, 0, off);
+
+        if (++off == 2) {
+            ++seg;
+            off = 0;
+        }
+    }
+}
+
+
+
+static void
+gpack(tuple *         const tupleRow,
+      struct pam *    const pamP,
+      uint8_t *       const outData,
+      unsigned int    const paddedWidth) {
+/*----------------------------------------------------------------------------
+   Pack a row of 4-level graysacle pixels 'tupleRow', described by *pamP into
+   'outData', padding it to 'paddedWidth' with white.
+
+   We pack 4 input pixels into one output byte.
+-----------------------------------------------------------------------------*/
+    unsigned int col;
+    unsigned int off;
+    uint8_t * seg;
+
+    for (col = 0, off = 0, seg = &outData[0]; col < paddedWidth; ++col) {
+        if (col < pamP->width)
+            setgpixel(*seg, 4 - tupleRow[col][0] * 4 / pamP->maxval, off);
+        else
+            /* Pad on the right with white */
+            setgpixel(*seg, 0, off);
+
+        if (++off == 4) {
+            ++seg;
+            off = 0;
+        }
+    }
+}
+
+
+
+static void
+mpack(tuple *         const tupleRow,
+      struct pam *    const pamP,
+      uint8_t *       const outData,
+      unsigned int    const paddedWidth) {
+/*----------------------------------------------------------------------------
+   Pack a row of monochrome pixels 'tupleRow', described by *pamP into
+   'outData', padding it to 'paddedWidth' with white.
+
+   We pack 8 input pixels into one output byte.
+-----------------------------------------------------------------------------*/
+    unsigned int col;
+    unsigned int off;
+    uint8_t * seg;
+
+    assert(paddedWidth % 8 == 0);
+
+    /* Initialize row to white, then set necessary pixels black */
+    memset(outData, 0, paddedWidth/8);
+
+    for (col = 0, off = 0, seg = &outData[0]; col < paddedWidth; ++col) {
+        if (col < pamP->width && tupleRow[col][0] == PAM_BLACK)
+            setmpixelblack(*seg, off);
+        if (++off == 8) {
+            ++seg;
+            off = 0;
+        }
+    }
+}
+
+
+
+static int
+adjustDimensions(unsigned int   const w,
+                 unsigned int   const h,
+                 unsigned int * const awP,
+                 unsigned int * const ahP) {
+
+    unsigned int provW, provH;
+
+    provW = w;
+    provH = h;
+    if (provW % 16 != 0)
+        provW += 16 - (provW % 16);
+    if (provW < 160)
+        provW = 160;
+    if (provH < 160)
+        provH = 160;
+
+    *awP = provW;
+    *ahP = provH;
+
+    return w == provW && h == provH;
+}
+
+
+
+/*
+ * You can allocate only 64k chunks of memory on the pilot and that
+ * supplies an image size limit.
+ */
+#define MAX_SIZE(t) ((1 << 16)*((t) == IMG_GRAY ? 4 : 8))
+
+static void
+imageInsertInit(IPDB * const pdbP,
+                int    const uw,
+                int    const uh,
+                int    const type) {
+
+    char * const name = pdbP->p->name;
+    unsigned int adjustedWidth, adjustedHeight;
+
+    if (pdbP->p->num_recs != 0)
+        pm_error("Image record already present, logic error.");
+    else {
+        adjustDimensions(uw, uh, &adjustedWidth, &adjustedHeight);
+        pm_message("Output dimensions: %uw x %uh",
+                   adjustedWidth, adjustedHeight);
+        if (adjustedWidth * adjustedHeight > MAX_SIZE(type))
+            pm_error("Image too large.   Maximum number of pixels allowed "
+                     "for a %s image is %u",
+                     ipdb_typeName(type), MAX_SIZE(type));
+        else {
+            pdbP->i =
+                ipdb_image_alloc(name, type, adjustedWidth, adjustedHeight);
+            if (pdbP->i == NULL)
+                pm_message("Could not get memory for %u x %u image",
+                           adjustedWidth, adjustedHeight);
+            else
+                pdbP->p->num_recs = 1;
+        }
+    }
+}
+
+
+
+static void
+insertG16image(IPDB *          const pdbP,
+               struct pam *    const pamP,
+               tuple **        const tuples) {
+/*----------------------------------------------------------------------------
+   Insert into the PDB an image in 16-level grayscale format.
+
+   The pixels of the image to insert are 'tuples', described by *pamP.
+   Note that the image inserted may be padded up to larger dimensions.
+-----------------------------------------------------------------------------*/
+    imageInsertInit(pdbP, pamP->width, pamP->height, IMG_GRAY16);
+    {
+        int const rowSize = ipdb_width(pdbP)/2;
+            /* The size in bytes of a packed, padded row */
+
+        uint8_t * outP;
+        unsigned int row;
+
+        for (row = 0, outP = &pdbP->i->data[0];
+             row < pamP->height;
+             ++row, outP += rowSize)
+            g16pack(tuples[row], pamP, outP, ipdb_width(pdbP));
+
+        /* Pad with white on the bottom */
+        for (; row < ipdb_height(pdbP); ++row)
+            memset(outP, 0, rowSize);
+    } 
+}
+
+
+
+static void
+insertGimage(IPDB *          const pdbP,
+             struct pam *    const pamP,
+             tuple **        const tuples) {
+/*----------------------------------------------------------------------------
+   Insert into the PDB an image in 4-level grayscale format.
+
+   The pixels of the image to insert are 'tuples', described by *pamP.
+   Note that the image inserted may be padded up to larger dimensions.
+-----------------------------------------------------------------------------*/
+    imageInsertInit(pdbP, pamP->width, pamP->height, IMG_GRAY);
+    {
+        int const rowSize = ipdb_width(pdbP)/4;
+            /* The size in bytes of a packed, padded row */
+
+        uint8_t * outP;
+        unsigned int row;
+
+        for (row = 0, outP = &pdbP->i->data[0];
+             row < pamP->height;
+             ++row, outP += rowSize)
+            gpack(tuples[row], pamP, outP, ipdb_width(pdbP));
+
+        /* Pad with white on the bottom */
+        for (; row < ipdb_height(pdbP); ++row)
+            memset(outP, 0, rowSize);
+    } 
+}
+
+
+
+static void
+insertMimage(IPDB *          const pdbP,
+             struct pam *    const pamP,
+             tuple **        const tuples) {
+/*----------------------------------------------------------------------------
+   Insert into the PDB an image in monochrome format.
+
+   The pixels of the image to insert are 'tuples', described by *pamP.
+   Note that the image inserted may be padded up to larger dimensions.
+-----------------------------------------------------------------------------*/
+    imageInsertInit(pdbP, pamP->width, pamP->height, IMG_MONO);
+    {
+        int const rowSize = ipdb_width(pdbP)/8;
+            /* The size in bytes of a packed, padded row */
+
+        uint8_t * outP;
+        unsigned int row;
+
+        for (row = 0, outP = &pdbP->i->data[0];
+             row < pamP->height;
+             ++row, outP += rowSize)
+            mpack(tuples[row], pamP, outP, ipdb_width(pdbP));
+
+        /* Pad with white on the bottom */
+        for (; row < ipdb_height(pdbP); ++row)
+            memset(outP, 0, rowSize);
+    } 
+}
+
+
+
+static int
+insertText(IPDB *       const pdbP,
+           const char * const s) {
+
+    int retval;
+
+    if (pdbP->i == NULL)
+        retval = E_IMAGENOTTHERE;
+    else if (pdbP->p->num_recs == 2)
+        retval = E_TEXTTHERE;
+    else {
+        pdbP->t = ipdb_text_alloc(s);
+        if (pdbP->t == NULL)
+            retval = ENOMEM;
+        else {
+            pdbP->p->num_recs = 2;
+
+            pdbP->i->r->offset += 8;
+            pdbP->t->r->offset =
+                pdbP->i->r->offset + IMAGESIZE + ipdb_img_size(pdbP->i);
+
+            retval = 0;
+        }
+    }
+    return retval;
+}
+
+
+
+static void
+readimg(IPDB * const pdbP,
+        FILE * const ifP,
+        bool   const depth4) {
+
+     struct pam inpam;
+    tuple ** tuples;
+
+    tuples = pnm_readpam(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
+
+    if (strneq(inpam.tuple_type, "RGB", 3))
+        pm_error("Input image is color.  Cannot make a Palm color image.");
+
     if (inpam.maxval == 1)
-        status = ipdb_insert_mimage(pdbP, inpam.width, inpam.height,
-                                    imgRaster);
+        insertMimage(pdbP, &inpam, tuples);
     else if (depth4)
-        status = ipdb_insert_g16image(pdbP, inpam.width, inpam.height,
-                                      imgRaster);
+        insertG16image(pdbP, &inpam, tuples);
     else
-        status = ipdb_insert_gimage(pdbP, inpam.width, inpam.height,
-                                    imgRaster);
+        insertGimage(pdbP, &inpam, tuples);
 
-    if (status != 0)
-        pm_error("ipdb_insert failed.  Error %d (%s)",
-                 status, ipdb_err(status));
-
-    pnm_freepamrow(tuplerow);
-    free(imgRaster);
+    pnm_freepamarray(tuples, &inpam);
 }
 
 
@@ -217,7 +762,7 @@ readtxt(IPDB *       const pdbP,
     for (n = strlen(fileContent) - 1; n >= 0 && fileContent[n] == '\n'; --n)
         fileContent[n] = '\0';
 
-    ipdb_insert_text(pdbP, fileContent);
+    insertText(pdbP, fileContent);
 }
 
 
@@ -229,7 +774,6 @@ main(int argc, const char **argv) {
     IPDB * pdbP;
     FILE * ifP;
     int comp;
-    int status;
 
     pm_proginit(&argc, argv);
 
@@ -253,10 +797,7 @@ main(int argc, const char **argv) {
     if (cmdline.notefile)
         readtxt(pdbP, cmdline.notefile);
 
-    status = ipdb_write(pdbP, comp, stdout);
-
-    if (status != 0)
-        pm_error("Failed to write PDB.  %s.", ipdb_err(status));
+    ipdbWrite(pdbP, comp, stdout);
 
     if (comp == IPDB_COMPMAYBE && !ipdb_compressed(pdbP))
         pm_message("Image too complex to be compressed.");
diff --git a/converter/other/pdbimgtopam.c b/converter/other/pdbimgtopam.c
new file mode 100644
index 00000000..6e33be0d
--- /dev/null
+++ b/converter/other/pdbimgtopam.c
@@ -0,0 +1,774 @@
+/*=============================================================================
+                               pamtopdbimg
+===============================================================================
+
+  Convert Palm Pilot PDB Image format (for viewing by
+  Pilot Image Viewer) to Netpbm image.
+
+  Bryan Henderson derived this from Eric Howe's program named
+  'imgvtopnm', in September 2010.
+=============================================================================*/
+/*
+ * Copyright (C) 1997 Eric A. Howe
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ *   Authors:  Eric A. Howe (mu@trends.net)
+ *             Bryan Henderson
+ */
+#include <stdlib.h>
+#include <assert.h>
+
+#include "pm_c_util.h"
+#include "mallocvar.h"
+#include "nstring.h"
+#include "shhopt.h"
+#include "pam.h"
+
+#include "ipdb.h"
+
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char * inputFileName;  /* '-' if stdin */
+    const char * notefile;  /* NULL if not specified */
+    unsigned int verbose;
+};
+
+
+
+static void
+parseCommandLine(int argc, const char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   parse program command line described in Unix standard form by argc
+   and argv.  Return the information in the options as *cmdlineP.  
+
+   If command line is internally inconsistent (invalid options, etc.),
+   issue error message to stderr and abort program.
+
+   Note that the strings we return are stored in the storage that
+   was passed to us as the argv array.  We also trash *argv.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def;
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    unsigned int notefileSpec;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "notefile",            OPT_STRING,    &cmdlineP->notefile,
+            &notefileSpec,            0);
+    OPTENT3(0, "verbose",             OPT_FLAG,    NULL,
+            &cmdlineP->verbose,       0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = false;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = false;  /* We have no parms that are negative numbers */
+
+    optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (!notefileSpec)
+        cmdlineP->notefile = NULL;
+    
+    if (argc-1 < 1)
+        cmdlineP->inputFileName = "-";
+    else if (argc-1 == 1)
+        cmdlineP->inputFileName = argv[1];
+    else
+        pm_error("Program takes at most one argument:  input file name");
+}
+
+
+
+#define getg16pixel(b,o)    (((b) >> (4 - 4*(o))) & 0x0f)
+#define getgpixel(b,o)      (((b) >> (6 - 2*(o))) & 0x03)
+#define getmpixel(b,o)      (((b) >> (7 - (o))) & 0x01)
+
+
+static void
+abortShort() {
+    pm_error("Invalid image.  Compression algorithm runs out of "
+             "compressed data before generating the expected "
+             "amount of image data");
+}
+
+
+
+static void
+abortOverrun() {
+    pm_error("Invalid image.  Compression algorithm finds the end of "
+             "the image in the middle of a run");
+}
+
+
+
+static void
+decompress(const uint8_t * const compressed,
+           size_t          const compressedSize,
+           size_t          const imageSize,
+           uint8_t **      const uncompressedP) {
+/*----------------------------------------------------------------------------
+   Decompress the data 'compressed', which is 'compressedSize' bytes long.
+   Return the decompressed data in newly malloced storage as
+   *decompressedP.  Decompression should yield exactly 'imageSize' bytes.
+-----------------------------------------------------------------------------*/
+    /*
+     * The compression scheme used is a simple RLE; the control codes,
+     * CODE, are one byte and have the following meanings:
+     *
+     *  CODE >  0x80    Insert (CODE + 1 - 0x80) copies of the next byte.
+     *  CODE <= 0x80    Insert the next (CODE + 1) literal bytes.
+     *
+     * Compressed pieces can (and do) cross row boundaries.
+     */
+    uint8_t * uncompressed;
+
+    MALLOCARRAY(uncompressed, imageSize);
+
+    if (uncompressed) {
+        const uint8_t * inP;
+        uint8_t *       outP;
+        size_t          bytesLeft;
+        
+        for (bytesLeft = imageSize,
+                 inP  = &compressed[0], outP = &uncompressed[0];
+             bytesLeft > 0;
+            ) {
+
+            int got, put;
+
+            if (inP > compressed + compressedSize)
+                abortShort();
+
+            if (*inP > 0x80) {
+                put = *inP++ + 1 - 0x80;
+                if (outP + put > uncompressed + imageSize)
+                    abortOverrun();
+                memset(outP, *inP, put);
+                got = 1;
+            } else {
+                put = *inP++ + 1;
+                if (inP + put > compressed + compressedSize)
+                    abortShort();
+                if (outP + put > uncompressed + imageSize)
+                    abortOverrun();
+                memcpy(outP, inP, put);
+                got = put;
+            }
+            inP       += got;
+            outP      += put;
+            assert(bytesLeft >= put);
+            bytesLeft -= put;
+        }
+    }
+    *uncompressedP = uncompressed;
+}
+
+
+
+#define UNKNOWN_OFFSET  (uint32_t)-1
+
+static void
+readCompressed(IMAGE *    const imgP,
+               uint32_t   const end_offset,
+               FILE *     const fP,
+               size_t *   const dataSizeP,
+               uint8_t ** const dataP,
+               int *      const retvalP) {
+/*----------------------------------------------------------------------------
+   Read the compressed data from file *fP (actually, if the image isn't
+   compressed, then it's just the regular data).
+
+   Return the data in newly malloced storage as *dataP, which is
+   *dataSizeP bytes long.
+-----------------------------------------------------------------------------*/
+    int retval;
+    uint8_t * buffer;
+    size_t dataSize;
+
+    dataSize = 0;  /* initial value */
+
+    if (end_offset == UNKNOWN_OFFSET) {
+        /*
+         * Read until EOF. Some of them have an extra zero byte
+         * dangling off the end.  I originally thought this was
+         * an empty note record (even though there was no record
+         * header for it); however, the release notes for Image
+         * Compression Manager 1.1 on http://www.pilotgear.com
+         * note this extra byte as a bug in Image Compression
+         * Manager 1.0 which 1.1 fixes.  We'll just blindly read
+         * this extra byte and ignore it by paying attention to
+         * the image dimensions.
+         */
+        MALLOCARRAY(buffer, ipdb_img_size(imgP));
+
+        if (buffer == NULL)
+            retval = ENOMEM;
+        else {
+            dataSize = fread(buffer, 1, ipdb_img_size(imgP), fP);
+            if (dataSize <= 0)
+                retval = EIO;
+            else
+                retval = 0;
+
+            if (retval != 0)
+                free(buffer);
+        }
+    } else {
+        /*
+         * Read to the indicated offset.
+         */
+        dataSize = end_offset - ftell(fP) + 1;
+        
+        MALLOCARRAY(buffer, dataSize);
+
+        if (buffer == NULL)
+            retval = ENOMEM;
+        else {
+            ssize_t rc;
+            rc = fread(buffer, 1, dataSize, fP);
+            if (rc != dataSize)
+                retval = EIO;
+            else
+                retval = 0;
+
+            if (retval != 0)
+                free(buffer);
+        }
+    }
+    *dataSizeP = dataSize;
+    *dataP = buffer;
+    *retvalP = retval;
+}
+
+
+
+static void
+imageReadHeader(FILE *  const fileP,
+                IMAGE * const imgP,
+                bool    const dump) {
+
+    fread(&imgP->name, 1, 32, fileP);
+    pm_readcharu(fileP, &imgP->version);
+    pm_readcharu(fileP, &imgP->type);
+    fread(&imgP->reserved1, 1, 4, fileP);
+    fread(&imgP->note, 1, 4, fileP);
+    pm_readbigshortu(fileP, &imgP->x_last);
+    pm_readbigshortu(fileP, &imgP->y_last);
+    fread(&imgP->reserved2, 1, 4, fileP);
+    pm_readbigshortu(fileP, &imgP->x_anchor);
+    pm_readbigshortu(fileP, &imgP->y_anchor);
+    pm_readbigshortu(fileP, &imgP->width);
+    pm_readbigshortu(fileP, &imgP->height);
+
+    if (dump) {
+        pm_message("PDB IMAGE header:");
+        pm_message("  Name: '%.*s'", (int)sizeof(imgP->name), imgP->name);
+        pm_message("  Version: %02x", imgP->version);
+        pm_message("  Type: %s", ipdb_typeName(imgP->type));
+        pm_message("  Note: %02x %02x %02x %02x",
+                   imgP->note[0], imgP->note[1], imgP->note[2], imgP->note[3]);
+        pm_message("  X_last: %u", imgP->x_last);
+        pm_message("  Y_last: %u", imgP->y_last);
+        pm_message("  X_anchor: %u", imgP->x_anchor);
+        pm_message("  Y_anchor: %u", imgP->y_anchor);
+        pm_message("  Width: %u", imgP->width);
+        pm_message("  Height: %u", imgP->height);
+        pm_message("Pixels per byte: %u", ipdb_img_ppb(imgP));
+        pm_message("Image size: %lu bytes",
+                   (unsigned long)ipdb_img_size(imgP));
+    }
+}
+
+
+static int
+imageReadData(FILE *   const fileP,
+              IMAGE *  const imgP,
+              uint32_t const end_offset) {
+
+    int retval;
+    size_t dataSize;
+    uint8_t * buffer;
+
+    readCompressed(imgP, end_offset, fileP, &dataSize, &buffer, &retval);
+
+    if (retval == 0) {
+        /*
+         * Compressed data can cross row boundaries so we decompress
+         * the data here to avoid messiness in the row access functions.
+         */
+        if (dataSize != ipdb_img_size(imgP)) {
+            decompress(buffer, dataSize, ipdb_img_size(imgP), &imgP->data);
+            if (imgP->data == NULL)
+                retval = ENOMEM;
+            else
+                imgP->compressed = true;
+            free(buffer);
+        } else {
+            imgP->compressed = false;
+            imgP->data       = buffer;
+            /* Storage at 'buffer' now belongs to *imgP */
+        }
+    }
+    return retval;
+}
+
+
+
+static int
+imageRead(IMAGE *  const imgP,
+          uint32_t const end_offset,
+          FILE *   const fileP,
+          bool     const verbose) {
+
+    if (imgP) {
+        imgP->r->offset = (uint32_t)ftell(fileP);
+
+        imageReadHeader(fileP, imgP, verbose);
+
+        imageReadData(fileP, imgP, end_offset);
+    }
+    return 0;
+}
+
+
+
+static int
+textRead(TEXT * const textP,
+         FILE * const fileP) {
+
+    int retval;
+    char    * s;
+    char    buf[128];
+    int used, alloced, len;
+
+    if (textP == NULL)
+        return 0;
+
+    textP->r->offset = (uint32_t)ftell(fileP);
+    
+    /*
+     * What a pain in the ass!  Why the hell isn't there a length
+     * attached to the text record?  I suppose the designer wasn't
+     * concerned about non-seekable (i.e. pipes) input streams.
+     * Perhaps I'm being a little harsh, the lack of a length probably
+     * isn't much of an issue on the Pilot.
+     */
+    used    = 0;
+    alloced = 0;
+    s       = NULL;
+    retval = 0;  /* initial value */
+    while ((len = fread(buf, 1, sizeof(buf), fileP)) != 0 && retval == 0) {
+        if (buf[len - 1] == '\0')
+            --len;
+        if (used + len > alloced) {
+            alloced += 2 * sizeof(buf);
+            REALLOCARRAY(s, alloced);
+
+            if (s == NULL)
+                retval = ENOMEM;
+        }
+        if (retval == 0) {
+            memcpy(s + used, buf, len);
+            used += len;
+        }
+    }
+    if (retval == 0) {
+        textP->data = calloc(1, used + 1);
+        if (textP->data == NULL)
+            retval = ENOMEM;
+        else
+            memcpy(textP->data, s, used);
+    }
+    if (s)
+        free(s);
+
+    return retval;
+}
+
+
+
+static int
+pdbheadRead(PDBHEAD * const pdbHeadP,
+            FILE *    const fileP) {
+
+    int retval;
+
+    fread(pdbHeadP->name, 1, 32, fileP);
+    pm_readbigshortu(fileP, &pdbHeadP->flags);
+    pm_readbigshortu(fileP, &pdbHeadP->version);
+    pm_readbiglongu2(fileP, &pdbHeadP->ctime);
+    pm_readbiglongu2(fileP, &pdbHeadP->mtime);
+    pm_readbiglongu2(fileP, &pdbHeadP->btime);
+    pm_readbiglongu2(fileP, &pdbHeadP->mod_num);
+    pm_readbiglongu2(fileP, &pdbHeadP->app_info);
+    pm_readbiglongu2(fileP, &pdbHeadP->sort_info);
+    fread(pdbHeadP->type, 1, 4,  fileP);
+    fread(pdbHeadP->id,   1, 4,  fileP);
+    pm_readbiglongu2(fileP, &pdbHeadP->uniq_seed);
+    pm_readbiglongu2(fileP, &pdbHeadP->next_rec);
+    pm_readbigshortu(fileP, &pdbHeadP->num_recs);
+
+    if (!memeq(pdbHeadP->type, IPDB_vIMG, 4) 
+        || !memeq(pdbHeadP->id, IPDB_View, 4))
+        retval = E_NOTIMAGE;
+    else
+        retval = 0;
+
+    return retval;
+}
+
+
+
+static int
+rechdrRead(RECHDR * const rechdrP,
+           FILE *   const fileP) {
+
+    int retval;
+    off_t   len;
+
+    pm_readbiglongu2(fileP, &rechdrP->offset);
+
+    len = (off_t)rechdrP->offset - ftell(fileP);
+    switch(len) {
+    case 4:
+    case 12:
+        /*
+         * Version zero (eight bytes of record header) or version
+         * two with a note (two chunks of eight record header bytes).
+         */
+        fread(&rechdrP->unknown[0], 1, 3, fileP);
+        fread(&rechdrP->rec_type,   1, 1, fileP);
+        rechdrP->n_extra = 0;
+        rechdrP->extra   = NULL;
+        retval = 0;
+        break;
+    case 6:
+        /*
+         * Version one (ten bytes of record header).
+         */
+        fread(&rechdrP->unknown[0], 1, 3, fileP);
+        fread(&rechdrP->rec_type,   1, 1, fileP);
+        rechdrP->n_extra = 2;
+        MALLOCARRAY(rechdrP->extra, rechdrP->n_extra);
+        if (rechdrP->extra == NULL)
+            retval = ENOMEM;
+        else {
+            fread(rechdrP->extra, 1, rechdrP->n_extra, fileP);
+            retval = 0;
+        }
+        break;
+    default:
+        /*
+         * hmmm.... I'll assume this is the record header
+         * for a text record.
+         */
+        fread(&rechdrP->unknown[0], 1, 3, fileP);
+        fread(&rechdrP->rec_type,   1, 1, fileP);
+        rechdrP->n_extra = 0;
+        rechdrP->extra   = NULL;
+        retval = 0;
+        break;
+    }
+    if (retval == 0) {
+        if ((rechdrP->rec_type != IMG_REC && rechdrP->rec_type != TEXT_REC)
+            || !memeq(rechdrP->unknown, IPDB_MYST, 3))
+            retval = E_NOTRECHDR;
+    }
+    return retval;
+}
+
+
+
+static int
+ipdbRead(IPDB * const pdbP,
+         FILE * const fileP,
+         bool   const verbose) {
+
+    int retval;
+
+    ipdb_clear(pdbP);
+
+    pdbP->p = ipdb_pdbhead_alloc(NULL);
+
+    if (pdbP->p == NULL)
+        retval = ENOMEM;
+    else {
+        int status;
+
+        status = pdbheadRead(pdbP->p, fileP);
+
+        if (status != 0)
+            retval = status;
+        else {
+            pdbP->i = ipdb_image_alloc(pdbP->p->name, IMG_GRAY, 0, 0);
+            if (pdbP->i == NULL)
+                retval = ENOMEM;
+            else {
+                int status;
+                status = rechdrRead(pdbP->i->r, fileP);
+                if (status != 0)
+                    retval = status;
+                else {
+                    if (pdbP->p->num_recs > 1) {
+                        pdbP->t = ipdb_text_alloc(NULL);
+                        if (pdbP->t == NULL)
+                            retval = ENOMEM;
+                        else {
+                            int status;
+                            status = rechdrRead(pdbP->t->r, fileP);
+                            if (status != 0)
+                                retval = status;
+                            else
+                                retval = 0;
+                        }
+                    } else
+                        retval = 0;
+                    
+                    if (retval == 0) {
+                        uint32_t const offset =
+                            pdbP->t == NULL ?
+                            UNKNOWN_OFFSET : pdbP->t->r->offset - 1;
+
+                        int status;
+
+                        status = imageRead(pdbP->i, offset, fileP, verbose);
+                        if (status != 0)
+                            retval = status;
+                        else {
+                            if (pdbP->t != NULL) {
+                                int status;
+                                
+                                status = textRead(pdbP->t, fileP);
+                                if (status != 0)
+                                    retval = status;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+    return retval;
+}
+
+
+
+static void
+g16unpack(const uint8_t * const p,
+          uint8_t *       const g,
+          int             const w) {
+
+    static const uint8_t pal[] =
+        {0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88,
+         0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x00};
+    const uint8_t * seg;
+    unsigned int i;
+
+    for (i = 0, seg = p; i < w; i += 2, ++seg) {
+        g[i + 0] = pal[getg16pixel(*seg, 0)];
+        g[i + 1] = pal[getg16pixel(*seg, 1)];
+    }
+}
+
+
+
+static void
+gunpack(const uint8_t * const p,
+        uint8_t *       const g,
+        int             const w) {
+
+    static const uint8_t pal[] = {0xff, 0xaa, 0x55, 0x00};
+    const uint8_t * seg;
+    unsigned int i;
+
+    for (i = 0, seg = p; i < w; i += 4, ++seg) {
+        g[i + 0] = pal[getgpixel(*seg, 0)];
+        g[i + 1] = pal[getgpixel(*seg, 1)];
+        g[i + 2] = pal[getgpixel(*seg, 2)];
+        g[i + 3] = pal[getgpixel(*seg, 3)];
+    }
+}
+
+
+
+static void
+munpack(const uint8_t * const p,
+        uint8_t *       const b,
+        int             const w) {
+
+    static const uint8_t pal[] = {0x00, 0x01};
+    const uint8_t * seg;
+    unsigned int i;
+
+    for (i = 0, seg = p; i < w; i += 8, ++seg) {
+        b[i + 0] = pal[getmpixel(*seg, 0)];
+        b[i + 1] = pal[getmpixel(*seg, 1)];
+        b[i + 2] = pal[getmpixel(*seg, 2)];
+        b[i + 3] = pal[getmpixel(*seg, 3)];
+        b[i + 4] = pal[getmpixel(*seg, 4)];
+        b[i + 5] = pal[getmpixel(*seg, 5)];
+        b[i + 6] = pal[getmpixel(*seg, 6)];
+        b[i + 7] = pal[getmpixel(*seg, 7)];
+    }
+}
+
+
+
+static void
+ipdb_g16row(IPDB *       const pdbP,
+            unsigned int const row,
+            uint8_t *    const buffer) {
+
+    g16unpack(ipdb_img_row(pdbP->i, row), buffer, ipdb_width(pdbP));
+}
+
+
+
+static void
+ipdb_grow(IPDB *       const pdbP,
+          unsigned int const row,
+          uint8_t *    const buffer) {
+
+    gunpack(ipdb_img_row(pdbP->i, row), buffer, ipdb_width(pdbP));
+}
+
+
+
+static void
+ipdb_mrow(IPDB *       const pdbP,
+          unsigned int const row,
+          uint8_t *    const buffer) {
+
+    munpack(ipdb_img_row(pdbP->i, row), buffer, ipdb_width(pdbP));
+}
+
+
+
+static void
+writePdbImg(IPDB * const pdbP,
+            FILE * const ofP) {
+
+    struct pam pam;
+    tuple * tupleRow;
+    unsigned int row;
+    uint8_t * imgRow;
+
+    MALLOCARRAY(imgRow, ipdb_width(pdbP));
+
+    pam.size             = sizeof(pam);
+    pam.len              = PAM_STRUCT_SIZE(tuple_type);
+    pam.file             = ofP;
+    pam.plainformat      = 0;
+    pam.width            = ipdb_width(pdbP);
+    pam.height           = ipdb_height(pdbP);
+    pam.depth            = 1;
+    pam.maxval           = ipdb_type(pdbP) == IMG_MONO ? 1 : 255;
+    pam.bytes_per_sample = pnm_bytespersample(pam.maxval);
+    pam.format           = PAM_FORMAT;
+    strcpy(pam.tuple_type,
+           ipdb_type(pdbP) == IMG_MONO ?
+           PAM_PBM_TUPLETYPE : PAM_PGM_TUPLETYPE);
+
+    pnm_writepaminit(&pam);
+    
+    tupleRow = pnm_allocpamrow(&pam);
+
+    for (row = 0; row < pam.height; ++row) {
+        unsigned int col;
+
+
+        if (ipdb_type(pdbP) == IMG_MONO)
+            ipdb_mrow(pdbP, row, imgRow);
+        else if (ipdb_type(pdbP) == IMG_GRAY)
+            ipdb_grow(pdbP, row, imgRow);
+        else
+            ipdb_g16row(pdbP, row, imgRow);
+
+        for (col = 0; col < pam.width; ++col)
+            tupleRow[col][0] = imgRow[col];
+        
+        pnm_writepamrow(&pam, tupleRow);
+    }
+    pnm_freepamrow(tupleRow);
+
+    free(imgRow);
+}
+
+
+
+static void
+writetxt(IPDB *       const pdbP,
+         const char * const name) {
+
+    const char * const note = ipdb_text(pdbP);
+
+    FILE * fP;
+
+    if (name == NULL || note == NULL) {
+    } else {
+        fP = pm_openw(name);
+        if (fP == NULL)
+            pm_error("Could not open note file '%s' for output", name);
+        
+        fprintf(fP, "%s\n", note);
+
+        pm_close(fP);
+    }
+}
+
+
+
+int
+main(int argc, const char ** argv) {
+
+    struct cmdlineInfo cmdline;
+    FILE * ifP;
+    IPDB * pdbP;
+    int status;
+
+    pm_proginit(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFileName);
+
+    pdbP = ipdb_alloc(NULL);
+    if (pdbP == NULL)
+        pm_error("Could not allocate IPDB structure.");
+
+    status = ipdbRead(pdbP, ifP, cmdline.verbose);
+    if (status != 0)
+        pm_error("Image header read error: %s.", ipdb_err(status));
+
+    writePdbImg(pdbP, stdout);
+
+    writetxt(pdbP, cmdline.notefile);
+
+    ipdb_free(pdbP);
+
+    pm_close(ifP);
+
+    return EXIT_SUCCESS;
+}
diff --git a/doc/HISTORY b/doc/HISTORY
index 72d41a12..05980ce5 100644
--- a/doc/HISTORY
+++ b/doc/HISTORY
@@ -6,7 +6,7 @@ CHANGE HISTORY
 
 not yet  BJH  Release 10.52.00
 
-              Add pamtopdbimg.
+              Add pamtopdbimg, pdbimgtopam.
 
               Add pamrecolor.  Thanks Scott Pakin.