[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