about summary refs log tree commit diff
path: root/editor/pnmcrop.c
diff options
context:
space:
mode:
Diffstat (limited to 'editor/pnmcrop.c')
-rw-r--r--editor/pnmcrop.c787
1 files changed, 646 insertions, 141 deletions
diff --git a/editor/pnmcrop.c b/editor/pnmcrop.c
index c6aabff1..d6bae1d3 100644
--- a/editor/pnmcrop.c
+++ b/editor/pnmcrop.c
@@ -1,4 +1,4 @@
-/* pnmcrop.c - crop a portable anymap
+/* pnmcrop.c - crop a Netpbm image
 **
 ** Copyright (C) 1988 by Jef Poskanzer.
 **
@@ -29,10 +29,24 @@
 #include "pnm.h"
 #include "shhopt.h"
 #include "mallocvar.h"
+#include "nstring.h"
 
-enum bg_choice {BG_BLACK, BG_WHITE, BG_DEFAULT, BG_SIDES};
+static double const sqrt3 = 1.73205080756887729352;
+    /* The square root of 3 */
+static double const EPSILON = 1.0e-5;
 
-typedef enum { LEFT = 0, RIGHT = 1, TOP = 2, BOTTOM = 3} edgeLocation;
+enum BgChoice {BG_BLACK, BG_WHITE, BG_DEFAULT, BG_SIDES, BG_CORNER, BG_COLOR};
+
+enum BaseOp {OP_CROP, OP_REPORT_FULL, OP_REPORT_SIZE};
+
+enum BlankMode {BLANK_ABORT, BLANK_PASS, BLANK_MINIMIZE, BLANK_MAXCROP};
+
+typedef enum {LEFT = 0, RIGHT = 1, TOP = 2, BOTTOM = 3} EdgeLocation;
+
+typedef struct {
+    EdgeLocation v;
+    EdgeLocation h;
+} CornerLocation;
 
 static const char * const edgeName[] = {
     "left",
@@ -53,24 +67,32 @@ typedef enum {
         /* Immediately after the raster */
 } imageFilePos;
 
-struct cmdlineInfo {
+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;
+    enum BaseOp baseOperation;
+    enum BgChoice background;
     bool wantCrop[4];
         /* User wants crop of left, right, top, bottom, resp. */
-    unsigned int verbose;
     unsigned int margin;
     const char * borderfile;  /* NULL if none */
+    float closeness;
+    CornerLocation bgCorner;     /* valid if background == BG_CORNER */
+    const char * bgColor;  /* valid if background == BG_COLOR */
+        /* Note that we can have only the name of the color, not the color
+           itself, because we don't know the maxval at option parsing time.
+        */
+    enum BlankMode blankMode;
+    unsigned int verbose;
 };
 
 
 
 static void
 parseCommandLine(int argc, const char ** argv,
-                 struct cmdlineInfo *cmdlineP) {
+                 struct CmdlineInfo * const cmdlineP) {
 /*----------------------------------------------------------------------------
    Note that the file spec array we return is stored in the storage that
    was passed to us as the argv array.
@@ -81,26 +103,42 @@ parseCommandLine(int argc, const char ** argv,
     optStruct3 opt;
 
     unsigned int blackOpt, whiteOpt, sidesOpt;
-    unsigned int marginSpec, borderfileSpec;
+    unsigned int marginSpec, borderfileSpec, closenessSpec;
     unsigned int leftOpt, rightOpt, topOpt, bottomOpt;
-    
+    unsigned int bgCornerSpec, bgColorSpec;
+    unsigned int blankModeSpec;
+    unsigned int reportFullOpt, reportSizeOpt;
+
     unsigned int option_def_index;
 
+    char * bgCornerOpt;
+    char * blankModeOpOpt;
+
     MALLOCARRAY_NOFAIL(option_def, 100);
 
     option_def_index = 0;   /* incremented by OPTENT3 */
-    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, &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,    
+    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, "bg-color",    OPT_STRING, &cmdlineP->bgColor,
+            &bgColorSpec,   0);
+    OPTENT3(0, "bg-corner",   OPT_STRING, &bgCornerOpt,
+            &bgCornerSpec,  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, "margin",      OPT_UINT,   &cmdlineP->margin,
             &marginSpec,     0);
-    OPTENT3(0, "borderfile", OPT_STRING, &cmdlineP->borderfile,
+    OPTENT3(0, "borderfile",  OPT_STRING, &cmdlineP->borderfile,
             &borderfileSpec, 0);
+    OPTENT3(0, "closeness",   OPT_FLOAT,  &cmdlineP->closeness,
+            &closenessSpec,  0);
+    OPTENT3(0, "blank-image", OPT_STRING, &blankModeOpOpt,
+            &blankModeSpec,  0);
+    OPTENT3(0, "reportfull",  OPT_FLAG,   NULL, &reportFullOpt,       0);
+    OPTENT3(0, "reportsize",  OPT_FLAG,   NULL, &reportSizeOpt,       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 */
@@ -108,30 +146,73 @@ parseCommandLine(int argc, const char ** argv,
 
     pm_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 */
     else if (argc-1 == 1)
         cmdlineP->inputFilespec = argv[1];
-    else 
+    else
         pm_error("Too many arguments (%d).  "
                  "Only need one: the input filespec", argc-1);
 
-    if (blackOpt && whiteOpt)
-        pm_error("You cannot specify both -black and -white");
-    else if (sidesOpt &&( blackOpt || whiteOpt ))
-        pm_error("You cannot specify both -sides and either -black or -white");
+    /* Base operation */
+
+    if (reportFullOpt && reportSizeOpt)
+        pm_error("You cannot specify both -reportfull and -reportsize");
+
+    if ((reportFullOpt || reportSizeOpt) && borderfileSpec)
+        pm_error("You cannot specify -reportfull or -reportsize "
+                 "with -borderfile");
+
+    if (reportFullOpt)
+        cmdlineP->baseOperation = OP_REPORT_FULL;
+    else if (reportSizeOpt)
+        cmdlineP->baseOperation = OP_REPORT_SIZE;
+    else
+        cmdlineP->baseOperation = OP_CROP;
+
+    /* Background color */
+
+    if (blackOpt + whiteOpt + sidesOpt + bgColorSpec + bgCornerSpec > 1)
+        pm_error("You cannot specify more than one of "
+                 "-black, -white, -sides, -bg-color, -bg-corner");
     else if (blackOpt)
         cmdlineP->background = BG_BLACK;
     else if (whiteOpt)
         cmdlineP->background = BG_WHITE;
     else if (sidesOpt)
         cmdlineP->background = BG_SIDES;
+    else if (bgColorSpec)
+        cmdlineP->background = BG_COLOR;
+    else if (bgCornerSpec)
+        cmdlineP->background = BG_CORNER;
     else
         cmdlineP->background = BG_DEFAULT;
 
+    if (bgCornerSpec) {
+        if (false) {
+        } else if (streq(bgCornerOpt, "topleft")) {
+            cmdlineP->bgCorner.v = TOP;
+            cmdlineP->bgCorner.h = LEFT;
+        } else if (streq(bgCornerOpt, "topright")) {
+            cmdlineP->bgCorner.v = TOP;
+            cmdlineP->bgCorner.h = RIGHT;
+        } else if (streq(bgCornerOpt, "bottomleft")) {
+            cmdlineP->bgCorner.v = BOTTOM;
+            cmdlineP->bgCorner.h = LEFT;
+        } else if (streq(bgCornerOpt, "bottomright")) {
+            cmdlineP->bgCorner.v = BOTTOM;
+            cmdlineP->bgCorner.h = RIGHT;
+        } else
+            pm_error("Invalid value for -bg-corner."
+                     "Must be one of "
+                     "'topleft', 'topright', 'bottomleft', 'bottomright'");
+    }
+
+    /* Border specification */
+
     if (!leftOpt && !rightOpt && !topOpt && !bottomOpt) {
         unsigned int i;
         for (i = 0; i < 4; ++i)
@@ -142,11 +223,45 @@ parseCommandLine(int argc, const char ** argv,
         cmdlineP->wantCrop[TOP]    = !!topOpt;
         cmdlineP->wantCrop[BOTTOM] = !!bottomOpt;
     }
+
+    /* Blank image handling */
+
+    if (blankModeSpec) {
+        if (false) {
+        } else if (streq(blankModeOpOpt, "abort"))
+            cmdlineP->blankMode = BLANK_ABORT;
+        else if (streq(blankModeOpOpt,   "pass"))
+            cmdlineP->blankMode = BLANK_PASS;
+        else if (streq(blankModeOpOpt,   "minimize"))
+            cmdlineP->blankMode = BLANK_MINIMIZE;
+        else if (streq(blankModeOpOpt,   "maxcrop")) {
+            if (cmdlineP->baseOperation == OP_CROP)
+                pm_error("Option -blank-image=maxcrop requires "
+                         "-reportfull or -reportsize");
+            else
+                cmdlineP->blankMode = BLANK_MAXCROP;
+        } else
+            pm_error ("Invalid value for -blank-image");
+    } else
+        cmdlineP->blankMode = BLANK_ABORT; /* the default */
+
+    /* Other options */
+
     if (!marginSpec)
         cmdlineP->margin = 0;
 
     if (!borderfileSpec)
         cmdlineP->borderfile = NULL;
+
+    if (!closenessSpec)
+        cmdlineP->closeness = 0.0;
+
+    if (cmdlineP->closeness < 0.0)
+        pm_error("-closeness value %f is negative", cmdlineP->closeness);
+
+    if (cmdlineP->closeness > 100.0)
+        pm_error("-closeness value %f is more than 100%%",
+                 cmdlineP->closeness);
 }
 
 
@@ -163,21 +278,21 @@ typedef struct {
         /* Size in pixels of the border to remove */
     unsigned int padSize;
         /* Size in pixels of the border to add */
-} cropOp;
+} CropOp;
 
 
 typedef struct {
-    cropOp op[4];
-} cropSet;
+    CropOp op[4];
+} CropSet;
 
 
 
 static xel
-background3Corners(FILE * const ifP,
-                   int    const rows,
-                   int    const cols,
-                   pixval const maxval,
-                   int    const format) {
+background3Corners(FILE *       const ifP,
+                   unsigned int const rows,
+                   unsigned int const cols,
+                   pixval       const maxval,
+                   int          const format) {
 /*----------------------------------------------------------------------------
   Read in the whole image, and check all the corners to determine the
   background color.  This is a quite reliable way to determine the
@@ -186,14 +301,14 @@ background3Corners(FILE * const ifP,
   Expect the file to be positioned to the start of the raster, and leave
   it positioned arbitrarily.
 ----------------------------------------------------------------------------*/
-    int row;
+    unsigned int row;
     xel ** xels;
     xel background;   /* our return value */
 
     xels = pnm_allocarray(cols, rows);
 
     for (row = 0; row < rows; ++row)
-        pnm_readpnmrow( ifP, xels[row], cols, maxval, format );
+        pnm_readpnmrow(ifP, xels[row], cols, maxval, format);
 
     background = pnm_backgroundxel(xels, cols, rows, maxval, format);
 
@@ -205,10 +320,10 @@ background3Corners(FILE * const ifP,
 
 
 static xel
-background2Corners(FILE * const ifP,
-                   int    const cols,
-                   pixval const maxval,
-                   int    const format) {
+background2Corners(FILE *       const ifP,
+                   unsigned int const cols,
+                   pixval       const maxval,
+                   int          const format) {
 /*----------------------------------------------------------------------------
   Look at just the top row of pixels and determine the background
   color from the top corners; often this is enough to accurately
@@ -217,9 +332,9 @@ background2Corners(FILE * const ifP,
   Expect the file to be positioned to the start of the raster, and leave
   it positioned arbitrarily.
 ----------------------------------------------------------------------------*/
-    xel *xelrow;
+    xel * xelrow;
     xel background;   /* our return value */
-    
+
     xelrow = pnm_allocrow(cols);
 
     pnm_readpnmrow(ifP, xelrow, cols, maxval, format);
@@ -234,24 +349,118 @@ background2Corners(FILE * const ifP,
 
 
 static xel
-computeBackground(FILE *         const ifP,
-                  int            const cols,
-                  int            const rows,
-                  xelval         const maxval,
+background1Corner(FILE *         const ifP,
+                  unsigned int   const rows,
+                  unsigned int   const cols,
+                  pixval         const maxval,
                   int            const format,
-                  enum bg_choice const backgroundChoice) {
+                  CornerLocation const corner) {
+/*----------------------------------------------------------------------------
+  Let the pixel in corner 'corner' be the background.
+
+  Expect the file to be positioned to the start of the raster, and leave
+  it positioned arbitrarily.
+----------------------------------------------------------------------------*/
+    xel * xelrow;
+    xel background;   /* our return value */
+
+    xelrow = pnm_allocrow(cols);
+
+    if (corner.v == BOTTOM) {
+        /* read and discard all but bottom row */
+        unsigned int row;
+
+        for (row = 0; row < rows - 1; ++row)
+            pnm_readpnmrow(ifP, xelrow, cols, maxval, format);
+    }
+    pnm_readpnmrow(ifP, xelrow, cols, maxval, format);
+
+    background = corner.h == LEFT ? xelrow[0] : xelrow[cols - 1];
+
+    pnm_freerow(xelrow);
+
+    return background;
+}
+
+
+
+static xel
+backgroundColorFmName(const char * const colorName,
+                      xelval       const maxval,
+                      int          const format) {
+/*----------------------------------------------------------------------------
+   The color indicated by 'colorName'.
+
+   The return value is based on maxval 'maxval' and format 'format'.
+
+   Abort the program if 'colorName' names a color that cannot be represented
+   in format 'format'.
+
+   Development note: It would be logical to relax the above restriction when
+   -closeness is specified.  Implementation is harder than it seems because of
+   the -margin option.  It is unlikely that there is demand for this feature.
+   If really necessary, the user can convert the input image to PPM.
+-----------------------------------------------------------------------------*/
+    pixel const backgroundColor    =
+        ppm_parsecolor(colorName, maxval);
+
+    pixel const backgroundColorMax =
+        ppm_parsecolor(colorName, PNM_MAXMAXVAL);
+
+    bool const hasColor =
+        !(backgroundColorMax.r == backgroundColorMax.g &&
+          backgroundColorMax.r == backgroundColorMax.b);
+
+    bool const hasGray  =
+        !hasColor &&
+        (backgroundColorMax.r != PNM_MAXMAXVAL &&
+         backgroundColorMax.r !=  0 );
+
+    xel backgroundXel;
+
+    if (PPM_FORMAT_TYPE(format)) {
+        backgroundXel = pnm_pixeltoxel(backgroundColor);
+    } else {
+        /* Derive PBM or PGM xel from pixel 'backgroundColor' */
+        if (hasColor)
+            pm_error("Invalid color specified: '%s'. "
+                     "Image does not have color.", colorName);
+        else {
+            if (PBM_FORMAT_TYPE(format) == PBM_TYPE && hasGray)
+                pm_error("Invalid color specified: '%s'. "
+                         "Image has no intermediate levels of gray.",
+                         colorName);
+            else
+                PNM_ASSIGN1(backgroundXel, backgroundColor.r);
+        }
+    }
+
+    return backgroundXel;
+}
+
+
+
+static xel
+backgroundColor(FILE *         const ifP,
+                int            const cols,
+                int            const rows,
+                xelval         const maxval,
+                int            const format,
+                enum BgChoice  const backgroundChoice,
+                CornerLocation const corner,
+                const char   * const colorName) {
 /*----------------------------------------------------------------------------
    Determine what color is the background color of the image in file
    *ifP, which is described by 'cols', 'rows', 'maxval', and 'format'.
 
    'backgroundChoice' is the method we are to use in determining the
    background color.
-   
+
    Expect the file to be positioned to the start of the raster, and leave
    it positioned arbitrarily.
 -----------------------------------------------------------------------------*/
     xel background;  /* Our return value */
-    
+
     switch (backgroundChoice) {
     case BG_WHITE:
         background = pnm_whitexel(maxval, format);
@@ -259,17 +468,45 @@ computeBackground(FILE *         const ifP,
     case BG_BLACK:
         background = pnm_blackxel(maxval, format);
         break;
-    case BG_SIDES: 
-        background = 
+    case BG_COLOR:
+        background =
+            backgroundColorFmName(colorName, maxval, format);
+        break;
+    case BG_SIDES:
+        background =
             background3Corners(ifP, rows, cols, maxval, format);
         break;
-    case BG_DEFAULT: 
-        background = 
+    case BG_DEFAULT:
+        background =
             background2Corners(ifP, cols, maxval, format);
         break;
+    case BG_CORNER:
+        background =
+            background1Corner(ifP, rows, cols, maxval, format, corner);
+        break;
+
+    default:
+        pm_error("internal error");
     }
 
-    return(background);
+    return background;
+}
+
+
+
+static bool
+colorMatches(pixel        const comparand,
+             pixel        const comparator,
+             unsigned int const allowableDiff) {
+/*----------------------------------------------------------------------------
+   The colors 'comparand' and 'comparator' are within 'allowableDiff'
+   color levels of each other, in cartesian distance.
+-----------------------------------------------------------------------------*/
+    /* Fast path for usual case */
+    if (allowableDiff < EPSILON)
+        return PPM_EQUAL(comparand, comparator);
+
+    return PPM_DISTANCE(comparand, comparator) <= SQR(allowableDiff);
 }
 
 
@@ -281,62 +518,66 @@ findBordersInImage(FILE *         const ifP,
                    xelval         const maxval,
                    int            const format,
                    xel            const backgroundColor,
+                   double         const closeness,
                    bool *         const hasBordersP,
                    borderSet *    const borderSizeP) {
 /*----------------------------------------------------------------------------
    Find the left, right, top, and bottom borders in the image 'ifP'.
    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.
 -----------------------------------------------------------------------------*/
+    unsigned int const allowableDiff = ROUNDU(sqrt3 * maxval * closeness/100);
+
     xel * xelrow;        /* A row of the input image */
     int row;
-    bool gottop;
+    bool gotTop;
     int left, right, bottom, top;
         /* 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 */
 
-    gottop = FALSE;
-    for (row = 0; row < rows; ++row) {
+    for (row = 0, gotTop = false; row < rows; ++row) {
         int col;
         int thisRowLeft;
         int thisRowRight;
 
         pnm_readpnmrow(ifP, xelrow, cols, maxval, format);
-        
+
         col = 0;
-        while (col < cols && PNM_EQUAL(xelrow[col], backgroundColor))
+        while (col < cols &&
+               colorMatches(xelrow[col], backgroundColor, allowableDiff))
             ++col;
         thisRowLeft = col;
 
         col = cols-1;
-        while (col >= thisRowLeft && PNM_EQUAL(xelrow[col], backgroundColor))
+        while (col >= thisRowLeft &&
+               colorMatches(xelrow[col], backgroundColor, allowableDiff))
             --col;
         thisRowRight = col + 1;
 
         if (thisRowLeft < cols) {
             /* This row is not entirely background */
-            
+
             left  = MIN(thisRowLeft,  left);
             right = MAX(thisRowRight, right);
 
-            if (!gottop) {
-                gottop = TRUE;
+            if (!gotTop) {
                 top = row;
+                gotTop = true;
             }
             bottom = row + 1;   /* New candidate */
         }
     }
-    
+
     free(xelrow);
 
     if (right == -1)
@@ -359,7 +600,10 @@ analyzeImage(FILE *         const ifP,
              unsigned int   const rows,
              xelval         const maxval,
              int            const format,
-             enum bg_choice const backgroundReq,
+             enum BgChoice  const backgroundReq,
+             double         const closeness,
+             CornerLocation const corner,
+             const char   * const colorName,
              imageFilePos   const newFilePos,
              xel *          const backgroundColorP,
              bool *         const hasBordersP,
@@ -383,13 +627,13 @@ analyzeImage(FILE *         const ifP,
 
     pm_tell2(ifP, &rasterpos, sizeof(rasterpos));
 
-    background = computeBackground(ifP, cols, rows, maxval, format,
-                                   backgroundReq);
+    background = backgroundColor(ifP, cols, rows, maxval, format,
+                                 backgroundReq, corner, colorName);
 
     pm_seek2(ifP, &rasterpos, sizeof(rasterpos));
 
-    findBordersInImage(ifP, cols, rows, maxval, format, 
-                       background, hasBordersP, borderSizeP);
+    findBordersInImage(ifP, cols, rows, maxval, format,
+                       background, closeness, hasBordersP, borderSizeP);
 
     if (newFilePos == FILEPOS_BEG)
         pm_seek2(ifP, &rasterpos, sizeof(rasterpos));
@@ -408,7 +652,7 @@ ending(unsigned int const n) {
 
 
 static void
-reportCroppingParameters(cropSet const crop) {
+reportCroppingParameters(CropSet const crop) {
 
     unsigned int i;
 
@@ -431,6 +675,85 @@ reportCroppingParameters(cropSet const crop) {
 
 
 
+static void
+reportDimensions(CropSet      const crop,
+                 unsigned int const cols,
+                 unsigned int const rows) {
+
+    unsigned int i;
+
+    for (i = 0; i < ARRAY_SIZE(crop.op); ++i) {
+        if (crop.op[i].removeSize > 0 && crop.op[i].padSize > 0)
+            pm_error("Attempt to add %u and crop %u on %s edge.  "
+                     "Simultaneous pad and crop is not allowed",
+                     crop.op[i].padSize, crop.op[i].removeSize, edgeName[i]);
+        else if (crop.op[i].removeSize > 0)   /* crop */
+            printf ("-%u ", crop.op[i].removeSize);
+        else if (crop.op[i].removeSize == 0) {
+            if (crop.op[i].padSize == 0)      /* no operation */
+                printf ("0 ");
+            else                              /* pad */
+                printf ("+%u ", crop.op[i].padSize);
+        }
+    }
+
+    {
+        unsigned int outputCols, outputRows;
+
+        if (crop.op[LEFT ].removeSize == cols ||
+            crop.op[RIGHT].removeSize == cols)
+            outputCols = cols;
+        else {
+            outputCols =
+                cols - crop.op[LEFT].removeSize - crop.op[RIGHT].removeSize +
+                crop.op[LEFT].padSize + crop.op[RIGHT].padSize;
+        }
+
+        if (crop.op[TOP   ].removeSize == rows ||
+            crop.op[BOTTOM].removeSize == rows)
+            outputRows = rows;
+        else
+            outputRows =
+                rows - crop.op[TOP].removeSize - crop.op[BOTTOM].removeSize +
+                crop.op[TOP].padSize + crop.op[BOTTOM].padSize;
+
+        printf("%u %u", outputCols, outputRows);
+    }
+}
+
+
+static void
+reportSize(CropSet      const crop,
+           unsigned int const cols,
+           unsigned int const rows) {
+
+    reportDimensions(crop, cols, rows);
+
+    putchar('\n');
+
+}
+
+
+
+static void
+reportFull(CropSet      const crop,
+           unsigned int const cols,
+           unsigned int const rows,
+           int          const format,
+           xelval       const maxval,
+           xel          const bgColor,
+           float        const closeness) {
+
+    pixel const backgroundPixel = pnm_xeltopixel(bgColor, format);
+
+    reportDimensions(crop, cols, rows);
+
+    printf(" rgb-%u:%u/%u/%u %f\n", maxval,
+           backgroundPixel.r, backgroundPixel.g, backgroundPixel.b,
+           closeness);
+}
+
+
 
 static void
 fillRow(xel *        const xelrow,
@@ -485,7 +808,7 @@ outputNewBorderNonPbm(unsigned int const height,
 
     for (i = 0; i < height; ++i)
         pnm_writepnmrow(ofP, xelrow, width, maxval, format, 0);
-    
+
     pnm_freerow(xelrow);
 }
 
@@ -497,7 +820,7 @@ writeCroppedNonPbm(FILE *       const ifP,
                    unsigned int const rows,
                    xelval       const maxval,
                    int          const format,
-                   cropSet      const crop,
+                   CropSet      const crop,
                    xel          const backgroundColor,
                    FILE *       const ofP) {
 
@@ -529,7 +852,7 @@ writeCroppedNonPbm(FILE *       const ifP,
        the buffer that lines up the first foreground pixel at
        'foregroundLeft'.
 
-       When we output the row, we pick a starting location in the 
+       When we output the row, we pick a starting location in the
        buffer that includes the proper number of left border pixels
        before 'foregroundLeft'.
 
@@ -542,7 +865,7 @@ writeCroppedNonPbm(FILE *       const ifP,
 
     unsigned int const foregroundCols =
         cols - crop.op[LEFT].removeSize - crop.op[RIGHT].removeSize;
-    unsigned int const outputCols     = 
+    unsigned int const outputCols     =
         foregroundCols + crop.op[LEFT].padSize + crop.op[RIGHT].padSize;
     unsigned int const foregroundRows =
         rows - crop.op[TOP].removeSize - crop.op[BOTTOM].removeSize;
@@ -581,19 +904,19 @@ writeCroppedNonPbm(FILE *       const ifP,
 
     /* Read and output foreground rows */
     for (i = 0; i < foregroundRows; ++i) {
- 
+
         /* Read foreground pixels */
         pnm_readpnmrow(ifP,
                        &(xelrow[foregroundLeft - crop.op[LEFT].removeSize]),
                        cols, maxval, format);
-        
+
         pnm_writepnmrow(ofP,
                         &(xelrow[foregroundLeft - crop.op[LEFT].padSize]),
                         outputCols, maxval, format, 0);
     }
 
     readOffBorderNonPbm(crop.op[BOTTOM].removeSize, ifP, cols, maxval, format);
-    
+
     outputNewBorderNonPbm(crop.op[BOTTOM].padSize, outputCols,
                           backgroundColor,
                           ofP, maxval, format);
@@ -616,10 +939,10 @@ fillRowPBM(unsigned char * const bitrow,
     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;
 }
@@ -664,7 +987,7 @@ outputNewBorderPbm(unsigned int const height,
 
     for (i = 0; i < height; ++i)
         pbm_writepbmrow_packed(ofP, bitrow, width, 0);
-    
+
     pbm_freerow_packed(bitrow);
 }
 
@@ -675,17 +998,17 @@ writeCroppedPBM(FILE *       const ifP,
                 unsigned int const cols,
                 unsigned int const rows,
                 int          const format,
-                cropSet      const crop,
+                CropSet      const crop,
                 xel          const backgroundColor,
                 FILE *       const ofP) {
-    
-    /* See comments for writeCroppedNonPBM(), which uses identical logic flow. 
+
+    /* See comments for writeCroppedNonPBM(), which uses identical logic flow.
        Uses pbm functions instead of general pnm functions.
     */
 
     unsigned int const foregroundCols =
         cols - crop.op[LEFT].removeSize - crop.op[RIGHT].removeSize;
-    unsigned int const outputCols     = 
+    unsigned int const outputCols     =
         foregroundCols + crop.op[LEFT].padSize + crop.op[RIGHT].padSize;
     unsigned int const foregroundRows =
         rows - crop.op[TOP].removeSize - crop.op[BOTTOM].removeSize;
@@ -697,7 +1020,7 @@ writeCroppedPBM(FILE *       const ifP,
     unsigned int const foregroundRight = foregroundLeft + foregroundCols;
 
     unsigned int const allocCols =
-        foregroundRight + 
+        foregroundRight +
         MAX(crop.op[RIGHT].removeSize, crop.op[RIGHT].padSize);
 
     unsigned int const backgroundBlackWhite =
@@ -709,7 +1032,7 @@ writeCroppedPBM(FILE *       const ifP,
     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);
@@ -726,15 +1049,15 @@ writeCroppedPBM(FILE *       const ifP,
     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() 
+           distorted by pbm_writepbmrow_bitoffset()
            (No need to mend any left-side padding)
         */
-        if (crop.op[RIGHT].padSize > 0)    
+        if (crop.op[RIGHT].padSize > 0)
             bitrow[lastWriteChar] = backgroundBlackWhite * 0xff;
     }
 
@@ -749,29 +1072,168 @@ writeCroppedPBM(FILE *       const ifP,
 
 
 
-static void
-determineCrops(struct cmdlineInfo const cmdline,
-               borderSet *        const oldBorderSizeP,
-               cropSet *          const cropP) {
+static CropSet
+crops(struct CmdlineInfo const cmdline,
+      borderSet          const oldBorderSize) {
 
-    edgeLocation i;
+    CropSet retval;
 
-    for (i = 0; i < 4; ++i) {
+    EdgeLocation i;
+
+    for (i = 0; i < ARRAY_SIZE(retval.op); ++i) {
         if (cmdline.wantCrop[i]) {
-            if (oldBorderSizeP->size[i] > cmdline.margin) {
-                cropP->op[i].removeSize =
-                    oldBorderSizeP->size[i] - cmdline.margin;
-                cropP->op[i].padSize    = 0;
+            if (oldBorderSize.size[i] > cmdline.margin) {
+                retval.op[i].removeSize =
+                    oldBorderSize.size[i] - cmdline.margin;
+                retval.op[i].padSize    = 0;
             } else {
-                cropP->op[i].removeSize = 0;
-                cropP->op[i].padSize    =
-                    cmdline.margin - oldBorderSizeP->size[i];
+                retval.op[i].removeSize = 0;
+                retval.op[i].padSize    =
+                    cmdline.margin - oldBorderSize.size[i];
             }
         } else {
-            cropP->op[i].removeSize = 0;
-            cropP->op[i].padSize    = 0;
+            retval.op[i].removeSize = 0;
+            retval.op[i].padSize    = 0;
         }
     }
+    return retval;
+}
+
+
+
+static CropSet
+noCrops(struct CmdlineInfo const cmdline) {
+
+    CropSet retval;
+
+    EdgeLocation i;
+
+    if (cmdline.verbose)
+        pm_message("The image is entirely background; "
+                   "there is nothing to crop.  Copying to output.");
+
+    if (cmdline.margin > 0)
+        pm_message ("-margin value %u ignored", cmdline.margin);
+
+    for (i = 0; i < 4; ++i) {
+        retval.op[i].removeSize = 0;
+        retval.op[i].padSize    = 0;
+    }
+    return retval;
+}
+
+
+
+static CropSet
+extremeCrops(struct CmdlineInfo const cmdline,
+             unsigned int       const cols,
+             unsigned int       const rows) {
+/*----------------------------------------------------------------------------
+   Crops that crop as much as possible, reducing output to a single pixel.
+-----------------------------------------------------------------------------*/
+    CropSet retval;
+
+    if (cmdline.verbose)
+        pm_message("Input image has no distinction between "
+                   "border and content");
+
+    /* We can't just pick a representive pixel, say top-left corner.
+       If -top and/or -bottom was specified but not -left and -right,
+       the output should be one row, not a single pixel.
+
+       The "entirely background" image may have several colors: this
+       happens when -closeness was specified.
+    */
+
+    if (cmdline.wantCrop[LEFT] && cmdline.wantCrop[RIGHT]) {
+        retval.op[LEFT ].removeSize = cols / 2;
+        retval.op[RIGHT].removeSize = cols - retval.op[LEFT].removeSize -1;
+    } else if (cmdline.wantCrop[LEFT]) {
+        retval.op[LEFT ].removeSize = cols - 1;
+        retval.op[RIGHT].removeSize = 0;
+    } else if (cmdline.wantCrop[RIGHT]) {
+        retval.op[LEFT ].removeSize = 0;
+        retval.op[RIGHT].removeSize = cols - 1;
+    } else {
+        retval.op[LEFT ].removeSize = 0;
+        retval.op[RIGHT].removeSize = 0;
+    }
+
+    if (cmdline.wantCrop[TOP] && cmdline.wantCrop[BOTTOM]) {
+        retval.op[ TOP  ].removeSize = rows / 2;
+        retval.op[BOTTOM].removeSize = rows - retval.op[TOP].removeSize -1;
+    } else if (cmdline.wantCrop[TOP]) {
+        retval.op[ TOP  ].removeSize = rows - 1;
+        retval.op[BOTTOM].removeSize = 0;
+    } else if (cmdline.wantCrop[BOTTOM]) {
+        retval.op[ TOP  ].removeSize = 0;
+        retval.op[BOTTOM].removeSize = rows - 1;
+    } else {
+        retval.op[ TOP  ].removeSize = 0;
+        retval.op[BOTTOM].removeSize = 0;
+    }
+
+    if (cmdline.margin > 0)
+        pm_message ("-margin value %u ignored", cmdline.margin);
+
+    {
+        EdgeLocation i;
+        for (i = 0; i < ARRAY_SIZE(retval.op); ++i)
+            retval.op[i].padSize = 0;
+    }
+    return retval;
+}
+
+
+
+static CropSet
+maxcropReport(struct CmdlineInfo const cmdline,
+              unsigned int       const cols,
+              unsigned int       const rows) {
+/*----------------------------------------------------------------------------
+   Report maximum possible crop extents.
+-----------------------------------------------------------------------------*/
+    CropSet retval;
+
+    if (cmdline.wantCrop[LEFT] && cmdline.wantCrop[RIGHT]) {
+        retval.op[LEFT ].removeSize = cols;
+        retval.op[RIGHT].removeSize = cols;
+    } else if (cmdline.wantCrop[LEFT]) {
+        retval.op[LEFT ].removeSize = cols;
+        retval.op[RIGHT].removeSize = 0;
+    } else if (cmdline.wantCrop[RIGHT]) {
+        retval.op[LEFT ].removeSize = 0;
+        retval.op[RIGHT].removeSize = cols;
+    } else {
+        retval.op[LEFT ].removeSize = 0;
+        retval.op[RIGHT].removeSize = 0;
+    }
+
+    if (cmdline.wantCrop[TOP] && cmdline.wantCrop[BOTTOM]) {
+        retval.op[ TOP  ].removeSize = rows;
+        retval.op[BOTTOM].removeSize = rows;
+    } else if (cmdline.wantCrop[TOP]) {
+        retval.op[ TOP  ].removeSize = rows;
+        retval.op[BOTTOM].removeSize = 0;
+    } else if (cmdline.wantCrop[BOTTOM]) {
+        retval.op[ TOP  ].removeSize = 0;
+        retval.op[BOTTOM].removeSize = rows;
+    } else {
+        retval.op[ TOP  ].removeSize = 0;
+        retval.op[BOTTOM].removeSize = 0;
+    }
+
+
+    if (cmdline.margin > 0)
+        pm_message("-margin value %u ignored", cmdline.margin);
+
+    {
+        EdgeLocation i;
+
+        for (i = 0; i < ARRAY_SIZE(retval.op); ++i)
+            retval.op[i].padSize = 0;
+    }
+    return retval;
 }
 
 
@@ -779,7 +1241,7 @@ determineCrops(struct cmdlineInfo const cmdline,
 static void
 validateComputableSize(unsigned int const cols,
                        unsigned int const rows,
-                       cropSet      const crop) {
+                       CropSet      const crop) {
 
     double const newcols =
         (double)cols +
@@ -798,7 +1260,7 @@ validateComputableSize(unsigned int const cols,
 
 
 static void
-cropOneImage(struct cmdlineInfo const cmdline,
+cropOneImage(struct CmdlineInfo const cmdline,
              FILE *             const ifP,
              FILE *             const bdfP,
              FILE *             const ofP) {
@@ -811,51 +1273,95 @@ cropOneImage(struct cmdlineInfo const cmdline,
 
    Both files are seekable.
 -----------------------------------------------------------------------------*/
-    xelval maxval, bmaxval;
-    int format, bformat;
-    int rows, cols, brows, bcols;
+    int rows, cols, format;
+    xelval maxval;      /* The input file image */
+
+    int brows, bcols, bformat;
+    xelval bmaxval;     /* The separate border file, if specified */
+
+    FILE * afP;
+    int arows, acols, aformat;
+    xelval amaxval;
+    /* The file we use for analysis, either the input file or border file */
+
     bool hasBorders;
     borderSet oldBorder;
         /* The sizes of the borders in the input image */
-    cropSet crop;
+    CropSet crop;
         /* The crops we have to do on each side */
     xel background;
 
     pnm_readpnminit(ifP, &cols, &rows, &maxval, &format);
 
-    if (bdfP)
+    if (bdfP) {
         pnm_readpnminit(bdfP, &bcols, &brows, &bmaxval, &bformat);
 
-    if (bdfP)
-        analyzeImage(bdfP, bcols, brows, bmaxval, bformat, cmdline.background,
-                     FILEPOS_END,
-                     &background, &hasBorders, &oldBorder);
-    else
-        analyzeImage(ifP, cols, rows, maxval, format, cmdline.background,
-                     FILEPOS_BEG,
-                     &background, &hasBorders, &oldBorder);
+        if (cols != bcols || rows != brows)
+            pm_error("Input file image [%u x %u] and border file image "
+                     "[%u x %u] differ in size", cols, rows, bcols, brows);
+        else {
+            afP = bdfP;
+            acols = bcols; arows = brows;
+            amaxval = maxval;
+            aformat = bformat;
+        }
+    } else {
+        afP = ifP;
+        acols = cols; arows = rows;
+        amaxval = maxval;
+        aformat = format;
+    }
+
+    analyzeImage(afP, acols, arows, amaxval, aformat,
+                 cmdline.background, cmdline.closeness,
+                 cmdline.bgCorner, cmdline.bgColor,
+                 (bdfP || cmdline.baseOperation != OP_CROP) ?
+                     FILEPOS_END : FILEPOS_BEG,
+                 &background, &hasBorders, &oldBorder);
 
     if (cmdline.verbose) {
         pixel const backgroundPixel = pnm_xeltopixel(background, format);
-        pm_message("Background color is %s", 
+        pm_message("Background color is %s",
                    ppm_colorname(&backgroundPixel, maxval, TRUE /*hexok*/));
     }
-    if (!hasBorders)
-        pm_error("The image is entirely background; "
-                 "there is nothing to crop.");
-
-    determineCrops(cmdline, &oldBorder, &crop);
+    if (!hasBorders) {
+        switch (cmdline.blankMode) {
+        case BLANK_ABORT:
+            pm_error("The image is entirely background; "
+                     "there is nothing to crop.");
+            break;
+        case BLANK_PASS:
+            crop = noCrops(cmdline);                   break;
+        case BLANK_MINIMIZE:
+            crop = extremeCrops(cmdline, cols, rows);  break;
+        case BLANK_MAXCROP:
+            crop = maxcropReport(cmdline, cols, rows); break;
+        }
+    } else {
+        crop = crops(cmdline, oldBorder);
 
-    validateComputableSize(cols, rows, crop);
+        validateComputableSize(cols, rows, crop);
 
-    if (cmdline.verbose) 
-        reportCroppingParameters(crop);
+        if (cmdline.verbose)
+            reportCroppingParameters(crop);
+    }
 
-    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);
+    switch (cmdline.baseOperation) {
+    case OP_CROP:
+        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);
+        break;
+    case OP_REPORT_FULL:
+        reportFull(crop, cols, rows,
+                   aformat, amaxval, background, cmdline.closeness);
+        break;
+    case OP_REPORT_SIZE:
+        reportSize(crop, cols, rows);
+        break;
+    }
 }
 
 
@@ -863,8 +1369,8 @@ cropOneImage(struct cmdlineInfo const cmdline,
 int
 main(int argc, const char *argv[]) {
 
-    struct cmdlineInfo cmdline;
-    FILE * ifP;   
+    struct CmdlineInfo cmdline;
+    FILE * ifP;
         /* The program's regular input file.  Could be a seekable copy of
            it in a temporary file.
         */
@@ -884,18 +1390,17 @@ main(int argc, const char *argv[]) {
     else
         bdfP = NULL;
 
-    eof = beof = FALSE;
-    while (!eof) {
+    for (eof = beof = FALSE; !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."); 
+                    pm_error("Input file has more images than border file.");
                 else
                     pm_error("Border file has more images than image file.");
             }