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