/********************************************************************
 * Project: CS664 Project 1            Justin Miller 
 * File: boxGaussian.c              BOXGAUSSIAN.C
 *
 * Purpose: Implements in the callable procedure boxGaussian, an 
 *   approximate gaussian smoother using repeated convolution with 
 *   a box filter.
 * This File: 
 *   -the box smooth procedures
 *   -the magic number for odd/odd-even filter sizes threshold.
 *
 * See Also: boxGaussian.h
 * To Do: fix the stupid two forms that exist.
 *
 * BUG: figure out what one really wants to do with a shift.
 * Copyright (c) 1995 Cornell University.  All Rights Reserved.
 ********************************************************************/
static char rcsid[] = "@(#)$Header: /usr/u/jmiller/prog/ImProc/src/RCS/boxGaussian.c,v 1.1 1994/08/02 23:45:22 jmiller Exp jmiller $";



#include <stdio.h>
#include "math.h"
#include "image.h"
#include "misc.h"
#include "dbug.h"
#include "boxGaussian.h"
#include "panic.h"
#include "solaris.h"

/* MAGIC NUMBERS*/
#define USE_EVEN_BELOW 2.0  /* threshold above (>=) which only odd 
			       filter sizes are used.*/

/* PROTOTYPES FOR INTERNAL PROTOTYPES*/
static double CalcRecur(int stepsize, int number, int *dest, double goal);
static int *CalcFilterSizes(int number, double sigma);
static void BoxConvolve(int n, FloatImage inim, FloatImage outim);


/* GLOBAL VARIABLES*/
static int shift = 0;


/* PROCEDURES*/

/* FloatImage boxGaussian(GrayImage inimage, double sigma, int nfilters)
 *
 * Performs an approxiamate Gaussian smooth of the input image using
 *   a repeated box convolution.  Does not alter input pgm.
 * Input: pgm image, sigma, and the number of box filters to use.
 * Output: pfm smoothed image.
 * Debug Output: Image size, size of filters used.
 */
FloatImage boxGaussian(GrayImage inimage, double sigma, int nfilters){
  FloatImage filterim;      /* used to process image*/
  FloatImage tempim;
  int *boxsizes;             /* array which holds sizes for filters*/
  int x,y,k;                 /* index variables*/
  float weight = 1;

  DEBUG("BOX_GAUSSIAN: Input image: %d x %d  \t Sigma = %f\n", 
	(int)imGetWidth(inimage), (int)imGetHeight(inimage), sigma);
  assert(sigma >= 1.000000);


  /* Allocate memory for working images*/
  filterim = (FloatImage)imNew(IMAGE_FLOAT, (int)imGetWidth(inimage),
			       (int)imGetHeight(inimage));
  tempim = (FloatImage)imNew(IMAGE_FLOAT, (int)imGetWidth(inimage),
			       (int)imGetHeight(inimage));

  /* promote the image to a FloatImage*/
  for(y = 0; y <= (int)imGetHeight(filterim) - 1; y++) {
    for(x = 0; x <= (int)imGetWidth(filterim) - 1; x++) {
      imRef(filterim, x, y) = (float)imRef(inimage, x, y);
    }
  }
  
  /* calculate the size of the filters*/
  boxsizes = CalcFilterSizes(nfilters, sigma);

  /* perform the convolutions for each filter*/
  for(k = 0; k< nfilters; k++){
    if(boxsizes[k] < 2){
      DEBUG("BOX_GAUSSIAN: Skip mask of size %d\n", boxsizes[k]);
      continue;
    }
    else 
      DEBUG("BOX_GAUSSIAN: Using mask of size %d\n", boxsizes[k]);

    BoxConvolve(boxsizes[k], filterim, tempim);
    weight *= ((float)boxsizes[k])*((float)boxsizes[k]);
  }
  imFree(tempim);        /* free the last tempim */

  for(y = 0; y< (int)imGetHeight(filterim); y++)
    for(x = 0; x< (int)imGetWidth(filterim); x++)
  imRef(filterim, x, y) = imRef(filterim, x, y)/ (float)weight;

  free(boxsizes);


  return(filterim);
}

/* FloatImage boxGaussian(GrayImage inimage, double sigma, int nfilters)
 * MODIFIES
 * Performs an approxiamate Gaussian smooth of the input image using
 *   a repeated box convolution.  Does not alter input pgm.
 * Input: pgm image, sigma, and the number of box filters to use.
 * Output: pfm smoothed image.
 * Debug Output: Image size, size of filters used.
 */
FloatImage 
boxGauss_pfm(FloatImage inimage, double sigma, int nfilters){
  FloatImage filterim = inimage;  /* used to process image*/
  FloatImage tempim;
  int *boxsizes;                  /* array which holds sizes for filters*/
  int x,y,k;                      /* index variables*/
  float weight = 1;

  DEBUG("BOX_GAUSSIAN: Input image: %d x %d  \t Sigma = %f\n", 
	(int)imGetWidth(inimage), (int)imGetHeight(inimage), sigma);
  assert(sigma >= 1.000000);

  /* Allocate memory for working images*/
  tempim = (FloatImage)imNew(IMAGE_FLOAT, (int)imGetWidth(inimage),
			       (int)imGetHeight(inimage));

  /* calculate the size of the filters*/
  boxsizes = CalcFilterSizes(nfilters, sigma);

  /* perform the convolutions for each filter*/
  for(k = 0; k< nfilters; k++){
    DEBUG("BOX_GAUSSIAN: Using mask of size %d\n", boxsizes[k]);
    if(boxsizes[k] < 2){
      DEBUG("BOX_GAUSSIAN: Skip mask of size %d\n", boxsizes[k]);
      continue;
    }
    
    BoxConvolve(boxsizes[k], filterim, tempim);
    weight *= ((float)boxsizes[k])*((float)boxsizes[k]);
  }
  imFree(tempim);        /* free the last tempim */

  for(y = 0; y< (int)imGetHeight(filterim); y++)
    for(x = 0; x< (int)imGetWidth(filterim); x++)
  imRef(filterim, x, y) = imRef(filterim, x, y)/ (float)weight;

  free(boxsizes);


  return(filterim);
}

/* static void BoxConvolve(int n, FloatImage inim, FloatImage outim)
 *                            INTERNAL
 * Box smooths the image with a filter of size n, using a horizontal
 *   then a vertical pass.  On successive even passes the mask is
 *   shifted by one pixel to help offset image shifting.  Note that 
 *   the input image is modified by the convolution and is invalid 
 *   upon return.
 * Input: n - size of filter
 *        inim - the floating point input image
 *        outim - convolved image;
 * Output: (none), inim and outim are modified.
 * Debug Output: (none).
 */
static void BoxConvolve(int n, FloatImage inim, FloatImage outim){
  FloatImage tempim;
  int maxX, maxY;
  int x,y;
  float sum;
  int half, begin, end;

  maxX = (int) imGetWidth(inim);
  maxY = (int) imGetHeight(inim);

  half = n / 2;
  if(n % 2){
    begin = -(half + 1);
    end = half;
  }
  else if(shift){
    begin = -half;
    end = half;
  }
  else{
    begin = -(half + 1);
    end = half - 1;
  }
  
  for(y = 0; y< maxY; y++){
    /* intialize the first term in the output array*/
    sum = 0;

    /* for a even sized mask we want to alternate the 
       direction the image gets shifted */
    if(n % 2){
      for(x = - (half); x <= half  ; x ++){
	sum += imRef(inim, abs(x), y);
      }
    }
    else{
      if(shift % 2){
	for(x = - (half) + 1 ; x <= half  ; x ++){
	  sum += imRef(inim, abs(x), y);
	}
      }
      else{
	for(x = half - 1; x >= -half ; x--){
	  sum += imRef(inim, abs(x), y);
	}
      }
    }

    imRef(outim, 0, y) = sum;

    /* this is the main loop*/
    for(x = 1; x<maxX; x++){
      if(x + end < maxX)
	imRef(outim, x, y) = imRef(outim, x - 1, y) - 
	  imRef(inim, abs(x+begin),y) + 
	    imRef(inim, (x + end), y);
      else
	imRef(outim, x, y) = imRef(outim, x - 1, y) - 
	  imRef(inim, abs(x+begin),y) + 
	    imRef(inim, (maxX-1) - ((x + end) % maxX), y);
    }
  }

  tempim = inim;
  inim = outim;
  outim = tempim;

  for(x = 0; x< maxX; x++){
    /* intialize the first term in the output array*/
    sum = 0;

    if(n % 2){
      for(y = - (half); y <= half  ; y ++){
	sum += imRef(inim, x, abs(y));
      }
    }
    else{
      if(shift % 2){
	for(y = - (half) + 1 ; y <= half  ; y ++){
	  sum += imRef(inim, x, abs(y));
	}
      }
      else{
	for(y = half - 1; y >= -half ; y--){
	  sum += imRef(inim, x, abs(y));
	}
      }
    }
    imRef(outim, x, 0) = sum;
  }

  /* this is the main loop it is independent of the initialization 
     to take advantage of the fact that consequtive x locations are
     located closer in memory than consequtive y locations*/
  for(y = 1; y<maxY; y++){
    for(x = 0; x< maxX; x++){
      if( y + end < maxY)
	imRef(outim, x, y) = imRef(outim, x , y-1) - 
	  imRef(inim, x, abs(y+begin)) + 
	    imRef(inim, x, (y + end));
      else
	imRef(outim, x, y) = imRef(outim, x , y-1) - 
	  imRef(inim, x, abs(y+begin)) + 
	    imRef(inim, x, (maxY - 1) - ((y + end) % maxY));
    }
  }
  
/*  if(n % 2)
    shift = (shift +1) % 2; */           /* increment the shift toggle*/

}


/* static double CalcRecur(int stepsize, int number, 
 *                         int *dest, double goal);
 *                         INTERNAL
 * Calculates the sizes that should be used for the box filters
 *   based on the formula 
 *     n(1)^2 + n(2)^2 +...+n(3)^2 ~= 12*sigma^2 + 4
 *   by examing all possible filter sizes between .6 and .3 of the 
 *   goal, except for the last filter which is the integer closest
 *   to the square root of the goal.  
 * See Also: CalcFilterSizes().
 * Input: stepsize - (1, even and odd; 2, just odd)
 *        number- number of filters remaining to be calculated, 
 *        *dest - array in which to put optimal answer,
 *        goal - amount of total unaccounted for so far.
 * Output: array of integers specifying the sizes of the filters.
 * DebugOutput: (none).
 */  
static double CalcRecur(int stepsize, int number, 
			int *dest, double goal){
  int k;
  int upper, lower;
  double bestdist = infinity();
  double current;
  int *bestarray, *temparray, *tempptr;


  if(goal <= 1){
    bestdist = ABS(goal);
    for(k=1; k<=number; k++)
      dest[k] = 0;
    return(bestdist);
  }
  if(number == 1)
    {
      /* this is the last filter and should be the closest integer*/
      dest[1] = nint(sqrt(goal));
      
      if(((dest[1]) % 2) != 1 && stepsize > 1){
	/* it has to be odd for stepsize of 2, find closest odd*/
	if(ABS((goal - 
		((double) dest[1]-1)*((double) dest[1]-1))) 
	   < ABS(goal - 
		 ((double) dest[1]+1)*((double) dest[1]+1)))
	  dest[1]--;
	else 
	  dest[1]++;
      }
      bestdist = (ABS(goal - ((double) dest[1])*((double) dest[1])));
    }
  else{
    temparray = malloc((number + 1)*sizeof(int));  /* working array*/
    bestarray = malloc((number + 1)*sizeof(int));  /* optimal solution*/

    /* we are calculating an intermediate filter size*/
    
    upper = nint(0.7 * sqrt(goal));   /* upper bound on search*/
    lower = nint(0.3 * sqrt(goal));   /* lower bound on search*/
    if(!(upper % 2))                  /* make them odd*/
      upper = upper + 1;
    if(!(lower % 2))
      lower = lower + 1;
    
    for(k = upper; k>=lower && k>=2; k -=stepsize){
      current = CalcRecur(stepsize, number - 1 , temparray, 
			  (goal - 
			   ((double) k)*((double) k)));
      if(current < bestdist){
	temparray[0] = k;
	tempptr = bestarray;
	bestarray = temparray;
	temparray = tempptr;
	bestdist = current;
      }
    }
    
    for(k = 1; k <= number; k++){
      if(goal <= 1)                  
	dest[k] = 0;                 /* if the goal for this level is 
					1 or 0 don't include the filter*/
      else
	dest[k] = bestarray[k-1];    /* copy the optimal solution into 
					input array*/
    }
    free(bestarray);
    free(temparray);
  }
  
  return(bestdist);
}

/* static int *CalcFilterSizes(int number, double sigma)   
 *                         INTERNAL
 * Calculates the sizes that should be used for the box filters
 *   based on the formula 
 *     n(1)^2 + n(2)^2 +...+n(3)^2 ~= 12*sigma^2 + 4
 *   by calling the recursive procedure CalcRecur().  Note that for
 *   sigma >= USE_EVEN_BELOW, only odd sized filters are used, this 
 *   helps to avoid shifting of the image.
 * Input: number of filters and sigma
 * Output: array of integers specifying the sizes of the filters.
 * DebugOutput: (none).
 */
static int *CalcFilterSizes(int number, double sigma){
  int *array, *output;
  int k;

  array = malloc((number+1)*sizeof(int));
  output = malloc((number)*sizeof(int));
  if(sigma >= USE_EVEN_BELOW)
    CalcRecur(2, number, array, (12*sigma*sigma + 4));
  else
    CalcRecur(1, number, array, (12*sigma*sigma + 4));
  for(k=1; k<= number; k++)
    output[k-1] = array[k];
  
  free(array);
  return(output);

}

    
	
