From d1358229662309b836c9c0f426ac7d03c0dfb840 Mon Sep 17 00:00:00 2001 From: matyalatte Date: Sat, 27 Jan 2024 15:39:06 +0900 Subject: [PATCH 1/4] fix drawmatrix transform order on Linux and macOS Co-authored-by: Mike Sinkovsky Co-authored-by: Arisotura --- common/matrix.c | 6 ------ common/uipriv.h | 1 - darwin/draw.m | 24 +++++++++++++----------- unix/drawmatrix.c | 24 +++++++++++++----------- 4 files changed, 26 insertions(+), 29 deletions(-) diff --git a/common/matrix.c b/common/matrix.c index 93d4d3579..c4ebfb164 100644 --- a/common/matrix.c +++ b/common/matrix.c @@ -31,12 +31,6 @@ void uiprivFallbackSkew(uiDrawMatrix *m, double x, double y, double xamount, dou uiDrawMatrixMultiply(m, &n); } -void uiprivScaleCenter(double xCenter, double yCenter, double *x, double *y) -{ - *x = xCenter - (*x * xCenter); - *y = yCenter - (*y * yCenter); -} - // the basic algorithm is from cairo // but it's the same algorithm as the transform point, just without M31 and M32 taken into account, so let's just do that instead void uiprivFallbackTransformSize(uiDrawMatrix *m, double *x, double *y) diff --git a/common/uipriv.h b/common/uipriv.h index 6441ada52..667fc3d14 100644 --- a/common/uipriv.h +++ b/common/uipriv.h @@ -56,7 +56,6 @@ extern int uiprivFromScancode(uintptr_t, uiAreaKeyEvent *); // matrix.c extern void uiprivFallbackSkew(uiDrawMatrix *, double, double, double, double); -extern void uiprivScaleCenter(double, double, double *, double *); extern void uiprivFallbackTransformSize(uiDrawMatrix *, double *, double *); // OS-specific text.* files diff --git a/darwin/draw.m b/darwin/draw.m index c6b4efcc8..62b2295d9 100644 --- a/darwin/draw.m +++ b/darwin/draw.m @@ -325,35 +325,37 @@ static void c2m(CGAffineTransform *c, uiDrawMatrix *m) void uiDrawMatrixTranslate(uiDrawMatrix *m, double x, double y) { CGAffineTransform c; + CGAffineTransform tmp; m2c(m, &c); - c = CGAffineTransformTranslate(c, x, y); + tmp = CGAffineTransformMakeTranslation(x, y); + c = CGAffineTransformConcat(c, tmp); c2m(&c, m); } void uiDrawMatrixScale(uiDrawMatrix *m, double xCenter, double yCenter, double x, double y) { CGAffineTransform c; - double xt, yt; + CGAffineTransform tmp; m2c(m, &c); - xt = x; - yt = y; - uiprivScaleCenter(xCenter, yCenter, &xt, &yt); - c = CGAffineTransformTranslate(c, xt, yt); - c = CGAffineTransformScale(c, x, y); - c = CGAffineTransformTranslate(c, -xt, -yt); + tmp = CGAffineTransformMakeTranslation(xCenter, yCenter); + tmp = CGAffineTransformScale(tmp, x, y); + tmp = CGAffineTransformTranslate(tmp, -xCenter, -yCenter); + c = CGAffineTransformConcat(c, tmp); c2m(&c, m); } void uiDrawMatrixRotate(uiDrawMatrix *m, double x, double y, double amount) { CGAffineTransform c; + CGAffineTransform tmp; m2c(m, &c); - c = CGAffineTransformTranslate(c, x, y); - c = CGAffineTransformRotate(c, amount); - c = CGAffineTransformTranslate(c, -x, -y); + tmp = CGAffineTransformMakeTranslation(x, y); + tmp = CGAffineTransformRotate(tmp, amount); + tmp = CGAffineTransformTranslate(tmp, -x, -y); + c = CGAffineTransformConcat(c, tmp); c2m(&c, m); } diff --git a/unix/drawmatrix.c b/unix/drawmatrix.c index ffb4db34b..2addbeef8 100644 --- a/unix/drawmatrix.c +++ b/unix/drawmatrix.c @@ -31,35 +31,37 @@ static void c2m(cairo_matrix_t *c, uiDrawMatrix *m) void uiDrawMatrixTranslate(uiDrawMatrix *m, double x, double y) { cairo_matrix_t c; + cairo_matrix_t tmp; m2c(m, &c); - cairo_matrix_translate(&c, x, y); + cairo_matrix_init_translate(&tmp, x, y); + cairo_matrix_multiply(&c, &c, &tmp); c2m(&c, m); } void uiDrawMatrixScale(uiDrawMatrix *m, double xCenter, double yCenter, double x, double y) { cairo_matrix_t c; - double xt, yt; + cairo_matrix_t tmp; m2c(m, &c); - xt = x; - yt = y; - uiprivScaleCenter(xCenter, yCenter, &xt, &yt); - cairo_matrix_translate(&c, xt, yt); - cairo_matrix_scale(&c, x, y); - cairo_matrix_translate(&c, -xt, -yt); + cairo_matrix_init_translate(&tmp, xCenter, yCenter); + cairo_matrix_scale(&tmp, x, y); + cairo_matrix_translate(&tmp, -xCenter, -yCenter); + cairo_matrix_multiply(&c, &c, &tmp); c2m(&c, m); } void uiDrawMatrixRotate(uiDrawMatrix *m, double x, double y, double amount) { cairo_matrix_t c; + cairo_matrix_t tmp; m2c(m, &c); - cairo_matrix_translate(&c, x, y); - cairo_matrix_rotate(&c, amount); - cairo_matrix_translate(&c, -x, -y); + cairo_matrix_init_translate(&tmp, x, y); + cairo_matrix_rotate(&tmp, amount); + cairo_matrix_translate(&tmp, -x, -y); + cairo_matrix_multiply(&c, &c, &tmp); c2m(&c, m); } From 1eac8a40a632022c9fc99bf722d97dcfd259bb4a Mon Sep 17 00:00:00 2001 From: matyalatte Date: Tue, 30 Jan 2024 21:02:02 +0900 Subject: [PATCH 2/4] add unit tests for uiDrawMatrix --- test/unit/drawmatrix.c | 215 +++++++++++++++++++++++++++++++++++++++++ test/unit/main.c | 1 + test/unit/meson.build | 1 + test/unit/unit.h | 1 + 4 files changed, 218 insertions(+) create mode 100644 test/unit/drawmatrix.c diff --git a/test/unit/drawmatrix.c b/test/unit/drawmatrix.c new file mode 100644 index 000000000..f1b3c31c1 --- /dev/null +++ b/test/unit/drawmatrix.c @@ -0,0 +1,215 @@ +#include "unit.h" + +#ifndef ABS +#define ABS(a) ((a) >= 0 ? (a) : -(a)) +#endif +#ifndef MAX +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#endif +#define EPSILON 0.00001 + +static int compareDouble(double a, double b, double epsilon) +{ + double diff = ABS(a - b); + double eps_scale = MAX(1.0, MAX(ABS(a), ABS(b))); + return diff <= epsilon * eps_scale; +} + +// It's not defined in cmocka.h but exists in cmocka.c +void cm_print_error(const char * const format, ...); + +// Check if a == b without aborting the test. +static int expectDoubleEqual(double a, double b, const char* error_prefix) +{ + int equal = compareDouble(a, b, EPSILON); + if (!equal) { + if (error_prefix == NULL) + cm_print_error("%f != %f\n", a, b); + else + cm_print_error("%s%f != %f\n", error_prefix, a, b); + } + return equal; +} + +// Assertion for uiDrawMatrix. +// It works in the same way as the assert_* variants of cmocka. +#define assertMatrixEqual(a, b) \ + _assertMatrixEqual((uiDrawMatrix*)a, (uiDrawMatrix*)b, __FILE__, __LINE__) + +static void _assertMatrixEqual(uiDrawMatrix *a, uiDrawMatrix *b, const char * const file, const int line) +{ + int equal = 1; + equal &= expectDoubleEqual(a->M11, b->M11, "M11: "); + equal &= expectDoubleEqual(a->M12, b->M12, "M12: "); + equal &= expectDoubleEqual(a->M21, b->M21, "M21: "); + equal &= expectDoubleEqual(a->M22, b->M22, "M22: "); + equal &= expectDoubleEqual(a->M31, b->M31, "M31: "); + equal &= expectDoubleEqual(a->M32, b->M32, "M32: "); + if (!equal) + _fail(file, line); +} + +static void drawMatrixIdentity(void **state) +{ + uiDrawMatrix *m = *state; + uiDrawMatrix expected = { + 1.0, 0.0, + 0.0, 1.0, + 0.0, 0.0 + }; + + assertMatrixEqual(&expected, m); +} + +static void drawMatrixTranslate(void **state) +{ + uiDrawMatrix *m = *state; + double tx = 0.5; + double ty = 0.25; + uiDrawMatrixTranslate(m, tx, ty); + + uiDrawMatrix expected = { + 1.0, 0.0, + 0.0, 1.0, + tx, ty + }; + + assertMatrixEqual(&expected, m); +} + +static void drawMatrixScale(void **state) +{ + uiDrawMatrix *m = *state; + double cx = 0.25; + double cy = 0.125; + double sx = 0.5; + double sy = 0.25; + uiDrawMatrixScale(m, cx, cy, sx, sy); + + uiDrawMatrix expected = { + sx, 0.0, + 0.0, sy, + -cx * sx + cx, + -cy * sy + cy + }; + + assertMatrixEqual(&expected, m); +} + +#define THETA uiPi / 6 +#define COS 1.73205080757 / 2 +#define SIN 0.5 + +static void drawMatrixRotate(void **state) +{ + uiDrawMatrix *m = *state; + double cx = 0.25; + double cy = 0.125; + uiDrawMatrixRotate(m, cx, cy, THETA); + + uiDrawMatrix expected = { + COS, SIN, + -SIN, COS, + -cx * COS + cy * SIN + cx, + -cx * SIN - cy * COS + cy + }; + + assertMatrixEqual(&expected, m); +} + +static void drawMatrixTRS(void **state) +{ + uiDrawMatrix *m = *state; + + double tx = 0.5; + double ty = 0.25; + double sx = 0.3; + double sy = 0.1; + uiDrawMatrixTranslate(m, tx, ty); + uiDrawMatrixRotate(m, tx, ty, THETA); + uiDrawMatrixScale(m, tx, ty, sx, sy); + + uiDrawMatrix expected = { + COS * sx, SIN * sy, + -SIN * sx, COS * sy, + tx, ty, + }; + + assertMatrixEqual(&expected, m); +} + +static void drawMatrixMultiply(void **state) +{ + // Test the same transform as drawMatrixTRS with uiDrawMatrixMultiply + uiDrawMatrix *m = *state; + + uiDrawMatrix t; + double tx = 0.5; + double ty = 0.25; + uiDrawMatrixSetIdentity(&t); + uiDrawMatrixTranslate(&t, tx, ty); + + uiDrawMatrix r; + uiDrawMatrixSetIdentity(&r); + uiDrawMatrixRotate(&r, tx, ty, THETA); + + uiDrawMatrix s; + double sx = 0.3; + double sy = 0.1; + uiDrawMatrixSetIdentity(&s); + uiDrawMatrixScale(&s, tx, ty, sx, sy); + + uiDrawMatrixMultiply(m, &t); + uiDrawMatrixMultiply(m, &r); + uiDrawMatrixMultiply(m, &s); + + uiDrawMatrix expected = { + COS * sx, SIN * sy, + -SIN * sx, COS * sy, + tx, ty, + }; + + assertMatrixEqual(&expected, m); +} + +static int drawMatrixTestsSetup(void **state) +{ + *state = malloc(sizeof(uiDrawMatrix)); + assert_non_null(*state); + return 0; +} + +static int drawMatrixTestsTeardown(void **state) +{ + free(*state); + return 0; +} + +int drawMatrixTestSetup(void **state) +{ + uiDrawMatrix *m = *state; + uiDrawMatrixSetIdentity(m); + return 0; +} + +int drawMatrixTestTeardown(void **state) +{ + return 0; +} + +#define drawMatrixUnitTest(f) cmocka_unit_test_setup_teardown((f), \ + drawMatrixTestSetup, drawMatrixTestTeardown) + +int drawMatrixRunUnitTests(void) +{ + const struct CMUnitTest tests[] = { + drawMatrixUnitTest(drawMatrixIdentity), + drawMatrixUnitTest(drawMatrixTranslate), + drawMatrixUnitTest(drawMatrixScale), + drawMatrixUnitTest(drawMatrixRotate), + drawMatrixUnitTest(drawMatrixTRS), + drawMatrixUnitTest(drawMatrixMultiply), + }; + + return cmocka_run_group_tests_name("uiDrawMatrix", tests, drawMatrixTestsSetup, drawMatrixTestsTeardown); +} diff --git a/test/unit/main.c b/test/unit/main.c index bd3b1b3ef..5d0712add 100644 --- a/test/unit/main.c +++ b/test/unit/main.c @@ -67,6 +67,7 @@ int main(void) { radioButtonsRunUnitTests }, { entryRunUnitTests }, { progressBarRunUnitTests }, + { drawMatrixRunUnitTests }, }; for (i = 0; i < sizeof(unitTests)/sizeof(*unitTests); ++i) { diff --git a/test/unit/meson.build b/test/unit/meson.build index 0a8dcb7b7..f3991e3a4 100644 --- a/test/unit/meson.build +++ b/test/unit/meson.build @@ -14,6 +14,7 @@ libui_unit_sources = [ 'entry.c', 'menu.c', 'progressbar.c', + 'drawmatrix.c', ] if libui_OS == 'windows' diff --git a/test/unit/unit.h b/test/unit/unit.h index 4f76aa12a..d06c65a8d 100644 --- a/test/unit/unit.h +++ b/test/unit/unit.h @@ -24,6 +24,7 @@ int radioButtonsRunUnitTests(void); int entryRunUnitTests(void); int menuRunUnitTests(void); int progressBarRunUnitTests(void); +int drawMatrixRunUnitTests(void); /** * Helper for general setup/teardown of controls embedded in a window. From 281de495378adf669390203952b05b0124e32270 Mon Sep 17 00:00:00 2001 From: matyalatte Date: Wed, 31 Jan 2024 21:41:57 +0900 Subject: [PATCH 3/4] convert indentation to tabs --- test/unit/drawmatrix.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/unit/drawmatrix.c b/test/unit/drawmatrix.c index f1b3c31c1..28078096f 100644 --- a/test/unit/drawmatrix.c +++ b/test/unit/drawmatrix.c @@ -24,10 +24,10 @@ static int expectDoubleEqual(double a, double b, const char* error_prefix) int equal = compareDouble(a, b, EPSILON); if (!equal) { if (error_prefix == NULL) - cm_print_error("%f != %f\n", a, b); + cm_print_error("%f != %f\n", a, b); else - cm_print_error("%s%f != %f\n", error_prefix, a, b); - } + cm_print_error("%s%f != %f\n", error_prefix, a, b); + } return equal; } From a99eb965d8be3988da61453197615138cd1b28a8 Mon Sep 17 00:00:00 2001 From: matyalatte Date: Sun, 4 Feb 2024 20:46:35 +0900 Subject: [PATCH 4/4] fix error messages for assertMatrixEqual Co-authored-by: cody271 --- test/unit/drawmatrix.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/test/unit/drawmatrix.c b/test/unit/drawmatrix.c index 28078096f..2e263fc01 100644 --- a/test/unit/drawmatrix.c +++ b/test/unit/drawmatrix.c @@ -19,14 +19,16 @@ static int compareDouble(double a, double b, double epsilon) void cm_print_error(const char * const format, ...); // Check if a == b without aborting the test. -static int expectDoubleEqual(double a, double b, const char* error_prefix) +static int expectDoubleEqual(double a, double b, int first_error, const char* error_prefix) { + // Function only used by assertMatrixEqual() macro below right now. + const char* func_prefix = first_error ? "(assertMatrixEqual)\n" : ""; int equal = compareDouble(a, b, EPSILON); if (!equal) { if (error_prefix == NULL) - cm_print_error("%f != %f\n", a, b); + cm_print_error("%s%f != %f\n", func_prefix, a, b); else - cm_print_error("%s%f != %f\n", error_prefix, a, b); + cm_print_error("%s%s%f != %f\n", func_prefix, error_prefix, a, b); } return equal; } @@ -39,12 +41,12 @@ static int expectDoubleEqual(double a, double b, const char* error_prefix) static void _assertMatrixEqual(uiDrawMatrix *a, uiDrawMatrix *b, const char * const file, const int line) { int equal = 1; - equal &= expectDoubleEqual(a->M11, b->M11, "M11: "); - equal &= expectDoubleEqual(a->M12, b->M12, "M12: "); - equal &= expectDoubleEqual(a->M21, b->M21, "M21: "); - equal &= expectDoubleEqual(a->M22, b->M22, "M22: "); - equal &= expectDoubleEqual(a->M31, b->M31, "M31: "); - equal &= expectDoubleEqual(a->M32, b->M32, "M32: "); + equal &= expectDoubleEqual(a->M11, b->M11, equal, "M11: "); + equal &= expectDoubleEqual(a->M12, b->M12, equal, "M12: "); + equal &= expectDoubleEqual(a->M21, b->M21, equal, "M21: "); + equal &= expectDoubleEqual(a->M22, b->M22, equal, "M22: "); + equal &= expectDoubleEqual(a->M31, b->M31, equal, "M31: "); + equal &= expectDoubleEqual(a->M32, b->M32, equal, "M32: "); if (!equal) _fail(file, line); }