about summary refs log tree commit diff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/libpam.c33
-rw-r--r--lib/libpamwrite.c205
-rw-r--r--lib/libpbm2.c4
-rw-r--r--lib/libpbmfont1.c63
-rw-r--r--lib/libpm.c3
-rw-r--r--lib/libpnm2.c61
-rw-r--r--lib/pam.h26
-rw-r--r--lib/pbm.h13
-rw-r--r--lib/pm.h3
-rw-r--r--lib/pmfileio.c112
-rw-r--r--lib/util/rand.c43
-rw-r--r--lib/util/rand.h5
12 files changed, 415 insertions, 156 deletions
diff --git a/lib/libpam.c b/lib/libpam.c
index 72502749..b24f230e 100644
--- a/lib/libpam.c
+++ b/lib/libpam.c
@@ -90,7 +90,12 @@ validateComputableSize(struct pam * const pamP) {
    the size of a tuple row, in bytes, can be represented by an 'int'.
 
    Another common operation is adding 1 or 2 to the highest row, column,
-   or plane number in the image, so we make sure that's possible.
+   or plane number in the image, so we make sure that's possible.  And in
+   bitmap images, rounding up to multiple of 8 is common, so we provide for
+   that too.
+
+   Note that it's still the programmer's responsibility to ensure that his
+   code, using values known to have been validated here, cannot overflow.
 -----------------------------------------------------------------------------*/
     if (pamP->width == 0)
         pm_error("Width is zero.  Image must be at least one pixel wide");
@@ -111,10 +116,10 @@ validateComputableSize(struct pam * const pamP) {
 
         if (depth > INT_MAX - 2)
             pm_error("image depth (%u) too large to be processed", depth);
-        if (pamP->width > INT_MAX - 2)
+        if (pamP->width > INT_MAX - 10)
             pm_error("image width (%u) too large to be processed",
                      pamP->width);
-        if (pamP->height > INT_MAX - 2)
+        if (pamP->height > INT_MAX - 10)
             pm_error("image height (%u) too large to be processed",
                      pamP->height);
     }
@@ -416,6 +421,20 @@ pnm_setpamrow(const struct pam * const pamP,
 
 
 
+static void
+setSeekableAndRasterPos(struct pam * const pamP) {
+
+    if (pamP->size >= PAM_STRUCT_SIZE(is_seekable))
+        pamP->is_seekable = pm_is_seekable(pamP->file);
+
+    if (pamP->size >= PAM_STRUCT_SIZE(raster_pos)) {
+        if (pamP->is_seekable)
+            pm_tell2(pamP->file, &pamP->raster_pos, sizeof(pamP->raster_pos));
+    }
+}
+
+
+
 #define MAX_LABEL_LENGTH 8
 #define MAX_VALUE_LENGTH 255
 
@@ -945,6 +964,8 @@ pnm_readpaminit(FILE *       const file,
     pamP->plainformat = FALSE;
         /* See below for complex explanation of why this is FALSE. */
 
+    setSeekableAndRasterPos(pamP);
+
     interpretTupleType(pamP);
 
     validateComputableSize(pamP);
@@ -1053,8 +1074,6 @@ pnm_writepaminit(struct pam * const pamP) {
 
     interpretTupleType(pamP);
 
-    pamP->len = MIN(pamP->size, PAM_STRUCT_SIZE(opacity_plane));
-
     switch (PAM_FORMAT_TYPE(pamP->format)) {
     case PAM_TYPE:
         /* See explanation below of why we ignore 'pm_plain_output' here. */
@@ -1113,6 +1132,10 @@ pnm_writepaminit(struct pam * const pamP) {
         pm_error("Invalid format passed to pnm_writepaminit(): %d",
                  pamP->format);
     }
+
+    setSeekableAndRasterPos(pamP);
+
+    pamP->len = MIN(pamP->size, PAM_STRUCT_SIZE(raster_pos));
 }
 
 
diff --git a/lib/libpamwrite.c b/lib/libpamwrite.c
index 7edc90dc..0e1ff469 100644
--- a/lib/libpamwrite.c
+++ b/lib/libpamwrite.c
@@ -71,7 +71,7 @@ writePamPlainPbmRow(const struct pam *  const pamP,
 
 static void
 writePamPlainRow(const struct pam *  const pamP,
-                    const tuple *       const tuplerow) {
+                 const tuple *       const tuplerow) {
 
     int const samplesPerLine =
         samplesPerPlainLine(pamP->maxval, pamP->depth, 79);
@@ -101,17 +101,25 @@ writePamPlainRow(const struct pam *  const pamP,
 
 
 static void
-formatPbmRow(const struct pam * const pamP,
-             const tuple *      const tuplerow,
-             unsigned char *    const outbuf,
-             unsigned int *     const rowSizeP) {
+formatPbm(const struct pam * const pamP,
+          const tuple *      const tuplerow,
+          unsigned char *    const outbuf,
+          unsigned int       const nTuple,
+          unsigned int *     const rowSizeP) {
+/*----------------------------------------------------------------------------
+   Create the image of 'nTuple' consecutive tuples of a row in the raster of a
+   raw format PBM image.
 
+   Put the image at *outbuf; put the number of bytes of it at *rowSizeP.
+-----------------------------------------------------------------------------*/
     unsigned char accum;
     int col;
 
+    assert(nTuple <= pamP->width);
+
     accum = 0;  /* initial value */
 
-    for (col=0; col < pamP->width; ++col) {
+    for (col=0; col < nTuple; ++col) {
         accum |=
             (tuplerow[col][0] == PAM_PBM_BLACK ? PBM_BLACK : PBM_WHITE)
                 << (7-col%8);
@@ -120,12 +128,12 @@ formatPbmRow(const struct pam * const pamP,
                 accum = 0;
         }
     }
-    if (pamP->width % 8 != 0) {
-        unsigned int const lastByteIndex = pamP->width/8;
+    if (nTuple % 8 != 0) {
+        unsigned int const lastByteIndex = nTuple/8;
         outbuf[lastByteIndex] = accum;
         *rowSizeP = lastByteIndex + 1;
     } else
-        *rowSizeP = pamP->width/8;
+        *rowSizeP = nTuple/8;
 }
 
 
@@ -171,36 +179,40 @@ sampleToBytes4(unsigned char       buf[4],
 
 
 static __inline__ void
-format1BpsRow(const struct pam * const pamP,
-              const tuple *      const tuplerow,
-              unsigned char *    const outbuf,
-              unsigned int *     const rowSizeP) {
+format1Bps(const struct pam * const pamP,
+           const tuple *      const tuplerow,
+           unsigned char *    const outbuf,
+           unsigned int       const nTuple,
+           unsigned int *     const rowSizeP) {
 /*----------------------------------------------------------------------------
-   Create the image of a row in the raster of a raw format Netpbm
-   image that has one byte per sample (ergo not PBM).
+   Create the image of 'nTuple' consecutive tuples of a row in the raster of a
+   raw format Netpbm image that has one byte per sample (ergo not PBM).
 
    Put the image at *outbuf; put the number of bytes of it at *rowSizeP.
 -----------------------------------------------------------------------------*/
     int col;
     unsigned int bufferCursor;
 
+    assert(nTuple <= pamP->width);
+
     bufferCursor = 0;  /* initial value */
 
-    for (col = 0; col < pamP->width; ++col) {
+    for (col = 0; col < nTuple; ++col) {
         unsigned int plane;
         for (plane=0; plane < pamP->depth; ++plane)
             outbuf[bufferCursor++] = (unsigned char)tuplerow[col][plane];
     }
-    *rowSizeP = pamP->width * 1 * pamP->depth;
+    *rowSizeP = nTuple * 1 * pamP->depth;
 }
 
 
 
 static __inline__ void
-format2BpsRow(const struct pam * const pamP,
-              const tuple *      const tuplerow,
-              unsigned char *    const outbuf,
-              unsigned int *     const rowSizeP) {
+format2Bps(const struct pam * const pamP,
+           const tuple *      const tuplerow,
+           unsigned char *    const outbuf,
+           unsigned int       const nTuple,
+           unsigned int *     const rowSizeP) {
 /*----------------------------------------------------------------------------
   Analogous to format1BpsRow().
 -----------------------------------------------------------------------------*/
@@ -209,24 +221,27 @@ format2BpsRow(const struct pam * const pamP,
     int col;
     unsigned int bufferCursor;
 
+    assert(nTuple <= pamP->width);
+
     bufferCursor = 0;  /* initial value */
 
-    for (col=0; col < pamP->width; ++col) {
+    for (col=0; col < nTuple; ++col) {
         unsigned int plane;
         for (plane = 0; plane < pamP->depth; ++plane)
             sampleToBytes2(ob[bufferCursor++], tuplerow[col][plane]);
     }
 
-    *rowSizeP = pamP->width * 2 * pamP->depth;
+    *rowSizeP = nTuple * 2 * pamP->depth;
 }
 
 
 
 static __inline__ void
-format3BpsRow(const struct pam * const pamP,
-              const tuple *      const tuplerow,
-              unsigned char *    const outbuf,
-              unsigned int *     const rowSizeP) {
+format3Bps(const struct pam * const pamP,
+           const tuple *      const tuplerow,
+           unsigned char *    const outbuf,
+           unsigned int       const nTuple,
+           unsigned int *     const rowSizeP) {
 /*----------------------------------------------------------------------------
   Analogous to format1BpsRow().
 -----------------------------------------------------------------------------*/
@@ -235,24 +250,27 @@ format3BpsRow(const struct pam * const pamP,
     int col;
     unsigned int bufferCursor;
 
+    assert(nTuple <= pamP->width);
+
     bufferCursor = 0;  /* initial value */
 
-    for (col=0; col < pamP->width; ++col) {
+    for (col=0; col < nTuple; ++col) {
         unsigned int plane;
         for (plane = 0; plane < pamP->depth; ++plane)
             sampleToBytes3(ob[bufferCursor++], tuplerow[col][plane]);
     }
 
-    *rowSizeP = pamP->width * 3 * pamP->depth;
+    *rowSizeP = nTuple * 3 * pamP->depth;
 }
 
 
 
 static __inline__ void
-format4BpsRow(const struct pam * const pamP,
-              const tuple *      const tuplerow,
-              unsigned char *    const outbuf,
-              unsigned int *     const rowSizeP) {
+format4Bps(const struct pam * const pamP,
+           const tuple *      const tuplerow,
+           unsigned char *    const outbuf,
+           unsigned int       const nTuple,
+           unsigned int *     const rowSizeP) {
 /*----------------------------------------------------------------------------
   Analogous to format1BpsRow().
 -----------------------------------------------------------------------------*/
@@ -261,41 +279,49 @@ format4BpsRow(const struct pam * const pamP,
     int col;
     unsigned int bufferCursor;
 
+    assert(nTuple <= pamP->width);
+
     bufferCursor = 0;  /* initial value */
 
-    for (col=0; col < pamP->width; ++col) {
+    for (col=0; col < nTuple; ++col) {
         unsigned int plane;
         for (plane = 0; plane < pamP->depth; ++plane)
             sampleToBytes4(ob[bufferCursor++], tuplerow[col][plane]);
     }
 
-    *rowSizeP = pamP->width * 4 * pamP->depth;
+    *rowSizeP = nTuple * 4 * pamP->depth;
 }
 
 
 
 void
-pnm_formatpamrow(const struct pam * const pamP,
-                 const tuple *      const tuplerow,
-                 unsigned char *    const outbuf,
-                 unsigned int *     const rowSizeP) {
-/*----------------------------------------------------------------------------
-   Create the image of a row in the raster of a raw (not plain) format
-   Netpbm image, as described by *pamP and tuplerow[].  Put the image
-   at *outbuf.
+pnm_formatpamtuples(const struct pam * const pamP,
+                    const tuple *      const tuplerow,
+                    unsigned char *    const outbuf,
+                    unsigned int       const nTuple,
+                    unsigned int *     const rowSizeP) {
+/*----------------------------------------------------------------------------   Create the image of 'nTuple' consecutive tuples of a row in the raster of a
+   raw (not plain) format Netpbm image, as described by *pamP and tuplerow[].
+   Put the image at *outbuf.
 
    'outbuf' must be the address of space allocated with pnm_allocrowimage().
 
-   We return as *rowSizeP the number of bytes in the row image.
+   We return as *rowSizeP the number of bytes in the image.
 -----------------------------------------------------------------------------*/
+    if (nTuple > pamP->width) {
+        pm_error("pnm_formatpamtuples called to write more tuples (%u) "
+                 "than the width of a row (%u)",
+                 nTuple, pamP->width);
+    }
+
     if (PAM_FORMAT_TYPE(pamP->format) == PBM_TYPE)
-        formatPbmRow(pamP, tuplerow, outbuf, rowSizeP);
+        formatPbm(pamP, tuplerow, outbuf, nTuple, rowSizeP);
     else {
         switch(pamP->bytes_per_sample){
-        case 1: format1BpsRow(pamP, tuplerow, outbuf, rowSizeP); break;
-        case 2: format2BpsRow(pamP, tuplerow, outbuf, rowSizeP); break;
-        case 3: format3BpsRow(pamP, tuplerow, outbuf, rowSizeP); break;
-        case 4: format4BpsRow(pamP, tuplerow, outbuf, rowSizeP); break;
+        case 1: format1Bps(pamP, tuplerow, outbuf, nTuple, rowSizeP); break;
+        case 2: format2Bps(pamP, tuplerow, outbuf, nTuple, rowSizeP); break;
+        case 3: format3Bps(pamP, tuplerow, outbuf, nTuple, rowSizeP); break;
+        case 4: format4Bps(pamP, tuplerow, outbuf, nTuple, rowSizeP); break;
         default:
             pm_error("invalid bytes per sample passed to "
                      "pnm_formatpamrow(): %u",  pamP->bytes_per_sample);
@@ -305,6 +331,19 @@ pnm_formatpamrow(const struct pam * const pamP,
 
 
 
+void
+pnm_formatpamrow(const struct pam * const pamP,
+                 const tuple *      const tuplerow,
+                 unsigned char *    const outbuf,
+                 unsigned int *     const rowSizeP) {
+/*----------------------------------------------------------------------------
+  Same as 'pnm_formatpamtuples', except formats an entire row.
+-----------------------------------------------------------------------------*/
+    pnm_formatpamtuples(pamP, tuplerow, outbuf, pamP->width, rowSizeP);
+}
+
+
+
 static void
 writePamRawRow(const struct pam * const pamP,
                const tuple *      const tuplerow,
@@ -398,6 +437,74 @@ pnm_writepamrowmult(const struct pam * const pamP,
 
 
 void
+pnm_writepamrowpart(const struct pam * const pamP,
+                    const tuple *      const tuplerow,
+                    unsigned int       const firstRow,
+                    unsigned int       const firstCol,
+                    unsigned int       const rowCt,
+                    unsigned int       const colCt) {
+/*----------------------------------------------------------------------------
+   Write part of multiple consecutive rows to the file.
+
+   For each of 'rowCt' consecutive rows starting at 'firstRow', write the
+   'colCt' columns starting at 'firstCol'.  The tuples to write are those in
+   'tuplerow', starting at the beginning of 'tuplerow'.
+
+   Fail if the file is not seekable (or not known to be seekable) or the
+   output format is not raw (i.e. is plain) or the output format is PBM.
+-----------------------------------------------------------------------------*/
+    unsigned int const bytesPerTuple = pamP->depth * pamP->bytes_per_sample;
+
+    jmp_buf jmpbuf;
+    jmp_buf * origJmpbufP;
+    unsigned int tupleImageSize;
+    unsigned char * outbuf;  /* malloc'ed */
+
+    if (pamP->len < PAM_STRUCT_SIZE(raster_pos) || !pamP->raster_pos)
+        pm_error("pnm_writepamrowpart called on nonseekable file");
+
+    if (PAM_FORMAT_TYPE(pamP->format) == PBM_TYPE)
+        pm_error("pnm_witepamrowpart called for PBM image");
+
+    if (pm_plain_output || pamP->plainformat)
+        pm_error("pnm_writepamrowpart called for plain format image");
+
+    outbuf = pnm_allocrowimage(pamP);
+
+    pnm_formatpamtuples(pamP, tuplerow, outbuf, colCt, &tupleImageSize);
+
+    if (setjmp(jmpbuf) != 0) {
+        pnm_freerowimage(outbuf);
+        pm_setjmpbuf(origJmpbufP);
+        pm_longjmp();
+    } else {
+        unsigned int row;
+
+        pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
+
+        for (row = firstRow; row < firstRow + rowCt; ++row) {
+            pm_filepos const firstTuplePos =
+                pamP->raster_pos +
+                (row * pamP->width + firstCol) * bytesPerTuple;
+            size_t bytesWritten;
+
+            pm_seek2(pamP->file, &firstTuplePos, sizeof(firstTuplePos));
+
+            bytesWritten = fwrite(outbuf, 1, tupleImageSize, pamP->file);
+
+            if (bytesWritten != tupleImageSize)
+                pm_error("fwrite() failed to write %u image tuples "
+                         "to the file.  errno=%d (%s)",
+                         colCt, errno, strerror(errno));
+        }
+        pm_setjmpbuf(origJmpbufP);
+    }
+    pnm_freerowimage(outbuf);
+}
+
+
+
+void
 pnm_writepam(struct pam * const pamP,
              tuple **     const tuplearray) {
 
diff --git a/lib/libpbm2.c b/lib/libpbm2.c
index a611bec5..2a2e2aac 100644
--- a/lib/libpbm2.c
+++ b/lib/libpbm2.c
@@ -69,8 +69,8 @@ validateComputableSize(unsigned int const cols,
    you expect.  That failed expectation can be disastrous if you use
    it to allocate memory.
 
-   A common operation is adding 1 or 2 to the highest row or
-   column number in the image, so we make sure that's possible.
+   See comments at 'validateComputableSize' in libpam.c for details on
+   the purpose of these validations.
 -----------------------------------------------------------------------------*/
     if (cols > INT_MAX - 10)
         pm_error("image width (%u) too large to be processed", cols);
diff --git a/lib/libpbmfont1.c b/lib/libpbmfont1.c
index fe014150..a76d0e6b 100644
--- a/lib/libpbmfont1.c
+++ b/lib/libpbmfont1.c
@@ -184,7 +184,7 @@ computeCharacterSize(const bit **   const font,
 
 
 
-struct font*
+struct font *
 pbm_dissectfont(const bit ** const fontsheet,
                 unsigned int const frows,
                 unsigned int const fcols) {
@@ -222,56 +222,61 @@ pbm_dissectfont(const bit ** const fontsheet,
     unsigned int charWidth, charHeight;
         /* Maximum dimensions of glyph itself, inside its cell */
 
-    int row, col, ch, r, c, i;
-    struct font * fn;
+    unsigned int row, col;
+    int ch;
+    unsigned int i;
+    struct font * fontP;
 
     computeCharacterSize(fontsheet, fcols, frows,
                          &cellWidth, &cellHeight, &charWidth, &charHeight);
 
     /* Now convert to a general font */
 
-    MALLOCVAR(fn);
-    if (fn == NULL)
+    MALLOCVAR(fontP);
+    if (fontP == NULL)
         pm_error("out of memory allocating font structure");
 
-    fn->maxwidth  = charWidth;
-    fn->maxheight = charHeight;
-    fn->x = fn->y = 0;
+    fontP->maxwidth  = charWidth;
+    fontP->maxheight = charHeight;
+    fontP->x = fontP->y = 0;
 
-    fn->oldfont = fontsheet;
-    fn->frows = frows;
-    fn->fcols = fcols;
+    fontP->oldfont = fontsheet;
+    fontP->frows = frows;
+    fontP->fcols = fcols;
 
     /* Now fill in the 0,0 coords. */
     row = cellHeight * 2;
     col = cellWidth  * 2;
 
     /* Load individual glyphs */
-    for ( ch = 0; ch < nCharsInFont; ++ch ) {
+    for (ch = 0; ch < nCharsInFont; ++ch) {
         /* Allocate memory separately for each glyph.
            pbm_loadbdffont2() does this in exactly the same manner.
          */
-        struct glyph * const glyph =
+        struct glyph * const glyphP =
              (struct glyph *) malloc (sizeof (struct glyph));
-        char * const bmap = (char*) malloc(fn->maxwidth * fn->maxheight);
+        char * const bmap = (char*) malloc(fontP->maxwidth * fontP->maxheight);
 
-        if ( bmap == NULL || glyph == NULL )
-            pm_error( "out of memory allocating glyph data" );
+        unsigned int r;
 
-        glyph->width  = fn->maxwidth;
-        glyph->height = fn->maxheight;
-        glyph->x = glyph->y = 0;
-        glyph->xadd = cellWidth;
+        if (bmap == NULL || glyphP == NULL)
+            pm_error( "out of memory allocating glyph data" );
 
-        for ( r = 0; r < glyph->height; ++r )
-            for ( c = 0; c < glyph->width; ++c )
-                bmap[r * glyph->width + c] = fontsheet[row + r][col + c];
+        glyphP->width  = fontP->maxwidth;
+        glyphP->height = fontP->maxheight;
+        glyphP->x = glyphP->y = 0;
+        glyphP->xadd = cellWidth;
 
-        glyph->bmap = bmap;
-        fn->glyph[firstCodePoint + ch] = glyph;
+        for (r = 0; r < glyphP->height; ++r) {
+            unsigned int c;
+            for (c = 0; c < glyphP->width; ++c)
+                bmap[r * glyphP->width + c] = fontsheet[row + r][col + c];
+        }
+        glyphP->bmap = bmap;
+        fontP->glyph[firstCodePoint + ch] = glyphP;
 
         col += cellWidth;
-        if ( col >= cellWidth * 14 ) {
+        if (col >= cellWidth * 14) {
             col = cellWidth * 2;
             row += cellHeight;
         }
@@ -279,12 +284,12 @@ pbm_dissectfont(const bit ** const fontsheet,
 
     /* Initialize all remaining character positions to "undefined." */
     for (i = 0; i < firstCodePoint; ++i)
-        fn->glyph[i] = NULL;
+        fontP->glyph[i] = NULL;
 
     for (i = firstCodePoint + nCharsInFont; i <= PM_FONT_MAXGLYPH; ++i)
-        fn->glyph[i] = NULL;
+        fontP->glyph[i] = NULL;
 
-    return fn;
+    return fontP;
 }
 
 
diff --git a/lib/libpm.c b/lib/libpm.c
index 6f9dea3d..78d941fa 100644
--- a/lib/libpm.c
+++ b/lib/libpm.c
@@ -844,6 +844,9 @@ pm_parse_width(const char * const arg) {
    Return the image width represented by the decimal ASCIIZ string
    'arg'.  Fail if it doesn't validly represent a width or represents
    a width that can't be conveniently used in computation.
+
+   See comments at 'validateComputableSize' in libpam.c for details on
+   the purpose of these validations.
 -----------------------------------------------------------------------------*/
     unsigned int width;
     const char * error;
diff --git a/lib/libpnm2.c b/lib/libpnm2.c
index fa4bb8be..6fec91e9 100644
--- a/lib/libpnm2.c
+++ b/lib/libpnm2.c
@@ -23,11 +23,11 @@
 
 
 void
-pnm_writepnminit(FILE * const fileP, 
-                 int    const cols, 
-                 int    const rows, 
-                 xelval const maxval, 
-                 int    const format, 
+pnm_writepnminit(FILE * const fileP,
+                 int    const cols,
+                 int    const rows,
+                 xelval const maxval,
+                 int    const format,
                  int    const forceplain) {
 
     bool const plainFormat = forceplain || pm_plain_output;
@@ -47,7 +47,7 @@ pnm_writepnminit(FILE * const fileP,
 
     default:
         pm_error("invalid format argument received by pnm_writepnminit(): %d"
-                 "PNM_FORMAT_TYPE(format) must be %d, %d, or %d", 
+                 "PNM_FORMAT_TYPE(format) must be %d, %d, or %d",
                  format, PBM_TYPE, PGM_TYPE, PPM_TYPE);
     }
 }
@@ -55,19 +55,19 @@ pnm_writepnminit(FILE * const fileP,
 
 
 static void
-writepgmrow(FILE *       const fileP, 
-            const xel *  const xelrow, 
-            unsigned int const cols, 
-            xelval       const maxval, 
-            int          const format, 
+writepgmrow(FILE *       const fileP,
+            const xel *  const xelrow,
+            unsigned int const cols,
+            xelval       const maxval,
+            int          const format,
             bool         const plainFormat) {
-    
+
     jmp_buf jmpbuf;
     jmp_buf * origJmpbufP;
     gray * grayrow;
-    
+
     grayrow = pgm_allocrow(cols);
-    
+
     if (setjmp(jmpbuf) != 0) {
         pgm_freerow(grayrow);
         pm_setjmpbuf(origJmpbufP);
@@ -76,10 +76,10 @@ writepgmrow(FILE *       const fileP,
         unsigned int col;
 
         pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
-        
+
         for (col = 0; col < cols; ++col)
             grayrow[col] = PNM_GET1(xelrow[col]);
-    
+
         pgm_writepgmrow(fileP, grayrow, cols, (gray) maxval, plainFormat);
 
         pm_setjmpbuf(origJmpbufP);
@@ -100,7 +100,7 @@ writepbmrow(FILE *       const fileP,
     bit * bitrow;
 
     bitrow = pbm_allocrow(cols);
-    
+
     if (setjmp(jmpbuf) != 0) {
         pbm_freerow(bitrow);
         pm_setjmpbuf(origJmpbufP);
@@ -112,29 +112,29 @@ writepbmrow(FILE *       const fileP,
 
         for (col = 0; col < cols; ++col)
             bitrow[col] = PNM_GET1(xelrow[col]) == 0 ? PBM_BLACK : PBM_WHITE;
-    
+
         pbm_writepbmrow(fileP, bitrow, cols, plainFormat);
 
         pm_setjmpbuf(origJmpbufP);
     }
     pbm_freerow(bitrow);
-}    
+}
 
 
 
 void
-pnm_writepnmrow(FILE *      const fileP, 
-                const xel * const xelrow, 
-                int         const cols, 
-                xelval      const maxval, 
-                int         const format, 
+pnm_writepnmrow(FILE *      const fileP,
+                const xel * const xelrow,
+                int         const cols,
+                xelval      const maxval,
+                int         const format,
                 int         const forceplain) {
 
     bool const plainFormat = forceplain || pm_plain_output;
-    
+
     switch (PNM_FORMAT_TYPE(format)) {
     case PPM_TYPE:
-        ppm_writeppmrow(fileP, (pixel*) xelrow, cols, (pixval) maxval, 
+        ppm_writeppmrow(fileP, (pixel*) xelrow, cols, (pixval) maxval,
                         plainFormat);
         break;
 
@@ -145,10 +145,10 @@ pnm_writepnmrow(FILE *      const fileP,
     case PBM_TYPE:
         writepbmrow(fileP, xelrow, cols, plainFormat);
         break;
-    
+
     default:
         pm_error("invalid format argument received by pnm_writepnmrow(): %d"
-                 "PNM_FORMAT_TYPE(format) must be %d, %d, or %d", 
+                 "PNM_FORMAT_TYPE(format) must be %d, %d, or %d",
                  format, PBM_TYPE, PGM_TYPE, PPM_TYPE);
     }
 }
@@ -167,7 +167,10 @@ pnm_writepnm(FILE * const fileP,
     unsigned int row;
 
     pnm_writepnminit(fileP, cols, rows, maxval, format, forceplain);
-    
+
     for (row = 0; row < rows; ++row)
         pnm_writepnmrow(fileP, xels[row], cols, maxval, format, forceplain);
 }
+
+
+
diff --git a/lib/pam.h b/lib/pam.h
index aebf529a..88b8c2bd 100644
--- a/lib/pam.h
+++ b/lib/pam.h
@@ -136,6 +136,18 @@ struct pam {
         /* The plane number of the opacity plane;  meaningless if
            'haveOpacity' is false or 'visual' is false.
         */
+    int is_seekable;  /* boolean */
+        /* The file 'file' is seekable -- you can set the position of next
+           reading or writing to anything and any time.
+
+           If libnetpbm cannot tell if it is seekable or not, this is false.
+        */
+    pm_filepos raster_pos;
+        /* The file position of the raster (which is also the end of the
+           header).
+
+           Meaningless if 'is_seekable' is false.
+        */
 };
 
 #define PAM_HAVE_ALLOCATION_DEPTH 1
@@ -341,6 +353,12 @@ void
 pnm_writepaminit(struct pam * const pamP);
 
 void
+pnm_formatpamtuples(const struct pam * const pamP,
+                    const tuple *      const tuplerow,
+                    unsigned char *    const outbuf,
+                    unsigned int       const nTuple,
+                    unsigned int *     const rowSizeP);
+void
 pnm_formatpamrow(const struct pam * const pamP,
                  const tuple *      const tuplerow,
                  unsigned char *    const outbuf,
@@ -355,6 +373,14 @@ pnm_writepamrowmult(const struct pam * const pamP,
                     unsigned int       const rptcnt);
 
 void
+pnm_writepamrowpart(const struct pam * const pamP,
+                    const tuple *      const tuplerow,
+                    unsigned int       const firstRow,
+                    unsigned int       const firstCol,
+                    unsigned int       const rowCt,
+                    unsigned int       const colCt);
+
+void
 pnm_writepam(struct pam * const pamP, tuple ** const tuplearray);
 
 void
diff --git a/lib/pbm.h b/lib/pbm.h
index 27fd1163..57ab3812 100644
--- a/lib/pbm.h
+++ b/lib/pbm.h
@@ -47,6 +47,19 @@ pbm_allocrow(unsigned int const cols);
   ((bit**) pm_allocarray(cols, rows, sizeof(bit)))
 #define pbm_freearray(bits, rows) pm_freearray((char**) bits, rows)
 #define pbm_freerow(bitrow) pm_freerow((char*) bitrow)
+
+/* Beware of arithmetic overflows when using pbm_packed_bytes(),
+   pbm_allocrow_packed() and pbm_allocarray_packed().
+
+   When cols is signed int, pbm_packed_bytes(cols + 8) overflows
+   with large values.   Same with pamP->width which is always signed int.
+
+   Function validateComputableSize() called by pbm_readpbminit()
+   provides a margin of 10, but the "+7" uses much of it.
+
+   To prevent overflows, cast cols or pamP->width to unsigned int
+   like this: pbm_packed_bytes((unsigned int) cols +8))
+*/
 #define pbm_packed_bytes(cols) (((cols)+7)/8)
 #define pbm_allocrow_packed(cols) \
     ((unsigned char *) pm_allocrow(pbm_packed_bytes(cols), \
diff --git a/lib/pm.h b/lib/pm.h
index 3fc92fb4..b3c3d202 100644
--- a/lib/pm.h
+++ b/lib/pm.h
@@ -386,6 +386,9 @@ pm_bs_short(short const s);
 long
 pm_bs_long(long const l);
 
+int
+pm_is_seekable(FILE * const fileP);
+
 unsigned int
 pm_tell(FILE * const fileP);
 
diff --git a/lib/pmfileio.c b/lib/pmfileio.c
index 4048e74d..1ed71f18 100644
--- a/lib/pmfileio.c
+++ b/lib/pmfileio.c
@@ -452,27 +452,16 @@ pm_tmpfile_fd(void) {
 }
 
 
-
-FILE *
-pm_openr_seekable(const char name[]) {
+static bool
+isSeekable(FILE * const fP) {
 /*----------------------------------------------------------------------------
-  Open the file named by name[] such that it is seekable (i.e. it can be
-  rewound and read in multiple passes with fseek()).
+   The file is seekable -- we can set its read/write position to anything we
+   want.
 
-  If the file is actually seekable, this reduces to the same as
-  pm_openr().  If not, we copy the named file to a temporary file
-  and return that file's stream descriptor.
-
-  We use a file that the operating system recognizes as temporary, so
-  it picks the filename and deletes the file when Caller closes it.
+   If we can't tell if it is seekable, we return false.
 -----------------------------------------------------------------------------*/
-    int stat_rc;
-    int seekable;  /* logical: file is seekable */
+    int statRc;
     struct stat statbuf;
-    FILE * original_file;
-    FILE * seekable_file;
-
-    original_file = pm_openr((char *) name);
 
     /* I would use fseek() to determine if the file is seekable and
        be a little more general than checking the type of file, but I
@@ -486,41 +475,62 @@ pm_openr_seekable(const char name[]) {
        some other file is, it doesn't hurt much to assume it isn't.
     */
 
-    stat_rc = fstat(fileno(original_file), &statbuf);
-    if (stat_rc == 0 && S_ISREG(statbuf.st_mode))
-        seekable = TRUE;
-    else
-        seekable = FALSE;
+    statRc = fstat(fileno(fP), &statbuf);
+
+    return statRc == 0 && S_ISREG(statbuf.st_mode);
+}
+
+
+
+FILE *
+pm_openr_seekable(const char name[]) {
+/*----------------------------------------------------------------------------
+  Open the file named by name[] such that it is seekable (i.e. it can be
+  rewound and read in multiple passes with fseek()).
+
+  If the file is actually seekable, this reduces to the same as
+  pm_openr().  If not, we copy the named file to a temporary file
+  and return that file's stream descriptor.
 
-    if (seekable) {
-        seekable_file = original_file;
+  We use a file that the operating system recognizes as temporary, so
+  it picks the filename and deletes the file when Caller closes it.
+-----------------------------------------------------------------------------*/
+    FILE * originalFileP;
+    FILE * seekableFileP;
+
+    originalFileP = pm_openr((char *) name);
+
+    if (isSeekable(originalFileP)) {
+        seekableFileP = originalFileP;
     } else {
-        seekable_file = pm_tmpfile();
+        seekableFileP = pm_tmpfile();
 
         /* Copy the input into the temporary seekable file */
-        while (!feof(original_file) && !ferror(original_file)
-               && !ferror(seekable_file)) {
+        while (!feof(originalFileP) && !ferror(originalFileP)
+               && !ferror(seekableFileP)) {
             char buffer[4096];
-            int bytes_read;
-            bytes_read = fread(buffer, 1, sizeof(buffer), original_file);
-            fwrite(buffer, 1, bytes_read, seekable_file);
+            size_t nBytesRead;
+
+            nBytesRead = fread(buffer, 1, sizeof(buffer), originalFileP);
+            fwrite(buffer, 1, nBytesRead, seekableFileP);
         }
-        if (ferror(original_file))
+        if (ferror(originalFileP))
             pm_error("Error reading input file into temporary file.  "
                      "Errno = %s (%d)", strerror(errno), errno);
-        if (ferror(seekable_file))
+        if (ferror(seekableFileP))
             pm_error("Error writing input into temporary file.  "
                      "Errno = %s (%d)", strerror(errno), errno);
-        pm_close(original_file);
+        pm_close(originalFileP);
         {
-            int seek_rc;
-            seek_rc = fseek(seekable_file, 0, SEEK_SET);
-            if (seek_rc != 0)
+            int seekRc;
+
+            seekRc = fseek(seekableFileP, 0, SEEK_SET);
+            if (seekRc != 0)
                 pm_error("fseek() failed to rewind temporary file.  "
                          "Errno = %s (%d)", strerror(errno), errno);
         }
     }
-    return seekable_file;
+    return seekableFileP;
 }
 
 
@@ -968,6 +978,20 @@ pm_bs_long(long const l) {
 
 
 
+int
+pm_is_seekable(FILE * const fP) {
+
+    return isSeekable(fP) ? 1 : 0;
+}
+
+
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wpragmas"
+#pragma GCC diagnostic ignored "-Wduplicated-cond"
+
+
+
 void
 pm_tell2(FILE *       const fileP,
          void *       const fileposP,
@@ -1008,6 +1032,10 @@ pm_tell2(FILE *       const fileP,
 
 
 
+#pragma GCC diagnostic pop
+
+
+
 unsigned int
 pm_tell(FILE * const fileP) {
 
@@ -1020,6 +1048,12 @@ pm_tell(FILE * const fileP) {
 
 
 
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wpragmas"
+#pragma GCC diagnostic ignored "-Wduplicated-cond"
+
+
+
 void
 pm_seek2(FILE *             const fileP,
          const pm_filepos * const fileposP,
@@ -1047,6 +1081,10 @@ pm_seek2(FILE *             const fileP,
 
 
 
+#pragma GCC diagnostic pop
+
+
+
 void
 pm_seek(FILE * const fileP, unsigned long filepos) {
 /*----------------------------------------------------------------------------
diff --git a/lib/util/rand.c b/lib/util/rand.c
index 2f60de83..6a0a2cdb 100644
--- a/lib/util/rand.c
+++ b/lib/util/rand.c
@@ -72,8 +72,8 @@ https://wnww.gnu.org/software/gsl/doc/html/rng.html
   Twister method and does not rely on any randomness facility of the operating
   system, but it is easy to compile an alternative version that uses others.
 
-  The Mersenne Twister method was new to Netpbm in Netpbm 10.94
-  (March 2021).  Before that, Netpbm used standard OS-provided facilities.
+  The Mersenne Twister method was new to Netpbm in Netpbm 10.94 (March 2021).
+  Before that, Netpbm used standard OS-provided facilities.
 
   Programs that use random numbers have existed in Netpbm since PBMPlus days.
   The system rand() function was used in instances randomness was required;
@@ -87,15 +87,15 @@ https://wnww.gnu.org/software/gsl/doc/html/rng.html
 
   This was not considered a problem in the early days.  Deterministic
   operation was not a feature users requested and it was impossible regardless
-  of the random number generation method on most programs because they did
-  not allow a user to specify a seed for the generator.
+  of the random number generation method on most programs because they did not
+  allow a user to specify a seed for the generator.
 
   This state of affairs changed as Netpbm got firmly established as a
   base-level system package.  Security became critical for many users.  A
   crucial component of quality control is automated regression tests (="make
   check").  Unpredictable behavior gets in the way of testing.  One by one
   programs were given the -randomseed (or -seed) option to ensure reproducible
-  results.  Often this was done as new tests cases were written.  However,
+  results.  Often this was done as new test cases were written.  However,
   inconsistent output caused by system-level differences in rand()
   implementation remained a major obstacle.
 
@@ -219,6 +219,39 @@ pm_gaussrand(struct pm_randSt * const randStP) {
 
 
 
+uint32_t
+pm_rand32(struct pm_randSt * const randStP) {
+/*-----------------------------------------------------------------------------
+  Generate a 32-bit random number.
+
+  This is a provision for users who select a non-default random number
+  generator which returns less than 32 bits per call.  Many system generators
+  are known to return 31 bits (max = 2147483647 or 0x7FFFFFFF).
+
+  This does not work with generators that return less than 11 bits per call.
+  The least we know of is the archaic RANDU, which generates 15 bits (max =
+  32767 or 0x7FFF).
+-----------------------------------------------------------------------------*/
+    unsigned int const randMax = randStP->max;
+
+    uint32_t retval;
+
+    if (randMax >= 0xFFFFFFFF)
+        retval = pm_rand(randStP);
+    else {
+        uint32_t scale;
+
+        retval = 0;  /* Initial value */
+
+        for (scale = 0xFFFFFFFF; scale > 0; scale /= (randMax +1))
+            retval *= (randMax + 1) + pm_rand(randStP);
+    }
+
+    return retval;;
+}
+
+
+
 void
 pm_randinit(struct pm_randSt * const randStP) {
 /*----------------------------------------------------------------------------
diff --git a/lib/util/rand.h b/lib/util/rand.h
index c441890a..44095243 100644
--- a/lib/util/rand.h
+++ b/lib/util/rand.h
@@ -3,6 +3,8 @@
 #ifndef RAND_H_INCLUDED
 #define RAND_H_INCLUDED
 
+#include <inttypes.h>
+
 #include "netpbm/pm_c_util.h"
 #include "netpbm/mallocvar.h"
 
@@ -103,5 +105,8 @@ pm_gaussrand2(struct pm_randSt * const randStP,
 extern double
 pm_gaussrand(struct pm_randSt * const randStP);
 
+extern uint32_t
+pm_rand32(struct pm_randSt * const randStP);
+
 
 #endif