#ifndef _GFilter_H
#define _GFilter_H

#include "GImage.h"

/* GFilter is an abstract decendant of GImage.  Derivatives of this class can
 * be used to filter an image in some way (scale, rotate, etc).  Filters
 * defined in this file include:
 *
 *     GFilter_GammaCorrection
 *     GFilter_Scale
 *     GFilter_Rotate (0, 90, 180, and 270 degrees only)
 *     GFilter_ContrastAdjustment
 *     GFilter_WhiteBalance
 * 
 * Written by:  Chris Studholme
 * Last Update: 25-May-2000
 * Copyright:   GPL (http://www.fsf.org/copyleft/gpl.html)
 */

class GFilter : public GImage {
 protected:
  GImage& src;
  
 protected:
  GFilter(GImage& image) : src(image) {
  }
  GImage* getSourceImage() {
    return &src;
  }

  /* These get???() routines are just defaults.  They are all virtual and 
   * should be overridden where necessary.
   */
  const char* getComments() {
    return src.getComments();
  }
  int getWidth() {
    return src.getWidth();
  }
  int getHeight() {
    return src.getHeight();
  }
  int getPreferredWidth() {
    return src.getPreferredWidth();
  }
  int getPreferredHeight() {
    return src.getPreferredHeight();
  }
  double getAspectRatio() {
    return src.getAspectRatio();
  }
};


class GFilter_GammaCorrection : public GFilter {
  /*
   * gamma correction filter
   */

 protected:
  int min, range;
  short* table;

 public:
  GFilter_GammaCorrection(GImage& image, float gamma=1)
    : GFilter(image) {
    table=NULL;
    min=-16384;
    range=32768;
    setGammaCorrection(gamma);
  }

  ~GFilter_GammaCorrection() {
    if (table)
      delete[] table;
  }

  void setGammaCorrection(float gamma=1);

  void getPixel(short& red, short& green, short& blue,
		float x1, float y1, float x2, float y2);
  void getScanLineHorz(short* red, short* green, short* blue,
		       float x1, float y1, float x2, float y2, int npixels);
  void getScanLineVert(short* red, short* green, short* blue,
		       float x1, float y1, float x2, float y2, int npixels);
};


class GFilter_ContrastAdjustment : public GFilter {
  /*
   * contrast adjustment filter
   */

 protected:
  int min;
  int diff;

 public:
  GFilter_ContrastAdjustment(GImage& image, int min, int max)
    : GFilter(image) {
    setContrastAdjustment(min,max);
  }

  /* [min,max] is translated to [-16384,+16383]
   */
  void setContrastAdjustment(int mn, int mx) {
    min=mn;
    diff=mx-mn;
  }

  void getPixel(short& red, short& green, short& blue,
		float x1, float y1, float x2, float y2);
  void getScanLineHorz(short* red, short* green, short* blue,
		       float x1, float y1, float x2, float y2, int npixels);
  void getScanLineVert(short* red, short* green, short* blue,
		       float x1, float y1, float x2, float y2, int npixels);
};


class GFilter_WhiteBalance : public GFilter {
  /*
   * contrast adjustment filter
   */

 protected:
  int rdiff,gdiff,bdiff;

 public:
  GFilter_WhiteBalance(GImage& image, float red=1, float blue=1)
    : GFilter(image) {
    setWhiteLevel(red,blue);
  }

  /* green is implied
   */
  void setWhiteLevel(float red, float blue) {
    float green=1;
    if (red>1) {
      green/=red;
      blue/=red;
      red=1;
    }
    if (blue>1) {
      red/=blue;
      green/=blue;
      blue=1;
    }
    rdiff=(int)(32678*red);
    gdiff=(int)(32678*green);
    bdiff=(int)(32678*blue);
  }

  void getPixel(short& red, short& green, short& blue,
		float x1, float y1, float x2, float y2);
  void getScanLineHorz(short* red, short* green, short* blue,
		       float x1, float y1, float x2, float y2, int npixels);
  void getScanLineVert(short* red, short* green, short* blue,
		       float x1, float y1, float x2, float y2, int npixels);
};

class GFilter_Scale : public GFilter {
  /*
   * scaling filter
   */

 protected:
  double aspectratio;
  int w,h;

  float xfactor,xoffset;
  float yfactor,yoffset;

 public:
  GFilter_Scale(GImage& image)
    : GFilter(image) {
    aspectratio=src.getAspectRatio();
    w=src.getWidth();
    h=src.getHeight();
    calculateConstants();
  }

  /* if either of width or height are zero, it will be calculated;
   * both must not be zero
   */
  GFilter_Scale(GImage& image, int width, int height, double aspect=1)
    : GFilter(image) {
    w=width;
    h=height;
    aspectratio=aspect;
    if (w==0)
      w = (int)((h*src.getWidth()*src.getAspectRatio())/(src.getHeight()*aspectratio));
    else if (h==0)
      h = (int)((w*src.getHeight()*aspectratio)/(src.getWidth()*src.getAspectRatio()));
    calculateConstants();
  }

  /* changing the aspect ratio does not change the image dimensions;
   * therefore, the image may end up cropped if the dimensions are not reset
   */
  void setAspectRatio(double ratio) {
    aspectratio=ratio;
    calculateConstants();
  }

  /* sets the maximum dimensions the image may have; one dimension may be
   * set smaller to maintain aspect ratio and not crop the image
   */
  void setMaximumDimensions(int width, int height) {
    w=width;
    h=height;
    double temp = (src.getWidth()*src.getAspectRatio())/(src.getHeight()*aspectratio);
    int ow = (int)(h*temp);
    if (ow<w)
      w=ow;
    else {
      int oh = (int)(w/temp);
      if (oh<h)
	h=oh;
    }
    calculateConstants();
  }

  /* sets the minimum dimensions the image may have; one dimension may be
   * set larger to maintain aspect ratio and not crop the image;
   * if you wish to only set one dimension, use this method by specifying
   * the other as zero (it will be automatically set as appropriate)
   */
  void setMinimumDimensions(int width, int height) {
    w=width;
    h=height;
    double temp = (src.getWidth()*src.getAspectRatio())/(src.getHeight()*aspectratio);
    int ow = (int)(h*temp);
    if (ow>w)
      w=ow;
    else {
      int oh = (int)(w/temp);
      if (oh>h)
	h=oh;
    }
    calculateConstants();
  }

  /* sets the image dimensions exactly; if crop=true, the image may be
   * cropped to maintain aspect ratio; if crop=false, the aspect ratio
   * will be changed to ensure that the image is not cropped
   */
  void setDimensions(int width, int height, bool crop=true) {
    w=width;
    h=height;
    if (!crop)
      aspectratio=h*src.getWidth()*src.getAspectRatio()/(w*src.getHeight());
    calculateConstants();
  }

  int getWidth() {
    return w;
  }
  int getUncropWidth() {
    int ow=(int)((h*src.getWidth()*src.getAspectRatio())/(src.getHeight()*aspectratio));
    return ow>w?ow:w;
  }
  int getHeight() {
    return h;
  }
  int getUncropHeight() {
    int oh=(int)((w*src.getHeight()*aspectratio)/(src.getWidth()*src.getAspectRatio()));
    return oh>h?oh:h;
  }
  int getPreferredWidth() {
    return w;
  }
  int getPreferredHeight() {
    return h;
  }
  double getAspectRatio() {
    return aspectratio;
  }

  void getPixel(short& red, short& green, short& blue,
		float x1, float y1, float x2, float y2);
  void getScanLineHorz(short* red, short* green, short* blue,
		       float x1, float y1, float x2, float y2, int npixels);
  void getScanLineVert(short* red, short* green, short* blue,
		       float x1, float y1, float x2, float y2, int npixels);
  
 private:
  void calculateConstants();

};


class GFilter_Rotate : public GFilter {
  /*
   * rotate filter
   */

 protected:
  int degree;

 public:
  GFilter_Rotate(GImage& image, int angle=0)
    : GFilter(image) {
    degree=(360+angle)%360;
    if (degree%90!=0)
      fprintf(stderr,"GFilter_Rotate: rotate angle must be multiple of 90 degrees %d\n",degree);
  }

  void setClockwiseRotate(int angle=90) {
    degree=(360+angle)%360;
    if (degree%90!=0)
      fprintf(stderr,"GFilter_Rotate: rotate angle must be multiple of 90 degrees %d\n",degree);
  }

  void setCounterClockwiseRotate(int angle=90) {
    degree=(360-angle)%360;
    if (degree%90!=0)
      fprintf(stderr,"GFilter_Rotate: rotate angle must be multiple of 90 degrees %d\n",degree);
  }

  int getWidth() {
    return (degree==90)||(degree==270) ? src.getHeight() : src.getWidth();
  }
  int getHeight() {
    return (degree==90)||(degree==270) ? src.getWidth() : src.getHeight();
  }
  int getPreferredWidth() {
    return (degree==90)||(degree==270) ? src.getPreferredHeight() : src.getPreferredWidth();
  }
  int getPreferredHeight() {
    return (degree==90)||(degree==270) ? src.getPreferredWidth() : src.getPreferredHeight();
  }
  double getAspectRatio() {
    return (degree==90)||(degree==270) ? 1/src.getAspectRatio() : src.getAspectRatio();
  }

  void getPixel(short& red, short& green, short& blue,
		float x1, float y1, float x2, float y2);
  void getScanLineHorz(short* red, short* green, short* blue,
		       float x1, float y1, float x2, float y2, int npixels);
  void getScanLineVert(short* red, short* green, short* blue,
		       float x1, float y1, float x2, float y2, int npixels);
  
};

#endif

