Img2Num C++ (Internal Developer Docs) dev
API Documentation
Loading...
Searching...
No Matches
douglas_peucker.cpp
1#include "internal/douglas_peucker.h"
2
3#include <algorithm>
4#include <cmath>
5#include <cstdlib>
6#include <vector>
7
8// --- Signed perpendicular distance of P from the directed line A->B ---
9// Positive = P lies to the "left" of the A->B travel direction (using the
10// n = (-dy, dx) normal). Falls back to the point distance for a zero-length AB.
11static inline float signed_left_dist(const Point& A, const Point& B, const Point& P) {
12 const float dx = B.x - A.x, dy = B.y - A.y;
13 const float l = std::sqrt(dx * dx + dy * dy);
14 if (l < 1e-9f)
15 return std::sqrt(Point::distSq(P, A));
16 return ((P.y - A.y) * dx - (P.x - A.x) * dy) / l;
17}
18
51static void dp_reduce(
52 const std::vector<Point>& pts, int lo, int hi, float eps, float retract_eps,
53 float interior_sign, std::vector<uint8_t>& keep
54) {
55 if (hi - lo < 2)
56 return; // no interior vertices to drop
57
58 const Point& A = pts[lo];
59 const Point& B = pts[hi];
60
61 float max_abs_dev = 0.0f; // worst |deviation| -> fidelity / split choice
62 float max_retract = 0.0f; // worst inward move -> watertightness guard
63 int split = lo;
64 for (int k = lo + 1; k < hi; ++k) {
65 const float d = signed_left_dist(A, B, pts[k]);
66 const float adev = std::fabs(d);
67 if (adev > max_abs_dev) {
68 max_abs_dev = adev;
69 split = k;
70 }
71 // interior_sign * d > 0 => vertex is interior (concave, safe to flatten);
72 // < 0 => vertex is exterior (convex), dropping it retracts the boundary.
73 const float retract = -interior_sign * d;
74 if (retract > max_retract)
75 max_retract = retract;
76 }
77
78 if (max_abs_dev <= eps && max_retract <= retract_eps)
79 return; // drop the run
80
81 keep[split] = 1;
82 dp_reduce(pts, lo, split, eps, retract_eps, interior_sign, keep);
83 dp_reduce(pts, split, hi, eps, retract_eps, interior_sign, keep);
84}
85
104void dp_curve_reduction(
105 const std::vector<std::vector<Point>>& chains, const std::vector<std::vector<uint8_t>>& fixed,
106 std::vector<std::vector<QuadBezier>>& results, float eps
107) {
108 float retract_eps = std::min(eps, 0.5f);
109
110 for (size_t i = 0; i < chains.size(); ++i) {
111 const std::vector<Point>& chain = chains[i];
112 const int n = static_cast<int>(chain.size());
113 std::vector<QuadBezier> result;
114 if (n < 2) {
115 results.push_back(result);
116 continue;
117 }
118
119 // Orientation: sign of the shoelace area decides which side of a chord is
120 // the region interior (see dp_reduce). Computed over the closed chain.
121 double sa = 0.0;
122 for (int k = 0; k < n; ++k) {
123 const Point& p = chain[k];
124 const Point& q = chain[(k + 1) % n];
125 sa += static_cast<double>(p.x) * q.y - static_cast<double>(q.x) * p.y;
126 }
127 const float interior_sign = (sa > 0.0) ? 1.0f : -1.0f;
128
129 // Segment boundaries: chain ends plus every interior fixed point. DP runs
130 // inside each [bounds[s], bounds[s+1]] span with its ends pinned.
131 std::vector<int> bounds;
132 bounds.push_back(0);
133 for (int k = 1; k < n - 1; ++k)
134 if (k < static_cast<int>(fixed[i].size()) && fixed[i][k])
135 bounds.push_back(k);
136 bounds.push_back(n - 1);
137
138 std::vector<uint8_t> keep(n, 0);
139 for (int b : bounds)
140 keep[b] = 1;
141 for (size_t s = 0; s + 1 < bounds.size(); ++s)
142 dp_reduce(chain, bounds[s], bounds[s + 1], eps, retract_eps, interior_sign, keep);
143
144 // Emit kept points in order as straight-line quads.
145 int prev = -1;
146 for (int k = 0; k < n; ++k) {
147 if (!keep[k])
148 continue;
149 if (prev >= 0) {
150 const Point& P = chain[prev];
151 const Point& Q = chain[k];
152 result.push_back({P, (P + Q) * 0.5f, Q});
153 }
154 prev = k;
155 }
156 results.push_back(result);
157 }
158}
Definition Point.h:5