aboutsummaryrefslogtreecommitdiff
path: root/svg-flatten
diff options
context:
space:
mode:
authorjaseg <git@jaseg.de>2023-03-29 23:53:01 +0200
committerjaseg <git@jaseg.de>2023-03-29 23:53:01 +0200
commit5f008f623aed5bb73a407d1c4b1fdb202fee266a (patch)
tree5836abb62acf95936b9b1c181083265072054b07 /svg-flatten
parent10669301a14caff853df0b97495a03872e4f507f (diff)
downloadgerbolyze-5f008f623aed5bb73a407d1c4b1fdb202fee266a.tar.gz
gerbolyze-5f008f623aed5bb73a407d1c4b1fdb202fee266a.tar.bz2
gerbolyze-5f008f623aed5bb73a407d1c4b1fdb202fee266a.zip
svg-flatten: Add transform decomposition unit tests
Diffstat (limited to 'svg-flatten')
-rw-r--r--svg-flatten/include/geom2d.hpp52
-rw-r--r--svg-flatten/src/test/nopencv_test.cpp28
2 files changed, 62 insertions, 18 deletions
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<double, double, double, double> 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 <subprocess.h>
#include <minunit.h>
@@ -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) {