1#include "internal/graph.h"
3#include "internal/bezier.h"
4#include "internal/douglas_peucker.h"
5#include "internal/Pixel.h"
6#include "internal/shared_contours.h"
21 static_cast<float>(a.red),
static_cast<float>(a.green),
static_cast<float>(a.blue)
24 static_cast<float>(b.red),
static_cast<float>(b.green),
static_cast<float>(b.blue)
27 (af.red - bf.red) * (af.red - bf.red) + (af.green - bf.green) * (af.green - bf.green) +
28 (af.blue - bf.blue) * (af.blue - bf.blue)
38void Graph::hash_node_ids() {
39 for (int32_t i {0}; i < m_nodes->size(); i++) {
40 const int32_t key {m_nodes->at(i)->id()};
45bool Graph::all_areas_bigger_than(int32_t min_area) {
46 for (
auto& n : *m_nodes) {
47 if (n->area() < min_area) {
55bool Graph::add_edge(int32_t node_id1, int32_t node_id2) {
56 auto end_node_ids {m_node_ids.end()};
57 auto node1_it {m_node_ids.find(node_id1)};
58 auto node2_it {m_node_ids.find(node_id2)};
60 if (node1_it == end_node_ids || node2_it == end_node_ids) {
64 const int32_t idx1 {node1_it->second};
65 const int32_t idx2 {node2_it->second};
67 m_nodes->at(idx1)->add_edge(m_nodes->at(idx2));
68 m_nodes->at(idx2)->add_edge(m_nodes->at(idx1));
72bool Graph::merge_nodes(
const Node_ptr& node_to_keep,
const Node_ptr& node_to_remove) {
73 auto end_node_ids {m_node_ids.end()};
74 auto node1_it {m_node_ids.find(node_to_keep->id())};
75 auto node2_it {m_node_ids.find(node_to_remove->id())};
77 if (node1_it == end_node_ids || node2_it == end_node_ids) {
81 const int32_t idx_k {node1_it->second};
82 const int32_t idx_r {node2_it->second};
85 for (Node_ptr n : m_nodes->at(idx_r)->edges()) {
86 if (n->id() != node_to_keep->id()) {
88 n->remove_edge(node_to_remove);
89 n->add_edge(node_to_keep);
90 node_to_keep->add_edge(n);
94 node_to_keep->add_pixels(node_to_remove->get_pixels());
96 node_to_remove->clear_all();
101void Graph::clear_unconnected_nodes() {
102 std::vector<Node_ptr>& nodes {*m_nodes};
106 nodes.begin(), nodes.end(), [](
const Node_ptr& n) { return n->area() == 0; }
114void Graph::discover_edges(
115 const std::vector<int32_t>& region_labels,
const int32_t width,
const int32_t height
118 constexpr int8_t dirs[8][2] {{1, 0}, {-1, 0}, {0, 1}, {0, -1},
119 {1, 1}, {-1, -1}, {-1, 1}, {1, -1}};
123 for (int32_t y {0}; y < height; ++y) {
124 for (int32_t x {0}; x < width; ++x) {
125 const int32_t idx {y * width + x};
126 const int32_t rid {region_labels[idx]};
128 for (int32_t k {0}; k < 8; ++k) {
129 const int32_t nx {x + dirs[k][0]};
130 const int32_t ny {y + dirs[k][1]};
132 if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
133 rneigh[k] = region_labels[ny * width + nx];
139 for (int32_t r : rneigh) {
148void Graph::process_overlapping_edges() {
150 std::vector<int32_t> label_map(
static_cast<size_t>(m_width) *
static_cast<size_t>(m_height), 0);
152 for (
const Node_ptr& n : get_nodes()) {
156 for (
auto& [_, p] : n->get_pixels()) {
157 label_map[p.y * m_width + p.x] = n->id();
161 constexpr int8_t dirs[8][2] {{1, 0}, {-1, 0}, {0, 1}, {0, -1},
162 {1, 1}, {-1, -1}, {-1, 1}, {1, -1}};
165 for (
const Node_ptr& n : get_nodes()) {
168 int32_t val = n->id();
170 for (
auto& [_, p] : n->get_pixels()) {
175 for (
int k = 0; k < 8; ++k) {
176 int nx = x + dirs[k][0];
177 int ny = y + dirs[k][1];
180 if (nx < 0 || nx >= m_width || ny < 0 || ny >= m_height)
183 int32_t n_val = label_map[ny * m_width + nx];
186 if (n_val != 0 && n_val != val && val < n_val) {
187 bool is_too_thin =
false;
190 for (
int mk = 0; mk < 8; ++mk) {
191 int mx = nx + dirs[mk][0];
192 int my = ny + dirs[mk][1];
194 if (mx < 0 || mx >= m_width || my < 0 || my >= m_height)
197 int32_t m_val = label_map[my * m_width + mx];
199 if (m_val != 0 && m_val != val && m_val != n_val) {
207 Node_ptr neighbor_node =
208 m_nodes->at(m_node_ids[n_val]);
211 neighbor_node->add_edge_pixel(
XY {x, y});
215 n->add_edge_pixel(
XY {nx, ny});
224 std::vector<uint8_t> junction_map(
static_cast<size_t>(w) * h, 0);
230 int dx[] = {0, 1, 1, 1, 0, -1, -1, -1};
231 int dy[] = {-1, -1, 0, 1, 1, 1, 0, -1};
233 for (
int y = 0; y < h; ++y) {
234 for (
int x = 0; x < w; ++x) {
235 if (
getPixel(skel, w, h, x, y) == 0)
240 for (
int k = 0; k < 8; ++k) {
241 p[k] =
getPixel(skel, w, h, x + dx[k], y + dy[k]) ? 1 : 0;
247 for (
int k = 0; k < 8; ++k) {
248 if (p[k] == 0 && p[(k + 1) % 8] == 1)
254 for (
int k = 0; k < 8; ++k)
258 if (transitions >= 3) {
259 junction_map[y * w + x] = 1;
266void Graph::compute_contours() {
276 std::vector<int32_t> labels(
static_cast<size_t>(m_width) * m_height, -1);
277 for (
const Node_ptr& n : get_nodes()) {
280 for (
auto& p : n->get_pixels())
281 labels[static_cast<size_t>(p.position.y) * m_width + p.position.x] = n->id();
284 auto loops = build_shared_loops(labels, m_width, m_height, eps);
286 for (
const Node_ptr& n : get_nodes()) {
290 auto it = loops.find(n->id());
291 if (it == loops.end())
295 for (std::vector<QuadBezier>& curve : it->second) {
296 std::vector<Point> anchors;
297 anchors.reserve(curve.size() + 1);
299 anchors.push_back(q.p0);
301 anchors.push_back(curve.back().p2);
302 n->m_contours.contours.push_back(std::move(anchors));
303 n->m_contours.curves.push_back(std::move(curve));
304 n->m_contours.colors.push_back(col);
305 n->m_contours.hierarchy.push_back({-1, -1, -1, -1});
306 n->m_contours.is_hole.push_back(
false);
315void dt_1d(
const std::vector<float>& f, std::vector<float>& d,
int n) {
316 constexpr float INF = 1e20f;
317 std::vector<int> v(n);
318 std::vector<float> z(n + 1);
323 for (
int q = 1; q < n; ++q) {
326 s = ((f[q] +
static_cast<float>(q) * q) - (f[v[k]] +
static_cast<float>(v[k]) * v[k])) /
327 (2.0f *
static_cast<float>(q - v[k]));
328 if (s <= z[k] && k > 0) {
340 for (
int q = 0; q < n; ++q) {
341 while (z[k + 1] <
static_cast<float>(q))
343 const float dq =
static_cast<float>(q - v[k]);
344 d[q] = dq * dq + f[v[k]];
353float max_inscribed_radius(
const Node_ptr& n) {
354 std::vector<uint8_t> mask;
355 const std::array<int, 4> xywh = n->create_binary_image(mask);
356 const int w = xywh[2];
357 const int h = xywh[3];
358 if (w <= 0 || h <= 0)
363 const int pw = w + 2;
364 const int ph = h + 2;
365 constexpr float INF = 1e20f;
367 std::vector<float> grid(
static_cast<size_t>(pw) * ph);
368 for (
int y = 0; y < ph; ++y) {
369 for (
int x = 0; x < pw; ++x) {
370 const bool inside = x >= 1 && x <= w && y >= 1 && y <= h &&
371 mask[
static_cast<size_t>(y - 1) * w + (x - 1)];
372 grid[
static_cast<size_t>(y) * pw + x] = inside ? INF : 0.0f;
377 std::vector<float> in, out(std::max(pw, ph));
379 for (
int x = 0; x < pw; ++x) {
380 for (
int y = 0; y < ph; ++y)
381 in[y] = grid[
static_cast<size_t>(y) * pw + x];
383 for (
int y = 0; y < ph; ++y)
384 grid[
static_cast<size_t>(y) * pw + x] = out[y];
388 for (
int y = 0; y < ph; ++y) {
389 for (
int x = 0; x < pw; ++x)
390 in[x] = grid[
static_cast<size_t>(y) * pw + x];
392 for (
int x = 0; x < pw; ++x)
396 return std::sqrt(max_d2);
401void Graph::merge_small_area_nodes(
const int32_t min_area,
const int32_t min_thickness) {
406 bool merged_any =
true;
410 for (
const Node_ptr& n : get_nodes()) {
414 bool needs_merge = n->area() <
static_cast<size_t>(min_area);
415 if (!needs_merge && min_thickness > 0) {
417 needs_merge = 2.0f * max_inscribed_radius(n) <
static_cast<float>(min_thickness);
424 Node_ptr best_neighbor =
nullptr;
425 float best_score = std::numeric_limits<float>::max();
426 for (
const Node_ptr& ne : n->edges()) {
427 if (ne->area() > 0) {
429 float score =
static_cast<float>(ne->area()) + 10.f * cdist;
430 if (score < best_score) {
438 if (!best_neighbor) {
442 if (best_neighbor->area() >= n->area()) {
443 merge_nodes(best_neighbor, n);
445 merge_nodes(n, best_neighbor);
450 clear_unconnected_nodes();
uint8_t getPixel(const std::vector< uint8_t > &img, int w, int h, int x, int y)
std::vector< uint8_t > analyzeJunctions(const std::vector< uint8_t > &skel, int w, int h)