svg-flatten: Add transform decomposition unit tests

This commit is contained in:
jaseg 2023-03-29 23:53:01 +02:00
parent 10669301a1
commit 5f008f623a
2 changed files with 62 additions and 18 deletions

View file

@ -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;

View file

@ -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) {