summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKrzysztof KosiƄski <tweenk.pl@gmail.com>2013-09-05 16:02:14 +0100
committerChris Wilson <chris@chris-wilson.co.uk>2013-09-05 16:08:19 +0100
commitfb57ea13e04d82866cbc8e86c83261148bb3e231 (patch)
treebccf3fccae9f051af675da7c614e8a36c75f7c03
parent28ad0f9f3bec65e462e29a1d0b1757a86d16c129 (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.c65
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);
+ }
}
{