[cairo] [PATCH 4/4] Implement PIXMAN_FILTER_GOOD/BEST as separable convolutions
Bill Spitzak
spitzak at gmail.com
Mon Jul 28 19:30:53 PDT 2014
GOOD uses the BOX filter, and uses BILINEAR for all scales > 1/1.35.
BEST uses CATMULL_ROM and fiddles with the filter scale so that it
produces square pixels at large zoom in, but normal filtering between
1 and 2.
---
pixman/pixman-image.c | 267 +++++++++++++++++++++++++++++++++++--------------
1 file changed, 191 insertions(+), 76 deletions(-)
diff --git a/pixman/pixman-image.c b/pixman/pixman-image.c
index 1ff1a49..2a312b1 100644
--- a/pixman/pixman-image.c
+++ b/pixman/pixman-image.c
@@ -28,6 +28,7 @@
#include <stdio.h>
#include <string.h>
#include <assert.h>
+#include <math.h>
#include "pixman-private.h"
@@ -261,6 +262,48 @@ pixman_disable_out_of_bounds_workaround (void)
{
}
+static int
+nearest_works (pixman_fixed_t (*m)[3], uint32_t flags)
+{
+ /* Here we have a chance to optimize BILINEAR filter to NEAREST if
+ * they are equivalent for the currently used transformation matrix.
+ */
+ if (
+ /* affine and integer translation components in matrix ... */
+ ((flags & FAST_PATH_AFFINE_TRANSFORM) &&
+ !pixman_fixed_frac (m[0][2] |
+ m[1][2])) &&
+ (
+ /* ... combined with a simple rotation */
+ (flags & (FAST_PATH_ROTATE_90_TRANSFORM |
+ FAST_PATH_ROTATE_180_TRANSFORM |
+ FAST_PATH_ROTATE_270_TRANSFORM)) ||
+ /* ... or combined with a simple non-rotated translation */
+ (m[0][0] == pixman_fixed_1 &&
+ m[1][1] == pixman_fixed_1 &&
+ m[0][1] == 0 &&
+ m[1][0] == 0)
+ )
+ )
+ {
+ /* FIXME: there are some affine-test failures, showing that
+ * handling of BILINEAR and NEAREST filter is not quite
+ * equivalent when getting close to 32K for the translation
+ * components of the matrix. That's likely some bug, but for
+ * now just skip BILINEAR->NEAREST optimization in this case.
+ */
+ pixman_fixed_t magic_limit = pixman_int_to_fixed (30000);
+ if (m[0][2] <= magic_limit &&
+ m[1][2] <= magic_limit &&
+ m[0][2] >= -magic_limit &&
+ m[1][2] >= -magic_limit)
+ {
+ return 1;
+ }
+ }
+ return 0;
+}
+
static void
compute_image_info (pixman_image_t *image)
{
@@ -274,112 +317,184 @@ compute_image_info (pixman_image_t *image)
FAST_PATH_X_UNIT_POSITIVE |
FAST_PATH_Y_UNIT_ZERO |
FAST_PATH_AFFINE_TRANSFORM);
+ switch (image->common.filter)
+ {
+ case PIXMAN_FILTER_CONVOLUTION:
+ break;
+ case PIXMAN_FILTER_SEPARABLE_CONVOLUTION:
+ flags |= FAST_PATH_SEPARABLE_CONVOLUTION_FILTER;
+ break;
+ default:
+ flags |= (FAST_PATH_NEAREST_FILTER |
+ FAST_PATH_NO_CONVOLUTION_FILTER);
+ break;
+ }
}
else
{
+ pixman_fixed_t (*m)[3] = image->common.transform->matrix;
+ double dx, dy;
+ int xsubsample, ysubsample;
+
flags |= FAST_PATH_HAS_TRANSFORM;
- if (image->common.transform->matrix[2][0] == 0 &&
- image->common.transform->matrix[2][1] == 0 &&
- image->common.transform->matrix[2][2] == pixman_fixed_1)
+ if (m[2][0] == 0 &&
+ m[2][1] == 0 &&
+ m[2][2] == pixman_fixed_1)
{
flags |= FAST_PATH_AFFINE_TRANSFORM;
- if (image->common.transform->matrix[0][1] == 0 &&
- image->common.transform->matrix[1][0] == 0)
+ if (m[0][1] == 0 &&
+ m[1][0] == 0)
{
- if (image->common.transform->matrix[0][0] == -pixman_fixed_1 &&
- image->common.transform->matrix[1][1] == -pixman_fixed_1)
+ if (m[0][0] == -pixman_fixed_1 &&
+ m[1][1] == -pixman_fixed_1)
{
flags |= FAST_PATH_ROTATE_180_TRANSFORM;
}
flags |= FAST_PATH_SCALE_TRANSFORM;
}
- else if (image->common.transform->matrix[0][0] == 0 &&
- image->common.transform->matrix[1][1] == 0)
+ else if (m[0][0] == 0 &&
+ m[1][1] == 0)
{
- pixman_fixed_t m01 = image->common.transform->matrix[0][1];
- pixman_fixed_t m10 = image->common.transform->matrix[1][0];
-
- if (m01 == -pixman_fixed_1 && m10 == pixman_fixed_1)
+ if (m[0][1] == -pixman_fixed_1 &&
+ m[1][0] == pixman_fixed_1)
flags |= FAST_PATH_ROTATE_90_TRANSFORM;
- else if (m01 == pixman_fixed_1 && m10 == -pixman_fixed_1)
+ else if (m[0][1] == pixman_fixed_1 &&
+ m[1][0] == -pixman_fixed_1)
flags |= FAST_PATH_ROTATE_270_TRANSFORM;
}
}
- if (image->common.transform->matrix[0][0] > 0)
+ if (m[0][0] > 0)
flags |= FAST_PATH_X_UNIT_POSITIVE;
- if (image->common.transform->matrix[1][0] == 0)
+ if (m[1][0] == 0)
flags |= FAST_PATH_Y_UNIT_ZERO;
- }
- /* Filter */
- switch (image->common.filter)
- {
- case PIXMAN_FILTER_NEAREST:
- case PIXMAN_FILTER_FAST:
- flags |= (FAST_PATH_NEAREST_FILTER | FAST_PATH_NO_CONVOLUTION_FILTER);
- break;
+ switch (image->common.filter)
+ {
+ case PIXMAN_FILTER_NEAREST:
+ case PIXMAN_FILTER_FAST:
+ flags |= (FAST_PATH_NEAREST_FILTER |
+ FAST_PATH_NO_CONVOLUTION_FILTER);
+ break;
- case PIXMAN_FILTER_BILINEAR:
- case PIXMAN_FILTER_GOOD:
- case PIXMAN_FILTER_BEST:
- flags |= (FAST_PATH_BILINEAR_FILTER | FAST_PATH_NO_CONVOLUTION_FILTER);
+ case PIXMAN_FILTER_BILINEAR:
+ if (nearest_works(m, flags))
+ flags |= (FAST_PATH_NEAREST_FILTER |
+ FAST_PATH_NO_CONVOLUTION_FILTER);
+ else
+ flags |= (FAST_PATH_BILINEAR_FILTER |
+ FAST_PATH_NO_CONVOLUTION_FILTER);
+ break;
- /* Here we have a chance to optimize BILINEAR filter to NEAREST if
- * they are equivalent for the currently used transformation matrix.
- */
- if (flags & FAST_PATH_ID_TRANSFORM)
- {
- flags |= FAST_PATH_NEAREST_FILTER;
- }
- else if (
- /* affine and integer translation components in matrix ... */
- ((flags & FAST_PATH_AFFINE_TRANSFORM) &&
- !pixman_fixed_frac (image->common.transform->matrix[0][2] |
- image->common.transform->matrix[1][2])) &&
- (
- /* ... combined with a simple rotation */
- (flags & (FAST_PATH_ROTATE_90_TRANSFORM |
- FAST_PATH_ROTATE_180_TRANSFORM |
- FAST_PATH_ROTATE_270_TRANSFORM)) ||
- /* ... or combined with a simple non-rotated translation */
- (image->common.transform->matrix[0][0] == pixman_fixed_1 &&
- image->common.transform->matrix[1][1] == pixman_fixed_1 &&
- image->common.transform->matrix[0][1] == 0 &&
- image->common.transform->matrix[1][0] == 0)
- )
- )
- {
- /* FIXME: there are some affine-test failures, showing that
- * handling of BILINEAR and NEAREST filter is not quite
- * equivalent when getting close to 32K for the translation
- * components of the matrix. That's likely some bug, but for
- * now just skip BILINEAR->NEAREST optimization in this case.
- */
- pixman_fixed_t magic_limit = pixman_int_to_fixed (30000);
- if (image->common.transform->matrix[0][2] <= magic_limit &&
- image->common.transform->matrix[1][2] <= magic_limit &&
- image->common.transform->matrix[0][2] >= -magic_limit &&
- image->common.transform->matrix[1][2] >= -magic_limit)
- {
- flags |= FAST_PATH_NEAREST_FILTER;
+ case PIXMAN_FILTER_GOOD:
+ if (nearest_works(m, flags)) {
+ flags |= (FAST_PATH_NEAREST_FILTER |
+ FAST_PATH_NO_CONVOLUTION_FILTER);
+ break;
}
- }
- break;
+ /* Compute derivative at 1,1. Note that division by last row
+ both contributes the w component and converts from fixed. */
+ dy = 1.0 / (m[2][0] + m[2][1] + m[2][2]);
+ dx = hypot (m[0][0] * dy, m[0][1] * dy);
+ dy = hypot (m[1][0] * dy, m[1][1] * dy);
+ /* clamp at 1, and use bilinear for scales near 1 that don't
+ look any better with the box filter. Also clamp off large
+ filters to prevent extreme slowness. */
+ if (dx <= 1.35) {
+ if (dy <= 1.35) {
+ flags |= (FAST_PATH_BILINEAR_FILTER |
+ FAST_PATH_NO_CONVOLUTION_FILTER);
+ break;
+ }
+ dx = 1.0;
+ } else if (dx > 16.0)
+ dx = 16.0;
+ if (dy <= 1.35)
+ dy = 1.0;
+ else if (dy > 16.0)
+ dy = 16.0;
+
+ xsubsample = 0;
+ while (dx * (1 << xsubsample) <= 128.0) xsubsample++;
+ ysubsample = 0;
+ while (dy * (1 << ysubsample) <= 128.0) ysubsample++;
+
+ if (image->common.filter_params)
+ free (image->common.filter_params);
+
+ image->common.filter_params =
+ pixman_filter_create_separable_convolution
+ ( & image->common.n_filter_params,
+ pixman_double_to_fixed(dx),
+ pixman_double_to_fixed(dy),
+ 0, 0,
+ PIXMAN_KERNEL_BOX, PIXMAN_KERNEL_BOX,
+ xsubsample, ysubsample);
+
+ flags |= FAST_PATH_SEPARABLE_CONVOLUTION_FILTER;
+ break;
- case PIXMAN_FILTER_CONVOLUTION:
- break;
+ case PIXMAN_FILTER_BEST:
+ if (nearest_works(m, flags)) {
+ flags |= (FAST_PATH_NEAREST_FILTER |
+ FAST_PATH_NO_CONVOLUTION_FILTER);
+ break;
+ }
+ /* Compute derivative at 1,1. Note that division by last row
+ both contributes the w component and converts from fixed. */
+ dy = 1.0 / (m[2][0] + m[2][1] + m[2][2]);
+ dx = hypot (m[0][0] * dy, m[0][1] * dy);
+ dy = hypot (m[1][0] * dy, m[1][1] * dy);
+
+ /* Clamp to prevent extreme slowness (it also switches to box).
+ When zooming in, blur until pixels are 2x, then use square
+ pixels for further zoom. Add more blur at extreme zoom-in
+ to avoid lots of subsamples */
+ if (dx > 24.0) dx = 24.0;
+ else if (dx < 1.0) {
+ if (dx >= 0.5) dx = 1.0;
+ else if (dx > 1.0/128) dx = 1.0 / (1.0 / dx - 1.0);
+ else dx = 1.0/127;
+ }
+ if (dy > 24.0) dy = 24.0;
+ else if (dy < 1.0) {
+ if (dy >= 0.5) dy = 1.0;
+ else if (dy > 1.0/128) dy = 1.0 / (1.0 / dy - 1.0);
+ else dy = 1.0/127;
+ }
- case PIXMAN_FILTER_SEPARABLE_CONVOLUTION:
- flags |= FAST_PATH_SEPARABLE_CONVOLUTION_FILTER;
- break;
+ xsubsample = 0;
+ while (dx * (1 << xsubsample) <= 128.0) xsubsample++;
+ ysubsample = 0;
+ while (dy * (1 << ysubsample) <= 128.0) ysubsample++;
+
+ image->common.filter_params =
+ pixman_filter_create_separable_convolution
+ ( & image->common.n_filter_params,
+ pixman_double_to_fixed(dx),
+ pixman_double_to_fixed(dy),
+ 0, 0,
+ dx < 16.0 ? PIXMAN_KERNEL_CATMULL_ROM : PIXMAN_KERNEL_BOX,
+ dy < 16.0 ? PIXMAN_KERNEL_CATMULL_ROM : PIXMAN_KERNEL_BOX,
+ xsubsample, ysubsample);
+
+ flags |= FAST_PATH_SEPARABLE_CONVOLUTION_FILTER;
+ break;
- default:
- flags |= FAST_PATH_NO_CONVOLUTION_FILTER;
- break;
+ case PIXMAN_FILTER_CONVOLUTION:
+ break;
+
+ case PIXMAN_FILTER_SEPARABLE_CONVOLUTION:
+ flags |= FAST_PATH_SEPARABLE_CONVOLUTION_FILTER;
+ break;
+
+ default:
+ flags |= FAST_PATH_NO_CONVOLUTION_FILTER;
+ break;
+ }
}
/* Repeat mode */
--
1.7.9.5
More information about the cairo
mailing list