/*
    Scan Tailor - Interactive post-processing tool for scanned pages.
    Copyright (C)  Joseph Artsimovich <joseph.artsimovich@gmail.com>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef IMAGETRANSFORMATION_H_
#define IMAGETRANSFORMATION_H_

#include "OrthogonalRotation.h"
#include "Dpi.h"
#include <QTransform>
#include <QPolygonF>
#include <QRectF>

/**
 * \brief Provides a transformed view of an image.
 *
 * \anchor transformations
 * Suppose we need to make the following transformations
 * to a particular image (in this order):
 * -#  Scale it, to achieve the desired optical resolution.
 *     It's generally done only in case input had different
 *     horizontal and vertical DPI.  We refer to this operation
 *     as pre-scale.
 * -#  Rotate it by a multiple of 90 degrees,
 *     swapping its width and height if necessary.
 *     That's done on the "Fix Orientation" stage.  We refer to
 *     this operation as pre-rotate.
 * -#  Crop it, reducing its width and height.
 *     That's done on the "Split Pages" stage, where we crop
 *     to the bounding box of a polygon formed by cutters and
 *     remaining image edges.  We refer to this operation as
 *     pre-crop.
 * -#  Apply a rotation around the image center,
 *     increasing its width and height as necessary.
 *     That's done on the "Deskew" stage.  We refer to this
 *     operation as post-rotate.
 * -#  Apply another crop, or maybe extend the image boundaries,
 *     in order to place the required margins around content.
 *     That's done on the "Margins" stage.  We refer to this operation
 *     as post-crop.
 * -#  Scale the image to desired DPI.  That's done on the "Output" stage.
 *     We refer to this operation as post-scale.
 *
 * Instead of actually modifying the image, we provide
 * a mapping from the original image coordinates to the new ones.
 * Note that all transformation steps are optional.
 */
class ImageTransformation
{
public:
	// Member-wise copying is OK.

	ImageTransformation(QRectF const& orig_image_rect, Dpi const& orig_dpi);

	~ImageTransformation();

	/**
	 * \brief Set the 1st step transformation, recalculating the following ones.
	 *
	 * \see \ref transformations Transformations.
	 */
	void preScaleToDpi(Dpi const& dpi);

	/**
	 * \brief Set the 1st step transformation, recalculating the following ones.
	 *
	 * Suppose the original image DPI is 300x600.  This will scale it to
	 * 300x300, the minimum of two.
	 *
	 * \note This transformation is applied automatically on construction.
	 *
	 * \see \ref transformations Transformations.
	 */
	void preScaleToEqualizeDpi();

	/**
	 * \brief Get the original image DPI.
	 */
	Dpi const& origDpi() const { return m_origDpi; }

	/**
	 * \brief Get the target DPI for pre-scaling.
	 *
	 * Note that if the original DPI was assymetric, pre-scaling to
	 * a symmetric DPI will be applied implicitly.
	 */
	Dpi const& preScaledDpi() const { return m_preScaledDpi; }

	/**
	 * \brief Set the 2nd step transformation, resetting the following ones.
	 *
	 * \see \ref transformations Transformations.
	 */
	void setPreRotation(OrthogonalRotation rotation);

	/**
	 * \brief Returns the 2nd step rotation.
	 */
	OrthogonalRotation preRotation() const { return m_preRotation; }

	/**
	 * \brief Set the 3rd step transformation, resetting the following ones.
	 *
	 * Providing a null polygon has the same effect as providing a polygon
	 * that covers the entire image.  A crop area that exceedes the image
	 * is allowed.
	 *
	 * \see \ref transformations Transformations.
	 */
	void setPreCropArea(QPolygonF const& area);

	/**
	 * \brief Get the effective pre-crop area in pre-rotated coordinates.
	 *
	 * If pre-crop area was explicitly set with setPreCropArea(), then
	 * this function returns it as is.  Otherwise, the whole available
	 * area is returned.
	 */
	QPolygonF const& preCropArea() const { return m_preCropArea; }

	/**
	 * \brief Returns the pre-crop area after all transformations.
	 *
	 * If no pre-crop area was set, the whole image is assumed to be
	 * the pre-crop area.
	 */
	QPolygonF const& resultingPreCropArea() const { return m_resultingPreCropArea; }

	/**
	 * \brief Set the 4th step transformation, resetting  the following ones.
	 *
	 * \see \ref transformations Transformations.
	 */
	void setPostRotation(double degrees);

	/**
	 * \brief Returns the 4th step rotation in degrees, as specified.
	 */
	double postRotation() const { return m_postRotation; }

	/**
	 * \brief Returns the sine of the 4th step rotation angle.
	 */
	double postRotationSin() const { return m_postRotateXform.m12(); }

	/**
	 * \brief Returns the cosine of the 3rd step rotation angle.
	 */
	double postRotationCos() const { return m_postRotateXform.m11(); }

	/**
	 * \brief Set the 5th step transformation, resetting the following ones.
	 */
	void setPostCropArea(QPolygonF const& area);

	/**
	 * \brief Returns the post-crop area after all transformations.
	 *
	 * If no post-crop area was set, the whole image is assumed to be
	 * the post-crop area.
	 */
	QPolygonF const& resultingPostCropArea() const { return m_resultingPostCropArea; }

	/**
	 * \brief Set the 6th step transformation.
	 *
	 * Passing a null (default constructed) Dpi means "don't apply post-scaling".
	 */
	void postScaleToDpi(Dpi const& dpi);

	/**
	 * \brief Returns the transformation matrix from the original
	 *        to resulting image coordinates.
	 */
	QTransform const& transform() const { return m_transform; }

	/**
	 * \brief Returns the transformation matrix from the resulting
	 *        to original image coordinates.
	 */
	QTransform const& transformBack() const { return m_invTransform; }

	/**
	 * \brief Returns the original image rectangle, as specified.
	 */
	QRectF const& origRect() const { return m_origRect; }

	/**
	 * \brief Returns the resulting image rectangle.
	 *
	 * The top-left corner of the resulting rectangle is expected
	 * to be very close to (0, 0), assuming the original rectangle
	 * had it at (0, 0), but it's not guaranteed to be exactly there.
	 */
	QRectF const& resultingRect() const { return m_resultingRect; }
private:
	QTransform calcCropXform(QPolygonF const& crop_area);

	QTransform calcPostRotateXform(double degrees);

	QTransform calcPostScaleXform(Dpi const& target_dpi);

	void resetPreCropArea();

	void resetPostRotation();

	void resetPostCrop();

	void resetPostScale();

	void update();

	QTransform m_preScaleXform;
	QTransform m_preRotateXform;
	QTransform m_preCropXform;
	QTransform m_postRotateXform;
	QTransform m_postCropXform;
	QTransform m_postScaleXform;
	QTransform m_transform;
	QTransform m_invTransform;
	double m_postRotation;
	QRectF m_origRect;
	QRectF m_resultingRect; // Managed by update().
	QPolygonF m_preCropArea;
	QPolygonF m_resultingPreCropArea; // Managed by update().
	QPolygonF m_postCropArea;
	QPolygonF m_resultingPostCropArea; // Managed by update().
	Dpi m_origDpi;
	Dpi m_preScaledDpi; // Always set, as preScaleToEqualizeDpi() is called from the constructor.
	Dpi m_postScaledDpi; // Default constructed object if no post-scaling.
	OrthogonalRotation m_preRotation;
};

#endif
