Extract outer contours from a binary mask.
Uses the Suzuki-Abe border following algorithm (single pass). Only outer contours are extracted; holes are not traced. Each contour is a closed polygon; the last point connects back to the first.
Contours with fewer than 3 points are discarded. Contours whose normalised area is below min_area are discarded after tracing. If max_contours is non-zero, only the largest max_contours contours by area are returned; excess contours are dropped before the result is returned.
73{
75 if (cc.count == 0)
76 return {};
77
78 std::vector<Contour> result(cc.count);
79 std::vector<uint8_t> valid(cc.count, 0);
80
81 P::for_each(P::par_unseq,
82 std::views::iota(uint32_t { 0 }, cc.count).begin(),
83 std::views::iota(uint32_t { 0 }, cc.count).end(),
84 [&](uint32_t ci) {
85 const uint32_t lbl = ci + 1;
86 const auto& box = cc.boxes[ci];
87
88 const auto x0 = static_cast<uint32_t>(box.x * static_cast<float>(w));
89 const auto y0 =
static_cast<uint32_t
>(box.y *
static_cast<float>(
h));
90 const auto x1 = std::min(x0 + static_cast<uint32_t>(box.w * static_cast<float>(w)) - 1U, w - 1);
91 const auto y1 = std::min(y0 +
static_cast<uint32_t
>(box.h *
static_cast<float>(
h)) - 1U,
h - 1);
92
93 uint32_t start_x = 0, start_y = 0;
94 bool found = false;
95 for (uint32_t py = y0; py <= y1 && !found; ++py) {
96 for (uint32_t px = x0; px <= x1 && !found; ++px) {
97 if (cc.label_map[static_cast<size_t>(py) * w + px] == lbl) {
98 start_x = px;
99 start_y = py;
100 found = true;
101 }
102 }
103 }
104
105 if (!found)
106 return;
107
108 const uint32_t bw = x1 - x0 + 1;
109 const uint32_t bh = y1 - y0 + 1;
110
111 if (bw < 2 || bh < 2)
112 return;
113
114 std::vector<uint8_t> visited(static_cast<size_t>(bw) * bh, 0);
115
116 auto is_component_fg = [&](int32_t px, int32_t py) -> bool {
117 if (px < 0 || py < 0
118 || static_cast<uint32_t>(px) >= w
119 ||
static_cast<uint32_t
>(py) >=
h)
120 return false;
121 return cc.label_map[static_cast<size_t>(py) * w + px] == lbl;
122 };
123
124 auto mark_visited = [&](int32_t px, int32_t py) {
125 if (static_cast<uint32_t>(px) < x0 || static_cast<uint32_t>(py) < y0
126 || static_cast<uint32_t>(px) > x1 || static_cast<uint32_t>(py) > y1)
127 return;
128 const uint32_t lx = static_cast<uint32_t>(px) - x0;
129 const uint32_t ly = static_cast<uint32_t>(py) - y0;
130 visited[static_cast<size_t>(ly) * bw + lx] = 1;
131 };
132
133 auto cx = static_cast<int32_t>(start_x);
134 auto cy = static_cast<int32_t>(start_y);
135
136 int32_t start_dir = 0;
137 for (int32_t d = 0; d < 8; ++d) {
138 const int32_t nx = cx + dx8[d];
139 const int32_t ny = cy + dy8[d];
140 if (!is_component_fg(nx, ny)) {
141 start_dir = d;
142 break;
143 }
144 }
145
146 const int32_t orig_x = cx;
147 const int32_t orig_y = cy;
148 int32_t dir = start_dir;
149 bool first = true;
150 std::vector<glm::vec2>
points;
151
152 while (true) {
153 bool step_found = false;
154 for (int32_t i = 0; i < 8; ++i) {
155 const int32_t d = (dir + 6 + i) % 8;
156 const int32_t nx = cx + dx8[d];
157 const int32_t ny = cy + dy8[d];
158
159 if (!is_component_fg(nx, ny))
160 continue;
161
163 static_cast<float>(cx) / static_cast<float>(w),
164 static_cast<float>(cy) /
static_cast<float>(
h));
165 mark_visited(cx, cy);
166 cx = nx;
167 cy = ny;
168 dir = d;
169 step_found = true;
170 break;
171 }
172
173 if (!step_found)
174 break;
175 if (!first && cx == orig_x && cy == orig_y)
176 break;
177 first = false;
178 if (
points.size() >
static_cast<size_t>(w) *
h)
179 break;
180 }
181
183 return;
184
185 const float area = polygon_area(
points);
187 return;
188
189 result[ci] = { .points = std::move(
points), .area = area, .perimeter = polygon_perimeter(
points) };
190 valid[ci] = 1;
191 });
192
193 std::vector<Contour> out;
194 out.reserve(cc.count);
195 for (uint32_t i = 0; i < cc.count; ++i) {
196 if (valid[i])
197 out.push_back(std::move(result[i]));
198 }
199
201 std::partial_sort(out.begin(),
203 out.end(),
204 [](
const Contour&
a,
const Contour&
b) { return a.area > b.area; });
206 }
207
208 return out;
209}
std::vector< glm::vec2 > * points
ComponentResult connected_components(std::span< const float > mask, uint32_t w, uint32_t h)
Label connected foreground components in a binary mask.