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

#include "GFilter.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).  
 * 
 * Written by:  Chris Studholme
 * Last Update: 25-May-2000
 * Copyright:   GPL (http://www.fsf.org/copyleft/gpl.html)
 */


inline short boundcheck(int x) {
  if (x<-32768) return -32768;
  if (x>32767) return 32767;
  return x;
}


/* ================ GFilter_GammaCorrection methods  ================ */
inline void GammaCorrect(short& red, short& green, short& blue, short* table) {
  red = *(short*)(((char*)table)+(red&0xFFFE));
  green = *(short*)(((char*)table)+(green&0xFFFE));
  blue = *(short*)(((char*)table)+(blue&0xFFFE));
}
void GFilter_GammaCorrection::setGammaCorrection(float gamma) {
  // create table
  if (table)
    delete[] table;
  table = new short[32768];

  // fill table
  float offset = (1-gamma)*log(range);
  for (int i=-32768; i<32768; ++i) {
    short e;
    if ((i<=min)||(i>=min+range))
      e=i;
    else
      e=min+(int)exp((log(i-min)-offset)/gamma);
    *(short*)(((char*)table)+(i&0xFFFE))=e;
  }
}
void GFilter_GammaCorrection::getPixel(short& red, short& green, short& blue,
				      float x1, float y1, float x2, float y2) {
  src.getPixel(red,green,blue,x1,y1,x2,y2);
  GammaCorrect(red,green,blue,table);
}
void GFilter_GammaCorrection::getScanLineHorz(short* red, short* green, short* blue,
					      float x1, float y1, float x2, float y2, int npixels) {
  src.getScanLineHorz(red,green,blue,x1,y1,x2,y2,npixels);
  for (int i=0; i<npixels; ++i)
    GammaCorrect(red[i],green[i],blue[i],table);
}
void GFilter_GammaCorrection::getScanLineVert(short* red, short* green, short* blue,
					      float x1, float y1, float x2, float y2, int npixels) {
  src.getScanLineVert(red,green,blue,x1,y1,x2,y2,npixels);
  for (int i=0; i<npixels; ++i)
    GammaCorrect(red[i],green[i],blue[i],table);
}


/* ================ GFilter_ContrastAdjustment methods  ================ */
#define CONTRAST_ADJUST(pixel) pixel=boundcheck((pixel-min)*32768/diff-16384)
void GFilter_ContrastAdjustment::getPixel(short& red, short& green, short& blue,
					 float x1, float y1, 
					 float x2, float y2) {
  src.getPixel(red,green,blue,x1,y1,x2,y2);
  CONTRAST_ADJUST(red);
  CONTRAST_ADJUST(green);
  CONTRAST_ADJUST(blue);
}
void GFilter_ContrastAdjustment::getScanLineHorz(short* red, short* green, short* blue,
					      float x1, float y1, float x2, float y2, int npixels) {
  src.getScanLineHorz(red,green,blue,x1,y1,x2,y2,npixels);
  for (int i=0; i<npixels; ++i) {
    CONTRAST_ADJUST(red[i]);
    CONTRAST_ADJUST(green[i]);
    CONTRAST_ADJUST(blue[i]);
  }
}
void GFilter_ContrastAdjustment::getScanLineVert(short* red, short* green, short* blue,
					      float x1, float y1, float x2, float y2, int npixels) {
  src.getScanLineVert(red,green,blue,x1,y1,x2,y2,npixels);
  for (int i=0; i<npixels; ++i) {
    CONTRAST_ADJUST(red[i]);
    CONTRAST_ADJUST(green[i]);
    CONTRAST_ADJUST(blue[i]);
  }
}

/* ================ GFilter_Scale methods  ================ */
void GFilter_Scale::calculateConstants() {
  float ow = (float)((h*src.getWidth()*src.getAspectRatio())/(src.getHeight()*aspectratio));
  float oh = (float)((w*src.getHeight()*aspectratio)/(src.getWidth()*src.getAspectRatio()));

  if (w<ow)       // width cropped
    oh=h;
  else if (h<oh)  // height cropped
    ow=w;

  xfactor=src.getWidth()/ow;
  yfactor=src.getHeight()/oh;
  xoffset=(ow-w)/2;
  yoffset=(oh-h)/2;
}

void GFilter_Scale::getPixel(short& red, short& green, short& blue,
			    float x1, float y1, float x2, float y2) {
  src.getPixel(red,green,blue,xoffset+xfactor*x1,yoffset+yfactor*y1,
	       xoffset+xfactor*x2,yoffset+yfactor*y2);
}
void GFilter_Scale::getScanLineHorz(short* red, short* green, short* blue,
				    float x1, float y1, float x2, float y2, int npixels) {
  src.getScanLineHorz(red,green,blue,xoffset+xfactor*x1,yoffset+yfactor*y1,
		      xoffset+xfactor*x2,yoffset+yfactor*y2,npixels);
}
void GFilter_Scale::getScanLineVert(short* red, short* green, short* blue,
				    float x1, float y1, float x2, float y2, int npixels) {
  src.getScanLineVert(red,green,blue,xoffset+xfactor*x1,yoffset+yfactor*y1,
		      xoffset+xfactor*x2,yoffset+yfactor*y2,npixels);
}
  
/* ================ GFilter_Rotate methods  ================ */
void GFilter_Rotate::getPixel(short& red, short& green, short& blue,
			     float x1, float y1, float x2, float y2) {
  switch (degree) {
  case 0:
    src.getPixel(red,green,blue,x1,y1,x2,y2);
    return;
  case 90:
    src.getPixel(red,green,blue,y1,src.getHeight()-x2,
		 y2,src.getHeight()-x1);
    return;
  case 180:
    src.getPixel(red,green,blue,src.getWidth()-x2,src.getHeight()-y2,
		 src.getWidth()-x1,src.getHeight()-y1);
    return;
  case 270:
    src.getPixel(red,green,blue,src.getWidth()-y2,x1,
		 src.getWidth()-y1,x2);
    return;
  }
  // rotate angle must be a multiple of 90 degrees
  red=green=blue=0;
}

void GFilter_Rotate::getScanLineHorz(short* red, short* green, short* blue,
				     float x1, float y1, 
				     float x2, float y2, int npixels) {
  switch (degree) {
  case 0:
    src.getScanLineHorz(red,green,blue,x1,y1,x2,y2,npixels);
    return;
  case 180:
    src.getScanLineHorz(red,green,blue,src.getWidth()-x1,src.getHeight()-y2,
			src.getWidth()-x2,src.getHeight()-y1,npixels);
    return;
  case 90:
    src.getScanLineVert(red,green,blue,y1,src.getHeight()-x1,
			y2,src.getHeight()-x2,npixels);
    return;
  case 270:
    src.getScanLineVert(red,green,blue,src.getWidth()-y2,x1,
			src.getWidth()-y1,x2,npixels);
    return;
  }
  GImage::getScanLineHorz(red,green,blue,x1,y1,x2,y2,npixels);
}

void GFilter_Rotate::getScanLineVert(short* red, short* green, short* blue,
				     float x1, float y1, 
				     float x2, float y2, int npixels) {
  switch (degree) {
  case 0:
    src.getScanLineVert(red,green,blue,x1,y1,x2,y2,npixels);
    return;
  case 180:
    src.getScanLineVert(red,green,blue,src.getWidth()-x2,src.getHeight()-y1,
			src.getWidth()-x1,src.getHeight()-y2,npixels);
    return;
  case 90:
    src.getScanLineHorz(red,green,blue,y1,src.getHeight()-x2,
			y2,src.getHeight()-x1,npixels);
    return;
  case 270:
    src.getScanLineHorz(red,green,blue,src.getWidth()-y1,x1,
			src.getWidth()-y2,x2,npixels);
    return;
  }
  GImage::getScanLineVert(red,green,blue,x1,y1,x2,y2,npixels);
}

/* ================ GFilter_WhiteBalance methods  ================ */
#define WHITE_BALANCE(pixel,diff) pixel=(pixel+16384)*diff/32768-16384
void GFilter_WhiteBalance::getPixel(short& red, short& green, short& blue,
				    float x1, float y1, 
				    float x2, float y2) {
  src.getPixel(red,green,blue,x1,y1,x2,y2);
  WHITE_BALANCE(red,rdiff);
  WHITE_BALANCE(green,gdiff);
  WHITE_BALANCE(blue,bdiff);
}
void GFilter_WhiteBalance::getScanLineHorz(short* red, short* green, short* blue,
					   float x1, float y1, float x2, float y2, int npixels) {
  src.getScanLineHorz(red,green,blue,x1,y1,x2,y2,npixels);
  for (int i=0; i<npixels; ++i) {
    WHITE_BALANCE(red[i],rdiff);
    WHITE_BALANCE(green[i],gdiff);
    WHITE_BALANCE(blue[i],bdiff);
  }
}
void GFilter_WhiteBalance::getScanLineVert(short* red, short* green, short* blue,
					   float x1, float y1, float x2, float y2, int npixels) {
  src.getScanLineVert(red,green,blue,x1,y1,x2,y2,npixels);
  for (int i=0; i<npixels; ++i) {
    WHITE_BALANCE(red[i],rdiff);
    WHITE_BALANCE(green[i],gdiff);
    WHITE_BALANCE(blue[i],bdiff);
  }
}


/* ================ GFilter_BarrelCorrection methods  ================ */



