Extract contours from an existing CPU component labeling.
Applies the same tracing, filtering, and measurement rules as mask-based extraction without repeating connected-component labeling.
125{
126 if (w == 0 ||
h == 0 || cc.count == 0)
127 return {};
128
129 const size_t n =
static_cast<size_t>(w) *
h;
130 std::vector<TraceSeed> seeds(cc.count, TraceSeed { .pixel = n, .label = 0, .parent = 0 });
131 for (size_t i = 0; i < n; ++i) {
132 const uint32_t
label = cc.label_map[i];
134 seeds[
label - 1] = { .pixel = i, .label =
label, .parent = 0 };
135 }
136
137 std::vector<uint32_t> background(n, 0);
138 std::vector<size_t> pending;
139 uint32_t region = 0;
140 for (size_t seed = 0; seed < n; ++seed) {
141 if (cc.label_map[seed] != 0 || background[seed] != 0)
142 continue;
143
144 ++region;
145 pending.clear();
146 pending.push_back(seed);
147 background[seed] = region;
148 bool exterior = false;
149 for (size_t head = 0; head < pending.size(); ++head) {
150 const size_t pixel = pending[head];
151 const auto x =
static_cast<uint32_t
>(
pixel % w);
152 const auto y =
static_cast<uint32_t
>(
pixel / w);
153 exterior = exterior || x == 0 || y == 0 || x + 1 == w || y + 1 ==
h;
154
155 const auto visit = [&](size_t neighbor) {
156 if (cc.label_map[neighbor] == 0 && background[neighbor] == 0) {
157 background[neighbor] = region;
158 pending.push_back(neighbor);
159 }
160 };
161 if (x > 0)
163 if (x + 1 < w)
165 if (y > 0)
169 }
170
171 if (!exterior) {
172 const uint32_t
parent = cc.label_map[seed - w];
173 seeds.push_back({ .pixel = seed, .label = region, .parent =
parent });
174 }
175 }
176
177 std::vector<Contour> result(seeds.size());
178 std::vector<uint8_t> valid(seeds.size(), 0);
179 const size_t max_points = max_points_per_contour > 0
180 ? static_cast<size_t>(max_points_per_contour)
181 : n * 8;
182
183 std::for_each(std::execution::par_unseq,
184 std::views::iota(size_t { 0 }, seeds.size()).begin(),
185 std::views::iota(size_t { 0 }, seeds.size()).end(),
186 [&](size_t ci) {
187 const auto& seed = seeds[ci];
188 const glm::ivec2 start(static_cast<int32_t>(seed.pixel % w), static_cast<int32_t>(seed.pixel / w));
189 const auto inside = [&](glm::ivec2 p) {
190 if (p.x < 0 || p.y < 0 ||
static_cast<uint32_t
>(p.x) >= w ||
static_cast<uint32_t
>(p.y) >=
h)
191 return false;
192 const size_t index =
static_cast<size_t>(p.y) * w +
static_cast<uint32_t
>(p.x);
194 };
195 const auto points = walk_boundary(start, max_points, inside);
197 return;
198
199 auto contour = measure_contour(
points, w,
h, seed.parent);
200 if (contour.area < min_area)
201 return;
202
203 result[ci] = std::move(contour);
204 valid[ci] = 1;
205 });
206
207 std::vector<Contour>
out;
208 out.reserve(seeds.size());
209 for (size_t i = 0; i < seeds.size(); ++i) {
210 if (valid[i])
211 out.push_back(std::move(result[i]));
212 }
213
214 if (max_contours > 0) {
215 const size_t count = std::min(
out.size(),
static_cast<size_t>(max_contours));
216 std::partial_sort(
out.begin(),
217 out.begin() +
static_cast<ptrdiff_t
>(
count),
219 [](
const Contour&
a,
const Contour&
b) { return a.area > b.area; });
221 }
222
224}
std::vector< glm::vec2 > * points
std::vector< float > * out