svg-flatten: Add transform decomposition unit tests
This commit is contained in:
parent
10669301a1
commit
5f008f623a
2 changed files with 62 additions and 18 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue