diff --git a/bindings/c/include/cimg2num.h b/bindings/c/include/cimg2num.h index d867930cd..4bfb51cc1 100644 --- a/bindings/c/include/cimg2num.h +++ b/bindings/c/include/cimg2num.h @@ -40,8 +40,9 @@ void img2num_bilateral_filter(uint8_t *image, size_t width, size_t height, doubl double sigma_range, uint8_t color_space); /// @copydoc ::IMG2NUM_H_LABELS_TO_SVG_DOC -char *img2num_labels_to_svg(uint8_t *data, int32_t *labels, const int width, const int height, - const int min_area, const bool draw_contour_borders); +char *img2num_labels_to_svg(const uint8_t *data, const int32_t *labels, + const int width, const int height, + const int min_area); #ifdef __cplusplus } diff --git a/bindings/c/src/cimg2num.cpp b/bindings/c/src/cimg2num.cpp index 66cc44186..b8bcbdf3e 100644 --- a/bindings/c/src/cimg2num.cpp +++ b/bindings/c/src/cimg2num.cpp @@ -38,14 +38,15 @@ void img2num_bilateral_filter(uint8_t *image, size_t width, size_t height, doubl sigma_spatial, sigma_range, color_space); } -char *img2num_labels_to_svg(uint8_t *data, int32_t *labels, const int width, const int height, - const int min_area, const bool draw_contour_borders) { +char *img2num_labels_to_svg(const uint8_t *data, const int32_t *labels, + const int width, const int height, + const int min_area) { char *result{nullptr}; img2num::clear_last_error_and_catch( - [&](uint8_t *d, int32_t *l, int w, int h, int min_a, bool draw_contours) { - result = img2num::labels_to_svg(d, l, w, h, min_a, draw_contours); + [&](const uint8_t *d, const int32_t *l, const int w, const int h, const int min_a) { + result = img2num::labels_to_svg(d, l, w, h, min_a); }, - data, labels, width, height, min_area, draw_contour_borders); + data, labels, width, height, min_area); return result; } } diff --git a/bindings/js/src/wasm_wrapper.cpp b/bindings/js/src/wasm_wrapper.cpp index 5867eb2e5..3e3cf2726 100644 --- a/bindings/js/src/wasm_wrapper.cpp +++ b/bindings/js/src/wasm_wrapper.cpp @@ -34,8 +34,7 @@ EMSCRIPTEN_KEEPALIVE void bilateral_filter(uint8_t *image, size_t width, size_t } EMSCRIPTEN_KEEPALIVE 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) { - return img2num_labels_to_svg(data, labels, width, height, min_area, draw_contour_borders); + const int height, const int min_area) { + return img2num_labels_to_svg(data, labels, width, height, min_area); } } diff --git a/core/include/img2num.h b/core/include/img2num.h index 978919317..3fb479c9d 100644 --- a/core/include/img2num.h +++ b/core/include/img2num.h @@ -37,8 +37,9 @@ void bilateral_filter(uint8_t *image, size_t width, size_t height, double sigma_ double sigma_range, uint8_t color_space); /// @copydoc IMG2NUM_H_LABELS_TO_SVG_DOC -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); +char *labels_to_svg(const uint8_t *data, const int32_t *labels, + const int width, const int height, + const int min_area); } // namespace img2num #endif // IMG2NUM_H diff --git a/core/src/internal/labels_to_svg.cpp b/core/src/internal/labels_to_svg.cpp index fa0463556..d3f4857c5 100644 --- a/core/src/internal/labels_to_svg.cpp +++ b/core/src/internal/labels_to_svg.cpp @@ -186,8 +186,9 @@ format ([r,g,b,a, r,g,b,a, ...]) labels: int32_t* -> output of labelled regions from K-Means, should be 1/4 the size of data since data is RGBA labels : width * height : number of pixels in image = 1 : 1 : 1 */ -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) { +char *labels_to_svg(const uint8_t *data, const int32_t *labels, + const int width, const int height, + const int min_area) { const int32_t num_pixels{width * height}; std::vector labels_vector{labels, labels + num_pixels}; std::vector region_labels; @@ -244,24 +245,16 @@ char *labels_to_svg(uint8_t *data, int32_t *labels, const int width, const int h } } - // 7. Copy recolored image back - const auto &modified = results.getData(); - std::memcpy(data, modified.data(), modified.size() * sizeof(ImageLib::RGBAPixel)); + // 7. Return SVG + std::string svg{contoursResultToSVG(all_contours, width, height)}; - // 8. Return SVG if requested - if (!draw_contour_borders) { - std::string svg{contoursResultToSVG(all_contours, width, height)}; - - // Dynamic C-style allocation (since returned over C ABI) - char *res_svg{static_cast(std::malloc(svg.size() + 1))}; - if (!res_svg) { - return nullptr; // Allocation failed - } - std::memcpy(res_svg, svg.c_str(), svg.size() + 1); - - return res_svg; + // Dynamic C-style allocation (since returned over C ABI) + char *res_svg{static_cast(std::malloc(svg.size() + 1))}; + if (!res_svg) { + return nullptr; // Allocation failed } + std::memcpy(res_svg, svg.c_str(), svg.size() + 1); - return nullptr; // no SVG + return res_svg; } } // namespace img2num diff --git a/docs/docs/internal/packages/js/index.md b/docs/docs/internal/packages/js/index.md index 82cdec3d1..e8ac06eab 100644 --- a/docs/docs/internal/packages/js/index.md +++ b/docs/docs/internal/packages/js/index.md @@ -131,7 +131,11 @@ Messages **optionally** specify bufferKeys as structured descriptors: ```js title="Arguments passed to WASM function" args: { - (pixels, labels, width, height, min_area, draw_contour_borders); + pixels, + labels, + width, + height, + min_area, } ``` diff --git a/doxygen/img2num.h.dox b/doxygen/img2num.h.dox index 141ef6e1d..2f25db097 100644 --- a/doxygen/img2num.h.dox +++ b/doxygen/img2num.h.dox @@ -84,7 +84,6 @@ /// @param width Width of the image in pixels. /// @param height Height of the image in pixels. /// @param min_area Minimum area (in pixels) for a region to be included in the SVG. -/// @param draw_contour_borders If true, contours of labeled regions will be drawn. /// @return Pointer to a dynamically allocated C-string containing the SVG data. /// @note Caller is responsible for freeing the returned string. /// @note Dox File: `doxygen/img2num.h.dox` diff --git a/example-apps/console-c/main.c b/example-apps/console-c/main.c index 82dd57aba..797b3c195 100644 --- a/example-apps/console-c/main.c +++ b/example-apps/console-c/main.c @@ -55,8 +55,6 @@ int main(int argc, char** argv) { uint8_t* img_data = (uint8_t*)malloc(img_size); uint8_t* out_data = (uint8_t*)malloc(img_size); int32_t* out_labels = (int32_t*)malloc(width * height * sizeof(int32_t)); - char* res_svg; - if (!img_data || !out_data || !out_labels) { fprintf(stderr, "Failed to allocate memory\n"); stbi_image_free(image_data_original); @@ -69,12 +67,17 @@ int main(int argc, char** argv) { // Apply bilateral (C API) double sigma = width * SIGMA_WIDTH_RATIO; img2num_bilateral_filter(img_data, width, height, sigma, 50.0, 0); - // Apply kmeans (C API) img2num_kmeans(img_data, out_data, out_labels, width, height, 16, 100, 1); - // Generate SVG - res_svg = img2num_labels_to_svg(img_data, out_labels, width, height, 100, false); + char* res_svg = img2num_labels_to_svg(img_data, out_labels, width, height, 100); + + if (res_svg == NULL) { + fprintf(stderr, "Failed to generate SVG\n"); + stbi_image_free(image_data_original); + free(img_data); free(out_data); free(out_labels); + return 1; + } // Save outputs char out_path[512]; @@ -87,18 +90,18 @@ int main(int argc, char** argv) { int exit_code = 0; const bool blur_save_success = stbi_write_png(out_path, width, height, NUM_CHANNELS, img_data, width * NUM_CHANNELS); const bool kmeans_save_success = stbi_write_png(kmeans_path, width, height, NUM_CHANNELS, out_data, width * NUM_CHANNELS); - + FILE* file = fopen(svg_path, "w"); if (file == NULL) { printf("Error: Could not open the file!\n"); - exit_code == 1; + exit_code = 1; } if (exit_code == 0) { fputs(res_svg, file); fclose(file); } - + if (blur_save_success && kmeans_save_success && (exit_code == 0)) { printf("\n\nSUCCESS!\nThe below images have been saved:\n\t- %s\n\t- %s\n\t- %s\n", out_path, kmeans_path, svg_path); } else { @@ -110,5 +113,6 @@ int main(int argc, char** argv) { free(img_data); free(out_data); free(out_labels); + free(res_svg); return exit_code; } diff --git a/example-apps/console-cpp/main.cpp b/example-apps/console-cpp/main.cpp index 63c1ce55a..70a2dac1a 100644 --- a/example-apps/console-cpp/main.cpp +++ b/example-apps/console-cpp/main.cpp @@ -49,25 +49,29 @@ int main(int argc, char** argv) { uint8_t* img_data{new uint8_t[width * height * NUM_CHANNELS]}; uint8_t* out_data{new uint8_t[width * height * NUM_CHANNELS]}; int32_t* out_labels{new int32_t[width * height]}; - char* res_svg = nullptr; - for (int ITER=0; ITER(width) * static_cast(height) * NUM_CHANNELS); - - // Apply bilateral - const double sigma{width * SIGMA_WIDTH_RATIO}; - img2num::bilateral_filter(img_data, width, height, sigma, 50.0, 0); - // Apply kmeans - img2num::kmeans(img_data, out_data, out_labels, width, height, 32, 100, 1); - // Generate SVG - if (res_svg != nullptr) { - std::free(res_svg); - } - res_svg = img2num::labels_to_svg(img_data, out_labels, width, height, 100, false); + + std::cout << "Image loaded: " << width << "x" << height << " with " << NUM_CHANNELS << " channel(s)." << std::endl; + + // Allocate a copy of the original image + // uint8_t* img_data{new uint8_t[width * height * NUM_CHANNELS]}; + std::memcpy(img_data, image_data_original, static_cast(width) * static_cast(height) * NUM_CHANNELS); + + // Apply bilateral + const double sigma{width * SIGMA_WIDTH_RATIO}; + img2num::bilateral_filter(img_data, width, height, sigma, 50.0, 0); + // Apply kmeans + img2num::kmeans(img_data, out_data, out_labels, width, height, 32, 100, 1); + // Generate SVG + char* res_svg{img2num::labels_to_svg(img_data, out_labels, width, height, 100)}; + if (!res_svg) { + std::cerr << "Failed to generate SVG: allocation failed" << std::endl; + stbi_image_free(image_data_original); + delete[] img_data; + delete[] out_data; + delete[] out_labels; + return 1; } + // Save the blurred image std::string out_path{std::string(OUT_DIR) + "/console-cpp-output.png"}; std::string kmeans_path{std::string(OUT_DIR) + "/console-cpp-kmeans.png"}; @@ -76,7 +80,7 @@ int main(int argc, char** argv) { int exit_code{0}; const bool blur_save_success{stbi_write_png(out_path.c_str(), width, height, NUM_CHANNELS, img_data, width * NUM_CHANNELS) == 1 ? true : false}; const bool kmeans_save_success{stbi_write_png(kmeans_path.c_str(), width, height, NUM_CHANNELS, out_data, width * NUM_CHANNELS) == 1 ? true : false}; - + std::ofstream svgFile(svg_path); if (!svgFile.is_open()) { std::cerr << "Error: Could not open the file!" << std::endl; diff --git a/packages/js/safeWasmWrappers.js b/packages/js/safeWasmWrappers.js index d07d44fb2..78d4a3dd7 100644 --- a/packages/js/safeWasmWrappers.js +++ b/packages/js/safeWasmWrappers.js @@ -174,8 +174,7 @@ export const kmeans = async ({ * @summary Convert labeled regions to SVG contours. * * @description - * Default path: convert an input image and its labeled regions into an SVG. - * `draw_contour_borders`=true: the input image with its contours traced and marked with unique colors. + * Convert an input image and its labeled regions into an SVG. * * @async * @function findContours @@ -185,7 +184,6 @@ export const kmeans = async ({ * @property {number} __named_parameters.width - Image width. * @property {number} __named_parameters.height - Image height. * @property {number} [__named_parameters.min_area=100] - Minimum area of a region to be considered a contour. - * @property {boolean} [__named_parameters.draw_contour_borders=false] - Whether to draw contour borders in visualization. * @returns {Promise<{svg: string, visualization: Uint8ClampedArray}>} Generated SVG and optionally pixels with visualized contours. * @throws {Error} If the WASM function fails or input labels are invalid. * @example @@ -194,10 +192,10 @@ export const kmeans = async ({ * @variation Contour extraction with optional visualization * @since 0.0.0 */ -export const findContours = async ({ pixels, labels, width, height, min_area = 100, draw_contour_borders = false }) => { +export const findContours = async ({ pixels, labels, width, height, min_area = 100 }) => { const result = await callWasm({ funcName: "labels_to_svg", - args: { pixels, labels, width, height, min_area, draw_contour_borders }, + args: { pixels, labels, width, height, min_area }, bufferKeys: [ { key: "pixels", type: "Uint8ClampedArray" }, { key: "labels", type: "Int32Array" },