[PATCH 4/4] Implement PIXMAN_FILTER_GOOD/BEST as separable convolutions

Bill Spitzak <[email protected]>
Newsgroups gmane.comp.lib.cairo,gmane.comp.graphics.pixman
Message-ID <[email protected]>
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

-- 
cairo mailing list
[email protected]
http://lists.cairographics.org/mailman/listinfo/cairo
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.