
/* Copyright (c) 1991, 1992 Cornell University.  All Rights Reserved.
 *
 * Use, reproduction and preparation of derivative works of this software is
 * permitted.  Any copy of this software or of any derivative work must
 * include both the above copyright notices of Cornell University and this
 * paragraph.  This software is made available AS IS, and Cornell University
 * disclaims all warranties, express or implied.
 */

static char rcsid[] = "some thing wjr's assert makes me put here";

/*   Greg Klanderman    Spring 1990
     
     this program takes as input an unsigned-byte picture (pgm)
     and smoothes it according to the mask specified,
     outputing a signed float array.
*/

#include <stdio.h>
#include <math.h>

#include "clarg.h"
#include "smooth.h"
#include "strings.h"
#include "string-support.h"
#include "boxfilter.h"


static char *USAGE =
  "\n%s:  smooth the PGM input using the mask specified, outputting a PFM file.\n"
  "\n"
  "Arguments are:\n"
  "   -input file:          Specify the input file name (PGM or PFM)\n"
  "   -output file:         Specify the output file name (PFM)\n"
  "   -gaussian sigma:      Specify sigma for a gaussian mask\n"
  "   -width wid:           Mask width in terms of sigma (default=4.0) for\n"
  "                         Gaussian smoother, or width of lowpass filter\n"
  "                         (default=13) in pixels (eg. filter:[-width .. width])\n"
  "   -mask W1 W2 ... Wn:   Enter the mask directly, from center to right edge.\n"
  "   -normalize:           Normalize the sum of absolute values of the mask to 1.0\n"
  "   -scale [scale]:       Scale the mask by the scale given (default 1.0)\n"
  "   -print:               Output the gaussian or user-specified mask to stderr.\n"
  "   -box a b c d:         Box filter the image once with parameters a, b, c, d.\n"
  "   -approx sigma:        Approximate a gaussian convolution with a sequence\n"
  "                         of box-filtering operations.\n"
  "   -lp [cutfreq]:        Truncated separable lowpass filter with w_c=pi*cutfreq.\n"
  "   -kaiser [beta]:       Use a Kaiser window (delault beta=3.86) with the\n"
  "                         Gaussian, user specified, or lowpass filter.\n"
  "\n"
  "Various types of smoothing operations are supported.  For the Gaussian mask,\n"
  "a normalized Gaussian with sigma=S is used.  Narrowing the mask width with\n"
  "the \"width\" option will give faster but less accurate results.  The user-\n"
  "specified mask allows direct entry of the mask values, starting from the\n"
  "center and working to the right edge.  The one-dimensional mask specified is\n"
  "used for separate convolutions in X and Y.  The \"showmask\" option may be\n"
  "used with either of these operations to print the right side of the mask to\n"
  "stderr.  The \"normalize\" option causes the gaussian or user specified mask\n"
  "to be normalized to the sum specified, or 1.0 if no sum is given.  The box\n"
  "filter smoothing operation allows you to specify the size and offset for a\n"
  "box filter.  The parameter A specifies the number of pixels left of the\n"
  "orgin the box filter reaches. B specifies the number of pixels right of the\n"
  "orgin.  C specifies the number of pixels above the orgin the filter reaches.\n"
  "D specifies the number of pixels beneath the orgin the filter reaches.  So\n"
  "A=B=C=D=0 gives no smoothing (1x1 box filter) and A=B=C=D=2 gives an orgin-\n"
  "centered 5x5 box filter.  Finally, a gaussian filter may be approximated by\n"
  "a series of 1 to 4 box-filtering operations, requiring only constant time in\n"
  "terms of sigma.  The combination is guaranteed to have 0 pixels group delay.\n"
  "Argument names may be abreviated.  If no input file is specified, input is\n"
  "from stdin, and if no output file is specified, output is to stdout.\n\n";


#define INPUT 	"i.nput"
#define OUTPUT 	"o.utput"
#define GMASK	"g.aussian"
#define MASK    "m.ask"
#define WIDTH   "w.idth"
#define SHOW	"p.rint"
#define NORM    "n.ormalize"
#define BOX     "b.ox"
#define APPROX  "a.pprox"
#define LOWPASS  "lp"
#define KAISER  "k.aiser"
#define SCALE   "s.cale"


static CLINIT clinit[] = {{INPUT,  1,  1, NULL,    0},
			  {OUTPUT, 1,  1, NULL,    0},
			  {GMASK,  1,  1, cl_real, 0},
			  {MASK,   1, 99, cl_real, 0},
			  {WIDTH,  1,  1, cl_real, 0},
			  {NORM,   0,  0, NULL,    0},
			  {SCALE,  0,  1, cl_real, 0},
			  {SHOW,   0,  0, NULL,    0},
			  {BOX,    4,  4, cl_int,  0},
			  {APPROX, 1,  1, cl_real, 0},
			  {LOWPASS, 0,  1, cl_real, 0},
			  {KAISER, 0,  1, cl_real, 0},
			  {NULL,   1,  1, NULL,    0}};


static char *depend[] = {GMASK "+" MASK "+" BOX "+" APPROX "+" LOWPASS "== 1",    /* exactly one */
			 WIDTH " => " GMASK "||" LOWPASS,
			 SCALE"||"SHOW"||"NORM"||"KAISER "=>" GMASK"||"MASK"||"LOWPASS,
			 "!(UNNAMED && "INPUT")",
			 NULL};

extern inline double square(double x)
{
  return x*x;
}

double ideal_lowpass(double x, double cut)
{
#ifdef LINUX
  return x == 0.0 ? cut : sin(cut * x * M_PI) / (M_PI * x);
#else
  return x == 0.0 ? cut : sinpi(cut * x) / (M_PI * x);
#endif
}

FloatVector makeLowpassFilter(double cutfreq, int width) /* return lp[0..width] */
{
  FloatVector lp = vecNew(VECTOR_FLOAT, width + 1);
  int i;

  for (i = 0; i <= width; i++) {
    vecRef(lp, i) = ideal_lowpass(i, cutfreq);
  }

  return lp;
}

double bessel_I0(register double x)          /* see Oppenheim&Schafer p.457 */
{
  register double ds=1.0, d=0.0, s=1.0;

  x *= x;
  do {
    d  += 2.0;
    ds *= x/(d*d);
    s  += ds;
  } while (ds >= 0.2e-8 * s);

  return s;
}

FloatVector makeKaiserWindow(double beta, int width) /* see O+S pp.452--455 */
{                                            /* return kw[0..width] */
  FloatVector kw = vecNew(VECTOR_FLOAT, width + 1);
  int i;
  double w_inv = 1.0/width;
  double bb = bessel_I0(beta);

  for (i = 0; i <= width; i++) {
    vecRef(kw, i) = bessel_I0(beta * sqrt(1.0 - square(i * w_inv))) / bb;
  }

  return kw;
}
    

int main(int argc, char **argv, char **envp)
{
  AnyImage    pict=0;
  AnyImage    smoothed=0;
  FloatVector mask = (FloatVector) NULL;
  int         len;
  int         z;
  float       norm;
  int         a=0, b=0, c=0, d=0;
  float       approx_sigma = 0.0;
  char        *infile;
  int         imno;
  

  if (cl_init(argc, argv, envp, clinit, depend, USAGE, CL_NORESTRICT))  /* parse args */
    return 0;

  infile = (cl_opt(INPUT)
	    ? cl_arg1(INPUT)
	    : cl_opt(CL_UNNAMED) ? cl_arg1(CL_UNNAMED) : NULL);

  if (cl_opt(BOX)) {
    a = atol(cl_arg(BOX, 1));
    b = atol(cl_arg(BOX, 2));
    c = atol(cl_arg(BOX, 3));
    d = atol(cl_arg(BOX, 4));

    if (a < 0  ||  b < 0  ||  c < 0  ||  d < 0) {
      fprintf(stderr, "SMOOTH: Box filter parameters must be positive\n");
      return -1;
    }
  }
  else if (cl_opt(APPROX)) {
    approx_sigma = atof(cl_arg1(APPROX));
  }
  else {
    if (cl_opt(GMASK)) {		     /* make gaussian mask if specified */
      float width, sigma;

      width = cl_opt(WIDTH) ? atof(cl_arg1(WIDTH)) : 4.0; /* get width */
      if (width <= 0.0)  width = 4.0;

      sigma =  atof(cl_arg1(GMASK));
      mask  = smoothMakeGaussianMask(sigma, width);
    }
    else if (cl_opt(LOWPASS)) {
      mask = makeLowpassFilter(cl_numarg(LOWPASS) ? atof(cl_arg1(LOWPASS)) : 0.5,
                               cl_opt(WIDTH) ? (int)atof(cl_arg1(WIDTH)) : 13);
    }
    else {				     /* else get user spec. mask */
      len  = (cl_numarg(MASK));
      mask = (FloatVector) vecNew(VECTOR_FLOAT, len);

      if (mask == (FloatVector) NULL) {
	fprintf(stderr, "SMOOTH: Couldn't allocate mask\n");
	return -1;
      }
      
      for (z = 0; z < len; z++)
	vecRef(mask, z) = atof(cl_arg(MASK, z+1));
    }

    if (mask == (FloatVector) NULL) {
      fprintf(stderr, "SMOOTH: Couldn't allocate mask\n");
      return -1;
    }

    if (cl_opt(KAISER)) {
      FloatVector kw;

      kw = makeKaiserWindow(cl_numarg(KAISER) ? atof(cl_arg1(KAISER)) : 3.86,
                            vecGetLength(mask) - 1);
      len = vecGetLength(mask);
      for (z = 0; z < len; z++) { 
        vecRef(mask, z) *= vecRef(kw, z);
      }

      vecFree(kw);
    }

    if (cl_opt(NORM)) {
      smoothNormalizeMask(mask);
    }

    if (cl_opt(SCALE)) {
      norm = cl_numarg(SCALE) ? atof(cl_arg1(SCALE)) : 1.0;

      len = vecGetLength(mask);
      for (z = 0; z < len; z++) {            /* scale the mask */
        vecRef(mask, z) *= norm;
      }
    }
  
    if (cl_opt(SHOW)) {			     /* print it if user wants to show */
      fprintf(stderr, "\n********** MASK ****************\n");
      len = vecGetLength(mask);
      for (z = 0; z < len; z++) {
	if (z != 0  &&  z % 6 == 0)  fprintf(stderr, "\n");
	fprintf(stderr, "%10.6f  ", vecRef(mask, z));
      }
      fprintf(stderr, "\n********************************\n\n");

      return 0;				     /* do no smoothing */
    }
  }


  imno = 0;                                  /* read in image(s) */
  while ((imno == 0 || infile == NULL) &&
         (pict = imLoad(IMAGE_GRAY | IMAGE_FLOAT | IMAGE_RGB, infile))) {
    imno++;

    if (imGetType(pict) == IMAGE_GRAY) {
      if (cl_opt(BOX)) {
        smoothed = (AnyImage) boxFilterGray(((GrayImage) pict), a, b, c, d);
      }
      else if (cl_opt(APPROX)) {
        smoothed = (AnyImage) boxFilterGraySigma(((GrayImage) pict), approx_sigma);
      }
      else {
        smoothed = smoothImage(((GrayImage) pict), mask);
      }
    }
    else if (imGetType(pict) == IMAGE_FLOAT) {
      if (cl_opt(BOX)) {
        smoothed = (AnyImage) boxFilterFloat(((FloatImage) pict), a, b, c, d);
      }
      else if (cl_opt(APPROX)) {
        smoothed = (AnyImage) boxFilterFloatSigma(((FloatImage) pict), approx_sigma);
      }
      else {
        smoothed = smoothImage(((FloatImage) pict), mask);
      }
    }
    else if (imGetType(pict) == IMAGE_RGB) {
      if (cl_opt(BOX)  ||  cl_opt(APPROX)) {
        smoothed = (AnyImage) NULL;
      }
      else {
        smoothed = smoothImage(((RGBImage) pict), mask);
      }
    }
    else {				     /* better never get here */
      assert(0);
    }

    if (smoothed == (AnyImage) NULL) {
      fprintf(stderr, "SMOOTH: Error smoothing image\n");
      return -1;
    }

    imDocString(smoothed) = strNewComment(argc, argv, envp, imDocString(pict), NULLSTRING);

    if (cl_opt(APPROX)) {
      String msg = strPrintF(NULLSTRING, "\n >>\n >>     Approximated Sigma: %12.5f",
                             BoxFilterApproxSigma);
      strCat(imDocString(smoothed), msg);
      strFree(msg);
    }
  
    if (cl_opt(GMASK)) {
      String msg = strPrintF(NULLSTRING, "\n >>\n >>     Gaussian Sigma:     %12.5f",
                             atof(cl_arg1(GMASK)));
      strCat(imDocString(smoothed), msg);
      strFree(msg);
    }

    imSave(smoothed, (cl_opt(OUTPUT) ? cl_arg1(OUTPUT) : NULL));
    imFree(smoothed);
    imFree(pict);
  }

  if (imno == 0) {
    fprintf(stderr, "SMOOTH: Error reading input or unsupported image type\n");
    return -1;
  }

  return 0;
}





