Skip to content

Commit

Permalink
Merge pull request libui-ng#265 from matyalatte/fix_matrix_scale
Browse files Browse the repository at this point in the history
fix uiDrawMatrixScale on Linux and macOS
  • Loading branch information
cody271 authored Feb 4, 2024
2 parents 8bf8964 + a99eb96 commit 0258d00
Show file tree
Hide file tree
Showing 8 changed files with 246 additions and 29 deletions.
6 changes: 0 additions & 6 deletions common/matrix.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 0 additions & 1 deletion common/uipriv.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
24 changes: 13 additions & 11 deletions darwin/draw.m
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
217 changes: 217 additions & 0 deletions test/unit/drawmatrix.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
#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, 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("%s%f != %f\n", func_prefix, a, b);
else
cm_print_error("%s%s%f != %f\n", func_prefix, 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, 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);
}

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);
}
1 change: 1 addition & 0 deletions test/unit/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ int main(void)
{ radioButtonsRunUnitTests },
{ entryRunUnitTests },
{ progressBarRunUnitTests },
{ drawMatrixRunUnitTests },
};

for (i = 0; i < sizeof(unitTests)/sizeof(*unitTests); ++i) {
Expand Down
1 change: 1 addition & 0 deletions test/unit/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ libui_unit_sources = [
'entry.c',
'menu.c',
'progressbar.c',
'drawmatrix.c',
]

if libui_OS == 'windows'
Expand Down
1 change: 1 addition & 0 deletions test/unit/unit.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
24 changes: 13 additions & 11 deletions unix/drawmatrix.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down

0 comments on commit 0258d00

Please sign in to comment.