diff options
author | Krzysztof KosiĆski <tweenk.pl@gmail.com> | 2013-09-05 16:02:14 +0100 |
---|---|---|
committer | Chris Wilson <chris@chris-wilson.co.uk> | 2013-09-05 16:08:19 +0100 |
commit | fb57ea13e04d82866cbc8e86c83261148bb3e231 (patch) | |
tree | bccf3fccae9f051af675da7c614e8a36c75f7c03 | |
parent | 28ad0f9f3bec65e462e29a1d0b1757a86d16c129 (diff) |
image: Use convolution filters for sample reconstruction when downscaling
I had a look at how complex would it be to add correct downscaling to
Cairo now that Pixman supports convolution filters. It turns out it
this is rather easy. Here is an initial, minimal attempt. It uses
convolution filters only if the image is being downscaled by more than
half a pixel in at least one dimension.
Some discussion:
1. The sampling and reconstruction kernels are picked in a way that
gives comparable quality when upscaling and downscaling. I paired box
sampling with bilinear reconstruction and impulse (point) sampling
with box reconstruction. This gives the expected result for NEAREST
filter. BEST filter uses Lanczos3 for both kernels.
> Do we need to use a reconstruction filter for NEAREST at all? Or maybe
> differentiate between NEAREST and FAST in that case?
If impulse (point) sampling is used, there must be some reconstruction
filter, otherwise no image is produced. That's because the sampling
grid does not match the data grid, and since there is no
reconstruction filter, values between data points are undefined. The
alternative is to use box sampling + no reconstruction.
2. Subsampling bits are always set to 1, since this doesn't seem to
affect quality at all.
3. I am not sure whether this code works correctly for matrices with a
skew component. It should be OK for any combination of scale, rotation
and translation.
4. This patch causes new failures in the test suite:
- recording-surface*: possibly an effect of improved quality.
- surface-pattern-scale-down*, surface-pattern-big-scale-down: the
reference images should be updated.
- pthread-same-source: I have no idea why this is failing, since this
test shouldn't even trigger the new code.
- large-source-roi: this test attempts to downscale an image which is
30000 pixels wide down to 7 pixels. The filter parameters seem to be
created correctly, but they might trigger an overflow somewhere in the
convolution code; the output rectangle is white instead of red, as if
nothing was drawn.
- device-offset-scale: there are subtle differences which look like
convolution-related smoothing; I'm not sure whether this is OK or not.
-rw-r--r-- | src/cairo-image-source.c | 65 |
1 files changed, 55 insertions, 10 deletions
diff --git a/src/cairo-image-source.c b/src/cairo-image-source.c index c5bd228c4..661bc1068 100644 --- a/src/cairo-image-source.c +++ b/src/cairo-image-source.c @@ -554,24 +554,42 @@ _pixman_image_set_properties (pixman_image_t *pixman_image, } else { + double scale_x, scale_y; + int shrink_x, shrink_y; pixman_filter_t pixman_filter; + pixman_kernel_t pixman_kernel_sample, pixman_kernel_reconstruct; + + /* Compute scale factors as the length of basis vectors transformed by + * the pattern matrix. These scale factors are from user to pattern space, + * and as such they are greater than 1.0 for downscaling and less than 1.0 + * for upscaling. + * TODO: this approach may not be completely correct if the matrix + * contains a skew component. */ + scale_x = hypot (pattern->matrix.xx, pattern->matrix.yx); + scale_y = hypot (pattern->matrix.yx, pattern->matrix.yy); + + /* Use convolution filtering if the transformation shrinks the image + * by more than half a pixel */ + shrink_x = (extents->width / scale_x - extents->width) < -0.5; + shrink_y = (extents->height / scale_y - extents->height) < -0.5; switch (pattern->filter) { case CAIRO_FILTER_FAST: - pixman_filter = PIXMAN_FILTER_FAST; - break; - case CAIRO_FILTER_GOOD: - pixman_filter = PIXMAN_FILTER_GOOD; - break; - case CAIRO_FILTER_BEST: - pixman_filter = PIXMAN_FILTER_BEST; - break; case CAIRO_FILTER_NEAREST: pixman_filter = PIXMAN_FILTER_NEAREST; + pixman_kernel_sample = PIXMAN_KERNEL_IMPULSE; + pixman_kernel_reconstruct = PIXMAN_KERNEL_BOX; break; + case CAIRO_FILTER_GOOD: case CAIRO_FILTER_BILINEAR: pixman_filter = PIXMAN_FILTER_BILINEAR; + pixman_kernel_sample = PIXMAN_KERNEL_BOX; + pixman_kernel_reconstruct = PIXMAN_KERNEL_LINEAR; break; + case CAIRO_FILTER_BEST: + pixman_filter = PIXMAN_FILTER_BEST; + pixman_kernel_sample = PIXMAN_KERNEL_LANCZOS3; + pixman_kernel_reconstruct = PIXMAN_KERNEL_LANCZOS3; case CAIRO_FILTER_GAUSSIAN: /* XXX: The GAUSSIAN value has no implementation in cairo * whatsoever, so it was really a mistake to have it in the @@ -579,10 +597,37 @@ _pixman_image_set_properties (pixman_image_t *pixman_image, * else inventing semantics and providing an actual * implementation for it. */ default: - pixman_filter = PIXMAN_FILTER_BEST; + pixman_filter = PIXMAN_FILTER_BILINEAR; + pixman_kernel_sample = PIXMAN_KERNEL_BOX; + pixman_kernel_reconstruct = PIXMAN_KERNEL_LINEAR; } - pixman_image_set_filter (pixman_image, pixman_filter, NULL, 0); + if (pixman_filter != PIXMAN_FILTER_NEAREST && (shrink_x || shrink_y)) { + pixman_kernel_t sampling_kernel_x, sampling_kernel_y; + int n_params; + pixman_fixed_t *params; + + sampling_kernel_x = shrink_x ? pixman_kernel_sample : PIXMAN_KERNEL_IMPULSE; + sampling_kernel_y = shrink_y ? pixman_kernel_sample : PIXMAN_KERNEL_IMPULSE; + + n_params = 0; + params = pixman_filter_create_separable_convolution (&n_params, + scale_x * 65536.0 + 0.5, + scale_y * 65536.0 + 0.5, + pixman_kernel_reconstruct, + pixman_kernel_reconstruct, + sampling_kernel_x, + sampling_kernel_y, + 1, 1); + + pixman_image_set_filter (pixman_image, + PIXMAN_FILTER_SEPARABLE_CONVOLUTION, + params, n_params); + + free (params); + } else { + pixman_image_set_filter (pixman_image, pixman_filter, NULL, 0); + } } { |