about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--editor/pnmconvol.c2156
1 files changed, 818 insertions, 1338 deletions
diff --git a/editor/pnmconvol.c b/editor/pnmconvol.c
index 9d3a3ce9..ed701413 100644
--- a/editor/pnmconvol.c
+++ b/editor/pnmconvol.c
@@ -15,8 +15,10 @@
 
 /* A change history is at the bottom */
 
+#include <assert.h>
+
 #include "pm_c_util.h"
-#include "pnm.h"
+#include "pam.h"
 #include "shhopt.h"
 #include "mallocvar.h"
 
@@ -81,78 +83,51 @@ parseCommandLine(int argc, char ** argv,
 
 
 struct convKernel {
-    unsigned int const cols;
-    unsigned int const rows;
+    unsigned int cols;
+        /* Width of the convolution window */
+    unsigned int rows;
+        /* Height of the convolution window */
+    unsigned int planes;
+        /* Depth of the kernel -- this had better be the same as the
+           depth of the image being convolved.
+        */
     float ** weight[3];
-};
-
-convKernelDestroy(struct convKernel * const convKernelP) {
+        /* weight[PLANE][ROW][COL] is the weight to give to Plane PLANE
+           of the pixel at row ROW, column COL within the convolution window.
+           
+           One means full weight.
 
-    unsigned int plane;
-
-    for (plane = 0; plane < 3; ++plane)
-        pm_freearray(convKernelP->weight[plane]);
-
-    free(convKernelP);
-}
-
-struct convolveType {
-    void (*convolver)(struct pam *       const inpamP,
-                      struct pam *       const outpamP,
-                      const convKernel * const convKernelP);
+           It can have magnitude greater than or less than one.  It can be
+           positive or negative.  
+        */
 };
 
 
 
 static void
-computeKernel(struct pam *         const cpamP
-              tuple * const *      const ctuples, 
-              bool                 const offsetPgm,
-              struct convKernel ** const convKernelP) {
-/*----------------------------------------------------------------------------
-   Compute the convolution matrix in normalized form from the PGM
-   form.  Each element of the output matrix is the actual weight we give an
-   input pixel -- i.e. the thing by which we multiple a value from the
-   input image.
-
-   'offsetPgm' means the PGM convolution matrix is defined in offset form so
-   that it can represent negative values.  E.g. with maxval 100, 50 means
-   0, 100 means 50, and 0 means -50.  If 'offsetPgm' is false, 0 means 0
-   and there are no negative weights.
------------------------------------------------------------------------------*/
-    double const scale = (offsetPgm ? 2.0 : 1.0) / cpamP->maxval;
-    double const offset = offsetPgm ? - 1.0 : 0.0;
+warnBadKernel(struct convKernel * const convKernelP) {
 
-    struct convKernel * convKernelP;
     float sum[3];
     unsigned int plane;
     unsigned int row;
 
-    MALLOCVAR_NOFAIL(convKernelP);
-    
-    convKernelP->weight[PAM_RED_PLANE] =
-        pm_allocarray(cpamP->width, cpamP->height,
-                      sizeof(convKernelP->weight[PAM_RED_PLANE][0][0]));
-    
-    for (plane = 0; plane < 3; ++plane)
+    for (plane = 0; plane < convKernelP->planes; ++plane)
         sum[plane] = 0.0; /* initial value */
     
-    for (row = 0; row < cpamP->height; ++row) {
+    for (row = 0; row < convKernelP->rows; ++row) {
         unsigned int col;
-        for (col = 0; col < cpamP->width; ++col) {
+        for (col = 0; col < convKernelP->cols; ++col) {
             unsigned int plane;
-            for (plane = 0; plane < cpamP->depth; ++plane) {
-                sum += convKernelP->weight[plane][row][col] =
-                    ctuples[row][col][plane] * scale + offset);
+            for (plane = 0; plane < convKernelP->planes; ++plane)
+                sum[plane] += convKernelP->weight[plane][row][col];
         }
     }
 
-    switch (PNM_FORMAT_TYPE(cpamP->format)) {
-    case PPM_TYPE: {
+    if (convKernelP->planes == 3) {
         unsigned int plane;
         bool biased, negative;
         for (plane = 0, biased = false, negative = false;
-             plane < cpamP->depth;
+             plane < convKernelP->planes;
              ++plane) {
             if (sum[plane] < 0.9 || sum[plane] > 1.1)
                 biased = true;
@@ -171,19 +146,127 @@ computeKernel(struct pam *         const cpamP
             if (negative)
                 pm_message("Maybe you want the -nooffset option?");
         }
-    } break;
-
-    default:
+    } else if (convKernelP->planes == 1) {
         if (sum[0] < 0.9 || sum[0] > 1.1)
             pm_message("WARNING - this convolution matrix is biased.  "
                        "average weight = %f (unbiased would be 1)",
                        sum[0]);
-        break;
+        if (sum[0] < 0.0)
+            pm_message("Maybe you want the -nooffset option?");
     }
 }
 
 
 
+static void
+convKernelCreate(struct pam *         const cpamP,
+                 tuple * const *      const ctuples, 
+                 unsigned int         const depth,
+                 bool                 const offsetPgm,
+                 struct convKernel ** const convKernelPP) {
+/*----------------------------------------------------------------------------
+   Compute the convolution matrix in normalized form from the PGM
+   form.  Each element of the output matrix is the actual weight we give an
+   input pixel -- i.e. the thing by which we multiple a value from the
+   input image.
+
+   'depth' is the required number of planes in the kernel.  If 'ctuples' has
+   fewer planes than that, we duplicate as necessary.  E.g. if 'ctuples' is
+   from a PGM input file and we're convolving a PPM image, we'll make a
+   3-plane convolution kernel by repeating the one plane in 'ctuples'.  If
+   'ctuples' has more planes than specified, we ignore the higher numbered
+   ones.
+
+   'offsetPgm' means the PGM convolution matrix is defined in offset form so
+   that it can represent negative values.  E.g. with maxval 100, 50 means
+   0, 100 means 50, and 0 means -50.  If 'offsetPgm' is false, 0 means 0
+   and there are no negative weights.
+-----------------------------------------------------------------------------*/
+    double const scale = (offsetPgm ? 2.0 : 1.0) / cpamP->maxval;
+    double const offset = offsetPgm ? - 1.0 : 0.0;
+    unsigned int const planes = MIN(3, depth);
+
+    struct convKernel * convKernelP;
+    unsigned int plane;
+
+    MALLOCVAR_NOFAIL(convKernelP);
+
+    convKernelP->cols   = cpamP->width;
+    convKernelP->rows   = cpamP->height;
+    convKernelP->planes = planes;
+
+    for (plane = 0; plane < planes; ++plane) {
+        unsigned int row;
+
+        MALLOCARRAY_NOFAIL(convKernelP->weight[plane], cpamP->height);
+    
+        for (row = 0; row < cpamP->height; ++row) {
+            unsigned int col;
+
+            MALLOCARRAY_NOFAIL(convKernelP->weight[plane][row], cpamP->width);
+
+            for (col = 0; col < cpamP->width; ++col) {
+                sample const inValue = plane < cpamP->depth ?
+                    ctuples[row][col][plane] : ctuples[row][col][0];
+
+                convKernelP->weight[plane][row][col] =
+                    inValue * scale + offset;
+            }
+        }
+    }
+    warnBadKernel(convKernelP);
+        
+    *convKernelPP = convKernelP;
+}
+
+
+
+static void
+convKernelDestroy(struct convKernel * const convKernelP) {
+
+    unsigned int plane;
+
+    for (plane = 0; plane < convKernelP->planes; ++plane) {
+        unsigned int row;
+
+        for (row = 0; row < convKernelP->rows; ++row)
+            free(convKernelP->weight[plane][row]);
+
+        free(convKernelP->weight[plane]);
+    }
+    free(convKernelP);
+}
+
+
+
+static void
+validateEnoughImageToConvolve(const struct pam *        const inpamP,
+                              const struct convKernel * const convKernelP) {
+/*----------------------------------------------------------------------------
+   Abort program if the image isn't big enough in both directions to have
+   at least one convolved pixel.
+
+   The program could theoretically operate with an image smaller than that by
+   simply outputting the input unchanged (like it does with the edges of an
+   image anyway), but we're too lazy to write code for this special case.  The
+   simple code expects the unconvolved edges to exist full-size and some of it
+   convolves the first convolveable row and/or column specially and expects it
+   to exist.
+-----------------------------------------------------------------------------*/
+
+    if (inpamP->height < convKernelP->rows + 1)
+        pm_error("Image is too short (%u rows) to convolve with this "
+                 "%u-row convolution kernel.",
+                 inpamP->height, convKernelP->rows);
+    
+    if (inpamP->width < convKernelP->cols + 1)
+        pm_error("Image is too narrow (%u columns) to convolve with this "
+                 "%u-column convolution kernel.",
+                 inpamP->width, convKernelP->cols);
+}
+
+
+
 static tuple **
 allocRowbuf(struct pam * const pamP,
             unsigned int const height) {
@@ -193,12 +276,12 @@ allocRowbuf(struct pam * const pamP,
     MALLOCARRAY(rowbuf, height);
 
     if (rowbuf == NULL)
-        pm_error("Failed to allocate %u-row buffer");
+        pm_error("Failed to allocate %u-row buffer", height);
     else {
         unsigned int row;
     
         for (row = 0; row < height; ++row)
-            rowbuf[i] = pnm_allocpamrow(pamP);
+            rowbuf[row] = pnm_allocpamrow(pamP);
     }
 
     return rowbuf;
@@ -238,29 +321,29 @@ readAndScaleRow(struct pam * const inpamP,
 
 
 static void
-readInitialRowbuf(struct pam *        const inpamP,
-                  struct convKernel * const convKernelP,
-                  tuple **            const rowbuf,
-                  sample              const outputMaxval,
-                  unsigned int        const outputDepth) {
+readAndScaleRows(struct pam *              const inpamP,
+                 unsigned int              const count,
+                 tuple **                  const rowbuf,
+                 sample                    const outputMaxval,
+                 unsigned int              const outputDepth) {
 /*----------------------------------------------------------------------------
-  Read in one convolution kernel's worth of image, less one row,
-  into the row buffer rowbuf[], starting at the beginning of the buffer.
+  Read in 'count' rows into rowbuf[].
   
-  Scale the contents to maxval 'outputMaxval'.
+  Scale the contents to maxval 'outputMaxval' and expand to depth
+  'outputDepth'.
 -----------------------------------------------------------------------------*/
     unsigned int row;
 
-    for (row = 0; row < convKernelP->rows - 1; ++row)
+    for (row = 0; row < count; ++row)
         readAndScaleRow(inpamP, rowbuf[row], outputMaxval, outputDepth);
 }
 
 
 
 static void
-writeUnconvolvedTop(struct pam *        const outpamP,
-                    struct convKernel * const convKernelP,
-                    tuple **            const rowbuf) {
+writeUnconvolvedTop(struct pam *              const outpamP,
+                    const struct convKernel * const convKernelP,
+                    tuple **                  const rowbuf) {
 /*----------------------------------------------------------------------------
    Write out the top part that we can't convolve because the convolution
    kernel runs off the top of the image.
@@ -277,20 +360,25 @@ writeUnconvolvedTop(struct pam *        const outpamP,
 
 
 static void
-writeUnconvolvedBottom(struct pam *        const outpamP,
-                       struct convKernel * const convKernelP,
-                       tuple **            const circMap) {
+writeUnconvolvedBottom(struct pam *              const outpamP,
+                       const struct convKernel * const convKernelP,
+                       unsigned int              const windowHeight,
+                       tuple **                  const circMap) {
 /*----------------------------------------------------------------------------
   Write out the bottom part that we can't convolve because the convolution
   kernel runs off the bottom of the image.
 
-  Assume the end of the image is in the row buffer, mapped by 'circMap'
-  such that the top of the window is circMap[0].
+  Assume the 'windowHeight' rows at the bottom of the image is in the row
+  buffer, mapped by 'circMap' such that the top of the window is circMap[0].
 -----------------------------------------------------------------------------*/
     unsigned int row;
 
-    for (row = convKernelP->rows / 2 + 1; row < convKernelP->rows; ++row)
+    for (row = windowHeight - convKernelP->rows / 2;
+         row < windowHeight;
+         ++row) {
+
         pnm_writepamrow(outpamP, circMap[row]);
+    }
 }
 
 
@@ -298,6 +386,7 @@ writeUnconvolvedBottom(struct pam *        const outpamP,
 static void
 setupCircMap(tuple **     const circMap,
              tuple **     const rowbuf,
+             unsigned int const windowHeight,
              unsigned int const topRowbufRow) {
 /*----------------------------------------------------------------------------
   Set up circMap[] to reflect the case that index 'topRowbufRow' of rowbuf[]
@@ -308,7 +397,7 @@ setupCircMap(tuple **     const circMap,
 
     i = 0;
 
-    for (row = topRowbufRow; row < convKernelP->rows; ++i, ++row)
+    for (row = topRowbufRow; row < windowHeight; ++i, ++row)
         circMap[i] = rowbuf[row];
 
     for (row = 0; row < topRowbufRow; ++row, ++i)
@@ -318,12 +407,22 @@ setupCircMap(tuple **     const circMap,
 
 
 static void
-convolveGeneralRowPlane(struct pam *        const pamP,
-                        tuple **            const circMap,
-                        struct convKernel * const convKernelP,
-                        unsigned int        const plane,
-                        tuple *             const outputrow) {
+convolveGeneralRowPlane(struct pam *              const pamP,
+                        tuple **                  const window,
+                        const struct convKernel * const convKernelP,
+                        unsigned int              const plane,
+                        tuple *                   const outputrow) {
+/*----------------------------------------------------------------------------
+   Given a window of input window[], where window[0] is the top row of the
+   window and the window is the height of the convolution kernel, convolve
+   Plane 'plane' of the row at the center of the window.
+
+   Return the convolved row as outputrow[].
 
+   *pamP describes the rows in window[] (but not the number of rows).
+
+   *convKernelP is the convolution kernel to use.
+-----------------------------------------------------------------------------*/
     unsigned int const crowso2 = convKernelP->rows / 2;
     unsigned int const ccolso2 = convKernelP->cols / 2;
 
@@ -331,14 +430,14 @@ convolveGeneralRowPlane(struct pam *        const pamP,
     
     for (col = 0; col < pamP->width; ++col) {
         if (col < ccolso2 || col >= pamP->width - ccolso2)
-            outputrow[col][plane] = circMap[crowso2][col][plane];
+            outputrow[col][plane] = window[crowso2][col][plane];
         else {
             unsigned int const leftcol = col - ccolso2;
             unsigned int crow;
             float sum;
             sum = 0.0;
-            for (crow = 0; crow < crows; ++crow) {
-                tuple ** const leftrptr = &circMap[crow][leftcol];
+            for (crow = 0; crow < convKernelP->rows; ++crow) {
+                const tuple * const leftrptr = &window[crow][leftcol];
                 unsigned int ccol;
                 for (ccol = 0; ccol < convKernelP->cols; ++ccol)
                     sum += leftrptr[ccol][plane] *
@@ -352,9 +451,9 @@ convolveGeneralRowPlane(struct pam *        const pamP,
 
 
 static void
-convolveGeneral(struct pam *        const inpamP,
-                struct pam *        const outpamP,
-                struct convKernel * const convKernelP) {
+convolveGeneral(struct pam *              const inpamP,
+                struct pam *              const outpamP,
+                const struct convKernel * const convKernelP) {
 /*----------------------------------------------------------------------------
    Do the convolution without taking advantage of any useful redundancy in the
    convolution matrix.
@@ -374,8 +473,11 @@ convolveGeneral(struct pam *        const inpamP,
     tuple * outputrow;
         /* The convolved row to be output */
     unsigned int row;
+        /* Row number of the bottom of the current convolution window;
+           i.e. the row to be read or just read from the input file.
+        */
 
-    rowbuf = allocRowbuf(pamP, convKernelP->rows);
+    rowbuf = allocRowbuf(outpamP, convKernelP->rows);
     MALLOCARRAY_NOFAIL(circMap, convKernelP->rows);
     outputrow = pnm_allocpamrow(outpamP);
 
@@ -383,7 +485,8 @@ convolveGeneral(struct pam *        const inpamP,
 
     assert(convKernelP->rows > 0);
 
-    readInitialRowbuf(inpamP, convKernelP, rowbuf, outpamP->maxval);
+    readAndScaleRows(inpamP, convKernelP->rows - 1, rowbuf,
+                      outpamP->maxval, outpamP->depth);
 
     writeUnconvolvedTop(outpamP, convKernelP, rowbuf);
 
@@ -394,7 +497,10 @@ convolveGeneral(struct pam *        const inpamP,
     for (row = convKernelP->rows - 1; row < inpamP->height; ++row) {
         unsigned int const rowbufRow = row % convKernelP->rows;
 
-        setupCircMap(circMap, rowbuf, (row + 1) % convKernelP->rows);
+        unsigned int plane;
+
+        setupCircMap(circMap, rowbuf, convKernelP->rows,
+                     (row + 1) % convKernelP->rows);
 
         readAndScaleRow(inpamP, rowbuf[rowbufRow],
                         outpamP->maxval, outpamP->depth);
@@ -405,7 +511,7 @@ convolveGeneral(struct pam *        const inpamP,
 
         pnm_writepamrow(outpamP, outputrow);
     }
-    writeUnconvolvedBottom(outpamP, convKernelP, circMap);
+    writeUnconvolvedBottom(outpamP, convKernelP, convKernelP->rows, circMap);
 
     freeRowbuf(rowbuf, convKernelP->rows);
 }
@@ -452,51 +558,90 @@ freeSum(sample **    const sum,
 
 
 static void
-convolveAndComputeColumnSums(struct pam *        const inpamP,
-                             struct pam *        const outpamP,
-                             tuple **            const rows,
-                             struct convKernel * const convKernelP,
-                             unsigned int        conts plane,
-                             tuple *             const outputrow,
-                             sample **           const convColumnSum) {
+computeInitialColumnSums(struct pam *              const pamP,
+                         tuple **                  const window,
+                         const struct convKernel * const convKernelP,
+                         sample **                 const convColumnSum) {
+/*----------------------------------------------------------------------------
+  Add up the sum of each column of window[][], whose rows are described
+  by *inpamP.  The window's height is that of tthe convolution kernel
+  *convKernelP.
 
-    unsigned int const ccolso2 = convKernelP->cols / 2;
-    float        const weight  = convKernelP->weight[plane][0][0];
+  Return it as convColumnSum[][].
+-----------------------------------------------------------------------------*/
+    unsigned int plane;
 
-    unsigned int col;
-    sample matrixSum;
+    for (plane = 0; plane < pamP->depth; ++plane) {
+        unsigned int col;
 
-    for (col = 0; col < inpamP->width; ++col)
-        convColumnSum[col] = 0;      /* Initial value */
+        for (col = 0; col < pamP->width; ++col) {
+            unsigned int row;
+            for (row = 0, convColumnSum[plane][col] = 0;
+                 row < convKernelP->rows;
+                 ++row)
+                convColumnSum[plane][col] += window[row][col][plane];
+        }            
+    }
+}
+
+
+
+static void
+convolveRowWithColumnSumsMean(const struct convKernel * const convKernelP,
+                              struct pam *              const pamP,
+                              tuple **                  const window,
+                              tuple *                   const outputrow,
+                              sample **                 const convColumnSum) {
+/*----------------------------------------------------------------------------
+  Convolve the rows in window[][] -- one convolution kernel's worth, where
+  window[0] is the top.  Put the result in outputrow[].
+
+  Use convColumnSum[][]: the sum of the pixels in each column over the
+  convolution window, where convColumnSum[P][C] is the sum for Plane P of
+  Column C.
+
+  Assume the convolution weight is the same everywhere within the convolution
+  matrix.  Ergo, we don't need any more information about the contents of a
+  column than the sum of its pixels.
+
+  Except that we need the individual input pixels for the edges (which can't
+  be convolved because the convolution window runs off the edge).
+-----------------------------------------------------------------------------*/
+    unsigned int plane;
+    
+    for (plane = 0; plane < pamP->depth; ++plane) {
+        unsigned int const crowso2 = convKernelP->rows / 2;
+        unsigned int const ccolso2 = convKernelP->cols / 2;
+        float const weight = convKernelP->weight[plane][0][0];
+
+        unsigned int col;
+        sample gisum;
+
+        gisum = 0;
+        for (col = 0; col < pamP->width; ++col) {
+            if (col < ccolso2 || col >= pamP->width - ccolso2)
+                outputrow[col][plane] = window[crowso2][col][plane];
+            else if (col == ccolso2) {
+                unsigned int const leftcol = col - ccolso2;
 
-    for (col = 0, matrixSum = 0; col < inpamP->width; ++col) {
-        if (col < ccolso2 || col >= inpamP->width - ccolso2)
-            outputrow[col] = circMap[crowso2][col];
-        else if (col == ccolso2) {
-            unsigned int const leftcol = col - ccolso2;
-            unsigned int crow;
-            unsigned int ccol;
-            for (crow = 0; crow < convKernelP->rows; ++crow) {
-                tuple ** const temprptr = &circMap[crow][leftcol];
                 unsigned int ccol;
+
                 for (ccol = 0; ccol < convKernelP->cols; ++ccol)
-                    convColumnSum[leftcol + ccol] += temprptr[ccol][plane];
+                    gisum += convColumnSum[plane][leftcol + ccol];
+
+                outputrow[col][plane] =
+                    MIN(pamP->maxval, MAX(0, gisum * weight + 0.5));
+            } else {
+                /* Column numbers to subtract or add to isum */
+                unsigned int const subcol = col - ccolso2 - 1;
+                unsigned int const addcol = col + ccolso2;  
+
+                gisum -= convColumnSum[plane][subcol];
+                gisum += convColumnSum[plane][addcol];
+
+                outputrow[col][plane] =
+                    MIN(pamP->maxval, MAX(0, gisum * weight + 0.5));
             }
-            for (ccol = 0; ccol < convKernelP->cols; ++ccol)
-                matrixSum += convColumnSum[leftcol + ccol];
-            outputrow[col][plane] =
-                MIN(outpamP->maxval, MAX(0, matrixSum * weight + 0.5));
-        } else {
-            /* Column numbers to subtract or add to isum */
-            unsigned int const subcol = col - ccolso2 - 1;
-            unsigned int const addcol = col + ccolso2;  
-            unsigned int crow;
-            for (crow = 0; crow < convKernelP->rows; ++crow)
-                convColumnSum[addcol] += circMap[crow][addcol][plane];
-            matrixSum =
-                matrixSum - convColumnSum[subcol] + convColumnSum[addcol];
-            outputrow[col][plane] =
-                MIN(outpamP->maxval, MAX(0, matrixSum * weight + 0.5));
         }
     }
 }
@@ -504,47 +649,94 @@ convolveAndComputeColumnSums(struct pam *        const inpamP,
 
 
 static void
-convolveAndComputeColumnSums(struct pam *        const inpamP,
-                             struct pam *        const outpamP,
-                             tuple **            const rows,
-                             struct convKernel * const convKernelP,
-                             tuple *             const outputrow,
-                             sample **           const convColumnSum) {
+convolveRowWithColumnSumsVertical(
+    const struct convKernel * const convKernelP,
+    struct pam *              const pamP,
+    tuple **                  const window,
+    tuple *                   const outputrow,
+    sample **                 const convColumnSum) {
 /*----------------------------------------------------------------------------
-  Convolve the rows in rows[] -- one convolution kernel's worth, where
-  rows[0] is the top.  Put the result in outputrow[].
+  Convolve the rows in window[][] -- one convolution kernel's worth, where
+  window[0] is the top.  Put the result in outputrow[].
+
+  Use convColumnSum[][]: the sum of the pixels in each column over the
+  convolution window, where convColumnSum[P][C] is the sum for Plane P of
+  Column C.
 
-  Along the way, add up the sum of each column and return that as
-  convColumnSum[].
+  Assume the convolution weight is the same everywhere within a column.  Ergo,
+  we don't need any more information about the contents of a column than the
+  sum of its pixels.
+
+  Except that we need the individual input pixels for the edges (which can't
+  be convolved because the convolution window runs off the edge).
 -----------------------------------------------------------------------------*/
+    unsigned int const crowso2 = convKernelP->rows / 2;
+    unsigned int const ccolso2 = convKernelP->cols / 2;
+
     unsigned int plane;
 
-    for (plane = 0; plane < outpamP->depth; ++plane) 
-        convolveAndComputeColumnSumsPlane(inpamP, outpamP,
-                                          rows, convKernelP, plane,
-                                          outputrow, convColumnSum[plane]);
+    for (plane = 0; plane < pamP->depth; ++plane) {
+        unsigned int col;
+    
+        for (col = 0; col < pamP->width; ++col) {
+            if (col < ccolso2 || col >= pamP->width - ccolso2)
+                outputrow[col][plane] = window[crowso2][col][plane];
+            else {
+                unsigned int const leftcol = col - ccolso2;
+                unsigned int ccol;
+                float sum;
+
+                sum = 0.0;
+
+                for (ccol = 0; ccol < convKernelP->cols; ++ccol)
+                    sum += convColumnSum[plane][leftcol + ccol] *
+                        convKernelP->weight[plane][0][ccol];
+
+                outputrow[col][plane] = MIN(pamP->maxval, MAX(0, sum + 0.5));
+            }
+        }
+    }
 }
 
 
 
 static void
-convolveMeanRowPlane(struct pam *        const pamP,
-                     tuple **            const circMap,
-                     struct convKernel * const convKernelP,
-                     unsigned int        const plane,
-                     tuple *             const outputrow,
-                     sample *            const convColumnSum) {
+convolveMeanRowPlane(struct pam *              const pamP,
+                     tuple **                  const window,
+                     const struct convKernel * const convKernelP,
+                     unsigned int              const plane,
+                     tuple *                   const outputrow,
+                     sample *                  const convColumnSum) {
+/*----------------------------------------------------------------------------
+  Convolve plane 'plane' of one row of the image.  window[] is a vertical
+  window of the input image, one convolution kernel plus one row high.  The
+  top row (window[0] is the row that just passed out of the convolution
+  window, whereas the bottom row is the row that just entered it.
 
+  *pamP describes the tuple rows in window[] and also 'outputrow' (they are
+  the same).
+
+  Return the convolution result as outputrow[].
+
+  We update convColumnSum[] for use in convolving later rows.
+-----------------------------------------------------------------------------*/
+    unsigned int const crowso2 = convKernelP->rows / 2;
     unsigned int const ccolso2 = convKernelP->cols / 2;
     float const weight = convKernelP->weight[plane][0][0];
+    unsigned int const subrow = 0;
+        /* Row just above convolution window -- what we subtract from
+           running sum
+        */
+    unsigned int const addrow = 1 + (convKernelP->rows - 1);
+        /* Bottom row of convolution window: What we add to running sum */
 
     unsigned int col;
     sample gisum;
 
     gisum = 0;
-    for (col = 0; col < cols; ++col) {
-        if (col < ccolso2 || col >= cols - ccolso2)
-            outputrow[col] = circMap[crowso2][col];
+    for (col = 0; col < pamP->width; ++col) {
+        if (col < ccolso2 || col >= pamP->width - ccolso2)
+            outputrow[col][plane] = window[crowso2][col][plane];
         else if (col == ccolso2) {
             unsigned int const leftcol = col - ccolso2;
 
@@ -554,34 +746,43 @@ convolveMeanRowPlane(struct pam *        const pamP,
                 sample * const thisColumnSumP =
                     &convColumnSum[leftcol + ccol];
                 *thisColumnSumP = *thisColumnSumP
-                    - circMap[subrow][ccol][plane]
-                    + circMap[addrow][ccol][plane];
+                    - window[subrow][ccol][plane]
+                    + window[addrow][ccol][plane];
                 gisum += *thisColumnSumP;
             }
             outputrow[col][plane] =
-                MIN(outpamP->maxval, MAX(0, gisum * weight + 0.5));
+                MIN(pamP->maxval, MAX(0, gisum * weight + 0.5));
         } else {
             /* Column numbers to subtract or add to isum */
             unsigned int const subcol = col - ccolso2 - 1;
             unsigned int const addcol = col + ccolso2;  
 
             convColumnSum[addcol] = convColumnSum[addcol]
-                - circMap[subrow][addcol][plane]
-                + circMap[addrow][addcol][plane];
+                - window[subrow][addcol][plane]
+                + window[addrow][addcol][plane];
+
             gisum = gisum - convColumnSum[subcol] + convColumnSum[addcol];
 
             outputrow[col][plane] =
-                MIN(outpamP->maxval, MAX(0, gisum * weight + 0.5));
+                MIN(pamP->maxval, MAX(0, gisum * weight + 0.5));
         }
     }
 }
 
 
 
+typedef void convolver(struct pam *              const inpamP,
+                       struct pam *              const outpamP,
+                       const struct convKernel * const convKernelP);
+
+
+
+static convolver convolveMean;
+
 static void
-convolveMean(struct pam *        const inpamP,
-             struct pam *        const outpamP,
-             struct convKernel * const convKernelP) {
+convolveMean(struct pam *              const inpamP,
+             struct pam *              const outpamP,
+             const struct convKernel * const convKernelP) {
 /*----------------------------------------------------------------------------
   Mean Convolution
 
@@ -613,17 +814,22 @@ convolveMean(struct pam *        const inpamP,
            the sliding mean, we need to subtract off the row that the
            convolution kernel just slid past.
         */
-
+    unsigned int const crowso2 = convKernelP->rows / 2;
+        /* Number of rows of the convolution window above/below the current
+           row.  Note that the convolution window is always an odd number
+           of rows, so this rounds down.
+        */
     tuple ** rowbuf;
-        /* Same as in convolvePgmGeneral */
+        /* Same as in convolveGeneral */
     tuple ** circMap;
-        /* Same as in convolvePgmGeneral */
+        /* Same as in convolveGeneral */
     tuple * outputrow;
-        /* Same as in convolvePgmGeneral */
+        /* Same as in convolveGeneral */
     unsigned int row;
-        /* Row number of next row to read in from the file */
-    sample matrixSum;
-        /* Sum of all pixels in current convolution window */
+        /* Row number of the row currently being convolved; i.e. the row
+           at the center of the current convolution window and the row of
+           the output file to be output next.
+        */
     sample ** convColumnSum;  /* Malloc'd */
         /* convColumnSum[plane][col] is the sum of Plane 'plane' of all the
            pixels in the Column 'col' of the image within the current vertical
@@ -631,9 +837,8 @@ convolveMean(struct pam *        const inpamP,
            and we're now looking at Rows 10-15, convColumn[0][3] is the sum of
            Plane 0 of Column 3, Rows 10-15.
         */
-    unsigned int col;
 
-    rowbuf = allocRowbuf(pamP, windowHeight);
+    rowbuf = allocRowbuf(outpamP, windowHeight);
     MALLOCARRAY_NOFAIL(circMap, windowHeight);
     outputrow = pnm_allocpamrow(outpamP);
 
@@ -641,42 +846,45 @@ convolveMean(struct pam *        const inpamP,
 
     pnm_writepaminit(outpamP);
 
-    readInitialRowbuf(inpamP, convKernelP, rowbuf, outpamP->maxval);
+    readAndScaleRows(inpamP, convKernelP->rows, rowbuf,
+                      outpamP->maxval, outpamP->depth);
 
     writeUnconvolvedTop(outpamP, convKernelP, rowbuf);
 
-    /* Add a row to the window to have enough to convolve */
-    readAndScaleRow(inpamP, rowbuf[convKernelP->rows - 1],
-                    outpamP->maxval, outpamP->depth);
-
-    setupCircMap(circMap, rowbuf, 0);
+    setupCircMap(circMap, rowbuf, windowHeight, 0);
 
     /* Convolve the first window the long way */
-    convolveAndComputeColumnSums(inpamP, outpamP, circMap, convKernelP,
-                                 outputrow, convColumnSum);
+    computeInitialColumnSums(inpamP, circMap, convKernelP, convColumnSum);
+
+    convolveRowWithColumnSumsMean(convKernelP, outpamP, circMap,
+                                  outputrow, convColumnSum);
 
-    /* Write that first convolved row */
     pnm_writepamrow(outpamP, outputrow);
 
     /* For all subsequent rows do it this way as the columnsums have been
-       generated.  Now we can use them to reduce further calculations.
+       generated.  Now we can use them to reduce further calculations.  We
+       slide the window down a row at a time by reading a row into the bottom
+       of the circular buffer, adding it to the column sums, then subtracting
+       out the row at the top of the circular buffer.
     */
-    for (row = convKernelP->rows; row < inpamP->height; ++row) {
-        unsigned int const subrow = 0;
-            /* Row just above convolution window -- what we subtract from
-               running sum
+    for (row = crowso2 + 1; row < inpamP->height - crowso2; ++row) {
+        unsigned int const windowBotRow = row + crowso2;
+            /* Row number of bottom-most row present in rowbuf[],
+               which is the bottom of the convolution window for the current
+               row.
             */
-        unsigned int const addrow = 1 + (convKernelP->rows - 1);
-            /* Bottom row of convolution window: What we add to running sum */
+        unsigned int const windowTopRow = row - crowso2 - 1;
+            /* Row number of top-most row present in rowbuf[], which is
+               the top row of the convolution window for the previous row:
+               just above the convolution window for the current row.
+            */
+        unsigned int plane;
 
-        readAndScaleRow(inpamP, rowbuf[row % windowHeight],
+        readAndScaleRow(inpamP, rowbuf[windowBotRow % windowHeight],
                         outpamP->maxval, outpamP->depth);
 
-        /* Remember the window is one row higher than the convolution
-           kernel.  The top row in the window is not part of this convolution.
-        */
-
-        setupCircMap(circMap, rowbuf, (row + 1) % windowHeight);
+        setupCircMap(circMap, rowbuf, windowHeight,
+                     windowTopRow % windowHeight);
 
         for (plane = 0; plane < outpamP->depth; ++plane)
             convolveMeanRowPlane(outpamP, circMap, convKernelP, plane,
@@ -684,64 +892,129 @@ convolveMean(struct pam *        const inpamP,
 
         pnm_writepamrow(outpamP, outputrow);
     }
-    writeUnconvolvedBottom(outpamP, convKernelP, circMap);
+    writeUnconvolvedBottom(outpamP, convKernelP, windowHeight, circMap);
 
-    freeColumnSum(convColumnSum, outpamP->depth);
+    freeSum(convColumnSum, outpamP->depth);
     freeRowbuf(rowbuf, windowHeight);
 }
 
 
 
+static sample ***
+allocRowSum(unsigned int const depth,
+            unsigned int const height,
+            unsigned int const width) {
+
+    sample *** sum;
+
+    MALLOCARRAY(sum, depth);
+
+    if (!sum)
+        pm_error("Could not allocate memory for %u planes of sums", depth);
+    else {
+        unsigned int plane;
+
+        for (plane = 0; plane < depth; ++plane) {
+            MALLOCARRAY(sum[plane], height);
+            
+            if (!sum[plane])
+                pm_error("Could not allocate memory for %u rows of sums",
+                         height);
+            else {
+                unsigned int row;
+
+                for (row = 0; row < height; ++row) {
+                    MALLOCARRAY(sum[plane][row], width);
+                    
+                    if (!sum[plane][row])
+                        pm_error("Could not allocate memory "
+                                 "for a row of sums");
+                }
+            }
+        }
+    }
+    return sum;
+}
+
+
+
 static void
-convolveHorizontalRowPlane(struct pam *        const outpamP,
-                           tuple **            const circMap,
-                           struct convKernel * const convKernelP,
-                           unsigned int        const plane,
-                           tuple *             const outputrow,
-                           sample *            const sumCircMap) {
+freeRowSum(sample ***   const sum,
+           unsigned int const depth,
+           unsigned int const height) {
+
+    unsigned int plane;
+
+    for (plane = 0; plane < depth; ++plane) {
+        unsigned int row;
+
+        for (row = 0; row < height; ++row)
+            free(sum[plane][row]);
+
+        free(sum[plane]);
+    }
+    free(sum);
+}
+
+
+
+static void
+convolveHorizontalRowPlane0(struct pam *              const outpamP,
+                            tuple **                  const window,
+                            const struct convKernel * const convKernelP,
+                            unsigned int              const plane,
+                            tuple *                   const outputrow,
+                            sample **                 const sumWindow) {
+/*----------------------------------------------------------------------------
+   Convolve the first convolvable row and generate the row sums from scratch.
+   (For subsequent rows, Caller can just incrementally modify the row sums).
+-----------------------------------------------------------------------------*/
+    unsigned int const crowso2 = convKernelP->rows / 2;
+    unsigned int const ccolso2 = convKernelP->cols / 2;
 
     unsigned int col;
 
-    for (col = 0; col < cols; ++col) {
-        if (col < ccolso2 || col >= cols - ccolso2)
-            outputrow[col][0] = circMap[crowso2][col][0];
+    for (col = 0; col < outpamP->width; ++col) {
+        if (col < ccolso2 || col >= outpamP->width - ccolso2)
+            outputrow[col][plane] = window[crowso2][col][plane];
         else if (col == ccolso2) {
-            unsigned int const leftcol = col - ccolso2;
+            /* This is the first column for which the entire convolution
+               kernel fits within the image horizontally.  I.e. the window
+               starts at the left edge of the image.
+            */
+            unsigned int const leftcol = 0;
             
             float matrixSum;
             unsigned int crow;
 
-            for (crow = 0, matrixSum = 0.0; crow < crows; ++crow) {
-                tuple ** const temprptr = circMap[crow] + leftcol;
+            for (crow = 0, matrixSum = 0.0; crow < convKernelP->rows; ++crow) {
+                tuple * const tuplesInWindow = &window[crow][leftcol];
 
                 unsigned int ccol;
                 
-                sumCircMap[crow][leftcol] = 0L;
+                sumWindow[crow][col] = 0;
                 for (ccol = 0; ccol < convKernelP->cols; ++ccol)
-                    sumCircMap[crow][leftcol] += temprptr[ccol][0];
-                matrixSum += sumCircMap[crow][leftcol] *
-                    convKernelP->weight[crow][0][0];
+                    sumWindow[crow][col] += tuplesInWindow[ccol][plane];
+                matrixSum +=
+                    sumWindow[crow][col] * convKernelP->weight[plane][crow][0];
             }
-            outputrow[col][0] =
+            outputrow[col][plane] =
                 MIN(outpamP->maxval, MAX(0, matrixSum + 0.5));
         } else {
-            unsigned int const leftcol = col - ccolso2;
             unsigned int const subcol  = col - ccolso2 - 1;
-            usnigned int const addcol  = col + ccolso2;
+            unsigned int const addcol  = col + ccolso2;
 
             float matrixSum;
             unsigned int crow;
 
-            matrixSum = 0.0;
-
-            for (crow = 0; crow < crows; ++crow) {
-                sumCircMap[crow][leftcol] = sumCircMap[crow][subcol]
-                    - circMap[crow][subcol][0]
-                    + circMap[crow][addcol][0];
-                matrixSum += sumCircMap[crow][leftcol] *
-                    convKernelP->weight[crow][0][0];
+            for (crow = 0, matrixSum = 0.0; crow < convKernelP->rows; ++crow) {
+                sumWindow[crow][col] = sumWindow[crow][col-1] +
+                    + window[crow][addcol][plane]
+                    - window[crow][subcol][plane];
+                matrixSum +=
+                    sumWindow[crow][col] * convKernelP->weight[plane][crow][0];
             }
-            outputrow[col][0] =
+            outputrow[col][plane] =
                 MIN(outpamP->maxval, MAX(0, matrixSum + 0.5));
         }
     }
@@ -750,1231 +1023,440 @@ convolveHorizontalRowPlane(struct pam *        const outpamP,
 
 
 static void
-convolveHorizontal(struct pam *        const inpamP,
-                   struct pam *        const outpamP,
-                   struct convKernel * const convKernelP) {
+setupCircMap2(tuple **     const rowbuf,
+              sample **    const convRowSum,
+              tuple **     const circMap,
+              sample **    const sumCircMap,
+              unsigned int const windowTopRow,
+              unsigned int const windowHeight) {
+
+    unsigned int const toprow = windowTopRow % windowHeight;
+    
+    unsigned int crow;
+    unsigned int i;
+
+
+    i = 0;
+
+    for (crow = toprow; crow < windowHeight; ++i, ++crow) {
+        circMap[i] = rowbuf[crow];
+        sumCircMap[i] = convRowSum[crow];
+    }
+    for (crow = 0; crow < toprow; ++crow, ++i) {
+        circMap[i] = rowbuf[crow];
+        sumCircMap[i] = convRowSum[crow];
+    }
+}
+
+
+
+static void
+convolveHorizontalRowPlane(struct pam *              const pamP,
+                           tuple **                  const window,
+                           const struct convKernel * const convKernelP,
+                           unsigned int              const plane,
+                           tuple *                   const outputrow,
+                           sample **                 const sumWindow) {
+/*----------------------------------------------------------------------------
+   Convolve the row at the center of the convolution window described
+   by *convKernelP, where window[][] contains the input image tuples
+   for the window.  *pamP describes the rows in it, but its height is
+   one convolution window.
+
+   Convolve only the Plane 'plane' samples.
+
+   sumWindow[][] mirrors window[].  sumWindow[R] applies to window[R].
+   sumWindow[R][C] is the sum of samples in row R of the convolution window
+   centered on Column C.  We assume the convolution weights are the same
+   everywhere within a row of the kernel, so that we can generate these
+   sums incrementally, moving to the right through the image.
+-----------------------------------------------------------------------------*/
+    unsigned int const ccolso2 = convKernelP->cols / 2;
+    unsigned int const crowso2 = convKernelP->rows / 2;
+
+    unsigned int const newrow  = convKernelP->rows - 1;
+
+    unsigned int col;
+
+    for (col = 0; col < pamP->width; ++col) {
+        if (col < ccolso2 || col >= pamP->width - ccolso2)
+            outputrow[col][plane] = window[crowso2][col][plane];
+        else if (col == ccolso2) {
+            unsigned int const leftcol = 0;
+                // Window is up againt left edge of image
+
+            float matrixSum;
+
+            {
+                unsigned int ccol;
+                sumWindow[newrow][col] = 0;
+                for (ccol = 0; ccol < convKernelP->cols; ++ccol)
+                    sumWindow[newrow][col] +=
+                        window[newrow][leftcol + ccol][plane];
+            }
+            {
+                unsigned int crow;
+                for (crow = 0, matrixSum = 0.0;
+                     crow < convKernelP->rows;
+                     ++crow) {
+                    matrixSum += sumWindow[crow][col] *
+                        convKernelP->weight[plane][crow][0];
+                }
+            }
+            outputrow[col][plane] =
+                MIN(pamP->maxval, MAX(0, matrixSum + 0.5));
+        } else {
+            unsigned int const subcol  = col - ccolso2 - 1;
+            unsigned int const addcol  = col + ccolso2;  
+
+            float matrixSum;
+            unsigned int crow;
+
+            sumWindow[newrow][col] =
+                sumWindow[newrow][col-1]
+                + window[newrow][addcol][plane]
+                - window[newrow][subcol][plane];
+
+            for (crow = 0, matrixSum = 0.0; crow < convKernelP->rows; ++crow) {
+                matrixSum += sumWindow[crow][col] *
+                    convKernelP->weight[plane][crow][0];
+            }
+            outputrow[col][plane] =
+                MIN(pamP->maxval, MAX(0, matrixSum + 0.5));
+        }
+    }
+}
+
+
+
+static convolver convolveHorizontal;
+
+static void
+convolveHorizontal(struct pam *              const inpamP,
+                   struct pam *              const outpamP,
+                   const struct convKernel * const convKernelP) {
 /*----------------------------------------------------------------------------
   Horizontal Convolution
 
-  Similar idea to using columnsums of the Mean and Vertical convolution,
-  but uses temporary sums of row values.  Need to multiply by weights crows
-  number of times.  Each time a new line is started, must recalculate the
-  initials rowsums for the newest row only.  Uses queue to still access
-  previous row sums.
+  Similar idea to using columnsums of the Mean and Vertical convolution, but
+  uses temporary sums of row values.  Need to multiply by weights once for
+  each row in the convolution kernel.  Each time we start a new line, we must
+  recalculate the initials rowsums for the newest row only.  Uses queue to
+  still access previous row sums.
 -----------------------------------------------------------------------------*/
-    unsigned int const ccolso2 = convKernelP->cols / 2;
     unsigned int const crowso2 = convKernelP->rows / 2;
-    unsigned int const windowHeight = convKernelP->rows + 1;
-        /* Same as in convolvePgmMean */
+        /* Same as in convolveMean */
+    unsigned int const windowHeight = convKernelP->rows;
+        /* Same as in convolveMean */
 
     tuple ** rowbuf;
-        /* Same as in convolvePgmGeneral */
+        /* Same as in convolveGeneral */
     tuple ** circMap;
-        /* Same as in convolvePgmGeneral */
+        /* Same as in convolveGeneral */
     tuple * outputrow;
-        /* Same as in convolvePgmGeneral */
-    unsigned int row;
-        /* Row number of next row to read in from the file */
-    unsigned int plane;
-    sample ** convRowSum;  /* Malloc'd */
-    sample * sumCircMap;  /* Malloc'd */
+        /* Same as in convolveGeneral */
     unsigned int plane;
+    sample *** convRowSum;  /* Malloc'd */
+    sample ** sumCircMap;  /* Malloc'd */
 
-    rowbuf = allocRowbuf(pamP, windowHeight);
+    rowbuf = allocRowbuf(inpamP, windowHeight);
     MALLOCARRAY_NOFAIL(circMap, windowHeight);
     outputrow = pnm_allocpamrow(outpamP);
 
-    convRowSum = allocSum(outpamP->depth, windowHeight);
+    convRowSum = allocRowSum(outpamP->depth, windowHeight, outpamP->width);
     MALLOCARRAY_NOFAIL(sumCircMap, windowHeight);
 
     pnm_writepaminit(outpamP);
 
-    readInitialRowbuf(inpamP, convKernelP, rowbuf, outpamP->maxval);
+    readAndScaleRows(inpamP, convKernelP->rows, rowbuf,
+                      outpamP->maxval, outpamP->depth);
 
     writeUnconvolvedTop(outpamP, convKernelP, rowbuf);
 
-    /* Add a row to the window to have enough to convolve */
-    readAndScaleRow(inpamP, rowbuf[convKernelP->rows - 1],
-                    outpamP->maxval, outpamP->depth);
-
-    setupCircMap(circMap, rowbuf, 0);
+    setupCircMap(circMap, rowbuf, windowHeight, 0);
 
+    /* Convolve the first convolvable row and generate convRowSum[][] */
     for (plane = 0; plane < outpamP->depth; ++plane) {
         unsigned int crow;
 
-        for (crow = 0; crow < crows; ++crow)
+        for (crow = 0; crow < convKernelP->rows; ++crow)
             sumCircMap[crow] = convRowSum[plane][crow];
  
-        convolveHorizontalRowPlane(outpamP, circMap, convKernelP, plane,
-                                   outputrow, sumCircMap);
+        convolveHorizontalRowPlane0(outpamP, circMap, convKernelP, plane,
+                                    outputrow, sumCircMap);
     }
     pnm_writepamrow(outpamP, outputrow);
 
-    {
-        /* For all subsequent rows */
+    /* Convolve the rest of the rows, using convRowSum[] */
 
-        unsigned int const addrow = crows - 1;
-        unsigned int const windowHeight = crows + 1;
+    for (plane = 0; plane < outpamP->depth; ++plane) {
+        unsigned int row;
+            /* Same as in convolveMean */
 
-        for (row = convKernelP->rows ; row < rows; ++row) {
-            unsigned int const toprow = (row + 2) % windowHeight;
+        for (row = convKernelP->rows/2 + 1;
+             row < inpamP->height - convKernelP->rows/2;
+             ++row) {
 
-            unsigned int col;
+            unsigned int const windowBotRow = row + crowso2;
+            unsigned int const windowTopRow = row - crowso2;
+                /* Same as in convolveMean */
 
-            readAndScaleRow(inpamP, rowbuf[row % windowHeight],
+            readAndScaleRow(inpamP, rowbuf[windowBotRow % windowHeight],
                             outpamP->maxval, outpamP->depth);
             
-            i = 0;
-            for (irow = toprow; irow < windowHeight; ++i, ++irow) {
-                circMap[i] = rowbuf[irow];
-                sumCircMap[i] = convRowSum[plane][irow];
-            }
-            for (irow = 0; irow < toprow; ++irow, ++i) {
-                circMap[i] = rowbuf[irow];
-                sumCircMap[i] = convRowSum[plane][irow];
-            }
+            setupCircMap2(rowbuf, convRowSum[plane], circMap, sumCircMap,
+                          windowTopRow, windowHeight);
+
+            convolveHorizontalRowPlane(outpamP, circMap, convKernelP, plane,
+                                       outputrow, sumCircMap);
             
-            for (col = 0; col < cols; ++col) {
-                if (col < ccolso2 || col >= cols - ccolso2)
-                    outputrow[col] = circMap[crowso2][col];
-                else if (col == ccolso2) {
-                    unsigned int const leftcol = col - ccolso2;
-
-                    float matrixSum;
-
-                    {
-                        unsigned int ccol;
-                        sumCircMap[addrow][leftcol] = 0L;
-                        for (ccol = 0; ccol < convKernelP->cols; ++ccol)
-                            sumCircMap[addrow][leftcol] += 
-                                circMap[addrow][leftcol + ccol][0];
-                    }
-                    {
-                        unsigned int crow;
-                        for (crow = 0, matrixSum = 0.0; crow < crows; ++crow)
-                            matrixSum += sumCircMap[crow][leftcol] *
-                                convKernelP->weight[crow][0][0];
-                    }
-                    outputrow[col][0] =
-                        MIN(outpamP->maxval, MAX(0, matrixSum + 0.5));
-                } else {
-                    unsigned int const leftcol = col - ccolso2;
-                    unsigned int const subcol  = col - ccolso2 - 1;
-                    unsigned int const addcol  = col + ccolso2;  
-
-                    float matrixSum;
-                    unsigned int crow;
-
-                    sumCircMap[addrow][leftcol] = sumCircMap[addrow][subcol]
-                        - circMap[addrow][subcol][0]
-                        + circMap[addrow][addcol][0];
-
-                    for (crow = 0, matrixSum = 0.0; crow < crows; ++crow)
-                        matrixSum += sumCircMap[crow][leftcol] *
-                            convKernelP->weight[crow][0][0];
-
-                    outputrow[col][0] =
-                        MIN(outpamP->maxval, MAX(0, matrixSum + 0.5));
-                }
-            }
             pnm_writepamrow(outpamP, outputrow);
         }
     }
-    writeUnconvolvedBottom(outpamP, convKernelP, circMap);
 
+    writeUnconvolvedBottom(outpamP, convKernelP, windowHeight, circMap);
+
+    freeRowSum(convRowSum, outpamP->depth, windowHeight);
     freeRowbuf(rowbuf, windowHeight);
 }
 
 
 
 static void
-convolvePgmVertical(struct pam *        const inpamP,
-                    struct pam *        const outpamP,
-                    struct convKernel * const convKernelP) {
+convolveVerticalRowPlane(struct pam *              const pamP,
+                         tuple **                  const circMap,
+                         const struct convKernel * const convKernelP,
+                         unsigned int              const plane,
+                         tuple *                   const outputrow,
+                         sample *                  const convColumnSum) {
+
+    unsigned int const crowso2 = convKernelP->rows / 2;
+    unsigned int const ccolso2 = convKernelP->cols / 2;
+
+    unsigned int const subrow = 0;
+        /* Row just above convolution window -- what we subtract from
+           running sum
+        */
+    unsigned int const addrow = 1 + (convKernelP->rows - 1);
+        /* Bottom row of convolution window: What we add to running sum */
+    
+    unsigned int col;
+
+    for (col = 0; col < pamP->width; ++col) {
+        if (col < ccolso2 || col >= pamP->width - ccolso2)
+            outputrow[col][plane] = circMap[crowso2][col][plane];
+        else if (col == ccolso2) {
+            unsigned int const leftcol = 0;
+                // Convolution window is againt left edge of image
+
+            float matrixSum;
+            unsigned int ccol;
+
+            // Slide window down in the first kernel's worth of columns
+            for (ccol = 0; ccol < convKernelP->cols; ++ccol) {
+                convColumnSum[leftcol + ccol] +=
+                    circMap[addrow][leftcol + ccol][plane];
+                convColumnSum[leftcol + ccol] -=
+                    circMap[subrow][leftcol + ccol][plane];
+            }
+            for (ccol = 0, matrixSum = 0.0; ccol < convKernelP->cols; ++ccol) {
+                matrixSum += convColumnSum[leftcol + ccol] *
+                    convKernelP->weight[plane][0][ccol];
+            }
+            outputrow[col][plane] =
+                MIN(pamP->maxval, MAX(0, matrixSum + 0.5));
+        } else {
+            unsigned int const leftcol = col - ccolso2;
+            unsigned int const addcol  = col + ccolso2;
+
+            float matrixSum;
+            unsigned int ccol;
+
+            // Slide window down in the column that just entered the window
+            convColumnSum[addcol] += circMap[addrow][addcol][plane];
+            convColumnSum[addcol] -= circMap[subrow][addcol][plane];
+
+            for (ccol = 0, matrixSum = 0.0; ccol < convKernelP->cols; ++ccol) {
+                matrixSum += convColumnSum[leftcol + ccol] *
+                    convKernelP->weight[plane][0][ccol];
+            }
+            outputrow[col][plane] =
+                MIN(pamP->maxval, MAX(0, matrixSum + 0.5));
+        }
+    }
+}
+
+
+
+static convolver convolveVertical;
+
+static void
+convolveVertical(struct pam *              const inpamP,
+                 struct pam *              const outpamP,
+                 const struct convKernel * const convKernelP) {
 
     /* Uses column sums as in mean convolution, above */
 
-    unsigned int const ccolso2 = convKernelP->cols / 2;
-    float const weight = convKernelP->weight[0][0][0];
     unsigned int const windowHeight = convKernelP->rows + 1;
-        /* The height of the window we keep in the row buffer.  The buffer
-           contains the rows covered by the convolution kernel, plus the row
-           immediately above that.  The latter is there because to compute
-           the sliding mean, we need to subtract off the row that the
-           convolution kernel just slid past.
-        */
+        /* Same as in convolveMean */
+    unsigned int const crowso2 = convKernelP->rows / 2;
+        /* Same as in convolveMean */
 
     tuple ** rowbuf;
-        /* Same as in convolvePgmGeneral */
+        /* Same as in convolveGeneral */
     tuple ** circMap;
-        /* Same as in convolvePgmGeneral */
+        /* Same as in convolveGeneral */
     tuple * outputrow;
-        /* Same as in convolvePgmGeneral */
+        /* Same as in convolveGeneral */
     unsigned int row;
         /* Row number of next row to read in from the file */
-    sample * convColumnSum;  /* Malloc'd */
-        /* convColumnSum[col] is the sum of all the pixels in the Column 'col'
-           of the image within the current vertical convolution window.
-           I.e. if our convolution kernel is 5 rows high and we're now looking
-           at Rows 10-15, convColumn[3] is the sum of Column 3, Rows 10-15.
-        */
-    sample matrixSum;
-    unsigned int col;
+    sample ** convColumnSum;  /* Malloc'd */
+        /* Same as in convolveMean() */
 
-    rowbuf = allocRowbuf(pamP, windowHeight);
+    rowbuf = allocRowbuf(inpamP, windowHeight);
     MALLOCARRAY_NOFAIL(circMap, windowHeight);
     outputrow = pnm_allocpamrow(outpamP);
 
-    MALLOCARRAY_NOFAIL(convColumnSum, inpamP->width);
+    convColumnSum = allocSum(outpamP->depth, outpamP->width);
 
     pnm_writepaminit(outpamP);
 
-    readInitialRowbuf(inpamP, convKernelP, rowbuf, outpamP->maxval);
+    readAndScaleRows(inpamP, convKernelP->rows, rowbuf,
+                      outpamP->maxval, outpamP->depth);
 
     writeUnconvolvedTop(outpamP, convKernelP, rowbuf);
 
-    setupCircMap(circMap, rowbuf, 0);
+    setupCircMap(circMap, rowbuf, windowHeight, 0);
 
     /* Convolve the first window the long way */
-    convolveAndComputeColumnSums(inpamP, outpamP, circMap, convKernelP,
-                                 outputrow, convColumnSum);
+    computeInitialColumnSums(inpamP, circMap, convKernelP, convColumnSum);
 
-    /* Write that first convolved row */
-    pnm_writepamrow(outpamP, outputrow);
+    convolveRowWithColumnSumsVertical(convKernelP, outpamP, circMap,
+                                      outputrow, convColumnSum);
 
-    for (row = convKernelP->rows; row < inpamP->height; ++row) {
-        unsigned int const subrow = 0;
-            /* Row just above convolution window -- what we subtract from
-               running sum
-            */
-        unsigned int const addrow = 1 + (convKernelP->rows - 1);
-            /* Bottom row of convolution window: What we add to running sum */
+    pnm_writepamrow(outpamP, outputrow);
 
-        unsigned int col;
+    for (row = crowso2 + 1; row < inpamP->height - crowso2; ++row) {
+        unsigned int const windowBotRow = row + crowso2;
+            /* Same as in convolveMean */
+        unsigned int const windowTopRow = row - crowso2 - 1;
+            /* Same as in convolveMean */
+        unsigned int plane;
 
-        readAndScaleRow(inpamP, rowbuf[row % windowHeight],
+        readAndScaleRow(inpamP, rowbuf[windowBotRow % windowHeight],
                         outpamP->maxval, outpamP->depth);
 
         /* Remember the window is one row higher than the convolution
            kernel.  The top row in the window is not part of this convolution.
         */
 
-        setupCircMap(circMap, rowbuf, (row + 1) % windowHeight);
-
-        for (col = 0; col < cols; ++col) {
-            if (col < ccolso2 || col >= cols - ccolso2)
-                outputrow[col] = circMap[crowso2][col];
-            else if (col == ccolso2) {
-                unsigned int const leftcol = col - ccolso2;
+        setupCircMap(circMap, rowbuf, windowHeight,
+                     windowTopRow % windowHeight);
 
-                float matrixSum;
-                unsigned int crow;
-                unsigned int ccol;
-
-                for (crow = 0; crow < convKernelP->rows; ++crow) {
-                    unsigned int ccol;
-
-                    for (ccol = 0; ccol < convKernelP->cols; ++ccol)
-                        convColumnSum[leftcol + ccol] +=
-                            circMap[crow][ccol][0];
-                }
-                for (ccol = 0, matrixSum = 0.0;
-                     ccol < convKernelP->cols;
-                     ++ccol) {
-                    matrixSum += convColumnSum[leftcol + ccol] *
-                        convKernelP->weight[0][ccol][0];
-                }
-                outputrow[col][0] =
-                    MIN(outpamP->maxval, MAX(0, matrixSum + 0.5));
-            } else {
-                unsigned int const leftcol = col - ccolso2;
-                unsigned int const addcol  = col + ccolso2;
-
-                float matrixSum;
-                unsigned int ccol;
-
-                convColumnSum[addcol] = convColumnSum[addcol]
-                    - circMap[subrow][addcol][0]
-                    + circMap[addrow][addcol][0];
+        for (plane = 0; plane < outpamP->depth; ++plane)
+            convolveVerticalRowPlane(outpamP, circMap, convKernelP, plane,
+                                     outputrow, convColumnSum[plane]);
 
-                for (ccol = 0, matrixSum = 0.0;
-                     ccol < convKernelP->cols;
-                     ++ccol) {
-                    matrixSum += convColumnSum[leftcol + ccol] *
-                        convKernelP->weight[0][ccol][0];
-                }
-                outputrow[col][0] =
-                    MIN(outpamP->maxval, MAX(0, matrixSum + 0.5));
-            }
-        }
         pnm_writepamrow(outpamP, outputrow);
     }
-    writeUnconvolvedBottom(outpamP, convKernelP, circMap);
+    writeUnconvolvedBottom(outpamP, convKernelP, windowHeight, circMap);
 
+    freeSum(convColumnSum, outpamP->depth);
     freeRowbuf(rowbuf, windowHeight);
 }
 
 
 
-/* PPM General Convolution Algorithm
-**
-** No redundancy in convolution matrix.  Just use brute force.
-** See convolvePgmGeneral() for more details.
-*/
-
-static void
-ppm_general_convolve(const float ** const rweights,
-                     const float ** const gweights,
-                     const float ** const bweights) {
-    int ccol, col;
-    xel** xelbuf;
-    xel* outputrow;
-    xelval r, g, b;
-    int row, crow;
-    float rsum, gsum, bsum;
-    xel **rowptr, *temprptr;
-    int toprow, temprow;
-    int i, irow;
-    int leftcol;
-    long temprsum, tempgsum, tempbsum;
-
-    /* Allocate space for one convolution-matrix's worth of rows, plus
-    ** a row output buffer. */
-    xelbuf = pnm_allocarray( cols, crows );
-    outputrow = pnm_allocrow( cols );
-
-    /* Allocate array of pointers to xelbuf */
-    rowptr = (xel **) pnm_allocarray( 1, crows );
-
-    pnm_writepnminit( stdout, cols, rows, maxval, newformat, 0 );
-
-    /* Read in one convolution-matrix's worth of image, less one row. */
-    for ( row = 0; row < crows - 1; ++row )
-    {
-    pnm_readpnmrow( ifp, xelbuf[row], cols, maxval, format );
-    if ( PNM_FORMAT_TYPE(format) != newformat )
-        pnm_promoteformatrow(
-        xelbuf[row], cols, maxval, format, maxval, newformat );
-    /* Write out just the part we're not going to convolve. */
-    if ( row < crowso2 )
-        pnm_writepnmrow( stdout, xelbuf[row], cols, maxval, newformat, 0 );
-    }
-
-    /* Now the rest of the image - read in the row at the end of
-    ** xelbuf, and convolve and write out the row in the middle.
-    */
-    for ( ; row < rows; ++row )
-    {
-    toprow = row + 1;
-    temprow = row % crows;
-    pnm_readpnmrow( ifp, xelbuf[temprow], cols, maxval, format );
-    if ( PNM_FORMAT_TYPE(format) != newformat )
-        pnm_promoteformatrow(
-        xelbuf[temprow], cols, maxval, format, maxval, newformat );
-
-    /* Arrange rowptr to eliminate the use of mod function to determine
-    ** which row of xelbuf is 0...crows.  Mod function can be very costly.
-    */
-    temprow = toprow % crows;
-    i = 0;
-    for (irow = temprow; irow < crows; ++i, ++irow)
-        rowptr[i] = xelbuf[irow];
-    for (irow = 0; irow < temprow; ++irow, ++i)
-        rowptr[i] = xelbuf[irow];
-
-    for ( col = 0; col < cols; ++col )
-        {
-        if ( col < ccolso2 || col >= cols - ccolso2 )
-        outputrow[col] = rowptr[crowso2][col];
-        else
-        {
-        leftcol = col - ccolso2;
-        rsum = gsum = bsum = 0.0;
-        for ( crow = 0; crow < crows; ++crow )
-            {
-            temprptr = rowptr[crow] + leftcol;
-            for ( ccol = 0; ccol < ccols; ++ccol )
-            {
-            rsum += PPM_GETR( *(temprptr + ccol) )
-                * rweights[crow][ccol];
-            gsum += PPM_GETG( *(temprptr + ccol) )
-                * gweights[crow][ccol];
-            bsum += PPM_GETB( *(temprptr + ccol) )
-                * bweights[crow][ccol];
-            }
-            }
-            temprsum = rsum + 0.5;
-            tempgsum = gsum + 0.5;
-            tempbsum = bsum + 0.5;
-            CHECK_RED;
-            CHECK_GREEN;
-            CHECK_BLUE;
-            PPM_ASSIGN( outputrow[col], r, g, b );
-        }
-        }
-    pnm_writepnmrow( stdout, outputrow, cols, maxval, newformat, 0 );
-    }
-
-    /* Now write out the remaining unconvolved rows in xelbuf. */
-    for ( irow = crowso2 + 1; irow < crows; ++irow )
-    pnm_writepnmrow(
-            stdout, rowptr[irow], cols, maxval, newformat, 0 );
-
-    }
-
-
-/* PPM Mean Convolution
-**
-** Same as pgm_mean_convolve() but for PPM.
-**
-*/
-
-static void
-ppm_mean_convolve(const float ** const rweights,
-                  const float ** const gweights,
-                  const float ** const bweights) {
-    /* All weights of a single color are the same so just grab any one
-       of them.  
-    */
-    float const rmeanweight = rweights[0][0];
-    float const gmeanweight = gweights[0][0];
-    float const bmeanweight = bweights[0][0];
-
-    int ccol, col;
-    xel** xelbuf;
-    xel* outputrow;
-    xelval r, g, b;
-    int row, crow;
-    xel **rowptr, *temprptr;
-    int leftcol;
-    int i, irow;
-    int toprow, temprow;
-    int subrow, addrow;
-    int subcol, addcol;
-    long risum, gisum, bisum;
-    long temprsum, tempgsum, tempbsum;
-    int tempcol, crowsp1;
-    long *rcolumnsum, *gcolumnsum, *bcolumnsum;
-
-
-
-    /* Allocate space for one convolution-matrix's worth of rows, plus
-    ** a row output buffer.  MEAN uses an extra row. */
-    xelbuf = pnm_allocarray( cols, crows + 1 );
-    outputrow = pnm_allocrow( cols );
-
-    /* Allocate array of pointers to xelbuf. MEAN uses an extra row. */
-    rowptr = (xel **) pnm_allocarray( 1, crows + 1);
-
-    /* Allocate space for intermediate column sums */
-    rcolumnsum = (long *) pm_allocrow( cols, sizeof(long) );
-    gcolumnsum = (long *) pm_allocrow( cols, sizeof(long) );
-    bcolumnsum = (long *) pm_allocrow( cols, sizeof(long) );
-    for ( col = 0; col < cols; ++col )
-    {
-    rcolumnsum[col] = 0L;
-    gcolumnsum[col] = 0L;
-    bcolumnsum[col] = 0L;
-    }
-
-    pnm_writepnminit( stdout, cols, rows, maxval, newformat, 0 );
-
-    /* Read in one convolution-matrix's worth of image, less one row. */
-    for ( row = 0; row < crows - 1; ++row )
-    {
-    pnm_readpnmrow( ifp, xelbuf[row], cols, maxval, format );
-    if ( PNM_FORMAT_TYPE(format) != newformat )
-        pnm_promoteformatrow(
-        xelbuf[row], cols, maxval, format, maxval, newformat );
-    /* Write out just the part we're not going to convolve. */
-    if ( row < crowso2 )
-        pnm_writepnmrow( stdout, xelbuf[row], cols, maxval, newformat, 0 );
-    }
-
-    /* Do first real row only */
-    subrow = crows;
-    addrow = crows - 1;
-    toprow = row + 1;
-    temprow = row % crows;
-    pnm_readpnmrow( ifp, xelbuf[temprow], cols, maxval, format );
-    if ( PNM_FORMAT_TYPE(format) != newformat )
-    pnm_promoteformatrow(
-        xelbuf[temprow], cols, maxval, format, maxval, newformat );
-
-    temprow = toprow % crows;
-    i = 0;
-    for (irow = temprow; irow < crows; ++i, ++irow)
-    rowptr[i] = xelbuf[irow];
-    for (irow = 0; irow < temprow; ++irow, ++i)
-    rowptr[i] = xelbuf[irow];
-
-    risum = 0L;
-    gisum = 0L;
-    bisum = 0L;
-    for ( col = 0; col < cols; ++col )
-    {
-    if ( col < ccolso2 || col >= cols - ccolso2 )
-        outputrow[col] = rowptr[crowso2][col];
-    else if ( col == ccolso2 )
-        {
-        leftcol = col - ccolso2;
-        for ( crow = 0; crow < crows; ++crow )
-        {
-        temprptr = rowptr[crow] + leftcol;
-        for ( ccol = 0; ccol < ccols; ++ccol )
-            {
-            rcolumnsum[leftcol + ccol] += 
-            PPM_GETR( *(temprptr + ccol) );
-            gcolumnsum[leftcol + ccol] += 
-            PPM_GETG( *(temprptr + ccol) );
-            bcolumnsum[leftcol + ccol] += 
-            PPM_GETB( *(temprptr + ccol) );
-            }
-        }
-        for ( ccol = 0; ccol < ccols; ++ccol)
-        {
-        risum += rcolumnsum[leftcol + ccol];
-        gisum += gcolumnsum[leftcol + ccol];
-        bisum += bcolumnsum[leftcol + ccol];
-        }
-        temprsum = (float) risum * rmeanweight + 0.5;
-        tempgsum = (float) gisum * gmeanweight + 0.5;
-        tempbsum = (float) bisum * bmeanweight + 0.5;
-        CHECK_RED;
-        CHECK_GREEN;
-        CHECK_BLUE;
-        PPM_ASSIGN( outputrow[col], r, g, b );
-        }
-    else
-        {
-        /* Column numbers to subtract or add to isum */
-        subcol = col - ccolso2 - 1;
-        addcol = col + ccolso2;  
-        for ( crow = 0; crow < crows; ++crow )
-        {
-        rcolumnsum[addcol] += PPM_GETR( rowptr[crow][addcol] );
-        gcolumnsum[addcol] += PPM_GETG( rowptr[crow][addcol] );
-        bcolumnsum[addcol] += PPM_GETB( rowptr[crow][addcol] );
-        }
-        risum = risum - rcolumnsum[subcol] + rcolumnsum[addcol];
-        gisum = gisum - gcolumnsum[subcol] + gcolumnsum[addcol];
-        bisum = bisum - bcolumnsum[subcol] + bcolumnsum[addcol];
-        temprsum = (float) risum * rmeanweight + 0.5;
-        tempgsum = (float) gisum * gmeanweight + 0.5;
-        tempbsum = (float) bisum * bmeanweight + 0.5;
-        CHECK_RED;
-        CHECK_GREEN;
-        CHECK_BLUE;
-        PPM_ASSIGN( outputrow[col], r, g, b );
-        }
-    }
-    pnm_writepnmrow( stdout, outputrow, cols, maxval, newformat, 0 );
-
-    ++row;
-    /* For all subsequent rows do it this way as the columnsums have been
-    ** generated.  Now we can use them to reduce further calculations.
-    */
-    crowsp1 = crows + 1;
-    for ( ; row < rows; ++row )
-    {
-    toprow = row + 1;
-    temprow = row % (crows + 1);
-    pnm_readpnmrow( ifp, xelbuf[temprow], cols, maxval, format );
-    if ( PNM_FORMAT_TYPE(format) != newformat )
-        pnm_promoteformatrow(
-        xelbuf[temprow], cols, maxval, format, maxval, newformat );
-
-    /* This rearrangement using crows+1 rowptrs and xelbufs will cause
-    ** rowptr[0..crows-1] to always hold active xelbufs and for 
-    ** rowptr[crows] to always hold the oldest (top most) xelbuf.
-    */
-    temprow = (toprow + 1) % crowsp1;
-    i = 0;
-    for (irow = temprow; irow < crowsp1; ++i, ++irow)
-        rowptr[i] = xelbuf[irow];
-    for (irow = 0; irow < temprow; ++irow, ++i)
-        rowptr[i] = xelbuf[irow];
-
-    risum = 0L;
-    gisum = 0L;
-    bisum = 0L;
-    for ( col = 0; col < cols; ++col )
-        {
-        if ( col < ccolso2 || col >= cols - ccolso2 )
-        outputrow[col] = rowptr[crowso2][col];
-        else if ( col == ccolso2 )
-        {
-        leftcol = col - ccolso2;
-        for ( ccol = 0; ccol < ccols; ++ccol )
-            {
-            tempcol = leftcol + ccol;
-            rcolumnsum[tempcol] = rcolumnsum[tempcol]
-            - PPM_GETR( rowptr[subrow][ccol] )
-            + PPM_GETR( rowptr[addrow][ccol] );
-            risum += rcolumnsum[tempcol];
-            gcolumnsum[tempcol] = gcolumnsum[tempcol]
-            - PPM_GETG( rowptr[subrow][ccol] )
-            + PPM_GETG( rowptr[addrow][ccol] );
-            gisum += gcolumnsum[tempcol];
-            bcolumnsum[tempcol] = bcolumnsum[tempcol]
-            - PPM_GETB( rowptr[subrow][ccol] )
-            + PPM_GETB( rowptr[addrow][ccol] );
-            bisum += bcolumnsum[tempcol];
-            }
-        temprsum = (float) risum * rmeanweight + 0.5;
-        tempgsum = (float) gisum * gmeanweight + 0.5;
-        tempbsum = (float) bisum * bmeanweight + 0.5;
-        CHECK_RED;
-        CHECK_GREEN;
-        CHECK_BLUE;
-        PPM_ASSIGN( outputrow[col], r, g, b );
-        }
-        else
-        {
-        /* Column numbers to subtract or add to isum */
-        subcol = col - ccolso2 - 1;
-        addcol = col + ccolso2;  
-        rcolumnsum[addcol] = rcolumnsum[addcol]
-            - PPM_GETR( rowptr[subrow][addcol] )
-            + PPM_GETR( rowptr[addrow][addcol] );
-        risum = risum - rcolumnsum[subcol] + rcolumnsum[addcol];
-        gcolumnsum[addcol] = gcolumnsum[addcol]
-            - PPM_GETG( rowptr[subrow][addcol] )
-            + PPM_GETG( rowptr[addrow][addcol] );
-        gisum = gisum - gcolumnsum[subcol] + gcolumnsum[addcol];
-        bcolumnsum[addcol] = bcolumnsum[addcol]
-            - PPM_GETB( rowptr[subrow][addcol] )
-            + PPM_GETB( rowptr[addrow][addcol] );
-        bisum = bisum - bcolumnsum[subcol] + bcolumnsum[addcol];
-        temprsum = (float) risum * rmeanweight + 0.5;
-        tempgsum = (float) gisum * gmeanweight + 0.5;
-        tempbsum = (float) bisum * bmeanweight + 0.5;
-        CHECK_RED;
-        CHECK_GREEN;
-        CHECK_BLUE;
-        PPM_ASSIGN( outputrow[col], r, g, b );
-        }
-        }
-    pnm_writepnmrow( stdout, outputrow, cols, maxval, newformat, 0 );
-    }
-
-    /* Now write out the remaining unconvolved rows in xelbuf. */
-    for ( irow = crowso2 + 1; irow < crows; ++irow )
-    pnm_writepnmrow(
-            stdout, rowptr[irow], cols, maxval, newformat, 0 );
-
-    }
-
-
-/* PPM Horizontal Convolution
-**
-** Same as pgm_horizontal_convolve()
-**
-**/
-
-static void
-ppm_horizontal_convolve(const float ** const rweights,
-                        const float ** const gweights,
-                        const float ** const bweights) {
-    int ccol, col;
-    xel** xelbuf;
-    xel* outputrow;
-    xelval r, g, b;
-    int row, crow;
-    xel **rowptr, *temprptr;
-    int leftcol;
-    int i, irow;
-    int temprow;
-    int subcol, addcol;
-    float rsum, gsum, bsum;
-    int addrow, subrow;
-    long **rrowsum, **rrowsumptr;
-    long **growsum, **growsumptr;
-    long **browsum, **browsumptr;
-    int crowsp1;
-    long temprsum, tempgsum, tempbsum;
-
-    /* Allocate space for one convolution-matrix's worth of rows, plus
-    ** a row output buffer. */
-    xelbuf = pnm_allocarray( cols, crows + 1 );
-    outputrow = pnm_allocrow( cols );
-
-    /* Allocate array of pointers to xelbuf */
-    rowptr = (xel **) pnm_allocarray( 1, crows + 1);
-
-    /* Allocate intermediate row sums.  HORIZONTAL uses an extra row */
-    rrowsum = (long **) pm_allocarray( cols, crows + 1, sizeof(long) );
-    rrowsumptr = (long **) pnm_allocarray( 1, crows + 1);
-    growsum = (long **) pm_allocarray( cols, crows + 1, sizeof(long) );
-    growsumptr = (long **) pnm_allocarray( 1, crows + 1);
-    browsum = (long **) pm_allocarray( cols, crows + 1, sizeof(long) );
-    browsumptr = (long **) pnm_allocarray( 1, crows + 1);
-
-    pnm_writepnminit( stdout, cols, rows, maxval, newformat, 0 );
-
-    /* Read in one convolution-matrix's worth of image, less one row. */
-    for ( row = 0; row < crows - 1; ++row )
-    {
-    pnm_readpnmrow( ifp, xelbuf[row], cols, maxval, format );
-    if ( PNM_FORMAT_TYPE(format) != newformat )
-        pnm_promoteformatrow(
-        xelbuf[row], cols, maxval, format, maxval, newformat );
-    /* Write out just the part we're not going to convolve. */
-    if ( row < crowso2 )
-        pnm_writepnmrow( stdout, xelbuf[row], cols, maxval, newformat, 0 );
-    }
+struct convolveType {
+    convolver * convolve;
+};
 
-    /* First row only */
-    temprow = row % crows;
-    pnm_readpnmrow( ifp, xelbuf[temprow], cols, maxval, format );
-    if ( PNM_FORMAT_TYPE(format) != newformat )
-    pnm_promoteformatrow(
-        xelbuf[temprow], cols, maxval, format, maxval, newformat );
 
-    temprow = (row + 1) % crows;
-    i = 0;
-    for (irow = temprow; irow < crows; ++i, ++irow)
-    rowptr[i] = xelbuf[irow];
-    for (irow = 0; irow < temprow; ++irow, ++i)
-    rowptr[i] = xelbuf[irow];
-
-    for ( crow = 0; crow < crows; ++crow )
-    {
-    rrowsumptr[crow] = rrowsum[crow];
-    growsumptr[crow] = growsum[crow];
-    browsumptr[crow] = browsum[crow];
-    }
- 
-    for ( col = 0; col < cols; ++col )
-    {
-    if ( col < ccolso2 || col >= cols - ccolso2 )
-        outputrow[col] = rowptr[crowso2][col];
-    else if ( col == ccolso2 )
-        {
-        leftcol = col - ccolso2;
-        rsum = 0.0;
-        gsum = 0.0;
-        bsum = 0.0;
-        for ( crow = 0; crow < crows; ++crow )
-        {
-        temprptr = rowptr[crow] + leftcol;
-        rrowsumptr[crow][leftcol] = 0L;
-        growsumptr[crow][leftcol] = 0L;
-        browsumptr[crow][leftcol] = 0L;
-        for ( ccol = 0; ccol < ccols; ++ccol )
-            {
-            rrowsumptr[crow][leftcol] += 
-                PPM_GETR( *(temprptr + ccol) );
-            growsumptr[crow][leftcol] += 
-                PPM_GETG( *(temprptr + ccol) );
-            browsumptr[crow][leftcol] += 
-                PPM_GETB( *(temprptr + ccol) );
-            }
-        rsum += rrowsumptr[crow][leftcol] * rweights[crow][0];
-        gsum += growsumptr[crow][leftcol] * gweights[crow][0];
-        bsum += browsumptr[crow][leftcol] * bweights[crow][0];
-        }
-        temprsum = rsum + 0.5;
-        tempgsum = gsum + 0.5;
-        tempbsum = bsum + 0.5;
-        CHECK_RED;
-        CHECK_GREEN;
-        CHECK_BLUE;
-        PPM_ASSIGN( outputrow[col], r, g, b );
-        }
-    else
-        {
-        rsum = 0.0;
-        gsum = 0.0;
-        bsum = 0.0;
-        leftcol = col - ccolso2;
-        subcol = col - ccolso2 - 1;
-        addcol = col + ccolso2;
-        for ( crow = 0; crow < crows; ++crow )
-        {
-        rrowsumptr[crow][leftcol] = rrowsumptr[crow][subcol]
-            - PPM_GETR( rowptr[crow][subcol] )
-            + PPM_GETR( rowptr[crow][addcol] );
-        rsum += rrowsumptr[crow][leftcol] * rweights[crow][0];
-        growsumptr[crow][leftcol] = growsumptr[crow][subcol]
-            - PPM_GETG( rowptr[crow][subcol] )
-            + PPM_GETG( rowptr[crow][addcol] );
-        gsum += growsumptr[crow][leftcol] * gweights[crow][0];
-        browsumptr[crow][leftcol] = browsumptr[crow][subcol]
-            - PPM_GETB( rowptr[crow][subcol] )
-            + PPM_GETB( rowptr[crow][addcol] );
-        bsum += browsumptr[crow][leftcol] * bweights[crow][0];
-        }
-        temprsum = rsum + 0.5;
-        tempgsum = gsum + 0.5;
-        tempbsum = bsum + 0.5;
-        CHECK_RED;
-        CHECK_GREEN;
-        CHECK_BLUE;
-        PPM_ASSIGN( outputrow[col], r, g, b );
-        }
-        }
-    pnm_writepnmrow( stdout, outputrow, cols, maxval, newformat, 0 );
 
+static bool
+convolutionIncludesHorizontal(tuple **                  const tuples,
+                              const struct convKernel * const convKernelP) {
 
-    /* For all subsequent rows */
+    bool horizontal;
+    unsigned int row;
 
-    subrow = crows;
-    addrow = crows - 1;
-    crowsp1 = crows + 1;
-    ++row;
-    for ( ; row < rows; ++row )
-    {
-    temprow = row % crowsp1;
-    pnm_readpnmrow( ifp, xelbuf[temprow], cols, maxval, format );
-    if ( PNM_FORMAT_TYPE(format) != newformat )
-        pnm_promoteformatrow(
-        xelbuf[temprow], cols, maxval, format, maxval, newformat );
+    for (row = 0, horizontal = TRUE;
+         row < convKernelP->rows && horizontal;
+        ++row) {
+        unsigned int col;
+        for (col = 1, horizontal = TRUE;
+             col < convKernelP->cols && horizontal;
+             ++col) {
 
-    temprow = (row + 2) % crowsp1;
-    i = 0;
-    for (irow = temprow; irow < crowsp1; ++i, ++irow)
-        {
-        rowptr[i] = xelbuf[irow];
-        rrowsumptr[i] = rrowsum[irow];
-        growsumptr[i] = growsum[irow];
-        browsumptr[i] = browsum[irow];
-        }
-    for (irow = 0; irow < temprow; ++irow, ++i)
-        {
-        rowptr[i] = xelbuf[irow];
-        rrowsumptr[i] = rrowsum[irow];
-        growsumptr[i] = growsum[irow];
-        browsumptr[i] = browsum[irow];
-        }
+            unsigned int plane;
 
-    for ( col = 0; col < cols; ++col )
-        {
-        if ( col < ccolso2 || col >= cols - ccolso2 )
-        outputrow[col] = rowptr[crowso2][col];
-        else if ( col == ccolso2 )
-        {
-        rsum = 0.0;
-        gsum = 0.0;
-        bsum = 0.0;
-        leftcol = col - ccolso2;
-        rrowsumptr[addrow][leftcol] = 0L;
-        growsumptr[addrow][leftcol] = 0L;
-        browsumptr[addrow][leftcol] = 0L;
-        for ( ccol = 0; ccol < ccols; ++ccol )
-            {
-            rrowsumptr[addrow][leftcol] += 
-            PPM_GETR( rowptr[addrow][leftcol + ccol] );
-            growsumptr[addrow][leftcol] += 
-            PPM_GETG( rowptr[addrow][leftcol + ccol] );
-            browsumptr[addrow][leftcol] += 
-            PPM_GETB( rowptr[addrow][leftcol + ccol] );
-            }
-        for ( crow = 0; crow < crows; ++crow )
-            {
-            rsum += rrowsumptr[crow][leftcol] * rweights[crow][0];
-            gsum += growsumptr[crow][leftcol] * gweights[crow][0];
-            bsum += browsumptr[crow][leftcol] * bweights[crow][0];
-            }
-        temprsum = rsum + 0.5;
-        tempgsum = gsum + 0.5;
-        tempbsum = bsum + 0.5;
-        CHECK_RED;
-        CHECK_GREEN;
-        CHECK_BLUE;
-        PPM_ASSIGN( outputrow[col], r, g, b );
-        }
-        else
-        {
-        rsum = 0.0;
-        gsum = 0.0;
-        bsum = 0.0;
-        leftcol = col - ccolso2;
-        subcol = col - ccolso2 - 1;
-        addcol = col + ccolso2;  
-        rrowsumptr[addrow][leftcol] = rrowsumptr[addrow][subcol]
-            - PPM_GETR( rowptr[addrow][subcol] )
-            + PPM_GETR( rowptr[addrow][addcol] );
-        growsumptr[addrow][leftcol] = growsumptr[addrow][subcol]
-            - PPM_GETG( rowptr[addrow][subcol] )
-            + PPM_GETG( rowptr[addrow][addcol] );
-        browsumptr[addrow][leftcol] = browsumptr[addrow][subcol]
-            - PPM_GETB( rowptr[addrow][subcol] )
-            + PPM_GETB( rowptr[addrow][addcol] );
-        for ( crow = 0; crow < crows; ++crow )
-            {
-            rsum += rrowsumptr[crow][leftcol] * rweights[crow][0];
-            gsum += growsumptr[crow][leftcol] * gweights[crow][0];
-            bsum += browsumptr[crow][leftcol] * bweights[crow][0];
+            for (plane = 0; plane < convKernelP->planes; ++plane) {
+                if (tuples[row][col][plane] != tuples[row][0][plane])
+                    horizontal = FALSE;
             }
-        temprsum = rsum + 0.5;
-        tempgsum = gsum + 0.5;
-        tempbsum = bsum + 0.5;
-        CHECK_RED;
-        CHECK_GREEN;
-        CHECK_BLUE;
-        PPM_ASSIGN( outputrow[col], r, g, b );
-        }
         }
-    pnm_writepnmrow( stdout, outputrow, cols, maxval, newformat, 0 );
     }
+    return horizontal;
+}
 
-    /* Now write out the remaining unconvolved rows in xelbuf. */
-    for ( irow = crowso2 + 1; irow < crows; ++irow )
-    pnm_writepnmrow(
-            stdout, rowptr[irow], cols, maxval, newformat, 0 );
 
-    }
 
+static bool
+convolutionIncludesVertical(tuple **                  const tuples,
+                            const struct convKernel * const convKernelP) {
 
-/* PPM Vertical Convolution
-**
-** Same as pgm_vertical_convolve()
-**
-*/
-
-static void
-ppm_vertical_convolve(const float ** const rweights,
-                      const float ** const gweights,
-                      const float ** const bweights) {
-    int ccol, col;
-    xel** xelbuf;
-    xel* outputrow;
-    xelval r, g, b;
-    int row, crow;
-    xel **rowptr, *temprptr;
-    int i, irow;
-    int toprow, temprow;
-    int subrow, addrow;
-    int tempcol;
-    long *rcolumnsum, *gcolumnsum, *bcolumnsum;
-    int crowsp1;
-    int addcol;
-    long temprsum, tempgsum, tempbsum;
-
-    /* Allocate space for one convolution-matrix's worth of rows, plus
-    ** a row output buffer. VERTICAL uses an extra row. */
-    xelbuf = pnm_allocarray(cols, crows + 1);
-    outputrow = pnm_allocrow(cols);
-
-    /* Allocate array of pointers to xelbuf */
-    rowptr = (xel **) pnm_allocarray(1, crows + 1);
-
-    /* Allocate space for intermediate column sums */
-    MALLOCARRAY_NOFAIL(rcolumnsum, cols);
-    MALLOCARRAY_NOFAIL(gcolumnsum, cols);
-    MALLOCARRAY_NOFAIL(bcolumnsum, cols);
-
-    for (col = 0; col < cols; ++col) {
-        rcolumnsum[col] = 0L;
-        gcolumnsum[col] = 0L;
-        bcolumnsum[col] = 0L;
-    }
-
-    pnm_writepnminit(stdout, cols, rows, maxval, newformat, 0);
-
-    /* Read in one convolution-matrix's worth of image, less one row. */
-    for (row = 0; row < crows - 1; ++row) {
-        pnm_readpnmrow(ifp, xelbuf[row], cols, maxval, format);
-        if (PNM_FORMAT_TYPE(format) != newformat)
-            pnm_promoteformatrow(xelbuf[row], cols, maxval, format, 
-                                 maxval, newformat);
-        /* Write out just the part we're not going to convolve. */
-        if (row < crowso2)
-            pnm_writepnmrow(stdout, xelbuf[row], cols, maxval, newformat, 0);
-    }
+    bool vertical;
+    unsigned int col;
 
-    /* Now the rest of the image - read in the row at the end of
-    ** xelbuf, and convolve and write out the row in the middle.
-    */
-    /* For first row only */
+    for (col = 0, vertical = TRUE;
+         col < convKernelP->cols && vertical;
+        ++col) {
+        unsigned int row;
+        for (row = 1, vertical = TRUE;
+             row < convKernelP->rows && vertical;
+             ++row) {
 
-    toprow = row + 1;
-    temprow = row % crows;
-    pnm_readpnmrow(ifp, xelbuf[temprow], cols, maxval, format);
-    if (PNM_FORMAT_TYPE(format) != newformat)
-        pnm_promoteformatrow(xelbuf[temprow], cols, maxval, format, maxval, 
-                             newformat);
+            unsigned int plane;
 
-    /* Arrange rowptr to eliminate the use of mod function to determine
-    ** which row of xelbuf is 0...crows.  Mod function can be very costly.
-    */
-    temprow = toprow % crows;
-    i = 0;
-    for (irow = temprow; irow < crows; ++i, ++irow)
-        rowptr[i] = xelbuf[irow];
-    for (irow = 0; irow < temprow; ++irow, ++i)
-        rowptr[i] = xelbuf[irow];
-
-    for (col = 0; col < cols; ++col) {
-        if (col < ccolso2 || col >= cols - ccolso2)
-            outputrow[col] = rowptr[crowso2][col];
-        else if (col == ccolso2) {
-            int const leftcol = col - ccolso2;
-            float rsum, gsum, bsum;
-            rsum = 0.0;
-            gsum = 0.0;
-            bsum = 0.0;
-            for (crow = 0; crow < crows; ++crow) {
-                temprptr = rowptr[crow] + leftcol;
-                for (ccol = 0; ccol < ccols; ++ccol) {
-                    rcolumnsum[leftcol + ccol] += 
-                        PPM_GETR(*(temprptr + ccol));
-                    gcolumnsum[leftcol + ccol] += 
-                        PPM_GETG(*(temprptr + ccol));
-                    bcolumnsum[leftcol + ccol] += 
-                        PPM_GETB(*(temprptr + ccol));
-                }
-            }
-            for (ccol = 0; ccol < ccols; ++ccol) {
-                rsum += rcolumnsum[leftcol + ccol] * rweights[0][ccol];
-                gsum += gcolumnsum[leftcol + ccol] * gweights[0][ccol];
-                bsum += bcolumnsum[leftcol + ccol] * bweights[0][ccol];
-            }
-            temprsum = rsum + 0.5;
-            tempgsum = gsum + 0.5;
-            tempbsum = bsum + 0.5;
-            CHECK_RED;
-            CHECK_GREEN;
-            CHECK_BLUE;
-            PPM_ASSIGN(outputrow[col], r, g, b);
-        } else {
-            int const leftcol = col - ccolso2;
-            float rsum, gsum, bsum;
-            rsum = 0.0;
-            gsum = 0.0;
-            bsum = 0.0;
-            addcol = col + ccolso2;  
-            for (crow = 0; crow < crows; ++crow) {
-                rcolumnsum[addcol] += PPM_GETR( rowptr[crow][addcol]);
-                gcolumnsum[addcol] += PPM_GETG( rowptr[crow][addcol]);
-                bcolumnsum[addcol] += PPM_GETB( rowptr[crow][addcol]);
-            }
-            for (ccol = 0; ccol < ccols; ++ccol) {
-                rsum += rcolumnsum[leftcol + ccol] * rweights[0][ccol];
-                gsum += gcolumnsum[leftcol + ccol] * gweights[0][ccol];
-                bsum += bcolumnsum[leftcol + ccol] * bweights[0][ccol];
-            }
-            temprsum = rsum + 0.5;
-            tempgsum = gsum + 0.5;
-            tempbsum = bsum + 0.5;
-            CHECK_RED;
-            CHECK_GREEN;
-            CHECK_BLUE;
-            PPM_ASSIGN(outputrow[col], r, g, b);
-        }
-    }
-    pnm_writepnmrow(stdout, outputrow, cols, maxval, newformat, 0);
-    
-    /* For all subsequent rows */
-    subrow = crows;
-    addrow = crows - 1;
-    crowsp1 = crows + 1;
-    ++row;
-    for (; row < rows; ++row) {
-        toprow = row + 1;
-        temprow = row % (crows +1);
-        pnm_readpnmrow(ifp, xelbuf[temprow], cols, maxval, format);
-        if (PNM_FORMAT_TYPE(format) != newformat)
-            pnm_promoteformatrow(xelbuf[temprow], cols, maxval, format, 
-                                 maxval, newformat);
-
-        /* Arrange rowptr to eliminate the use of mod function to determine
-        ** which row of xelbuf is 0...crows.  Mod function can be very costly.
-        */
-        temprow = (toprow + 1) % crowsp1;
-        i = 0;
-        for (irow = temprow; irow < crowsp1; ++i, ++irow)
-            rowptr[i] = xelbuf[irow];
-        for (irow = 0; irow < temprow; ++irow, ++i)
-            rowptr[i] = xelbuf[irow];
-
-        for (col = 0; col < cols; ++col) {
-            if (col < ccolso2 || col >= cols - ccolso2)
-                outputrow[col] = rowptr[crowso2][col];
-            else if (col == ccolso2) {
-                int const leftcol = col - ccolso2;
-                float rsum, gsum, bsum;
-                rsum = 0.0;
-                gsum = 0.0;
-                bsum = 0.0;
-
-                for (ccol = 0; ccol < ccols; ++ccol) {
-                    tempcol = leftcol + ccol;
-                    rcolumnsum[tempcol] = rcolumnsum[tempcol] 
-                        - PPM_GETR(rowptr[subrow][ccol])
-                        + PPM_GETR(rowptr[addrow][ccol]);
-                    rsum = rsum + rcolumnsum[tempcol] * rweights[0][ccol];
-                    gcolumnsum[tempcol] = gcolumnsum[tempcol] 
-                        - PPM_GETG(rowptr[subrow][ccol])
-                        + PPM_GETG(rowptr[addrow][ccol]);
-                    gsum = gsum + gcolumnsum[tempcol] * gweights[0][ccol];
-                    bcolumnsum[tempcol] = bcolumnsum[tempcol] 
-                        - PPM_GETB(rowptr[subrow][ccol])
-                        + PPM_GETB(rowptr[addrow][ccol]);
-                    bsum = bsum + bcolumnsum[tempcol] * bweights[0][ccol];
-                }
-                temprsum = rsum + 0.5;
-                tempgsum = gsum + 0.5;
-                tempbsum = bsum + 0.5;
-                CHECK_RED;
-                CHECK_GREEN;
-                CHECK_BLUE;
-                PPM_ASSIGN(outputrow[col], r, g, b);
-            } else {
-                int const leftcol = col - ccolso2;
-                float rsum, gsum, bsum;
-                rsum = 0.0;
-                gsum = 0.0;
-                bsum = 0.0;
-                addcol = col + ccolso2;
-                rcolumnsum[addcol] = rcolumnsum[addcol]
-                    - PPM_GETR(rowptr[subrow][addcol])
-                    + PPM_GETR(rowptr[addrow][addcol]);
-                gcolumnsum[addcol] = gcolumnsum[addcol]
-                    - PPM_GETG(rowptr[subrow][addcol])
-                    + PPM_GETG(rowptr[addrow][addcol]);
-                bcolumnsum[addcol] = bcolumnsum[addcol]
-                    - PPM_GETB(rowptr[subrow][addcol])
-                    + PPM_GETB(rowptr[addrow][addcol]);
-                for (ccol = 0; ccol < ccols; ++ccol) {
-                    rsum += rcolumnsum[leftcol + ccol] * rweights[0][ccol];
-                    gsum += gcolumnsum[leftcol + ccol] * gweights[0][ccol];
-                    bsum += bcolumnsum[leftcol + ccol] * bweights[0][ccol];
-                }
-                temprsum = rsum + 0.5;
-                tempgsum = gsum + 0.5;
-                tempbsum = bsum + 0.5;
-                CHECK_RED;
-                CHECK_GREEN;
-                CHECK_BLUE;
-                PPM_ASSIGN(outputrow[col], r, g, b);
+            for (plane = 0; plane < convKernelP->planes; ++plane) {
+                if (tuples[row][col][plane] != tuples[0][col][plane])
+                    vertical = FALSE;
             }
         }
-        pnm_writepnmrow(stdout, outputrow, cols, maxval, newformat, 0);
     }
-
-    /* Now write out the remaining unconvolved rows in xelbuf. */
-    for (irow = crowso2 + 1; irow < crows; ++irow)
-        pnm_writepnmrow(stdout, rowptr[irow], cols, maxval, newformat, 0);
-
+    return vertical;
 }
 
 
 
 static void
-determineConvolveType(xel * const *         const cxels,
-                      struct convolveType * const typeP) {
+determineConvolveType(tuple **                  const tuples,
+                      const struct convKernel * const convKernelP,
+                      struct convolveType *     const typeP) {
 /*----------------------------------------------------------------------------
-   Determine which form of convolution is best.  The general form always
-   works, but with some special case convolution matrices, faster forms
-   of convolution are possible.
+   *Determine which form of convolution is best to convolve the kernel
+   *convKernelP over tuples[][].  The general form always works, but with some
+   *special case convolution matrices, faster forms of convolution are
+   *possible.
 
    We don't check for the case that one of the PPM colors can have 
    differing types.  We handle only cases where all PPMs are of the same
    special case.
 -----------------------------------------------------------------------------*/
-    int horizontal, vertical;
-    int tempcxel, rtempcxel, gtempcxel, btempcxel;
-    int crow, ccol;
-
-    switch (PNM_FORMAT_TYPE(format)) {
-    case PPM_TYPE:
-        horizontal = TRUE;  /* initial assumption */
-        crow = 0;
-        while (horizontal && (crow < crows)) {
-            ccol = 1;
-            rtempcxel = PPM_GETR(cxels[crow][0]);
-            gtempcxel = PPM_GETG(cxels[crow][0]);
-            btempcxel = PPM_GETB(cxels[crow][0]);
-            while (horizontal && (ccol < ccols)) {
-                if ((PPM_GETR(cxels[crow][ccol]) != rtempcxel) ||
-                    (PPM_GETG(cxels[crow][ccol]) != gtempcxel) ||
-                    (PPM_GETB(cxels[crow][ccol]) != btempcxel)) 
-                    horizontal = FALSE;
-                ++ccol;
-            }
-            ++crow;
-        }
+    bool const horizontal = convolutionIncludesHorizontal(tuples, convKernelP);
+    bool const vertical   = convolutionIncludesVertical(tuples, convKernelP);
 
-        vertical = TRUE;   /* initial assumption */
-        ccol = 0;
-        while (vertical && (ccol < ccols)) {
-            crow = 1;
-            rtempcxel = PPM_GETR(cxels[0][ccol]);
-            gtempcxel = PPM_GETG(cxels[0][ccol]);
-            btempcxel = PPM_GETB(cxels[0][ccol]);
-            while (vertical && (crow < crows)) {
-                if ((PPM_GETR(cxels[crow][ccol]) != rtempcxel) |
-                    (PPM_GETG(cxels[crow][ccol]) != gtempcxel) |
-                    (PPM_GETB(cxels[crow][ccol]) != btempcxel))
-                    vertical = FALSE;
-                ++crow;
-            }
-            ++ccol;
-        }
-        break;
-        
-    default:
-        horizontal = TRUE; /* initial assumption */
-        crow = 0;
-        while (horizontal && (crow < crows)) {
-            ccol = 1;
-            tempcxel = PNM_GET1(cxels[crow][0]);
-            while (horizontal && (ccol < ccols)) {
-                if (PNM_GET1(cxels[crow][ccol]) != tempcxel)
-                    horizontal = FALSE;
-                ++ccol;
-            }
-            ++crow;
-        }
-        
-        vertical = TRUE;  /* initial assumption */
-        ccol = 0;
-        while (vertical && (ccol < ccols)) {
-            crow = 1;
-            tempcxel = PNM_GET1(cxels[0][ccol]);
-            while (vertical && (crow < crows)) {
-                if (PNM_GET1(cxels[crow][ccol]) != tempcxel)
-                    vertical = FALSE;
-                ++crow;
-            }
-            ++ccol;
-        }
-        break;
-    }
-    
-    /* Which type do we have? */
     if (horizontal && vertical) {
-        typeP->ppmConvolver = convolvePpmMean;
-        typeP->pgmConvolver = convolvePgmMean;
+        pm_message("Convolution is a simple mean horizontally and vertically");
+        typeP->convolve = convolveMean;
     } else if (horizontal) {
-        typeP->ppmConvolver = convolvePpmHorizontal;
-        typeP->pgmConvolver = convolvePgmHorizontal;
+        pm_message("Convolution is a simple mean horizontally");
+        typeP->convolve = convolveHorizontal;
     } else if (vertical) {
-        typeP->ppmConvolver = convolvePpmVertical;
-        typeP->pgmConvolver = convolvePgmVertical;
+        pm_message("Convolution is a simple mean vertically");
+        typeP->convolve = convolveVertical;
     } else {
-        typeP->ppmConvolver = convolvePpmGeneral;
-        typeP->pgmConvolver = convolvePgmGeneral;
-    }
-}
-
-
-
-static void
-convolveIt(struct pam *        const inpamP,
-           struct pam *        const outpamP,
-           struct convolveType const convolveType,
-           struct convKernel * const convKernelP) {
-
-    switch (PNM_FORMAT_TYPE(inpamP->format)) {
-    case PPM_TYPE:
-        convolveType.ppmConvolver(inpamP, outpamP, convKernelP);
-        break;
-
-    default:
-        convolveType.pgmConvolver(inpamP, outpamP, convKernelP);
+        typeP->convolve = convolveGeneral;
     }
 }
 
@@ -1987,12 +1469,11 @@ main(int argc, char * argv[]) {
     FILE * ifP;
     FILE * cifP;
     tuple ** ctuples;
-    int cformat;
-    xelval cmaxval;
     struct convolveType convolveType;
     struct convKernel * convKernelP;
     struct pam inpam;
-    sturct pam cpam;
+    struct pam outpam;
+    struct pam cpam;
 
     pnm_init(&argc, argv);
 
@@ -2004,26 +1485,23 @@ main(int argc, char * argv[]) {
     ctuples = pnm_readpam(cifP, &cpam, PAM_STRUCT_SIZE(tuple_type));
     pm_close(cifP);
 
-    if (ccols % 2 != 1 || crows % 2 != 1)
+    if (cpam.width % 2 != 1 || cpam.height % 2 != 1)
         pm_error("the convolution matrix must have an odd number of "
                  "rows and columns" );
 
-    ccolso2 = ccols / 2;
-    crowso2 = crows / 2;
-
     ifP = pm_openr(cmdline.inputFilespec);
 
     pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(allocation_depth));
-    if (inpam.cols < cpam.cols || inpam.rows < cpam.rows)
+    if (inpam.width < cpam.width || inpam.height < cpam.height)
         pm_error("the image is smaller than the convolution matrix" );
 
-    outpam = inpam;
+    outpam = inpam;  /* initial value */
+
+    outpam.file = stdout;
 
     outpam.format = MAX(PNM_FORMAT_TYPE(cpam.format),
                         PNM_FORMAT_TYPE(inpam.format));
-    if (PNM_FORMAT_TYPE(cpam.format) != outpam.format)
-        pnm_promoteformat(cxels, ccols, crows, cmaxval, cformat, 
-                          cmaxval, outpam.format);
+
     if (PNM_FORMAT_TYPE(inpam.format) != outpam.format) {
         switch (PNM_FORMAT_TYPE(outpam.format)) {
         case PPM_TYPE:
@@ -2039,14 +1517,16 @@ main(int argc, char * argv[]) {
 
     pnm_setminallocationdepth(&inpam, MAX(inpam.depth, outpam.depth));
 
-    computeKernel(cpam, ctuples, outpam.format, !cmdline.nooffset,
-                  &convKernelP);
+    convKernelCreate(&cpam, ctuples, outpam.depth, !cmdline.nooffset,
+                     &convKernelP);
+
+    validateEnoughImageToConvolve(&inpam, convKernelP);
 
     /* Handle certain special cases when runtime can be improved. */
 
-    determineConvolveType(ctuples, &convolveType);
+    determineConvolveType(ctuples, convKernelP, &convolveType);
 
-    convolveIt(&inpam, &outpam, convolveType, convKernelP);
+    convolveType.convolve(&inpam, &outpam, convKernelP);
 
     convKernelDestroy(convKernelP);
     pm_close(stdout);