[cairo] [PATCH 6/6] image: Corrected extents calculations

Bill Spitzak spitzak at gmail.com
Thu Oct 9 19:46:16 PDT 2014


New implementations of _cairo_pattern_sampled_area and _cairo_pattern_get_extents
which produce a more accurate bounding box. These do not depend on side-effects
of analyze_filter, can handle different horizontal and vertical scales, filters
wider than 1 for down-scaling, and compute a somewhat tighter bounding box
in most cases.

I removed the pad output of _cairo_pattern_analyze_filter as it is unused.
---
 src/cairo-composite-rectangles.c |    2 +-
 src/cairo-pattern-private.h      |    5 +-
 src/cairo-pattern.c              |  275 +++++++++++++++++++++++---------------
 src/cairo-xlib-core-compositor.c |    4 +-
 src/cairoint.h                   |    4 -
 src/drm/cairo-drm-i915-shader.c  |   39 +-----
 6 files changed, 176 insertions(+), 153 deletions(-)

diff --git a/src/cairo-composite-rectangles.c b/src/cairo-composite-rectangles.c
index e6639d0..6c3e97d 100644
--- a/src/cairo-composite-rectangles.c
+++ b/src/cairo-composite-rectangles.c
@@ -57,7 +57,7 @@ _cairo_composite_reduce_pattern (const cairo_pattern_t *src,
     if (dst->base.type == CAIRO_PATTERN_TYPE_SOLID)
 	return;
 
-    dst->base.filter = _cairo_pattern_analyze_filter (&dst->base, NULL),
+    dst->base.filter = _cairo_pattern_analyze_filter (&dst->base);
 
     tx = ty = 0;
     if (_cairo_matrix_is_pixman_translation (&dst->base.matrix,
diff --git a/src/cairo-pattern-private.h b/src/cairo-pattern-private.h
index bbcfadd..be8ab9f 100644
--- a/src/cairo-pattern-private.h
+++ b/src/cairo-pattern-private.h
@@ -289,7 +289,7 @@ _cairo_mesh_pattern_coord_box (const cairo_mesh_pattern_t *mesh,
 			       double                     *out_xmax,
 			       double                     *out_ymax);
 
-cairo_private_no_warn cairo_filter_t
+cairo_private void
 _cairo_pattern_sampled_area (const cairo_pattern_t *pattern,
 			     const cairo_rectangle_int_t *extents,
 			     cairo_rectangle_int_t *sample);
@@ -328,6 +328,9 @@ cairo_private cairo_bool_t
 _cairo_pattern_equal (const cairo_pattern_t *a,
 		      const cairo_pattern_t *b);
 
+cairo_private cairo_filter_t
+_cairo_pattern_analyze_filter (const cairo_pattern_t *pattern);
+
 /* cairo-mesh-pattern-rasterizer.c */
 
 cairo_private void
diff --git a/src/cairo-pattern.c b/src/cairo-pattern.c
index 1a93d2b..e16823d 100644
--- a/src/cairo-pattern.c
+++ b/src/cairo-pattern.c
@@ -28,8 +28,6 @@
  *	    Carl Worth <cworth at cworth.org>
  */
 
-#include "cairoint.h"
-
 #include "cairo-array-private.h"
 #include "cairo-error-private.h"
 #include "cairo-freed-pool-private.h"
@@ -3361,24 +3359,13 @@ use_bilinear(double x, double y, double t)
 /**
  * _cairo_pattern_analyze_filter:
  * @pattern: surface pattern
- * @pad_out: location to store necessary padding in the source image, or %NULL
  * Returns: the optimized #cairo_filter_t to use with @pattern.
  *
- * Analyze the filter to determine how much extra needs to be sampled
- * from the source image to account for the filter radius and whether
- * we can optimize the filter to a simpler value.
- *
- * XXX: We don't actually have any way of querying the backend for
- *      the filter radius, so we just guess base on what we know that
- *      backends do currently (see bug #10508)
+ * Possibly optimize the filter to a simpler value depending on transformation
  **/
 cairo_filter_t
-_cairo_pattern_analyze_filter (const cairo_pattern_t	*pattern,
-			       double			*pad_out)
+_cairo_pattern_analyze_filter (const cairo_pattern_t *pattern)
 {
-    double pad;
-    cairo_filter_t optimized_filter;
-
     switch (pattern->filter) {
     case CAIRO_FILTER_GOOD:
     case CAIRO_FILTER_BEST:
@@ -3389,15 +3376,8 @@ _cairo_pattern_analyze_filter (const cairo_pattern_t	*pattern,
 	 * will cause blurriness)
 	 */
 	if (_cairo_matrix_is_pixel_exact (&pattern->matrix)) {
-	    pad = 0.;
-	    optimized_filter = CAIRO_FILTER_NEAREST;
+	    return CAIRO_FILTER_NEAREST;
 	} else {
-	    /* 0.5 is enough for a bilinear filter. It's possible we
-	     * should defensively use more for CAIRO_FILTER_BEST, but
-	     * without a single example, it's hard to know how much
-	     * more would be defensive...
-	     */
-	    pad = 0.5;
 	    /* Use BILINEAR for any scale greater than .75 instead
 	     * of GOOD. For scales of 1 and larger this is identical,
 	     * for the smaller sizes it was judged that the artifacts
@@ -3410,73 +3390,126 @@ _cairo_pattern_analyze_filter (const cairo_pattern_t	*pattern,
 			      pattern->matrix.x0) &&
 		use_bilinear (pattern->matrix.yx, pattern->matrix.yy,
 			      pattern->matrix.y0))
-		optimized_filter = CAIRO_FILTER_BILINEAR;
-	    else
-		optimized_filter = pattern->filter;
+		return CAIRO_FILTER_BILINEAR;
 	}
 	break;
 
     case CAIRO_FILTER_NEAREST:
     case CAIRO_FILTER_GAUSSIAN:
     default:
-	pad = 0.;
-	optimized_filter = pattern->filter;
 	break;
     }
 
-    if (pad_out)
-	*pad_out = pad;
+    return pattern->filter;
+}
 
-    return optimized_filter;
+/**
+ * _cairo_hypot:
+ * Returns: value similar to hypot(@x, at y)
+ *
+ * May want to replace this with Manhattan distance (abs(x)+abs(y)) if
+ * hypot is too slow, as there is no need for accuracy here.
+ **/
+static inline double
+_cairo_hypot(double x, double y)
+{
+    return hypot(x, y);
 }
 
-cairo_filter_t
+/**
+ * _cairo_pattern_sampled_area
+ *
+ * Return region of @pattern that will be sampled to fill @extents,
+ * based on the transformation and filter.
+ *
+ * This does not include pixels that are mulitiplied by values very
+ * close to zero by the ends of filters. This is so that transforms
+ * that should be the identity or 90 degree rotations do not expand
+ * the source unexpectedly.
+ *
+ * XXX: We don't actually have any way of querying the backend for
+ *      the filter radius, so we just guess base on what we know that
+ *      backends do currently (see bug #10508)
+ **/
+void
 _cairo_pattern_sampled_area (const cairo_pattern_t *pattern,
 			     const cairo_rectangle_int_t *extents,
 			     cairo_rectangle_int_t *sample)
 {
-    cairo_filter_t filter;
     double x1, x2, y1, y2;
-    double pad;
+    double padx, pady;
 
-    filter = _cairo_pattern_analyze_filter (pattern, &pad);
-    if (pad == 0.0 && _cairo_matrix_is_identity (&pattern->matrix)) {
+    /* Assume filters are interpolating, which means identity
+       cannot change the image */
+    if (_cairo_matrix_is_identity (&pattern->matrix)) {
 	*sample = *extents;
-	return filter;
+	return;
     }
 
-    x1 = extents->x;
-    y1 = extents->y;
-    x2 = extents->x + (int) extents->width;
-    y2 = extents->y + (int) extents->height;
-
+    /* Transform the centers of the corner pixels */
+    x1 = extents->x + 0.5;
+    y1 = extents->y + 0.5;
+    x2 = x1 + (extents->width - 1);
+    y2 = y1 + (extents->height - 1);
     _cairo_matrix_transform_bounding_box (&pattern->matrix,
 					  &x1, &y1, &x2, &y2,
 					  NULL);
-    if (x1 > CAIRO_RECT_INT_MIN)
-	sample->x = floor (x1 - pad);
-    else
-	sample->x = CAIRO_RECT_INT_MIN;
 
-    if (y1 > CAIRO_RECT_INT_MIN)
-	sample->y = floor (y1 - pad);
-    else
-	sample->y = CAIRO_RECT_INT_MIN;
+    /* How far away from center will it actually sample?
+     * This is the distance from a transformed pixel center to the
+     * furthest sample of reasonable size.
+     */
+    switch (pattern->filter) {
+    case CAIRO_FILTER_NEAREST:
+    case CAIRO_FILTER_FAST:
+	/* Correct value is zero, but when the sample is on an integer
+	 * it is unknown if the backend will sample the pixel to the
+	 * left or right. This value makes it include both possible pixels.
+	 */
+	padx = pady = 0.004;
+	break;
+    case CAIRO_FILTER_BILINEAR:
+    case CAIRO_FILTER_GAUSSIAN:
+    default:
+	/* Correct value is .5 */
+	padx = pady = 0.495;
+	break;
+    case CAIRO_FILTER_GOOD:
+	/* Correct value is max(width,1)*.5 */
+	padx = _cairo_hypot (pattern->matrix.xx, pattern->matrix.xy);
+	if (padx <= 1.0) padx = 0.495;
+	else if (padx >= 16.0) padx = 7.92;
+	else padx *= 0.495;
+	pady = _cairo_hypot (pattern->matrix.yx, pattern->matrix.yy);
+	if (pady <= 1.0) pady = 0.495;
+	else if (pady >= 16.0) pady = 7.92;
+	else pady *= 0.495;
+	break;
+    case CAIRO_FILTER_BEST:
+	/* Correct value is width*2 */
+	padx = _cairo_hypot (pattern->matrix.xx, pattern->matrix.xy) * 1.98;
+	if (padx > 7.92) padx = 7.92;
+	pady = _cairo_hypot (pattern->matrix.yx, pattern->matrix.yy) * 1.98;
+	if (pady > 7.92) pady = 7.92;
+	break;
+    }
 
-    if (x2 < CAIRO_RECT_INT_MAX)
-	sample->width = ceil (x2 + pad);
-    else
-	sample->width = CAIRO_RECT_INT_MAX;
+    /* round furthest samples to edge of pixels */
+    x1 = floor (x1 - padx);
+    if (x1 < CAIRO_RECT_INT_MIN) x1 = CAIRO_RECT_INT_MIN;
+    sample->x = x1;
 
-    if (y2 < CAIRO_RECT_INT_MAX)
-	sample->height = ceil (y2 + pad);
-    else
-	sample->height = CAIRO_RECT_INT_MAX;
+    y1 = floor (y1 - pady);
+    if (y1 < CAIRO_RECT_INT_MIN) y1 = CAIRO_RECT_INT_MIN;
+    sample->y = y1;
 
-    sample->width  -= sample->x;
-    sample->height -= sample->y;
+    x2 = floor (x2 + padx) + 1.0;
+    if (x2 > CAIRO_RECT_INT_MAX) x2 = CAIRO_RECT_INT_MAX;
+    sample->width = x2 - x1;
 
-    return filter;
+    y2 = floor (y2 + pady) + 1.0;
+    if (y2 > CAIRO_RECT_INT_MAX) y2 = CAIRO_RECT_INT_MAX;
+    sample->height = y2 - y1;
 }
 
 /**
@@ -3496,7 +3529,9 @@ _cairo_pattern_get_extents (const cairo_pattern_t         *pattern,
 			    cairo_rectangle_int_t         *extents)
 {
     double x1, y1, x2, y2;
-    cairo_status_t status;
+    int ix1, ix2, iy1, iy2;
+    cairo_bool_t round_x = FALSE;
+    cairo_bool_t round_y = FALSE;
 
     switch (pattern->type) {
     case CAIRO_PATTERN_TYPE_SOLID:
@@ -3508,7 +3543,6 @@ _cairo_pattern_get_extents (const cairo_pattern_t         *pattern,
 	    const cairo_surface_pattern_t *surface_pattern =
 		(const cairo_surface_pattern_t *) pattern;
 	    cairo_surface_t *surface = surface_pattern->surface;
-	    double pad;
 
 	    if (! _cairo_surface_get_extents (surface, &surface_extents))
 		goto UNBOUNDED;
@@ -3519,14 +3553,12 @@ _cairo_pattern_get_extents (const cairo_pattern_t         *pattern,
 	    if (pattern->extend != CAIRO_EXTEND_NONE)
 		goto UNBOUNDED;
 
-	    /* The filter can effectively enlarge the extents of the
-	     * pattern, so extend as necessary.
-	     */
-	    _cairo_pattern_analyze_filter (&surface_pattern->base, &pad);
-	    x1 = surface_extents.x - pad;
-	    y1 = surface_extents.y - pad;
-	    x2 = surface_extents.x + (int) surface_extents.width  + pad;
-	    y2 = surface_extents.y + (int) surface_extents.height + pad;
+	    x1 = surface_extents.x;
+	    y1 = surface_extents.y;
+	    x2 = surface_extents.x + (int) surface_extents.width;
+	    y2 = surface_extents.y + (int) surface_extents.height;
+
+	    goto HANDLE_FILTER;
 	}
 	break;
 
@@ -3534,7 +3566,6 @@ _cairo_pattern_get_extents (const cairo_pattern_t         *pattern,
 	{
 	    const cairo_raster_source_pattern_t *raster =
 		(const cairo_raster_source_pattern_t *) pattern;
-	    double pad;
 
 	    if (raster->extents.width == 0 || raster->extents.height == 0)
 		goto EMPTY;
@@ -3542,14 +3573,41 @@ _cairo_pattern_get_extents (const cairo_pattern_t         *pattern,
 	    if (pattern->extend != CAIRO_EXTEND_NONE)
 		goto UNBOUNDED;
 
-	    /* The filter can effectively enlarge the extents of the
-	     * pattern, so extend as necessary.
-	     */
-	    _cairo_pattern_analyze_filter (pattern, &pad);
-	    x1 = raster->extents.x - pad;
-	    y1 = raster->extents.y - pad;
-	    x2 = raster->extents.x + (int) raster->extents.width  + pad;
-	    y2 = raster->extents.y + (int) raster->extents.height + pad;
+	    x1 = raster->extents.x;
+	    y1 = raster->extents.y;
+	    x2 = raster->extents.x + (int) raster->extents.width;
+	    y2 = raster->extents.y + (int) raster->extents.height;
+	}
+    HANDLE_FILTER:
+	switch (pattern->filter) {
+	case CAIRO_FILTER_NEAREST:
+	case CAIRO_FILTER_FAST:
+	    round_x = round_y = TRUE;
+	    /* We don't know which way .5 will go, so fudge it slightly. */
+	    x1 -= 0.004;
+	    y1 -= 0.004;
+	    x2 += 0.004;
+	    y2 += 0.004;
+	    break;
+	case CAIRO_FILTER_BEST:
+	    /* Assume best filter will produce nice antialiased edges */
+	    break;
+	case CAIRO_FILTER_BILINEAR:
+	case CAIRO_FILTER_GAUSSIAN:
+	case CAIRO_FILTER_GOOD:
+	default:
+	    /* These filters can blur the edge out 1/2 pixel when scaling up */
+	    if (_cairo_hypot (pattern->matrix.xx, pattern->matrix.yx) < 1.0) {
+		x1 -= 0.5;
+		x2 += 0.5;
+		round_x = TRUE;
+	    }
+	    if (_cairo_hypot (pattern->matrix.xy, pattern->matrix.yy) < 1.0) {
+		y1 -= 0.5;
+		y2 += 0.5;
+		round_y = TRUE;
+	    }
+	    break;
 	}
 	break;
 
@@ -3621,6 +3679,10 @@ _cairo_pattern_get_extents (const cairo_pattern_t         *pattern,
 	    } else {
 		goto  UNBOUNDED;
 	    }
+
+	    /* The current linear renderer just point-samples in the middle
+	       of the pixels, similar to the NEAREST filter: */
+	    round_x = round_y = TRUE;
 	}
 	break;
 
@@ -3628,22 +3690,8 @@ _cairo_pattern_get_extents (const cairo_pattern_t         *pattern,
 	{
 	    const cairo_mesh_pattern_t *mesh =
 		(const cairo_mesh_pattern_t *) pattern;
-	    double padx, pady;
-	    cairo_bool_t is_valid;
-
-	    is_valid = _cairo_mesh_pattern_coord_box (mesh, &x1, &y1, &x2, &y2);
-	    if (!is_valid)
+	    if (! _cairo_mesh_pattern_coord_box (mesh, &x1, &y1, &x2, &y2))
 		goto EMPTY;
-
-	    padx = pady = 1.;
-	    cairo_matrix_transform_distance (&pattern->matrix, &padx, &pady);
-	    padx = fabs (padx);
-	    pady = fabs (pady);
-
-	    x1 -= padx;
-	    y1 -= pady;
-	    x2 += padx;
-	    y2 += pady;
 	}
 	break;
 
@@ -3656,6 +3704,7 @@ _cairo_pattern_get_extents (const cairo_pattern_t         *pattern,
 	y1 -= pattern->matrix.y0; y2 -= pattern->matrix.y0;
     } else {
 	cairo_matrix_t imatrix;
+	cairo_status_t status;
 
 	imatrix = pattern->matrix;
 	status = cairo_matrix_invert (&imatrix);
@@ -3667,22 +3716,34 @@ _cairo_pattern_get_extents (const cairo_pattern_t         *pattern,
 					      NULL);
     }
 
-    x1 = floor (x1);
+    if (!round_x) {
+	x1 -= 0.5;
+	x2 += 0.5;
+    }
     if (x1 < CAIRO_RECT_INT_MIN)
-	x1 = CAIRO_RECT_INT_MIN;
-    y1 = floor (y1);
-    if (y1 < CAIRO_RECT_INT_MIN)
-	y1 = CAIRO_RECT_INT_MIN;
-
-    x2 = ceil (x2);
+	ix1 = CAIRO_RECT_INT_MIN;
+    else 
+	ix1 = _cairo_lround (x1);
     if (x2 > CAIRO_RECT_INT_MAX)
-	x2 = CAIRO_RECT_INT_MAX;
-    y2 = ceil (y2);
+	ix2 = CAIRO_RECT_INT_MAX;
+    else
+	ix2 = _cairo_lround (x2);
+    extents->x = ix1; extents->width  = ix2 - ix1;
+
+    if (!round_y) {
+	y1 -= 0.5;
+	y2 += 0.5;
+    }
+    if (y1 < CAIRO_RECT_INT_MIN)
+	iy1 = CAIRO_RECT_INT_MIN;
+    else
+	iy1 = _cairo_lround (y1);
     if (y2 > CAIRO_RECT_INT_MAX)
-	y2 = CAIRO_RECT_INT_MAX;
+	iy2 = CAIRO_RECT_INT_MAX;
+    else
+	iy2 = _cairo_lround (y2);
+    extents->y = iy1; extents->height = iy2 - iy1;
 
-    extents->x = x1; extents->width  = x2 - x1;
-    extents->y = y1; extents->height = y2 - y1;
     return;
 
   UNBOUNDED:
diff --git a/src/cairo-xlib-core-compositor.c b/src/cairo-xlib-core-compositor.c
index 9398079..5babcc8 100644
--- a/src/cairo-xlib-core-compositor.c
+++ b/src/cairo-xlib-core-compositor.c
@@ -292,9 +292,7 @@ render_boxes (cairo_xlib_surface_t	*dst,
 	      const cairo_pattern_t	*pattern,
 	      cairo_boxes_t		*boxes)
 {
-    double pad;
-
-    if (_cairo_pattern_analyze_filter (pattern, &pad) != CAIRO_FILTER_NEAREST)
+    if (pattern->filter != CAIRO_FILTER_NEAREST)
 	return fallback_boxes (dst, pattern, boxes);
 
     switch (pattern->extend) {
diff --git a/src/cairoint.h b/src/cairoint.h
index 75b34d0..b4e8ac8 100644
--- a/src/cairoint.h
+++ b/src/cairoint.h
@@ -2009,10 +2009,6 @@ slim_hidden_proto (cairo_surface_write_to_png_stream);
 
 #endif
 
-cairo_private_no_warn cairo_filter_t
-_cairo_pattern_analyze_filter (const cairo_pattern_t	*pattern,
-			       double			*pad_out);
-
 CAIRO_END_DECLS
 
 #include "cairo-mutex-private.h"
diff --git a/src/drm/cairo-drm-i915-shader.c b/src/drm/cairo-drm-i915-shader.c
index a1911d0..85aa984 100644
--- a/src/drm/cairo-drm-i915-shader.c
+++ b/src/drm/cairo-drm-i915-shader.c
@@ -1467,42 +1467,6 @@ i915_shader_acquire_solid_surface (i915_shader_t *shader,
     return CAIRO_STATUS_SUCCESS;
 }
 
-static cairo_filter_t
-sampled_area (const cairo_surface_pattern_t *pattern,
-	      const cairo_rectangle_int_t *extents,
-	      cairo_rectangle_int_t *sample)
-{
-    cairo_rectangle_int_t surface_extents;
-    cairo_filter_t filter;
-    double x1, x2, y1, y2;
-    double pad;
-
-    x1 = extents->x;
-    y1 = extents->y;
-    x2 = extents->x + (int) extents->width;
-    y2 = extents->y + (int) extents->height;
-
-    if (_cairo_matrix_is_translation (&pattern->base.matrix)) {
-	x1 += pattern->base.matrix.x0; x2 += pattern->base.matrix.x0;
-	y1 += pattern->base.matrix.y0; y2 += pattern->base.matrix.y0;
-    } else {
-	_cairo_matrix_transform_bounding_box (&pattern->base.matrix,
-					      &x1, &y1, &x2, &y2,
-					      NULL);
-    }
-
-    filter = _cairo_pattern_analyze_filter (&pattern->base, &pad);
-    sample->x = floor (x1 - pad);
-    sample->y = floor (y1 - pad);
-    sample->width  = ceil (x2 + pad) - sample->x;
-    sample->height = ceil (y2 + pad) - sample->y;
-
-    if (_cairo_surface_get_extents (pattern->surface, &surface_extents))
-	_cairo_rectangle_intersect (sample, &surface_extents);
-
-    return filter;
-}
-
 static cairo_status_t
 i915_shader_acquire_surface (i915_shader_t *shader,
 			     union i915_shader_channel *src,
@@ -1524,7 +1488,8 @@ i915_shader_acquire_surface (i915_shader_t *shader,
 
     extend = pattern->base.extend;
     src->base.matrix = pattern->base.matrix;
-    filter = sampled_area (pattern, extents, &sample);
+    filter = pattern->base.filter;
+    _cairo_pattern_sampled_area(&pattern->base, extents, sample);
 
     if (surface->type == CAIRO_SURFACE_TYPE_DRM) {
 	if (surface->backend->type == CAIRO_SURFACE_TYPE_SUBSURFACE) {
-- 
1.7.9.5



More information about the cairo mailing list