diff options
Diffstat (limited to 'editor/pnmcrop.c')
-rw-r--r-- | editor/pnmcrop.c | 787 |
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."); } |