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 "PolygonUtils.h"
20 #include <QLineF>
21 #include <QPolygonF>
22 #include <cassert>
23 #include <cmath>
24
25 namespace imageproc {
26 const double PolygonUtils::ROUNDING_MULTIPLIER = 1 << 12;
27 const double PolygonUtils::ROUNDING_RECIP_MULTIPLIER = 1.0 / ROUNDING_MULTIPLIER;
28
29 class PolygonUtils::Before {
30 public:
operator ()(const QPointF & lhs,const QPointF & rhs) const31 bool operator()(const QPointF& lhs, const QPointF& rhs) const { return compare(lhs, rhs) < 0; }
32
operator ()(const QLineF & lhs,const QLineF & rhs)33 bool operator()(const QLineF& lhs, const QLineF& rhs) {
34 int comp = compare(lhs.p1(), rhs.p1());
35 if (comp != 0) {
36 return comp < 0;
37 }
38
39 return compare(lhs.p2(), rhs.p2()) < 0;
40 }
41
42 private:
compare(const QPointF & lhs,const QPointF & rhs)43 static int compare(const QPointF& lhs, const QPointF& rhs) {
44 const double dx = lhs.x() - rhs.x();
45 const double dy = lhs.y() - rhs.y();
46 if (std::fabs(dx) > std::fabs(dy)) {
47 if (dx < 0.0) {
48 return -1;
49 } else if (dx > 0.0) {
50 return 1;
51 }
52 } else {
53 if (dy < 0.0) {
54 return -1;
55 } else if (dy > 0.0) {
56 return 1;
57 }
58 }
59
60 return 0;
61 }
62 };
63
64
round(const QPolygonF & poly)65 QPolygonF PolygonUtils::round(const QPolygonF& poly) {
66 QPolygonF rounded;
67 rounded.reserve(poly.size());
68
69 for (const QPointF& p : poly) {
70 rounded.push_back(roundPoint(p));
71 }
72
73 return rounded;
74 }
75
fuzzyCompare(const QPolygonF & poly1,const QPolygonF & poly2)76 bool PolygonUtils::fuzzyCompare(const QPolygonF& poly1, const QPolygonF& poly2) {
77 if ((poly1.size() < 2) && (poly2.size() < 2)) {
78 return true;
79 } else if ((poly1.size() < 2) || (poly2.size() < 2)) {
80 return false;
81 }
82
83 assert(poly1.size() >= 2 && poly2.size() >= 2);
84
85 QPolygonF closed1(poly1);
86 QPolygonF closed2(poly2);
87 // Close if necessary.
88 if (closed1.back() != closed1.front()) {
89 closed1.push_back(closed1.front());
90 }
91 if (closed2.back() != closed2.front()) {
92 closed2.push_back(closed2.front());
93 }
94
95 std::vector<QLineF> edges1(extractAndNormalizeEdges(closed1));
96 std::vector<QLineF> edges2(extractAndNormalizeEdges(closed2));
97
98 if (edges1.size() != edges2.size()) {
99 return false;
100 }
101
102 std::sort(edges1.begin(), edges1.end(), Before());
103 std::sort(edges2.begin(), edges2.end(), Before());
104
105 return fuzzyCompareImpl(edges1, edges2);
106 } // PolygonUtils::fuzzyCompare
107
roundPoint(const QPointF & p)108 QPointF PolygonUtils::roundPoint(const QPointF& p) {
109 return QPointF(roundValue(p.x()), roundValue(p.y()));
110 }
111
roundValue(const double val)112 double PolygonUtils::roundValue(const double val) {
113 return std::floor(val * ROUNDING_MULTIPLIER + 0.5) * ROUNDING_RECIP_MULTIPLIER;
114 }
115
extractAndNormalizeEdges(const QPolygonF & poly)116 std::vector<QLineF> PolygonUtils::extractAndNormalizeEdges(const QPolygonF& poly) {
117 std::vector<QLineF> edges;
118
119 const int num_edges = poly.size();
120 if (num_edges > 1) {
121 for (int i = 1; i < num_edges; ++i) {
122 maybeAddNormalizedEdge(edges, poly[i - 1], poly[i]);
123 }
124 maybeAddNormalizedEdge(edges, poly[num_edges - 1], poly[0]);
125 }
126
127 return edges;
128 }
129
maybeAddNormalizedEdge(std::vector<QLineF> & edges,const QPointF & p1,const QPointF & p2)130 void PolygonUtils::maybeAddNormalizedEdge(std::vector<QLineF>& edges, const QPointF& p1, const QPointF& p2) {
131 if (fuzzyCompareImpl(p1, p2)) {
132 return;
133 }
134
135 if (Before()(p2, p1)) {
136 edges.emplace_back(p2, p1);
137 } else {
138 edges.emplace_back(p1, p2);
139 }
140 }
141
fuzzyCompareImpl(const std::vector<QLineF> & lines1,const std::vector<QLineF> & lines2)142 bool PolygonUtils::fuzzyCompareImpl(const std::vector<QLineF>& lines1, const std::vector<QLineF>& lines2) {
143 assert(lines1.size() == lines2.size());
144 const size_t size = lines1.size();
145 for (size_t i = 0; i < size; ++i) {
146 if (!fuzzyCompareImpl(lines1[i], lines2[i])) {
147 return false;
148 }
149 }
150
151 return true;
152 }
153
fuzzyCompareImpl(const QLineF & line1,const QLineF & line2)154 bool PolygonUtils::fuzzyCompareImpl(const QLineF& line1, const QLineF& line2) {
155 return fuzzyCompareImpl(line1.p1(), line2.p1()) && fuzzyCompareImpl(line1.p2(), line2.p2());
156 }
157
fuzzyCompareImpl(const QPointF & p1,const QPointF & p2)158 bool PolygonUtils::fuzzyCompareImpl(const QPointF& p1, const QPointF& p2) {
159 const double dx = std::fabs(p1.x() - p2.x());
160 const double dy = std::fabs(p1.y() - p2.y());
161
162 return dx <= ROUNDING_RECIP_MULTIPLIER && dy <= ROUNDING_RECIP_MULTIPLIER;
163 }
164
165 namespace {
166 struct LexicographicPointComparator {
operator ()imageproc::__anon9341eb2c0111::LexicographicPointComparator167 bool operator()(const QPointF& p1, const QPointF& p2) const {
168 if (p1.x() != p2.x()) {
169 return p1.x() < p2.x();
170 } else {
171 return p1.y() < p2.y();
172 }
173 }
174 };
175
176 // 2D cross product of OA and OB vectors, i.e. z-component of their 3D cross product.
177 // Returns a positive value, if OAB makes a counter-clockwise turn,
178 // negative for clockwise turn, and zero if the points are collinear.
cross(const QPointF & O,const QPointF & A,const QPointF & B)179 double cross(const QPointF& O, const QPointF& A, const QPointF& B) {
180 return (A.x() - O.x()) * (B.y() - O.y()) - (A.y() - O.y()) * (B.x() - O.x());
181 }
182 } // anonymous namespace
183
convexHull(std::vector<QPointF> point_cloud)184 QPolygonF PolygonUtils::convexHull(std::vector<QPointF> point_cloud) {
185 // "Monotone chain" algorithm.
186 // http://en.wikibooks.org/wiki/Algorithm_Implementation/Geometry/Convex_hull/Monotone_chain
187
188 const auto n = static_cast<const int>(point_cloud.size());
189 int k = 0;
190 std::vector<QPointF> hull(n * 2);
191
192 // Sort points by x, then y.
193 std::sort(point_cloud.begin(), point_cloud.end(), LexicographicPointComparator());
194
195 // Build lower hull.
196 for (int i = 0; i < n; ++i) {
197 while (k >= 2 && cross(hull[k - 2], hull[k - 1], point_cloud[i]) <= 0) {
198 k--;
199 }
200 hull[k++] = point_cloud[i];
201 }
202 // Build upper hull.
203 for (int i = n - 2, t = k + 1; i >= 0; --i) {
204 while (k >= t && cross(hull[k - 2], hull[k - 1], point_cloud[i]) <= 0) {
205 k--;
206 }
207 hull[k++] = point_cloud[i];
208 }
209
210 hull.resize(k);
211
212 QPolygonF poly(k);
213 for (const QPointF& pt : hull) {
214 poly << pt;
215 }
216
217 return poly;
218 }
219 } // namespace imageproc