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