--- pnm/pnmconvol.c.orig	Mon Oct  4 10:11:36 1993
+++ pnm/pnmconvol.c	Sun Jan 10 23:48:40 1999
@@ -1,5 +1,10 @@
 /* pnmconvol.c - general MxN convolution on a portable anymap
 **
+** Version 2.0.1 January 30, 1995
+**
+** Major rewriting by Mike Burns
+** Copyright (C) 1994, 1995 by Mike Burns (burns@chem.psu.edu)
+**
 ** Copyright (C) 1989, 1991 by Jef Poskanzer.
 **
 ** Permission to use, copy, modify, and distribute this software and its
@@ -10,31 +15,109 @@
 ** implied warranty.
 */
 
+/* Version 2.0.1 Changes
+** ---------------------
+** Fixed four lines that were improperly allocated as sizeof( float ) when they
+** should have been sizeof( long ).
+**
+** Version 2.0 Changes
+** -------------------
+** Reduce run time by general optimizations and handling special cases of
+** convolution matrices.  Program automatically determines if convolution 
+** matrix is one of the types it can make use of so no extra command line
+** arguments are necessary.
+**
+** Examples of convolution matrices for the special cases are
+**
+**    Mean       Horizontal    Vertical
+**    x x x        x x x        x y z
+**    x x x        y y y        x y z
+**    x x x        z z z        x y z
+**
+** I don't know if the horizontal and vertical ones are of much use, but
+** after working on the mean convolution, it gave me ideas for the other two.
+**
+** Some other compiler dependent optimizations
+** -------------------------------------------
+** Created separate functions as code was getting too large to put keep both
+** PGM and PPM cases in same function and also because SWITCH statement in
+** inner loop can take progressively more time the larger the size of the 
+** convolution matrix.  GCC is affected this way.
+**
+** Removed use of MOD (%) operator from innermost loop by modifying manner in
+** which the current xelbuf[] is chosen.
+** 
+*/
+
 #include "pnm.h"
 
+/* Macros to verify that r,g,b values are within proper range */
+
+#define CHECK_GRAY \
+    if ( tempgsum < 0L ) g = 0; \
+    else if ( tempgsum > maxval ) g = maxval; \
+    else g = tempgsum;
+
+#define CHECK_RED \
+    if ( temprsum < 0L ) r = 0; \
+    else if ( temprsum > maxval ) r = maxval; \
+    else r = temprsum;
+
+#define CHECK_GREEN \
+    if ( tempgsum < 0L ) g = 0; \
+    else if ( tempgsum > maxval ) g = maxval; \
+    else g = tempgsum;
+
+#define CHECK_BLUE \
+    if ( tempbsum < 0L ) b = 0; \
+    else if ( tempbsum > maxval ) b = maxval; \
+    else b = tempbsum;
+
+
+static int check_convolve_type ARGS((xel **cxels));
+static void pgm_general_convolve ARGS((void));
+static void ppm_general_convolve ARGS((void));
+static void pgm_mean_convolve ARGS((void));
+static void ppm_mean_convolve ARGS((void));
+static void pgm_vertical_convolve ARGS((void));
+static void ppm_vertical_convolve ARGS((void));
+static void pgm_horizontal_convolve ARGS((void));
+static void ppm_horizontal_convolve ARGS((void));
+
+#define TRUE	1
+#define FALSE	0
+
+#define GENERAL_CONVOLVE	0
+#define MEAN_CONVOLVE		1
+#define HORIZONTAL_CONVOLVE	2
+#define VERTICAL_CONVOLVE	3
+
+static FILE* ifp;
+static float** rweights;
+static float** gweights;
+static float** bweights;
+static int crows, ccols, ccolso2, crowso2;
+static int cols, rows;
+static xelval maxval;
+static int format, newformat;
+static float rmeanweight, gmeanweight, bmeanweight;
+
 int
 main( argc, argv )
     int argc;
     char* argv[];
     {
     FILE* cifp;
-    FILE* ifp;
     xel** cxels;
-    xel** xelbuf;
-    xel* outputrow;
-    xel x;
-    int argn, crows, ccols, cformat, ccolso2, crowso2;
-    int rows, cols, format, newformat, crow, row;
+    int argn, cformat;
+    int crow, row;
     register int ccol, col;
-    xelval cmaxval, maxval;
+    xelval cmaxval;
     xelval g;
-    float** gweights;
     float gsum;
-    xelval r, b;
-    float** rweights;
-    float** bweights;
     float rsum, bsum;
     char* usage = "<convolutionfile> [pnmfile]";
+    int convolve_type;
 
     pnm_init( &argc, argv );
 
@@ -90,6 +173,7 @@
             }
         }
 
+
     /* Set up the normalized weights. */
     rweights = (float**) pm_allocarray( ccols, crows, sizeof(float) );
     gweights = (float**) pm_allocarray( ccols, crows, sizeof(float) );
@@ -115,6 +199,14 @@
 		break;
 		}
 	    }
+
+    /* For mean_convolve routines.  All weights of a single color are the same
+    ** so just grab any one of them.
+    */
+    rmeanweight = rweights[0][0];
+    gmeanweight = gweights[0][0];
+    bmeanweight = bweights[0][0];
+
     switch ( PNM_FORMAT_TYPE(format) )
 	{
 	case PPM_TYPE:
@@ -131,11 +223,199 @@
 	break;
 	}
 
+    /* Handle certain special cases when runtime can be improved. */
+
+    convolve_type = check_convolve_type(cxels);
+
+    switch ( PNM_FORMAT_TYPE(format) )
+	{
+	case PPM_TYPE:
+	switch (convolve_type)
+	    {
+	    case MEAN_CONVOLVE:
+	    ppm_mean_convolve();
+	    break;
+
+	    case HORIZONTAL_CONVOLVE:
+	    ppm_horizontal_convolve();
+	    break;
+
+	    case VERTICAL_CONVOLVE:
+	    ppm_vertical_convolve();
+	    break;
+
+	    default: /* GENERAL_CONVOLVE */
+	    ppm_general_convolve();
+	    break;  
+	    }
+	break;
+
+	default:
+	switch (convolve_type)
+	    {
+	    case MEAN_CONVOLVE:
+	    pgm_mean_convolve();
+	    break;
+
+	    case HORIZONTAL_CONVOLVE:
+	    pgm_horizontal_convolve();
+	    break;
+
+	    case VERTICAL_CONVOLVE:
+	    pgm_vertical_convolve();
+	    break;
+
+	    default: /* GENERAL_CONVOLVE */
+	    pgm_general_convolve();
+	    break;  
+	    }
+	break;
+	} /* PNM_TYPE */
+
+    pm_close( ifp );
+    exit( 0 );
+    }
+
+
+
+/* check_convolve_type
+**
+** Determine if the convolution matrix is one of the special cases that
+** can be processed faster than the general form.
+**
+** Does not check for the case where one of the PPM colors can have differing 
+** types.  Only handles cases where all PPM's are of the same special case.
+*/
+
+static int
+check_convolve_type (cxels)
+    xel **cxels;
+    {
+    int convolve_type;
+    int horizontal, vertical;
+    int tempcxel, rtempcxel, gtempcxel, btempcxel;
+    int crow, ccol;
+
+    switch ( PNM_FORMAT_TYPE(format) )
+	{
+	case PPM_TYPE:
+	horizontal = TRUE;
+	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;
+	    }
+
+	vertical = TRUE;
+	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;
+	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;
+	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 )
+	convolve_type = MEAN_CONVOLVE;
+    else if ( horizontal )
+	convolve_type = HORIZONTAL_CONVOLVE;
+    else if ( vertical )
+	convolve_type = VERTICAL_CONVOLVE;
+    else
+	convolve_type = GENERAL_CONVOLVE;
+
+    return (convolve_type);
+    }
+
+
+
+
+/* General PGM Convolution
+**
+** No useful redundancy in convolution matrix.
+*/
+
+static void
+pgm_general_convolve()
+    {
+    register int ccol, col;
+    xel** xelbuf;
+    xel* outputrow;
+    xel x, y;
+    xelval g;
+    int row, crow;
+    float gsum;
+    xel **rowptr, *temprptr;
+    int toprow, temprow;
+    int i, irow;
+    int leftcol;
+    long tempgsum;
+
     /* 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. */
@@ -155,65 +435,1470 @@
     */
     for ( ; row < rows; ++row )
 	{
-	pnm_readpnmrow( ifp, xelbuf[row % crows], cols, maxval, format );
+	toprow = row + 1;
+	temprow = row % crows;
+	pnm_readpnmrow( ifp, xelbuf[temprow], cols, maxval, format );
 	if ( PNM_FORMAT_TYPE(format) != newformat )
 	    pnm_promoteformatrow(
-		xelbuf[row % crows], cols, maxval, format, maxval, newformat );
+		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] = xelbuf[(row - crowso2) % crows][col];
+		outputrow[col] = rowptr[crowso2][col];
 	    else
 		{
-                switch ( PNM_FORMAT_TYPE(format) )
-                    {
-                    case PPM_TYPE:
-		    rsum = gsum = bsum = 0.0;
-		    for ( crow = 0; crow < crows; ++crow )
-			for ( ccol = 0; ccol < ccols; ++ccol )
-			    {
-			    x = xelbuf[(row+1+crow) % crows][col-ccolso2+ccol];
-			    rsum += PPM_GETR( x ) * rweights[crow][ccol];
-			    gsum += PPM_GETG( x ) * gweights[crow][ccol];
-			    bsum += PPM_GETB( x ) * bweights[crow][ccol];
-			    }
-		    if ( rsum < 0.0 ) r = 0;
-		    else if ( rsum > maxval ) r = maxval;
-		    else r = rsum + 0.5;
-		    if ( gsum < 0.0 ) g = 0;
-		    else if ( gsum > maxval ) g = maxval;
-		    else g = gsum + 0.5;
-		    if ( bsum < 0.0 ) b = 0;
-		    else if ( bsum > maxval ) b = maxval;
-		    else b = bsum + 0.5;
-		    PPM_ASSIGN( outputrow[col], r, g, b );
-                    break;
-
-                    default:
-		    gsum = 0.0;
-		    for ( crow = 0; crow < crows; ++crow )
-			for ( ccol = 0; ccol < ccols; ++ccol )
-			    {
-			    x = xelbuf[(row+1+crow) % crows][col-ccolso2+ccol];
-			    gsum += PNM_GET1( x ) * gweights[crow][ccol];
-			    }
-		    if ( gsum < 0.0 ) g = 0;
-		    else if ( gsum > maxval ) g = maxval;
-		    else g = gsum + 0.5;
+		leftcol = col - ccolso2;
+		gsum = 0.0;
+		for ( crow = 0; crow < crows; ++crow )
+		    {
+		    temprptr = rowptr[crow] + leftcol;
+		    for ( ccol = 0; ccol < ccols; ++ccol )
+			gsum += PNM_GET1( *(temprptr + ccol) )
+			    * gweights[crow][ccol];
+		    }
+		    tempgsum = gsum + 0.5;
+		    CHECK_GRAY;
 		    PNM_ASSIGN1( outputrow[col], g );
-                    break;
-                    }
 		}
+	    }
+	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 );
+
+    pm_close( stdout );
+    }
+
+
+/* PGM Mean Convolution
+**
+** This is the common case where you just want the target pixel replaced with
+** the average value of its neighbors.  This can work much faster than the
+** general case because you can reduce the number of floating point operations
+** that are required since all the weights are the same.  You will only need
+** to multiply by the weight once, not for every pixel in the convolution
+** matrix.
+**
+** This algorithm works by creating sums for each column of crows height for
+** the whole width of the image.  Then add ccols column sums together to obtain
+** the total sum of the neighbors and multiply that sum by the weight.  As you
+** move right to left to calculate the next pixel, take the total sum you just
+** generated, add in the value of the next column and subtract the value of the
+** leftmost column.  Multiply that by the weight and that's it.  As you move
+** down a row, calculate new column sums by using previous sum for that column
+** and adding in pixel on current row and subtracting pixel in top row.
+**
+*/
+
+
+static void
+pgm_mean_convolve()
+    {
+    register int ccol, col;
+    xel** xelbuf;
+    xel* outputrow;
+    xelval g;
+    int row, crow;
+    xel **rowptr, *temprptr;
+    int leftcol;
+    int i, irow;
+    int toprow, temprow;
+    int subrow, addrow;
+    int subcol, addcol;
+    long gisum;
+    int tempcol, crowsp1;
+    long tempgsum;
+    long *gcolumnsum;
+
+    /* 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 */
+    gcolumnsum = (long *) pm_allocrow( cols, sizeof(long) );
+    for ( col = 0; col < cols; ++col )
+	gcolumnsum[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];
+
+    gisum = 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 )
+		    gcolumnsum[leftcol + ccol] += 
+			PNM_GET1( *(temprptr + ccol) );
+		}
+	    for ( ccol = 0; ccol < ccols; ++ccol)
+		gisum += gcolumnsum[leftcol + ccol];
+	    tempgsum = (float) gisum * gmeanweight + 0.5;
+	    CHECK_GRAY;
+	    PNM_ASSIGN1( outputrow[col], g );
+	    }
+	else
+	    {
+	    /* Column numbers to subtract or add to isum */
+	    subcol = col - ccolso2 - 1;
+	    addcol = col + ccolso2;  
+	    for ( crow = 0; crow < crows; ++crow )
+		gcolumnsum[addcol] += PNM_GET1( rowptr[crow][addcol] );
+	    gisum = gisum - gcolumnsum[subcol] + gcolumnsum[addcol];
+	    tempgsum = (float) gisum * gmeanweight + 0.5;
+	    CHECK_GRAY;
+	    PNM_ASSIGN1( outputrow[col], g );
+	    }
+	}
+    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];
+
+	gisum = 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;
+		    gcolumnsum[tempcol] = gcolumnsum[tempcol]
+			- PNM_GET1( rowptr[subrow][ccol] )
+			+ PNM_GET1( rowptr[addrow][ccol] );
+		    gisum += gcolumnsum[tempcol];
+		    }
+		tempgsum = (float) gisum * gmeanweight + 0.5;
+		CHECK_GRAY;
+		PNM_ASSIGN1( outputrow[col], g );
+		}
+	    else
+		{
+		/* Column numbers to subtract or add to isum */
+		subcol = col - ccolso2 - 1;
+		addcol = col + ccolso2;  
+		gcolumnsum[addcol] = gcolumnsum[addcol]
+		    - PNM_GET1( rowptr[subrow][addcol] )
+		    + PNM_GET1( rowptr[addrow][addcol] );
+		gisum = gisum - gcolumnsum[subcol] + gcolumnsum[addcol];
+		tempgsum = (float) gisum * gmeanweight + 0.5;
+		CHECK_GRAY;
+		PNM_ASSIGN1( outputrow[col], g );
+		}
+	    }
 	pnm_writepnmrow( stdout, outputrow, cols, maxval, newformat, 0 );
 	}
-    pm_close( ifp );
 
     /* Now write out the remaining unconvolved rows in xelbuf. */
-    for ( ; row < rows + crowso2; ++row )
+    for ( irow = crowso2 + 1; irow < crows; ++irow )
 	pnm_writepnmrow(
-            stdout, xelbuf[(row-crowso2) % crows], cols, maxval, newformat, 0 );
+            stdout, rowptr[irow], cols, maxval, newformat, 0 );
+
+    pm_close( stdout );
+    }
+
+
+/* PGM 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.
+**
+*/
+
+static void
+pgm_horizontal_convolve()
+    {
+    register int ccol, col;
+    xel** xelbuf;
+    xel* outputrow;
+    xel x;
+    xelval g;
+    int row, crow;
+    xel **rowptr, *temprptr;
+    int leftcol;
+    int i, irow;
+    int temprow;
+    int subcol, addcol;
+    float gsum;
+    int addrow, subrow;
+    long **growsum, **growsumptr;
+    int crowsp1;
+    long tempgsum;
+
+    /* 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. */
+    /* crows current rows and 1 extra for newest added row.           */
+    growsum = (long **) pm_allocarray( cols, crows + 1, sizeof(long) );
+    growsumptr = (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 );
+	}
+
+    /* 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 )
+	growsumptr[crow] = growsum[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;
+	    gsum = 0.0;
+	    for ( crow = 0; crow < crows; ++crow )
+		{
+		temprptr = rowptr[crow] + leftcol;
+		growsumptr[crow][leftcol] = 0L;
+		for ( ccol = 0; ccol < ccols; ++ccol )
+		    growsumptr[crow][leftcol] += 
+		        PNM_GET1( *(temprptr + ccol) );
+		gsum += growsumptr[crow][leftcol] * gweights[crow][0];
+		}
+	    tempgsum = gsum + 0.5;
+	    CHECK_GRAY;
+	    PNM_ASSIGN1( outputrow[col], g );
+	    }
+	else
+	    {
+	    gsum = 0.0;
+	    leftcol = col - ccolso2;
+	    subcol = col - ccolso2 - 1;
+	    addcol = col + ccolso2;
+	    for ( crow = 0; crow < crows; ++crow )
+		{
+		growsumptr[crow][leftcol] = growsumptr[crow][subcol]
+		    - PNM_GET1( rowptr[crow][subcol] )
+		    + PNM_GET1( rowptr[crow][addcol] );
+		gsum += growsumptr[crow][leftcol] * gweights[crow][0];
+		}
+	    tempgsum = gsum + 0.5;
+	    CHECK_GRAY;
+	    PNM_ASSIGN1( outputrow[col], g );
+	    }
+        }
+    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 )
+	{
+	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 );
+
+	temprow = (row + 2) % crowsp1;
+	i = 0;
+	for (irow = temprow; irow < crowsp1; ++i, ++irow)
+	    {
+	    rowptr[i] = xelbuf[irow];
+	    growsumptr[i] = growsum[irow];
+	    }
+	for (irow = 0; irow < temprow; ++irow, ++i)
+	    {
+	    rowptr[i] = xelbuf[irow];
+	    growsumptr[i] = growsum[irow];
+	    }
+
+	for ( col = 0; col < cols; ++col )
+	    {
+	    if ( col < ccolso2 || col >= cols - ccolso2 )
+		outputrow[col] = rowptr[crowso2][col];
+	    else if ( col == ccolso2 )
+		{
+		gsum = 0.0;
+		leftcol = col - ccolso2;
+		growsumptr[addrow][leftcol] = 0L;
+		for ( ccol = 0; ccol < ccols; ++ccol )
+		    growsumptr[addrow][leftcol] += 
+			PNM_GET1( rowptr[addrow][leftcol + ccol] );
+		for ( crow = 0; crow < crows; ++crow )
+		    gsum += growsumptr[crow][leftcol] * gweights[crow][0];
+		tempgsum = gsum + 0.5;
+		CHECK_GRAY;
+		PNM_ASSIGN1( outputrow[col], g );
+		}
+	    else
+		{
+		gsum = 0.0;
+		leftcol = col - ccolso2;
+		subcol = col - ccolso2 - 1;
+		addcol = col + ccolso2;  
+		growsumptr[addrow][leftcol] = growsumptr[addrow][subcol]
+		    - PNM_GET1( rowptr[addrow][subcol] )
+		    + PNM_GET1( rowptr[addrow][addcol] );
+		for ( crow = 0; crow < crows; ++crow )
+		    gsum += growsumptr[crow][leftcol] * gweights[crow][0];
+		tempgsum = gsum + 0.5;
+		CHECK_GRAY;
+		PNM_ASSIGN1( outputrow[col], g );
+		}
+	    }
+	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 );
+
+    pm_close( stdout );
+    }
+
+
+/* PGM Vertical Convolution
+**
+** Uses column sums as in Mean Convolution.
+**
+*/
+
+
+static void
+pgm_vertical_convolve()
+    {
+    register int ccol, col;
+    xel** xelbuf;
+    xel* outputrow;
+    xelval g;
+    int row, crow;
+    xel **rowptr, *temprptr;
+    int leftcol;
+    int i, irow;
+    int toprow, temprow;
+    int subrow, addrow;
+    int tempcol;
+    float gsum;
+    long *gcolumnsum;
+    int crowsp1;
+    int addcol;
+    long tempgsum;
+
+    /* 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 */
+    gcolumnsum = (long *) pm_allocrow( cols, sizeof(long) );
+    for ( col = 0; col < cols; ++col )
+	gcolumnsum[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 );
+	}
+
+    /* 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 */
+
+    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 if ( col == ccolso2 )
+	    {
+	    gsum = 0.0;
+	    leftcol = col - ccolso2;
+	    for ( crow = 0; crow < crows; ++crow )
+		{
+		temprptr = rowptr[crow] + leftcol;
+		for ( ccol = 0; ccol < ccols; ++ccol )
+		    gcolumnsum[leftcol + ccol] += 
+			PNM_GET1( *(temprptr + ccol) );
+		}
+	    for ( ccol = 0; ccol < ccols; ++ccol)
+		gsum += gcolumnsum[leftcol + ccol] * gweights[0][ccol];
+	    tempgsum = gsum + 0.5;
+	    CHECK_GRAY;
+	    PNM_ASSIGN1( outputrow[col], g );
+	    }
+	else
+	    {
+	    gsum = 0.0;
+	    leftcol = col - ccolso2;
+	    addcol = col + ccolso2;  
+	    for ( crow = 0; crow < crows; ++crow )
+		gcolumnsum[addcol] += PNM_GET1( rowptr[crow][addcol] );
+	    for ( ccol = 0; ccol < ccols; ++ccol )
+		gsum += gcolumnsum[leftcol + ccol] * gweights[0][ccol];
+	    tempgsum = gsum + 0.5;
+	    CHECK_GRAY;
+	    PNM_ASSIGN1( outputrow[col], g );
+	    }
+	}
+    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 )
+		{
+		gsum = 0.0;
+		leftcol = col - ccolso2;
+		for ( ccol = 0; ccol < ccols; ++ccol )
+		    {
+		    tempcol = leftcol + ccol;
+		    gcolumnsum[tempcol] = gcolumnsum[tempcol] 
+			- PNM_GET1( rowptr[subrow][ccol] )
+			+ PNM_GET1( rowptr[addrow][ccol] );
+		    gsum = gsum + gcolumnsum[tempcol] * gweights[0][ccol];
+		    }
+		tempgsum = gsum + 0.5;
+		CHECK_GRAY;
+		PNM_ASSIGN1( outputrow[col], g );
+		}
+	    else
+		{
+		gsum = 0.0;
+		leftcol = col - ccolso2;
+		addcol = col + ccolso2;
+		gcolumnsum[addcol] = gcolumnsum[addcol]
+		    - PNM_GET1( rowptr[subrow][addcol] )
+		    + PNM_GET1( rowptr[addrow][addcol] );
+		for ( ccol = 0; ccol < ccols; ++ccol )
+		    gsum += gcolumnsum[leftcol + ccol] * gweights[0][ccol];
+		tempgsum = gsum + 0.5;
+		CHECK_GRAY;
+		PNM_ASSIGN1( outputrow[col], g );
+		}
+	    }
+	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 );
+
+    pm_close( stdout );
+    }
+
+
+
+
+/* PPM General Convolution Algorithm
+**
+** No redundancy in convolution matrix.  Just use brute force.
+** See pgm_general_convolve() for more details.
+*/
+
+static void
+ppm_general_convolve()
+    {
+    register int ccol, col;
+    xel** xelbuf;
+    xel* outputrow;
+    xel x, y;
+    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 );
+
+    pm_close( stdout );
+    }
+
+
+/* PPM Mean Convolution
+**
+** Same as pgm_mean_convolve() but for PPM.
+**
+*/
+
+static void
+ppm_mean_convolve()
+    {
+    register 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;
+    float rsum, gsum, bsum;
+    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 );
+
+    pm_close( stdout );
+    }
+
+
+/* PPM Horizontal Convolution
+**
+** Same as pgm_horizontal_convolve()
+**
+**/
+
+static void
+ppm_horizontal_convolve()
+    {
+    register int ccol, col;
+    xel** xelbuf;
+    xel* outputrow;
+    xel x;
+    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 );
+	}
+
+    /* 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 );
+
+
+    /* For all subsequent rows */
+
+    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 );
+
+	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];
+	    }
+
+	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];
+		    }
+		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 );
+
+    pm_close( stdout );
+    }
+
+
+/* PPM Vertical Convolution
+**
+** Same as pgm_vertical_convolve()
+**
+*/
+
+static void
+ppm_vertical_convolve()
+    {
+    register 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 tempcol;
+    float rsum, gsum, bsum;
+    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 */
+    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 );
+	}
+
+    /* 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 */
+
+    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 if ( col == ccolso2 )
+	    {
+	    rsum = 0.0;
+	    gsum = 0.0;
+	    bsum = 0.0;
+	    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)
+		{
+		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
+	    {
+	    rsum = 0.0;
+	    gsum = 0.0;
+	    bsum = 0.0;
+	    leftcol = col - ccolso2;
+	    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 )
+		{
+		rsum = 0.0;
+		gsum = 0.0;
+		bsum = 0.0;
+		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] );
+		    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
+		{
+		rsum = 0.0;
+		gsum = 0.0;
+		bsum = 0.0;
+		leftcol = col - ccolso2;
+		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 );
+		}
+	    }
+	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 );
 
     pm_close( stdout );
-    exit( 0 );
     }
