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