1 #include "internal/graph.h"
8 #include "internal/Pixel.h"
9 #include "internal/bezier.h"
17 static_cast<float>(a.blue)};
19 static_cast<float>(b.blue)};
20 return std::sqrt((af.red - bf.red) * (af.red - bf.red) +
21 (af.green - bf.green) * (af.green - bf.green) +
22 (af.blue - bf.blue) * (af.blue - bf.blue));
31 void Graph::hash_node_ids() {
32 for (int32_t i{0}; i < m_nodes->size(); i++) {
33 const int32_t key{m_nodes->at(i)->id()};
38 bool Graph::all_areas_bigger_than(int32_t min_area) {
39 for (
auto &n : *m_nodes) {
40 if (n->area() < min_area) {
48 bool Graph::add_edge(int32_t node_id1, int32_t node_id2) {
49 auto end_node_ids{m_node_ids.end()};
50 auto node1_it{m_node_ids.find(node_id1)};
51 auto node2_it{m_node_ids.find(node_id2)};
53 if (node1_it == end_node_ids || node2_it == end_node_ids) {
57 const int32_t idx1{node1_it->second};
58 const int32_t idx2{node2_it->second};
60 m_nodes->at(idx1)->add_edge(m_nodes->at(idx2));
61 m_nodes->at(idx2)->add_edge(m_nodes->at(idx1));
65 bool Graph::merge_nodes(
const Node_ptr &node_to_keep,
const Node_ptr &node_to_remove) {
66 auto end_node_ids{m_node_ids.end()};
67 auto node1_it{m_node_ids.find(node_to_keep->id())};
68 auto node2_it{m_node_ids.find(node_to_remove->id())};
70 if (node1_it == end_node_ids || node2_it == end_node_ids) {
74 const int32_t idx_k{node1_it->second};
75 const int32_t idx_r{node2_it->second};
78 for (Node_ptr n : m_nodes->at(idx_r)->edges()) {
79 if (n->id() != node_to_keep->id()) {
81 n->remove_edge(node_to_remove);
82 n->add_edge(node_to_keep);
83 node_to_keep->add_edge(n);
87 node_to_keep->add_pixels(node_to_remove->get_pixels());
89 node_to_remove->clear_all();
94 void Graph::clear_unconnected_nodes() {
95 std::vector<Node_ptr> &nodes{*m_nodes};
97 nodes.erase(std::remove_if(nodes.begin(), nodes.end(),
98 [](
const Node_ptr &n) { return n->area() == 0; }),
104 void Graph::discover_edges(
const std::vector<int32_t> ®ion_labels,
const int32_t width,
105 const int32_t height) {
107 constexpr int8_t dirs[8][2]{{1, 0}, {-1, 0}, {0, 1}, {0, -1},
108 {1, 1}, {-1, -1}, {-1, 1}, {1, -1}};
112 for (int32_t y{0}; y < height; ++y) {
113 for (int32_t x{0}; x < width; ++x) {
114 const int32_t idx{y * width + x};
115 const int32_t rid{region_labels[idx]};
117 for (int32_t k{0}; k < 8; ++k) {
118 const int32_t nx{x + dirs[k][0]};
119 const int32_t ny{y + dirs[k][1]};
121 if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
122 rneigh[k] = region_labels[ny * width + nx];
128 for (int32_t r : rneigh) {
137 void Graph::compute_contours() {
141 std::set<std::pair<int, int>> adjusted_neighbors{};
143 constexpr int8_t dirs[8][2]{{1, 0}, {-1, 0}, {0, 1}, {0, -1},
144 {1, 1}, {-1, -1}, {-1, 1}, {1, -1}};
146 for (
const Node_ptr &n : get_nodes()) {
147 if (n->area() == 0)
continue;
149 std::vector<std::vector<uint8_t>> full_neighborhood;
150 std::vector<std::array<int32_t, 4>> xywh;
152 std::vector<uint8_t> node_binary;
153 std::array<int32_t, 4> xywh0 = n->create_binary_image(node_binary);
155 full_neighborhood.push_back(node_binary);
156 xywh.push_back(xywh0);
158 std::vector<Node_ptr> considered_neigbors;
159 for (
const auto &neighbor : n->edges()) {
160 if (neighbor->area() == 0)
continue;
163 std::pair<int, int> id1 = std::make_pair(n->id(), neighbor->id());
164 std::pair<int, int> id2 = std::make_pair(neighbor->id(), n->id());
165 auto _end{adjusted_neighbors.end()};
166 auto _it1{adjusted_neighbors.find(id1)};
167 auto _it2{adjusted_neighbors.find(id2)};
169 if (_it1 != _end || _it2 != _end) {
173 considered_neigbors.push_back(neighbor);
175 std::vector<uint8_t> neighbor_binary;
176 std::array<int32_t, 4> xywh1 = neighbor->create_binary_image(neighbor_binary);
177 full_neighborhood.push_back(neighbor_binary);
178 xywh.push_back(xywh1);
180 adjusted_neighbors.insert(id1);
184 std::vector<uint8_t> neighborhood;
185 std::array<int32_t, 4> bounds = {std::numeric_limits<int>::max(),
186 std::numeric_limits<int>::max(), -1, -1};
187 for (
auto &_xywh : xywh) {
188 if (_xywh[0] < bounds[0]) {
189 bounds[0] = _xywh[0];
191 if (_xywh[1] < bounds[1]) {
192 bounds[1] = _xywh[1];
194 if (_xywh[2] + _xywh[0] - 1 > bounds[2]) {
195 bounds[2] = _xywh[2] + _xywh[0] - 1;
197 if (_xywh[3] + _xywh[1] - 1 > bounds[3]) {
198 bounds[3] = _xywh[3] + _xywh[1] - 1;
202 bounds[2] = bounds[2] - bounds[0] + 1;
203 bounds[3] = bounds[3] - bounds[1] + 1;
207 static_cast<std::size_t
>(bounds[2]) *
static_cast<std::size_t
>(bounds[3]), 0);
209 for (
int i = 0; i < full_neighborhood.size(); ++i) {
210 for (
int y = 0; y < xywh[i][3]; ++y) {
211 for (
int x = 0; x < xywh[i][2]; ++x) {
212 int global_y = y + xywh[i][1] - bounds[1];
213 int global_x = x + xywh[i][0] - bounds[0];
214 uint8_t val = full_neighborhood[i][y * xywh[i][2] + x];
216 neighborhood[global_y * bounds[2] + global_x] = (i + 1);
225 for (
int y = 0; y < bounds[3]; ++y) {
226 for (
int x = 0; x < bounds[2]; ++x) {
227 uint8_t val = neighborhood[y * bounds[2] + x];
230 for (int32_t k{0}; k < 8; ++k) {
231 int32_t nx{x + dirs[k][0]};
232 int32_t ny{y + dirs[k][1]};
233 nx = std::clamp(nx, 0, bounds[2] - 1);
234 ny = std::clamp(ny, 0, bounds[3] - 1);
235 uint8_t n_val = neighborhood[ny * bounds[2] + nx];
236 if ((n_val != val) & (n_val != 0)) {
238 bool is_too_thin =
false;
241 for (int32_t k{0}; k < 8; ++k) {
242 int32_t mx{nx + dirs[k][0]};
243 int32_t my{ny + dirs[k][1]};
244 mx = std::clamp(mx, 0, bounds[2] - 1);
245 my = std::clamp(my, 0, bounds[3] - 1);
246 uint8_t m_val = neighborhood[my * bounds[2] + mx];
248 if ((m_val != val) & (m_val != n_val)) {
254 considered_neigbors[n_val - 2]->add_edge_pixel(
255 XY{x + bounds[0], y + bounds[1]});
257 n->add_edge_pixel(
XY{nx + bounds[0], ny + bounds[1]});
267 for (
const Node_ptr &n : get_nodes()) {
268 if (n->area() == 0)
continue;
269 n->compute_contour();
273 std::vector<std::vector<Point>> all_contours;
274 for (
const Node_ptr &n : get_nodes()) {
275 if (n->area() == 0)
continue;
278 for (
size_t i = 0; i < c0->contours.size(); ++i) {
279 all_contours.push_back(c0->contours[i]);
283 contours::coupled_smooth(
284 all_contours,
Rect{0.0f, 0.0f,
static_cast<float>(m_width),
static_cast<float>(m_height)});
286 std::vector<std::vector<QuadBezier>> all_curves;
287 fit_curve_reduction(all_contours, all_curves, 0.5f);
290 for (
const Node_ptr &n : get_nodes()) {
291 if (n->area() == 0)
continue;
294 for (
size_t i = 0; i < c0->contours.size(); ++i) {
295 std::copy(all_contours[j].begin(), all_contours[j].end(), c0->contours[i].begin());
297 c0->curves[i].resize(all_curves[j].size());
298 std::copy(all_curves[j].begin(), all_curves[j].end(), c0->curves[i].begin());
304 void Graph::merge_small_area_nodes(
const int32_t min_area) {
306 while (!all_areas_bigger_than(min_area)) {
307 for (
const Node_ptr &n : get_nodes()) {
308 if (n->area() < min_area) {
309 std::vector<Node_ptr> neighbors;
310 neighbors.reserve(n->num_edges());
311 std::copy(n->edges().begin(), n->edges().end(), std::back_inserter(neighbors));
315 std::sort(neighbors.begin(), neighbors.end(), [col](Node_ptr a, Node_ptr b) {
316 float cdista = ImageLib::RGBPixel<uint8_t>::colorDistance(a->color(), col);
317 float cdistb = ImageLib::RGBPixel<uint8_t>::colorDistance(b->color(), col);
318 return (static_cast<float>(a->area()) + 10.f * cdista) <
319 (static_cast<float>(b->area()) + 10.f * cdistb);
324 for (Node_ptr &ne : neighbors) {
325 if (ne->area() > 0) {
332 if (idx >=
static_cast<int32_t
>(neighbors.size())) {
336 if (neighbors[idx]->area() >= n->area()) {
337 merge_nodes(neighbors[idx], n);
339 merge_nodes(n, neighbors[idx]);
344 clear_unconnected_nodes();