18 #include "internal/bezier.h"
19 #include "internal/contours.h"
20 #include "internal/graph.h"
23 int flood_fill(std::vector<int32_t> &label_array, std::vector<int32_t> ®ion_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) {
27 auto index = [width](
int x,
int y) {
return y * width + x; };
30 int dirs[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
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};
38 region_array[size_t(index(x, y))] = label_value;
40 out_pixels->push_back(pix);
43 while (!queue.empty()) {
46 for (
auto &d : dirs) {
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);
69 void region_labeling(
const uint8_t *data, std::vector<int32_t> &labels,
70 std::vector<int32_t> ®ions,
int width,
int height,
71 std::vector<Node_ptr> &nodes) {
72 auto index = [width](
int x,
int y) {
return y * width + x; };
74 regions.resize(
static_cast<size_t>(height) *
static_cast<size_t>(width), -1);
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))]};
86 std::unique_ptr<std::vector<RGBXY>> p_ptr = std::make_unique<std::vector<RGBXY>>();
88 flood_fill(labels, regions, data, i, j, label, r_lbl, width, height, p_ptr);
89 int num_pixels = p_ptr->size();
92 Node_ptr n_ptr = std::make_shared<Node>(r_lbl, p_ptr);
93 nodes.push_back(n_ptr);
99 void visualize_contours(
const std::vector<std::vector<Point>> &contours,
101 int height,
int xmin = 0,
int ymin = 0) {
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) {
107 static_cast<uint8_t
>(dist(rng)),
108 static_cast<uint8_t
>(dist(rng)), 255};
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};
115 if (_x < 0 || _x >= width || _y < 0 || _y >= height)
continue;
117 results(_x, _y) = rand_color;
122 std::string contourToSVGPath(
const std::vector<Point> &contour) {
123 if (contour.empty())
return "";
125 std::ostringstream path;
126 path << std::fixed << std::setprecision(2);
129 path <<
"M " << contour[0].x <<
" " << contour[0].y <<
" ";
132 for (
size_t i = 1; i < contour.size(); ++i) {
133 path <<
"L " << contour[i].x <<
" " << contour[i].y <<
" ";
141 std::string contourToSVGCurve(
const std::vector<QuadBezier> &curves) {
142 if (curves.empty())
return "";
144 std::ostringstream path;
145 path << std::fixed << std::setprecision(2);
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 <<
" ";
158 std::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\" "
162 << width <<
"\" height=\"" << height <<
"\">\n";
164 for (
size_t i = 0; i < result.curves.size(); ++i) {
165 std::string pathData = contourToSVGCurve(result.curves[i]);
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);
175 svg <<
" <path d=\"" << pathData <<
"\" fill=\"" << oss.str() <<
"\" />\n";
189 char *
labels_to_svg(uint8_t *data, int32_t *labels,
const int width,
const int height,
190 const int min_area,
const bool draw_contour_borders) {
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;
196 std::vector<Node_ptr> nodes;
197 region_labeling(data, labels_vector, region_labels, width, height, 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);
205 G.discover_edges(region_labels, width, height);
208 G.merge_small_area_nodes(min_area);
212 for (
auto &n : G.get_nodes()) {
213 if (n->area() == 0)
continue;
215 auto [r, g, b] = n->color();
216 for (
auto &[_, p] : n->get_pixels()) {
217 results(p.x, p.y) = {r, g, b};
223 G.compute_contours();
227 for (
auto &n : G.get_nodes()) {
228 if (n->area() == 0)
continue;
230 for (
auto &c : node_contours.contours) {
231 all_contours.contours.push_back(c);
233 for (
auto &c : node_contours.hierarchy) {
234 all_contours.hierarchy.push_back(c);
236 for (
bool b : node_contours.is_hole) {
237 all_contours.is_hole.push_back(b);
239 for (
auto &c : node_contours.colors) {
240 all_contours.colors.push_back(c);
242 for (
auto &c : node_contours.curves) {
243 all_contours.curves.push_back(c);
248 const auto &modified = results.getData();
252 if (!draw_contour_borders) {
253 std::string svg{contoursResultToSVG(all_contours, width, height)};
256 char *res_svg{
static_cast<char *
>(std::malloc(svg.size() + 1))};
260 std::memcpy(res_svg, svg.c_str(), svg.size() + 1);
Core image processing functions for img2num project.
char * labels_to_svg(uint8_t *data, int32_t *labels, const int width, const int height, const int min_area, const bool draw_contour_borders)
Convert labeled regions of an image into an SVG string.