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