diff options
author | jaseg <git@jaseg.de> | 2021-06-02 15:05:36 +0200 |
---|---|---|
committer | jaseg <git@jaseg.de> | 2021-06-02 15:05:36 +0200 |
commit | 6cca4a3278c30fb35e97dd0017c5a5a25a78bcf0 (patch) | |
tree | f072376d37410477b809fc48af61666cca4ca1c7 | |
parent | 3e58a4228bff78e2c51136312337fc4b47efa587 (diff) | |
download | gerbolyze-6cca4a3278c30fb35e97dd0017c5a5a25a78bcf0.tar.gz gerbolyze-6cca4a3278c30fb35e97dd0017c5a5a25a78bcf0.tar.bz2 gerbolyze-6cca4a3278c30fb35e97dd0017c5a5a25a78bcf0.zip |
Port svg-flatten to nopencv
-rw-r--r-- | svg-flatten/Makefile | 8 | ||||
-rw-r--r-- | svg-flatten/src/nopencv.cpp | 101 | ||||
-rw-r--r-- | svg-flatten/src/nopencv.hpp | 40 | ||||
-rw-r--r-- | svg-flatten/src/nopencv_test.cpp | 11 | ||||
-rw-r--r-- | svg-flatten/src/vec_core.cpp | 141 | ||||
-rw-r--r-- | svg-flatten/src/vec_core.h | 1 |
6 files changed, 172 insertions, 130 deletions
diff --git a/svg-flatten/Makefile b/svg-flatten/Makefile index 0824561..30f3966 100644 --- a/svg-flatten/Makefile +++ b/svg-flatten/Makefile @@ -25,6 +25,7 @@ SOURCES := src/svg_color.cpp \ src/out_dilater.cpp \ src/lambda_sink.cpp \ src/flatten.cpp \ + src/nopencv.cpp \ $(UPSTREAM_DIR)/cpp-base64/base64.cpp CLIPPER_SOURCES ?= $(UPSTREAM_DIR)/clipper-6.4.2/cpp/clipper.cpp @@ -44,14 +45,9 @@ INCLUDES := -Iinclude -Isrc $(CLIPPER_INCLUDES) $(VORONOI_INCLUDES) $(POISSON_IN PKG_CONFIG_DEPS := pugixml CXXFLAGS := -std=c++2a -g -Wall -Wextra -O0 CXXFLAGS += $(shell $(PKG_CONFIG) --cflags $(PKG_CONFIG_DEPS)) -# hack for stone age opencv in debian stable -CXXFLAGS += $(shell $(PKG_CONFIG) --cflags opencv4 2> /dev/null || $(PKG_CONFIG) --cflags opencv 2>/dev/null) LDFLAGS := -lm -lc -lstdc++ LDFLAGS += $(shell $(PKG_CONFIG) --libs $(PKG_CONFIG_DEPS)) -# debian hack. see above. -OPENCV_LDFLAGS := $(shell $(PKG_CONFIG) --libs opencv4 2> /dev/null || $(PKG_CONFIG) --libs opencv 2>/dev/null) -LDFLAGS += $(shell echo $(OPENCV_LDFLAGS) | sed 's/-l\S\+ //g') -lopencv_core -lopencv_imgproc -lopencv_imgcodecs TARGET := svg-flatten @@ -61,8 +57,6 @@ all: $(BUILDDIR)/$(TARGET) check-deps: @echo @$(PKG_CONFIG) --cflags --libs pugixml >/dev/null - # debian hack. see above. - @$(PKG_CONFIG) --cflags --libs opencv4 >/dev/null ||$(PKG_CONFIG) --cflags --libs opencv >/dev/null $(BUILDDIR)/%.o: %.cpp @mkdir -p $(dir $@) diff --git a/svg-flatten/src/nopencv.cpp b/svg-flatten/src/nopencv.cpp index 9fd2b2f..b490217 100644 --- a/svg-flatten/src/nopencv.cpp +++ b/svg-flatten/src/nopencv.cpp @@ -2,10 +2,20 @@ #include <iostream> #include <iomanip> +#include "nopencv.hpp" + #define STB_IMAGE_IMPLEMENTATION -#include "stb_image.h" +#include <stb_image.h> -#include "nopencv.hpp" +#define STB_IMAGE_RESIZE_IMPLEMENTATION +#include <stb_image_resize.h> + +#define IIR_GAUSS_BLUR_IMPLEMENTATION +#include "iir_gauss_blur.h" + +template void iir_gauss_blur<uint8_t>(unsigned int width, unsigned int height, unsigned char components, uint8_t* image, float sigma); +template void iir_gauss_blur<uint32_t> (unsigned int width, unsigned int height, unsigned char components, uint32_t* image, float sigma); +template void iir_gauss_blur<float> (unsigned int width, unsigned int height, unsigned char components, float* image, float sigma); using namespace gerbolyze; using namespace gerbolyze::nopencv; @@ -138,7 +148,7 @@ static void follow(gerbolyze::nopencv::Image32 &img, int start_x, int start_y, D } -void gerbolyze::nopencv::find_blobs(gerbolyze::nopencv::Image32 &img, gerbolyze::nopencv::ContourCallback cb) { +void gerbolyze::nopencv::find_contours(gerbolyze::nopencv::Image32 &img, gerbolyze::nopencv::ContourCallback cb) { /* Implementation of the hierarchical contour finding algorithm from Suzuki and Abe, 1983: Topological Structural * Analysis of Digitized Binary Images by Border Following * @@ -387,27 +397,40 @@ ContourCallback gerbolyze::nopencv::simplify_contours_teh_chin(ContourCallback c }; } +double gerbolyze::nopencv::polygon_area(Polygon_i &poly) { + double acc = 0; + size_t prev = poly.size() - 1; + for (size_t cur=0; cur<poly.size(); cur++) { + acc += (poly[prev][0] + poly[cur][0]) * (poly[prev][1] - poly[cur][1]); + prev = cur; + } + return acc / 2; +} -gerbolyze::nopencv::Image32::Image32(int size_x, int size_y, const int32_t *data) { +template<typename T> +gerbolyze::nopencv::Image<T>::Image(int size_x, int size_y, const T *data) { assert(size_x > 0 && size_x < 100000); assert(size_y > 0 && size_y < 100000); - m_data = new int32_t[size_x * size_y] { 0 }; + m_data = new T[size_x * size_y] { 0 }; m_rows = size_y; m_cols = size_x; if (data != nullptr) { - memcpy(m_data, data, sizeof(int32_t) * size_x * size_y); + memcpy(m_data, data, sizeof(T) * size_x * size_y); } } -bool gerbolyze::nopencv::Image32::load(const char *filename) { +template<typename T> +bool gerbolyze::nopencv::Image<T>::load(const char *filename) { return stb_to_internal(stbi_load(filename, &m_cols, &m_rows, nullptr, 1)); } -bool gerbolyze::nopencv::Image32::load_memory(uint8_t *buf, size_t len) { - return stb_to_internal(stbi_load_from_memory(buf, len, &m_cols, &m_rows, nullptr, 1)); +template<typename T> +bool gerbolyze::nopencv::Image<T>::load_memory(const void *buf, size_t len) { + return stb_to_internal(stbi_load_from_memory(reinterpret_cast<const uint8_t *>(buf), len, &m_cols, &m_rows, nullptr, 1)); } -void gerbolyze::nopencv::Image32::binarize() { +template<typename T> +void gerbolyze::nopencv::Image<T>::binarize() { assert(m_data != nullptr); assert(m_rows > 0 && m_cols > 0); @@ -418,7 +441,8 @@ void gerbolyze::nopencv::Image32::binarize() { } } -bool gerbolyze::nopencv::Image32::stb_to_internal(uint8_t *data) { +template<typename T> +bool gerbolyze::nopencv::Image<T>::stb_to_internal(uint8_t *data) { if (data == nullptr) return false; @@ -427,7 +451,7 @@ bool gerbolyze::nopencv::Image32::stb_to_internal(uint8_t *data) { if (m_cols < 0 || m_cols > 100000) return false; - m_data = new int32_t[size()] { 0 }; + m_data = new T[size()] { 0 }; for (int y=0; y<m_rows; y++) { for (int x=0; x<m_cols; x++) { m_data[y*m_cols + x] = data[y*m_cols + x]; @@ -438,13 +462,50 @@ bool gerbolyze::nopencv::Image32::stb_to_internal(uint8_t *data) { return true; } -double gerbolyze::nopencv::polygon_area(Polygon_i &poly) { - double acc = 0; - size_t prev = poly.size() - 1; - for (size_t cur=0; cur<poly.size(); cur++) { - acc += (poly[prev][0] + poly[cur][0]) * (poly[prev][1] - poly[cur][1]); - prev = cur; - } - return acc / 2; +template<typename T> +void gerbolyze::nopencv::Image<T>::blur(int radius) { + iir_gauss_blur(m_cols, m_rows, 1, m_data, radius/2.0); +} + +template<> +void gerbolyze::nopencv::Image<float>::resize(int new_w, int new_h) { + float *old_data = m_data; + m_data = new float[new_w * new_h]; + stbir_resize_float(old_data, m_cols, m_rows, 0, + m_data, new_w, new_h, 0, + 1); + m_cols = new_w; + m_rows = new_h; +} + +template<> +void gerbolyze::nopencv::Image<uint8_t>::resize(int new_w, int new_h) { + uint8_t *old_data = m_data; + m_data = new uint8_t[new_w * new_h]; + stbir_resize_uint8(old_data, m_cols, m_rows, 0, + m_data, new_w, new_h, 0, + 1); + m_cols = new_w; + m_rows = new_h; } +template gerbolyze::nopencv::Image<int32_t>::Image(int size_x, int size_y, const int32_t *data); +template bool gerbolyze::nopencv::Image<int32_t>::load(const char *filename); +template bool gerbolyze::nopencv::Image<int32_t>::load_memory(const void *buf, size_t len); +template void gerbolyze::nopencv::Image<int32_t>::binarize(); +template bool gerbolyze::nopencv::Image<int32_t>::stb_to_internal(uint8_t *data); +template void gerbolyze::nopencv::Image<int32_t>::blur(int radius); + +template gerbolyze::nopencv::Image<uint8_t>::Image(int size_x, int size_y, const uint8_t *data); +template bool gerbolyze::nopencv::Image<uint8_t>::load(const char *filename); +template bool gerbolyze::nopencv::Image<uint8_t>::load_memory(const void *buf, size_t len); +template void gerbolyze::nopencv::Image<uint8_t>::binarize(); +template bool gerbolyze::nopencv::Image<uint8_t>::stb_to_internal(uint8_t *data); +template void gerbolyze::nopencv::Image<uint8_t>::blur(int radius); + +template gerbolyze::nopencv::Image<float>::Image(int size_x, int size_y, const float *data); +template bool gerbolyze::nopencv::Image<float>::load(const char *filename); +template bool gerbolyze::nopencv::Image<float>::load_memory(const void *buf, size_t len); +template void gerbolyze::nopencv::Image<float>::binarize(); +template bool gerbolyze::nopencv::Image<float>::stb_to_internal(uint8_t *data); +template void gerbolyze::nopencv::Image<float>::blur(int radius); diff --git a/svg-flatten/src/nopencv.hpp b/svg-flatten/src/nopencv.hpp index afebce5..8f399b9 100644 --- a/svg-flatten/src/nopencv.hpp +++ b/svg-flatten/src/nopencv.hpp @@ -39,30 +39,37 @@ namespace gerbolyze { typedef std::function<void(Polygon_i&, ContourPolarity)> ContourCallback; - class Image32 { + template<typename T> class Image { public: - Image32() {} - Image32(int size_x, int size_y, const int32_t *data=nullptr); - Image32(const Image32 &other) : Image32(other.cols(), other.rows(), other.ptr()) {} + Image() {} + Image(int w, int h, const T *data=nullptr); + Image(const Image<T> &other) : Image<T>(other.cols(), other.rows(), other.ptr()) {} + template<typename U> Image(const Image<U> &other) : Image<T>(other.cols(), other.rows()) { + for (size_t y=0; y<m_rows; y++) { + for (size_t x=0; x<m_cols; x++) { + at(x, y) = other.at(x, y); + } + } + } - ~Image32() { + ~Image() { if (m_data) { delete m_data; } } bool load(const char *filename); - bool load_memory(uint8_t *buf, size_t len); + bool load_memory(const void *buf, size_t len); void binarize(); - int32_t &at(int x, int y) { + T &at(int x, int y) { assert(x >= 0 && y >= 0 && x < m_cols && y < m_rows); assert(m_data != nullptr); return m_data[y*m_cols + x]; }; - void set_at(int x, int y, int val) { + void set_at(int x, int y, T val) { assert(x >= 0 && y >= 0 && x < m_cols && y < m_rows); assert(m_data != nullptr); @@ -70,14 +77,14 @@ namespace gerbolyze { cerr << "set_at " << x << " " << y << ": " << val << " -> " << at(x, y) << endl; }; - const int32_t &at(int x, int y) const { + const T &at(int x, int y) const { assert(x >= 0 && y >= 0 && x < m_cols && y < m_rows); assert(m_data != nullptr); return m_data[y*m_cols + x]; }; - int32_t at_default(int x, int y, int32_t default_value=0) const { + T at_default(int x, int y, T default_value=0) const { assert(m_data != nullptr); if (x >= 0 && y >= 0 && x < m_cols && y < m_rows) { @@ -88,19 +95,26 @@ namespace gerbolyze { } }; + void blur(int radius); + void resize(int new_w, int new_h); + int rows() const { return m_rows; } int cols() const { return m_cols; } int size() const { return m_cols*m_rows; } - const int32_t *ptr() const { return m_data; } + const T *ptr() const { return m_data; } private: bool stb_to_internal(uint8_t *data); - int32_t *m_data = nullptr; + T *m_data = nullptr; int m_rows=0, m_cols=0; }; - void find_blobs(Image32 &img, ContourCallback cb); + typedef Image<uint8_t> Image8; + typedef Image<int32_t> Image32; + typedef Image<float> Image32f; + + void find_contours(Image32 &img, ContourCallback cb); ContourCallback simplify_contours_teh_chin(ContourCallback cb); double polygon_area(Polygon_i &poly); diff --git a/svg-flatten/src/nopencv_test.cpp b/svg-flatten/src/nopencv_test.cpp index eae17e2..17ba71e 100644 --- a/svg-flatten/src/nopencv_test.cpp +++ b/svg-flatten/src/nopencv_test.cpp @@ -97,7 +97,7 @@ MU_TEST(test_complex_example_from_paper) { const ContourPolarity expected_polarities[3] = {CP_CONTOUR, CP_HOLE, CP_HOLE}; int invocation_count = 0; - gerbolyze::nopencv::find_blobs(test_img, [&invocation_count, &expected_polarities, &expected_polys](Polygon_i &poly, ContourPolarity pol) { + gerbolyze::nopencv::find_contours(test_img, [&invocation_count, &expected_polarities, &expected_polys](Polygon_i &poly, ContourPolarity pol) { invocation_count += 1; mu_assert((invocation_count <= 3), "Too many contours returned"); @@ -188,7 +188,7 @@ static void testdata_roundtrip(const char *fn) { TempfileHack tmp_png(".png"); SVGPolyRenderer ctx(tmp_svg.c_str(), ref_img.cols(), ref_img.rows()); - gerbolyze::nopencv::find_blobs(ref_img, ctx.callback()); + gerbolyze::nopencv::find_contours(ref_img, ctx.callback()); ctx.close(); mu_assert_int_eq(0, render_svg(tmp_svg.c_str(), tmp_png.c_str())); @@ -246,7 +246,7 @@ static void test_polygon_area(const char *fn) { double pos_sum = 0.0; double neg_sum = ref_img.size(); - gerbolyze::nopencv::find_blobs(ref_img, [&pos_sum, &neg_sum](Polygon_i& poly, ContourPolarity pol) { + gerbolyze::nopencv::find_contours(ref_img, [&pos_sum, &neg_sum](Polygon_i& poly, ContourPolarity pol) { double area = polygon_area(poly); //cerr << endl << fn << ": " << area << " " << pos_sum << " / " << neg_sum << " -- " << white_px_count << " / " << black_px_count << " GOT: " << poly.size() << " w/ " << pol << endl; mu_assert(fabs(area) > 0.99, "Polygon smaller than a single pixel"); @@ -288,7 +288,7 @@ static void chain_approx_test(const char *fn) { TempfileHack tmp_png(".png"); SVGPolyRenderer ctx(tmp_svg.c_str(), ref_img.cols(), ref_img.rows()); - gerbolyze::nopencv::find_blobs(ref_img, simplify_contours_teh_chin(ctx.callback())); + gerbolyze::nopencv::find_contours(ref_img, simplify_contours_teh_chin(ctx.callback())); ctx.close(); mu_assert_int_eq(0, render_svg(tmp_svg.c_str(), tmp_png.c_str())); @@ -344,6 +344,7 @@ MU_TEST(chain_approx_test_two_px_inv) { chain_approx_test("testdata/two- MU_TEST_SUITE(nopencv_contours_suite) { MU_RUN_TEST(test_complex_example_from_paper); + MU_RUN_TEST(test_round_trip_blank); MU_RUN_TEST(test_round_trip_white); MU_RUN_TEST(test_round_trip_blob_border_w); @@ -359,6 +360,7 @@ MU_TEST_SUITE(nopencv_contours_suite) { MU_RUN_TEST(test_round_trip_two_blobs); MU_RUN_TEST(test_round_trip_two_px); MU_RUN_TEST(test_round_trip_two_px_inv); + MU_RUN_TEST(chain_approx_test_chromosome); MU_RUN_TEST(chain_approx_test_blank); MU_RUN_TEST(chain_approx_test_white); @@ -375,6 +377,7 @@ MU_TEST_SUITE(nopencv_contours_suite) { MU_RUN_TEST(chain_approx_test_two_blobs); MU_RUN_TEST(chain_approx_test_two_px); MU_RUN_TEST(chain_approx_test_two_px_inv); + MU_RUN_TEST(test_polygon_area_blank); MU_RUN_TEST(test_polygon_area_white); MU_RUN_TEST(test_polygon_area_blob_border_w); diff --git a/svg-flatten/src/vec_core.cpp b/svg-flatten/src/vec_core.cpp index aa4a1cc..b0221ca 100644 --- a/svg-flatten/src/vec_core.cpp +++ b/svg-flatten/src/vec_core.cpp @@ -22,7 +22,7 @@ #include <algorithm> #include <vector> #include <regex> -#include <opencv2/opencv.hpp> +#include "nopencv.hpp" #include "svg_import_util.h" #include "vec_core.h" #include "svg_import_defs.h" @@ -78,27 +78,18 @@ void gerbolyze::parse_img_meta(const pugi::xml_node &node, double &x, double &y, cerr << "image elem: w="<<width<<", h="<<height<<endl; } -string gerbolyze::read_img_data(const pugi::xml_node &node) { +template<typename T> nopencv::Image<T> *img_from_node(const pugi::xml_node &node) { /* Read image from data:base64... URL */ string img_data = parse_data_iri(node.attribute("xlink:href").value()); if (img_data.empty()) { cerr << "Warning: Empty or invalid image element with id \"" << node.attribute("id").value() << "\"" << endl; - return ""; + return nullptr; } - return img_data; -} - -cv::Mat read_img_opencv(const pugi::xml_node &node) { - string img_data = read_img_data(node); - /* slightly annoying round-trip through the std:: and cv:: APIs */ - vector<unsigned char> img_vec(img_data.begin(), img_data.end()); - cv::Mat data_mat(img_vec, true); - cv::Mat img = cv::imdecode(data_mat, cv::ImreadModes::IMREAD_GRAYSCALE | cv::ImreadModes::IMREAD_ANYDEPTH); - data_mat.release(); - - if (img.empty()) { + auto *img = new nopencv::Image<T>(); + if (!img->load_memory(img_data.c_str(), img_data.size())) { cerr << "Warning: Could not decode content of image element with id \"" << node.attribute("id").value() << "\"" << endl; + return nullptr; } return img; @@ -164,8 +155,8 @@ void gerbolyze::draw_bg_rect(xform2d &mat, double width, double height, ClipperL void gerbolyze::VoronoiVectorizer::vectorize_image(xform2d &mat, const pugi::xml_node &node, ClipperLib::Paths &clip_path, PolygonSink &sink, double min_feature_size_px) { double x, y, width, height; parse_img_meta(node, x, y, width, height); - cv::Mat img = read_img_opencv(node); - if (img.empty()) + nopencv::Image32f *img = img_from_node<float>(node); + if (img == nullptr) return; /* Set up target transform using SVG transform and x/y attributes */ @@ -173,8 +164,8 @@ void gerbolyze::VoronoiVectorizer::vectorize_image(xform2d &mat, const pugi::xml local_xf.translate(x, y); local_xf.transform(xform2d(node.attribute("transform").value())); - double orig_rows = img.rows; - double orig_cols = img.cols; + double orig_rows = img->rows(); + double orig_cols = img->cols(); double scale_x = (double)width / orig_cols; double scale_y = (double)height / orig_rows; double off_x = 0; @@ -206,19 +197,15 @@ void gerbolyze::VoronoiVectorizer::vectorize_image(xform2d &mat, const pugi::xml double px_h = height / min_feature_size_px * scale_featuresize_factor; /* Scale intermediate image (step 1.2) to have <scale_featuresize_factor> pixels per min_feature_size. */ - cv::Mat scaled(cv::Size{(int)round(px_w), (int)round(px_h)}, img.type()); - cv::resize(img, scaled, scaled.size(), 0, 0); - cerr << "scaled " << img.cols << ", " << img.rows << " -> " << scaled.cols << ", " << scaled.rows << endl; - img.release(); + cerr << "scaled " << img->cols() << ", " << img->rows() << " -> " << ((int)round(px_w)) << ", " << ((int)round(px_h)) << endl; + img->resize((int)round(px_w), (int)round(px_h)); /* Blur image with a kernel larger than our minimum feature size to avoid aliasing. */ - cv::Mat blurred(scaled.size(), scaled.type()); - int blur_size = (int)ceil(fmax(scaled.cols / width, scaled.rows / height) * center_distance); + int blur_size = (int)ceil(fmax(img->cols() / width, img->rows() / height) * center_distance); if (blur_size%2 == 0) blur_size += 1; cerr << "blur size " << blur_size << endl; - cv::GaussianBlur(scaled, blurred, {blur_size, blur_size}, 0, 0); - scaled.release(); + img->blur(blur_size); /* Calculate voronoi diagram for the grid generated above. */ jcv_diagram diagram; @@ -247,9 +234,9 @@ void gerbolyze::VoronoiVectorizer::vectorize_image(xform2d &mat, const pugi::xml for (int i=0; i<diagram.numsites; i++) { const jcv_point center = sites[i].p; - double pxd = (double)blurred.at<unsigned char>( - (int)round(center.y / (scale_y * orig_rows / blurred.rows)), - (int)round(center.x / (scale_x * orig_cols / blurred.cols))) / 255.0; + double pxd = img->at( + (int)round(center.y / (scale_y * orig_rows / img->rows())), + (int)round(center.x / (scale_x * orig_cols / img->cols()))) / 255.0; /* FIXME: This is a workaround for a memory corruption bug that happens with the square-grid setting. When using * square-grid on a fairly small test image, sometimes sites[i].index will be out of bounds here. */ @@ -359,9 +346,9 @@ void gerbolyze::VoronoiVectorizer::vectorize_image(xform2d &mat, const pugi::xml } } - blurred.release(); jcv_diagram_free( &diagram ); delete grid_centers; + delete img; } void gerbolyze::handle_aspect_ratio(string spec, double &scale_x, double &scale_y, double &off_x, double &off_y, double cols, double rows) { @@ -423,8 +410,8 @@ void gerbolyze::OpenCVContoursVectorizer::vectorize_image(xform2d &mat, const pu (void) min_feature_size_px; /* unused by this vectorizer */ double x, y, width, height; parse_img_meta(node, x, y, width, height); - cv::Mat img = read_img_opencv(node); - if (img.empty()) + nopencv::Image32 *img = img_from_node<int32_t>(node); + if (img == nullptr) return; /* Set up target transform using SVG transform and x/y attributes */ @@ -432,69 +419,53 @@ void gerbolyze::OpenCVContoursVectorizer::vectorize_image(xform2d &mat, const pu local_xf.transform(xform2d(node.attribute("transform").value())); local_xf.translate(x, y); - double scale_x = (double)width / (double)img.cols; - double scale_y = (double)height / (double)img.rows; + double scale_x = (double)width / (double)img->cols(); + double scale_y = (double)height / (double)img->rows(); double off_x = 0; double off_y = 0; handle_aspect_ratio(node.attribute("preserveAspectRatio").value(), - scale_x, scale_y, off_x, off_y, img.cols, img.rows); + scale_x, scale_y, off_x, off_y, img->cols(), img->rows()); draw_bg_rect(local_xf, width, height, clip_path, sink); - vector<vector<cv::Point>> contours; - vector<cv::Vec4i> hierarchy; - cv::findContours(img, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_TC89_KCOS); - - queue<pair<size_t, bool>> child_stack; - child_stack.push({ 0, true }); + nopencv::find_contours(*img, [&sink, &local_xf, &clip_path, off_x, off_y, scale_x, scale_y](Polygon_i& poly, nopencv::ContourPolarity pol) { + sink << ((pol == nopencv::CP_CONTOUR) ? GRB_POL_DARK : GRB_POL_CLEAR); - while (!child_stack.empty()) { - bool dark = child_stack.front().second; - for (int i=child_stack.front().first; i>=0; i = hierarchy[i][0]) { - if (hierarchy[i][2] >= 0) { - child_stack.push({ hierarchy[i][2], !dark }); - } - - sink << (dark ? GRB_POL_DARK : GRB_POL_CLEAR); + bool is_clockwise = nopencv::polygon_area(poly) > 0; + if (!is_clockwise) + std::reverse(poly.begin(), poly.end()); - bool is_clockwise = cv::contourArea(contours[i], true) > 0; - if (!is_clockwise) - std::reverse(contours[i].begin(), contours[i].end()); - - ClipperLib::Path out; - for (const auto &p : contours[i]) { - d2p q = local_xf.doc2phys(d2p{ - off_x + (double)p.x * scale_x, - off_y + (double)p.y * scale_y - }); - out.push_back({ - (ClipperLib::cInt)round(q[0] * clipper_scale), - (ClipperLib::cInt)round(q[1] * clipper_scale) - }); - } + ClipperLib::Path out; + for (const auto &p : poly) { + d2p q = local_xf.doc2phys(d2p{ + off_x + (double)p[0] * scale_x, + off_y + (double)p[1] * scale_y + }); + out.push_back({ + (ClipperLib::cInt)round(q[0] * clipper_scale), + (ClipperLib::cInt)round(q[1] * clipper_scale) + }); + } - ClipperLib::Clipper c; - c.AddPath(out, ClipperLib::ptSubject, /* closed */ true); - if (!clip_path.empty()) { - c.AddPaths(clip_path, ClipperLib::ptClip, /* closed */ true); - } - c.StrictlySimple(true); - ClipperLib::Paths polys; - c.Execute(ClipperLib::ctIntersection, polys, ClipperLib::pftNonZero, ClipperLib::pftNonZero); - - /* Draw into gerber. */ - for (const auto &poly : polys) { - vector<array<double, 2>> out; - for (const auto &p : poly) - out.push_back(std::array<double, 2>{ - ((double)p.X) / clipper_scale, ((double)p.Y) / clipper_scale - }); - sink << out; - } + ClipperLib::Clipper c; + c.AddPath(out, ClipperLib::ptSubject, /* closed */ true); + if (!clip_path.empty()) { + c.AddPaths(clip_path, ClipperLib::ptClip, /* closed */ true); } + c.StrictlySimple(true); + ClipperLib::Paths polys; + c.Execute(ClipperLib::ctIntersection, polys, ClipperLib::pftNonZero, ClipperLib::pftNonZero); - child_stack.pop(); - } + /* Draw into gerber. */ + for (const auto &poly : polys) { + vector<array<double, 2>> out; + for (const auto &p : poly) + out.push_back(std::array<double, 2>{ + ((double)p.X) / clipper_scale, ((double)p.Y) / clipper_scale + }); + sink << out; + } + }); } gerbolyze::VectorizerSelectorizer::VectorizerSelectorizer(const string default_vectorizer, const string defs) diff --git a/svg-flatten/src/vec_core.h b/svg-flatten/src/vec_core.h index 0d4da12..ce25807 100644 --- a/svg-flatten/src/vec_core.h +++ b/svg-flatten/src/vec_core.h @@ -50,7 +50,6 @@ namespace gerbolyze { }; void parse_img_meta(const pugi::xml_node &node, double &x, double &y, double &width, double &height); - std::string read_img_data(const pugi::xml_node &node); void draw_bg_rect(xform2d &mat, double width, double height, ClipperLib::Paths &clip_path, PolygonSink &sink); void handle_aspect_ratio(std::string spec, double &scale_x, double &scale_y, double &off_x, double &off_y, double cols, double rows); } |