about summary refs log tree commit diff
path: root/editor/pnmcrop.c
diff options
context:
space:
mode:
authorgiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2009-03-02 01:05:47 +0000
committergiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2009-03-02 01:05:47 +0000
commit150c1c5999ddf9fb1f0a638bf628c000649e8735 (patch)
tree644a9cd73339d60c34bf51fdfe1d0fb7c66603ae /editor/pnmcrop.c
parente4f137017d17bc98ef50d9f7ada38a4d65bcfdff (diff)
downloadnetpbm-mirror-150c1c5999ddf9fb1f0a638bf628c000649e8735.tar.gz
netpbm-mirror-150c1c5999ddf9fb1f0a638bf628c000649e8735.tar.xz
netpbm-mirror-150c1c5999ddf9fb1f0a638bf628c000649e8735.zip
Add PBM fastpath, fix various bugs
git-svn-id: http://svn.code.sf.net/p/netpbm/code/trunk@849 9d0c8265-081b-0410-96cb-a4ca84ce46f8
Diffstat (limited to 'editor/pnmcrop.c')
-rw-r--r--editor/pnmcrop.c654
1 files changed, 450 insertions, 204 deletions
diff --git a/editor/pnmcrop.c b/editor/pnmcrop.c
index a98c6f60..e9dcbe4a 100644
--- a/editor/pnmcrop.c
+++ b/editor/pnmcrop.c
@@ -31,13 +31,25 @@
 
 enum bg_choice {BG_BLACK, BG_WHITE, BG_DEFAULT, BG_SIDES};
 
+typedef enum { LEFT = 0, RIGHT = 1, TOP = 2, BOTTOM = 3} edgeLocation;
+
+static const char * const edgeName[] = {
+    "left",
+    "right",
+    "top",
+    "bottom"
+};
+
+
+
 struct cmdlineInfo {
     /* All the information the user supplied in the command line,
        in a form easy for the program to use.
     */
     const char * inputFilespec;
     enum bg_choice background;
-    unsigned int left, right, top, bottom;
+    bool wantCrop[4];
+        /* User wants crop of left, right, top, bottom, resp. */
     unsigned int verbose;
     unsigned int margin;
     const char * borderfile;  /* NULL if none */
@@ -46,7 +58,7 @@ struct cmdlineInfo {
 
 
 static void
-parseCommandLine(int argc, char ** argv,
+parseCommandLine(int argc, const char ** argv,
                  struct cmdlineInfo *cmdlineP) {
 /*----------------------------------------------------------------------------
    Note that the file spec array we return is stored in the storage that
@@ -59,6 +71,7 @@ parseCommandLine(int argc, char ** argv,
 
     unsigned int blackOpt, whiteOpt, sidesOpt;
     unsigned int marginSpec, borderfileSpec;
+    unsigned int leftOpt, rightOpt, topOpt, bottomOpt;
     
     unsigned int option_def_index;
 
@@ -68,10 +81,10 @@ parseCommandLine(int argc, char ** argv,
     OPTENT3(0, "black",      OPT_FLAG, NULL, &blackOpt,            0);
     OPTENT3(0, "white",      OPT_FLAG, NULL, &whiteOpt,            0);
     OPTENT3(0, "sides",      OPT_FLAG, NULL, &sidesOpt,            0);
-    OPTENT3(0, "left",       OPT_FLAG, NULL, &cmdlineP->left,      0);
-    OPTENT3(0, "right",      OPT_FLAG, NULL, &cmdlineP->right,     0);
-    OPTENT3(0, "top",        OPT_FLAG, NULL, &cmdlineP->top,       0);
-    OPTENT3(0, "bottom",     OPT_FLAG, NULL, &cmdlineP->bottom,    0);
+    OPTENT3(0, "left",       OPT_FLAG, NULL, &leftOpt,             0);
+    OPTENT3(0, "right",      OPT_FLAG, NULL, &rightOpt,            0);
+    OPTENT3(0, "top",        OPT_FLAG, NULL, &topOpt,              0);
+    OPTENT3(0, "bottom",     OPT_FLAG, NULL, &bottomOpt,           0);
     OPTENT3(0, "verbose",    OPT_FLAG, NULL, &cmdlineP->verbose,   0);
     OPTENT3(0, "margin",     OPT_UINT,   &cmdlineP->margin,    
             &marginSpec,     0);
@@ -82,8 +95,10 @@ parseCommandLine(int argc, char ** argv,
     opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
     opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
 
-    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+    optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
         /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+        
+    free(option_def);
 
     if (argc-1 == 0)
         cmdlineP->inputFilespec = "-";  /* stdin */
@@ -106,12 +121,16 @@ parseCommandLine(int argc, char ** argv,
     else
         cmdlineP->background = BG_DEFAULT;
 
-    if (!cmdlineP->left && !cmdlineP->right && !cmdlineP->top
-        && !cmdlineP->bottom) {
-        cmdlineP->left = cmdlineP->right = cmdlineP->top 
-            = cmdlineP->bottom = TRUE;
+    if (!leftOpt && !rightOpt && !topOpt && !bottomOpt) {
+        unsigned int i;
+        for (i = 0; i < 4; ++i)
+            cmdlineP->wantCrop[i] = true;
+    } else {
+        cmdlineP->wantCrop[LEFT]   = !!leftOpt;
+        cmdlineP->wantCrop[RIGHT]  = !!rightOpt;
+        cmdlineP->wantCrop[TOP]    = !!topOpt;
+        cmdlineP->wantCrop[BOTTOM] = !!bottomOpt;
     }
-
     if (!marginSpec)
         cmdlineP->margin = 0;
 
@@ -121,6 +140,22 @@ parseCommandLine(int argc, char ** argv,
 
 
 
+typedef struct {
+/*----------------------------------------------------------------------------
+   This describes a cropping operation of a single border (top, bottom,
+   left, or right).
+
+   Our definition of cropping includes padding to make a margin as well as
+   chopping stuff out.
+-----------------------------------------------------------------------------*/
+    unsigned int removeSize;
+        /* Size in pixels of the border to remove */
+    unsigned int padSize;
+        /* Size in pixels of the border to add */
+} cropOp;
+
+
+
 static xel
 background3Corners(FILE * const ifP,
                    int    const rows,
@@ -204,10 +239,10 @@ computeBackground(FILE *         const ifP,
     
     switch (backgroundChoice) {
     case BG_WHITE:
-	    background = pnm_whitexel(maxval, format);
+        background = pnm_whitexel(maxval, format);
         break;
     case BG_BLACK:
-	    background = pnm_blackxel(maxval, format);
+        background = pnm_blackxel(maxval, format);
         break;
     case BG_SIDES: 
         background = 
@@ -238,33 +273,28 @@ findBordersInImage(FILE *         const ifP,
                    xel            const backgroundColor,
                    bool           const verbose, 
                    bool *         const hasBordersP,
-                   unsigned int * const leftP,
-                   unsigned int * const rightP, 
-                   unsigned int * const topP,
-                   unsigned int * const bottomP) {
+                   unsigned int * const borderSize) {
 /*----------------------------------------------------------------------------
    Find the left, right, top, and bottom borders in the image 'ifP'.
-   Return their sizes in pixels as *leftP, *rightP, *topP, and *bottomP.
+   Return their sizes in pixels as borderSize[n].
    
    Iff the image is all background, *hasBordersP == FALSE.
 
    Expect the input file to be positioned to the beginning of the
    image raster and leave it positioned arbitrarily.
 -----------------------------------------------------------------------------*/
-    xel* xelrow;        /* A row of the input image */
+    xel * xelrow;        /* A row of the input image */
     int row;
     bool gottop;
     int left, right, bottom, top;
-        /* leftmost, etc. nonbackground pixel found so far.
-           Can be just off the edge of the image, so -1 or 'cols' or 'rows'
-        */
+        /* leftmost, etc. nonbackground pixel found so far; -1 for none */
 
     xelrow = pnm_allocrow(cols);
     
     left   = cols;  /* initial value */
-    right  = -1;   /* initial value */
-    top    = rows;   /* initial value */
-    bottom = -1;  /* initial value */
+    right  = -1;    /* initial value */
+    top    = rows;  /* initial value */
+    bottom = -1;    /* initial value */
 
     gottop = FALSE;
     for (row = 0; row < rows; ++row) {
@@ -275,7 +305,7 @@ findBordersInImage(FILE *         const ifP,
         pnm_readpnmrow(ifP, xelrow, cols, maxval, format);
         
         col = 0;
-        while (PNM_EQUAL(xelrow[col], backgroundColor) && col < cols)
+        while (col < cols && PNM_EQUAL(xelrow[col], backgroundColor))
             ++col;
         thisRowLeft = col;
 
@@ -297,46 +327,45 @@ findBordersInImage(FILE *         const ifP,
             bottom = row + 1;   /* New candidate */
         }
     }
+    
+    free(xelrow);
 
     if (right == -1)
         *hasBordersP = FALSE;
     else {
         *hasBordersP = TRUE;
         assert(right <= cols); assert(bottom <= rows);
-        *leftP       = left - 0;
-        *rightP      = cols - right;
-        *topP        = top - 0;
-        *bottomP     = rows - bottom;
+        borderSize[LEFT]   = left - 0;
+        borderSize[RIGHT]  = cols - right;
+        borderSize[TOP]    = top - 0;
+        borderSize[BOTTOM] = rows - bottom;
     }
 }
 
 
 
 static void
-findBordersInFile(const char *   const borderFileName,
+findBordersInFile(unsigned int   const icols,
+                  unsigned int   const irows,
+                  FILE *         const borderFileP, 
                   xel            const backgroundColor,
                   bool           const verbose, 
                   bool *         const hasBordersP,
-                  unsigned int * const leftP,
-                  unsigned int * const rightP, 
-                  unsigned int * const topP,
-                  unsigned int * const bottomP) {
-
-    FILE * borderFileP;
-    int cols;
-    int rows;
+                  unsigned int * const borderSize) {
+
+    int bcols;  /* cols and rows in the borderfile */
+    int brows;
     xelval maxval;
     int format;
     
-    borderFileP = pm_openr(borderFileName);
-    
-    pnm_readpnminit(borderFileP, &cols, &rows, &maxval, &format);
-    
-    findBordersInImage(borderFileP, cols, rows, maxval, format, 
-                       backgroundColor, verbose, hasBordersP,
-                       leftP, rightP, topP, bottomP);
+    pnm_readpnminit(borderFileP, &bcols, &brows, &maxval, &format);
 
-    pm_close(borderFileP);
+    if (bcols != icols || brows != irows)
+      pm_error("Image file and border file differ in size: %ux%u %ux%u",
+           icols, irows, bcols, brows);
+    
+    findBordersInImage(borderFileP, bcols, brows, maxval, format, 
+                       backgroundColor, verbose, hasBordersP, borderSize);
 } 
 
 
@@ -350,45 +379,24 @@ ending(unsigned int const n) {
 
 
 static void
-reportOneEdge(unsigned int const oldBorderSize,
-              unsigned int const newBorderSize,
-              const char * const place) {
-
-    if (newBorderSize > oldBorderSize)
-        pm_message("Adding %u pixel%s to the %u-pixel %s border",
-                   newBorderSize - oldBorderSize,
-                   ending(newBorderSize - oldBorderSize),
-                   oldBorderSize, place);
-    else if (newBorderSize < oldBorderSize)
-        pm_message("Cropping %u pixel%s from the %u-pixel %s border",
-                   oldBorderSize - newBorderSize,
-                   ending(oldBorderSize - newBorderSize),
-                   oldBorderSize, place);
-    else
-        pm_message("Leaving %s border unchanged at %u pixel%s",
-                   place, oldBorderSize, ending(oldBorderSize));
-}        
-
+reportCroppingParameters(cropOp const crop[]) {
 
+    unsigned int i;
 
-static void
-reportCroppingParameters(unsigned int const oldLeftBorderSize,
-                         unsigned int const oldRightBorderSize,
-                         unsigned int const oldTopBorderSize,
-                         unsigned int const oldBottomBorderSize,
-                         unsigned int const newLeftBorderSize,
-                         unsigned int const newRightBorderSize,
-                         unsigned int const newTopBorderSize,
-                         unsigned int const newBottomBorderSize) {
-
-    if (oldLeftBorderSize == 0 && oldRightBorderSize == 0 &&
-        oldTopBorderSize == 0 && oldBottomBorderSize == 0)
-        pm_message("No Border found.");
-
-    reportOneEdge(oldLeftBorderSize,   newLeftBorderSize,   "left"  );
-    reportOneEdge(oldRightBorderSize,  newRightBorderSize,  "right" );
-    reportOneEdge(oldTopBorderSize,    newTopBorderSize,    "top"   );
-    reportOneEdge(oldBottomBorderSize, newBottomBorderSize, "bottom");
+    for (i = 0; i < 4; ++i) {
+        if (crop[i].removeSize == 0 && crop[i].padSize == 0)
+            pm_message("Not cropping %s edge", edgeName[i]);
+        else {
+            if (crop[i].padSize > 0)
+                pm_message("Adding %u pixel%s to the %s border",
+                           crop[i].padSize, ending(crop[i].padSize),
+                           edgeName[i]);
+            if (crop[i].removeSize > 0)
+                pm_message("Cropping %u pixel%s from the %s border",
+                           crop[i].removeSize, ending(crop[i].removeSize),
+                           edgeName[i]);
+        }
+    }
 }
 
 
@@ -408,21 +416,60 @@ fillRow(xel *        const xelrow,
 
 
 static void
-writeCropped(FILE *       const ifP,
-             unsigned int const cols,
-             unsigned int const rows,
-             xelval       const maxval,
-             int          const format,
-             unsigned int const oldLeftBorder,
-             unsigned int const oldRightBorder,
-             unsigned int const oldTopBorder,
-             unsigned int const oldBottomBorder,
-             unsigned int const newLeftBorder,
-             unsigned int const newRightBorder,
-             unsigned int const newTopBorder,
-             unsigned int const newBottomBorder,
-             xel          const backgroundColor,
-             FILE *       const ofP) {
+readOffBorderNonPbm(unsigned int const height,
+                    FILE *       const ifP,
+                    unsigned int const cols,
+                    xelval       const maxval,
+                    int          const format) {
+
+    xel * xelrow;
+    unsigned int i;
+
+    xelrow = pnm_allocrow(cols);
+
+    for (i = 0; i < height; ++i)
+        pnm_readpnmrow(ifP, xelrow, cols, maxval, format);
+
+    pnm_freerow(xelrow);
+}
+
+
+
+static void
+outputNewBorderNonPbm(unsigned int const height,
+                      unsigned int const width,
+                      xel          const color,
+                      FILE *       const ofP,
+                      xelval       const maxval,
+                      int          const format) {
+/*----------------------------------------------------------------------------
+   Output to 'ofP' a horizontal border (i.e. top or bottom)
+   of color 'backgroundColor', height 'height', width 'width'.
+-----------------------------------------------------------------------------*/
+    xel * xelrow;
+    unsigned int i;
+
+    xelrow = pnm_allocrow(width);
+
+    fillRow(xelrow, width, color);
+
+    for (i = 0; i < height; ++i)
+        pnm_writepnmrow(ofP, xelrow, width, maxval, format, 0);
+    
+    pnm_freerow(xelrow);
+}
+
+
+
+static void
+writeCroppedNonPbm(FILE *       const ifP,
+                   unsigned int const cols,
+                   unsigned int const rows,
+                   xelval       const maxval,
+                   int          const format,
+                   cropOp       const crop[],
+                   xel          const backgroundColor,
+                   FILE *       const ofP) {
 
     /* In order to do cropping, padding or both at the same time, we have
        a rather complicated row buffer:
@@ -431,6 +478,11 @@ writeCropped(FILE *       const ifP,
        the foreground pixels, the original border pixels, and the new
        border pixels.
 
+       We're calling foreground everything that isn't being cropped out
+       or padded in.  So the "foreground" may include some of what is really
+       a background border in the original image -- because the user can
+       choose to retain part of that border as a margin.
+
        The foreground pixels are in the center of the
        buffer, starting at Column 'foregroundLeft' and going to
        'foregroundRight'.
@@ -453,72 +505,65 @@ writeCropped(FILE *       const ifP,
 
        That's for the middle rows.  For the top and bottom, we just use
        the left portion of xelrow[], starting at 0.
+
+       This is the general case.  Enhancement for PBM appears below.
+       (Logic works for PBM).
     */
 
     unsigned int const foregroundCols =
-        cols - oldLeftBorder - oldRightBorder;
+        cols - crop[LEFT].removeSize - crop[RIGHT].removeSize;
     unsigned int const outputCols     = 
-        foregroundCols + newLeftBorder + newRightBorder;
+        foregroundCols + crop[LEFT].padSize + crop[RIGHT].padSize;
     unsigned int const foregroundRows =
-        rows - oldTopBorder - oldBottomBorder;
+        rows - crop[TOP].removeSize - crop[BOTTOM].removeSize;
     unsigned int const outputRows     =
-        foregroundRows + newTopBorder + newBottomBorder;
+        foregroundRows + crop[TOP].padSize + crop[BOTTOM].padSize;
 
-    unsigned int const foregroundLeft  = MAX(oldLeftBorder, newLeftBorder);
+    unsigned int const foregroundLeft  =
+        MAX(crop[LEFT].removeSize, crop[LEFT].padSize);
         /* Index into xelrow[] of leftmost pixel of foreground */
     unsigned int const foregroundRight = foregroundLeft + foregroundCols;
         /* Index into xelrow[] just past rightmost pixel of foreground */
 
     unsigned int const allocCols =
-        foregroundRight + MAX(oldRightBorder, newRightBorder);
+        foregroundRight + MAX(crop[RIGHT].removeSize, crop[RIGHT].padSize);
 
-    xel *xelrow;
+    xel * xelrow;
     unsigned int i;
 
-    assert(outputCols == newLeftBorder + foregroundCols + newRightBorder);
-    assert(outputRows == newTopBorder + foregroundRows + newBottomBorder);
-    
     pnm_writepnminit(ofP, outputCols, outputRows, maxval, format, 0);
 
     xelrow = pnm_allocrow(allocCols);
 
-    /* Read off existing top border */
-    for (i = 0; i < oldTopBorder; ++i)
-        pnm_readpnmrow(ifP, xelrow, cols, maxval, format);
+    readOffBorderNonPbm(crop[TOP].removeSize, ifP, cols, maxval, format);
 
+    outputNewBorderNonPbm(crop[TOP].padSize, outputCols, backgroundColor,
+                          ofP, maxval, format);
 
-    /* Output new top border */
-    fillRow(xelrow, outputCols, backgroundColor);
-    for (i = 0; i < newTopBorder; ++i)
-        pnm_writepnmrow(ofP, xelrow, outputCols, maxval, format, 0);
+    /* Set left border pixels */
+    fillRow(&xelrow[foregroundLeft - crop[LEFT].padSize], crop[LEFT].padSize,
+            backgroundColor);
 
+    /* Set right border pixels */
+    fillRow(&xelrow[foregroundRight], crop[RIGHT].padSize, backgroundColor);
 
     /* Read and output foreground rows */
     for (i = 0; i < foregroundRows; ++i) {
-        /* Set left border pixels */
-        fillRow(&xelrow[foregroundLeft - newLeftBorder], newLeftBorder,
-                backgroundColor);
-
+ 
         /* Read foreground pixels */
-        pnm_readpnmrow(ifP, &(xelrow[foregroundLeft - oldLeftBorder]), cols,
-                       maxval, format);
-
-        /* Set right border pixels */
-        fillRow(&xelrow[foregroundRight], newRightBorder, backgroundColor);
+        pnm_readpnmrow(ifP, &(xelrow[foregroundLeft - crop[LEFT].removeSize]),
+                       cols, maxval, format);
         
         pnm_writepnmrow(ofP,
-                        &(xelrow[foregroundLeft - newLeftBorder]), outputCols,
-                        maxval, format, 0);
+                        &(xelrow[foregroundLeft - crop[LEFT].padSize]),
+                        outputCols, maxval, format, 0);
     }
 
-    /* Read off existing bottom border */
-    for (i = 0; i < oldBottomBorder; ++i)
-        pnm_readpnmrow(ifP, xelrow, cols, maxval, format);
-
-    /* Output new bottom border */
-    fillRow(xelrow, outputCols, backgroundColor);
-    for (i = 0; i < newBottomBorder; ++i)
-        pnm_writepnmrow(ofP, xelrow, outputCols, maxval, format, 0);
+    readOffBorderNonPbm(crop[BOTTOM].removeSize, ifP, cols, maxval, format);
+    
+    outputNewBorderNonPbm(crop[BOTTOM].padSize, outputCols,
+                          backgroundColor,
+                          ofP, maxval, format);
 
     pnm_freerow(xelrow);
 }
@@ -526,103 +571,304 @@ writeCropped(FILE *       const ifP,
 
 
 static void
-determineNewBorders(struct cmdlineInfo const cmdline,
-                    unsigned int       const leftBorderSize,
-                    unsigned int       const rightBorderSize,
-                    unsigned int       const topBorderSize,
-                    unsigned int       const bottomBorderSize,
-                    unsigned int *     const newLeftSizeP,
-                    unsigned int *     const newRightSizeP,
-                    unsigned int *     const newTopSizeP,
-                    unsigned int *     const newBottomSizeP) {
-
-    *newLeftSizeP   = cmdline.left   ? cmdline.margin : leftBorderSize   ;
-    *newRightSizeP  = cmdline.right  ? cmdline.margin : rightBorderSize  ;
-    *newTopSizeP    = cmdline.top    ? cmdline.margin : topBorderSize    ;
-    *newBottomSizeP = cmdline.bottom ? cmdline.margin : bottomBorderSize ;
-}
+fillRowPBM(unsigned char * const bitrow,
+           unsigned int    const cols,
+           unsigned int    const blackWhite) {
+/*----------------------------------------------------------------------------
+   Fill the packed PBM row buffer bitrow[] with 'cols' columns of
+   black or white: black if 'blackWhite' is 1; white if it is '0'.
+   'blackWhite' cannot be anything else.
+-----------------------------------------------------------------------------*/
+    unsigned int const colChars = pbm_packed_bytes(cols);
+    unsigned int i;
+
+    assert(blackWhite == 0 || blackWhite == 1);
+    
+    for (i = 0; i < colChars; ++i)
+        bitrow[i] = blackWhite * 0xff;
         
+    if (cols % 8 > 0)
+        bitrow[colChars-1] <<= 8 - cols % 8;
+}
 
 
-int
-main(int argc, char *argv[]) {
 
-    struct cmdlineInfo cmdline;
-    FILE * ifP;   
-        /* The program's regular input file.  Could be a seekable copy of it
-           in a temporary file.
+static void
+readOffBorderPbm(unsigned int const height,
+                 FILE *       const ifP,
+                 unsigned int const cols,
+                 int          const format) {
+
+    unsigned char * bitrow;
+    unsigned int i;
+
+    bitrow = pbm_allocrow_packed(cols);
+
+    for (i = 0; i < height; ++i)
+        pbm_readpbmrow_packed(ifP, bitrow, cols, format);
+
+    pbm_freerow_packed(bitrow);
+}
+
+
+
+static void
+outputNewBorderPbm(unsigned int const height,
+                   unsigned int const width,
+                   unsigned int const blackWhite,
+                   FILE *       const ofP) {
+/*----------------------------------------------------------------------------
+   Output to 'ofP' a horizontal border (i.e. top or bottom)
+   of height 'height', width 'width'.  Make it black if 'blackWhite' is
+   1; white if 'blackWhite' is 0.  'blackWhite' can't be anything else.
+-----------------------------------------------------------------------------*/
+    unsigned char * bitrow;
+    unsigned int i;
+
+    bitrow = pbm_allocrow_packed(width);
+
+    fillRowPBM(bitrow, width, blackWhite);
+
+    for (i = 0; i < height; ++i)
+        pbm_writepbmrow_packed(ofP, bitrow, width, 0);
+    
+    pbm_freerow_packed(bitrow);
+}
+
+
+
+static void
+writeCroppedPBM(FILE *       const ifP,
+                unsigned int const cols,
+                unsigned int const rows,
+                int          const format,
+                cropOp       const crop[],
+                xel          const backgroundColor,
+                FILE *       const ofP) {
+    
+    /* See comments for writeCroppedNonPBM(), which uses identical logic flow. 
+       Uses pbm functions instead of general pnm functions.
+    */
+
+    unsigned int const foregroundCols =
+        cols - crop[LEFT].removeSize - crop[RIGHT].removeSize;
+    unsigned int const outputCols     = 
+        foregroundCols + crop[LEFT].padSize + crop[RIGHT].padSize;
+    unsigned int const foregroundRows =
+        rows - crop[TOP].removeSize - crop[BOTTOM].removeSize;
+    unsigned int const outputRows     =
+        foregroundRows + crop[TOP].padSize + crop[BOTTOM].padSize;
+
+    unsigned int const foregroundLeft  =
+        MAX(crop[LEFT].removeSize, crop[LEFT].padSize);
+    unsigned int const foregroundRight = foregroundLeft + foregroundCols;
+
+    unsigned int const allocCols =
+        foregroundRight + MAX(crop[RIGHT].removeSize, crop[RIGHT].padSize);
+
+    unsigned int const backgroundBlackWhite =
+        PNM_EQUAL(backgroundColor, pnm_whitexel(1, PBM_TYPE)) ? 0: 1;
+
+    unsigned int const readOffset    = foregroundLeft - crop[LEFT].removeSize;
+    unsigned int const writeOffset   = foregroundLeft - crop[LEFT].padSize;
+    unsigned int const lastWriteChar = writeOffset/8 + (outputCols-1)/8;
+    unsigned char * bitrow;
+    unsigned int i;
+    
+    pbm_writepbminit(ofP, outputCols, outputRows, 0);
+
+    bitrow = pbm_allocrow_packed(allocCols);
+
+    readOffBorderPbm(crop[TOP].removeSize, ifP, cols, format);
+
+    outputNewBorderPbm(crop[TOP].padSize, outputCols, backgroundBlackWhite,
+                       ofP);
+
+    /* Prepare padding: left and/or right */
+    fillRowPBM(bitrow, allocCols, backgroundBlackWhite);
+
+    /* Read and output foreground rows */
+    for (i = 0; i < foregroundRows; ++i) {
+        /* Read foreground pixels */
+        pbm_readpbmrow_bitoffset(ifP, bitrow, cols, format, readOffset);
+  
+        pbm_writepbmrow_bitoffset(ofP,
+                                  bitrow, outputCols, format, writeOffset);
+                              
+        /* If there is right-side padding, repair the write buffer
+           distorted by pbm_writepbmrow_bitoffset() 
+           (No need to mend any left-side padding)
         */
-    bool eof;   /* no more images in input stream */
+        if (crop[RIGHT].padSize > 0)    
+            bitrow[lastWriteChar] = backgroundBlackWhite * 0xff;
+    }
+
+    readOffBorderPbm(crop[BOTTOM].removeSize, ifP, cols, format);
+
+    outputNewBorderPbm(crop[BOTTOM].padSize, outputCols, backgroundBlackWhite,
+                       ofP);
+
+    pbm_freerow_packed(bitrow);
+}
+
+
+
+static void
+determineCrops(struct cmdlineInfo const cmdline,
+               unsigned int       const oldBorderSize[],
+               cropOp *           const cropArray) {
+
+    edgeLocation i;
+
+    for (i = 0; i < 4; ++i) {
+        if (cmdline.wantCrop[i]) {
+            if (oldBorderSize[i] > cmdline.margin) {
+                cropArray[i].removeSize = oldBorderSize[i] - cmdline.margin;
+                cropArray[i].padSize    = 0;
+            } else {
+                cropArray[i].removeSize = 0;
+                cropArray[i].padSize    = cmdline.margin - oldBorderSize[i];
+            }
+        } else {
+            cropArray[i].removeSize = 0;
+            cropArray[i].padSize    = 0;
+        }
+    }
+}
 
+
+
+static void
+validateComputableSize(unsigned int const cols,
+                       unsigned int const rows,
+                       cropOp       const crop[]) {
+
+    double const newcols =
+        (double)cols +
+        (double)crop[LEFT].padSize + (double)crop[RIGHT].padSize;
+
+    double const newrows =
+        (double)rows +
+        (double)crop[TOP].padSize + (double)crop[BOTTOM].padSize;
+
+    if (newcols > INT_MAX)
+       pm_error("Output width too large: %.0f.", newcols);
+    if (newrows > INT_MAX)
+       pm_error("Output height too large: %.0f.", newrows);
+}
+
+
+
+static void
+cropOneImage(struct cmdlineInfo const cmdline,
+             FILE *             const ifP,
+             FILE *             const bdfP,
+             FILE *             const ofP) {
+/*----------------------------------------------------------------------------
+   Crop the image to which the stream *ifP is presently positioned
+   and write the results to *ofP.  If bdfP is non-null, use the image
+   to which stream *bdfP is presently positioned as the borderfile
+   (the file that tells us where the existing borders are in the input
+   image).  Leave *ifP and *bdfP positioned after the image.
+
+   *ifP is seekable; *bdfP may not be.
+-----------------------------------------------------------------------------*/
     xelval maxval;
     int format;
     int rows, cols;   /* dimensions of input image */
     bool hasBorders;
-    unsigned int oldLeftBorder, oldRightBorder, oldTopBorder, oldBottomBorder;
+    unsigned int oldBorder[4];
         /* The sizes of the borders in the input image */
-    unsigned int newLeftBorder, newRightBorder, newTopBorder, newBottomBorder;
-        /* The sizes of the borders in the output image */
+    cropOp crop[4];
+        /* The crops we have to do on each side */
+    
     xel background;
     pm_filepos rasterpos;
 
-    pnm_init(&argc, argv);
+    pnm_readpnminit(ifP, &cols, &rows, &maxval, &format);
 
-    parseCommandLine(argc, argv, &cmdline);
+    pm_tell2(ifP, &rasterpos, sizeof(rasterpos));
 
-    ifP = pm_openr_seekable(cmdline.inputFilespec);
+    background = computeBackground(ifP, cols, rows, maxval, format,
+                                   cmdline.background, cmdline.verbose);
 
-    eof = FALSE;
-    while (!eof) {
-        pnm_readpnminit(ifP, &cols, &rows, &maxval, &format);
+    if (cmdline.borderfile) {
+        findBordersInFile(cols, rows, bdfP, background,
+                          cmdline.verbose, &hasBorders, oldBorder);
+    } else {
+        pm_seek2(ifP, &rasterpos, sizeof(rasterpos));
 
-        pm_tell2(ifP, &rasterpos, sizeof(rasterpos));
+        findBordersInImage(ifP, cols, rows, maxval, format, 
+                           background, cmdline.verbose, &hasBorders,
+                           oldBorder);
+    }
+    if (!hasBorders)
+        pm_error("The image is entirely background; "
+                 "there is nothing to crop.");
 
-        background = computeBackground(ifP, cols, rows, maxval, format,
-                                       cmdline.background, cmdline.verbose);
+    determineCrops(cmdline, oldBorder, crop);
 
-        if (cmdline.borderfile) {
-            findBordersInFile(cmdline.borderfile,
-                              background, cmdline.verbose, &hasBorders,
-                              &oldLeftBorder, &oldRightBorder,
-                              &oldTopBorder,  &oldBottomBorder);
-        } else {
-            pm_seek2(ifP, &rasterpos, sizeof(rasterpos));
+    validateComputableSize(cols, rows, crop);
 
-            findBordersInImage(ifP, cols, rows, maxval, format, 
-                               background, cmdline.verbose, &hasBorders,
-                               &oldLeftBorder, &oldRightBorder,
-                               &oldTopBorder,  &oldBottomBorder);
-        }
-        if (!hasBorders)
-            pm_error("The image is entirely background; "
-                     "there is nothing to crop.");
-
-        determineNewBorders(cmdline, 
-                            oldLeftBorder, oldRightBorder,
-                            oldTopBorder,  oldBottomBorder,
-                            &newLeftBorder, &newRightBorder,
-                            &newTopBorder,  &newBottomBorder);
-
-        if (cmdline.verbose) 
-            reportCroppingParameters(oldLeftBorder, oldRightBorder,
-                                     oldTopBorder,  oldBottomBorder,
-                                     newLeftBorder, newRightBorder,
-                                     newTopBorder,  newBottomBorder);
+    if (cmdline.verbose) 
+        reportCroppingParameters(crop);
 
-        pm_seek2(ifP, &rasterpos, sizeof(rasterpos));
+    pm_seek2(ifP, &rasterpos, sizeof(rasterpos));
 
-        writeCropped(ifP, cols, rows, maxval, format,
-                     oldLeftBorder, oldRightBorder,
-                     oldTopBorder,  oldBottomBorder,
-                     newLeftBorder, newRightBorder,
-                     newTopBorder,  newBottomBorder,
-                     background, stdout);
+    if (PNM_FORMAT_TYPE(format) == PBM_TYPE)
+        writeCroppedPBM(ifP, cols, rows, format, crop, background, ofP);
+    else
+        writeCroppedNonPbm(ifP, cols, rows, maxval, format, crop,
+                           background, ofP);
+}
+
+
+
+int
+main(int argc, const char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+    FILE * ifP;   
+        /* The program's regular input file.  Could be a seekable copy of
+           it in a temporary file.
+        */
+    FILE * bdfP; /* The borderfile; NULL if none.  No seeks.  */
+    bool eof;    /* no more images in input stream */
+    bool beof;   /* no more images in borderfile stream */
+
+    pm_proginit(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr_seekable(cmdline.inputFilespec);
+
+    if (cmdline.borderfile)
+        bdfP = pm_openr(cmdline.borderfile);
+    else
+        bdfP = NULL;
+
+    eof = beof = FALSE;
+    while (!eof) {
+        cropOneImage(cmdline, ifP, bdfP, stdout);
 
         pnm_nextimage(ifP, &eof);
+
+        if (bdfP) {
+            pnm_nextimage(bdfP, &beof);
+            
+            if (eof != beof) {
+                if (!eof)
+                    pm_error("Input file has more images than border file."); 
+                else
+                    pm_error("Border file has more images than image file.");
+            }
+        }
     }
 
     pm_close(stdout);
     pm_close(ifP);
+    if (bdfP)
+        pm_close(bdfP);
 
     return 0;
 }