diff options
Diffstat (limited to 'editor/pamcut.c')
-rw-r--r-- | editor/pamcut.c | 480 |
1 files changed, 299 insertions, 181 deletions
diff --git a/editor/pamcut.c b/editor/pamcut.c index 7c41af38..db5b5b3d 100644 --- a/editor/pamcut.c +++ b/editor/pamcut.c @@ -1,9 +1,9 @@ -/*============================================================================ +/*============================================================================ pamcut ============================================================================== Cut a rectangle out of a Netpbm image - This is inspired by and intended as a replacement for Pnmcut by + This is inspired by and intended as a replacement for Pnmcut by Jef Poskanzer, 1989. By Bryan Henderson, San Jose CA. Contributed to the public domain @@ -24,27 +24,55 @@ but we hope not. */ +typedef struct { +/*---------------------------------------------------------------------------- + A location in one dimension (row or column) in the image. +-----------------------------------------------------------------------------*/ + enum { LOCTYPE_NONE, LOCTYPE_FROMNEAR, LOCTYPE_FROMFAR } locType; + + unsigned int n; + /* Row or column count. + + If LOCTYPE_NONE: Meaningless + + If LOCTYPE_FROMFAR: Number of colums from the far edge of the image + (right or bottom). Last column/row is 1. + + If LOCTYPE_FROMNEAR: Number of colums from the near edge of the + image (left or top). First column/row is 0. + */ +} Location; + + + struct CmdlineInfo { /* All the information the user supplied in the command line, in a form easy for the program to use. */ const char * inputFileName; /* File name of input file */ - /* The following describe the rectangle the user wants to cut out. + /* The following describe the rectangle the user wants to cut out. the value UNSPEC for any of them indicates that value was not specified. A negative value means relative to the far edge. - 'width' and 'height' are not negative. These specifications + 'width' and 'height' are not negative. These specifications do not necessarily describe a valid rectangle; they are just what the user said. - */ - int left; - int right; - int top; - int bottom; - int width; - int height; - unsigned int pad; + These do not follow the Netpbm convention of having members of this + structure that are identical to the name of an option class being + the value of that option. 'left', for example, is not the value of + the -left option; it could reflect the value of a -cropleft option + instead. + */ + Location leftLoc; + Location rghtLoc; + Location topLoc; + Location botLoc; + unsigned int widthSpec; + unsigned int width; + unsigned int heightSpec; + unsigned int height; + unsigned int pad; unsigned int verbose; }; @@ -63,26 +91,34 @@ parseCommandLine(int argc, const char ** const argv, optStruct3 opt; unsigned int option_def_index; + int left, right, top, bottom; + unsigned int cropleftSpec, croprightSpec, croptopSpec, cropbottomSpec; + unsigned int cropleft, cropright, croptop, cropbottom; + unsigned int leftSpec, rightSpec, topSpec, bottomSpec; + + bool haveLegacyLocationArgs; + /* The user specified location with top, left, height, and width + arguments like in original Pnmcut instead of with named options. + */ + MALLOCARRAY_NOFAIL(option_def, 100); option_def_index = 0; /* incremented by OPTENT3 */ - OPTENT3(0, "left", OPT_INT, &cmdlineP->left, NULL, 0); - OPTENT3(0, "right", OPT_INT, &cmdlineP->right, NULL, 0); - OPTENT3(0, "top", OPT_INT, &cmdlineP->top, NULL, 0); - OPTENT3(0, "bottom", OPT_INT, &cmdlineP->bottom, NULL, 0); - OPTENT3(0, "width", OPT_INT, &cmdlineP->width, NULL, 0); - OPTENT3(0, "height", OPT_INT, &cmdlineP->height, NULL, 0); + OPTENT3(0, "left", OPT_INT, &left, &leftSpec, 0); + OPTENT3(0, "right", OPT_INT, &right, &rightSpec, 0); + OPTENT3(0, "top", OPT_INT, &top, &topSpec, 0); + OPTENT3(0, "bottom", OPT_INT, &bottom, &bottomSpec, 0); + OPTENT3(0, "cropleft", OPT_UINT, &cropleft, &cropleftSpec, 0); + OPTENT3(0, "cropright", OPT_UINT, &cropright, &croprightSpec, 0); + OPTENT3(0, "croptop", OPT_UINT, &croptop, &croptopSpec, 0); + OPTENT3(0, "cropbottom", OPT_UINT, &cropbottom, &cropbottomSpec, 0); + OPTENT3(0, "width", OPT_UINT, &cmdlineP->width, + &cmdlineP->widthSpec, 0); + OPTENT3(0, "height", OPT_UINT, &cmdlineP->height, + &cmdlineP->heightSpec, 0); OPTENT3(0, "pad", OPT_FLAG, NULL, &cmdlineP->pad, 0); OPTENT3(0, "verbose", OPT_FLAG, NULL, &cmdlineP->verbose, 0); - /* Set the defaults */ - cmdlineP->left = UNSPEC; - cmdlineP->right = UNSPEC; - cmdlineP->top = UNSPEC; - cmdlineP->bottom = UNSPEC; - cmdlineP->width = UNSPEC; - cmdlineP->height = UNSPEC; - opt.opt_table = option_def; opt.short_allowed = FALSE; /* We have no short (old-fashioned) options */ opt.allowNegNum = TRUE; /* We may have parms that are negative numbers */ @@ -90,10 +126,10 @@ parseCommandLine(int argc, const char ** const argv, pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0); /* Uses and sets argc, argv, and some of *cmdlineP and others. */ - if (cmdlineP->width < 0) - pm_error("-width may not be negative."); - if (cmdlineP->height < 0) - pm_error("-height may not be negative."); + if (cmdlineP->widthSpec && cmdlineP->width == 0) + pm_error("-width may not be zero."); + if (cmdlineP->heightSpec && cmdlineP->height == 0) + pm_error("-height may not be zero."); if ((argc-1) != 0 && (argc-1) != 1 && (argc-1) != 4 && (argc-1) != 5) pm_error("Wrong number of arguments: %u. The only argument in " @@ -104,170 +140,250 @@ parseCommandLine(int argc, const char ** const argv, switch (argc-1) { case 0: cmdlineP->inputFileName = "-"; + haveLegacyLocationArgs = false; break; case 1: cmdlineP->inputFileName = argv[1]; + haveLegacyLocationArgs = false; break; case 4: - case 5: { - int warg, harg; /* The "width" and "height" command line arguments */ + cmdlineP->inputFileName = "-"; + haveLegacyLocationArgs = true; + break; + case 5: + cmdlineP->inputFileName = argv[5]; + haveLegacyLocationArgs = true; + break; + } - if (sscanf(argv[1], "%d", &cmdlineP->left) != 1) + if (haveLegacyLocationArgs) { + int leftArg, topArg, widthArg, heightArg; + + if (sscanf(argv[1], "%d", &leftArg) != 1) pm_error("Invalid number for left column argument"); - if (sscanf(argv[2], "%d", &cmdlineP->top) != 1) + if (sscanf(argv[2], "%d", &topArg) != 1) pm_error("Invalid number for right column argument"); - if (sscanf(argv[3], "%d", &warg) != 1) + if (sscanf(argv[3], "%d", &widthArg) != 1) pm_error("Invalid number for width argument"); - if (sscanf(argv[4], "%d", &harg) != 1) + if (sscanf(argv[4], "%d", &heightArg) != 1) pm_error("Invalid number for height argument"); - if (warg > 0) { - cmdlineP->width = warg; - cmdlineP->right = UNSPEC; + if (leftArg < 0) { + cmdlineP->leftLoc.locType = LOCTYPE_FROMFAR; + cmdlineP->leftLoc.n = -leftArg; } else { - cmdlineP->width = UNSPEC; - cmdlineP->right = warg -1; + cmdlineP->leftLoc.locType = LOCTYPE_FROMNEAR; + cmdlineP->leftLoc.n = leftArg; } - if (harg > 0) { - cmdlineP->height = harg; - cmdlineP->bottom = UNSPEC; + if (topArg < 0) { + cmdlineP->topLoc.locType = LOCTYPE_FROMFAR; + cmdlineP->topLoc.n = -topArg; } else { - cmdlineP->height = UNSPEC; - cmdlineP->bottom = harg - 1; + cmdlineP->topLoc.locType = LOCTYPE_FROMNEAR; + cmdlineP->topLoc.n = topArg; } - - if (argc-1 == 4) - cmdlineP->inputFileName = "-"; - else - cmdlineP->inputFileName = argv[5]; - break; - } + if (widthArg > 0) { + cmdlineP->width = widthArg; + cmdlineP->widthSpec = 1; + cmdlineP->rghtLoc.locType = LOCTYPE_NONE; + } else { + cmdlineP->widthSpec = 0; + cmdlineP->rghtLoc.locType = LOCTYPE_FROMFAR; + cmdlineP->rghtLoc.n = -(widthArg - 1); + } + if (heightArg > 0) { + cmdlineP->height = heightArg; + cmdlineP->heightSpec = 1; + cmdlineP->botLoc.locType = LOCTYPE_NONE; + } else { + cmdlineP->heightSpec = 0; + cmdlineP->botLoc.locType = LOCTYPE_FROMFAR; + cmdlineP->botLoc.n = -(heightArg - 1); + } + } else { + if (leftSpec && cropleftSpec) + pm_error("You cannot specify both -left and -cropleft"); + if (leftSpec) { + if (left >= 0) { + cmdlineP->leftLoc.locType = LOCTYPE_FROMNEAR; + cmdlineP->leftLoc.n = left; + } else { + cmdlineP->leftLoc.locType = LOCTYPE_FROMFAR; + cmdlineP->leftLoc.n = -left; + } + } else if (cropleftSpec) { + cmdlineP->leftLoc.locType = LOCTYPE_FROMNEAR; + cmdlineP->leftLoc.n = cropleft; + } else + cmdlineP->leftLoc.locType = LOCTYPE_NONE; + + if (rightSpec && croprightSpec) + pm_error("You cannot specify both -right and -cropright"); + if (rightSpec) { + if (right >= 0) { + cmdlineP->rghtLoc.locType = LOCTYPE_FROMNEAR; + cmdlineP->rghtLoc.n = right; + } else { + cmdlineP->rghtLoc.locType = LOCTYPE_FROMFAR; + cmdlineP->rghtLoc.n = -right; + } + } else if (croprightSpec) { + cmdlineP->rghtLoc.locType = LOCTYPE_FROMFAR; + cmdlineP->rghtLoc.n = 1 + cropright; + } else + cmdlineP->rghtLoc.locType = LOCTYPE_NONE; + + if (topSpec && croptopSpec) + pm_error("You cannot specify both -top and -croptop"); + if (topSpec) { + if (top >= 0) { + cmdlineP->topLoc.locType = LOCTYPE_FROMNEAR; + cmdlineP->topLoc.n = top; + } else { + cmdlineP->topLoc.locType = LOCTYPE_FROMFAR; + cmdlineP->topLoc.n = -top; + } + } else if (croptopSpec) { + cmdlineP->topLoc.locType = LOCTYPE_FROMNEAR; + cmdlineP->topLoc.n = croptop; + } else + cmdlineP->topLoc.locType = LOCTYPE_NONE; + + if (bottomSpec && cropbottomSpec) + pm_error("You cannot specify both -bottom and -cropbottom"); + if (bottomSpec) { + if (bottom >= 0) { + cmdlineP->botLoc.locType = LOCTYPE_FROMNEAR; + cmdlineP->botLoc.n = bottom; + } else { + cmdlineP->botLoc.locType = LOCTYPE_FROMFAR; + cmdlineP->botLoc.n = -bottom; + } + } else if (cropbottomSpec) { + cmdlineP->botLoc.locType = LOCTYPE_FROMFAR; + cmdlineP->botLoc.n = 1 + cropbottom; + } else + cmdlineP->botLoc.locType = LOCTYPE_NONE; } } +static int +near(Location const loc, + unsigned int const edge) { -static void -computeCutBounds(const int cols, const int rows, - const int leftarg, const int rightarg, - const int toparg, const int bottomarg, - const int widtharg, const int heightarg, - int * const leftcolP, int * const rightcolP, - int * const toprowP, int * const bottomrowP) { -/*---------------------------------------------------------------------------- - From the values given on the command line 'leftarg', 'rightarg', - 'toparg', 'bottomarg', 'widtharg', and 'heightarg', determine what - rectangle the user wants cut out. - - Any of these arguments may be UNSPEC to indicate "not specified". - Any except 'widtharg' and 'heightarg' may be negative to indicate - relative to the far edge. 'widtharg' and 'heightarg' are positive. + int retval; - Return the location of the rectangle as *leftcolP, *rightcolP, - *toprowP, and *bottomrowP. ------------------------------------------------------------------------------*/ - - int leftcol, rightcol, toprow, bottomrow; - /* The left and right column numbers and top and bottom row numbers - specified by the user, except with negative values translated - into the actual values. + switch (loc.locType) { + case LOCTYPE_NONE: + assert(false); + retval = 0; + break; + case LOCTYPE_FROMNEAR: + retval = loc.n; + break; + case LOCTYPE_FROMFAR: + retval = (int)edge - (int)loc.n; + } - Note that these may very well be negative themselves, such - as when the user says "column -10" and there are only 5 columns - in the image. - */ + return retval; +} - /* Translate negative column and row into real column and row */ - /* Exploit the fact that UNSPEC is a positive number */ - if (leftarg >= 0) - leftcol = leftarg; - else - leftcol = cols + leftarg; - if (rightarg >= 0) - rightcol = rightarg; - else - rightcol = cols + rightarg; - if (toparg >= 0) - toprow = toparg; - else - toprow = rows + toparg; - if (bottomarg >= 0) - bottomrow = bottomarg; - else - bottomrow = rows + bottomarg; - /* Sort out left, right, and width specifications */ +static void +computeCutBounds(unsigned int const cols, + unsigned int const rows, + Location const leftArg, + Location const rghtArg, + Location const topArg, + Location const botArg, + bool const widthSpec, + unsigned int const widthArg, + bool const heightSpec, + unsigned int const heightArg, + int * const leftColP, + int * const rghtColP, + int * const topRowP, + int * const botRowP) { +/*---------------------------------------------------------------------------- + From the values given on the command line 'leftArg', 'rghtArg', 'topArg', + 'botArg', 'widthArg', and 'heightArg', determine what rectangle the user + wants cut out. - if (leftcol == UNSPEC && rightcol == UNSPEC && widtharg == UNSPEC) { - *leftcolP = 0; - *rightcolP = cols - 1; - } - if (leftcol == UNSPEC && rightcol == UNSPEC && widtharg != UNSPEC) { - *leftcolP = 0; - *rightcolP = 0 + widtharg - 1; - } - if (leftcol == UNSPEC && rightcol != UNSPEC && widtharg == UNSPEC) { - *leftcolP = 0; - *rightcolP = rightcol; - } - if (leftcol == UNSPEC && rightcol != UNSPEC && widtharg != UNSPEC) { - *leftcolP = rightcol - widtharg + 1; - *rightcolP = rightcol; - } - if (leftcol != UNSPEC && rightcol == UNSPEC && widtharg == UNSPEC) { - *leftcolP = leftcol; - *rightcolP = cols - 1; - } - if (leftcol != UNSPEC && rightcol == UNSPEC && widtharg != UNSPEC) { - *leftcolP = leftcol; - *rightcolP = leftcol + widtharg - 1; - } - if (leftcol != UNSPEC && rightcol != UNSPEC && widtharg == UNSPEC) { - *leftcolP = leftcol; - *rightcolP = rightcol; - } - if (leftcol != UNSPEC && rightcol != UNSPEC && widtharg != UNSPEC) { - pm_error("You may not specify left, right, and width.\n" - "Choose at most two of these."); + Return the location of the rectangle as *leftcolP, *rghtcolP, *toprowP, and + *botrowP. Any of these can be outside the image, including by being + negative. +-----------------------------------------------------------------------------*/ + /* Find left and right bounds */ + + if (widthSpec) + assert(widthArg > 0); + + if (leftArg.locType == LOCTYPE_NONE) { + if (rghtArg.locType == LOCTYPE_NONE) { + *leftColP = 0; + if (widthSpec) + *rghtColP = 0 + (int)widthArg - 1; + else + *rghtColP = (int)cols - 1; + } else { + *rghtColP = near(rghtArg, cols); + if (widthSpec) + *leftColP = near(rghtArg, cols) - (int)widthArg + 1; + else + *leftColP = 0; + } + } else { + *leftColP = near(leftArg, cols); + if (rghtArg.locType == LOCTYPE_NONE) { + if (widthSpec) + *rghtColP = near(leftArg, cols) + (int)widthArg - 1; + else + *rghtColP = (int)cols - 1; + } else { + if (widthSpec) { + pm_error("You may not specify left, right, and width. " + "Choose at most two of these."); + } else + *rghtColP = near(rghtArg, cols); + } } + /* Find top and bottom bounds */ - /* Sort out top, bottom, and height specifications */ + if (heightSpec) + assert(heightArg > 0); - if (toprow == UNSPEC && bottomrow == UNSPEC && heightarg == UNSPEC) { - *toprowP = 0; - *bottomrowP = rows - 1; - } - if (toprow == UNSPEC && bottomrow == UNSPEC && heightarg != UNSPEC) { - *toprowP = 0; - *bottomrowP = 0 + heightarg - 1; - } - if (toprow == UNSPEC && bottomrow != UNSPEC && heightarg == UNSPEC) { - *toprowP = 0; - *bottomrowP = bottomrow; - } - if (toprow == UNSPEC && bottomrow != UNSPEC && heightarg != UNSPEC) { - *toprowP = bottomrow - heightarg + 1; - *bottomrowP = bottomrow; - } - if (toprow != UNSPEC && bottomrow == UNSPEC && heightarg == UNSPEC) { - *toprowP = toprow; - *bottomrowP = rows - 1; - } - if (toprow != UNSPEC && bottomrow == UNSPEC && heightarg != UNSPEC) { - *toprowP = toprow; - *bottomrowP = toprow + heightarg - 1; - } - if (toprow != UNSPEC && bottomrow != UNSPEC && heightarg == UNSPEC) { - *toprowP = toprow; - *bottomrowP = bottomrow; - } - if (toprow != UNSPEC && bottomrow != UNSPEC && heightarg != UNSPEC) { - pm_error("You may not specify top, bottom, and height.\n" - "Choose at most two of these."); + if (topArg.locType == LOCTYPE_NONE) { + if (botArg.locType == LOCTYPE_NONE) { + *topRowP = 0; + if (heightSpec) + *botRowP = 0 + (int)heightArg - 1; + else + *botRowP = (int)rows - 1; + } else { + *botRowP = near(botArg, rows); + if (heightSpec) + *topRowP = near(botArg, rows) - (int)heightArg + 1; + else + *topRowP = 0; + } + } else { + *topRowP = near(topArg, rows); + if (botArg.locType == LOCTYPE_NONE) { + if (heightSpec) + *botRowP = near(topArg, rows) + (int)heightArg - 1; + else + *botRowP = (int)rows - 1; + } else { + if (heightSpec) { + pm_error("You may not specify top, bottom, and height. " + "Choose at most two of these."); + } else + *botRowP = near(botArg, rows); + } } - } @@ -324,7 +440,7 @@ rejectOutOfBounds(unsigned int const cols, static void -writeBlackRows(const struct pam * const outpamP, +writeBlackRows(const struct pam * const outpamP, int const rows) { /*---------------------------------------------------------------------------- Write out 'rows' rows of black tuples of the image described by *outpamP. @@ -336,11 +452,11 @@ writeBlackRows(const struct pam * const outpamP, tuple blackTuple; tuple * blackRow; int col; - + pnm_createBlackTuple(outpamP, &blackTuple); MALLOCARRAY_NOFAIL(blackRow, outpamP->width); - + for (col = 0; col < outpamP->width; ++col) blackRow[col] = blackTuple; @@ -360,14 +476,14 @@ struct rowCutter { pnm_readpamrow() and one pnm_writepamrow(). It works like this: The array inputPointers[] contains an element for each pixel in an input - row. If it's a pixel that gets discarded in the cutting process, + row. If it's a pixel that gets discarded in the cutting process, inputPointers[] points to a special "discard" tuple. All thrown away pixels have the same discard tuple to save CPU cache space. If it's a pixel that gets copied to the output, inputPointers[] points to some tuple to which outputPointers[] also points. The array outputPointers[] contains an element for each pixel in an - output row. If the pixel is one that gets copied from the input, + output row. If the pixel is one that gets copied from the input, outputPointers[] points to some tuple to which inputPointers[] also points. If it's a pixel that gets padded with black, outputPointers[] points to a constant black tuple. All padded pixels have the same @@ -489,7 +605,7 @@ destroyRowCutter(struct rowCutter * const rowCutterP) { pnm_freepamtuple(rowCutterP->discardTuple); free(rowCutterP->inputPointers); free(rowCutterP->outputPointers); - + free(rowCutterP); } @@ -519,7 +635,7 @@ extractRowsGen(const struct pam * const inpamP, pnm_writepamrow(outpamP, rowCutterP->outputPointers); } else /* row < toprow || row > bottomrow */ pnm_readpamrow(inpamP, NULL); - + /* Note that we may be tempted just to quit after reaching the bottom of the extracted image, but that would cause a broken pipe problem for the process that's feeding us the image. @@ -527,7 +643,7 @@ extractRowsGen(const struct pam * const inpamP, } destroyRowCutter(rowCutterP); - + /* Write out bottom padding */ if ((bottomrow - (inpamP->height-1)) > 0) writeBlackRows(outpamP, bottomrow - (inpamP->height-1)); @@ -574,7 +690,7 @@ extractRowsPBM(const struct pam * const inpamP, /* Prevent overflows in pbm_allocrow_packed() */ pm_error("Specified right edge is too far " "from the right end of input image"); - + readOffset = 0; writeOffset = leftcol; } else { @@ -582,7 +698,7 @@ extractRowsPBM(const struct pam * const inpamP, if (totalWidth > INT_MAX) pm_error("Specified left/right edge is too far " "from the left/right end of input image"); - + readOffset = -leftcol; writeOffset = 0; } @@ -606,7 +722,7 @@ extractRowsPBM(const struct pam * const inpamP, pbm_writepbmrow_bitoffset(outpamP->file, bitrow, outpamP->width, 0, writeOffset); - + if (rightcol >= inpamP->width) /* repair right padding */ bitrow[writeOffset/8 + pbm_packed_bytes(outpamP->width) - 1] = @@ -632,18 +748,20 @@ cutOneImage(FILE * const ifP, FILE * const ofP) { int leftcol, rightcol, toprow, bottomrow; + /* Could be out of bounds, even negative */ struct pam inpam; /* Input PAM image */ struct pam outpam; /* Output PAM image */ pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type)); - - computeCutBounds(inpam.width, inpam.height, - cmdline.left, cmdline.right, - cmdline.top, cmdline.bottom, - cmdline.width, cmdline.height, + + computeCutBounds(inpam.width, inpam.height, + cmdline.leftLoc, cmdline.rghtLoc, + cmdline.topLoc, cmdline.botLoc, + cmdline.widthSpec, cmdline.width, + cmdline.heightSpec, cmdline.height, &leftcol, &rightcol, &toprow, &bottomrow); - rejectOutOfBounds(inpam.width, inpam.height, leftcol, rightcol, + rejectOutOfBounds(inpam.width, inpam.height, leftcol, rightcol, toprow, bottomrow, cmdline.pad); if (cmdline.verbose) { @@ -691,6 +809,6 @@ main(int argc, const char *argv[]) { pm_close(ifP); pm_close(ofP); - + return 0; } |