[PATCH 6/6] image: Corrected extents calculations

Bill Spitzak <[email protected]>
Newsgroups gmane.comp.lib.cairo
Message-ID <[email protected]>
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 <[email protected]>
  */
 
-#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,@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

-- 
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.