diff --git a/src/cairo-pattern.c b/src/cairo-pattern.c index c60b1f620..995f49a4d 100644 --- a/src/cairo-pattern.c +++ b/src/cairo-pattern.c @@ -1756,6 +1756,13 @@ _cairo_pattern_analyze_filter (cairo_surface_pattern_t *pattern, return optimized_filter; } + +static double +_pixman_nearest_sample (double d) +{ + return ceil (d - .5); +} + static cairo_int_status_t _cairo_pattern_acquire_surface_for_surface (cairo_surface_pattern_t *pattern, cairo_surface_t *dst, @@ -1782,6 +1789,34 @@ _cairo_pattern_acquire_surface_for_surface (cairo_surface_pattern_t *pattern, attr->x_offset = tx; attr->y_offset = ty; } + else if (attr->filter == CAIRO_FILTER_NEAREST) + { + /* + * For NEAREST, we can remove the fractional translation component + * from the transformation - this ensures that the pattern will always + * hit fast-paths in the backends for simple transformations that + * become (almost) identity, without loss of quality. + */ + attr->matrix = pattern->base.matrix; + attr->matrix.x0 = 0; + attr->matrix.y0 = 0; + if (_cairo_matrix_is_pixel_exact (&attr->matrix)) { + double x1, y1; + + /* The rounding here is rather peculiar as it needs to match the + * rounding performed on the sample coordinate used by pixman. + */ + x1 = _pixman_nearest_sample (pattern->base.matrix.x0); + y1 = _pixman_nearest_sample (pattern->base.matrix.y0); + cairo_matrix_transform_point (&attr->matrix, &x1, &y1); + attr->x_offset = tx = _cairo_lround (x1); + attr->y_offset = ty = _cairo_lround (y1); + } else { + attr->matrix = pattern->base.matrix; + attr->x_offset = attr->y_offset = 0; + tx = ty = 0; + } + } else { attr->matrix = pattern->base.matrix; diff --git a/test/.gitignore b/test/.gitignore index edcf41af7..e7ec82b2f 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -79,6 +79,7 @@ fill-missed-stop fill-rule filter-bilinear-extents filter-nearest-offset +filter-nearest-transformed finer-grained-fallbacks ft-text-antialias-none ft-font-create-for-ft-face diff --git a/test/Makefile.am b/test/Makefile.am index bd7272e42..e244b5505 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -64,6 +64,7 @@ fill-missed-stop$(EXEEXT) \ fill-rule$(EXEEXT) \ filter-bilinear-extents$(EXEEXT) \ filter-nearest-offset$(EXEEXT) \ +filter-nearest-transformed$(EXEEXT) \ finer-grained-fallbacks$(EXEEXT) \ font-face-get-type$(EXEEXT) \ font-matrix-translation$(EXEEXT) \ @@ -479,6 +480,10 @@ REFERENCE_IMAGES = \ filter-nearest-offset-ps3-ref.png \ filter-nearest-offset-svg11-ref.png \ filter-nearest-offset-svg12-ref.png \ + filter-nearest-transformed-ref.png \ + filter-nearest-transformed-pdf-ref.png \ + filter-nearest-transformed-svg11-ref.png \ + filter-nearest-transformed-svg12-ref.png \ finer-grained-fallbacks-ref.png \ finer-grained-fallbacks-rgb24-ref.png \ finer-grained-fallbacks-ps2-ref.png \ diff --git a/test/filter-nearest-transformed-pdf-ref.png b/test/filter-nearest-transformed-pdf-ref.png new file mode 100644 index 000000000..c1e7b57c3 Binary files /dev/null and b/test/filter-nearest-transformed-pdf-ref.png differ diff --git a/test/filter-nearest-transformed-ref.png b/test/filter-nearest-transformed-ref.png new file mode 100644 index 000000000..39fe4b24e Binary files /dev/null and b/test/filter-nearest-transformed-ref.png differ diff --git a/test/filter-nearest-transformed-svg11-ref.png b/test/filter-nearest-transformed-svg11-ref.png new file mode 100644 index 000000000..39ba69f1a Binary files /dev/null and b/test/filter-nearest-transformed-svg11-ref.png differ diff --git a/test/filter-nearest-transformed-svg12-ref.png b/test/filter-nearest-transformed-svg12-ref.png new file mode 100644 index 000000000..39ba69f1a Binary files /dev/null and b/test/filter-nearest-transformed-svg12-ref.png differ diff --git a/test/filter-nearest-transformed.c b/test/filter-nearest-transformed.c new file mode 100644 index 000000000..a6dc85a32 --- /dev/null +++ b/test/filter-nearest-transformed.c @@ -0,0 +1,103 @@ +/* + * Copyright © 2008 Chris Wilson + * + * 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. + * + * Author: Chris Wilson + */ + +#include "cairo-test.h" + +/* + * We wish to check the optimization away of non-fractional translations + * for NEAREST surface patterns under a few transformations. + */ + +static cairo_test_draw_function_t draw; + +static const cairo_test_t test = { + "filter-nearest-transformed", + "Test sample position when drawing transformed images with FILTER_NEAREST", + 14, 14, + draw +}; + +/* A single, black pixel */ +static const uint32_t black_pixel = 0xff000000; + +static cairo_test_status_t +draw (cairo_t *cr, int width, int height) +{ + unsigned int i, j, k; + cairo_surface_t *surface; + cairo_pattern_t *pattern; + const cairo_matrix_t transform[] = { + { 1, 0, 0, 1, 0, 0 }, + { -1, 0, 0, 1, 8, 0 }, + { 1, 0, 0, -1, 0, 8 }, + { -1, 0, 0, -1, 8, 8 }, + }; + const double colour[][3] = { + {0, 0, 0}, + {1, 0, 0}, + {0, 1, 0}, + {0, 0, 1}, + }; + cairo_matrix_t m; + + surface = cairo_image_surface_create_for_data ((uint8_t *) &black_pixel, + CAIRO_FORMAT_ARGB32, + 1, 1, 4); + pattern = cairo_pattern_create_for_surface (surface); + cairo_surface_destroy (surface); + + cairo_pattern_set_filter (pattern, CAIRO_FILTER_NEAREST); + + /* Fill background white */ + cairo_set_source_rgb (cr, 1, 1, 1); + cairo_paint (cr); + + cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE); + + for (k = 0; k < sizeof (transform) / sizeof (transform[0]); k++) { + cairo_set_source_rgb (cr, colour[k][0], colour[k][1], colour[k][2]); + for (j = 4; j <= 6; j++) { + for (i = 4; i <= 6; i++) { + cairo_matrix_init_translate (&m, + -(2*(i-4) + .1*i), + -(2*(j-4) + .1*j)); + cairo_matrix_multiply (&m, &m, &transform[k]); + cairo_pattern_set_matrix (pattern, &m); + cairo_mask (cr, pattern); + } + } + } + + cairo_pattern_destroy (pattern); + + return CAIRO_TEST_SUCCESS; +} + +int +main (void) +{ + return cairo_test (&test); +}