/*
 * $Header: /usr/u/wjr/src/support/RCS/smooth.c,v 1.7 1991/11/07 22:43:34 wjr Exp $
 */
/*
 * Perform Gaussian convolution on images.
 */

static char rcsid[] = "@(#)$Header: /usr/u/wjr/src/support/RCS/smooth.c,v 1.7 1991/11/07 22:43:34 wjr Exp $";

#include "misc.h"
#include "image.h"
#include "hist.h"
#include "gaussian.h"

FloatImage
gaussConvol(GrayImage im, double sigma)
{
    unsigned width;
    unsigned height;
    FloatImage intermed;
    FloatImage final;
    double *gaussian;
    int sspread;    
    int x, y, j;
    register double gaussval0;

    assert(im != (GrayImage)NULL);

    width = imGetWidth(im);
    height = imGetHeight(im);

    /* Allocate work areas */
    intermed = (FloatImage)imNew(IMAGE_FLOAT, width, height);
    if (intermed == (FloatImage)NULL) {
	return((FloatImage)NULL);
	}

    final = (FloatImage)imNew(IMAGE_FLOAT, width, height);
    if (final == (FloatImage)NULL) {
	imFree(intermed);
	return((FloatImage)NULL);
	}

    /* Generate the (one-sided) Gaussian distribution */
    gaussian = makeGaussian(sigma, &sspread);
    if (gaussian == (double *)NULL) {
	imFree(intermed);
	imFree(final);
	return((FloatImage)NULL);
	}

    /*
     * We do the convolution in two passes: once convolving
     * horizontally with a gaussian, then re-convolving vertically.
     * Since a gaussian is separable, this gives the required
     * two-dimensional convolution.
     *
     * We do this by, for the horizontal direction:
     *  for each x value in the output of the convolution,
     *    for each value in the convolution mask,
     *      find out which x value in the input mask corresponds to
     *      that position in the mask and that position in the output
     *      image
     *      for each y position,
     *        add into the output image the appropriate pixel of the input
     *        image, multiplied by that part of the convolution mask.
     *
     * The vertical direction is similar.
     *
     * Note that since we traverse the image both horizontally and
     * vertically, we are bound to hit unfortunate cache and VM behaviour,
     * due to having a large stride on one of the passes.
     * Since the original image pixel values are smaller than the
     * intermediate or final pixel values (they are small integers,
     * not floating point numbers), the "worse" direction is computed
     * first to minimise this effect.
     */

    /* Now convolve with a gaussian in the X direction. */
    gaussval0 = gaussian[0];
    for (x = 0; x < width; x++) {
	/*
	 * When we hit the edge of the image, we want the edge row
	 * or column to continue to infinity, so there is no sharp
	 * transition at the edge, as there would be if we treated all
	 * pixels outside the image as being zero, or if we replicated
	 * the image.
	 */

	/* Do the centre bin of the Gaussian */
	for (y = 0; y < height; y++) {
	    imRef(intermed, x, y) = imRef(im, x, y) * gaussval0;
	    }

	/* and do the side bins */
	for (j = 1; j <= sspread; j++) {
	    /* Find out the actual values of x+-j, with borders sticking. */
	    /* Gotta cast width to int, or the expression becomes unsigned */
	    int xval1 = MIN((int)width - 1, x + j);
	    int xval2 = MAX(0, x - j);
	    register double gaussval = gaussian[j];

	    for (y = 0; y < height; y++) {
		imRef(intermed, x, y) +=
		    (imRef(im, xval1, y) + imRef(im, xval2, y)) * gaussval;
		}
	    }
	}

    /* Now convolve with a gaussian in the Y direction */
    for (y = 0; y < height; y++) {
	for (x = 0; x < width; x++) {
	    imRef(final, x, y) = imRef(intermed, x, y) * gaussval0;
	    }

	for (j = 1; j <= sspread; j++) {
	    /* Find out the actual values of y+-j, with borders sticking. */
	    int yval1 = MIN((int)height - 1, y + j);
	    int yval2 = MAX(0, y - j);
	    register double gaussval = gaussian[j];

	    for (x = 0; x < width; x++) {
		imRef(final, x, y) +=
		    (imRef(intermed, x, yval1) + imRef(intermed, x, yval2))
			* gaussval;
		}
	    }
	}

    /* Free all intermediate storage */
    imFree(intermed);

    return(final);
    }

Histogram
smoothHist(Histogram h, double sigma, boolean circular)
{
    unsigned nbins;
    Histogram newH;
    int i, j;
    int loc1;
    int loc2;
    double *gaussian;
    int sspread;
    double range;

    assert(h != NULLHIST);
    nbins = hiNBins(h);
    newH = hiNew(nbins, hiLow(h), hiHigh(h));
    if (newH == NULLHIST) {
	return(NULLHIST);
	}
    range = hiHigh(h) - hiLow(h);

    gaussian = makeGaussian(sigma, &sspread);
    if (gaussian == (double *)NULL) {
	hiFree(newH);
	return(NULLHIST);
	}
    
    if (!circular) {
	/* Replicate the edges out to infinity */
	for (i = 0; i < nbins; i++) {
	    hiCount(newH, i) = hiCount(h, i) * gaussian[0];

	    for (j = 1; j <= sspread; j++) {
		hiCount(newH, i) +=
		    (hiCount(h, MAX(0, i - j)) +
		     hiCount(h, MIN((int)nbins - 1, i + j))) * gaussian[j];
		}
	    }
	}
    else {
	for (i = 0; i < nbins; i++) {
	    hiCount(newH, i) = hiCount(h, i) * gaussian[0];
	    for (j = 1; j <= sspread; j++) {
		/*
		 * The input histogram is circular - perform a circular
		 * convolution.
		 */
		loc1 = i + j;
		while (loc1 >= nbins) {
		    loc1 -= nbins;
		    }
		loc2 = i - j;
		while (loc2 < 0) {
		    loc2 += nbins;
		    }
		hiCount(newH, i) +=
		    (hiCount(h, loc1) + hiCount(h, loc2))
			* gaussian[j];
		}
	    }
	}

    return(newH);
    }
