about summary refs log tree commit diff
path: root/editor/pnmcrop.c
diff options
context:
space:
mode:
Diffstat (limited to 'editor/pnmcrop.c')
-rw-r--r--editor/pnmcrop.c613
1 files changed, 613 insertions, 0 deletions
diff --git a/editor/pnmcrop.c b/editor/pnmcrop.c
new file mode 100644
index 00000000..5fafbaac
--- /dev/null
+++ b/editor/pnmcrop.c
@@ -0,0 +1,613 @@
+/* pnmcrop.c - crop a portable anymap
+**
+** Copyright (C) 1988 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+/* IDEA FOR EFFICIENCY IMPROVEMENT:
+
+   If we have to read the input into a regular file because it is not
+   seekable (a pipe), find the borders as we do the copy, so that we
+   do 2 passes through the file instead of 3.  Also find the background
+   color in that pass to save yet another pass with -sides.
+*/
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+
+#include "pnm.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+
+enum bg_choice {BG_BLACK, BG_WHITE, BG_DEFAULT, BG_SIDES};
+
+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;
+    unsigned int left, right, top, bottom;
+    unsigned int verbose;
+    unsigned int margin;
+    const char * borderfile;  /* NULL if none */
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo *cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry * option_def;
+        /* Instructions to OptParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int blackOpt, whiteOpt, sidesOpt;
+    unsigned int marginSpec, borderfileSpec;
+    
+    unsigned int option_def_index;
+
+    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, &cmdlineP->left,      0);
+    OPTENT3(0, "right",      OPT_FLAG, NULL, &cmdlineP->right,     0);
+    OPTENT3(0, "top",        OPT_FLAG, NULL, &cmdlineP->top,       0);
+    OPTENT3(0, "bottom",     OPT_FLAG, NULL, &cmdlineP->bottom,    0);
+    OPTENT3(0, "verbose",    OPT_FLAG, NULL, &cmdlineP->verbose,   0);
+    OPTENT3(0, "margin",     OPT_UINT,   &cmdlineP->margin,    
+            &marginSpec,     0);
+    OPTENT3(0, "borderfile", OPT_STRING, &cmdlineP->borderfile,
+            &borderfileSpec, 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 */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (argc-1 == 0)
+        cmdlineP->inputFilespec = "-";  /* stdin */
+    else if (argc-1 == 1)
+        cmdlineP->inputFilespec = argv[1];
+    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");
+    else if (blackOpt)
+        cmdlineP->background = BG_BLACK;
+    else if (whiteOpt)
+        cmdlineP->background = BG_WHITE;
+    else if (sidesOpt)
+        cmdlineP->background = BG_SIDES;
+    else
+        cmdlineP->background = BG_DEFAULT;
+
+    if (!cmdlineP->left && !cmdlineP->right && !cmdlineP->top
+        && !cmdlineP->bottom) {
+        cmdlineP->left = cmdlineP->right = cmdlineP->top 
+            = cmdlineP->bottom = TRUE;
+    }
+
+    if (!marginSpec)
+        cmdlineP->margin = 0;
+
+    if (!borderfileSpec)
+        cmdlineP->borderfile = NULL;
+}
+
+
+
+static xel
+background3Corners(FILE * const ifP,
+                   int    const rows,
+                   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
+  background color.
+
+  Expect the file to be positioned to the start of the raster, and leave
+  it positioned arbitrarily.
+----------------------------------------------------------------------------*/
+    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 );
+
+    background = pnm_backgroundxel(xels, cols, rows, maxval, format);
+
+    pnm_freearray(xels, rows);
+
+    return background;
+}
+
+
+
+static xel
+background2Corners(FILE * const ifP,
+                   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
+  determine the background color.
+
+  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);
+
+    pnm_readpnmrow(ifP, xelrow, cols, maxval, format);
+
+    background = pnm_backgroundxelrow(xelrow, cols, maxval, format);
+
+    pnm_freerow(xelrow);
+
+    return background;
+}
+
+
+
+static xel
+computeBackground(FILE *         const ifP,
+                  int            const cols,
+                  int            const rows,
+                  xelval         const maxval,
+                  int            const format,
+                  enum bg_choice const backgroundChoice,
+                  int            const verbose) {
+/*----------------------------------------------------------------------------
+   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);
+        break;
+    case BG_BLACK:
+	    background = pnm_blackxel(maxval, format);
+        break;
+    case BG_SIDES: 
+        background = 
+            background3Corners(ifP, rows, cols, maxval, format);
+        break;
+    case BG_DEFAULT: 
+        background = 
+            background2Corners(ifP, cols, maxval, format);
+        break;
+    }
+
+    if (verbose)
+        pm_message("Background color is %s", 
+                   ppm_colorname(&background, maxval, TRUE /*hexok*/));
+
+    return(background);
+}
+
+
+
+static void
+findBordersInImage(FILE *         const ifP,
+                   unsigned int   const cols,
+                   unsigned int   const rows,
+                   xelval         const maxval,
+                   int            const format,
+                   xel            const backgroundColor,
+                   bool           const verbose, 
+                   bool *         const hasBordersP,
+                   unsigned int * const leftP,
+                   unsigned int * const rightP, 
+                   unsigned int * const topP,
+                   unsigned int * const bottomP) {
+/*----------------------------------------------------------------------------
+   Find the left, right, top, and bottom borders in the image 'ifP'.
+   Return their sizes in pixels as *leftP, *rightP, *topP, and *bottomP.
+   
+   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.
+-----------------------------------------------------------------------------*/
+    xel* xelrow;        /* A row of the input image */
+    int row;
+    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) {
+        int col;
+        int thisRowLeft;
+        int thisRowRight;
+
+        pnm_readpnmrow(ifP, xelrow, cols, maxval, format);
+        
+        col = 0;
+        while (PNM_EQUAL(xelrow[col], backgroundColor) && col < cols)
+            ++col;
+        thisRowLeft = col;
+
+        col = cols-1;
+        while (col >= thisRowLeft && PNM_EQUAL(xelrow[col], backgroundColor))
+            --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;
+                top = row;
+            }
+            bottom = row + 1;   /* New candidate */
+        }
+    }
+
+    if (right == -1)
+        *hasBordersP = FALSE;
+    else {
+        *hasBordersP = TRUE;
+        assert(right <= cols); assert(bottom <= rows);
+        *leftP       = left - 0;
+        *rightP      = cols - right;
+        *topP        = top - 0;
+        *bottomP     = rows - bottom;
+    }
+}
+
+
+
+static void
+findBordersInFile(const char *   const borderFileName,
+                  xel            const backgroundColor,
+                  bool           const verbose, 
+                  bool *         const hasBordersP,
+                  unsigned int * const leftP,
+                  unsigned int * const rightP, 
+                  unsigned int * const topP,
+                  unsigned int * const bottomP) {
+
+    FILE * borderFileP;
+    int cols;
+    int rows;
+    xelval maxval;
+    int format;
+    
+    borderFileP = pm_openr(borderFileName);
+    
+    pnm_readpnminit(borderFileP, &cols, &rows, &maxval, &format);
+    
+    findBordersInImage(borderFileP, cols, rows, maxval, format, 
+                       backgroundColor, verbose, hasBordersP,
+                       leftP, rightP, topP, bottomP);
+
+    pm_close(borderFileP);
+} 
+
+
+
+static void
+reportOneEdge(unsigned int const oldBorderSize,
+              unsigned int const newBorderSize,
+              const char * const place) {
+
+#define ending(n) (((n) > 1) ? "s" : "")
+
+    if (newBorderSize > oldBorderSize)
+        pm_message("Adding %u pixel%s to the %u-pixel %s border",
+                   newBorderSize - oldBorderSize,
+                   ending(newBorderSize - oldBorderSize),
+                   oldBorderSize, place);
+    else if (newBorderSize < oldBorderSize)
+        pm_message("Cropping %u pixel%s from the %u-pixel %s border",
+                   oldBorderSize - newBorderSize,
+                   ending(oldBorderSize - newBorderSize),
+                   oldBorderSize, place);
+    else
+        pm_message("Leaving %s border unchanged at %u pixel%s",
+                   place, oldBorderSize, ending(oldBorderSize));
+}        
+
+
+
+static void
+reportCroppingParameters(unsigned int const oldLeftBorderSize,
+                         unsigned int const oldRightBorderSize,
+                         unsigned int const oldTopBorderSize,
+                         unsigned int const oldBottomBorderSize,
+                         unsigned int const newLeftBorderSize,
+                         unsigned int const newRightBorderSize,
+                         unsigned int const newTopBorderSize,
+                         unsigned int const newBottomBorderSize) {
+
+    if (oldLeftBorderSize == 0 && oldRightBorderSize == 0 &&
+        oldTopBorderSize == 0 && oldBottomBorderSize == 0)
+        pm_message("No Border found.");
+
+    reportOneEdge(oldLeftBorderSize,   newLeftBorderSize,   "left"   );
+    reportOneEdge(oldRightBorderSize,  newRightBorderSize,  "right"  );
+    reportOneEdge(oldTopBorderSize,    newTopBorderSize,    "top"    );
+    reportOneEdge(oldBottomBorderSize, newBottomBorderSize, "bottom" );
+}
+
+
+
+
+static void
+fillRow(xel *        const xelrow,
+        unsigned int const cols,
+        xel          const color) {
+
+    unsigned int col;
+
+    for (col = 0; col < cols; ++col)
+        xelrow[col] = color;
+}
+
+
+
+static void
+writeCropped(FILE *       const ifP,
+             unsigned int const cols,
+             unsigned int const rows,
+             xelval       const maxval,
+             int          const format,
+             unsigned int const oldLeftBorder,
+             unsigned int const oldRightBorder,
+             unsigned int const oldTopBorder,
+             unsigned int const oldBottomBorder,
+             unsigned int const newLeftBorder,
+             unsigned int const newRightBorder,
+             unsigned int const newTopBorder,
+             unsigned int const newBottomBorder,
+             xel          const backgroundColor,
+             FILE *       const ofP) {
+
+    /* In order to do cropping, padding or both at the same time, we have
+       a rather complicated row buffer:
+
+       xelrow[] is both the input and the output buffer.  So it contains
+       the foreground pixels, the original border pixels, and the new
+       border pixels.
+
+       The foreground pixels are in the center of the
+       buffer, starting at Column 'foregroundLeft' and going to
+       'foregroundRight'.
+
+       There is space to the left of that for the larger of the input
+       left border and the output left border.
+
+       Similarly, there is space to the right of the foreground pixels
+       for the larger of the input right border and the output right
+       border.
+
+       We have to read an entire row, including the pixels we'll be
+       leaving out of the output, so we pick a starting location in
+       the buffer that lines up the first foreground pixel at
+       'foregroundLeft'.
+
+       When we output the row, we pick a starting location in the 
+       buffer that includes the proper number of left border pixels
+       before 'foregroundLeft'.
+
+       That's for the middle rows.  For the top and bottom, we just use
+       the left portion of xelrow[], starting at 0.
+    */
+
+    unsigned int const foregroundCols =
+        cols - oldLeftBorder - oldRightBorder;
+    unsigned int const outputCols     = 
+        foregroundCols + newLeftBorder + newRightBorder;
+    unsigned int const foregroundRows =
+        rows - oldTopBorder - oldBottomBorder;
+    unsigned int const outputRows     =
+        foregroundRows + newTopBorder + newBottomBorder;
+
+    unsigned int const foregroundLeft  = MAX(oldLeftBorder, newLeftBorder);
+        /* Index into xelrow[] of leftmost pixel of foreground */
+    unsigned int const foregroundRight = foregroundLeft + foregroundCols;
+        /* Index into xelrow[] just past rightmost pixel of foreground */
+
+    unsigned int const allocCols =
+        foregroundRight + MAX(oldRightBorder, newRightBorder);
+
+    xel *xelrow;
+    unsigned int i;
+
+    assert(outputCols == newLeftBorder + foregroundCols + newRightBorder);
+    assert(outputRows == newTopBorder + foregroundRows + newBottomBorder);
+    
+    pnm_writepnminit(ofP, outputCols, outputRows, maxval, format, 0);
+
+    xelrow = pnm_allocrow(allocCols);
+
+    /* Read off existing top border */
+    for (i = 0; i < oldTopBorder; ++i)
+        pnm_readpnmrow(ifP, xelrow, cols, maxval, format);
+
+
+    /* Output new top border */
+    fillRow(xelrow, outputCols, backgroundColor);
+    for (i = 0; i < newTopBorder; ++i)
+        pnm_writepnmrow(ofP, xelrow, outputCols, maxval, format, 0);
+
+
+    /* Read and output foreground rows */
+    for (i = 0; i < foregroundRows; ++i) {
+        /* Set left border pixels */
+        fillRow(&xelrow[foregroundLeft - newLeftBorder], newLeftBorder,
+                backgroundColor);
+
+        /* Read foreground pixels */
+        pnm_readpnmrow(ifP, &(xelrow[foregroundLeft - oldLeftBorder]), cols,
+                       maxval, format);
+
+        /* Set right border pixels */
+        fillRow(&xelrow[foregroundRight], newRightBorder, backgroundColor);
+        
+        pnm_writepnmrow(ofP,
+                        &(xelrow[foregroundLeft - newLeftBorder]), outputCols,
+                        maxval, format, 0);
+    }
+
+    /* Read off existing bottom border */
+    for (i = 0; i < oldBottomBorder; ++i)
+        pnm_readpnmrow(ifP, xelrow, cols, maxval, format);
+
+    /* Output new bottom border */
+    fillRow(xelrow, outputCols, backgroundColor);
+    for (i = 0; i < newBottomBorder; ++i)
+        pnm_writepnmrow(ofP, xelrow, outputCols, maxval, format, 0);
+
+    pnm_freerow(xelrow);
+}
+
+
+
+static void
+determineNewBorders(struct cmdlineInfo const cmdline,
+                    unsigned int       const leftBorderSize,
+                    unsigned int       const rightBorderSize,
+                    unsigned int       const topBorderSize,
+                    unsigned int       const bottomBorderSize,
+                    unsigned int *     const newLeftSizeP,
+                    unsigned int *     const newRightSizeP,
+                    unsigned int *     const newTopSizeP,
+                    unsigned int *     const newBottomSizeP) {
+
+    *newLeftSizeP   = cmdline.left   ? cmdline.margin : leftBorderSize   ;
+    *newRightSizeP  = cmdline.right  ? cmdline.margin : rightBorderSize  ;
+    *newTopSizeP    = cmdline.top    ? cmdline.margin : topBorderSize    ;
+    *newBottomSizeP = cmdline.bottom ? cmdline.margin : bottomBorderSize ;
+}
+        
+
+
+int
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+    FILE * ifP;   
+        /* The program's regular input file.  Could be a seekable copy of it
+           in a temporary file.
+        */
+
+    xelval maxval;
+    int format;
+    int rows, cols;   /* dimensions of input image */
+    bool hasBorders;
+    unsigned int oldLeftBorder, oldRightBorder, oldTopBorder, oldBottomBorder;
+        /* The sizes of the borders in the input image */
+    unsigned int newLeftBorder, newRightBorder, newTopBorder, newBottomBorder;
+        /* The sizes of the borders in the output image */
+    xel background;
+    pm_filepos rasterpos;
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr_seekable(cmdline.inputFilespec);
+
+    pnm_readpnminit(ifP, &cols, &rows, &maxval, &format);
+
+    pm_tell2(ifP, &rasterpos, sizeof(rasterpos));
+
+    background = computeBackground(ifP, cols, rows, maxval, format,
+                                   cmdline.background, cmdline.verbose);
+
+    if (cmdline.borderfile) {
+        findBordersInFile(cmdline.borderfile,
+                          background, cmdline.verbose, &hasBorders,
+                          &oldLeftBorder, &oldRightBorder,
+                          &oldTopBorder,  &oldBottomBorder);
+    } else {
+        pm_seek2(ifP, &rasterpos, sizeof(rasterpos));
+
+        findBordersInImage(ifP, cols, rows, maxval, format, 
+                           background, cmdline.verbose, &hasBorders,
+                           &oldLeftBorder, &oldRightBorder,
+                           &oldTopBorder,  &oldBottomBorder);
+    }
+    if (!hasBorders)
+        pm_error("The image is entirely background; "
+                 "there is nothing to crop.");
+
+    determineNewBorders(cmdline, 
+                        oldLeftBorder, oldRightBorder,
+                        oldTopBorder,  oldBottomBorder,
+                        &newLeftBorder, &newRightBorder,
+                        &newTopBorder,  &newBottomBorder);
+
+    if (cmdline.verbose) 
+        reportCroppingParameters(oldLeftBorder, oldRightBorder,
+                                 oldTopBorder,  oldBottomBorder,
+                                 newLeftBorder, newRightBorder,
+                                 newTopBorder,  newBottomBorder);
+
+    pm_seek2(ifP, &rasterpos, sizeof(rasterpos));
+
+    writeCropped(ifP, cols, rows, maxval, format,
+                 oldLeftBorder, oldRightBorder,
+                 oldTopBorder,  oldBottomBorder,
+                 newLeftBorder, newRightBorder,
+                 newTopBorder,  newBottomBorder,
+                 background, stdout);
+
+    pm_close(stdout);
+    pm_close(ifP);
+    
+    return 0;
+}