From 5f008f623aed5bb73a407d1c4b1fdb202fee266a Mon Sep 17 00:00:00 2001 From: jaseg Date: Wed, 29 Mar 2023 23:53:01 +0200 Subject: svg-flatten: Add transform decomposition unit tests --- svg-flatten/include/geom2d.hpp | 52 +++++++++++++++++++++++------------ svg-flatten/src/test/nopencv_test.cpp | 28 +++++++++++++++++++ 2 files changed, 62 insertions(+), 18 deletions(-) (limited to 'svg-flatten') diff --git a/svg-flatten/include/geom2d.hpp b/svg-flatten/include/geom2d.hpp index 128da0f..33f2dd8 100644 --- a/svg-flatten/include/geom2d.hpp +++ b/svg-flatten/include/geom2d.hpp @@ -41,8 +41,8 @@ namespace gerbolyze { class xform2d { public: - xform2d(double xx, double yx, double xy, double yy, double x0=0.0, double y0=0.0) : - xx(xx), yx(yx), xy(xy), yy(yy), x0(x0), y0(y0) {} + xform2d(double xx, double xy, double yx, double yy, double x0=0.0, double y0=0.0) : + xx(xx), xy(xy), x0(x0), yx(yx), yy(yy), y0(y0) {} xform2d() : xform2d(1.0, 0.0, 0.0, 1.0) {} @@ -83,6 +83,20 @@ namespace gerbolyze { return *this; } + xform2d &rotate(double theta) { + double s = sin(theta); + double c = cos(theta); + xform2d xf(c, -s, s, c); + transform(xf); + return *this; + } + + xform2d &skew(double m) { + xform2d xf(1, m, 0, 1); + transform(xf); + return *this; + } + xform2d &transform(const xform2d &other) { double n_xx = other.xx * xx + other.yx * xy; double n_yx = other.xx * yx + other.yx * yy; @@ -112,22 +126,22 @@ namespace gerbolyze { return dist_doc / sqrt(xx*xx + xy*xy); } - void decompose() { + std::tuple decompose() { /* FIXME unit tests, especially for degenerate cases! */ if (decomposed) { - return; + return {s_x, s_y, m, theta}; } /* https://math.stackexchange.com/a/3521141 */ /* https://stackoverflow.com/a/70381885 */ - /* xx yx x0 - * xy yy y0 */ - s_x = sqrt(xx*xx + xy*xy); + /* xx xy x0 + * yx yy y0 */ + s_x = sqrt(xx*xx + yx*yx); - if (xx == 0 && xy == 0) { + if (xx == 0 && yx == 0) { theta = 0; } else { - theta = atan2(xy, xx); + theta = atan2(yx, xx); } double f = (xx*yy - xy*yx); @@ -135,16 +149,17 @@ namespace gerbolyze { if (f == 0) { m = 0; } else { - m = (xx*yx + yy*xy) / f; + m = (xx*xy + yy*yx) / f; } - f = xx + m*xy; - if (f == 0) { - f = m*xx - xy; - if (f == 0) { + f = xx + m*yx; + if (fabs(f) < 1e-12) { + f = m*xx - yx; + if (fabs(f) < 1e-12) { s_y = 0; + } else { + s_y = xy*s_x / f; } - s_y = yx*s_x / f; } else { s_y = yy*s_x / f; } @@ -154,6 +169,7 @@ namespace gerbolyze { f_max = fmax(s_x, b); decomposed = true; + return {s_x, s_y, m, theta}; } bool doc2phys_skew_ok(double dist_doc, double rel_tol, double abs_tol) { @@ -280,6 +296,7 @@ namespace gerbolyze { } string dbg_str() { + decompose(); ostringstream os; os << "xform2d< " << setw(5); os << xx << ", " << xy << ", " << x0 << " / "; @@ -291,9 +308,8 @@ namespace gerbolyze { } private: - double xx, yx, - xy, yy, - x0, y0; + double xx, xy, x0, + yx, yy, y0; double theta, m, s_x, s_y; double f_min, f_max; bool decomposed = false; diff --git a/svg-flatten/src/test/nopencv_test.cpp b/svg-flatten/src/test/nopencv_test.cpp index 204ee59..4a4634c 100644 --- a/svg-flatten/src/test/nopencv_test.cpp +++ b/svg-flatten/src/test/nopencv_test.cpp @@ -7,6 +7,7 @@ #include "util.h" #include "nopencv.hpp" +#include "geom2d.hpp" #include #include @@ -330,6 +331,31 @@ MU_TEST(chain_approx_test_two_px_inv) { chain_approx_test("testdata/two- MU_TEST(chain_approx_test_contour_tracing_demo_input) { chain_approx_test("testdata/contour_tracing_demo_input.png"); } +MU_TEST(test_transform_decomposition) { + double scales[] = {0.1, 0.5, 0.9, 0.999999999, 1.0, 1.000000001, 1.1, 1.5, 2.0, 1000}; + double ms[] = {0, -5.0, -1.0, -0.1, 0.1, 0.5, 1.0, 2.0, 5.0, 6.123, 100.0}; + for (double &s_x : scales) { + for (double &s_y : scales) { + for (int i_theta=0; i_theta<25; i_theta++) { + double theta = i_theta * std::numbers::pi / 12.0; + for (double &m : ms) { + xform2d xf; + //cerr << endl << "testing s_x=" << s_x << ", s_y=" << s_y << ", m=" << m << ", theta=" << theta << endl; + xf.rotate(theta).skew(m).scale(s_x, s_y); + //cerr << " -> " << xf.dbg_str() << endl; + const auto [dec_s_x, dec_s_y, dec_m, dec_theta] = xf.decompose(); + mu_assert(fabs(s_x - dec_s_x) < 1e-9, "s_x incorrect"); + mu_assert(fabs(s_y - dec_s_y) < 1e-9, "s_y incorrect"); + mu_assert(fabs(m - dec_m) < 1e-9, "m incorrect"); + double a = dec_theta - theta + std::numbers::pi; + mu_assert(fabs(a - floor(a/(2*std::numbers::pi)) * 2 * std::numbers::pi - std::numbers::pi) < 1e-12, "theta incorrect"); + } + } + } + } +} + + MU_TEST_SUITE(nopencv_contours_suite) { MU_RUN_TEST(test_complex_example_from_paper); @@ -384,6 +410,8 @@ MU_TEST_SUITE(nopencv_contours_suite) { MU_RUN_TEST(chain_approx_test_two_px); MU_RUN_TEST(chain_approx_test_two_px_inv); MU_RUN_TEST(chain_approx_test_contour_tracing_demo_input); + + MU_RUN_TEST(test_transform_decomposition); }; int main(int argc, char **argv) { -- cgit