/*
 * class ImageResizer
 *
 * $Date$
 * $Revision$
 *
 */

#ifndef IMAGE_RESIZER_HH__
#define IMAGE_RESIZER_HH__

#include "WImage/ColorImage.hh"
#include "Wave/WaveletTransform.hh"

/**
 * @addtogroup WTools
 * @{ */

/**
 * A class for advanced resizing of images, i.e. wavelet-based
 * techniques can be used to intelligently crop images to fit
 *
 * This class will only discard background pixels if the image is
 * shrunk.
 */
class ImageResizer
{
public:

  /**
   * Constructor, loads an image, sets Wavelet to be used.
   * @param img a reference to the image
   * @param flt a reference to the filterset
   * @param fill if not NULL it must point to an array of as many
   * values as color channels, so that each of the image's channels
   * has its own fill greyscale value for the remaining space
   * (else the a smaller image size will be chosen if the aspect ratio
   * does not match)
   * @param optimizeImage if set to true, we will try to locate the actual
   * contents regardless of whether the target dimensions fit or not
   * @param threshold the threshold for the aggressiveness of the
   * cropping (< 1), the higher the more aggressive, default is 0.001
   * @param scalingStrategy the scaling strategy (0: bilinear
   * interpolation, 1: average, 2: nearest neighbour).
   * @return a new rescaled image */
  ImageResizer (const ColorImage &img, FilterSet &flt, int *fill = NULL,
	        coeff threshold = 0.001, bool optimizeImage = false,
		int scalingStrategy = 0);

  /**
   * Destructor. Cleans up if necessary.
   */
  ~ImageResizer (void);

  /** Create a resized image's of x/y dimensions. The result image will be
   * cropped to optimally fit in the new dimensions. The maximal percentage
   * of cropping as well as the thresholds for guessing insignificant areas
   * can be set. If both dimensions are set to 0 and the optimization feature
   * is set, we get an auto-crop (i.e. the size is chosen from the image's
   * contents)
   * The new dimensions must all be greater than zero.
   * @exception invalid_argument
   * one or both dimensions are either negative or zero
   * @param rows
   * the new number of rows
   * @param cols
   * the new number of cols
   * @param steps the number of steps to decompose before checking for features
   * @return the new resized image
   */
  ColorImage* resize (int rows, int cols, int steps = 1);

  /**
   * Prepare an image for resizing to x/y dimensions. The result image will be
   * cropped to optimally fit in the new dimensions. The maximal percentage
   * of cropping as well as the thresholds for guessing insignificant areas
   * can be set. This operation skips the actual scaling, so that it can be
   * performed by someone else.
   * The new dimensions must all be greater than zero.
   * @exception invalid_argument
   * one or both dimensions are either negative or zero
   * @param rows
   * the new number of rows
   * @param cols
   * the new number of cols
   * @param steps the number of steps to decompose before checking for features
   * @return the new redimensioned image
   */
  ColorImage* redimension (int rows, int cols, int steps = 1);

  /**
  * Return the inner (absolute) average per size (used for quality measurements)
  * @return the inner average per size
  */
  inline double getInnerAvgPerSize (void) const { return m_innerAvgPerSize; }
  /**
  * Return the inner standard deviation (used for quality measurements)
  * @return the inner standard deviation
  */
  inline double getInnerSDeviation (void) const { return m_innerSDeviation; }
  /**
  * Return the size of the inner region used for the above calculation
  * @return the size of the inner region used for the above calculation
  */
  inline int getInnerRegionSize (void) const { return m_innerRegionSize; }

  /**
  * Return the threshold for cropping-while-resizing
  * @return the threshold for cropping-while-resizing
  */
  inline double threshold (void) const { return m_threshold; }
  /**
  * Set the threshold for cropping-while-resizing
  * @param threshold the threshold for cropping-while-resizing
  */
  inline void threshold (double threshold) { m_threshold = threshold; }

  /**
  * Set the optimize-image-setting
  * @param optimize true if optimization is active
  */
  inline void optimizeImage (bool optimizeImage) { m_optimizeImage = optimizeImage; }
  /**
  * Return the optimize-image-setting
  * @return true if optimization is active
  */
  inline bool optimizeImage (void) { return m_optimizeImage; }

protected:
  /**
   * Calculate the image's new dimensions
   * @param rows the number of rows
   * @param cols the number of cols
   */
  void calcDimensions (int rows, int cols);

  /**
   * If the target dimensions do not match the source dimensions, determine
   * how many rows or cols respectively can be discarded to further optimize
   * the image.
   * @param rows the target rows
   * @param cols the target cols
   * @return the number of rows / cols which can be discarded in the original
   * image
   */
  int fixDimensions (int rows, int cols);

  /**
   * Determine how many rows and cols can be discarded regardless of the
   * target dimensions. This is calculated once within ::resize(), so that
   * if more than one target size is required by repeated resizing operations
   * little or no further effort is necessary after the first operation.
   */
  void calcOptimization (void);

  /**
   * Calculate the average and variance for an outer region of the
   * (internal) maxDetail image. This is needed to see whether we can
   * discard this outer region or not.
   * @param avg the calculated average
   * @param variance the calculated variance
   * @param firstTop the first top of the frame
   * @param firstLeft the first left of the frame
   * @param firstBottom the first bottom of the frame
   * @param firstRight the first right of the frame
   * @param secondTop the second top of the frame
   * @param secondLeft the second left of the frame
   * @param secondBottom the second bottom of the frame
   * @param secondRight the second right of the frame
   */
  void calcOuterStats (coeff &avg, coeff &variance, int firstTop, int firstLeft,
		       int firstBottom, int firstRight, int secondTop, int secondLeft,
		       int secondBottom, int secondRight);

  /**
   * Calculate the average and variance for an inner region of the
   * (internal) maxDetail image. This is needed to see whether we can
   * discard the region around it or not.
   * @param avg the calculated average
   * @param variance the calculated variance
   * @param top the top of the inner region
   * @param left the left of the inner region
   * @param bottom the bottom of the inner region
   * @param right the right of the inner region
   */
  void calcInnerStats (coeff &avg, coeff &sDev,
		       int top, int left, int bottom, int right);

  /**
   * Calculate average and standard deviation for the regions either top and
   * bottom or left and right (depending on the value of m_cropWhat) and the
   * region between for a given number of rows/columns symetrically from the
   * boundaries to the center of the image.
   * @param nRows the number of rows over which to calculate
   * @param nCols the number of rows over which to calculate
   * @param outerAvg (out parameter) is where the calculated average for the
   * regions to crop is stored
   * @param outerSDev (out parameter) is where the calculated standard
   * deviation for the regions to crop is stored
   * @param innerAvg (out parameter) is where the calculated average for
   * the region between is stored
   * @param innerSDev (out parameter) is where the calculated standard
   * deviation for the region between is stored
   */
  void calcStats (int nRows, int nCols, coeff &outerAvg,
		  coeff &outerSDev, coeff &innerAvg, coeff &innerSDev);

  /**
   * Calculate the maxDetail image for the statistics
   * @param steps the number of decomposition steps
   */
  void genMaxDetail (int steps);

  /**
   * Create the final unscaled result image using the previously calculated
   * number of vectors to discard.
   * @param rows the number of rows
   * @param cols the number of cols
   * @param discardRows the number of rows to discard on each side
   * (i.e. we actually discard 2 * discardRows overall); if less than or equal
   * to zero, nothing will be discarded
   * @param discardCols the number of rows to discard on each side
   * (i.e. we actually discard 2 * discardCols overall); if less than or equal
   * to zero, nothing will be discarded
   * @return the result image
   */
  ColorImage* getUnscaledImage (int rows, int cols, int discardRows, int discardCols);

  /**
   * Create the final scaled result image using the previously calculated number
   * of vectors to discard.
   * @param rows the number of rows
   * @param cols the number of cols
   * @param discardRows the number of rows to discard on each side
   * (i.e. we actually discard 2 * discardRows overall); if less than or equal
   * to zero, nothing will be discarded
   * @param discardCols the number of rows to discard on each side
   * (i.e. we actually discard 2 * discardCols overall); if less than or equal
   * to zero, nothing will be discarded
   * @return the result image
   */
  ColorImage* getScaledImage (int rows, int cols, int discardRows, int discardCols);

  /**
   * This operation applies the cropping using the previously calculated numbers
   * of rows or cols which can be safely discarded.
   * @param discardRows the number of rows to discard on each side
   * (i.e. we actually discard 2 * discardRows overall); if less than or equal
   * to zero, nothing will be discarded
   * @param discardCols the number of rows to discard on each side
   * (i.e. we actually discard 2 * discardCols overall); if less than or equal
   * to zero, nothing will be discarded
   * @return the cropped image or a copy of the original if nothing was cropped
   */
  ColorImage* getCroppedImage (int discardRows, int discardCols);

  /** Create a resized image's of x/y dimensions. The result image will be
   * cropped to optimally fit in the new dimensions. The maximal percentage
   * of cropping as well as the thresholds for guessing insignificant areas
   * can be set. If both dimensions are set to 0 and the optimization feature
   * is set, we get an auto-crop (i.e. the size is chosen from the image's
   * contents)
   * The new dimensions must all be greater than zero.
   * @exception invalid_argument
   * one or both dimensions are either negative or zero
   * @param rows
   * the new number of rows
   * @param cols
   * the new number of cols
   * @param steps the number of steps to decompose before checking for features
   * @param redimensionOnly if true, the image will be prepared for scaling,
   * i.e. the dimensions are fixed filling performed etc., so that it can be
   * scaled by some other application later.
   * @return the new resized or prepared image
   */
  ColorImage* doResize (int rows, int cols, int steps, bool redimensionOnly);

  const ColorImage &m_image;
  Image *m_maxDetail;
  FilterSet &m_filter;
  float m_qRows;
  float m_qCols;
  int *m_fill;
  int m_scalingStrategy;
  double m_innerAvgPerSize;
  double m_innerSDeviation;
  int m_innerRegionSize;
  coeff m_threshold;
  bool m_optimizeImage;
  bool m_optimizationCalculated;
  int m_cropMaxRows;
  int m_cropMaxCols;
  double m_rowsMapping;
  double m_colsMapping;
  int m_steps;
  enum {
    CROP_COLS, CROP_ROWS, CROP_BOTH_OR_NONE, CROP_AUTOMATICALLY
  } m_cropWhat;
};

/** @} */

#endif // IMAGE_RESIZER_HH__
