about summary refs log tree commit diff
path: root/other/pamarith.c
diff options
context:
space:
mode:
Diffstat (limited to 'other/pamarith.c')
-rw-r--r--other/pamarith.c493
1 files changed, 353 insertions, 140 deletions
diff --git a/other/pamarith.c b/other/pamarith.c
index 3f44dc73..31f52a59 100644
--- a/other/pamarith.c
+++ b/other/pamarith.c
@@ -1,14 +1,18 @@
 #include <assert.h>
 #include <string.h>
 #include <limits.h>
+#include <math.h>
 
 #include "pm_c_util.h"
 #include "mallocvar.h"
+#include "nstring.h"
 #include "shhopt.h"
 #include "pam.h"
 
-enum function {FN_ADD, FN_SUBTRACT, FN_MULTIPLY, FN_DIVIDE, FN_DIFFERENCE,
-               FN_MINIMUM, FN_MAXIMUM, FN_MEAN, FN_COMPARE,
+static double const EPSILON = 1.0e-5;
+
+enum Function {FN_ADD, FN_SUBTRACT, FN_MULTIPLY, FN_DIVIDE, FN_DIFFERENCE,
+               FN_MINIMUM, FN_MAXIMUM, FN_MEAN, FN_EQUAL, FN_COMPARE,
                FN_AND, FN_OR, FN_NAND, FN_NOR, FN_XOR,
                FN_SHIFTLEFT, FN_SHIFTRIGHT
               };
@@ -16,16 +20,15 @@ enum function {FN_ADD, FN_SUBTRACT, FN_MULTIPLY, FN_DIVIDE, FN_DIFFERENCE,
 
 
 static bool
-isDyadic(enum function const function) {
+isDyadic(enum Function const function) {
 
     bool retval;
-    
+
     switch (function) {
     case FN_ADD:
     case FN_MULTIPLY:
     case FN_MINIMUM:
     case FN_MAXIMUM:
-    case FN_MEAN:
     case FN_AND:
     case FN_NAND:
     case FN_OR:
@@ -37,8 +40,10 @@ isDyadic(enum function const function) {
     case FN_DIFFERENCE:
     case FN_COMPARE:
     case FN_DIVIDE:
+    case FN_MEAN:
     case FN_SHIFTLEFT:
     case FN_SHIFTRIGHT:
+    case FN_EQUAL:
         retval = TRUE;
         break;
     }
@@ -51,9 +56,10 @@ struct CmdlineInfo {
     /* All the information the user supplied in the command line,
        in a form easy for the program to use.
     */
-    enum function function;
+    enum Function function;
     unsigned int operandCt;
     const char ** operandFileNames;
+    double closeness;
 };
 
 
@@ -71,12 +77,14 @@ parseCommandLine(int argc, const char ** const argv,
     optStruct3 opt;
 
     unsigned int option_def_index;
-    
+
     unsigned int addSpec, subtractSpec, multiplySpec, divideSpec,
         differenceSpec,
-        minimumSpec, maximumSpec, meanSpec, compareSpec,
+        minimumSpec, maximumSpec, meanSpec, equalSpec, compareSpec,
         andSpec, orSpec, nandSpec, norSpec, xorSpec,
-        shiftleftSpec, shiftrightSpec;
+        shiftleftSpec, shiftrightSpec, closenessSpec;
+
+    float closeness;
 
     MALLOCARRAY_NOFAIL(option_def, 100);
 
@@ -89,6 +97,7 @@ parseCommandLine(int argc, const char ** const argv,
     OPTENT3(0, "minimum",     OPT_FLAG,   NULL, &minimumSpec,    0);
     OPTENT3(0, "maximum",     OPT_FLAG,   NULL, &maximumSpec,    0);
     OPTENT3(0, "mean",        OPT_FLAG,   NULL, &meanSpec,       0);
+    OPTENT3(0, "equal",       OPT_FLAG,   NULL, &equalSpec,      0);
     OPTENT3(0, "compare",     OPT_FLAG,   NULL, &compareSpec,    0);
     OPTENT3(0, "and",         OPT_FLAG,   NULL, &andSpec,        0);
     OPTENT3(0, "or",          OPT_FLAG,   NULL, &orSpec,         0);
@@ -97,6 +106,7 @@ parseCommandLine(int argc, const char ** const argv,
     OPTENT3(0, "xor",         OPT_FLAG,   NULL, &xorSpec,        0);
     OPTENT3(0, "shiftleft",   OPT_FLAG,   NULL, &shiftleftSpec,  0);
     OPTENT3(0, "shiftright",  OPT_FLAG,   NULL, &shiftrightSpec, 0);
+    OPTENT3(0, "closeness",   OPT_FLOAT,  &closeness, &closenessSpec, 0);
 
     opt.opt_table = option_def;
     opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
@@ -106,7 +116,7 @@ parseCommandLine(int argc, const char ** const argv,
         /* Uses and sets argc, argv, and some of *cmdlineP and others. */
 
     if (addSpec + subtractSpec + multiplySpec + divideSpec + differenceSpec +
-        minimumSpec + maximumSpec + meanSpec + compareSpec +
+        minimumSpec + maximumSpec + meanSpec + equalSpec + compareSpec +
         andSpec + orSpec + nandSpec + norSpec + xorSpec +
         shiftleftSpec + shiftrightSpec > 1)
         pm_error("You may specify only one function");
@@ -127,6 +137,8 @@ parseCommandLine(int argc, const char ** const argv,
         cmdlineP->function = FN_MAXIMUM;
     else if (meanSpec)
         cmdlineP->function = FN_MEAN;
+    else if (equalSpec)
+        cmdlineP->function = FN_EQUAL;
     else if (compareSpec)
         cmdlineP->function = FN_COMPARE;
     else if (andSpec)
@@ -146,21 +158,35 @@ parseCommandLine(int argc, const char ** const argv,
     else
         pm_error("You must specify a function (e.g. '-add')");
 
+    if (closenessSpec) {
+        if (cmdlineP->function != FN_EQUAL)
+            pm_error("-closeness is valid only with -equal");
+        else {
+            if (closeness < 0 || closeness > 100)
+                pm_error("-closeness value %f is not a valid percentage",
+                         closeness);
+            cmdlineP->closeness = (double)closeness/100;
+        }
+    } else
+        cmdlineP->closeness = EPSILON;
+
     if (argc-1 < 2)
         pm_error("You must specify at least two arguments: the files which "
                  "are the operands of the function.  You specified %u",
                  argc-1);
     else {
         if (isDyadic(cmdlineP->function) && argc-1 > 2)
-            pm_error("You specified %u arguments, but a dyadic function.  "
-                     "For a dyadic function, you must specify 2 arguments:  "
-                     "the operands of the function", argc-1);
+            pm_error("You specified %u arguments, but a function "
+                     "which is not associative and commutative.  "
+                     "Unless a function is associative and commutative "
+                     "(like -add), you must specify exactly two arguments. ",
+                     argc-1);
         else {
             cmdlineP->operandCt = argc-1;
             cmdlineP->operandFileNames = &argv[1];
         }
     }
-}        
+}
 
 
 
@@ -179,11 +205,69 @@ enum category {
 
 
 
+static void
+validateSameWidthHeight(const struct pam * const inpam  /* array */,
+                        unsigned int       const operandCt) {
+    unsigned int i;
+
+    for (i = 1; i < operandCt; ++i) {
+
+        if (i > 0) {
+            if (inpam[i].width  != inpam[0].width ||
+                inpam[i].height != inpam[0].height) {
+                pm_error("The images must be the same width and height.  "
+                         "The first is %ux%ux%u, but another is %ux%ux%u",
+                         inpam[0].width, inpam[0].height, inpam[0].depth,
+                         inpam[i].width, inpam[i].height, inpam[i].depth);
+            }
+        }
+    }
+}
+
+
+
+static void
+validateCompatibleDepth(const struct pam * const inpam  /* array */,
+                        unsigned int       const operandCt) {
+
+    unsigned int i;
+    bool         haveNonUnityDepth;
+    unsigned int nonUnityDepth;
+
+    for (i = 0, haveNonUnityDepth = false; i < operandCt; ++i) {
+        if (inpam[i].depth != 1) {
+            if (haveNonUnityDepth) {
+                if (inpam[i].depth != nonUnityDepth)
+                    pm_error("The images must have the same depth "
+                             "or depth 1.  "
+                             "But one has depth %u and another %u",
+                             nonUnityDepth, inpam[i].depth);
+            } else {
+                haveNonUnityDepth = true;
+                nonUnityDepth = inpam[i].depth;
+            }
+        }
+    }
+}
+
+
+
+static void
+validateConsistentDimensions(const struct pam * const inpam  /* array */,
+                             unsigned int       const operandCt) {
+
+    validateSameWidthHeight(inpam, operandCt);
+
+    validateCompatibleDepth(inpam, operandCt);
+}
+
+
+
 static enum category
-functionCategory(enum function const function) {
+functionCategory(enum Function const function) {
 
     enum category retval;
-    
+
     switch (function) {
     case FN_ADD:
     case FN_SUBTRACT:
@@ -191,6 +275,7 @@ functionCategory(enum function const function) {
     case FN_MINIMUM:
     case FN_MAXIMUM:
     case FN_MEAN:
+    case FN_EQUAL:
     case FN_COMPARE:
     case FN_MULTIPLY:
     case FN_DIVIDE:
@@ -237,58 +322,147 @@ outFmtForCompare(int const format1,
 
 
 
+static unsigned int
+maxDepth(const struct pam * const pam,
+         unsigned int       const pamCt) {
+
+    unsigned int maxDepthSoFar;
+    unsigned int i;
+
+    assert(pamCt >= 1);
+
+    maxDepthSoFar = pam[0].depth;
+    for (i = 1; i < pamCt; ++i) {
+        if (pam[i].depth > maxDepthSoFar)
+            maxDepthSoFar = pam[i].depth;
+    }
+    return maxDepthSoFar;
+}
+
+
+
+static int
+maxFormat(const struct pam * const pam,
+          unsigned int       const pamCt) {
+
+    int maxFormatSoFar;
+    unsigned int i;
+
+    assert(pamCt >= 1);
+
+    maxFormatSoFar = pam[0].format;
+    for (i = 1; i < pamCt; ++i) {
+        if (pam[i].format > maxFormatSoFar)
+            maxFormatSoFar = pam[i].format;
+    }
+    return maxFormatSoFar;
+}
+
+
+
+static sample
+maxMaxval(const struct pam * const pam,
+          unsigned int       const pamCt) {
+
+    sample maxMaxvalSoFar;
+    unsigned int i;
+
+    assert(pamCt >= 1);
+
+    maxMaxvalSoFar = pam[0].maxval;
+    for (i = 1; i < pamCt; ++i) {
+        if (pam[i].maxval > maxMaxvalSoFar)
+            maxMaxvalSoFar = pam[i].maxval;
+    }
+    return maxMaxvalSoFar;
+}
+
+
+
+static bool
+maxvalsAreEqual(const struct pam * const pam,
+                unsigned int       const pamCt) {
+
+    bool equalSoFar;
+    unsigned int i;
+
+    assert(pamCt >= 1);
+
+    equalSoFar = true;
+
+    for (i = 1; i < pamCt; ++i) {
+        if (pam[i].maxval != pam[0].maxval)
+            equalSoFar = false;
+    }
+    return equalSoFar;
+}
+
+
+
 static void
-computeOutputType(struct pam *  const outpamP,
-                  struct pam    const inpam1,
-                  struct pam    const inpam2,
-                  enum function const function) {
+computeOutputType(struct pam *       const outpamP,
+                  const struct pam * const inpam,   /* array */
+                  unsigned int       const operandCt,
+                  enum Function      const function) {
+
+    assert(operandCt >= 1);
 
     outpamP->size        = sizeof(struct pam);
     outpamP->len         = PAM_STRUCT_SIZE(tuple_type);
     outpamP->file        = stdout;
     outpamP->plainformat = FALSE;
-    outpamP->height      = inpam1.height;
-    outpamP->width       = inpam1.width;
-    outpamP->depth       = MAX(inpam1.depth, inpam2.depth);
+    outpamP->height      = inpam[0].height;
+    outpamP->width       = inpam[0].width;
+    outpamP->depth       = maxDepth(inpam, operandCt);
 
-    if (function == FN_COMPARE)
-        outpamP->format = outFmtForCompare(inpam1.format, inpam2.format);
-    else
-        outpamP->format = MAX(inpam1.format, inpam2.format);
+
+    if (function == FN_COMPARE) {
+        assert(operandCt == 2);
+        outpamP->format = outFmtForCompare(inpam[0].format, inpam[1].format);
+    } else
+        outpamP->format = maxFormat(inpam, operandCt);
 
     switch (functionCategory(function)) {
     case CATEGORY_FRACTIONAL_ARITH:
         if (function == FN_COMPARE)
             outpamP->maxval = 2;
+        else if (function == FN_EQUAL)
+            outpamP->maxval = 1;
         else
-            outpamP->maxval = MAX(inpam1.maxval, inpam2.maxval);
+            outpamP->maxval = maxMaxval(inpam, operandCt);
         break;
     case CATEGORY_BITSTRING:
-        if (inpam2.maxval != inpam1.maxval)
+        if (!maxvalsAreEqual(inpam, operandCt))
             pm_error("For a bit string operation, the maxvals of the "
-                     "left and right image must be the same.  You have "
-                     "left=%u and right=%u", 
-                     (unsigned)inpam1.maxval, (unsigned)inpam2.maxval);
+                     "operand images must be the same.  Yours differ");
 
-        if (pm_bitstomaxval(pm_maxvaltobits(inpam1.maxval)) != inpam1.maxval)
+        if (pm_bitstomaxval(pm_maxvaltobits(inpam[0].maxval)) !=
+            inpam[0].maxval)
             pm_error("For a bit string operation, the maxvals of the inputs "
                      "must be a full binary count, i.e. a power of two "
                      "minus one such as 0xff.  You have 0x%x",
-                     (unsigned)inpam1.maxval);
+                     (unsigned)inpam[0].maxval);
 
-        outpamP->maxval = inpam1.maxval;
+        outpamP->maxval = inpam[0].maxval;
         break;
     case CATEGORY_SHIFT:
-        if (pm_bitstomaxval(pm_maxvaltobits(inpam1.maxval)) != inpam1.maxval)
+        if (pm_bitstomaxval(pm_maxvaltobits(inpam[0].maxval)) !=
+            inpam[0].maxval)
             pm_error("For a bit shift operation, the maxval of the left "
                      "input image "
                      "must be a full binary count, i.e. a power of two "
                      "minus one such as 0xff.  You have 0x%x",
-                     (unsigned)inpam1.maxval);
-        outpamP->maxval = inpam1.maxval;
+                     (unsigned)inpam[0].maxval);
+        outpamP->maxval = inpam[0].maxval;
     }
     outpamP->bytes_per_sample = (pm_maxvaltobits(outpamP->maxval)+7)/8;
-    strcpy(outpamP->tuple_type, inpam1.tuple_type);
+
+    if (outpamP->maxval > 1 &&
+        strneq(inpam[0].tuple_type, "BLACKANDWHITE", 13)) {
+
+        strcpy(outpamP->tuple_type, "");
+    } else
+        strcpy(outpamP->tuple_type, inpam[0].tuple_type);
 }
 
 
@@ -311,6 +485,21 @@ samplenSum(samplen      const operands[],
 
 
 static samplen
+samplenProduct(samplen      const operands[],
+               unsigned int const operandCt) {
+
+    unsigned int i;
+    double product;
+
+    for (i = 1, product = operands[0]; i < operandCt; ++i)
+        product *= operands[i];
+
+    return product;
+}
+
+
+
+static samplen
 samplenMin(samplen      const operands[],
            unsigned int const operandCt) {
 
@@ -358,24 +547,28 @@ samplenMean(samplen      const operands[],
 
 
 static samplen
-samplenProduct(samplen      const operands[],
-               unsigned int const operandCt) {
+samplenEqual(samplen      const operands[],
+             unsigned int const operandCt,
+             double       const closeness) {
 
     unsigned int i;
-    double product;
+    bool allEqual;
 
-    for (i = 1, product = operands[0]; i < operandCt; ++i)
-        product *= operands[i];
+    for (i = 1, allEqual = true; i < operandCt; ++i) {
+        if (fabs(operands[i]- operands[0]) > closeness)
+            allEqual = false;
+    }
 
-    return product;
+    return allEqual ? 1.0 : 0.0;
 }
 
 
 
 static samplen
-applyNormalizedFunction(enum function const function,
+applyNormalizedFunction(enum Function const function,
                         samplen       const operands[],
-                        unsigned int  const operandCt) {
+                        unsigned int  const operandCt,
+                        double        const closeness) {
 
     samplen result;
 
@@ -394,7 +587,7 @@ applyNormalizedFunction(enum function const function,
         operands[0] / operands[1] : 1.;
         break;
     case FN_DIFFERENCE:
-        result = operands[0] > operands[1] ? 
+        result = operands[0] > operands[1] ?
             operands[0] - operands[1] : operands[1] - operands[0];
         break;
     case FN_MINIMUM:
@@ -406,8 +599,11 @@ applyNormalizedFunction(enum function const function,
     case FN_MEAN:
         result = samplenMean(operands, operandCt);
         break;
+    case FN_EQUAL:
+        result = samplenEqual(operands, operandCt, closeness);
+        break;
     case FN_COMPARE:
-        result = 
+        result =
             operands[0] > operands[1] ?
             1. : operands[0] < operands[1] ?
             0. : .5;
@@ -423,16 +619,11 @@ applyNormalizedFunction(enum function const function,
 
 
 static void
-doNormalizedArith(struct pam *  const inpam1P,
-                  struct pam *  const inpam2P,
-                  struct pam *  const outpamP,
-                  enum function const function) {
-
-    /* Some of the logic in this subroutine is designed for future
-       expansion into non-dyadic computations.  But for now, all
-       computations have exactly two operands.
-    */
-    unsigned int const operandCt = 2;
+doNormalizedArith(const struct pam * const inpam,  /* array */
+                  unsigned int       const operandCt,
+                  const struct pam * const outpamP,
+                  enum Function      const function,
+                  double             const closeness) {
 
     tuplen ** tuplerown;
         /* tuplerown[0] is the current row in the first operand image */
@@ -444,38 +635,43 @@ doNormalizedArith(struct pam *  const inpam1P,
            computation
         */
     unsigned int * plane;
-        /* plane[0] is the plane number in the first operand image for 
+        /* plane[0] is the plane number in the first operand image for
            the current one-sample computation.  plane[1] is the plane number
            in the second operand image, etc.
          */
+    unsigned int i;
 
     MALLOCARRAY_NOFAIL(operands, operandCt);
     MALLOCARRAY_NOFAIL(plane, operandCt);
     MALLOCARRAY_NOFAIL(tuplerown, operandCt);
 
-    tuplerown[0] = pnm_allocpamrown(inpam1P);
-    tuplerown[1] = pnm_allocpamrown(inpam2P);
+    for (i = 0; i < operandCt; ++i)
+        tuplerown[i] = pnm_allocpamrown(&inpam[i]);
     tuplerownOut = pnm_allocpamrown(outpamP);
 
     for (row = 0; row < outpamP->height; ++row) {
+        unsigned int i;
         unsigned int col;
-        pnm_readpamrown(inpam1P, tuplerown[0]);
-        pnm_readpamrown(inpam2P, tuplerown[1]);
-        
+
+        for (i = 0; i < operandCt; ++i)
+            pnm_readpamrown(&inpam[i], tuplerown[i]);
+
         for (col = 0; col < outpamP->width; ++col) {
             unsigned int outplane;
-            
+
             for (outplane = 0; outplane < outpamP->depth; ++outplane) {
+                unsigned int i;
                 unsigned int op;
 
-                plane[0] = MIN(outplane, inpam1P->depth-1);
-                plane[1] = MIN(outplane, inpam2P->depth-1);
+                for (i = 0; i < operandCt; ++i)
+                    plane[i] = MIN(outplane, inpam[i].depth-1);
 
                 for (op = 0; op < operandCt; ++op)
                     operands[op] = tuplerown[op][col][plane[op]];
 
-                tuplerownOut[col][outplane] = 
-                    applyNormalizedFunction(function, operands, operandCt); 
+                tuplerownOut[col][outplane] =
+                    applyNormalizedFunction(function, operands, operandCt,
+                                            closeness);
                 assert(tuplerownOut[col][outplane] >= 0.);
                 assert(tuplerownOut[col][outplane] <= 1.);
             }
@@ -483,8 +679,8 @@ doNormalizedArith(struct pam *  const inpam1P,
         pnm_writepamrown(outpamP, tuplerownOut);
     }
 
-    pnm_freepamrown(tuplerown[0]);
-    pnm_freepamrown(tuplerown[1]);
+    for (i = 0; i < operandCt; ++i)
+        pnm_freepamrown(tuplerown[i]);
     free(tuplerown);
     pnm_freepamrown(tuplerownOut);
     free(plane);
@@ -512,6 +708,22 @@ sampleSum(sample       const operands[],
 
 
 static sample
+sampleProduct(sample       const operands[],
+              unsigned int const operandCt,
+              sample       const maxval) {
+
+    unsigned int i;
+    double product;
+
+    for (i = 0, product = 1.0; i < operandCt; ++i) {
+        product *= ((double)operands[i]/maxval);
+    }
+    return (sample)(product * maxval + 0.5);
+}
+
+
+
+static sample
 sampleMin(sample       const operands[],
           unsigned int const operandCt) {
 
@@ -561,17 +773,18 @@ sampleMean(sample       const operands[],
 
 
 static sample
-sampleProduct(sample       const operands[],
-              unsigned int const operandCt,
-              sample       const maxval) {
+sampleEqual(sample       const operands[],
+            unsigned int const operandCt,
+            sample       const maxval) {
 
     unsigned int i;
-    double product;
+    bool allEqual;
 
-    for (i = 0, product = 1.0; i < operandCt; ++i) {
-        product *= ((double)operands[i]/maxval);
+    for (i = 1, allEqual = true; i < operandCt; ++i) {
+        if (operands[i] != operands[0])
+            allEqual = false;
     }
-    return (sample)(product * maxval + 0.5);
+    return allEqual ? maxval : 0;
 }
 
 
@@ -654,7 +867,7 @@ sampleXor(sample       const operands[],
 
 
 static sample
-applyUnNormalizedFunction(enum function const function,
+applyUnNormalizedFunction(enum Function const function,
                           sample        const operands[],
                           unsigned int  const operandCt,
                           sample        const maxval) {
@@ -694,6 +907,9 @@ applyUnNormalizedFunction(enum function const function,
     case FN_MEAN:
         result = sampleMean(operands, operandCt);
         break;
+    case FN_EQUAL:
+        result = sampleEqual(operands, operandCt, maxval);
+        break;
     case FN_COMPARE:
         result = operands[0] > operands[1] ?
             2 : operands[0] < operands[1] ? 0 : 1;
@@ -738,21 +954,15 @@ applyUnNormalizedFunction(enum function const function,
 
 
 static void
-doUnNormalizedArith(struct pam *  const inpam1P,
-                    struct pam *  const inpam2P,
-                    struct pam *  const outpamP,
-                    enum function const function) {
+doUnNormalizedArith(const struct pam * const inpam,  /* array */
+                    unsigned int       const operandCt,
+                    const struct pam * const outpamP,
+                    enum Function      const function) {
 /*----------------------------------------------------------------------------
    Take advantage of the fact that both inputs and the output use the same
    maxval to do the computation without time-consuming normalization of
    sample values.
 -----------------------------------------------------------------------------*/
-    /* Some of the logic in this subroutine is designed for future
-       expansion into non-dyadic computations.  But for now, all
-       computations have exactly two operands.
-    */
-    unsigned int const operandCt = 2;
-
     sample const maxval = outpamP->maxval;
 
     tuple ** tuplerow;
@@ -765,42 +975,42 @@ doUnNormalizedArith(struct pam *  const inpam1P,
            computation
         */
     unsigned int * plane;
-        /* plane[0] is the plane number in the first operand image for 
+        /* plane[0] is the plane number in the first operand image for
            the current one-sample computation.  plane[1] is the plane number
            in the second operand image, etc.
          */
-
-    /* Input conditions: */
-    assert(inpam1P->maxval == maxval);
-    assert(inpam2P->maxval == maxval);
-    assert(outpamP->maxval == maxval);
+    unsigned int i;
 
     MALLOCARRAY_NOFAIL(operands, operandCt);
     MALLOCARRAY_NOFAIL(plane, operandCt);
     MALLOCARRAY_NOFAIL(tuplerow, operandCt);
 
-    tuplerow[0]   = pnm_allocpamrow(inpam1P);
-    tuplerow[1]   = pnm_allocpamrow(inpam2P);
+    for (i = 0; i < operandCt; ++i)
+        tuplerow[i]   = pnm_allocpamrow(&inpam[i]);
+
     tuplerowOut = pnm_allocpamrow(outpamP);
 
     for (row = 0; row < outpamP->height; ++row) {
+        unsigned int i;
         unsigned int col;
-        pnm_readpamrow(inpam1P, tuplerow[0]);
-        pnm_readpamrow(inpam2P, tuplerow[1]);
-        
+
+        for (i = 0; i < operandCt; ++i)
+            pnm_readpamrow(&inpam[i], tuplerow[i]);
+
         for (col = 0; col < outpamP->width; ++col) {
             unsigned int outplane;
-            
+
             for (outplane = 0; outplane < outpamP->depth; ++outplane) {
                 unsigned int op;
+                unsigned int i;
 
-                plane[0] = MIN(outplane, inpam1P->depth-1);
-                plane[1] = MIN(outplane, inpam2P->depth-1);
+                for (i = 0; i < operandCt; ++i)
+                    plane[i] = MIN(outplane, inpam[i].depth-1);
 
                 for (op = 0; op < operandCt; ++op)
                     operands[op] = tuplerow[op][col][plane[op]];
 
-                tuplerowOut[col][outplane] = 
+                tuplerowOut[col][outplane] =
                     applyUnNormalizedFunction(function, operands, operandCt,
                                               maxval);
 
@@ -811,8 +1021,8 @@ doUnNormalizedArith(struct pam *  const inpam1P,
         pnm_writepamrow(outpamP, tuplerowOut);
     }
 
-    pnm_freepamrow(tuplerow[0]);
-    pnm_freepamrow(tuplerow[1]);
+    for (i = 0; i < operandCt; ++i)
+        pnm_freepamrow(tuplerow[i]);
     free(tuplerow);
     pnm_freepamrow(tuplerowOut);
     free(plane);
@@ -825,57 +1035,60 @@ int
 main(int argc, const char *argv[]) {
 
     struct CmdlineInfo cmdline;
-    struct pam inpam1;
-    struct pam inpam2;
-    struct pam outpam;
-    FILE * if1P;
-    FILE * if2P;
-    
+    struct pam * inpam;  /* malloc'ed array */
+    FILE **      ifP;    /* malloc'ed array */
+    struct pam   outpam;
+
     pm_proginit(&argc, argv);
 
     parseCommandLine(argc, argv, &cmdline);
 
-    if (cmdline.operandCt != 2)
-        /* Code for > 2 operands not written yet */
-        pm_error("You specified %u operands.  We understand only 2.",
-                 cmdline.operandCt);
+    MALLOCARRAY(inpam, cmdline.operandCt);
+    MALLOCARRAY(ifP,   cmdline.operandCt);
 
-    if1P = pm_openr(cmdline.operandFileNames[0]);
-    if2P = pm_openr(cmdline.operandFileNames[1]);
-
-    pnm_readpaminit(if1P, &inpam1, PAM_STRUCT_SIZE(tuple_type));
-    pnm_readpaminit(if2P, &inpam2, PAM_STRUCT_SIZE(tuple_type));
+    if (!inpam || !ifP)
+        pm_error("Failed to allocate arrays for %u operands",
+                 cmdline.operandCt);
 
-    if (inpam1.width != inpam2.width || inpam1.height != inpam2.height)
-        pm_error("The two images must be the same width and height.  "
-                 "The first is %ux%ux%u, but the second is %ux%ux%u",
-                 inpam1.width, inpam1.height, inpam1.depth,
-                 inpam2.width, inpam2.height, inpam2.depth);
+    {
+        unsigned int i;
+        for (i = 0; i < cmdline.operandCt; ++i) {
+            ifP[i] = pm_openr(cmdline.operandFileNames[i]);
 
-    if (inpam1.depth != 1 && inpam2.depth != 1 && inpam1.depth != inpam2.depth)
-        pm_error("The two images must have the same depth or one of them "
-                 "must have depth 1.  The first has depth %u and the second "
-                 "has depth %u", inpam1.depth, inpam2.depth);
+            pnm_readpaminit(ifP[i], &inpam[i], PAM_STRUCT_SIZE(tuple_type));
+        }
+    }
+    validateConsistentDimensions(inpam, cmdline.operandCt);
 
-    computeOutputType(&outpam, inpam1, inpam2, cmdline.function);
+    computeOutputType(&outpam, inpam, cmdline.operandCt, cmdline.function);
 
     pnm_writepaminit(&outpam);
 
-    switch (functionCategory(cmdline.function)) {    
+    switch (functionCategory(cmdline.function)) {
     case CATEGORY_FRACTIONAL_ARITH:
-        if (inpam1.maxval == inpam2.maxval && inpam2.maxval == outpam.maxval)
-            doUnNormalizedArith(&inpam1, &inpam2, &outpam, cmdline.function);
+        if (maxvalsAreEqual(inpam, cmdline.operandCt) &&
+            inpam[0].maxval == outpam.maxval)
+            doUnNormalizedArith(inpam, cmdline.operandCt, &outpam,
+                                cmdline.function);
         else
-            doNormalizedArith(&inpam1, &inpam2, &outpam, cmdline.function);
+            doNormalizedArith(inpam, cmdline.operandCt, &outpam,
+                              cmdline.function, cmdline.closeness);
         break;
     case CATEGORY_BITSTRING:
     case CATEGORY_SHIFT:
-        doUnNormalizedArith(&inpam1, &inpam2, &outpam, cmdline.function);
+        doUnNormalizedArith(inpam, cmdline.operandCt, &outpam,
+                            cmdline.function);
         break;
     }
 
-    pm_close(if1P);
-    pm_close(if2P);
-    
+    {
+        unsigned int i;
+        for (i = 0; i < cmdline.operandCt; ++i)
+            pm_close(ifP[i]);
+    }
+
     return 0;
 }
+
+
+