Posted to tcl by kbk at Sun Sep 20 02:48:30 GMT 2009view pretty

/* 
 * Implementation of a Gaussian IIR filter after
 * Ian T. Young and Lucas J. van Vliet. "Recursive implementation of
 * the Gaussian filter." _Signal Processing_ 44 (1995), pp. 139-151.
 */

/* Initialize the filter coefficients for the IIR filter. */

static inline void
iir_coefficients(float sigma,	/* Radius of the filter */
		 float* coefs) /* Output coefficients, normalized */
{
    float q;
    float b0, b1, b2, b3;

    /* Correct the filter width (Eq. 11a in the paper). */

    if (sigma > 2.5f) {
	q = 0.98711f * sigma - 0.96330f;
    } else {
	q = 3.97156f - 4.14554f * sqrt(1.0f - 0.26891f * sigma);
    }

    /* Determine the filter coefficients */

    b0 = 1.57825f + q * (2.44413f + q * (1.42810f + q * 0.422205f));
    b1 = q * (2.44413f + q * (2.85619f + q * 1.26661f));
    b2 = q * q * (-1.42810f + q * -1.26661f);
    b3 = q * q * q * 0.422205f;

    /* Determine the normalization constant (Eq. 10) */

    coefs[0] = 1.0f - (b1 + b2 + b3) / b0;

    /* Scale the filter coefficients */

    coefs[1] = b1 / b0;
    coefs[2] = b2 / b0;
    coefs[3] = b3 / b0;

}

/* Apply the IIR filter to the input value and the last three output values */

static inline float
iir_kernel(float in,		/* Input value */
	   float out_1,		/* Last output value */
	   float out_2,		/* Penultimate output value */
	   float out_3,		/* Antepenultimate output value */
	   float* coefs)	/* Filter coefficients */
{
    return coefs[0] * in
	+ coefs[1] * out_1 + coefs[2] * out_2 + coefs[3] * out_3;
}

/* Apply the IIR filter across a row of pixels, first left then right.
 * Filter "in place": replace the row with a filtered copy.
 * The 'z' array is expected to be at least 'pad' elements in length
 * and is used to handle edge effects. */

static inline void
iir_row (unsigned width,	/* Width of the image */
	 unsigned pad,	    /* Number of pixels of pad at the edges */
	 float* coefs,		/* Filter coefficients */
	 float* row,		/* One row of pixels to filter */
	 float* z)     /* Temporary array for starting and stopping */
{
    int i;

    /* Start the filter running on the row in reverse to "prime the pump" */

    z[0] = iir_kernel(row[pad-1], 0.0f, 0.0f, 0.0f, coefs);
    z[1] = iir_kernel(row[pad-2], z[0], 0.0f, 0.0f, coefs);
    z[2] = iir_kernel(row[pad-3], z[1], z[0], 0.0f, coefs);
    for (i = 3; i < pad; ++i) {
	z[i] = iir_kernel(row[pad-i-1], z[i-1], z[i-2], z[i-3], coefs);
    }

    /* Turn around and start the row. */

    row[0] = iir_kernel(row[0], z[pad-1], z[pad-2], z[pad-3], coefs);
    row[1] = iir_kernel(row[1], row[0], z[pad-1], z[pad-2], coefs);
    row[2] = iir_kernel(row[2], row[1], row[0], z[pad-1], coefs);
    for (i = 3; i+pad < width; ++i) {
	row[i] = iir_kernel(row[i], row[i-1], row[i-2], row[i-3], coefs);
    }

    /* Stash the last 'pad' columns off in the 'z' array before
     * overwriting them */

    for (; i < width; ++i) {
	z[i+pad-width] = row[i];
	row[i] = iir_kernel(row[i], row[i-1], row[i-2], row[i-3], coefs);
    }

    /* Continue for an overrun of 'pad' columns */

    z[pad-1] = iir_kernel(z[pad-1], row[width-1], row[width-2], row[width-3],
			  coefs);
    z[pad-2] = iir_kernel(z[pad-2], z[pad-1], row[width-1], row[width-2],
			  coefs);
    z[pad-3] = iir_kernel(z[pad-3], z[pad-2], z[pad-1], row[width-1], coefs);
    for (i = pad-4; i >= 0; --i) {
	z[i] = iir_kernel(z[i], z[i+1], z[i+2], z[i+3], coefs);
    }

    /* Now prime the filter in the opposite direction. */

    for (i = 3; i < pad; ++i) {
	z[i] = iir_kernel(z[i], z[i-1], z[i-2], z[i-3], coefs);
    }

    /* Turn around and start filling the row */

    row[width-1] = iir_kernel(row[width-1], z[pad-1], z[pad-2], z[pad-3],
			      coefs);
    row[width-2] = iir_kernel(row[width-2], row[width-1], z[pad-1], z[pad-2],
			      coefs);
    row[width-3] = iir_kernel(row[width-3], row[width-2], row[width-1],
			      z[pad-1], coefs);

    /* Run the filter from right to left */

    for (i = width-4; i >= 0; --i) {
	row[i] = iir_kernel(row[i], row[i+1], row[i+2], row[i+3], coefs);
    }

}

/*
 * Apply the IIR blur filter across the columns on one row of an image
 */

static inline void
iir_v_row(unsigned width,	/* Width of the image */
	  float* coefs,		/* Filter coefficients */
	  float* in_row,	/* Input row */
	  float* out_row_1,	/* Previous output row */
	  float* out_row_2,	/* Penultimate output row */
	  float* out_row_3,	/* Antepenultimate output row */
	  float* out_row)	/* OUTPUT: current output row. May overwrite
				 * in_row */
{
    unsigned i;
    for (i = 0; i < width; ++i) {
	out_row[i] = iir_kernel(in_row[i], out_row_1[i], out_row_2[i],
				out_row_3[i], coefs);
    }
}

/*
 * Apply the IIR blur filter to an entire image
 */

void
iir_blur_image(unsigned height,	/* Height of the image */
	       unsigned width,	/* Width of the image */
	       float* image,	/* Pixels of the image, row-major order */
	       float sigma,	/* Sigma of the Gaussian filter */
	       unsigned pad)	/* Half-length of the support */
{
    int i;
    float coefs[4];
    float* y = malloc(pad * sizeof(float));
    float* z = malloc(pad * width * sizeof(float));
    float* zero = malloc(width * sizeof(float));
    memset(zero, 0, width * sizeof(float));

    /* Compute the coefficients */

    iir_coefficients(sigma, coefs);

    /* Start a filter running on the top fringe of the image in reverse
     * to 'prime the pump.' */

    iir_v_row(width, coefs, image + (pad-1)*width, zero, zero, zero, z);
    iir_v_row(width, coefs, image + (pad-2)*width, z, zero, zero, z+width);
    iir_v_row(width, coefs, image + (pad-3)*width, z+width, z, zero,
	      z+2*width);
    for (i = 3; i < pad; ++i) {
	iir_v_row(width, coefs, image+(pad-i-1)*width, z+(i-1)*width,
		  z+(i-2)*width, z+(i-3)*width, z+i*width);
    }

    /* Turn around and run the filter forward to the bottom fringe */

    iir_v_row(width, coefs, image, z+(pad-1)*width, z+(pad-2)*width,
	      z+(pad-3)*width, image);
    iir_v_row(width, coefs, image+width, image, z+(pad-1)*width,
	      z+(pad-2)*width, image+width);
    iir_v_row(width, coefs, image+2*width, image+width, image,
	      z+(pad-1)*width, image+2*width);
    for (i = 3; i + pad < height; ++i) {
	iir_v_row(width, coefs, image+i*width, image+(i-1)*width,
		  image+(i-2)*width, image+(i-3)*width, image+i*width);
    }

    /* Safely store the last 'pad' rows in the 'z' array before
     * overwriting them */

    for (; i < height; ++i) {
	memcpy(z+(i+pad-height)*width, image+i*width, width*sizeof(float));
	iir_v_row(width, coefs, image+i*width, image+(i-1)*width,
		  image+(i-2)*width, image+(i-3)*width, image+i*width);
    }

    /* Continue for an overrun of 'pad' rows */

    iir_v_row(width, coefs, z+(pad-1)*width, image+(height-1)*width,
	      image+(height-2)*width,
	      image+(height-3)*width, z+(pad-1)*width);
    iir_v_row(width, coefs, z+(pad-2)*width, z+(pad-1)*width,
	      image+(height-1)*width, image+(height-2)*width,
	      z+(pad-2)*width);
    iir_v_row(width, coefs, z+(pad-3)*width, z+(pad-2)*width,
	      z+(pad-1)*width, image+(height-1)*width, z+(pad-3)*width);
    for (i = pad-4; i >= 0; --i) {
	iir_v_row(width, coefs, z+i*width, z+(i+1)*width, z+(i+2)*width,
		  z+(i+3)*width, z+i*width);
    }

    /* Now start the filter in the upward direction */

    for (i = 3; i < pad; ++i) {
	iir_v_row(width, coefs, z+i*width, z+(i-1)*width, z+(i-2)*width,
		  z+(i-3)*width, z+i*width);
    }
    iir_v_row(width, coefs, image+(height-1)*width, z+(pad-1)*width,
	      z+(pad-2)*width, z+(pad-3)*width, image+(height-1)*width);
    iir_v_row(width, coefs, image+(height-2)*width, image+(height-1)*width,
	      z+(pad-1)*width, z+(pad-2)*width, image+(height-2)*width);
    iir_v_row(width, coefs, image+(height-3)*width, image+(height-2)*width,
	      image+(height-1)*width, z+(pad-1)*width, image+(height-3)*width);

    /* Run the filter upward to the top of the image */

    for (i=height-4; i >= 0; --i) {
	iir_v_row(width, coefs, image+i*width, image+(i+1)*width,
		  image+(i+2)*width, image+(i+3)*width, image+i*width);
	iir_row(width, pad, coefs, image+(i+3)*width, y);
    }
    iir_row(width, pad, coefs, image+2*width, y);
    iir_row(width, pad, coefs, image+width, y);
    iir_row(width, pad, coefs, image, y);

    free(zero);
    free(z);
    free(y);
}