1 /*
2     Scan Tailor - Interactive post-processing tool for scanned pages.
3     Copyright (C)  Joseph Artsimovich <joseph.artsimovich@gmail.com>
4 
5     This program is free software: you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation, either version 3 of the License, or
8     (at your option) any later version.
9 
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14 
15     You should have received a copy of the GNU General Public License
16     along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #ifndef IMAGETRANSFORMATION_H_
20 #define IMAGETRANSFORMATION_H_
21 
22 #include <QPolygonF>
23 #include <QRectF>
24 #include <QTransform>
25 #include "Dpi.h"
26 #include "OrthogonalRotation.h"
27 
28 /**
29  * \brief Provides a transformed view of an image.
30  *
31  * \anchor transformations
32  * Suppose we need to make the following transformations
33  * to a particular image (in this order):
34  * -#  Scale it, to achieve the desired optical resolution.
35  *     It's generally done only in case input had different
36  *     horizontal and vertical DPI.  We refer to this operation
37  *     as pre-scale.
38  * -#  Rotate it by a multiple of 90 degrees,
39  *     swapping its width and height if necessary.
40  *     That's done on the "Fix Orientation" stage.  We refer to
41  *     this operation as pre-rotate.
42  * -#  Crop it, reducing its width and height.
43  *     That's done on the "Split Pages" stage, where we crop
44  *     to the bounding box of a polygon formed by cutters and
45  *     remaining image edges.  We refer to this operation as
46  *     pre-crop.
47  * -#  Apply a rotation around the image center,
48  *     increasing its width and height as necessary.
49  *     That's done on the "Deskew" stage.  We refer to this
50  *     operation as post-rotate.
51  * -#  Apply another crop, or maybe extend the image boundaries,
52  *     in order to place the required margins around content.
53  *     That's done on the "Margins" stage.  We refer to this operation
54  *     as post-crop.
55  * -#  Scale the image to desired DPI.  That's done on the "Output" stage.
56  *     We refer to this operation as post-scale.
57  *
58  * Instead of actually modifying the image, we provide
59  * a mapping from the original image coordinates to the new ones.
60  * Note that all transformation steps are optional.
61  */
62 class ImageTransformation {
63  public:
64   // Member-wise copying is OK.
65 
66   ImageTransformation(const QRectF& orig_image_rect, const Dpi& orig_dpi);
67 
68   ~ImageTransformation();
69 
70   /**
71    * \brief Set the 1st step transformation, recalculating the following ones.
72    *
73    * \see \ref transformations Transformations.
74    */
75   void preScaleToDpi(const Dpi& dpi);
76 
77   /**
78    * \brief Set the 1st step transformation, recalculating the following ones.
79    *
80    * Suppose the original image DPI is 300x600.  This will scale it to
81    * 300x300, the minimum of two.
82    *
83    * \note This transformation is applied automatically on construction.
84    *
85    * \see \ref transformations Transformations.
86    */
87   void preScaleToEqualizeDpi();
88 
89   /**
90    * \brief Get the original image DPI.
91    */
origDpi()92   const Dpi& origDpi() const { return m_origDpi; }
93 
94   /**
95    * \brief Get the target DPI for pre-scaling.
96    *
97    * Note that if the original DPI was assymetric, pre-scaling to
98    * a symmetric DPI will be applied implicitly.
99    */
preScaledDpi()100   const Dpi& preScaledDpi() const { return m_preScaledDpi; }
101 
102   /**
103    * \brief Set the 2nd step transformation, resetting the following ones.
104    *
105    * \see \ref transformations Transformations.
106    */
107   void setPreRotation(OrthogonalRotation rotation);
108 
109   /**
110    * \brief Returns the 2nd step rotation.
111    */
preRotation()112   OrthogonalRotation preRotation() const { return m_preRotation; }
113 
114   /**
115    * \brief Set the 3rd step transformation, resetting the following ones.
116    *
117    * Providing a null polygon has the same effect as providing a polygon
118    * that covers the entire image.  A crop area that exceedes the image
119    * is allowed.
120    *
121    * \see \ref transformations Transformations.
122    */
123   void setPreCropArea(const QPolygonF& area);
124 
125   /**
126    * \brief Get the effective pre-crop area in pre-rotated coordinates.
127    *
128    * If pre-crop area was explicitly set with setPreCropArea(), then
129    * this function returns it as is.  Otherwise, the whole available
130    * area is returned.
131    */
preCropArea()132   const QPolygonF& preCropArea() const { return m_preCropArea; }
133 
134   /**
135    * \brief Returns the pre-crop area after all transformations.
136    *
137    * If no pre-crop area was set, the whole image is assumed to be
138    * the pre-crop area.
139    */
resultingPreCropArea()140   const QPolygonF& resultingPreCropArea() const { return m_resultingPreCropArea; }
141 
142   /**
143    * \brief Set the 4th step transformation, resetting  the following ones.
144    *
145    * \see \ref transformations Transformations.
146    */
147   void setPostRotation(double degrees);
148 
149   /**
150    * \brief Returns the 4th step rotation in degrees, as specified.
151    */
postRotation()152   double postRotation() const { return m_postRotation; }
153 
154   /**
155    * \brief Returns the sine of the 4th step rotation angle.
156    */
postRotationSin()157   double postRotationSin() const { return m_postRotateXform.m12(); }
158 
159   /**
160    * \brief Returns the cosine of the 3rd step rotation angle.
161    */
postRotationCos()162   double postRotationCos() const { return m_postRotateXform.m11(); }
163 
164   /**
165    * \brief Set the 5th step transformation, resetting the following ones.
166    */
167   void setPostCropArea(const QPolygonF& area);
168 
169   /**
170    * \brief Returns the post-crop area after all transformations.
171    *
172    * If no post-crop area was set, the whole image is assumed to be
173    * the post-crop area.
174    */
resultingPostCropArea()175   const QPolygonF& resultingPostCropArea() const { return m_resultingPostCropArea; }
176 
177   /**
178    * \brief Set the 6th step transformation.
179    *
180    * Passing a null (default constructed) Dpi means "don't apply post-scaling".
181    */
182   void postScaleToDpi(const Dpi& dpi);
183 
184   /**
185    * \brief Returns the transformation matrix from the original
186    *        to resulting image coordinates.
187    */
transform()188   const QTransform& transform() const { return m_transform; }
189 
190   /**
191    * \brief Returns the transformation matrix from the resulting
192    *        to original image coordinates.
193    */
transformBack()194   const QTransform& transformBack() const { return m_invTransform; }
195 
196   /**
197    * \brief Returns the original image rectangle, as specified.
198    */
origRect()199   const QRectF& origRect() const { return m_origRect; }
200 
201   /**
202    * \brief Returns the resulting image rectangle.
203    *
204    * The top-left corner of the resulting rectangle is expected
205    * to be very close to (0, 0), assuming the original rectangle
206    * had it at (0, 0), but it's not guaranteed to be exactly there.
207    */
resultingRect()208   const QRectF& resultingRect() const { return m_resultingRect; }
209 
210  private:
211   QTransform calcCropXform(const QPolygonF& crop_area);
212 
213   QTransform calcPostRotateXform(double degrees);
214 
215   QTransform calcPostScaleXform(const Dpi& target_dpi);
216 
217   void resetPreCropArea();
218 
219   void resetPostRotation();
220 
221   void resetPostCrop();
222 
223   void resetPostScale();
224 
225   void update();
226 
227   QTransform m_preScaleXform;
228   QTransform m_preRotateXform;
229   QTransform m_preCropXform;
230   QTransform m_postRotateXform;
231   QTransform m_postCropXform;
232   QTransform m_postScaleXform;
233   QTransform m_transform;
234   QTransform m_invTransform;
235   double m_postRotation;
236   QRectF m_origRect;
237   QRectF m_resultingRect;  // Managed by update().
238   QPolygonF m_preCropArea;
239   QPolygonF m_resultingPreCropArea;  // Managed by update().
240   QPolygonF m_postCropArea;
241   QPolygonF m_resultingPostCropArea;  // Managed by update().
242   Dpi m_origDpi;
243   Dpi m_preScaledDpi;   // Always set, as preScaleToEqualizeDpi() is called from the constructor.
244   Dpi m_postScaledDpi;  // Default constructed object if no post-scaling.
245   OrthogonalRotation m_preRotation;
246 };
247 
248 
249 #endif  // ifndef IMAGETRANSFORMATION_H_
250