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 "DistortionModel.h"
20 #include <QDomDocument>
21 #include <QRectF>
22 #include <QTransform>
23 #include "CylindricalSurfaceDewarper.h"
24
25 namespace dewarping {
26 DistortionModel::DistortionModel() = default;
27
DistortionModel(const QDomElement & el)28 DistortionModel::DistortionModel(const QDomElement& el)
29 : m_topCurve(el.namedItem("top-curve").toElement()), m_bottomCurve(el.namedItem("bottom-curve").toElement()) {}
30
toXml(QDomDocument & doc,const QString & name) const31 QDomElement DistortionModel::toXml(QDomDocument& doc, const QString& name) const {
32 if (!isValid()) {
33 return QDomElement();
34 }
35
36 QDomElement el(doc.createElement(name));
37 el.appendChild(m_topCurve.toXml(doc, "top-curve"));
38 el.appendChild(m_bottomCurve.toXml(doc, "bottom-curve"));
39
40 return el;
41 }
42
isValid() const43 bool DistortionModel::isValid() const {
44 if (!m_topCurve.isValid() || !m_bottomCurve.isValid()) {
45 return false;
46 }
47
48 const Vec2d poly[4] = {m_topCurve.polyline().front(), m_topCurve.polyline().back(), m_bottomCurve.polyline().back(),
49 m_bottomCurve.polyline().front()};
50
51 double min_dot = NumericTraits<double>::max();
52 double max_dot = NumericTraits<double>::min();
53
54 for (int i = 0; i < 4; ++i) {
55 const Vec2d cur(poly[i]);
56 const Vec2d prev(poly[(i + 3) & 3]);
57 const Vec2d next(poly[(i + 1) & 3]);
58
59 Vec2d prev_normal(cur - prev);
60 std::swap(prev_normal[0], prev_normal[1]);
61 prev_normal[0] = -prev_normal[0];
62
63 const double dot = prev_normal.dot(next - cur);
64 if (dot < min_dot) {
65 min_dot = dot;
66 }
67 if (dot > max_dot) {
68 max_dot = dot;
69 }
70 }
71
72 if (min_dot * max_dot <= 0) {
73 // Not convex.
74 return false;
75 }
76
77 if ((std::fabs(min_dot) < 0.01) || (std::fabs(max_dot) < 0.01)) {
78 // Too close - possible problems with calculating homography.
79 return false;
80 }
81
82 return true;
83 } // DistortionModel::isValid
84
matches(const DistortionModel & other) const85 bool DistortionModel::matches(const DistortionModel& other) const {
86 const bool this_valid = isValid();
87 const bool other_valid = other.isValid();
88 if (!this_valid && !other_valid) {
89 return true;
90 } else if (this_valid != other_valid) {
91 return false;
92 }
93
94 if (!m_topCurve.matches(other.m_topCurve)) {
95 return false;
96 } else if (!m_bottomCurve.matches(other.m_bottomCurve)) {
97 return false;
98 }
99
100 return true;
101 }
102
modelDomain(const CylindricalSurfaceDewarper & dewarper,const QTransform & to_output,const QRectF & output_content_rect) const103 QRectF DistortionModel::modelDomain(const CylindricalSurfaceDewarper& dewarper,
104 const QTransform& to_output,
105 const QRectF& output_content_rect) const {
106 QRectF model_domain(boundingBox(to_output));
107
108 // We not only uncurl the lines, but also stretch them in curved areas.
109 // Because we don't want to reach out of the content box, we shrink
110 // the model domain vertically, rather than stretching it horizontally.
111 const double vert_scale = 1.0 / dewarper.directrixArcLength();
112 // When scaling model_domain, we want the following point to remain where it is.
113 const QPointF scale_origin(output_content_rect.center());
114
115 const double new_upper_part = (scale_origin.y() - model_domain.top()) * vert_scale;
116 const double new_height = model_domain.height() * vert_scale;
117 model_domain.setTop(scale_origin.y() - new_upper_part);
118 model_domain.setHeight(new_height);
119
120 return model_domain;
121 }
122
boundingBox(const QTransform & transform) const123 QRectF DistortionModel::boundingBox(const QTransform& transform) const {
124 double top = NumericTraits<double>::max();
125 double left = top;
126 double bottom = NumericTraits<double>::min();
127 double right = bottom;
128
129 for (QPointF pt : m_topCurve.polyline()) {
130 pt = transform.map(pt);
131 left = std::min<double>(left, pt.x());
132 right = std::max<double>(right, pt.x());
133 top = std::min<double>(top, pt.y());
134 bottom = std::max<double>(bottom, pt.y());
135 }
136
137 for (QPointF pt : m_bottomCurve.polyline()) {
138 pt = transform.map(pt);
139 left = std::min<double>(left, pt.x());
140 right = std::max<double>(right, pt.x());
141 top = std::min<double>(top, pt.y());
142 bottom = std::max<double>(bottom, pt.y());
143 }
144
145 if ((top > bottom) || (left > right)) {
146 return QRectF();
147 } else {
148 return QRectF(left, top, right - left, bottom - top);
149 }
150 }
151 } // namespace dewarping