From 563d2b921e5611e8db9a83a2ddc847e556e5965f Mon Sep 17 00:00:00 2001 From: giraffedata Date: Thu, 30 Nov 2023 18:56:23 +0000 Subject: Add -color, -promote, -detect-background git-svn-id: http://svn.code.sf.net/p/netpbm/code/trunk@4796 9d0c8265-081b-0410-96cb-a4ca84ce46f8 --- doc/HISTORY | 2 +- editor/pnmpad.c | 459 +++++++++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 391 insertions(+), 70 deletions(-) diff --git a/doc/HISTORY b/doc/HISTORY index fc97a63c..c06ddd9a 100644 --- a/doc/HISTORY +++ b/doc/HISTORY @@ -6,7 +6,7 @@ CHANGE HISTORY not yet BJH Release 11.05.00 - pnmpad: Add -extend-edge. + pnmpad: Add -color, -promote, -extend-edge, -detect-background . pamtosvg: fix "zero determinant" failure. Introduced in Netpbm 11.04 (September 2023). diff --git a/editor/pnmpad.c b/editor/pnmpad.c index 7268a67c..da7eed2b 100644 --- a/editor/pnmpad.c +++ b/editor/pnmpad.c @@ -9,6 +9,7 @@ #include "pm_c_util.h" #include "mallocvar.h" +#include #include "shhopt.h" #include "pnm.h" @@ -17,6 +18,49 @@ arithmetic overflow */ +enum PadType { + /* PAD_COLOR means the specified padding is not black, white, or gray -- + even regardless of whether user specified it with a -color option. + E.g. -color=black is PAD_BLACK. + */ + PAD_BLACK, + PAD_WHITE, + PAD_GRAY, + PAD_COLOR, + PAD_DETECTED_BG, + PAD_EXTEND_EDGE +}; + +enum PromoteType { + PROMOTE_NONE, + PROMOTE_FORMAT, + PROMOTE_ALL +}; + + + +static enum PadType +padTypeFmColorStr(const char * const colorStr) { + + pixel const color = ppm_parsecolor(colorStr, PPM_OVERALLMAXVAL); + + enum PadType retval; + + if (PPM_ISGRAY(color)) { + if (PPM_GETR(color) == 0) + retval = PAD_BLACK; + else if (PPM_GETR(color) == PPM_OVERALLMAXVAL) + retval = PAD_WHITE; + else + retval = PAD_GRAY; + } else + retval = PAD_COLOR; + + return retval; +} + + + struct CmdlineInfo { /* All the information the user supplied in the command line, in a form easy for the program to use. @@ -38,8 +82,9 @@ struct CmdlineInfo { float yalign; unsigned int mwidth; unsigned int mheight; - unsigned int white; /* >0: pad white; 0: pad black */ - unsigned int extend_edge; + enum PadType padType; + const char * color; + enum PromoteType promote; unsigned int reportonly; unsigned int verbose; }; @@ -96,8 +141,12 @@ parseCommandLine(int argc, const char ** argv, optStruct3 opt; unsigned int option_def_index; - unsigned int blackOpt; + unsigned int black, white, extend_edge; unsigned int xalignSpec, yalignSpec, mwidthSpec, mheightSpec; + unsigned int colorSpec; + unsigned int detect_background; + unsigned int promoteSpec; + const char * promoteOpt; MALLOCARRAY_NOFAIL(option_def, 100); @@ -127,19 +176,25 @@ parseCommandLine(int argc, const char ** argv, OPTENT3(0, "valign", OPT_FLOAT, &cmdlineP->yalign, &yalignSpec, 0); OPTENT3(0, "mwidth", OPT_UINT, &cmdlineP->mwidth, - &mwidthSpec, 0); + &mwidthSpec, 0); OPTENT3(0, "mheight", OPT_UINT, &cmdlineP->mheight, - &mheightSpec, 0); + &mheightSpec, 0); OPTENT3(0, "black", OPT_FLAG, NULL, - &blackOpt, 0); + &black, 0); OPTENT3(0, "white", OPT_FLAG, NULL, - &cmdlineP->white, 0); + &white, 0); + OPTENT3(0, "color", OPT_STRING, &cmdlineP->color, + &colorSpec, 0); OPTENT3(0, "extend-edge", OPT_FLAG, NULL, - &cmdlineP->extend_edge, 0); + &extend_edge, 0); + OPTENT3(0, "detect-background", OPT_FLAG, NULL, + &detect_background, 0); + OPTENT3(0, "promote", OPT_STRING, &promoteOpt, + &promoteSpec, 0); OPTENT3(0, "reportonly", OPT_FLAG, NULL, - &cmdlineP->reportonly, 0); - OPTENT3(0, "verbose", OPT_FLAG, NULL, - &cmdlineP->verbose, 0); + &cmdlineP->reportonly, 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 */ @@ -148,14 +203,62 @@ 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. */ - if (cmdlineP->extend_edge) { - if (blackOpt || cmdlineP->white) - pm_error("You cannot specify -extend-edge with -black or -white"); + if (!colorSpec) + cmdlineP->color = NULL; + + if (black + white + colorSpec + detect_background + extend_edge > 1) + pm_error("You can specify only one one of -black, -white, " + "-color -detect-background and -extend-edge"); + else if (black) + cmdlineP->padType = PAD_BLACK; + else if (white) + cmdlineP->padType = PAD_WHITE; + else if (colorSpec) + cmdlineP->padType = padTypeFmColorStr(cmdlineP->color); + else if (extend_edge) + cmdlineP->padType = PAD_EXTEND_EDGE; + else if (detect_background) + cmdlineP->padType = PAD_DETECTED_BG; + else { + cmdlineP->padType = PAD_BLACK; + if (cmdlineP->verbose) + pm_message("No pad color specified. Padding with black."); } - if (blackOpt && cmdlineP->white) - pm_error("You cannot specify both -black and -white"); - + if (promoteSpec) { + if (!colorSpec) + pm_error("-promote requires -color"); + else { + switch (cmdlineP->padType) { + case PAD_COLOR: + case PAD_GRAY: + if (streq(promoteOpt, "none")) + cmdlineP->promote = PROMOTE_NONE; + else if (streq(promoteOpt, "format")) + cmdlineP->promote = PROMOTE_FORMAT; + else if (streq(promoteOpt, "all")) + cmdlineP->promote = PROMOTE_ALL; + else + pm_error("Invalid value for -promote. " + "Valid values are 'none', 'format', and 'all'"); + break; + case PAD_BLACK: + case PAD_WHITE: + cmdlineP->promote = PROMOTE_NONE; + break; + case PAD_DETECTED_BG: + case PAD_EXTEND_EDGE: + assert(false); /* Because we have -color */ + break; + } + } + } else { + if (cmdlineP->padType == PAD_COLOR || + cmdlineP->padType == PAD_GRAY) { + cmdlineP->promote = PROMOTE_ALL; + } else + cmdlineP->promote = PROMOTE_NONE; + } if (cmdlineP->topSpec > 1) pm_error("You can specify -top only once"); @@ -489,7 +592,9 @@ reportPadSizes(int const inCols, static unsigned char bitPeek(const unsigned char * const bitrow, unsigned int const position) { - +/*---------------------------------------------------------------------------- + Value of pixel (bit) of 'bitrow' at 'position'. +-----------------------------------------------------------------------------*/ bool retval; unsigned int const charPosition = position / 8; @@ -505,7 +610,10 @@ bitPeek(const unsigned char * const bitrow, static void extendLeftPbm(unsigned char * const bitrow, unsigned int const lpad) { - +/*---------------------------------------------------------------------------- + Add 'lpad' pixels of padding the the left side of PBM row 'bitrow' by + duplicating leftmost pixel. +-----------------------------------------------------------------------------*/ unsigned int const padCharCt = lpad / 8; unsigned int const fractBitCt = lpad % 8; unsigned int const color = bitPeek(bitrow, lpad); @@ -528,13 +636,16 @@ static void extendRightPbm(unsigned char * const bitrow, unsigned int const lcols, unsigned int const rpad) { - - unsigned int const rpad0 = (8 - lcols % 8) % 8; - unsigned int const rpad1 = rpad - rpad0; - unsigned int const rpad1CharCt = +/*---------------------------------------------------------------------------- + Add 'rpad' pixels of padding the the left side of PBM row 'bitrow', + which is 'lcols' columns wide, by duplicating leftmost pixel. +-----------------------------------------------------------------------------*/ + unsigned int const rpad0 = (8 - lcols % 8) % 8; + unsigned int const rpad1 = rpad - rpad0; + unsigned int const rpad1CharCt = rpad1 > 0 ? pbm_packed_bytes(rpad1) : 0; - unsigned int const lastColChar = lcols / 8 - (rpad0 == 0 ? 1 : 0); - unsigned char const fillColor = bitPeek(bitrow, lcols - 1); + unsigned int const lastColChar = lcols / 8 - (rpad0 == 0 ? 1 : 0); + unsigned char const fillColor = bitPeek(bitrow, lcols - 1); unsigned int colChar; @@ -542,12 +653,13 @@ extendRightPbm(unsigned char * const bitrow, if (fillColor == 0x1) { bitrow[lastColChar] = bitrow[lastColChar] | (bitrow[lastColChar] - 1); - } else { - pbm_cleanrowend_packed(bitrow, lcols); - /* Function pbm_cleanrowend_packed() is employed an atypical way - here. It cleans bits beyond the active image content, but the - position is not the row end. - */ + } else if (lcols % 8 > 0) { + /* This is essentially pbm_cleanrowend_packed(bitrow, lcols); */ + + unsigned int const last = pbm_packed_bytes(lcols) - 1; + + bitrow[last] >>= 8 - lcols % 8; + bitrow[last] <<= 8 - lcols % 8; } } @@ -596,7 +708,12 @@ extendEdgePbm(FILE * const ifP, for (row = 0; row < tpad + 1; ++row) pbm_writepbmrow_packed(ofP, newbitrow, newcols, 0); - /* Read rows, shift and write with left and right margins added */ + /* Read rows, shift and write with left and right margins added. + + Alter left/right margin only when the color of leftmost/rightmost + pixel is different from that of the previous row. + */ + for (row = 1; row < rows; ++row) { pbm_readpbmrow_bitoffset(ifP, newbitrow, cols, format, lpad); @@ -681,7 +798,7 @@ padPbm(FILE * const ifP, unsigned int const rpad, unsigned int const tpad, unsigned int const bpad, - bool const colorWhite, + enum PadType const padType, FILE * const ofP) { /*---------------------------------------------------------------------------- Fast padding routine for PBM @@ -690,7 +807,7 @@ padPbm(FILE * const ifP, unsigned char * const newrow = pbm_allocrow_packed(newcols); unsigned char const padChar = - 0xff * (colorWhite ? PBM_WHITE : PBM_BLACK); + 0xff * (padType == PAD_WHITE ? PBM_WHITE : PBM_BLACK); unsigned int const newColChars = pbm_packed_bytes(newcols); @@ -729,56 +846,245 @@ padPbm(FILE * const ifP, +static xel +regressColor(int const format, + int const maxval, + xel const color) { + + gray const grayval = (gray) ppm_luminosity(color); + + xel retval; + + if (format == PBM_TYPE) { + gray const threshold = maxval / 2; + + retval = grayval > threshold ? + pnm_whitexel(maxval, format) : pnm_blackxel(maxval, format); + } else { /* PGM_TYPE */ + retval.b = grayval; + retval.r = retval.g = 0; + } + return retval; +} + + + +static void +computeOutputFormatAndBackground(int const format, + int const maxval, + enum PadType const padType, + const char * const colorStr, + enum PromoteType const promoteType, + int * const newformatP, + pixval * const newmaxvalP, + xel * const backgroundP) { + + int newformat; + xelval newmaxval; + xel background; + + switch (promoteType) { + case PROMOTE_NONE: + newformat = format; + newmaxval = maxval; + + switch (padType) { + case PAD_BLACK: + background = pnm_blackxel(newmaxval, newformat); + break; + case PAD_WHITE: + background = pnm_whitexel(newmaxval, newformat); + break; + case PAD_GRAY: + case PAD_COLOR: + background = regressColor(format, maxval, + ppm_parsecolor(colorStr, newmaxval)); + break; + case PAD_DETECTED_BG: + case PAD_EXTEND_EDGE: + pm_error("internal error -- impossible value of 'promoteType' " + "in computeOutputFormatAndBackground"); + } break; + + case PROMOTE_FORMAT: + case PROMOTE_ALL: { + newmaxval = promoteType == PROMOTE_ALL ? MAX(255, maxval) : maxval; + + switch (padType) { + case PAD_BLACK: + newformat = format; + background = pnm_blackxel(newmaxval, newformat); + break; + case PAD_WHITE: + newformat = format; + background = pnm_whitexel(newmaxval, newformat); + break; + case PAD_GRAY: + newformat = + PNM_FORMAT_TYPE(format) == PBM_TYPE ? PGM_TYPE : format; + background = ppm_parsecolor(colorStr, newmaxval); + break; + case PAD_COLOR: + newformat = PPM_TYPE; + background = ppm_parsecolor(colorStr, newmaxval); + break; + case PAD_DETECTED_BG: + case PAD_EXTEND_EDGE: + pm_error("internal error -- impossible value of 'promoteType' " + "in computeOutputFormatAndBackground"); + } break; + } + } + + *newformatP = newformat; + *newmaxvalP = newmaxval; + *backgroundP = background; +} + + + +static void +padDetectedBg(FILE * const ifP, + unsigned int const cols, + unsigned int const rows, + xelval const maxval, + int const format, + unsigned int const newcols, + unsigned int const lpad, + unsigned int const rpad, + unsigned int const tpad, + unsigned int const bpad, + FILE * const ofP) { +/*---------------------------------------------------------------------------- + Pad using top left pixel as background color. + + (There is no special PBM routine for this case) +-----------------------------------------------------------------------------*/ + xel * const bgrow = pnm_allocrow(newcols); + /* A row of background color (for top and bottom padding) */ + xel * const xelrow = pnm_allocrow(newcols); + /* A row of padded output image. Left and right padding is + constant; middle varies from row to row. + */ + xel * const window = &xelrow[lpad]; + /* The foreground part of the current row */ + + unsigned int row;; + + pnm_writepnminit(ofP, newcols, rows + tpad + bpad, maxval, format, 0); + + pnm_readpnmrow(ifP, window, cols, maxval, format); + + { + /* Set 'bgrow' and constant parts of 'xelrow' */ + xel const background = window[0]; + + unsigned int col; + + for (col = 0; col < newcols; ++col) + bgrow[col] = background; + + for (col = 0; col < lpad; ++col) + xelrow[col] = background; + + for (col = lpad + cols; col < newcols; ++col) + xelrow[col] = background; + } + + /* Write top padding */ + for (row = 0; row < tpad; ++row) + pnm_writepnmrow(ofP, bgrow, newcols, maxval, format, 0); + + /* Write body of image */ + pnm_writepnmrow(ofP, xelrow, newcols, maxval, format, 0); + + for (row = 1; row < rows; ++row) { + pnm_readpnmrow(ifP, window, cols, maxval, format); + pnm_writepnmrow(ofP, xelrow, newcols, maxval, format, 0); + } + pnm_freerow(xelrow); + + /* Write bottom padding */ + for (row = 0; row < bpad; ++row) + pnm_writepnmrow(ofP, bgrow, newcols, maxval, format, 0); + + pnm_freerow(bgrow); +} + + + static void -padGeneral(FILE * const ifP, - unsigned int const cols, - unsigned int const rows, - xelval const maxval, - int const format, - unsigned int const newcols, - unsigned int const lpad, - unsigned int const rpad, - unsigned int const tpad, - unsigned int const bpad, - bool const colorWhite, - FILE * const ofP) { +padGeneral(FILE * const ifP, + unsigned int const cols, + unsigned int const rows, + xelval const maxval, + int const format, + unsigned int const newcols, + unsigned int const lpad, + unsigned int const rpad, + unsigned int const tpad, + unsigned int const bpad, + enum PadType const padType, + const char * const colorStr, + enum PromoteType const promoteType, + FILE * const ofP) { /*---------------------------------------------------------------------------- General padding routine (logic works for PBM) -----------------------------------------------------------------------------*/ - xel * const bgrow = pnm_allocrow(newcols); - xel const background = colorWhite ? - pnm_whitexel(maxval, format) : pnm_blackxel(maxval, format); + xel * const bgrow = pnm_allocrow(newcols); + /* A row of background color (for top and bottom padding) */ + + int newformat; + pixval newmaxval; + xel background; unsigned int row, col; + computeOutputFormatAndBackground( + format, maxval, padType, colorStr, promoteType, + &newformat, &newmaxval, &background); + + if (newformat != format) + pm_message("Promoting to %s", pnm_formattypenm(newformat)); + for (col = 0; col < newcols; ++col) bgrow[col] = background; - pnm_writepnminit(ofP, newcols, rows + tpad + bpad, maxval, format, 0); + pnm_writepnminit(ofP, newcols, rows + tpad + bpad, + newmaxval, newformat, 0); /* Write top padding */ for (row = 0; row < tpad; ++row) - pnm_writepnmrow(ofP, bgrow, newcols, maxval, format, 0); + pnm_writepnmrow(ofP, bgrow, newcols, newmaxval, newformat, 0); /* Write body of image */ { xel * const xelrow = pnm_allocrow(newcols); + /* A row of padded output image. Left and right padding is + constant; middle varies from row to row. + */ + xel * const window = &xelrow[lpad]; + /* The foreground part of the current row */ - /* initial value for 'xelrow': all background */ + /* Initial value for 'xelrow': all background */ for (col = 0; col < newcols; ++col) xelrow[col] = bgrow[col] = background; for (row = 0; row < rows; ++row) { - pnm_readpnmrow(ifP, &xelrow[lpad], cols, maxval, format); - pnm_writepnmrow(ofP, xelrow, newcols, maxval, format, 0); + pnm_readpnmrow(ifP, window, cols, maxval, format); + if (maxval != newmaxval || format != newformat) { + pnm_promoteformatrow(window, cols, maxval, format, + newmaxval, newformat); + } + pnm_writepnmrow(ofP, xelrow, newcols, newmaxval, newformat, 0); } pnm_freerow(xelrow); } /* Write bottom padding */ for (row = 0; row < bpad; ++row) - pnm_writepnmrow(ofP, bgrow, newcols, maxval, format, 0); + pnm_writepnmrow(ofP, bgrow, newcols, newmaxval, newformat, 0); pnm_freerow(bgrow); } @@ -805,7 +1111,8 @@ main(int argc, const char ** argv) { pnm_readpnminit(ifP, &cols, &rows, &maxval, &format); - if (cmdline.verbose) pm_message("image WxH = %dx%d", cols, rows); + if (cmdline.verbose) + pm_message("image WxH = %dx%d", cols, rows); computePadSizes(cmdline, cols, rows, &lpad, &rpad, &tpad, &bpad); @@ -813,21 +1120,35 @@ main(int argc, const char ** argv) { if (cmdline.reportonly) reportPadSizes(cols, rows, lpad, rpad, tpad, bpad); - else if (cmdline.extend_edge) { - if (PNM_FORMAT_TYPE(format) == PBM_TYPE) - extendEdgePbm(ifP, cols, rows, format, - newcols, lpad, rpad, tpad, bpad, stdout); - else - extendEdgeGeneral(ifP, cols, rows, maxval, format, + else { + switch (cmdline.padType) { + case PAD_EXTEND_EDGE: + if (PNM_FORMAT_TYPE(format) == PBM_TYPE) + extendEdgePbm(ifP, cols, rows, format, newcols, lpad, rpad, tpad, bpad, stdout); - } else { - if (PNM_FORMAT_TYPE(format) == PBM_TYPE) - padPbm(ifP, cols, rows, format, - newcols, lpad, rpad, tpad, bpad, !!cmdline.white, stdout); - else - padGeneral(ifP, cols, rows, maxval, format, - newcols, lpad, rpad, tpad, bpad, !!cmdline.white, + else + extendEdgeGeneral(ifP, cols, rows, maxval, format, + newcols, lpad, rpad, tpad, bpad, stdout); + break; + case PAD_DETECTED_BG: + padDetectedBg(ifP, cols, rows, maxval, format, + newcols, lpad, rpad, tpad, bpad, stdout); + break; + case PAD_BLACK: + case PAD_WHITE: + case PAD_GRAY: + case PAD_COLOR: + if (PNM_FORMAT_TYPE(format) == PBM_TYPE && + (cmdline.padType == PAD_WHITE || cmdline.padType == PAD_BLACK)) + padPbm(ifP, cols, rows, format, + newcols, lpad, rpad, tpad, bpad, cmdline.padType, stdout); + else + padGeneral(ifP, cols, rows, maxval, format, + newcols, lpad, rpad, tpad, bpad, cmdline.padType, + cmdline.color, cmdline.promote, stdout); + break; + } } pm_close(ifP); -- cgit 1.4.1