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 #include "ImageTransformation.h"
20
ImageTransformation(const QRectF & orig_image_rect,const Dpi & orig_dpi)21 ImageTransformation::ImageTransformation(const QRectF& orig_image_rect, const Dpi& orig_dpi)
22 : m_postRotation(0.0), m_origRect(orig_image_rect), m_resultingRect(orig_image_rect), m_origDpi(orig_dpi) {
23 preScaleToEqualizeDpi();
24 }
25
26 ImageTransformation::~ImageTransformation() = default;
27
preScaleToDpi(const Dpi & dpi)28 void ImageTransformation::preScaleToDpi(const Dpi& dpi) {
29 if (m_origDpi.isNull() || dpi.isNull()) {
30 return;
31 }
32
33 m_preScaledDpi = dpi;
34
35 const double xscale = (double) dpi.horizontal() / m_origDpi.horizontal();
36 const double yscale = (double) dpi.vertical() / m_origDpi.vertical();
37
38 const QSizeF new_pre_scaled_image_size(m_origRect.width() * xscale, m_origRect.height() * yscale);
39
40 // Undo's for the specified steps.
41 const QTransform undo21(m_preRotateXform.inverted() * m_preScaleXform.inverted());
42 const QTransform undo4321(m_postRotateXform.inverted() * m_preCropXform.inverted() * undo21);
43
44 // Update transform #1: pre-scale.
45 m_preScaleXform.reset();
46 m_preScaleXform.scale(xscale, yscale);
47
48 // Update transform #2: pre-rotate.
49 m_preRotateXform = m_preRotation.transform(new_pre_scaled_image_size);
50
51 // Update transform #3: pre-crop.
52 const QTransform redo12(m_preScaleXform * m_preRotateXform);
53 m_preCropArea = (undo21 * redo12).map(m_preCropArea);
54 m_preCropXform = calcCropXform(m_preCropArea);
55
56 // Update transform #4: post-rotate.
57 m_postRotateXform = calcPostRotateXform(m_postRotation);
58
59 // Update transform #5: post-crop.
60 const QTransform redo1234(redo12 * m_preCropXform * m_postRotateXform);
61 m_postCropArea = (undo4321 * redo1234).map(m_postCropArea);
62 m_postCropXform = calcCropXform(m_postCropArea);
63 // Update transform #6: post-scale.
64 m_postScaleXform = calcPostScaleXform(m_postScaledDpi);
65
66 update();
67 } // ImageTransformation::preScaleToDpi
68
preScaleToEqualizeDpi()69 void ImageTransformation::preScaleToEqualizeDpi() {
70 const int min_dpi = std::min(m_origDpi.horizontal(), m_origDpi.vertical());
71 preScaleToDpi(Dpi(min_dpi, min_dpi));
72 }
73
setPreRotation(const OrthogonalRotation rotation)74 void ImageTransformation::setPreRotation(const OrthogonalRotation rotation) {
75 m_preRotation = rotation;
76 m_preRotateXform = m_preRotation.transform(m_origRect.size());
77 resetPreCropArea();
78 resetPostRotation();
79 resetPostCrop();
80 resetPostScale();
81 update();
82 }
83
setPreCropArea(const QPolygonF & area)84 void ImageTransformation::setPreCropArea(const QPolygonF& area) {
85 m_preCropArea = area;
86 m_preCropXform = calcCropXform(area);
87 resetPostRotation();
88 resetPostCrop();
89 resetPostScale();
90 update();
91 }
92
setPostRotation(const double degrees)93 void ImageTransformation::setPostRotation(const double degrees) {
94 m_postRotateXform = calcPostRotateXform(degrees);
95 m_postRotation = degrees;
96 resetPostCrop();
97 resetPostScale();
98 update();
99 }
100
setPostCropArea(const QPolygonF & area)101 void ImageTransformation::setPostCropArea(const QPolygonF& area) {
102 m_postCropArea = area;
103 m_postCropXform = calcCropXform(area);
104 resetPostScale();
105 update();
106 }
107
postScaleToDpi(const Dpi & dpi)108 void ImageTransformation::postScaleToDpi(const Dpi& dpi) {
109 m_postScaledDpi = dpi;
110 m_postScaleXform = calcPostScaleXform(dpi);
111 update();
112 }
113
calcCropXform(const QPolygonF & area)114 QTransform ImageTransformation::calcCropXform(const QPolygonF& area) {
115 const QRectF bounds(area.boundingRect());
116 QTransform xform;
117 xform.translate(-bounds.x(), -bounds.y());
118
119 return xform;
120 }
121
calcPostRotateXform(const double degrees)122 QTransform ImageTransformation::calcPostRotateXform(const double degrees) {
123 QTransform xform;
124 if (degrees != 0.0) {
125 const QPointF origin(m_preCropArea.boundingRect().center());
126 xform.translate(-origin.x(), -origin.y());
127 xform *= QTransform().rotate(degrees);
128 xform *= QTransform().translate(origin.x(), origin.y());
129
130 // Calculate size changes.
131 const QPolygonF pre_rotate_poly(m_preCropXform.map(m_preCropArea));
132 const QRectF pre_rotate_rect(pre_rotate_poly.boundingRect());
133 const QPolygonF post_rotate_poly(xform.map(pre_rotate_poly));
134 const QRectF post_rotate_rect(post_rotate_poly.boundingRect());
135
136 xform *= QTransform().translate(pre_rotate_rect.left() - post_rotate_rect.left(),
137 pre_rotate_rect.top() - post_rotate_rect.top());
138 }
139
140 return xform;
141 }
142
calcPostScaleXform(const Dpi & target_dpi)143 QTransform ImageTransformation::calcPostScaleXform(const Dpi& target_dpi) {
144 if (target_dpi.isNull()) {
145 return QTransform();
146 }
147
148 // We are going to measure the effective DPI after the previous transforms.
149 // Normally m_preScaledDpi would be symmetric, so we could just
150 // use that, but just in case ...
151
152 const QTransform to_orig(m_postScaleXform * m_transform.inverted());
153 // IMPORTANT: in the above line we assume post-scale is the last transform.
154
155 const QLineF hor_unit(QPointF(0, 0), QPointF(1, 0));
156 const QLineF vert_unit(QPointF(0, 0), QPointF(0, 1));
157 const QLineF orig_hor_unit(to_orig.map(hor_unit));
158 const QLineF orig_vert_unit(to_orig.map(vert_unit));
159
160 const double xscale = target_dpi.horizontal() * orig_hor_unit.length() / m_origDpi.horizontal();
161 const double yscale = target_dpi.vertical() * orig_vert_unit.length() / m_origDpi.vertical();
162 QTransform xform;
163 xform.scale(xscale, yscale);
164
165 return xform;
166 }
167
resetPreCropArea()168 void ImageTransformation::resetPreCropArea() {
169 m_preCropArea.clear();
170 m_preCropXform.reset();
171 }
172
resetPostRotation()173 void ImageTransformation::resetPostRotation() {
174 m_postRotation = 0.0;
175 m_postRotateXform.reset();
176 }
177
resetPostCrop()178 void ImageTransformation::resetPostCrop() {
179 m_postCropArea.clear();
180 m_postCropXform.reset();
181 }
182
resetPostScale()183 void ImageTransformation::resetPostScale() {
184 m_postScaledDpi = Dpi();
185 m_postScaleXform.reset();
186 }
187
update()188 void ImageTransformation::update() {
189 const QTransform pre_scale_then_pre_rotate(m_preScaleXform * m_preRotateXform); // 12
190 const QTransform pre_crop_then_post_rotate(m_preCropXform * m_postRotateXform); // 34
191 const QTransform post_crop_then_post_scale(m_postCropXform * m_postScaleXform); // 56
192 const QTransform pre_crop_and_further(pre_crop_then_post_rotate * post_crop_then_post_scale); // 3456
193 m_transform = pre_scale_then_pre_rotate * pre_crop_and_further;
194 m_invTransform = m_transform.inverted();
195 if (m_preCropArea.empty()) {
196 m_preCropArea = pre_scale_then_pre_rotate.map(m_origRect);
197 }
198 if (m_postCropArea.empty()) {
199 m_postCropArea = pre_crop_then_post_rotate.map(m_preCropArea);
200 }
201 m_resultingPreCropArea = pre_crop_and_further.map(m_preCropArea);
202 m_resultingPostCropArea = post_crop_then_post_scale.map(m_postCropArea);
203 m_resultingRect = m_resultingPostCropArea.boundingRect();
204 }
205