Img2Num C++ (Internal Developer Docs) dev
API Documentation
Loading...
Searching...
No Matches
labels_to_svg.cpp
1#include "img2num.h"
2#include "internal/bezier.h"
3#include "internal/contours.h"
4#include "internal/graph.h"
5
6#include <array>
7#include <cmath>
8#include <cstdint>
9#include <cstdlib>
10#include <cstring>
11#include <ctime>
12#include <iomanip>
13#include <limits>
14#include <map>
15#include <memory>
16#include <queue>
17#include <random>
18#include <set>
19#include <sstream>
20#include <vector>
21
22/* Flood fill */
23int flood_fill(
24 std::vector<int32_t>& label_array, std::vector<int32_t>& region_array,
25 const uint8_t* color_array, int x, int y, int target_value, int label_value, size_t width,
26 size_t height, std::unique_ptr<std::vector<RGBXY>>& out_pixels
27) {
28 std::queue<XY> queue;
29 auto index = [width](int x, int y) {
30 return y * width + x;
31 };
32
33 int count = 0;
34 int dirs[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
35
36 RGBXY pix = RGBXY {
37 color_array[4 * size_t(index(x, y))], color_array[4 * size_t(index(x, y)) + 1],
38 color_array[4 * size_t(index(x, y)) + 2], x, y};
39
40 queue.push({x, y});
41
42 region_array[size_t(index(x, y))] = label_value;
43
44 out_pixels->push_back(pix);
45 count++;
46
47 while (!queue.empty()) {
48 XY c = queue.front();
49 queue.pop();
50 for (auto& d : dirs) {
51 int x1 = c.x + d[0];
52 int y1 = c.y + d[1];
53
54 // check conditions
55 if ((x1 >= 0) && (x1 < int(width)) && (y1 >= 0) && (y1 < int(height)) &&
56 (label_array[size_t(index(x1, y1))] == target_value) &&
57 (region_array[size_t(index(x1, y1))] == -1)) {
58 RGBXY pix1 = RGBXY {
59 color_array[4 * size_t(index(x1, y1))],
60 color_array[4 * size_t(index(x1, y1)) + 1],
61 color_array[4 * size_t(index(x1, y1)) + 2], x1, y1};
62 region_array[size_t(index(x1, y1))] = label_value;
63 out_pixels->push_back(pix1);
64 count++;
65
66 queue.push({x1, y1});
67 }
68 }
69 }
70
71 return count;
72}
73
74void region_labeling(
75 const uint8_t* data, std::vector<int32_t>& labels, std::vector<int32_t>& regions, int width,
76 int height, std::vector<Node_ptr>& nodes
77) {
78 auto index = [width](int x, int y) {
79 return y * width + x;
80 };
81
82 regions.resize(static_cast<size_t>(height) * static_cast<size_t>(width), -1);
83 int r_lbl = -1;
84
85 for (int i = 0; i < width; i++) {
86 for (int j = 0; j < height; j++) {
87 int label {labels[size_t(index(i, j))]};
88 int rlab {regions[size_t(index(i, j))]};
89
90 if (rlab == -1) {
91 r_lbl++;
92 // track pixels for this region
93 // std::vector<RGBXY> pixels;
94 std::unique_ptr<std::vector<RGBXY>> p_ptr = std::make_unique<std::vector<RGBXY>>();
95 int counts =
96 flood_fill(labels, regions, data, i, j, label, r_lbl, width, height, p_ptr);
97 int num_pixels = p_ptr->size();
98
99 // num_pixels == counts always
100 Node_ptr n_ptr = std::make_shared<Node>(r_lbl, p_ptr);
101 nodes.push_back(n_ptr);
102 }
103 }
104 }
105}
106
107void visualize_contours(
108 const std::vector<std::vector<Point>>& contours,
109 ImageLib::Image<ImageLib::RGBAPixel<uint8_t>>& results, int width, int height, int xmin = 0,
110 int ymin = 0
111) {
112 // Random generator for colors
113 static std::mt19937 rng(std::random_device {}());
114 static std::uniform_int_distribution<int32_t> dist(0, 255);
115 for (const auto& c : contours) {
117 static_cast<uint8_t>(dist(rng)), static_cast<uint8_t>(dist(rng)),
118 static_cast<uint8_t>(dist(rng)), 255};
119
120 for (const auto& p : c) {
121 int32_t _x {static_cast<int32_t>(p.x) + xmin};
122 int32_t _y {static_cast<int32_t>(p.y) + ymin};
123
124 // Ensure within bounds
125 if (_x < 0 || _x >= width || _y < 0 || _y >= height)
126 continue;
127
128 results(_x, _y) = rand_color;
129 }
130 }
131}
132
133std::string contourToSVGPath(const std::vector<Point>& contour) {
134 if (contour.empty())
135 return "";
136
137 std::ostringstream path;
138 path << std::fixed << std::setprecision(2);
139
140 // Move to the first point
141 path << "M " << contour[0].x << " " << contour[0].y << " ";
142
143 // Draw lines to the remaining points
144 for (size_t i = 1; i < contour.size(); ++i) {
145 path << "L " << contour[i].x << " " << contour[i].y << " ";
146 }
147
148 // Close the path
149 path << "Z";
150 return path.str();
151}
152
153std::string contourToSVGCurve(const std::vector<QuadBezier>& curves) {
154 if (curves.empty())
155 return "";
156
157 std::ostringstream path;
158 path << std::fixed << std::setprecision(2);
159
160 for (size_t i = 0; i < curves.size(); ++i) {
161 const auto& c = curves[i];
162 if (i == 0)
163 path << "M " << c.p0.x << " " << c.p0.y << " ";
164 path << "Q " << c.p1.x << " " << c.p1.y << " " << c.p2.x << " " << c.p2.y << " ";
165 }
166
167 // Close the path
168 path << "Z";
169 return path.str();
170}
171
172std::string contoursResultToSVG(const ColoredContours& result, const int width, const int height) {
173 std::ostringstream svg;
174 svg << "<svg xmlns=\"http://www.w3.org/2000/svg\" fill-rule=\"evenodd\" "
175 "width=\""
176 << width << "\" height=\"" << height << "\">\n";
177
178 for (size_t i = 0; i < result.curves.size(); ++i) {
179 std::string pathData = contourToSVGCurve(result.curves[i]);
180
181 const auto& px = result.colors[i];
182 std::ostringstream oss;
183 oss << "#" << std::hex << std::uppercase << std::setw(2) << std::setfill('0')
184 << static_cast<int>(px.red) << std::setw(2) << std::setfill('0')
185 << static_cast<int>(px.green) << std::setw(2) << std::setfill('0')
186 << static_cast<int>(px.blue);
187
188 // You can optionally style holes differently or rely on fill-rule
189 svg << " <path d=\"" << pathData << "\" fill=\"" << oss.str() << "\" />\n";
190 }
191
192 svg << "</svg>\n";
193 return svg.str();
194}
195
196namespace img2num {
197/*
198data: uint8_t* -> output image from K-Means (or similar) in RGBA repeating
199format ([r,g,b,a, r,g,b,a, ...]) labels: int32_t* -> output of labelled regions
200from K-Means, should be 1/4 the size of data since data is RGBA labels : width *
201height : number of pixels in image = 1 : 1 : 1
202*/
203std::string labels_to_svg(
204 const uint8_t* data, const int32_t* labels, const int width, const int height,
205 const int min_area, const int min_thickness = 0
206) {
207 const int32_t num_pixels {width * height};
208 std::vector<int32_t> labels_vector {labels, labels + num_pixels};
209 std::vector<int32_t> region_labels;
210
211 // 1. enumerate regions and convert to Nodes
212 std::vector<Node_ptr> nodes;
213 region_labeling(data, labels_vector, region_labels, width, height, nodes);
214
215 // 2. initialize Graph from all Nodes
216 std::unique_ptr<std::vector<Node_ptr>> node_ptr =
217 std::make_unique<std::vector<Node_ptr>>(std::move(nodes));
218 Graph G(node_ptr, width, height);
219
220 // 3. Discover node adjacencies - add edges to Graph
221 G.discover_edges(region_labels, width, height);
222
223 // 4. Merge small area nodes until all nodes are minArea or larger
224 G.merge_small_area_nodes(min_area, min_thickness);
225
226 // 5. recolor image on new regions
227 ImageLib::Image<ImageLib::RGBAPixel<uint8_t>> results {width, height};
228 for (auto& n : G.get_nodes()) {
229 if (n->area() == 0)
230 continue;
231
232 auto [r, g, b] = n->color();
233 for (auto& [_, p] : n->get_pixels()) {
234 results(p.x, p.y) = {r, g, b};
235 }
236 }
237
238 // 6. Contours
239 // graph will manage computing contours
240 G.compute_contours();
241
242 // accumulate all contours for svg export
243 ColoredContours all_contours;
244 for (auto& n : G.get_nodes()) {
245 if (n->area() == 0)
246 continue;
247 ColoredContours node_contours = n->get_contours();
248 for (auto& c : node_contours.contours) {
249 all_contours.contours.push_back(c);
250 }
251 for (auto& c : node_contours.hierarchy) {
252 all_contours.hierarchy.push_back(c);
253 }
254 for (bool b : node_contours.is_hole) {
255 all_contours.is_hole.push_back(b);
256 }
257 for (auto& c : node_contours.colors) {
258 all_contours.colors.push_back(c);
259 }
260 for (auto& c : node_contours.curves) {
261 all_contours.curves.push_back(c);
262 }
263 }
264
265 // 7. Return SVG
266 return contoursResultToSVG(all_contours, width, height);
267}
268} // namespace img2num
Definition graph.h:33
Core image processing functions for img2num project.
std::string labels_to_svg(const uint8_t *data, const int32_t *labels, const int width, const int height, const int min_area, const int min_thickness)
Convert labeled regions of an image into an SVG string.
Definition node.h:51
Definition node.h:38