diff --git a/src/cairo-matrix.c b/src/cairo-matrix.c index f2be48b74..2ccae0ebd 100644 --- a/src/cairo-matrix.c +++ b/src/cairo-matrix.c @@ -728,6 +728,11 @@ _cairo_matrix_is_integer_translation (const cairo_matrix_t *matrix, #define SCALING_EPSILON _cairo_fixed_to_double(1) +static cairo_bool_t +within_scaling_epsilon(double a, double b) { + return fabs(a - b) < SCALING_EPSILON; +} + /* This only returns true if the matrix is 90 degree rotations or * flips. It appears calling code is relying on this. It will return * false for other rotations even if the scale is one. Approximations @@ -737,21 +742,20 @@ _cairo_matrix_is_integer_translation (const cairo_matrix_t *matrix, cairo_bool_t _cairo_matrix_has_unity_scale (const cairo_matrix_t *matrix) { - /* check that the determinant is near +/-1 */ - double det = _cairo_matrix_compute_determinant (matrix); - if (fabs (det * det - 1.0) < SCALING_EPSILON) { - /* check that one axis is close to zero */ - if (fabs (matrix->xy) < SCALING_EPSILON && - fabs (matrix->yx) < SCALING_EPSILON) - return TRUE; - if (fabs (matrix->xx) < SCALING_EPSILON && - fabs (matrix->yy) < SCALING_EPSILON) - return TRUE; - /* If rotations are allowed then it must instead test for - * orthogonality. This is xx*xy+yx*yy ~= 0. - */ + if (within_scaling_epsilon(matrix->xy, 0.0) && within_scaling_epsilon(matrix->yx, 0.0)) { + if (! (within_scaling_epsilon(matrix->xx, 1.0) || within_scaling_epsilon(matrix->xx, -1.0))) + return FALSE; + if (! (within_scaling_epsilon(matrix->yy, 1.0) || within_scaling_epsilon(matrix->yy, -1.0))) + return FALSE; + } else if (within_scaling_epsilon(matrix->xx, 0.0) && within_scaling_epsilon(matrix->yy, 0.0)) { + if (! (within_scaling_epsilon(matrix->xy, 1.0) || within_scaling_epsilon(matrix->xy, -1.0))) + return FALSE; + if (! (within_scaling_epsilon(matrix->yx, 1.0) || within_scaling_epsilon(matrix->yx, -1.0))) + return FALSE; + } else { + return FALSE; } - return FALSE; + return TRUE; } /* By pixel exact here, we mean a matrix that is composed only of diff --git a/test/bug-927.c b/test/bug-927.c new file mode 100644 index 000000000..85003f590 --- /dev/null +++ b/test/bug-927.c @@ -0,0 +1,58 @@ +/* + * Copyright © 2026 Uli Schlachter + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "cairo-test.h" + +static cairo_test_status_t +draw (cairo_t *cr, int width, int height) +{ + double scale = 3; + cairo_matrix_t matrix = { + 1 / scale, 0, + 0, scale, + 0, -100 + }; + + cairo_set_line_width (cr, 20); + cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND); + + cairo_set_source_rgb (cr, 0, 0, 0); + cairo_paint (cr); + + cairo_set_source_rgb (cr, 1, 1, 1); + cairo_set_matrix (cr, &matrix); + cairo_move_to (cr, -50, -50); + cairo_line_to (cr, 50, 50); + cairo_line_to (cr, 150, -50); + cairo_stroke (cr); + + return CAIRO_TEST_SUCCESS; +} + +CAIRO_TEST (bug_927, + "Bug 927 (_cairo_matrix_has_unity_scale incorrectly returning true)", + "matrix", /* keywords */ + NULL, /* requirements */ + 30, 100, + NULL, draw) diff --git a/test/meson.build b/test/meson.build index 3b460afc5..a543d5d75 100644 --- a/test/meson.build +++ b/test/meson.build @@ -30,6 +30,7 @@ test_sources = [ 'bug-431.c', 'bug-448.c', 'bug-535.c', + 'bug-927.c', 'bug-51910.c', 'bug-75705.c', 'bug-84115.c', diff --git a/test/reference/bug-927.image16.ref.png b/test/reference/bug-927.image16.ref.png new file mode 100644 index 000000000..c6f79441c Binary files /dev/null and b/test/reference/bug-927.image16.ref.png differ diff --git a/test/reference/bug-927.ps2.ref.png b/test/reference/bug-927.ps2.ref.png new file mode 100644 index 000000000..1be717f15 Binary files /dev/null and b/test/reference/bug-927.ps2.ref.png differ diff --git a/test/reference/bug-927.ps3.ref.png b/test/reference/bug-927.ps3.ref.png new file mode 100644 index 000000000..1be717f15 Binary files /dev/null and b/test/reference/bug-927.ps3.ref.png differ diff --git a/test/reference/bug-927.ref.png b/test/reference/bug-927.ref.png new file mode 100644 index 000000000..81ece3565 Binary files /dev/null and b/test/reference/bug-927.ref.png differ diff --git a/test/reference/bug-927.script.xfail.png b/test/reference/bug-927.script.xfail.png new file mode 100644 index 000000000..4d8035eb3 Binary files /dev/null and b/test/reference/bug-927.script.xfail.png differ diff --git a/test/reference/bug-927.svg11.xfail.png b/test/reference/bug-927.svg11.xfail.png new file mode 100644 index 000000000..4d8035eb3 Binary files /dev/null and b/test/reference/bug-927.svg11.xfail.png differ