about summary refs log tree commit diff
path: root/editor/pnmpad.c
diff options
context:
space:
mode:
Diffstat (limited to 'editor/pnmpad.c')
-rw-r--r--editor/pnmpad.c834
1 files changed, 666 insertions, 168 deletions
diff --git a/editor/pnmpad.c b/editor/pnmpad.c
index 55cdcd6c..94ea0ed1 100644
--- a/editor/pnmpad.c
+++ b/editor/pnmpad.c
@@ -1,26 +1,71 @@
-/* pnmpad.c - add border to sides of a portable anymap
+/* pnmpad.c - add border to sides of a PNM
    ** AJCD 4/9/90
  */
 
+#include <stdbool.h>
 #include <assert.h>
 #include <string.h>
 #include <stdio.h>
 
 #include "pm_c_util.h"
 #include "mallocvar.h"
+#include <nstring.h>
 #include "shhopt.h"
 #include "pnm.h"
 
-#define MAX_WIDTHHEIGHT INT_MAX-10
+#define MAX_WIDTHHEIGHT ((INT_MAX)-10)
     /* The maximum width or height value we can handle without risking
        arithmetic overflow
     */
 
-struct cmdlineInfo {
+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.
     */
-    const char * input_filespec;  /* Filespecs of input files */
+    const char * inputFileNm;  /* Names of input files */
     unsigned int xsize;
     unsigned int xsizeSpec;
     unsigned int ysize;
@@ -37,7 +82,9 @@ struct cmdlineInfo {
     float yalign;
     unsigned int mwidth;
     unsigned int mheight;
-    unsigned int white;     /* >0: pad white; 0: pad black */
+    enum PadType padType;
+    const char * color;
+    enum PromoteType promote;
     unsigned int reportonly;
     unsigned int verbose;
 };
@@ -45,8 +92,45 @@ struct cmdlineInfo {
 
 
 static void
+validateNoOldOptionSyntax( int const argc, const char ** const argv) {
+/*----------------------------------------------------------------------------
+  Reject obsolete command line syntax, e.g. "pnmpad -l50".
+
+  Starting in Netpbm 9.25 (February 2002), this resulted in a warning message
+  and was no longer documented.  Starting in Netpbm 11.05 (December 2023),
+  it is no longer accepted.
+
+  It was too hard to maintain.
+-----------------------------------------------------------------------------*/
+    bool isOld;
+
+    isOld = false;  /* initial assumption */
+
+    if (argc > 1 && argv[1][0] == '-') {
+        if (argv[1][1] == 't' || argv[1][1] == 'b'
+            || argv[1][1] == 'l' || argv[1][1] == 'r') {
+            if (argv[1][2] >= '0' && argv[1][2] <= '9')
+                isOld = true;
+        }
+    }
+    if (argc > 2 && argv[2][0] == '-') {
+        if (argv[2][1] == 't' || argv[2][1] == 'b'
+            || argv[2][1] == 'l' || argv[2][1] == 'r') {
+            if (argv[2][2] >= '0' && argv[2][2] <= '9')
+                isOld = true;
+        }
+    }
+    if (isOld)
+        pm_error("Old-style Unix options (e.g. \"-l50\") "
+                 "not accepted by current 'pnmpad'.  "
+                 "Use e.g. \"-l 50\" instead");
+}
+
+
+
+static void
 parseCommandLine(int argc, const char ** argv,
-                 struct cmdlineInfo * const 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.
@@ -57,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);
 
@@ -88,28 +176,89 @@ 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,
+            &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 */
-    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+    opt.short_allowed = false;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = false;  /* We have no parms that are negative numbers */
 
     pm_optParseOptions3(&argc, (char **)argv, opt, sizeof opt, 0);
         /* Uses and sets argc, argv, and some of *cmdlineP and others. */
 
-    if (blackOpt && cmdlineP->white)
-        pm_error("You cannot specify both -black and -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 (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");
 
@@ -129,16 +278,16 @@ parseCommandLine(int argc, const char ** argv,
        pm_error("You can specify -height only once");
 
     if (xalignSpec && (cmdlineP->leftSpec || cmdlineP->rightSpec))
-        pm_error("You cannot specify both -xalign and -left or -right");
+        pm_error("You cannot specify both -halign and -left or -right");
 
     if (yalignSpec && (cmdlineP->topSpec || cmdlineP->bottomSpec))
-        pm_error("You cannot specify both -yalign and -top or -bottom");
+        pm_error("You cannot specify both -valign and -top or -bottom");
 
-    if (xalignSpec && !cmdlineP->xsizeSpec)
-        pm_error("-xalign is meaningless without -width");
+    if (xalignSpec && (!cmdlineP->xsizeSpec && !mwidthSpec) )
+        pm_error("-halign is meaningless without -width or -mwidth");
 
-    if (yalignSpec && !cmdlineP->ysizeSpec)
-        pm_error("-yalign is meaningless without -height");
+    if (yalignSpec && (!cmdlineP->ysizeSpec && !mheightSpec) )
+        pm_error("-valign is meaningless without -height or -mheight");
 
     if (xalignSpec) {
         if (cmdlineP->xalign < 0)
@@ -171,83 +320,15 @@ parseCommandLine(int argc, const char ** argv,
         pm_error("This program takes at most 1 parameter.  You specified %d",
                  argc-1);
     else if (argc-1 == 1)
-        cmdlineP->input_filespec = argv[1];
-    else
-        cmdlineP->input_filespec = "-";
-}
-
-
-
-static void
-parseCommandLineOld(int argc, const char ** argv,
-                    struct cmdlineInfo * const cmdlineP) {
-
-    /* This syntax was abandoned in February 2002. */
-    pm_message("Warning: old style options are deprecated!");
-
-    cmdlineP->xsize = cmdlineP->ysize = 0;
-    cmdlineP->left = cmdlineP->right = cmdlineP->top = cmdlineP->bottom = 0;
-    cmdlineP->xalign = cmdlineP->yalign = 0.5;
-    cmdlineP->white = cmdlineP->verbose = FALSE;
-
-    while (argc >= 2 && argv[1][0] == '-') {
-        if (strcmp(argv[1]+1,"black") == 0) cmdlineP->white = FALSE;
-        else if (strcmp(argv[1]+1,"white") == 0) cmdlineP->white = TRUE;
-        else switch (argv[1][1]) {
-        case 'l':
-            if (atoi(argv[1]+2) < 0)
-                pm_error("left border too small");
-            else if (atoi(argv[1]+2) > MAX_WIDTHHEIGHT)
-                pm_error("left border too large");
-            else
-                cmdlineP->left = atoi(argv[1]+2);
-            break;
-        case 'r':
-            if (atoi(argv[1]+2) < 0)
-                pm_error("right border too small");
-            else if (atoi(argv[1]+2) > MAX_WIDTHHEIGHT)
-                pm_error("right border too large");
-            else
-                cmdlineP->right = atoi(argv[1]+2);
-            break;
-        case 'b':
-            if (atoi(argv[1]+2) < 0)
-                pm_error("bottom border too small");
-            else if (atoi(argv[1]+2) > MAX_WIDTHHEIGHT)
-                pm_error("bottom border too large");
-            else
-                cmdlineP->bottom = atoi(argv[1]+2);
-            break;
-        case 't':
-            if (atoi(argv[1]+2) < 0)
-                pm_error("top border too small");
-            else if (atoi(argv[1]+2) > MAX_WIDTHHEIGHT)
-                pm_error("top border too large");
-            else
-                cmdlineP->top = atoi(argv[1]+2);
-            break;
-        default:
-            pm_usage("[-white|-black] [-l#] [-r#] [-t#] [-b#] [pnmfile]");
-        }
-        argc--, argv++;
-    }
-
-    cmdlineP->xsizeSpec = (cmdlineP->xsize > 0);
-    cmdlineP->ysizeSpec = (cmdlineP->ysize > 0);
-
-    if (argc > 2)
-        pm_usage("[-white|-black] [-l#] [-r#] [-t#] [-b#] [pnmfile]");
-
-    if (argc == 2)
-        cmdlineP->input_filespec = argv[1];
+        cmdlineP->inputFileNm = argv[1];
     else
-        cmdlineP->input_filespec = "-";
+        cmdlineP->inputFileNm = "-";
 }
 
 
 
 static void
-validateHorizontalSize(struct cmdlineInfo const cmdline,
+validateHorizontalSize(struct CmdlineInfo const cmdline,
                        unsigned int       const cols) {
 /*----------------------------------------------------------------------------
    Abort the program if the padding parameters in 'cmdline', applied to
@@ -268,14 +349,14 @@ validateHorizontalSize(struct cmdlineInfo const cmdline,
         pm_error("The right padding value you specified is too large.");
 
     if ((double) cols +
-        (double) lpad + 
+        (double) lpad +
         (double) rpad +
         (double) mwidthMaxPad > MAX_WIDTHHEIGHT)
         pm_error("Given padding parameters make output width too large "
                  "for this program to compute");
 
     if (cmdline.xsizeSpec &&
-        (double) cmdline.xsize + (double) mwidthMaxPad> MAX_WIDTHHEIGHT)
+        (double) cmdline.xsize + (double) mwidthMaxPad > MAX_WIDTHHEIGHT)
         pm_error("Given padding parameters make output width too large "
                  "for this program to compute");
 }
@@ -300,8 +381,8 @@ computePadSizeBeforeMult(unsigned int   const unpaddedSize,
 -----------------------------------------------------------------------------*/
     if (sizeSpec) {
         if (begPadSpec && endPadSpec) {
-            if (begPadReq + unpaddedSize + endPadReq < unpaddedSize) {
-                pm_error("Beginning adding (%u), and end "
+            if (begPadReq + unpaddedSize + endPadReq < sizeReq) {
+                pm_error("Beginning padding (%u), and end "
                          "padding (%u) are insufficient to bring the "
                          "image size of %u up to %u.",
                          begPadReq, endPadReq, unpaddedSize, sizeReq);
@@ -378,9 +459,9 @@ computePadSizesOneDim(unsigned int   const unpaddedSize,
         unsigned int const totalPadBeforeMult =
             begPadBeforeMult + endPadBeforeMult;
         double const begFrac =
-            totalPadBeforeMult > 0 ? 
+            totalPadBeforeMult > 0 ?
             (double)begPadBeforeMult / totalPadBeforeMult :
-            0.0;
+            align;
         unsigned int const addlMsizeBegPad = ROUNDU(morePadNeeded * begFrac);
             /* # of pixels we have to add to the beginning to satisfy
                user's desire for the final size to be a multiple of something
@@ -396,7 +477,7 @@ computePadSizesOneDim(unsigned int   const unpaddedSize,
 
 
 static void
-computeHorizontalPadSizes(struct cmdlineInfo const cmdline,
+computeHorizontalPadSizes(struct CmdlineInfo const cmdline,
                           unsigned int       const cols,
                           unsigned int *     const lpadP,
                           unsigned int *     const rpadP) {
@@ -415,7 +496,7 @@ computeHorizontalPadSizes(struct cmdlineInfo const cmdline,
 
 
 static void
-validateVerticalSize(struct cmdlineInfo const cmdline,
+validateVerticalSize(struct CmdlineInfo const cmdline,
                      unsigned int       const rows) {
 /*----------------------------------------------------------------------------
    Abort the program if the padding parameters in 'cmdline', applied to
@@ -453,7 +534,7 @@ validateVerticalSize(struct cmdlineInfo const cmdline,
 
 
 static void
-computeVerticalPadSizes(struct cmdlineInfo const cmdline,
+computeVerticalPadSizes(struct CmdlineInfo const cmdline,
                         int                const rows,
                         unsigned int *     const tpadP,
                         unsigned int *     const bpadP) {
@@ -472,7 +553,7 @@ computeVerticalPadSizes(struct cmdlineInfo const cmdline,
 
 
 static void
-computePadSizes(struct cmdlineInfo const cmdline,
+computePadSizes(struct CmdlineInfo const cmdline,
                 int                const cols,
                 int                const rows,
                 unsigned int *     const lpadP,
@@ -501,13 +582,212 @@ reportPadSizes(int          const inCols,
 
     unsigned int const outCols = inCols + lpad + rpad;
     unsigned int const outRows = inRows + tpad + bpad;
- 
+
     printf("%u %u %u %u %u %u\n", lpad, rpad, tpad, bpad, outCols, outRows);
 
 }
 
 
 
+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;
+    unsigned int const bitPosition  = position % 8;
+
+    retval =  (bitrow[charPosition] >> (7 - bitPosition)) & 0x01;
+
+    return retval;
+}
+
+
+
+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);
+
+    unsigned int colChar;
+
+    for (colChar = 0; colChar < padCharCt; ++colChar)
+        bitrow[colChar] = 0xff * color;
+
+    if (fractBitCt > 0) {
+        bitrow[colChar] = (unsigned char)
+            (0xff * color) << (8-fractBitCt) |
+            (bitrow[colChar] & (0xff >> fractBitCt));
+    }
+}
+
+
+
+static void
+extendRightPbm(unsigned char * const bitrow,
+               unsigned int    const lcols,
+               unsigned int    const rpad) {
+/*----------------------------------------------------------------------------
+  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 colChar;
+
+    if (rpad0 > 0) {
+        if (fillColor == 0x1) {
+            bitrow[lastColChar] =
+                bitrow[lastColChar] | (bitrow[lastColChar] - 1);
+        } 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;
+        }
+    }
+
+    for (colChar = 0; colChar < rpad1CharCt; ++colChar)
+        bitrow[lastColChar + colChar +1] = 0xff * fillColor;
+
+    if (fillColor == 0x1)
+        pbm_cleanrowend_packed(bitrow, lcols + rpad);
+}
+
+
+
+static void
+extendEdgePbm(FILE *       const ifP,
+              unsigned int const cols,
+              unsigned int const rows,
+              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) {
+/*----------------------------------------------------------------------------
+  Fast extend-edge routine for PBM
+-----------------------------------------------------------------------------*/
+    unsigned char * const newbitrow = pbm_allocrow_packed(newcols);
+
+    unsigned int row;
+
+    pbm_writepbminit(stdout, newcols, rows + tpad + bpad, 0);
+
+    /* Write top row tpad + 1 times */
+    if (rpad > 0)
+        newbitrow[(cols + lpad) / 8] = 0x00;
+
+    pbm_readpbmrow_bitoffset(ifP, newbitrow, cols, format, lpad);
+
+    if (lpad > 0)
+        extendLeftPbm(newbitrow, lpad);
+    if (rpad > 0)
+        extendRightPbm(newbitrow, lpad + cols, rpad);
+
+    pbm_cleanrowend_packed(newbitrow, newcols);
+
+    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.
+
+       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);
+
+        if (lpad > 0 &&
+            (bitPeek(newbitrow, lpad - 1) != bitPeek(newbitrow, lpad)))
+            extendLeftPbm(newbitrow, lpad);
+        if (rpad > 0 &&
+            (bitPeek(newbitrow, lpad + cols -1) !=
+             bitPeek(newbitrow, lpad + cols)))
+            extendRightPbm(newbitrow, lpad + cols, rpad);
+        pbm_writepbmrow_packed(ofP, newbitrow, newcols, 0);
+    }
+
+    /* Write bottom margin */
+    for (row = 0; row < bpad; ++row)
+        pbm_writepbmrow_packed(ofP, newbitrow, newcols, 0);
+
+    pnm_freerow(newbitrow);
+}
+
+
+
+static void
+extendEdgeGeneral(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) {
+/*----------------------------------------------------------------------------
+  General extend-edge routine (logic works for PBM)
+-----------------------------------------------------------------------------*/
+    xel * const xelrow = pnm_allocrow(newcols);
+
+    unsigned int row, col;
+
+    pnm_writepnminit(ofP, newcols, rows + tpad + bpad, maxval, format, 0);
+
+    pnm_readpnmrow(ifP, &xelrow[lpad], cols, maxval, format);
+
+    for (col = 0; col < lpad; ++col)
+        xelrow[col] = xelrow[lpad];
+    for (col = lpad + cols; col < newcols; ++col)
+        xelrow[col] = xelrow[lpad + cols - 1];
+
+    for (row = 0; row < tpad + 1; ++row)
+        pnm_writepnmrow(ofP, xelrow, newcols, maxval, format, 0);
+
+    for (row = 1; row < rows; ++row) {
+        unsigned int col;
+
+        pnm_readpnmrow(ifP, &xelrow[lpad], cols, maxval, format);
+
+        for (col = 0; col < lpad; ++col)
+            xelrow[col] = xelrow[lpad];
+        for (col = lpad + cols; col < newcols; ++col)
+            xelrow[col] = xelrow[lpad + cols - 1];
+
+        pnm_writepnmrow(ofP, xelrow, newcols, maxval, format, 0);
+    }
+
+    for (row = 0; row < bpad; ++row)
+        pnm_writepnmrow(ofP, xelrow, newcols, maxval, format, 0);
+
+    pnm_freerow(xelrow);
+}
+
+
+
 static void
 padPbm(FILE *       const ifP,
        unsigned int const cols,
@@ -518,7 +798,8 @@ 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
 -----------------------------------------------------------------------------*/
@@ -526,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);
 
@@ -542,70 +823,280 @@ padPbm(FILE *       const ifP,
         newrow[newColChars-1] <<= 8 - newcols % 8;
     }
 
-    pbm_writepbminit(stdout, newcols, rows + tpad + bpad, 0);
+    pbm_writepbminit(ofP, newcols, rows + tpad + bpad, 0);
 
     /* Write top margin */
     for (row = 0; row < tpad; ++row)
-        pbm_writepbmrow_packed(stdout, bgrow, newcols, 0);
-    
+        pbm_writepbmrow_packed(ofP, bgrow, newcols, 0);
+
     /* Read rows, shift and write with left and right margins added */
     for (row = 0; row < rows; ++row) {
         pbm_readpbmrow_bitoffset(ifP, newrow, cols, format, lpad);
-        pbm_writepbmrow_packed(stdout, newrow, newcols, 0);
+        pbm_writepbmrow_packed(ofP, newrow, newcols, 0);
     }
 
     pnm_freerow(newrow);
 
     /* Write bottom margin */
     for (row = 0; row < bpad; ++row)
-        pbm_writepbmrow_packed(stdout, bgrow, newcols, 0);
+        pbm_writepbmrow_packed(ofP, bgrow, newcols, 0);
 
     pnm_freerow(bgrow);
 }
 
 
+
+static xel
+regressColor(int  const format,
+             int  const maxval,
+             xel  const color) {
+
+    gray const grayval = (gray) ppm_luminosity(color);
+
+    xel retval;
+
+    switch (PNM_FORMAT_TYPE(format)) {
+    case PBM_TYPE:
+      {
+        gray const threshold = maxval / 2;
+
+        retval = grayval > threshold ?
+            pnm_whitexel(maxval, format) : pnm_blackxel(maxval, format);
+      }
+        break;
+    case PGM_TYPE:
+        retval.b = grayval;
+        retval.r = retval.g = 0;
+        break;
+    case PPM_TYPE:
+        retval = color;
+        break;
+    default:
+        pm_error("internal error -- impossible value of PNM format "
+                 "type in regressColor");
+    }
+    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
-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) {
+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) {
 /*----------------------------------------------------------------------------
-  General padding routine (logic works for PBM)
+  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);
-    xel background;
+        /* 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,
+           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);
+        /* A row of background color (for top and bottom padding) */
+
+    int    newformat;
+    pixval newmaxval;
+    xel    background;
+
     unsigned int row, col;
 
-    if (colorWhite)
-        background = pnm_whitexel(maxval, format);
-    else
-        background = pnm_blackxel(maxval, format);
+    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)
-        xelrow[col] = bgrow[col] = background;
+        bgrow[col] = background;
+
+    pnm_writepnminit(ofP, newcols, rows + tpad + bpad,
+                     newmaxval, newformat, 0);
 
-    pnm_writepnminit(stdout, newcols, rows + tpad + bpad, maxval, format, 0);
+    /* Write top padding */
 
     for (row = 0; row < tpad; ++row)
-        pnm_writepnmrow(stdout, bgrow, newcols, maxval, format, 0);
+        pnm_writepnmrow(ofP, bgrow, newcols, newmaxval, newformat, 0);
 
-    for (row = 0; row < rows; ++row) {
-        pnm_readpnmrow(ifP, &xelrow[lpad], cols, maxval, format);
-        pnm_writepnmrow(stdout, xelrow, newcols, maxval, format, 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 */
+        for (col = 0; col < newcols; ++col)
+            xelrow[col] = bgrow[col] = background;
+
+        for (row = 0; row < rows; ++row) {
+            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(stdout, bgrow, newcols, maxval, format, 0);
+        pnm_writepnmrow(ofP, bgrow, newcols, newmaxval, newformat, 0);
 
-    pnm_freerow(xelrow);
     pnm_freerow(bgrow);
 }
 
@@ -614,43 +1105,25 @@ padGeneral(FILE *       const ifP,
 int
 main(int argc, const char ** argv) {
 
-    struct cmdlineInfo cmdline;
+    struct CmdlineInfo cmdline;
     FILE * ifP;
 
     xelval maxval;
     int rows, cols, newcols, format;
-    bool depr_cmd; /* use deprecated commandline interface */
     unsigned int lpad, rpad, tpad, bpad;
 
     pm_proginit(&argc, argv);
 
-    /* detect deprecated options */
-    depr_cmd = FALSE;  /* initial assumption */
-    if (argc > 1 && argv[1][0] == '-') {
-        if (argv[1][1] == 't' || argv[1][1] == 'b'
-            || argv[1][1] == 'l' || argv[1][1] == 'r') {
-            if (argv[1][2] >= '0' && argv[1][2] <= '9')
-                depr_cmd = TRUE;
-        }
-    }
-    if (argc > 2 && argv[2][0] == '-') {
-        if (argv[2][1] == 't' || argv[2][1] == 'b'
-            || argv[2][1] == 'l' || argv[2][1] == 'r') {
-            if (argv[2][2] >= '0' && argv[2][2] <= '9')
-                depr_cmd = TRUE;
-        }
-    }
+    validateNoOldOptionSyntax(argc, argv);
 
-    if (depr_cmd)
-        parseCommandLineOld(argc, argv, &cmdline);
-    else
-        parseCommandLine(argc, argv, &cmdline);
+    parseCommandLine(argc, argv, &cmdline);
 
-    ifP = pm_openr(cmdline.input_filespec);
+    ifP = pm_openr(cmdline.inputFileNm);
 
     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);
 
@@ -659,15 +1132,40 @@ main(int argc, const char ** argv) {
     if (cmdline.reportonly)
         reportPadSizes(cols, rows, lpad, rpad, tpad, bpad);
     else {
-        if (PNM_FORMAT_TYPE(format) == PBM_TYPE)
-            padPbm(ifP, cols, rows, format, newcols, lpad, rpad, tpad, bpad,
-                   !!cmdline.white);
-        else
-            padGeneral(ifP, cols, rows, maxval, format,
-                       newcols, lpad, rpad, tpad, bpad, !!cmdline.white);
+        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
+                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);
 
     return 0;
 }
+
+
+