aboutsummaryrefslogtreecommitdiff
path: root/svg-flatten
diff options
context:
space:
mode:
authorjaseg <git@jaseg.de>2021-06-02 15:05:36 +0200
committerjaseg <git@jaseg.de>2021-06-02 15:05:36 +0200
commit6cca4a3278c30fb35e97dd0017c5a5a25a78bcf0 (patch)
treef072376d37410477b809fc48af61666cca4ca1c7 /svg-flatten
parent3e58a4228bff78e2c51136312337fc4b47efa587 (diff)
downloadgerbolyze-6cca4a3278c30fb35e97dd0017c5a5a25a78bcf0.tar.gz
gerbolyze-6cca4a3278c30fb35e97dd0017c5a5a25a78bcf0.tar.bz2
gerbolyze-6cca4a3278c30fb35e97dd0017c5a5a25a78bcf0.zip
Port svg-flatten to nopencv
Diffstat (limited to 'svg-flatten')
-rw-r--r--svg-flatten/Makefile8
-rw-r--r--svg-flatten/src/nopencv.cpp101
-rw-r--r--svg-flatten/src/nopencv.hpp40
-rw-r--r--svg-flatten/src/nopencv_test.cpp11
-rw-r--r--svg-flatten/src/vec_core.cpp141
-rw-r--r--svg-flatten/src/vec_core.h1
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);
}