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