/*
 * Implementation of class Image
 *
 * $Date$
 * $Revision$
 *
 */

#include "WImage/Image.hh"
#include "WImage/debug.h"
#include "WImage/tools.h"
#ifdef _WIN32_WCE
#undef DELETE
#endif
#include <cassert>
#include <cmath>
#include <cstdlib>
#include <stdexcept>
#ifndef _WIN32_WCE
#include <iostream>
#endif
#include "WImage/miscdefs.h"

#if !(defined __WATCOMC__ && __WATCOMC__ < 1230)
using namespace std;
#endif

Image::Image (void)
{
  m_xsize = 0;
  m_ysize = 0;
  m_xysize = 0;
  m_acol = NULL;
  m_arow = NULL;
  m_afull = NULL;
}

bool
Image::epsilonsAt (int abs, coeff value, coeff epsilon) const
{
  return tools_epsilons (at (abs), value, epsilon);
}

bool
Image::epsilonsAt (int y, int x, coeff value, coeff epsilon) const
{
  return tools_epsilons (at (y, x), value, epsilon);
}

void
Image::valadjust (void)
{
  coeff cmin = smin ();
  coeff cmax = smax ();
  coeff norm = 1.0;

  DPRINTF (("old max: %.2f, min: %.2f\n", cmax, cmin));

  if (cmax - cmin > 255.0)
    {
      norm = 255.0 / (cmax - cmin);
    }
  if (cmin < 0.0)
    {
      cmin *= -1;
      cmax = 0.0;
    }
  else if (cmax > 255.0)
    {
      cmax -= 255.0 / norm;
      cmin = 0.0;
    }
  else
    {
      cmax = cmin = 0;
    }

  DPRINTF (("new max: %.2f, min: %.2f\n", cmax, cmin));
  for (int i = 0; i < size (); i++)
    {
      coeff d = norm * (cmin + at (i) - cmax);
      to (i, d < 0? 0: d);
    }
  assert (smin () >= 0.0);
  assert (smax () < 256);
  DPRINTF (("now max: %.2f, min: %.2f\n\n", smax (), smin ()));
}

void
Image::dimcheck (const Image &img) const
{
  if (this->rows () != img.rows () || this->cols () != img.cols ())
    {
      throw invalid_argument ("Different dimensions.");
    }
}

Image *
Image::crop (int yoffs, int xoffs, int ysize, int xsize) const
{
  Image *ret = mkImage
    ((yoffs + ysize) > this->m_ysize? this->m_ysize - yoffs: ysize,
     (xoffs + xsize) > this->m_xsize? this->m_xsize - xoffs: xsize);

  for (int row = 0; row < ysize; row++)
    {
      for (int col = 0; col < xsize; col++)
        {
          int y = yoffs+row, x = xoffs+col;
          if (y < this->m_ysize && x < this->m_xsize)
            {
              ret->to (row, col, this->at (y, x));
            }
        }
    }
  return ret;
}

void
Image::paste (int yoffs, int xoffs, Image &img)
{
  for (int row = 0; row < img.rows (); row++)
    {
      for (int col = 0; col < img.cols (); col++)
        {
          if (row < this->m_ysize && col < this->m_xsize)
            {
              this->to (yoffs+row, xoffs+col, img.at (row, col));
            }
        }
    }
}

void
Image::copy (Image &img)
{
  resize (img.rows (), img.cols ());
  for (int row = 0; row < img.rows (); row++)
    {
      for (int col = 0; col < img.cols (); col++)
        {
          this->to (row, col, img.at (row, col));
        }
    }
}

void
Image::fill (coeff value, int fromY, int fromX, int toY, int toX)
{
  int lower = (toY < 0)? m_ysize: toY;
  int right = (toX < 0)? m_xsize: toX;

  for (int y = (fromY < 0? 0: fromY); y < lower && y < m_ysize; y++)
    {
      for (int x = (fromX < 0? 0: fromX); x < right && x < m_xsize; x++)
        {
	  this->to (y, x, value);
        }
    }
}

coeff
Image::sqvariance (int fromY, int fromX, int toY, int toX) const
{
  int nVals = 0;
  int lower = (toY < 0)? m_ysize: toY;
  int right = (toX < 0)? m_xsize: toX;
  coeff avg = this->saverage (fromY, fromX, lower, right);
  coeff ret = 0.0;

  for (int y = (fromY < 0? 0: fromY); y < lower && y < m_ysize; y++)
    {
      for (int x = (fromX < 0? 0: fromX); x < right && x < m_xsize; x++)
        {
          ++nVals;
          ret += this->at (y, x) * this->at (y, x);
        }
    }

  return (ret / nVals) - avg * avg;
}

coeff
Image::variance (int fromY, int fromX, int toY, int toX, bool abs) const
{
  int lower = (toY < 0)? m_ysize: toY;
  int right = (toX < 0)? m_xsize: toX;
  coeff avg = abs? this->aaverage (fromY, fromX, lower, right)
    : this->saverage (fromY, fromX, lower, right);
  return variance (fromY, fromX, toY, toX, avg, abs);
}

coeff
Image::variance (int fromY, int fromX, int toY, int toX,
		 coeff avg, bool abs) const
{
  int nVals = 0;
  int lower = (toY < 0)? m_ysize: toY;
  int right = (toX < 0)? m_xsize: toX;
  coeff ret = 0.0;
  coeff tmp;

  for (int y = (fromY < 0? 0: fromY); y < lower && y < m_ysize; y++)
    {
      for (int x = (fromX < 0? 0: fromX); x < right && x < m_xsize; x++)
        {
          ++nVals;
          tmp = abs? fabs (this->at (y, x)): this->at (y, x);
          tmp -= avg;
          ret += SQUARE (tmp);
        }
    }
  return ret / nVals;
}

coeff
Image::sdeviation (int fromY, int fromX, int toY, int toX, bool abs) const
{
  return sqrt (variance (fromY, fromX, toY, toX, abs));
}

double
Image::iproduct (Image &img) const
{
  coeff rc = 0.0;
  if (m_ysize != img.rows () || m_xsize != img.cols ())
    {
      throw invalid_argument ("iproduct: the image "
                              "dimensions are not identical.");
    }
  for (int i = 0; i < m_xysize; i++)
    {
      rc += this->at (i) * img.at (i);
    }

  return (double)rc;
}

void
Image::normalize (coeff factor)
{
  for (int i = 0; i < this->size (); i++)
    {
      this->to (i, this->at (i) / factor);
    }
}

void
Image::unnormalize (coeff factor)
{
  for (int i = 0; i < this->size (); i++)
    {
      this->to (i, this->at (i) * factor);
    }
}

void
Image::gammaCorrection (double factor, int yoffs, int xoffs,
                        int ysize, int xsize, int norm)
{
  if (yoffs < 0 || yoffs >= rows ())
    {
      yoffs = 0;
    }
  if (xoffs < 0 || xoffs >= cols ())
    {
      xoffs = 0;
    }
  int toY = (ysize <= 0? rows (): (yoffs + ysize));
  int toX = (xsize <= 0? cols (): (xoffs + xsize));

  if (toY >= rows ())
    {
      toY = rows ();
    }
  if (toX >= cols ())
    {
      toX = cols ();
    }

  coeff min = smin (yoffs, xoffs, toY, toX);
  coeff max = smax (yoffs, xoffs, toY, toX);

  int iMin = tools_coeff2int (min - 0.5);
  int iMax = tools_coeff2int (max);

  iMin = MIN (0, iMin);
  iMax = MAX (norm, iMax);

  norm = iMax - iMin;
  coeff normCoeff = (coeff)norm;

  coeff * replaceTable = NEW (coeff[norm + 1]);

  for (int i = 0; i <= norm; i++)
    {
      replaceTable[i] = tools_coeff2int (norm * pow ((coeff)(i) / normCoeff,
						     1.0 / factor)) + iMin;
    }

  for (int y = yoffs; y < toY; y++)
    {
      for ( int x = xoffs; x < toX ; x++ )
        {
          to (y, x, replaceTable[(int)(at (y, x) + iMin)]);
        }
    }

  DELETE (replaceTable);
}

double
Image::gammaCorrectionAuto ( int rows, int columns, int yoffs,
			     int xoffs, int ysize, int xsize, int norm)
{
  if ((yoffs < 0) || (yoffs >= this->rows ()))
    {
      yoffs = 0;
    }
  if (xoffs < 0 || xoffs >= this->cols ())
    {
      xoffs = 0;
    }
  int toY = (ysize <= 0? this->rows (): (yoffs + ysize));
  int toX = (xsize <= 0? this->cols (): (xoffs + xsize));

  if (toY >= this->rows ())
    {
      toY = this->rows ();
    }
  if (toX >= this->cols ())
    {
      toX = this->cols ();
    }

  int subY = toY - yoffs;
  int subX = toX - xoffs;

  columns = MIN (subX,columns);
  rows = MIN (subY,rows);

  while ((subX % columns) != 0)
    {
      columns--;
    }
  while ((subY % rows) != 0)
    {
      rows--;
    }

  coeff * average = NEW (coeff[rows*columns]);

  int width = subX / columns;
  int height = subY / rows;

  // Local average pixel values
  double min = 0.0, max = 0.0;
  for (int ix = 0, x = 0; ix < columns ; ix++, x += height)
    {
      for (int iy = 0, y = 0; iy < rows ; iy++, y += width)
	{
	  // Average pixel value of the local area
	  average[iy+(ix*rows)] = saverage (y , x, y + width, x + height);
	  if (!ix && !iy)
	    {
	      // i.e. first loop
	      min = average[iy + (ix * rows)];
	      max = average[iy + (ix * rows)];
	    }
	  else
	    {
	      if (min > average[iy + (ix * rows)])
		{
		  min = average[iy + (ix * rows)];
		}
	      else if (max < average[iy + (ix * rows)])
		{
		  max = average[iy + (ix * rows)];
		}
	    }
	}
    }

  DELETE (average);

  double gammaContrast = 1 - ((float)(max - min) / (max + min));

  gammaCorrection (1.0 / gammaContrast , yoffs , xoffs , ysize , xsize, norm);

  return gammaContrast;
}

void
Image::histEqualization (int yoffs, int xoffs, int ysize, int xsize,
			 int startH, int endH)
{
  if (yoffs < 0 || yoffs >= rows ())
    {
      yoffs = 0;
    }
  if (xoffs < 0 || xoffs >= cols ())
    {
      xoffs = 0;
    }
  int toY = (ysize <= 0? rows (): (yoffs + ysize));
  int toX = (xsize <= 0? cols (): (xoffs + xsize));

  if (toY >= rows ())
    {
      toY = rows ();
    }
  if (toX >= cols ())
    {
      toX = cols ();
    }

  int cRange = (endH + 1) - startH;
  bool lifted = false;

  if ((lifted = (startH < 0)))
    {
      for (int y = yoffs; y < toY; y++)
        {
          for (int x = xoffs; x < toX; x++)
            {
              // int temp = tools_coeff2int (at (y, x));
              to (y, x, at (y, x) - startH);
              // int newint = at (y,x);
              // ASSERT ( (at (y,x) >= 0) && (at (y,x) <= cRange) );
            }
	}
    }

  int * freqDistr = NEW (int [cRange]);    // Frequncy distrubution of pArray
  int * cumFreqDistr = NEW (int [cRange]); // Cumalitive Frequency Distrubution
  int * idealFreq = NEW (int [cRange]);    // Ideal Frequency
  int * cumIdealFreq = NEW (int [cRange]); // Cumalitive Ideal frequancy
  int * mappingTable = NEW (int [cRange]); // Mapping Table

  // Clear all arrays to 0;
  for(int i = 0; i < cRange; i++)
    {
      freqDistr[i] = 0;
      cumFreqDistr[i] = 0;
      idealFreq[i] = 0;
      cumIdealFreq[i] = 0;
      mappingTable[i] = 0;
    }

  // STEP ONE
  // Construct the histogram.

  for (int y = yoffs; y < toY; y++)
    {
      for (int x = xoffs; x < toX; x++)
        {
          // int temp = tools_coeff2int (at (y, x));
          // ASSERT ( (at (y,x) >= 0) && (at (y,x) <= cRange) );
          freqDistr[tools_coeff2int (at (y, x))]++;
        }
    }

  // STEP TWO
  // Calculate the cumulative frequency distribution

  int cumulative = 0;
  for(int i = 0; i < cRange; i++)
    {
      cumulative += freqDistr[i];
      cumFreqDistr[i] = cumulative;
    }

  // STEP THREE
  // Calculate the ideal frequency distribution

  int each = cumulative / cRange;
  int remain = cumulative % cRange;
  int start = 0;
  if (remain % 2 == 1)
    {
      start--;
    }
  start = start + (cRange / 2 - remain / 2);

  for(int i = 0; i < cRange; i++)
    {
      idealFreq[i] = each;
      if ((i >= start) && (remain > 0))
	{
	  idealFreq[i]++;
	  remain--;
	}
    }

  // STEP FOUR
  // Calculate the cumulative frequency for the ideal frequency

  int idealCumalitive = 0;
  for(int i = 0; i < cRange; i++)
    {
      idealCumalitive += idealFreq[i];
      cumIdealFreq[i] = idealCumalitive;
    }

  // STEP FIVE
  // Do the Mapping

  int checkup = 0;
  for(int i = 0; i < cRange; i++)
    {
      for (int k = 0; k < cRange; k++)
	{
	  if (cumFreqDistr[i] == cumIdealFreq[k])
	    {
	      mappingTable[i] = k;
	      checkup++;
	      break;
	    }

	  int postEntry;
	  int preEntry;

	  if (k == 0)
	    {
	      preEntry = cumIdealFreq[k];
	    }
	  else
	    {
	      preEntry = cumIdealFreq[k-1];
	    }

	  if (k == (cRange - 1))
	    {
	      postEntry = cumIdealFreq[k];
	    }
	  else
	    {
	      postEntry = cumIdealFreq[k+1];
	    }

	  if (cumFreqDistr[i] > postEntry)
	    {
	      continue;
	    }

	  int preDiff = cumFreqDistr[i] - preEntry;
	  int postDiff = cumFreqDistr[i] - postEntry;
	  preDiff = (preDiff < 0? -preDiff: preDiff);
	  postDiff = (postDiff < 0? -postDiff: postDiff);

	  if (preDiff < postDiff)
	    {
	      if(k == 0)
		{
		  mappingTable[i] = k;
		}
	      else
		{
		  mappingTable[i] = k-1;
		}
	      checkup++;
	      break;
	    }
	  else
	    {
	      mappingTable[i] = k + 1;
	      checkup++;
	      break;
	    }
	}
    }

  // STEP SIX
  // Change the original array

  for (int y = yoffs; y < toY; y++)
    {
      for (int x = xoffs; x < toX; x++)
        {
          coeff mapped = (coeff)(mappingTable[tools_coeff2int (at (y, x))]);
          to (y, x, mapped);
        }
    }

  if (lifted)
    {
      for (int y = yoffs; y < toY; y++)
        {
          for (int x = xoffs; x < toX; x++)
            {
              to (y, x, at (y, x) + startH);
            }
	}
    }

  //Clean Up
  DELETE (freqDistr);
  DELETE (cumFreqDistr);
  DELETE (idealFreq);
  DELETE (cumIdealFreq);
  DELETE (mappingTable);

}

void
Image::beautify (int yoffs, int xoffs, int ysize, int xsize)
{
  if (ysize > 0 && xsize > 0)
    {
      Image *templl = crop (yoffs, xoffs, ysize, xsize);
      coeff fill = (smax () - smin ()) / 2;

      for (int row = 0; row < ysize; row++)
        {
          for (int col = 0; col < xsize; col++)
            {
              assert (yoffs + row < m_ysize);
              assert (xoffs + col < m_xsize);
              to (yoffs + row, xoffs + col, fill);
            }
        }

      templl->valadjust ();
      valadjust ();
      paste (yoffs, xoffs, *templl);
      DELETE (templl);
    }
  else
    {
      valadjust ();
    }
}

void
Image::truncate (coeff min, coeff max)
{
  for (int i = 0; i < this->size (); i++)
    {
      if (this->at (i) < min)
        {
          this->to (i, min);
        }
      else if (this->at (i) > max)
        {
          this->to (i, max);
        }
    }
  assert (smin () >= 0);
}

void
Image::pixelize (void)
{
  truncate (0.0, 255.0);

  for (int i = 0; i < this->size (); i++)
    {
      this->to (i, (coeff)tools_coeff2pixel (this->at (i)));
    }
  assert (smin () >= 0);
}

void
Image::shift (int yoffs, int xoffs)
{
  Image *temp1=0;
  Image *temp2=0;

  if (xoffs < 0)
    {
      temp1=this->crop (0, -xoffs,this->m_ysize,this->m_xsize+xoffs);
      temp2=this->crop (0, 0,this->m_ysize,-xoffs);
      paste (0, 0, *temp1);
      paste (0, this->m_xsize+xoffs, *temp2);
    }
  else if (xoffs > 0)
    {
      temp1=this->crop (0, this->m_xsize-xoffs,this->m_ysize,xoffs);
      temp2=this->crop (0, 0,this->m_ysize,this->m_xsize-xoffs);
      paste (0, 0, *temp1);
      paste (0, xoffs, *temp2);
    }
  DELETE (temp1);
  DELETE (temp2);

  if (yoffs < 0)
    {
      temp1=this->crop (-yoffs,0,this->m_ysize+yoffs,this->m_xsize);
      temp2=this->crop (0, 0,-yoffs,this->m_xsize);
      paste (0, 0, *temp1);
      paste (this->m_ysize+yoffs,0, *temp2);
    }
  else if (yoffs > 0)
    {
      temp1=this->crop (this->m_ysize-yoffs,0,yoffs,this->m_xsize);
      temp2=this->crop (0, 0,this->m_ysize-yoffs,this->m_xsize);
      paste (0, 0, *temp1);
      paste (yoffs,0, *temp2);
    }
  DELETE (temp1);
  DELETE (temp2);
}


void
Image::importPixels (int rows, int cols, pixel *buf)
{
  resize (rows, cols);

  anasteps (0);
  synsteps (0);

  for (int i = 0; i < m_xysize; i++)
    {
      this->to (i, (coeff)buf[i]);
    }
}

pixel *
Image::exportPixels (void) const
{
  pixel *ret = NEW (pixel [m_xysize]);
  for (int i = 0; i < m_xysize; i++)
    {
      ret[i] = tools_coeff2pixel (this->at (i));
    }
  return ret;
}

void
Image::importCoeffs (int rows, int cols, coeff *buf)
{
  resize (rows, cols);

  anasteps (0);
  synsteps (0);

  for (int i = 0; i < m_xysize; i++)
    {
      this->to (i, buf[i]);
    }
}

coeff *
Image::exportCoeffs (void)
{
  coeff *ret = NEW (coeff [m_xysize]);
  for (int i = 0; i < m_xysize; i++)
    {
      ret[i] = this->at (i);
    }
  return ret;
}

static coeff bilinear (const Image &img, int y, int x, coeff factor);
static coeff nearestneighbour (const Image &img, int y, int x, coeff factor);
static coeff average (const Image &img, int y, int x, coeff factor);
static coeff vlinear (const Image &img, int y, int x, coeff factor);
static coeff hlinear (const Image &img, int y, int x, coeff factor);

static coeff bilinear (const Image &img, int y, int x,
                       coeff yFactor, coeff xFactor);
static coeff nearestneighbour (const Image &img, int y, int x,
                               coeff yFactor, coeff xFactor);
static coeff average (const Image &img, int y, int x,
                      coeff yFactor, coeff xFactor);
static coeff vlinear (const Image &img, int y, int x,
                      coeff yFactor, coeff xFactor);
static coeff hlinear (const Image &img, int y, int x,
                      coeff yFactor, coeff xFactor);

typedef coeff (*CalcFunc) (const Image &, int, int, coeff);
static const CalcFunc g_calc [] = {
  bilinear, average, nearestneighbour, hlinear, vlinear
};

typedef coeff (*CalcFunc2) (const Image &, int, int, coeff, coeff);
static const CalcFunc2 g_calc2 [] = {
  bilinear, average, nearestneighbour, hlinear, vlinear
};

static void checkCalcFuncArg(int function)
{
  if (function < 0 || (unsigned)function >= sizeof g_calc / sizeof (CalcFunc*))
    {
      throw invalid_argument ("scale: Invalid function index.");
    }
}

static void checkCalcFuncArg2(int function)
{
  if (function < 0 || (unsigned)function >= sizeof g_calc2 / sizeof (CalcFunc2*))
    {
      throw invalid_argument ("scale: Invalid function index.");
    }
}

class SimpleFill : public ResizeFillFunc {
public:
  SimpleFill (int c) : m_coeff ((coeff)c) {}
  virtual ~SimpleFill (void) {}
  virtual coeff operator() (const Image &scaled, double factor, int y, int x,
			    int newRows, int newCols,
			    int yOffset, int xOffset) {
    return m_coeff;
  }
private:
  coeff m_coeff;
};


class MirrorFill : public ResizeFillFunc {
public:
  MirrorFill (const Image &image, int resizeFunc)
    : m_image (image), m_resizeFunc (resizeFunc) {}
  virtual ~MirrorFill (void) {}
  virtual coeff operator() (const Image &scaled, double factor, int y, int x,
			    int newRows, int newCols,
			    int yOffset, int xOffset) {
    return doit (scaled, factor, y, x, newRows, newCols, yOffset, xOffset);
  }
private:
  virtual coeff doit (const Image &scaled, double factor, int y, int x,
		      int newRows, int newCols, int yOffset, int xOffset) {
    int row = y;
    int col = x;

    if (y < yOffset)
      {
	row = 2 * yOffset - y;
      }
    else if (y >= yOffset + newRows)
      {
	row = 2 * (yOffset + newRows - 1) - y;
      }
    if (x < xOffset)
      {
	col = 2 * xOffset - x;
      }
    else if (x >= xOffset + newCols)
      {
	col = 2 * (xOffset + newCols - 1) - x;
      }
    if ((row - yOffset) < 0 || (row - yOffset) / factor >= m_image.rows ()
	|| (col - xOffset) < 0 || (col - xOffset) / factor >= m_image.cols ())
      {
	return doit (scaled, factor, row, col,
		     newRows, newCols, yOffset, xOffset);
      }
    return g_calc[m_resizeFunc] (m_image, row - yOffset, col - xOffset, factor);
  }
  const Image &m_image;
  int m_resizeFunc;
};

class OuterBorderFill : public ResizeFillFunc {
public:
  OuterBorderFill (const Image &image)
    : m_image (image) {}
  virtual ~OuterBorderFill (void) {}
  virtual coeff operator() (const Image &scaled, double factor, int y, int x,
			    int newRows, int newCols,
			    int yOffset, int xOffset) {
    return doit (scaled, factor, y, x, newRows, newCols, yOffset, xOffset);
  }
private:
  virtual coeff doit (const Image &scaled, double factor, int y, int x,
		      int newRows, int newCols, int yOffset, int xOffset) {
    int row;
    int col;

    if (y < yOffset)
      {
	row = 0;
      }
    else if (y >= yOffset + newRows)
      {
	row = m_image.rows () - 1;
      }
    else
      {
	row = (int)((double)y / factor + 0.5);
	if (row >= m_image.rows ())
	  {
	    row = m_image.rows () - 1;
	  }
      }
    if (x < xOffset)
      {
	col = 0;
      }
    else if (x >= xOffset + newCols)
      {
	col = m_image.cols () - 1;
      }
    else
      {
	col = (int)((double)x / factor + 0.5);
	if (col >= m_image.cols ())
	  {
	    col = m_image.cols () - 1;
	  }
      }
    assert (row < m_image.rows ());
    assert (col < m_image.cols ());
    return m_image.at (row, col);
  }
  const Image &m_image;
};


Image *
Image::fitInto (int rows, int cols, int fill, int function) const
{
  SimpleFill fillFunc (fill);
  return fitInto (rows, cols, fill < 0? NULL: &fillFunc, function);
}

Image *
Image::fitInto (int rows, int cols, bool mirrorExtend, int function) const
{
  MirrorFill fillFunc (*this, function);
  return fitInto (rows, cols, mirrorExtend? &fillFunc : NULL, function);
}

Image *
Image::fitInto (int rows, int cols, ExtendFunc extend, int function) const
{
  MirrorFill mirrorFunc (*this, function);
  OuterBorderFill outerBorderFunc (*this);
  switch (extend)
    {
    case ef_mirror:
      return fitInto (rows, cols, &mirrorFunc, function);
    case ef_outerBorder:
      return fitInto (rows, cols, &outerBorderFunc, function);
    default:
      assert (0);
    }
  return NULL;
}

Image *
Image::fitInto (int rows, int cols,
		ResizeFillFunc *fillFunc, int function) const
{
  checkCalcFuncArg (function);

  double theFactor = (double)rows / (double)m_ysize;
  if (theFactor * m_xsize > cols)
    {
      theFactor = (double)cols / (double)m_xsize;
    }

  assert ((int)(theFactor * m_ysize + 0.5) <= rows);
  assert ((int)(theFactor * m_xsize + 0.5) <= cols);

  int scaleRows = (int)(theFactor * m_ysize + 0.5);
  int scaleCols = (int)(theFactor * m_xsize + 0.5);
  assert (scaleRows <= rows);
  assert (scaleCols <= cols);

  int theRows = fillFunc == NULL? scaleRows: rows;
  int theCols = fillFunc == NULL? scaleCols: cols;

  int rowOffset = (theRows - scaleRows);
  rowOffset = rowOffset / 2 + rowOffset % 2;
  int colOffset = (theCols - scaleCols);
  colOffset = colOffset / 2 + colOffset % 2;

  Image *image = mkImage (theRows, theCols);

  for (int y = 0; y < image->rows (); y++)
    {
      for (int x = 0; x < image->cols (); x++)
        {
	  if (y < rowOffset || y >= scaleRows + rowOffset
	      || x < colOffset || x >= scaleCols + colOffset)
	    {
	      image->to (y, x, (*fillFunc) (*image, theFactor, y, x,
					    scaleRows, scaleCols,
					    rowOffset, colOffset));
	    }
	  else
	    {
	      image->to (y, x, g_calc[function] (*this,
						 y - rowOffset,
						 x - colOffset,
						 theFactor));
	    }
        }
    }
  return image;
}

Image *
Image::scale (double factor, int function) const
{
  checkCalcFuncArg (function);

  if (factor < 0)
    {
      throw invalid_argument ("scale: Invalid factor.");
    }

  Image *image = mkImage ((int)(factor * this->rows () + 0.5),
                          (int)(factor * this->cols () + 0.5));

  for (int y = 0; y < image->rows (); y++)
    {
      for (int x = 0; x < image->cols (); x++)
        {
          image->to (y, x, g_calc[function] (*this, y, x, factor));
        }
    }
  return image;
}

Image *
Image::scale (double yFactor, double xFactor, int function) const
{
  checkCalcFuncArg2 (function);

  if (yFactor < 0 || xFactor < 0)
    {
      throw invalid_argument ("scale: Invalid factor.");
    }

  Image *image = mkImage ((int)(yFactor * this->rows () + 0.5),
                          (int)(xFactor * this->cols () + 0.5));

  for (int y = 0; y < image->rows (); y++)
    {
      for (int x = 0; x < image->cols (); x++)
        {
          image->to (y, x, g_calc2[function] (*this, y, x, yFactor, xFactor));
        }
    }
  return image;
}

static coeff
bilinear (const Image &img, int y, int x, coeff factor)
{
  coeff py = y / factor;
  coeff px = x / factor;
  int py1 = (int)py + 1;
  int px1 = (int)px + 1;
  py1 = (py1 < img.rows ()? py1: py1 - 1);
  px1 = (px1 < img.cols ()? px1: px1 - 1);

  assert (py >= 0);
  assert (px >= 0);
  assert (py < img.rows ());
  assert (px < img.cols ());

  assert (py1 >= 0);
  assert (px1 >= 0);
  assert (py1 < img.rows ());
  assert (px1 < img.cols ());
  assert (py1 >= (int)py);
  assert (px1 >= (int)px);

  // bilineard values
  coeff iy1, iy2;

  // two values
  coeff v1, v2;

  // ..
  coeff alpha;

  alpha = (py - (int)py);
  assert (alpha <= 1);

  v1 = img.at ((int)py, (int)px);
  v2 = img.at (py1, (int)px);

  iy1 = (v2 - v1) * alpha + v1;

  v1 = img.at ((int)py, px1);
  v2 = img.at (py1, px1);

  iy2 = (v2 - v1) * alpha + v1;

  alpha = (px - (int)px);
  assert (alpha <= 1);

  return (iy2 - iy1) * alpha + iy1;
}

static coeff
bilinear (const Image &img, int y, int x, coeff yFactor, coeff xFactor)
{
  coeff py = y / yFactor;
  coeff px = x / xFactor;
  int py1 = (int)py + 1;
  int px1 = (int)px + 1;
  py1 = (py1 < img.rows ()? py1: py1 - 1);
  px1 = (px1 < img.cols ()? px1: px1 - 1);

  assert (py >= 0);
  assert (px >= 0);
  assert (py < img.rows ());
  assert (px < img.cols ());

  assert (py1 >= 0);
  assert (px1 >= 0);
  assert (py1 < img.rows ());
  assert (px1 < img.cols ());
  assert (py1 >= (int)py);
  assert (px1 >= (int)px);

  // bilineard values
  coeff iy1, iy2;

  // two values
  coeff v1, v2;

  // ..
  coeff alpha;

  alpha = (py - (int)py);
  assert (alpha <= 1);

  v1 = img.at ((int)py, (int)px);
  v2 = img.at (py1, (int)px);

  iy1 = (v2 - v1) * alpha + v1;

  v1 = img.at ((int)py, px1);
  v2 = img.at (py1, px1);

  iy2 = (v2 - v1) * alpha + v1;

  alpha = (px - (int)px);
  assert (alpha <= 1);

  return (iy2 - iy1) * alpha + iy1;
}

static coeff
average (const Image &img, int y, int x, coeff factor)
{
  coeff py = y / factor;
  coeff px = x / factor;
  int py1 = (int)py + 1;
  int px1 = (int)px + 1;
  py1 = (py1 < img.rows ()? py1: py1 - 1);
  px1 = (px1 < img.cols ()? px1: px1 - 1);
  coeff rc = 0;

  for (int i = 0; i < 4; i++)
    {
      int cy = (int)py + ((i & 0x02) != 0);
      int cx = (int)px + ((i & 0x01) != 0);
      cy = (cy < img.rows ()? cy: cy - 1);
      cx = (cx < img.cols ()? cx: cx - 1);
      assert (cy <= py + 1);
      assert (cx <= px + 1);
      assert (cy >= 0);
      assert (cx >= 0);
      assert (cy < img.rows ());
      assert (cx < img.cols ());
      coeff c = img.at (cy, cx);
      rc += c;
    }

  return rc / 4;
}

static coeff
average (const Image &img, int y, int x, coeff yFactor, coeff xFactor)
{
  coeff py = y / yFactor;
  coeff px = x / xFactor;
  int py1 = (int)py + 1;
  int px1 = (int)px + 1;
  py1 = (py1 < img.rows () ? py1: py1 - 1);
  px1 = (px1 < img.cols () ? px1: px1 - 1);
  coeff rc = 0;

  for (int i = 0; i < 4; i++)
    {
      int cy = (int)py + ((i & 0x02) != 0);
      int cx = (int)px + ((i & 0x01) != 0);
      cy = (cy < img.rows ()? cy: cy - 1);
      cx = (cx < img.cols ()? cx: cx - 1);
      assert (cy <= py + 1);
      assert (cx <= px + 1);
      assert (cy >= 0);
      assert (cx >= 0);
      assert (cy < img.rows ());
      assert (cx < img.cols ());
      coeff c = img.at (cy, cx);
      rc += c;
    }

  return rc / 4;
}

static coeff
nearestneighbour (const Image &img, int y, int x, coeff factor)
{
  coeff py = y / factor;
  coeff px = x / factor;
  int py1 = (int)py + 1;
  int px1 = (int)px + 1;
  py1 = (py1 < img.rows () ? py1: py1 - 1);
  px1 = (px1 < img.cols () ? px1: px1 - 1);
  coeff rc = 0;
  coeff mindist = -1;

  for (int i = 0; i < 4; i++)
    {
      int cy = (int)py + ((i & 0x02) != 0);
      int cx = (int)px + ((i & 0x01) != 0);
      cy = (cy < img.rows ()? cy: cy - 1);
      cx = (cx < img.cols ()? cx: cx - 1);
      assert (cy <= py + 1);
      assert (cx <= px + 1);
      assert (cy >= 0);
      assert (cx >= 0);
      assert (cy < img.rows ());
      assert (cx < img.cols ());

      coeff dist = sqrt (SQUARE (py - cy) + SQUARE (px - cx));

      if (mindist < 0 || dist <= mindist)
        {
          mindist = dist;
          rc = img.at (cy, cx);
        }
    }
  return rc;
}

static coeff
nearestneighbour (const Image &img, int y, int x, coeff yFactor, coeff xFactor)
{
  coeff py = y / yFactor;
  coeff px = x / xFactor;
  int py1 = (int)py + 1;
  int px1 = (int)px + 1;
  py1 = (py1 < img.rows () ? py1: py1 - 1);
  px1 = (px1 < img.cols () ? px1: px1 - 1);
  coeff rc = 0;
  coeff mindist = -1;

  for (int i = 0; i < 4; i++)
    {
      int cy = (int)py + ((i & 0x02) != 0);
      int cx = (int)px + ((i & 0x01) != 0);
      cy = (cy < img.rows ()? cy: cy - 1);
      cx = (cx < img.cols ()? cx: cx - 1);
      assert (cy <= py + 1);
      assert (cx <= px + 1);
      assert (cy >= 0);
      assert (cx >= 0);
      assert (cy < img.rows ());
      assert (cx < img.cols ());

      coeff dist = sqrt (SQUARE (py - cy) + SQUARE (px - cx));

      if (mindist < 0 || dist <= mindist)
        {
          mindist = dist;
          rc = img.at (cy, cx);
        }
    }
  return rc;
}

// interpolate vertically choosing the nearest horizontal pair
static coeff vlinear (const Image &img, int y, int x, coeff factor)
{
  coeff py = y / factor;
  coeff px = x / factor;
  int py1 = (int)py + 1;
  int px1 = (int)px + 1;
  py1 = (py1 < img.rows () ? py1: py1 - 1);
  px1 = (px1 < img.cols () ? px1: px1 - 1);

  assert (py >= 0);
  assert (px >= 0);
  assert (py < img.rows ());
  assert (px < img.cols ());

  assert (py1 >= 0);
  assert (px1 >= 0);
  assert (py1 < img.rows ());
  assert (px1 < img.cols ());
  assert (py1 >= (int)py);
  assert (px1 >= (int)px);

  // two values
  coeff v1, v2;

  // ..
  coeff alpha;

  alpha = (py - (int)py);
  assert (alpha <= 1);

  int ix = (px1 - x > 0.5)? (int)px: (int)px1;

  v1 = img.at ((int)py, ix);
  v2 = img.at (py1, ix);

  return (v2 - v1) * alpha + v1;
}

// interpolate vertically choosing the nearest horizontal pair
static coeff vlinear (const Image &img, int y, int x,
                      coeff yFactor, coeff xFactor)
{
  coeff py = y / yFactor;
  coeff px = x / xFactor;
  int py1 = (int)py + 1;
  int px1 = (int)px + 1;
  py1 = (py1 < img.rows () ? py1: py1 - 1);
  px1 = (px1 < img.cols () ? px1: px1 - 1);

  assert (py >= 0);
  assert (px >= 0);
  assert (py < img.rows ());
  assert (px < img.cols ());

  assert (py1 >= 0);
  assert (px1 >= 0);
  assert (py1 < img.rows ());
  assert (px1 < img.cols ());
  assert (py1 >= (int)py);
  assert (px1 >= (int)px);

  // two values
  coeff v1, v2;

  // ..
  coeff alpha;

  alpha = (py - (int)py);
  assert (alpha <= 1);

  int ix = (px1 - x > 0.5)? (int)px: (int)px1;

  v1 = img.at ((int)py, ix);
  v2 = img.at (py1, ix);

  return (v2 - v1) * alpha + v1;
}

// interpolate horizontally choosing the nearest vertical pair
static coeff hlinear (const Image &img, int y, int x, coeff factor)
{
  coeff py = y / factor;
  coeff px = x / factor;
  int py1 = (int)py + 1;
  int px1 = (int)px + 1;
  py1 = (py1 < img.rows () ? py1: py1 - 1);
  px1 = (px1 < img.cols () ? px1: px1 - 1);

  assert (py >= 0);
  assert (px >= 0);
  assert (py < img.rows ());
  assert (px < img.cols ());

  assert (py1 >= 0);
  assert (px1 >= 0);
  assert (py1 < img.rows ());
  assert (px1 < img.cols ());
  assert (py1 >= (int)py);
  assert (px1 >= (int)px);

  // two values
  coeff v1, v2;

  // ..
  coeff alpha;

  alpha = (px - (int)px);
  assert (alpha <= 1);

  int iy = (py1 - y > 0.5)? (int)py: (int)py1;

  v1 = img.at (iy, (int)px);
  v2 = img.at (iy, px1);

  return (v2 - v1) * alpha + v1;
}

// interpolate horizontally choosing the nearest vertical pair
static coeff hlinear (const Image &img, int y, int x,
                      coeff yFactor, coeff xFactor)
{
  coeff py = y / yFactor;
  coeff px = x / xFactor;
  int py1 = (int)py + 1;
  int px1 = (int)px + 1;
  py1 = (py1 < img.rows () ? py1: py1 - 1);
  px1 = (px1 < img.cols () ? px1: px1 - 1);

  assert (py >= 0);
  assert (px >= 0);
  assert (py < img.rows ());
  assert (px < img.cols ());

  assert (py1 >= 0);
  assert (px1 >= 0);
  assert (py1 < img.rows ());
  assert (px1 < img.cols ());
  assert (py1 >= (int)py);
  assert (px1 >= (int)px);

  // two values
  coeff v1, v2;

  // ..
  coeff alpha;

  alpha = (px - (int)px);
  assert (alpha <= 1);

  int iy = (py1 - y > 0.5)? (int)py: (int)py1;

  v1 = img.at (iy, (int)px);
  v2 = img.at (iy, px1);

  return (v2 - v1) * alpha + v1;
}

