From b9407af6a4bc792c1bcb52c90aa8a618627bb618 Mon Sep 17 00:00:00 2001 From: Chris Wilson Date: Fri, 22 Jan 2010 17:57:41 +0000 Subject: [PATCH] image: Implement high level interface. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Write a dedicated compositor for pixman so that we avoid the middle-layer syndrome of surface-fallback. The major upshot of this rewrite is that the image surface is now several times quicker for glyph compositing, which dramatically improves performance for text rendering by firefox and friends. It also uses a couple of the new scan convertors, such as the rectangular scan converter for rectilinear paths. Speedups ======== image-rgba firefox-talos-gfx-0 342050.17 (342155.88 0.02%) -> 69412.44 (69702.90 0.21%): 4.93x speedup ███▉ image-rgba vim-0 97518.13 (97696.23 1.21%) -> 30712.63 (31238.65 0.85%): 3.18x speedup ██▏ image-rgba evolution-0 69927.77 (110261.08 19.84%) -> 24430.05 (25368.85 1.89%): 2.86x speedup █▉ image-rgba poppler-0 41452.61 (41547.03 2.51%) -> 21195.52 (21656.85 1.08%): 1.96x speedup █ image-rgba firefox-planet-gnome-0 217512.61 (217636.80 0.06%) -> 123341.02 (123641.94 0.12%): 1.76x speedup ▊ image-rgba swfdec-youtube-0 41302.71 (41373.60 0.11%) -> 31343.93 (31488.87 0.23%): 1.32x speedup ▍ image-rgba swfdec-giant-steps-0 20699.54 (20739.52 0.10%) -> 17360.19 (17375.51 0.04%): 1.19x speedup ▎ image-rgba gvim-0 167837.47 (168027.68 0.51%) -> 151105.94 (151635.85 0.18%): 1.11x speedup ▏ image-rgba firefox-talos-svg-0 375273.43 (388250.94 1.60%) -> 356846.09 (370370.08 1.86%): 1.05x speedup --- src/cairo-image-surface.c | 3987 +++++++++++++++++---- src/cairo-mutex-list-private.h | 2 + src/cairo-xcb-surface.c | 10 +- src/cairoint.h | 21 +- test/clip-fill-unbounded.argb32.ref.png | Bin 1615 -> 1607 bytes test/clip-fill-unbounded.rgb24.ref.png | Bin 1312 -> 1304 bytes test/clip-stroke-unbounded.argb32.ref.png | Bin 1703 -> 1694 bytes test/clip-stroke-unbounded.rgb24.ref.png | Bin 1383 -> 1372 bytes test/clip-stroke.ref.png | Bin 1451 -> 1442 bytes test/clipped-group.ref.png | Bin 289 -> 289 bytes test/leaky-dashed-rectangle.ref.png | Bin 347 -> 357 bytes test/scale-offset-image.xfail.png | Bin 9960 -> 9961 bytes test/scale-offset-similar.xfail.png | Bin 9960 -> 9961 bytes test/self-intersecting.ref.png | Bin 213 -> 168 bytes 14 files changed, 3342 insertions(+), 678 deletions(-) diff --git a/src/cairo-image-surface.c b/src/cairo-image-surface.c index b50a9941c..f3886c068 100644 --- a/src/cairo-image-surface.c +++ b/src/cairo-image-surface.c @@ -2,6 +2,7 @@ /* cairo - a vector graphics library with display and print output * * Copyright © 2003 University of Southern California + * Copyright © 2009,2010 Intel Corporation * * This library is free software; you can redistribute it and/or * modify it either under the terms of the GNU Lesser General Public @@ -33,19 +34,35 @@ * * Contributor(s): * Carl D. Worth + * Chris Wilson */ #include "cairoint.h" +#include "cairo-boxes-private.h" #include "cairo-clip-private.h" #include "cairo-composite-rectangles-private.h" #include "cairo-error-private.h" #include "cairo-region-private.h" +#include "cairo-scaled-font-private.h" +#include "cairo-surface-snapshot-private.h" +#include "cairo-surface-subsurface-private.h" /* Limit on the width / height of an image surface in pixels. This is * mainly determined by coordinates of things sent to pixman at the * moment being in 16.16 format. */ #define MAX_IMAGE_SIZE 32767 +#define PIXMAN_MAX_INT ((pixman_fixed_1 >> 1) - pixman_fixed_e) /* need to ensure deltas also fit */ + +static cairo_int_status_t +_cairo_image_surface_fill (void *dst, + cairo_operator_t op, + const cairo_pattern_t *source, + cairo_path_fixed_t *path, + cairo_fill_rule_t fill_rule, + double tolerance, + cairo_antialias_t antialias, + cairo_clip_t *clip); static cairo_bool_t _cairo_image_surface_is_size_valid (int width, int height) @@ -54,7 +71,7 @@ _cairo_image_surface_is_size_valid (int width, int height) 0 <= height && height <= MAX_IMAGE_SIZE; } -static cairo_format_t +cairo_format_t _cairo_format_from_pixman_format (pixman_format_code_t pixman_format) { switch (pixman_format) { @@ -96,69 +113,18 @@ _cairo_format_from_pixman_format (pixman_format_code_t pixman_format) return CAIRO_FORMAT_INVALID; } -static cairo_content_t +cairo_content_t _cairo_content_from_pixman_format (pixman_format_code_t pixman_format) { - switch (pixman_format) { - case PIXMAN_a8r8g8b8: - case PIXMAN_a8b8g8r8: - case PIXMAN_a1r5g5b5: - case PIXMAN_a1b5g5r5: - case PIXMAN_a4r4g4b4: - case PIXMAN_a4b4g4r4: - case PIXMAN_a2r2g2b2: - case PIXMAN_a2b2g2r2: - case PIXMAN_a1r1g1b1: - case PIXMAN_a1b1g1r1: -#if PIXMAN_VERSION >= PIXMAN_VERSION_ENCODE(0,11,9) - case PIXMAN_a2b10g10r10: -#endif -#if PIXMAN_VERSION >= PIXMAN_VERSION_ENCODE(0,14,1) - case PIXMAN_b8g8r8a8: -#endif -#if PIXMAN_VERSION >= PIXMAN_VERSION_ENCODE(0,15,16) - case PIXMAN_a2r10g10b10: -#endif - return CAIRO_CONTENT_COLOR_ALPHA; - case PIXMAN_x8r8g8b8: - case PIXMAN_x8b8g8r8: - case PIXMAN_r8g8b8: - case PIXMAN_b8g8r8: - case PIXMAN_r5g6b5: - case PIXMAN_b5g6r5: - case PIXMAN_x1r5g5b5: - case PIXMAN_x1b5g5r5: - case PIXMAN_x4r4g4b4: - case PIXMAN_x4b4g4r4: - case PIXMAN_r3g3b2: - case PIXMAN_b2g3r3: - case PIXMAN_c8: - case PIXMAN_g8: - case PIXMAN_r1g2b1: - case PIXMAN_b1g2r1: - case PIXMAN_c4: - case PIXMAN_g4: - case PIXMAN_g1: - case PIXMAN_yuy2: - case PIXMAN_yv12: -#if PIXMAN_VERSION >= PIXMAN_VERSION_ENCODE(0,11,9) - case PIXMAN_x2b10g10r10: -#endif -#if PIXMAN_VERSION >= PIXMAN_VERSION_ENCODE(0,14,1) - case PIXMAN_b8g8r8x8: -#endif -#if PIXMAN_VERSION >= PIXMAN_VERSION_ENCODE(0,15,16) - case PIXMAN_x2r10g10b10: -#endif - return CAIRO_CONTENT_COLOR; - case PIXMAN_a8: - case PIXMAN_a1: - case PIXMAN_x4a4: - case PIXMAN_a4: - return CAIRO_CONTENT_ALPHA; - } + cairo_content_t content; - return CAIRO_CONTENT_COLOR_ALPHA; + content = 0; + if (PIXMAN_FORMAT_RGB (pixman_format)) + content |= CAIRO_CONTENT_COLOR; + if (PIXMAN_FORMAT_A (pixman_format)) + content |= CAIRO_CONTENT_ALPHA; + + return content; } cairo_surface_t * @@ -169,10 +135,6 @@ _cairo_image_surface_create_for_pixman_image (pixman_image_t *pixman_image, int width = pixman_image_get_width (pixman_image); int height = pixman_image_get_height (pixman_image); - if (! _cairo_image_surface_is_size_valid (width, height)) { - return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_INVALID_SIZE)); - } - surface = malloc (sizeof (cairo_image_surface_t)); if (unlikely (surface == NULL)) return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY)); @@ -186,7 +148,7 @@ _cairo_image_surface_create_for_pixman_image (pixman_image_t *pixman_image, surface->pixman_format = pixman_format; surface->format = _cairo_format_from_pixman_format (pixman_format); - surface->data = (unsigned char *) pixman_image_get_data (pixman_image); + surface->data = (uint8_t *) pixman_image_get_data (pixman_image); surface->owns_data = FALSE; surface->transparency = CAIRO_IMAGE_UNKNOWN; @@ -195,8 +157,6 @@ _cairo_image_surface_create_for_pixman_image (pixman_image_t *pixman_image, surface->stride = pixman_image_get_stride (pixman_image); surface->depth = pixman_image_get_depth (pixman_image); - surface->clip_region = NULL; - return &surface->base; } @@ -305,46 +265,6 @@ _pixman_format_to_masks (pixman_format_code_t format, } } -/* XXX: This function really should be eliminated. We don't really - * want to advertise a cairo image surface that supports any possible - * format. A minimal step would be to replace this function with one - * that accepts a #cairo_internal_format_t rather than mask values. */ -cairo_surface_t * -_cairo_image_surface_create_with_masks (unsigned char *data, - cairo_format_masks_t *masks, - int width, - int height, - int stride) -{ - pixman_format_code_t pixman_format; - - if (! _pixman_format_from_masks (masks, &pixman_format)) { - fprintf (stderr, - "Error: Cairo %s does not yet support the requested image format:\n" - "\tDepth: %d\n" - "\tAlpha mask: 0x%08lx\n" - "\tRed mask: 0x%08lx\n" - "\tGreen mask: 0x%08lx\n" - "\tBlue mask: 0x%08lx\n" -#ifdef PACKAGE_BUGGREPORT - "Please file an enhancement request (quoting the above) at:\n" - PACKAGE_BUGREPORT"\n" -#endif - , - cairo_version_string (), - masks->bpp, masks->alpha_mask, - masks->red_mask, masks->green_mask, masks->blue_mask); - - ASSERT_NOT_REACHED; - } - - return _cairo_image_surface_create_with_pixman_format (data, - pixman_format, - width, - height, - stride); -} - static pixman_format_code_t _cairo_format_to_pixman_format_code (cairo_format_t format) { @@ -442,9 +362,6 @@ _cairo_image_surface_create_with_content (cairo_content_t content, int width, int height) { - if (! CAIRO_CONTENT_VALID (content)) - return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_INVALID_CONTENT)); - return cairo_image_surface_create (_cairo_format_from_content (content), width, height); } @@ -554,6 +471,9 @@ cairo_image_surface_create_for_data (unsigned char *data, if ((stride & (CAIRO_STRIDE_ALIGNMENT-1)) != 0) return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_INVALID_STRIDE)); + if (! _cairo_image_surface_is_size_valid (width, height)) + return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_INVALID_SIZE)); + minstride = cairo_format_stride_for_width (format, width); if (stride < 0) { if (stride > -minstride) { @@ -573,21 +493,6 @@ cairo_image_surface_create_for_data (unsigned char *data, } slim_hidden_def (cairo_image_surface_create_for_data); -cairo_surface_t * -_cairo_image_surface_create_for_data_with_content (unsigned char *data, - cairo_content_t content, - int width, - int height, - int stride) -{ - if (! CAIRO_CONTENT_VALID (content)) - return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_INVALID_CONTENT)); - - return cairo_image_surface_create_for_data (data, - _cairo_format_from_content (content), - width, height, stride); -} - /** * cairo_image_surface_get_data: * @surface: a #cairo_image_surface_t @@ -770,6 +675,9 @@ _cairo_image_surface_create_similar (void *abstract_other, { cairo_image_surface_t *other = abstract_other; + if (! _cairo_image_surface_is_size_valid (width, height)) + return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_INVALID_SIZE)); + if (content == other->base.content) { return _cairo_image_surface_create_with_pixman_format (NULL, other->pixman_format, @@ -796,8 +704,6 @@ _cairo_image_surface_finish (void *abstract_surface) surface->data = NULL; } - cairo_region_destroy (surface->clip_region); - return CAIRO_STATUS_SUCCESS; } @@ -825,176 +731,6 @@ _cairo_image_surface_release_source_image (void *abstract_surf { } -static cairo_status_t -_cairo_image_surface_acquire_dest_image (void *abstract_surface, - cairo_rectangle_int_t *interest_rect, - cairo_image_surface_t **image_out, - cairo_rectangle_int_t *image_rect_out, - void **image_extra) -{ - cairo_image_surface_t *surface = abstract_surface; - - image_rect_out->x = 0; - image_rect_out->y = 0; - image_rect_out->width = surface->width; - image_rect_out->height = surface->height; - - *image_out = surface; - *image_extra = NULL; - - return CAIRO_STATUS_SUCCESS; -} - -static void -_cairo_image_surface_release_dest_image (void *abstract_surface, - cairo_rectangle_int_t *interest_rect, - cairo_image_surface_t *image, - cairo_rectangle_int_t *image_rect, - void *image_extra) -{ -} - -static cairo_status_t -_cairo_image_surface_clone_similar (void *abstract_surface, - cairo_surface_t *src, - int src_x, - int src_y, - int width, - int height, - int *clone_offset_x, - int *clone_offset_y, - cairo_surface_t **clone_out) -{ - cairo_image_surface_t *surface = abstract_surface; - - if (src->backend == surface->base.backend) { - *clone_offset_x = *clone_offset_y = 0; - *clone_out = cairo_surface_reference (src); - - return CAIRO_STATUS_SUCCESS; - } - - return CAIRO_INT_STATUS_UNSUPPORTED; -} - -static cairo_status_t -_cairo_image_surface_set_matrix (cairo_image_surface_t *surface, - const cairo_matrix_t *matrix, - double xc, double yc) -{ - pixman_transform_t pixman_transform; - - _cairo_matrix_to_pixman_matrix (matrix, &pixman_transform, xc, yc); - - if (! pixman_image_set_transform (surface->pixman_image, &pixman_transform)) - return _cairo_error (CAIRO_STATUS_NO_MEMORY); - - return CAIRO_STATUS_SUCCESS; -} - -static cairo_status_t -_cairo_image_surface_set_filter (cairo_image_surface_t *surface, - cairo_filter_t filter) -{ - pixman_filter_t pixman_filter; - - switch (filter) { - case CAIRO_FILTER_FAST: - pixman_filter = PIXMAN_FILTER_FAST; - break; - case CAIRO_FILTER_GOOD: - pixman_filter = PIXMAN_FILTER_GOOD; - break; - case CAIRO_FILTER_BEST: - pixman_filter = PIXMAN_FILTER_BEST; - break; - case CAIRO_FILTER_NEAREST: - pixman_filter = PIXMAN_FILTER_NEAREST; - break; - case CAIRO_FILTER_BILINEAR: - pixman_filter = PIXMAN_FILTER_BILINEAR; - break; - case CAIRO_FILTER_GAUSSIAN: - /* XXX: The GAUSSIAN value has no implementation in cairo - * whatsoever, so it was really a mistake to have it in the - * API. We could fix this by officially deprecating it, or - * else inventing semantics and providing an actual - * implementation for it. */ - default: - pixman_filter = PIXMAN_FILTER_BEST; - } - - if (! pixman_image_set_filter (surface->pixman_image, - pixman_filter, - NULL, 0)) - { - return _cairo_error (CAIRO_STATUS_NO_MEMORY); - } - - return CAIRO_STATUS_SUCCESS; -} - -static cairo_status_t -_cairo_image_surface_set_extend (cairo_image_surface_t *surface, - cairo_extend_t extend) -{ - pixman_repeat_t pixman_repeat; - - switch (extend) { - case CAIRO_EXTEND_NONE: - pixman_repeat = PIXMAN_REPEAT_NONE; - break; - case CAIRO_EXTEND_REPEAT: - pixman_repeat = PIXMAN_REPEAT_NORMAL; - break; - case CAIRO_EXTEND_REFLECT: - pixman_repeat = PIXMAN_REPEAT_REFLECT; - break; - case CAIRO_EXTEND_PAD: - pixman_repeat = PIXMAN_REPEAT_PAD; - break; - } - - pixman_image_set_repeat (surface->pixman_image, pixman_repeat); - return CAIRO_STATUS_SUCCESS; -} - -static cairo_status_t -_cairo_image_surface_set_component_alpha (cairo_image_surface_t *surface, - cairo_bool_t ca) -{ - pixman_image_set_component_alpha (surface->pixman_image, ca); - return CAIRO_STATUS_SUCCESS; -} - -static cairo_status_t -_cairo_image_surface_set_attributes (cairo_image_surface_t *surface, - cairo_surface_attributes_t *attributes, - double xc, double yc) -{ - cairo_int_status_t status; - - status = _cairo_image_surface_set_matrix (surface, &attributes->matrix, - xc, yc); - if (unlikely (status)) - return status; - - status = _cairo_image_surface_set_filter (surface, attributes->filter); - if (unlikely (status)) - return status; - - status = _cairo_image_surface_set_extend (surface, attributes->extend); - if (unlikely (status)) - return status; - - status = _cairo_image_surface_set_component_alpha (surface, - attributes->has_component_alpha); - if (unlikely (status)) - return status; - - return CAIRO_STATUS_SUCCESS; -} - /* XXX: I think we should fix pixman to match the names/order of the * cairo operators, but that will likely be better done at the same * time the X server is ported to pixman, (which will change a lot of @@ -1077,148 +813,3223 @@ static cairo_status_t _cairo_image_surface_set_clip_region (cairo_image_surface_t *surface, cairo_region_t *region) { - if (region == surface->clip_region) - return CAIRO_STATUS_SUCCESS; - - if (cairo_region_equal (surface->clip_region, region)) - return CAIRO_STATUS_SUCCESS; - - cairo_region_destroy (surface->clip_region); - surface->clip_region = cairo_region_reference (region); - - if (! pixman_image_set_clip_region32 (surface->pixman_image, - region ? ®ion->rgn : NULL)) - { + if (! pixman_image_set_clip_region32 (surface->pixman_image, ®ion->rgn)) return _cairo_error (CAIRO_STATUS_NO_MEMORY); - } return CAIRO_STATUS_SUCCESS; } -static cairo_int_status_t -_cairo_image_surface_composite (cairo_operator_t op, - const cairo_pattern_t *src_pattern, - const cairo_pattern_t *mask_pattern, - void *abstract_dst, - int src_x, - int src_y, - int mask_x, - int mask_y, - int dst_x, - int dst_y, - unsigned int width, - unsigned int height, - cairo_region_t *clip_region) +static void +_cairo_image_surface_unset_clip_region (cairo_image_surface_t *surface) { - cairo_surface_attributes_t src_attr, mask_attr; - cairo_image_surface_t *dst = abstract_dst; - cairo_image_surface_t *src; - cairo_image_surface_t *mask; - cairo_int_status_t status; + pixman_image_set_clip_region32 (surface->pixman_image, NULL); +} - status = _cairo_image_surface_set_clip_region (dst, clip_region); - if (unlikely (status)) - return status; +static double +_pixman_nearest_sample (double d) +{ + return ceil (d - .5); +} - status = _cairo_pattern_acquire_surfaces (src_pattern, mask_pattern, - &dst->base, - src_x, src_y, - mask_x, mask_y, - width, height, - CAIRO_PATTERN_ACQUIRE_NONE, - (cairo_surface_t **) &src, - (cairo_surface_t **) &mask, - &src_attr, &mask_attr); - if (unlikely (status)) - return status; +static cairo_bool_t +_nearest_sample (cairo_filter_t filter, double *tx, double *ty) +{ + if (filter == CAIRO_FILTER_FAST || filter == CAIRO_FILTER_NEAREST) { + *tx = _pixman_nearest_sample (*tx); + *ty = _pixman_nearest_sample (*ty); + } else { + if (*tx != floor (*tx) || *ty != floor (*ty)) + return FALSE; + } + return fabs (*tx) < PIXMAN_MAX_INT && fabs (*ty) < PIXMAN_MAX_INT; +} - status = _cairo_image_surface_set_attributes (src, &src_attr, - dst_x + width / 2., - dst_y + height / 2.); - if (unlikely (status)) - goto CLEANUP_SURFACES; +#if HAS_ATOMIC_OPS +static pixman_image_t * +_pixman_transparent_image (void) +{ + static pixman_image_t *__pixman_transparent_image; + pixman_image_t *image; - if (mask) { - status = _cairo_image_surface_set_attributes (mask, &mask_attr, - dst_x + width / 2., - dst_y + height / 2.); - if (unlikely (status)) - goto CLEANUP_SURFACES; + image = __pixman_transparent_image; + if (unlikely (image == NULL)) { + pixman_color_t color; - pixman_image_composite (_pixman_operator (op), - src->pixman_image, - mask->pixman_image, - dst->pixman_image, - src_x + src_attr.x_offset, - src_y + src_attr.y_offset, - mask_x + mask_attr.x_offset, - mask_y + mask_attr.y_offset, - dst_x, dst_y, - width, height); + color.red = 0x00; + color.green = 0x00; + color.blue = 0x00; + color.alpha = 0x00; + + image = pixman_image_create_solid_fill (&color); + + if (_cairo_atomic_ptr_cmpxchg (&__pixman_transparent_image, + NULL, image) == NULL) + { + pixman_image_ref (image); + } } else { - pixman_image_composite (_pixman_operator (op), - src->pixman_image, - NULL, - dst->pixman_image, - src_x + src_attr.x_offset, - src_y + src_attr.y_offset, - 0, 0, - dst_x, dst_y, - width, height); + pixman_image_ref (image); } - if (! _cairo_operator_bounded_by_source (op)) { - status = _cairo_surface_composite_fixup_unbounded (&dst->base, - &src_attr, src->width, src->height, - mask ? &mask_attr : NULL, - mask ? mask->width : 0, - mask ? mask->height : 0, - src_x, src_y, - mask_x, mask_y, - dst_x, dst_y, width, height, - clip_region); - } + return image; +} - CLEANUP_SURFACES: - if (mask) - _cairo_pattern_release_surface (mask_pattern, &mask->base, &mask_attr); +static pixman_image_t * +_pixman_black_image (void) +{ + static pixman_image_t *__pixman_black_image; + pixman_image_t *image; - _cairo_pattern_release_surface (src_pattern, &src->base, &src_attr); + image = __pixman_black_image; + if (unlikely (image == NULL)) { + pixman_color_t color; - return status; -} + color.red = 0x00; + color.green = 0x00; + color.blue = 0x00; + color.alpha = 0xffff; -static cairo_int_status_t -_cairo_image_surface_fill_rectangles (void *abstract_surface, - cairo_operator_t op, - const cairo_color_t *color, - cairo_rectangle_int_t *rects, - int num_rects) -{ - cairo_image_surface_t *surface = abstract_surface; + image = pixman_image_create_solid_fill (&color); - pixman_color_t pixman_color; - pixman_rectangle16_t stack_rects[CAIRO_STACK_ARRAY_LENGTH (pixman_rectangle16_t)]; - pixman_rectangle16_t *pixman_rects = stack_rects; - int i; + if (_cairo_atomic_ptr_cmpxchg (&__pixman_black_image, + NULL, image) == NULL) + { + pixman_image_ref (image); + } + } else { + pixman_image_ref (image); + } - cairo_int_status_t status; + return image; +} - if (CAIRO_INJECT_FAULT ()) - return _cairo_error (CAIRO_STATUS_NO_MEMORY); +static pixman_image_t * +_pixman_white_image (void) +{ + static pixman_image_t *__pixman_white_image; + pixman_image_t *image; - pixman_color.red = color->red_short; - pixman_color.green = color->green_short; - pixman_color.blue = color->blue_short; - pixman_color.alpha = color->alpha_short; + image = __pixman_white_image; + if (unlikely (image == NULL)) { + pixman_color_t color; - status = _cairo_image_surface_set_clip_region (surface, NULL); - assert (status == CAIRO_STATUS_SUCCESS); + color.red = 0xffff; + color.green = 0xffff; + color.blue = 0xffff; + color.alpha = 0xffff; - if (num_rects > ARRAY_LENGTH (stack_rects)) { - pixman_rects = _cairo_malloc_ab (num_rects, sizeof (pixman_rectangle16_t)); - if (unlikely (pixman_rects == NULL)) - return _cairo_error (CAIRO_STATUS_NO_MEMORY); + image = pixman_image_create_solid_fill (&color); + + if (_cairo_atomic_ptr_cmpxchg (&__pixman_white_image, + NULL, image) == NULL) + { + pixman_image_ref (image); + } + } else { + pixman_image_ref (image); + } + + return image; +} +#else +static pixman_image_t * +_pixman_white_image (void) +{ + return _pixman_image_for_solid (&_cairo_pattern_white); +} +#endif + +static uint32_t +hars_petruska_f54_1_random (void) +{ +#define rol(x,k) ((x << k) | (x >> (32-k))) + static uint32_t x; + return x = (x ^ rol (x, 5) ^ rol (x, 24)) + 0x37798849; +#undef rol +} + +static pixman_image_t * +_pixman_image_for_solid (const cairo_solid_pattern_t *pattern) +{ + static struct { + cairo_color_t color; + pixman_image_t *image; + } cache[16]; + static int n_cached; + pixman_color_t color; + pixman_image_t *image; + int i; + +#if HAS_ATOMIC_OPS + if (pattern->color.alpha_short <= 0x00ff) + return _pixman_transparent_image (); + + if (pattern->color.alpha_short >= 0xff00) { + if (pattern->color.red_short <= 0x00ff && + pattern->color.green_short <= 0x00ff && + pattern->color.blue_short <= 0x00ff) + { + return _pixman_black_image (); + } + + if (pattern->color.red_short >= 0xff00 && + pattern->color.green_short >= 0xff00 && + pattern->color.blue_short >= 0xff00) + { + return _pixman_white_image (); + } + } +#endif + + CAIRO_MUTEX_LOCK (_cairo_image_solid_cache_mutex); + for (i = 0; i < n_cached; i++) { + if (_cairo_color_equal (&cache[i].color, &pattern->color)) { + image = pixman_image_ref (cache[i].image); + goto UNLOCK; + } + } + + color.red = pattern->color.red_short; + color.green = pattern->color.green_short; + color.blue = pattern->color.blue_short; + color.alpha = pattern->color.alpha_short; + + image = pixman_image_create_solid_fill (&color); + if (image == NULL) + goto UNLOCK; + + if (n_cached < ARRAY_LENGTH (cache)) { + i = n_cached++; + } else { + i = hars_petruska_f54_1_random () % ARRAY_LENGTH (cache); + pixman_image_unref (cache[i].image); + } + cache[i].image = pixman_image_ref (image); + cache[i].color = pattern->color; + +UNLOCK: + CAIRO_MUTEX_UNLOCK (_cairo_image_solid_cache_mutex); + return image; +} + +static pixman_image_t * +_pixman_image_for_gradient (const cairo_gradient_pattern_t *pattern, + const cairo_rectangle_int_t *extents, + int *ix, int *iy) +{ + pixman_image_t *pixman_image; + pixman_gradient_stop_t pixman_stops_static[2]; + pixman_gradient_stop_t *pixman_stops = pixman_stops_static; + cairo_matrix_t matrix = pattern->base.matrix; + double tx, ty; + unsigned int i; + + if (pattern->n_stops > ARRAY_LENGTH(pixman_stops_static)) { + pixman_stops = _cairo_malloc_ab (pattern->n_stops, + sizeof(pixman_gradient_stop_t)); + if (unlikely (pixman_stops == NULL)) + return NULL; + } + + for (i = 0; i < pattern->n_stops; i++) { + pixman_stops[i].x = _cairo_fixed_16_16_from_double (pattern->stops[i].offset); + pixman_stops[i].color.red = pattern->stops[i].color.red_short; + pixman_stops[i].color.green = pattern->stops[i].color.green_short; + pixman_stops[i].color.blue = pattern->stops[i].color.blue_short; + pixman_stops[i].color.alpha = pattern->stops[i].color.alpha_short; + } + + if (pattern->base.type == CAIRO_PATTERN_TYPE_LINEAR) { + cairo_linear_pattern_t *linear = (cairo_linear_pattern_t *) pattern; + pixman_point_fixed_t p1, p2; + cairo_fixed_t xdim, ydim; + + xdim = fabs (linear->p2.x - linear->p1.x); + ydim = fabs (linear->p2.y - linear->p1.y); + + /* + * Transform the matrix to avoid overflow when converting between + * cairo_fixed_t and pixman_fixed_t (without incurring performance + * loss when the transformation is unnecessary). + * + * XXX: Consider converting out-of-range co-ordinates and transforms. + * Having a function to compute the required transformation to + * "normalize" a given bounding box would be generally useful - + * cf linear patterns, gradient patterns, surface patterns... + */ + if (_cairo_fixed_integer_ceil (xdim) > PIXMAN_MAX_INT || + _cairo_fixed_integer_ceil (ydim) > PIXMAN_MAX_INT) + { + double sf; + + if (xdim > ydim) + sf = PIXMAN_MAX_INT / _cairo_fixed_to_double (xdim); + else + sf = PIXMAN_MAX_INT / _cairo_fixed_to_double (ydim); + + p1.x = _cairo_fixed_16_16_from_double (_cairo_fixed_to_double (linear->p1.x) * sf); + p1.y = _cairo_fixed_16_16_from_double (_cairo_fixed_to_double (linear->p1.y) * sf); + p2.x = _cairo_fixed_16_16_from_double (_cairo_fixed_to_double (linear->p2.x) * sf); + p2.y = _cairo_fixed_16_16_from_double (_cairo_fixed_to_double (linear->p2.y) * sf); + + cairo_matrix_scale (&matrix, sf, sf); + } + else + { + p1.x = _cairo_fixed_to_16_16 (linear->p1.x); + p1.y = _cairo_fixed_to_16_16 (linear->p1.y); + p2.x = _cairo_fixed_to_16_16 (linear->p2.x); + p2.y = _cairo_fixed_to_16_16 (linear->p2.y); + } + + pixman_image = pixman_image_create_linear_gradient (&p1, &p2, + pixman_stops, + pattern->n_stops); + } else { + cairo_radial_pattern_t *radial = (cairo_radial_pattern_t *) pattern; + pixman_point_fixed_t c1, c2; + pixman_fixed_t r1, r2; + + c1.x = _cairo_fixed_to_16_16 (radial->c1.x); + c1.y = _cairo_fixed_to_16_16 (radial->c1.y); + r1 = _cairo_fixed_to_16_16 (radial->r1); + + c2.x = _cairo_fixed_to_16_16 (radial->c2.x); + c2.y = _cairo_fixed_to_16_16 (radial->c2.y); + r2 = _cairo_fixed_to_16_16 (radial->r2); + + pixman_image = pixman_image_create_radial_gradient (&c1, &c2, r1, r2, + pixman_stops, + pattern->n_stops); + } + + if (pixman_stops != pixman_stops_static) + free (pixman_stops); + + tx = pattern->base.matrix.x0; + ty = pattern->base.matrix.y0; + if (! _cairo_matrix_is_translation (&pattern->base.matrix) || + ! _nearest_sample (pattern->base.filter, &tx, &ty)) + { + pixman_transform_t pixman_transform; + + if (tx != 0. || ty != 0.) { + cairo_matrix_t m, inv; + cairo_status_t status; + double x, y; + + /* pixman also limits the [xy]_offset to 16 bits so evenly + * spread the bits between the two. + */ + inv = pattern->base.matrix; + status = cairo_matrix_invert (&inv); + assert (status == CAIRO_STATUS_SUCCESS); + + x = floor (inv.x0 / 2); + y = floor (inv.y0 / 2); + tx = -x; + ty = -y; + cairo_matrix_init_translate (&inv, x, y); + cairo_matrix_multiply (&m, &inv, &pattern->base.matrix); + _cairo_matrix_to_pixman_matrix (&m, &pixman_transform, + extents->x + extents->width/2., + extents->y + extents->height/2.); + } else { + tx = ty = 0; + _cairo_matrix_to_pixman_matrix (&pattern->base.matrix, + &pixman_transform, + extents->x + extents->width/2., + extents->y + extents->height/2.); + } + + if (! pixman_image_set_transform (pixman_image, &pixman_transform)) { + pixman_image_unref (pixman_image); + return NULL; + } + } + *ix = tx; + *iy = ty; + + { + pixman_repeat_t pixman_repeat; + + switch (pattern->base.extend) { + default: + case CAIRO_EXTEND_NONE: + pixman_repeat = PIXMAN_REPEAT_NONE; + break; + case CAIRO_EXTEND_REPEAT: + pixman_repeat = PIXMAN_REPEAT_NORMAL; + break; + case CAIRO_EXTEND_REFLECT: + pixman_repeat = PIXMAN_REPEAT_REFLECT; + break; + case CAIRO_EXTEND_PAD: + pixman_repeat = PIXMAN_REPEAT_PAD; + break; + } + + pixman_image_set_repeat (pixman_image, pixman_repeat); + } + + return pixman_image; +} + +struct acquire_source_cleanup { + cairo_surface_t *surface; + cairo_image_surface_t *image; + void *image_extra; +}; + +static void +_acquire_source_cleanup (pixman_image_t *pixman_image, + void *closure) +{ + struct acquire_source_cleanup *data = closure; + + _cairo_surface_release_source_image (data->surface, + data->image, + data->image_extra); + free (data); +} + +static pixman_image_t * +_pixman_image_for_surface (const cairo_surface_pattern_t *pattern, + const cairo_rectangle_int_t *extents, + int *ix, int *iy) +{ + pixman_image_t *pixman_image; + cairo_extend_t extend; + double tx, ty; + + tx = pattern->base.matrix.x0; + ty = pattern->base.matrix.y0; + + extend = pattern->base.extend; + + pixman_image = NULL; + if (pattern->surface->type == CAIRO_SURFACE_TYPE_IMAGE) { + cairo_image_surface_t *source = (cairo_image_surface_t *) pattern->surface; + cairo_surface_type_t type; + + if (source->base.backend->type == CAIRO_INTERNAL_SURFACE_TYPE_SNAPSHOT) + source = (cairo_image_surface_t *) ((cairo_surface_snapshot_t *) pattern->surface)->target; + + type = source->base.backend->type; + if (type == CAIRO_SURFACE_TYPE_IMAGE) { + if (extend != CAIRO_EXTEND_NONE && + extents->x >= 0 && extents->y >= 0 && + extents->x + extents->width <= source->width && + extents->y + extents->height <= source->height) + { + extend = CAIRO_EXTEND_NONE; + } + + /* avoid allocating a 'pattern' image if we can reuse the original */ + if (extend == CAIRO_EXTEND_NONE && + _cairo_matrix_is_translation (&pattern->base.matrix) && + _nearest_sample (pattern->base.filter, &tx, &ty)) + { + *ix = tx; + *iy = ty; + return pixman_image_ref (source->pixman_image); + } + + pixman_image = pixman_image_create_bits (source->pixman_format, + source->width, + source->height, + (uint32_t *) source->data, + source->stride); + if (unlikely (pixman_image == NULL)) + return NULL; + } else if (type == CAIRO_INTERNAL_SURFACE_TYPE_SUBSURFACE) { + cairo_surface_subsurface_t *sub; + cairo_bool_t is_contained = FALSE; + + sub = (cairo_surface_subsurface_t *) source; + source = (cairo_image_surface_t *) sub->target; + + if (extend != CAIRO_EXTEND_NONE && + extents->x >= 0 && extents->y >= 0 && + extents->x + extents->width <= sub->extents.width && + extents->y + extents->height <= sub->extents.height) + { + extend = CAIRO_EXTEND_NONE; + is_contained = TRUE; + } + + if (is_contained && + _cairo_matrix_is_translation (&pattern->base.matrix) && + _nearest_sample (pattern->base.filter, &tx, &ty)) + { + *ix = tx + sub->extents.x; + *iy = ty + sub->extents.y; + return pixman_image_ref (source->pixman_image); + } + + /* Avoid sub-byte offsets, force a copy in that case. */ + if (PIXMAN_FORMAT_BPP (source->pixman_format) >= 8) { + pixman_image = pixman_image_create_bits (source->pixman_format, + sub->extents.width, + sub->extents.height, + (uint32_t *) (source->data + sub->extents.x * PIXMAN_FORMAT_BPP(source->pixman_format)/8 + sub->extents.y * source->stride), + source->stride); + if (unlikely (pixman_image == NULL)) + return NULL; + } + } + } + + if (pixman_image == NULL) { + struct acquire_source_cleanup *cleanup; + cairo_image_surface_t *source = (cairo_image_surface_t *) pattern->surface; + cairo_status_t status; + + cleanup = malloc (sizeof (*cleanup)); + if (unlikely (cleanup == NULL)) + return NULL; + + cleanup->surface = pattern->surface; + status = _cairo_surface_acquire_source_image (pattern->surface, + &cleanup->image, + &cleanup->image_extra); + if (unlikely (status)) { + free (cleanup); + return NULL; + } + + source = cleanup->image; + if (extend != CAIRO_EXTEND_NONE && + extents->x >= 0 && extents->y >= 0 && + extents->x + extents->width <= source->width && + extents->y + extents->height <= source->height) + { + extend = CAIRO_EXTEND_NONE; + } + + pixman_image = pixman_image_create_bits (source->pixman_format, + source->width, + source->height, + (uint32_t *) source->data, + source->stride); + if (unlikely (pixman_image == NULL)) { + _cairo_surface_release_source_image (pattern->surface, + cleanup->image, + cleanup->image_extra); + free (cleanup); + return NULL; + } + + pixman_image_set_destroy_function (pixman_image, + _acquire_source_cleanup, cleanup); + } + + if (! _cairo_matrix_is_translation (&pattern->base.matrix) || + ! _nearest_sample (pattern->base.filter, &tx, &ty)) + { + pixman_transform_t pixman_transform; + cairo_matrix_t m; + + m = pattern->base.matrix; + if (m.x0 != 0. || m.y0 != 0.) { + cairo_matrix_t inv; + cairo_status_t status; + double x, y; + + /* pixman also limits the [xy]_offset to 16 bits so evenly + * spread the bits between the two. + */ + inv = m; + status = cairo_matrix_invert (&inv); + assert (status == CAIRO_STATUS_SUCCESS); + + x = floor (inv.x0 / 2); + y = floor (inv.y0 / 2); + tx = -x; + ty = -y; + cairo_matrix_init_translate (&inv, x, y); + cairo_matrix_multiply (&m, &inv, &m); + } else { + tx = ty = 0; + } + + _cairo_matrix_to_pixman_matrix (&m, &pixman_transform, + extents->x + extents->width/2., + extents->y + extents->height/2.); + if (! pixman_image_set_transform (pixman_image, &pixman_transform)) { + pixman_image_unref (pixman_image); + return NULL; + } + } + *ix = tx; + *iy = ty; + + if (_cairo_matrix_has_unity_scale (&pattern->base.matrix) && + tx == pattern->base.matrix.x0 && + ty == pattern->base.matrix.y0) + { + pixman_image_set_filter (pixman_image, PIXMAN_FILTER_NEAREST, NULL, 0); + } + else + { + pixman_filter_t pixman_filter; + + switch (pattern->base.filter) { + case CAIRO_FILTER_FAST: + pixman_filter = PIXMAN_FILTER_FAST; + break; + case CAIRO_FILTER_GOOD: + pixman_filter = PIXMAN_FILTER_GOOD; + break; + case CAIRO_FILTER_BEST: + pixman_filter = PIXMAN_FILTER_BEST; + break; + case CAIRO_FILTER_NEAREST: + pixman_filter = PIXMAN_FILTER_NEAREST; + break; + case CAIRO_FILTER_BILINEAR: + pixman_filter = PIXMAN_FILTER_BILINEAR; + break; + case CAIRO_FILTER_GAUSSIAN: + /* XXX: The GAUSSIAN value has no implementation in cairo + * whatsoever, so it was really a mistake to have it in the + * API. We could fix this by officially deprecating it, or + * else inventing semantics and providing an actual + * implementation for it. */ + default: + pixman_filter = PIXMAN_FILTER_BEST; + } + + pixman_image_set_filter (pixman_image, pixman_filter, NULL, 0); + } + + { + pixman_repeat_t pixman_repeat; + + switch (extend) { + default: + case CAIRO_EXTEND_NONE: + pixman_repeat = PIXMAN_REPEAT_NONE; + break; + case CAIRO_EXTEND_REPEAT: + pixman_repeat = PIXMAN_REPEAT_NORMAL; + break; + case CAIRO_EXTEND_REFLECT: + pixman_repeat = PIXMAN_REPEAT_REFLECT; + break; + case CAIRO_EXTEND_PAD: + pixman_repeat = PIXMAN_REPEAT_PAD; + break; + } + + pixman_image_set_repeat (pixman_image, pixman_repeat); + } + + return pixman_image; +} + +static pixman_image_t * +_pixman_image_for_pattern (const cairo_pattern_t *pattern, + const cairo_rectangle_int_t *extents, + int *tx, int *ty) +{ + *tx = *ty = 0; + + if (pattern == NULL) + return _pixman_white_image (); + + switch (pattern->type) { + default: + ASSERT_NOT_REACHED; + case CAIRO_PATTERN_TYPE_SOLID: + return _pixman_image_for_solid ((const cairo_solid_pattern_t *) pattern); + + case CAIRO_PATTERN_TYPE_RADIAL: + case CAIRO_PATTERN_TYPE_LINEAR: + return _pixman_image_for_gradient ((const cairo_gradient_pattern_t *) pattern, + extents, tx, ty); + + case CAIRO_PATTERN_TYPE_SURFACE: + return _pixman_image_for_surface ((const cairo_surface_pattern_t *) pattern, + extents, tx, ty); + } +} + +static void +_cairo_image_surface_fixup_unbounded (cairo_image_surface_t *dst, + const cairo_composite_rectangles_t *rects, + cairo_clip_t *clip) +{ + pixman_image_t *mask = NULL; + int mask_x = 0, mask_y = 0; + + if (clip != NULL) { + cairo_surface_t *clip_surface; + + clip_surface = _cairo_clip_get_surface (clip, &dst->base); + assert (clip_surface->status == CAIRO_STATUS_SUCCESS); + + mask = ((cairo_image_surface_t *) clip_surface)->pixman_image; + mask_x = -clip->path->extents.x; + mask_y = -clip->path->extents.y; + } else { + if (rects->bounded.width == rects->unbounded.width && + rects->bounded.height == rects->unbounded.height) + { + return; + } + } + + /* top */ + if (rects->bounded.y != rects->unbounded.y) { + int x = rects->unbounded.x; + int y = rects->unbounded.y; + int width = rects->unbounded.width; + int height = rects->bounded.y - y; + + if (mask != NULL) { + pixman_image_composite (PIXMAN_OP_OUT_REVERSE, + mask, NULL, dst->pixman_image, + x + mask_x, y + mask_y, + 0, 0, + x, y, + width, height); + } else { + pixman_fill ((uint32_t *) dst->data, dst->stride / sizeof (uint32_t), + PIXMAN_FORMAT_BPP (dst->pixman_format), + x, y, width, height, + 0); + } + } + + /* left */ + if (rects->bounded.x != rects->unbounded.x) { + int x = rects->unbounded.x; + int y = rects->bounded.y; + int width = rects->bounded.x - rects->unbounded.x; + int height = rects->bounded.height; + + if (mask != NULL) { + pixman_image_composite (PIXMAN_OP_OUT_REVERSE, + mask, NULL, dst->pixman_image, + x + mask_x, y + mask_y, + 0, 0, + x, y, + width, height); + } else { + pixman_fill ((uint32_t *) dst->data, dst->stride / sizeof (uint32_t), + PIXMAN_FORMAT_BPP (dst->pixman_format), + x, y, width, height, + 0); + } + } + + /* right */ + if (rects->bounded.x + rects->bounded.width != rects->unbounded.x + rects->unbounded.width) { + int x = rects->bounded.x + rects->bounded.width; + int y = rects->bounded.y; + int width = rects->unbounded.x + rects->unbounded.width - x; + int height = rects->bounded.height; + + if (mask != NULL) { + pixman_image_composite (PIXMAN_OP_OUT_REVERSE, + mask, NULL, dst->pixman_image, + x + mask_x, y + mask_y, + 0, 0, + x, y, + width, height); + } else { + pixman_fill ((uint32_t *) dst->data, dst->stride / sizeof (uint32_t), + PIXMAN_FORMAT_BPP (dst->pixman_format), + x, y, width, height, + 0); + } + } + + /* bottom */ + if (rects->bounded.y + rects->bounded.height != rects->unbounded.y + rects->unbounded.height) { + int x = rects->unbounded.x; + int y = rects->bounded.y + rects->bounded.height; + int width = rects->unbounded.width; + int height = rects->unbounded.y + rects->unbounded.height - y; + + if (mask != NULL) { + pixman_image_composite (PIXMAN_OP_OUT_REVERSE, + mask, NULL, dst->pixman_image, + x + mask_x, y + mask_y, + 0, 0, + x, y, + width, height); + } else { + pixman_fill ((uint32_t *) dst->data, dst->stride / sizeof (uint32_t), + PIXMAN_FORMAT_BPP (dst->pixman_format), + x, y, width, height, + 0); + } + } +} + +static cairo_status_t +_cairo_image_surface_fixup_unbounded_boxes (cairo_image_surface_t *dst, + const cairo_composite_rectangles_t *extents, + cairo_region_t *clip_region, + cairo_boxes_t *boxes) +{ + cairo_boxes_t clear; + cairo_box_t box; + cairo_status_t status; + struct _cairo_boxes_chunk *chunk; + int i; + + if (boxes->num_boxes <= 1 && clip_region == NULL) { + _cairo_image_surface_fixup_unbounded (dst, extents, NULL); + return CAIRO_STATUS_SUCCESS; + } + + _cairo_boxes_init (&clear); + + box.p1.x = _cairo_fixed_from_int (extents->unbounded.x + extents->unbounded.width); + box.p1.y = _cairo_fixed_from_int (extents->unbounded.y); + box.p2.x = _cairo_fixed_from_int (extents->unbounded.x); + box.p2.y = _cairo_fixed_from_int (extents->unbounded.y + extents->unbounded.height); + + if (clip_region == NULL) { + cairo_boxes_t tmp; + + _cairo_boxes_init (&tmp); + + status = _cairo_boxes_add (&tmp, &box); + assert (status == CAIRO_STATUS_SUCCESS); + + tmp.chunks.next = &boxes->chunks; + tmp.num_boxes += boxes->num_boxes; + + status = _cairo_bentley_ottmann_tessellate_boxes (&tmp, + CAIRO_FILL_RULE_WINDING, + &clear); + + tmp.chunks.next = NULL; + } else { + pixman_box32_t *pbox; + + pbox = pixman_region32_rectangles (&clip_region->rgn, &i); + _cairo_boxes_limit (&clear, (cairo_box_t *) pbox, i); + + status = _cairo_boxes_add (&clear, &box); + assert (status == CAIRO_STATUS_SUCCESS); + + for (chunk = &boxes->chunks; chunk != NULL; chunk = chunk->next) { + for (i = 0; i < chunk->count; i++) { + status = _cairo_boxes_add (&clear, &chunk->base[i]); + if (unlikely (status)) { + _cairo_boxes_fini (&clear); + return status; + } + } + } + + status = _cairo_bentley_ottmann_tessellate_boxes (&clear, + CAIRO_FILL_RULE_WINDING, + &clear); + } + + if (likely (status == CAIRO_STATUS_SUCCESS)) { + for (chunk = &clear.chunks; chunk != NULL; chunk = chunk->next) { + for (i = 0; i < chunk->count; i++) { + int x1 = _cairo_fixed_integer_part (chunk->base[i].p1.x); + int y1 = _cairo_fixed_integer_part (chunk->base[i].p1.y); + int x2 = _cairo_fixed_integer_part (chunk->base[i].p2.x); + int y2 = _cairo_fixed_integer_part (chunk->base[i].p2.y); + + pixman_fill ((uint32_t *) dst->data, dst->stride / sizeof (uint32_t), + PIXMAN_FORMAT_BPP (dst->pixman_format), + x1, y1, x2 - x1, y2 - y1, + 0); + } + } + } + + _cairo_boxes_fini (&clear); + + return status; +} + +static cairo_bool_t +can_reduce_alpha_op (cairo_operator_t op) +{ + int iop = op; + switch (iop) { + case CAIRO_OPERATOR_OVER: + case CAIRO_OPERATOR_SOURCE: + case CAIRO_OPERATOR_ADD: + return TRUE; + default: + return FALSE; + } +} + +static cairo_bool_t +reduce_alpha_op (cairo_image_surface_t *dst, + cairo_operator_t op, + const cairo_pattern_t *pattern) +{ + return dst->base.is_clear && + dst->base.content == CAIRO_CONTENT_ALPHA && + _cairo_pattern_is_opaque_solid (pattern) && + can_reduce_alpha_op (op); +} + +/* low level compositor */ +typedef cairo_status_t +(*image_draw_func_t) (void *closure, + pixman_image_t *dst, + pixman_format_code_t dst_format, + cairo_operator_t op, + const cairo_pattern_t *src, + int dst_x, + int dst_y, + const cairo_rectangle_int_t *extents, + cairo_region_t *clip_region); + +static pixman_image_t * +_create_composite_mask_pattern (cairo_clip_t *clip, + image_draw_func_t draw_func, + void *draw_closure, + cairo_image_surface_t *dst, + const cairo_rectangle_int_t *extents) +{ + cairo_region_t *clip_region = NULL; + pixman_image_t *mask; + cairo_status_t status; + cairo_bool_t need_clip_surface = FALSE; + + if (clip != NULL) { + status = _cairo_clip_get_region (clip, &clip_region); + assert (! _cairo_status_is_error (status)); + + /* The all-clipped state should never propagate this far. */ + assert (status != CAIRO_INT_STATUS_NOTHING_TO_DO); + + need_clip_surface = status == CAIRO_INT_STATUS_UNSUPPORTED; + + if (clip_region != NULL && cairo_region_num_rectangles (clip_region) == 1) + clip_region = NULL; + } + + mask = pixman_image_create_bits (PIXMAN_a8, extents->width, extents->height, + NULL, 0); + if (unlikely (mask == NULL)) + return NULL; + + /* Is it worth setting the clip region here? */ + if (clip_region != NULL) { + pixman_region32_translate (&clip_region->rgn, -extents->x, -extents->y); + pixman_image_set_clip_region32 (mask, &clip_region->rgn); + pixman_region32_translate (&clip_region->rgn, extents->x, extents->y); + } + + status = draw_func (draw_closure, + mask, PIXMAN_a8, + CAIRO_OPERATOR_ADD, NULL, + extents->x, extents->y, + extents, NULL); + if (unlikely (status)) { + pixman_image_unref (mask); + return NULL; + } + + if (need_clip_surface) { + cairo_surface_t *tmp; + + tmp = _cairo_image_surface_create_for_pixman_image (mask, PIXMAN_a8); + if (unlikely (tmp->status)) { + pixman_image_unref (mask); + return NULL; + } + + pixman_image_ref (mask); + + status = _cairo_clip_combine_with_surface (clip, tmp, extents->x, extents->y); + cairo_surface_destroy (tmp); + if (unlikely (status)) { + pixman_image_unref (mask); + return NULL; + } + } + + if (clip_region != NULL) + pixman_image_set_clip_region (mask, NULL); + + return mask; +} + +/* Handles compositing with a clip surface when the operator allows + * us to combine the clip with the mask + */ +static cairo_status_t +_clip_and_composite_with_mask (cairo_clip_t *clip, + cairo_operator_t op, + const cairo_pattern_t *pattern, + image_draw_func_t draw_func, + void *draw_closure, + cairo_image_surface_t *dst, + const cairo_rectangle_int_t *extents) +{ + pixman_image_t *mask; + + mask = _create_composite_mask_pattern (clip, draw_func, draw_closure, dst, extents); + if (unlikely (mask == NULL)) + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + + if (pattern == NULL) { + if (dst->pixman_format == PIXMAN_a8) { + pixman_image_composite (_pixman_operator (op), + mask, NULL, dst->pixman_image, + 0, 0, 0, 0, + extents->x, extents->y, + extents->width, extents->height); + } else { + pixman_image_t *src; + + src = _pixman_white_image (); + if (unlikely (src == NULL)) { + pixman_image_unref (mask); + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + } + + pixman_image_composite (_pixman_operator (op), + src, mask, dst->pixman_image, + 0, 0, 0, 0, + extents->x, extents->y, + extents->width, extents->height); + pixman_image_unref (src); + } + } else { + pixman_image_t *src; + int src_x, src_y; + + src = _pixman_image_for_pattern (pattern, extents, &src_x, &src_y); + if (unlikely (src == NULL)) { + pixman_image_unref (mask); + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + } + + pixman_image_composite (_pixman_operator (op), + src, mask, dst->pixman_image, + extents->x + src_x, extents->y + src_y, + 0, 0, + extents->x, extents->y, + extents->width, extents->height); + pixman_image_unref (src); + } + + pixman_image_unref (mask); + + return CAIRO_STATUS_SUCCESS; +} + +/* Handles compositing with a clip surface when we have to do the operation + * in two pieces and combine them together. + */ +static cairo_status_t +_clip_and_composite_combine (cairo_clip_t *clip, + cairo_operator_t op, + const cairo_pattern_t *src, + image_draw_func_t draw_func, + void *draw_closure, + cairo_image_surface_t *dst, + const cairo_rectangle_int_t *extents) +{ + pixman_image_t *tmp; + cairo_surface_t *clip_surface; + cairo_status_t status; + + tmp = pixman_image_create_bits (dst->pixman_format, + extents->width, extents->height, + NULL, 0); + if (unlikely (tmp == NULL)) + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + + if (src == NULL) { + status = (*draw_func) (draw_closure, + tmp, dst->pixman_format, + CAIRO_OPERATOR_ADD, NULL, + extents->x, extents->y, + extents, NULL); + } else { + /* Initialize the temporary surface from the destination surface */ + if (! dst->base.is_clear) { + pixman_image_composite (PIXMAN_OP_SRC, + dst->pixman_image, NULL, tmp, + extents->x, extents->y, + 0, 0, + 0, 0, + extents->width, extents->height); + } + + status = (*draw_func) (draw_closure, + tmp, dst->pixman_format, + op, src, + extents->x, extents->y, + extents, NULL); + } + if (unlikely (status)) + goto CLEANUP_SURFACE; + + assert (clip->path != NULL); + clip_surface = _cairo_clip_get_surface (clip, &dst->base); + if (unlikely (clip_surface->status)) + goto CLEANUP_SURFACE; + + if (! dst->base.is_clear) { +#if PIXMAN_HAS_OP_LERP + pixman_image_composite (PIXMAN_OP_LERP, + tmp, + ((cairo_image_surface_t *) clip_surface)->pixman_image, + dst->pixman_image, + 0, 0, + extents->x - clip->path->extents.x, + extents->y - clip->path->extents.y, + extents->x, extents->y, + extents->width, extents->height); +#else + /* Punch the clip out of the destination */ + pixman_image_composite (PIXMAN_OP_OUT_REVERSE, + ((cairo_image_surface_t *) clip_surface)->pixman_image, + NULL, dst->pixman_image, + extents->x - clip->path->extents.x, + extents->y - clip->path->extents.y, + 0, 0, + extents->x, extents->y, + extents->width, extents->height); + + /* Now add the two results together */ + pixman_image_composite (PIXMAN_OP_ADD, + tmp, + ((cairo_image_surface_t *) clip_surface)->pixman_image, + dst->pixman_image, + 0, 0, + extents->x - clip->path->extents.x, + extents->y - clip->path->extents.y, + extents->x, extents->y, + extents->width, extents->height); +#endif + } else { + pixman_image_composite (PIXMAN_OP_SRC, + tmp, + ((cairo_image_surface_t *) clip_surface)->pixman_image, + dst->pixman_image, + 0, 0, + extents->x - clip->path->extents.x, + extents->y - clip->path->extents.y, + extents->x, extents->y, + extents->width, extents->height); + } + + CLEANUP_SURFACE: + pixman_image_unref (tmp); + + return status; +} + +/* Handles compositing for %CAIRO_OPERATOR_SOURCE, which is special; it's + * defined as (src IN mask IN clip) ADD (dst OUT (mask IN clip)) + */ +static cairo_status_t +_clip_and_composite_source (cairo_clip_t *clip, + const cairo_pattern_t *pattern, + image_draw_func_t draw_func, + void *draw_closure, + cairo_image_surface_t *dst, + const cairo_rectangle_int_t *extents) +{ + pixman_image_t *mask, *src; + int src_x, src_y; + + if (pattern == NULL) { + cairo_region_t *clip_region; + cairo_status_t status; + + status = draw_func (draw_closure, + dst->pixman_image, dst->pixman_format, + CAIRO_OPERATOR_SOURCE, NULL, + extents->x, extents->y, + extents, NULL); + if (unlikely (status)) + return status; + + if (_cairo_clip_get_region (clip, &clip_region) == CAIRO_INT_STATUS_UNSUPPORTED) + status = _cairo_clip_combine_with_surface (clip, &dst->base, 0, 0); + + return status; + } + + /* Create a surface that is mask IN clip */ + mask = _create_composite_mask_pattern (clip, draw_func, draw_closure, dst, extents); + if (unlikely (mask == NULL)) + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + + src = _pixman_image_for_pattern (pattern, extents, &src_x, &src_y); + if (unlikely (src == NULL)) { + pixman_image_unref (mask); + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + } + + if (! dst->base.is_clear) { +#if PIXMAN_HAS_OP_LERP + pixman_image_composite (PIXMAN_OP_LERP, + src, mask, dst->pixman_image, + extents->x + src_x, extents->y + src_y, + 0, 0, + extents->x, extents->y, + extents->width, extents->height); +#else + /* Compute dest' = dest OUT (mask IN clip) */ + pixman_image_composite (PIXMAN_OP_OUT_REVERSE, + mask, NULL, dst->pixman_image, + 0, 0, 0, 0, + extents->x, extents->y, + extents->width, extents->height); + + /* Now compute (src IN (mask IN clip)) ADD dest' */ + pixman_image_composite (PIXMAN_OP_ADD, + src, mask, dst->pixman_image, + extents->x + src_x, extents->y + src_y, + 0, 0, + extents->x, extents->y, + extents->width, extents->height); +#endif + } else { + pixman_image_composite (PIXMAN_OP_SRC, + src, mask, dst->pixman_image, + extents->x + src_x, extents->y + src_y, + 0, 0, + extents->x, extents->y, + extents->width, extents->height); + } + + pixman_image_unref (src); + pixman_image_unref (mask); + + return CAIRO_STATUS_SUCCESS; +} + +static cairo_status_t +_clip_and_composite (cairo_image_surface_t *dst, + cairo_operator_t op, + const cairo_pattern_t *src, + image_draw_func_t draw_func, + void *draw_closure, + const cairo_composite_rectangles_t*extents, + cairo_clip_t *clip) +{ + cairo_status_t status; + cairo_region_t *clip_region = NULL; + cairo_bool_t need_clip_surface = FALSE; + + if (clip != NULL) { + status = _cairo_clip_get_region (clip, &clip_region); + if (unlikely (status == CAIRO_INT_STATUS_NOTHING_TO_DO)) + return CAIRO_STATUS_SUCCESS; + + assert (! _cairo_status_is_error (status)); + need_clip_surface = status == CAIRO_INT_STATUS_UNSUPPORTED; + + if (clip_region != NULL && cairo_region_num_rectangles (clip_region) == 1) + clip_region = NULL; + } + + if (clip_region != NULL) { + status = _cairo_image_surface_set_clip_region (dst, clip_region); + if (unlikely (status)) + return status; + } + + if (reduce_alpha_op (dst, op, src)) { + op = CAIRO_OPERATOR_ADD; + src = NULL; + } + + if (op == CAIRO_OPERATOR_SOURCE) { + status = _clip_and_composite_source (clip, src, + draw_func, draw_closure, + dst, &extents->bounded); + } else { + if (op == CAIRO_OPERATOR_CLEAR) { + src = NULL; + op = CAIRO_OPERATOR_DEST_OUT; + } + + if (need_clip_surface) { + if (extents->is_bounded) { + status = _clip_and_composite_with_mask (clip, op, src, + draw_func, draw_closure, + dst, &extents->bounded); + } else { + status = _clip_and_composite_combine (clip, op, src, + draw_func, draw_closure, + dst, &extents->bounded); + } + } else { + status = draw_func (draw_closure, + dst->pixman_image, dst->pixman_format, + op, src, + 0, 0, + &extents->bounded, + clip_region); + } + } + + if (status == CAIRO_STATUS_SUCCESS && ! extents->is_bounded) { + _cairo_image_surface_fixup_unbounded (dst, extents, + need_clip_surface ? clip : NULL); + } + + if (clip_region != NULL) + _cairo_image_surface_unset_clip_region (dst); + + return status; +} + +#define CAIRO_FIXED_16_16_MIN _cairo_fixed_from_int (-32768) +#define CAIRO_FIXED_16_16_MAX _cairo_fixed_from_int (32767) + +static cairo_bool_t +_line_exceeds_16_16 (const cairo_line_t *line) +{ + return + line->p1.x <= CAIRO_FIXED_16_16_MIN || + line->p1.x >= CAIRO_FIXED_16_16_MAX || + + line->p2.x <= CAIRO_FIXED_16_16_MIN || + line->p2.x >= CAIRO_FIXED_16_16_MAX || + + line->p1.y <= CAIRO_FIXED_16_16_MIN || + line->p1.y >= CAIRO_FIXED_16_16_MAX || + + line->p2.y <= CAIRO_FIXED_16_16_MIN || + line->p2.y >= CAIRO_FIXED_16_16_MAX; +} + +static void +_project_line_x_onto_16_16 (const cairo_line_t *line, + cairo_fixed_t top, + cairo_fixed_t bottom, + pixman_line_fixed_t *out) +{ + cairo_point_double_t p1, p2; + double m; + + p1.x = _cairo_fixed_to_double (line->p1.x); + p1.y = _cairo_fixed_to_double (line->p1.y); + + p2.x = _cairo_fixed_to_double (line->p2.x); + p2.y = _cairo_fixed_to_double (line->p2.y); + + m = (p2.x - p1.x) / (p2.y - p1.y); + out->p1.x = _cairo_fixed_16_16_from_double (p1.x + m * _cairo_fixed_to_double (top - line->p1.y)); + out->p2.x = _cairo_fixed_16_16_from_double (p1.x + m * _cairo_fixed_to_double (bottom - line->p1.y)); +} + + +typedef struct { + cairo_trapezoid_t *traps; + int num_traps; + cairo_antialias_t antialias; +} composite_traps_info_t; + +static void +_pixman_image_add_traps (pixman_image_t *image, + int dst_x, int dst_y, + composite_traps_info_t *info) +{ + cairo_trapezoid_t *t = info->traps; + int num_traps = info->num_traps; + while (num_traps--) { + pixman_trapezoid_t trap; + + /* top/bottom will be clamped to surface bounds */ + trap.top = _cairo_fixed_to_16_16 (t->top); + trap.bottom = _cairo_fixed_to_16_16 (t->bottom); + + /* However, all the other coordinates will have been left untouched so + * as not to introduce numerical error. Recompute them if they + * exceed the 16.16 limits. + */ + if (unlikely (_line_exceeds_16_16 (&t->left))) { + _project_line_x_onto_16_16 (&t->left, t->top, t->bottom, &trap.left); + trap.left.p1.y = trap.top; + trap.left.p2.y = trap.bottom; + } else { + trap.left.p1.x = _cairo_fixed_to_16_16 (t->left.p1.x); + trap.left.p1.y = _cairo_fixed_to_16_16 (t->left.p1.y); + trap.left.p2.x = _cairo_fixed_to_16_16 (t->left.p2.x); + trap.left.p2.y = _cairo_fixed_to_16_16 (t->left.p2.y); + } + + if (unlikely (_line_exceeds_16_16 (&t->right))) { + _project_line_x_onto_16_16 (&t->right, t->top, t->bottom, &trap.right); + trap.right.p1.y = trap.top; + trap.right.p2.y = trap.bottom; + } else { + trap.right.p1.x = _cairo_fixed_to_16_16 (t->right.p1.x); + trap.right.p1.y = _cairo_fixed_to_16_16 (t->right.p1.y); + trap.right.p2.x = _cairo_fixed_to_16_16 (t->right.p2.x); + trap.right.p2.y = _cairo_fixed_to_16_16 (t->right.p2.y); + } + + pixman_rasterize_trapezoid (image, &trap, -dst_x, -dst_y); + + t++; + } +} + +static cairo_status_t +_composite_traps (void *closure, + pixman_image_t *dst, + pixman_format_code_t dst_format, + cairo_operator_t op, + const cairo_pattern_t *pattern, + int dst_x, + int dst_y, + const cairo_rectangle_int_t *extents, + cairo_region_t *clip_region) +{ + composite_traps_info_t *info = closure; + pixman_image_t *src, *mask; + pixman_format_code_t format; + int src_x = 0, src_y = 0; + cairo_status_t status; + + /* Special case adding trapezoids onto a mask surface; we want to avoid + * creating an intermediate temporary mask unnecessarily. + * + * We make the assumption here that the portion of the trapezoids + * contained within the surface is bounded by [dst_x,dst_y,width,height]; + * the Cairo core code passes bounds based on the trapezoid extents. + */ + format = info->antialias == CAIRO_ANTIALIAS_NONE ? PIXMAN_a1 : PIXMAN_a8; + if (dst_format == format && + (pattern == NULL || + (op == CAIRO_OPERATOR_ADD && _cairo_pattern_is_opaque_solid (pattern)))) + { + _pixman_image_add_traps (dst, dst_x, dst_y, info); + return CAIRO_STATUS_SUCCESS; + } + + src = _pixman_image_for_pattern (pattern, extents, &src_x, &src_y); + if (unlikely (src == NULL)) + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + + mask = pixman_image_create_bits (format, extents->width, extents->height, + NULL, 0); + if (unlikely (mask == NULL)) { + status = _cairo_error (CAIRO_STATUS_NO_MEMORY); + goto CLEANUP_SOURCE; + } + + _pixman_image_add_traps (mask, extents->x, extents->y, info); + pixman_image_composite (_pixman_operator (op), + src, mask, dst, + extents->x + src_x, extents->y + src_y, + 0, 0, + extents->x - dst_x, extents->y - dst_y, + extents->width, extents->height); + + pixman_image_unref (mask); + + status = CAIRO_STATUS_SUCCESS; + CLEANUP_SOURCE: + pixman_image_unref (src); + + return status; +} + +static inline uint32_t +color_to_uint32 (const cairo_color_t *color) +{ + return + (color->alpha_short >> 8 << 24) | + (color->red_short >> 8 << 16) | + (color->green_short & 0xff00) | + (color->blue_short >> 8); +} + +static inline cairo_bool_t +color_to_pixel (const cairo_color_t *color, + pixman_format_code_t format, + uint32_t *pixel) +{ + uint32_t c; + + if (!(format == PIXMAN_a8r8g8b8 || + format == PIXMAN_x8r8g8b8 || + format == PIXMAN_a8b8g8r8 || + format == PIXMAN_x8b8g8r8 || + format == PIXMAN_b8g8r8a8 || + format == PIXMAN_b8g8r8x8 || + format == PIXMAN_r5g6b5 || + format == PIXMAN_b5g6r5 || + format == PIXMAN_a8)) + { + return FALSE; + } + + c = color_to_uint32 (color); + + if (PIXMAN_FORMAT_TYPE (format) == PIXMAN_TYPE_ABGR) { + c = ((c & 0xff000000) >> 0) | + ((c & 0x00ff0000) >> 16) | + ((c & 0x0000ff00) >> 0) | + ((c & 0x000000ff) << 16); + } + + if (PIXMAN_FORMAT_TYPE (format) == PIXMAN_TYPE_BGRA) { + c = ((c & 0xff000000) >> 24) | + ((c & 0x00ff0000) >> 8) | + ((c & 0x0000ff00) << 8) | + ((c & 0x000000ff) << 24); + } + + if (format == PIXMAN_a8) { + c = c >> 24; + } else if (format == PIXMAN_r5g6b5 || format == PIXMAN_b5g6r5) { + c = ((((c) >> 3) & 0x001f) | + (((c) >> 5) & 0x07e0) | + (((c) >> 8) & 0xf800)); + } + + *pixel = c; + return TRUE; +} + +static inline cairo_bool_t +pattern_to_pixel (const cairo_solid_pattern_t *solid, + cairo_operator_t op, + pixman_format_code_t format, + uint32_t *pixel) +{ + if (op == CAIRO_OPERATOR_CLEAR) { + *pixel = 0; + return TRUE; + } + + if (solid->base.type != CAIRO_PATTERN_TYPE_SOLID) + return FALSE; + + if (op == CAIRO_OPERATOR_OVER) { + if (solid->color.alpha_short >= 0xff00) + op = CAIRO_OPERATOR_SOURCE; + } + + if (op != CAIRO_OPERATOR_SOURCE) + return FALSE; + + return color_to_pixel (&solid->color, format, pixel); +} + +typedef struct _fill_span { + cairo_span_renderer_t base; + + uint8_t *mask_data; + pixman_image_t *src, *dst, *mask; +} fill_span_renderer_t; + +static cairo_status_t +_fill_span (void *abstract_renderer, + int y, int height, + const cairo_half_open_span_t *spans, + unsigned num_spans) +{ + fill_span_renderer_t *renderer = abstract_renderer; + uint8_t *row; + unsigned i; + + if (num_spans == 0) + return CAIRO_STATUS_SUCCESS; + + row = renderer->mask_data - spans[0].x; + for (i = 0; i < num_spans - 1; i++) { + /* We implement setting the most common single pixel wide + * span case to avoid the overhead of a memset call. + * Open coding setting longer spans didn't show a + * noticeable improvement over memset. + */ + if (spans[i+1].x == spans[i].x + 1) { + row[spans[i].x] = spans[i].coverage; + } else { + memset (row + spans[i].x, + spans[i].coverage, + spans[i+1].x - spans[i].x); + } + } + + do { + pixman_image_composite (PIXMAN_OP_OVER, + renderer->src, renderer->mask, renderer->dst, + 0, 0, 0, 0, + spans[0].x, y++, + spans[i].x - spans[0].x, 1); + } while (--height); + + return CAIRO_STATUS_SUCCESS; +} + +/* avoid using region code to re-validate boxes */ +static cairo_status_t +_fill_unaligned_boxes (cairo_image_surface_t *dst, + const cairo_pattern_t *pattern, + uint32_t pixel, + const cairo_boxes_t *boxes, + const cairo_composite_rectangles_t *extents) +{ + uint8_t buf[CAIRO_STACK_BUFFER_SIZE]; + fill_span_renderer_t renderer; + cairo_rectangular_scan_converter_t converter; + const struct _cairo_boxes_chunk *chunk; + cairo_status_t status; + int i; + + /* XXX + * using composite for fill: + * spiral-box-nonalign-evenodd-fill.512 2201957 2.202 + * spiral-box-nonalign-nonzero-fill.512 336726 0.337 + * spiral-box-pixalign-evenodd-fill.512 352256 0.352 + * spiral-box-pixalign-nonzero-fill.512 147056 0.147 + * using fill: + * spiral-box-nonalign-evenodd-fill.512 3174565 3.175 + * spiral-box-nonalign-nonzero-fill.512 182710 0.183 + * spiral-box-pixalign-evenodd-fill.512 353863 0.354 + * spiral-box-pixalign-nonzero-fill.512 147402 0.147 + * + * cairo-perf-trace seems to favour using fill. + */ + + renderer.base.render_rows = _fill_span; + renderer.dst = dst->pixman_image; + + if ((unsigned) extents->bounded.width <= sizeof (buf)) { + renderer.mask = pixman_image_create_bits (PIXMAN_a8, + extents->bounded.width, 1, + (uint32_t *) buf, + sizeof (buf)); + } else { + renderer.mask = pixman_image_create_bits (PIXMAN_a8, + extents->bounded.width, 1, + NULL, 0); + } + if (unlikely (renderer.mask == NULL)) + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + + renderer.mask_data = (uint8_t *) pixman_image_get_data (renderer.mask); + + renderer.src = _pixman_image_for_solid ((const cairo_solid_pattern_t *) pattern); + if (unlikely (renderer.src == NULL)) { + status = _cairo_error (CAIRO_STATUS_NO_MEMORY); + goto CLEANUP_MASK; + } + + _cairo_rectangular_scan_converter_init (&converter, &extents->bounded); + + /* first blit any aligned part of the boxes */ + for (chunk = &boxes->chunks; chunk != NULL; chunk = chunk->next) { + const cairo_box_t *box = chunk->base; + + for (i = 0; i < chunk->count; i++) { + int x1 = _cairo_fixed_integer_ceil (box[i].p1.x); + int y1 = _cairo_fixed_integer_ceil (box[i].p1.y); + int x2 = _cairo_fixed_integer_floor (box[i].p2.x); + int y2 = _cairo_fixed_integer_floor (box[i].p2.y); + + if (x2 > x1 && y2 > y1) { + cairo_box_t b; + + pixman_fill ((uint32_t *) dst->data, + dst->stride / sizeof (uint32_t), + PIXMAN_FORMAT_BPP (dst->pixman_format), + x1, y1, x2 - x1, y2 - y1, + pixel); + + /* top */ + if (! _cairo_fixed_is_integer (box[i].p1.y)) { + b.p1.x = box[i].p1.x; + b.p1.y = box[i].p1.y; + b.p2.x = box[i].p2.x; + b.p2.y = _cairo_fixed_from_int (y1); + + status = _cairo_rectangular_scan_converter_add_box (&converter, &b, 1); + if (unlikely (status)) + goto CLEANUP_CONVERTER; + } + + /* left */ + if (! _cairo_fixed_is_integer (box[i].p1.x)) { + b.p1.x = box[i].p1.x; + b.p1.y = box[i].p1.y; + b.p2.x = _cairo_fixed_from_int (x1); + b.p2.y = box[i].p2.y; + + status = _cairo_rectangular_scan_converter_add_box (&converter, &b, 1); + if (unlikely (status)) + goto CLEANUP_CONVERTER; + } + + /* right */ + if (! _cairo_fixed_is_integer (box[i].p2.x)) { + b.p1.x = _cairo_fixed_from_int (x2); + b.p1.y = box[i].p1.y; + b.p2.x = box[i].p2.x; + b.p2.y = box[i].p2.y; + + status = _cairo_rectangular_scan_converter_add_box (&converter, &b, 1); + if (unlikely (status)) + goto CLEANUP_CONVERTER; + } + + /* bottom */ + if (! _cairo_fixed_is_integer (box[i].p2.y)) { + b.p1.x = box[i].p1.x; + b.p1.y = _cairo_fixed_from_int (y2); + b.p2.x = box[i].p2.x; + b.p2.y = box[i].p2.y; + + status = _cairo_rectangular_scan_converter_add_box (&converter, &b, 1); + if (unlikely (status)) + goto CLEANUP_CONVERTER; + } + } else { + status = _cairo_rectangular_scan_converter_add_box (&converter, &box[i], 1); + if (unlikely (status)) + goto CLEANUP_CONVERTER; + } + } + } + + status = converter.base.generate (&converter.base, &renderer.base); + + CLEANUP_CONVERTER: + converter.base.destroy (&converter.base); + pixman_image_unref (renderer.src); + CLEANUP_MASK: + pixman_image_unref (renderer.mask); + + return status; +} + +typedef struct _cairo_image_surface_span_renderer { + cairo_span_renderer_t base; + + uint8_t *mask_data; + uint32_t mask_stride; +} cairo_image_surface_span_renderer_t; + +static cairo_status_t +_cairo_image_surface_span (void *abstract_renderer, + int y, int height, + const cairo_half_open_span_t *spans, + unsigned num_spans) +{ + cairo_image_surface_span_renderer_t *renderer = abstract_renderer; + uint8_t *row; + unsigned i; + + if (num_spans == 0) + return CAIRO_STATUS_SUCCESS; + + /* XXX will it be quicker to repeat the sparse memset, + * or perform a simpler memcpy? + * The fairly dense spiral benchmarks suggests that the sparse + * memset is a win there as well. + */ + row = renderer->mask_data + y * renderer->mask_stride; + do { + for (i = 0; i < num_spans - 1; i++) { + if (! spans[i].coverage) + continue; + + /* We implement setting rendering the most common single + * pixel wide span case to avoid the overhead of a memset + * call. Open coding setting longer spans didn't show a + * noticeable improvement over memset. */ + if (spans[i+1].x == spans[i].x + 1) { + row[spans[i].x] = spans[i].coverage; + } else { + memset (row + spans[i].x, + spans[i].coverage, + spans[i+1].x - spans[i].x); + } + } + row += renderer->mask_stride; + } while (--height); + + return CAIRO_STATUS_SUCCESS; +} + +static cairo_status_t +_composite_unaligned_boxes (cairo_image_surface_t *dst, + cairo_operator_t op, + const cairo_pattern_t *pattern, + const cairo_boxes_t *boxes, + const cairo_composite_rectangles_t *extents) +{ + uint8_t buf[CAIRO_STACK_BUFFER_SIZE]; + cairo_image_surface_span_renderer_t renderer; + cairo_rectangular_scan_converter_t converter; + pixman_image_t *mask, *src; + cairo_status_t status; + const struct _cairo_boxes_chunk *chunk; + int i, src_x, src_y; + + i = CAIRO_STRIDE_FOR_WIDTH_BPP (extents->bounded.width, 8) * extents->bounded.height; + if ((unsigned) i <= sizeof (buf)) { + mask = pixman_image_create_bits (PIXMAN_a8, + extents->bounded.width, + extents->bounded.height, + (uint32_t *) buf, + CAIRO_STRIDE_FOR_WIDTH_BPP (extents->bounded.width, 8)); + memset (buf, 0, i); + } else { + mask = pixman_image_create_bits (PIXMAN_a8, + extents->bounded.width, + extents->bounded.height, + NULL, 0); + } + if (unlikely (mask == NULL)) + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + + renderer.base.render_rows = _cairo_image_surface_span; + renderer.mask_stride = pixman_image_get_stride (mask); + renderer.mask_data = (uint8_t *) pixman_image_get_data (mask); + renderer.mask_data -= extents->bounded.y * renderer.mask_stride + extents->bounded.x; + + _cairo_rectangular_scan_converter_init (&converter, &extents->bounded); + + for (chunk = &boxes->chunks; chunk != NULL; chunk = chunk->next) { + const cairo_box_t *box = chunk->base; + + for (i = 0; i < chunk->count; i++) { + status = _cairo_rectangular_scan_converter_add_box (&converter, &box[i], 1); + if (unlikely (status)) + goto CLEANUP; + } + } + + status = converter.base.generate (&converter.base, &renderer.base); + if (unlikely (status)) + goto CLEANUP; + + src = _pixman_image_for_pattern (pattern, &extents->bounded, &src_x, &src_y); + if (unlikely (src == NULL)) { + status = _cairo_error (CAIRO_STATUS_NO_MEMORY); + goto CLEANUP; + } + + pixman_image_composite (_pixman_operator (op), + src, mask, dst->pixman_image, + extents->bounded.x + src_x, extents->bounded.y + src_y, + 0, 0, + extents->bounded.x, extents->bounded.y, + extents->bounded.width, extents->bounded.height); + pixman_image_unref (src); + + CLEANUP: + converter.base.destroy (&converter.base); + pixman_image_unref (mask); + + return status; +} + +static cairo_status_t +_composite_boxes (cairo_image_surface_t *dst, + cairo_operator_t op, + const cairo_pattern_t *pattern, + cairo_boxes_t *boxes, + cairo_antialias_t antialias, + cairo_clip_t *clip, + const cairo_composite_rectangles_t *extents) +{ + cairo_region_t *clip_region = NULL; + cairo_bool_t need_clip_mask = FALSE; + cairo_status_t status; + struct _cairo_boxes_chunk *chunk; + uint32_t pixel; + int i; + + if (clip != NULL) { + status = _cairo_clip_get_region (clip, &clip_region); + need_clip_mask = status == CAIRO_INT_STATUS_UNSUPPORTED; + if (need_clip_mask && + (op == CAIRO_OPERATOR_SOURCE || ! extents->is_bounded)) + { + return CAIRO_INT_STATUS_UNSUPPORTED; + } + + if (clip_region != NULL && cairo_region_num_rectangles (clip_region) == 1) + clip_region = NULL; + } + + if (antialias != CAIRO_ANTIALIAS_NONE) { + if (! boxes->is_pixel_aligned) { + if (need_clip_mask) + return CAIRO_INT_STATUS_UNSUPPORTED; + + if (pattern_to_pixel ((cairo_solid_pattern_t *) pattern, op, + dst->pixman_format, &pixel)) + { + return _fill_unaligned_boxes (dst, pattern, pixel, boxes, extents); + } + else + { + return _composite_unaligned_boxes (dst, op, pattern, boxes, extents); + } + } + } + + status = CAIRO_STATUS_SUCCESS; + if (! need_clip_mask && + pattern_to_pixel ((cairo_solid_pattern_t *) pattern, op, dst->pixman_format, + &pixel)) + { + for (chunk = &boxes->chunks; chunk != NULL; chunk = chunk->next) { + cairo_box_t *box = chunk->base; + + for (i = 0; i < chunk->count; i++) { + int x1 = _cairo_fixed_integer_round (box[i].p1.x); + int y1 = _cairo_fixed_integer_round (box[i].p1.y); + int x2 = _cairo_fixed_integer_round (box[i].p2.x); + int y2 = _cairo_fixed_integer_round (box[i].p2.y); + + if (x2 == x1 || y2 == y1) + continue; + + pixman_fill ((uint32_t *) dst->data, dst->stride / sizeof (uint32_t), + PIXMAN_FORMAT_BPP (dst->pixman_format), + x1, y1, x2 - x1, y2 - y1, + pixel); + } + } + } + else + { + pixman_image_t *src = NULL, *mask = NULL; + int src_x, src_y, mask_x = 0, mask_y = 0; + pixman_op_t pixman_op = _pixman_operator (op); + + if (need_clip_mask) { + cairo_surface_t *clip_surface; + + clip_surface = _cairo_clip_get_surface (clip, &dst->base); + if (unlikely (clip_surface->status)) + return clip_surface->status; + + mask_x = -clip->path->extents.x; + mask_y = -clip->path->extents.y; + + if (op == CAIRO_OPERATOR_CLEAR) { + pattern = NULL; + pixman_op = PIXMAN_OP_OUT_REVERSE; + } + + mask = ((cairo_image_surface_t *) clip_surface)->pixman_image; + } + + if (pattern != NULL) { + src = _pixman_image_for_pattern (pattern, &extents->bounded, &src_x, &src_y); + if (unlikely (src == NULL)) + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + } else { + src = mask; + src_x = mask_x; + src_y = mask_y; + mask = NULL; + } + + for (chunk = &boxes->chunks; chunk != NULL; chunk = chunk->next) { + const cairo_box_t *box = chunk->base; + + for (i = 0; i < chunk->count; i++) { + int x1 = _cairo_fixed_integer_round (box[i].p1.x); + int y1 = _cairo_fixed_integer_round (box[i].p1.y); + int x2 = _cairo_fixed_integer_round (box[i].p2.x); + int y2 = _cairo_fixed_integer_round (box[i].p2.y); + + if (x2 == x1 || y2 == y1) + continue; + + pixman_image_composite (pixman_op, + src, mask, dst->pixman_image, + x1 + src_x, y1 + src_y, + x1 + mask_x, y1 + mask_y, + x1, y1, + x2 - x1, y2 - y1); + } + } + + if (pattern != NULL) + pixman_image_unref (src); + + if (! extents->is_bounded) { + status = + _cairo_image_surface_fixup_unbounded_boxes (dst, extents, + clip_region, boxes); + } + } + + return status; +} + +static cairo_status_t +_clip_and_composite_boxes (cairo_image_surface_t *dst, + cairo_operator_t op, + const cairo_pattern_t *src, + cairo_boxes_t *boxes, + cairo_antialias_t antialias, + const cairo_composite_rectangles_t *extents, + cairo_clip_t *clip) +{ + cairo_traps_t traps; + cairo_status_t status; + composite_traps_info_t info; + + if (boxes->num_boxes == 0 && extents->is_bounded) + return CAIRO_STATUS_SUCCESS; + + /* Use a fast path if the boxes are pixel aligned */ + status = _composite_boxes (dst, op, src, boxes, antialias, clip, extents); + if (status != CAIRO_INT_STATUS_UNSUPPORTED) + return status; + + /* Otherwise render via a mask and composite in the usual fashion. */ + status = _cairo_traps_init_boxes (&traps, boxes); + if (unlikely (status)) + return status; + + info.num_traps = traps.num_traps; + info.traps = traps.traps; + info.antialias = antialias; + return _clip_and_composite (dst, op, src, + _composite_traps, &info, + extents, clip); +} + +static cairo_bool_t +_mono_edge_is_vertical (const cairo_line_t *line) +{ + return _cairo_fixed_integer_round (line->p1.x) == _cairo_fixed_integer_round (line->p2.x); +} + +static cairo_bool_t +_traps_are_pixel_aligned (cairo_traps_t *traps, + cairo_antialias_t antialias) +{ + int i; + + if (antialias == CAIRO_ANTIALIAS_NONE) { + for (i = 0; i < traps->num_traps; i++) { + if (! _mono_edge_is_vertical (&traps->traps[i].left) || + ! _mono_edge_is_vertical (&traps->traps[i].right)) + { + traps->maybe_region = FALSE; + return FALSE; + } + } + } else { + for (i = 0; i < traps->num_traps; i++) { + if (traps->traps[i].left.p1.x != traps->traps[i].left.p2.x || + traps->traps[i].right.p1.x != traps->traps[i].right.p2.x || + ! _cairo_fixed_is_integer (traps->traps[i].top) || + ! _cairo_fixed_is_integer (traps->traps[i].bottom) || + ! _cairo_fixed_is_integer (traps->traps[i].left.p1.x) || + ! _cairo_fixed_is_integer (traps->traps[i].right.p1.x)) + { + traps->maybe_region = FALSE; + return FALSE; + } + } + } + + return TRUE; +} + +static void +_boxes_for_traps (cairo_boxes_t *boxes, + cairo_traps_t *traps) +{ + int i; + + _cairo_boxes_init (boxes); + + boxes->num_boxes = traps->num_traps; + boxes->chunks.base = (cairo_box_t *) traps->traps; + boxes->chunks.count = traps->num_traps; + boxes->chunks.size = traps->num_traps; + + for (i = 0; i < traps->num_traps; i++) { + cairo_fixed_t x1 = traps->traps[i].left.p1.x; + cairo_fixed_t x2 = traps->traps[i].right.p1.x; + cairo_fixed_t y1 = traps->traps[i].top; + cairo_fixed_t y2 = traps->traps[i].bottom; + + boxes->chunks.base[i].p1.x = x1; + boxes->chunks.base[i].p1.y = y1; + boxes->chunks.base[i].p2.x = x2; + boxes->chunks.base[i].p2.y = y2; + + if (boxes->is_pixel_aligned) { + boxes->is_pixel_aligned = + _cairo_fixed_is_integer (x1) && _cairo_fixed_is_integer (y1) && + _cairo_fixed_is_integer (x2) && _cairo_fixed_is_integer (y2); + } + } +} + +static cairo_status_t +_clip_and_composite_trapezoids (cairo_image_surface_t *dst, + cairo_operator_t op, + const cairo_pattern_t *src, + cairo_traps_t *traps, + cairo_antialias_t antialias, + const cairo_composite_rectangles_t *extents, + cairo_clip_t *clip) +{ + composite_traps_info_t info; + cairo_bool_t need_clip_surface = FALSE; + cairo_status_t status; + + if (traps->num_traps == 0 && extents->is_bounded) + return CAIRO_STATUS_SUCCESS; + + if (clip != NULL) { + cairo_region_t *clip_region; + + status = _cairo_clip_get_region (clip, &clip_region); + need_clip_surface = status == CAIRO_INT_STATUS_UNSUPPORTED; + } + + if (traps->has_intersections) { + if (traps->is_rectangular) + status = _cairo_bentley_ottmann_tessellate_rectangular_traps (traps, CAIRO_FILL_RULE_WINDING); + else if (traps->is_rectilinear) + status = _cairo_bentley_ottmann_tessellate_rectilinear_traps (traps, CAIRO_FILL_RULE_WINDING); + else + status = _cairo_bentley_ottmann_tessellate_traps (traps, CAIRO_FILL_RULE_WINDING); + if (unlikely (status)) + return status; + } + + /* Use a fast path if the trapezoids consist of a simple region, + * but we can only do this if we do not have a clip surface, or can + * substitute the mask with the clip. + */ + if (traps->maybe_region && _traps_are_pixel_aligned (traps, antialias) && + (! need_clip_surface || + (extents->is_bounded && op != CAIRO_OPERATOR_SOURCE))) + { + cairo_boxes_t boxes; + + _boxes_for_traps (&boxes, traps); + return _clip_and_composite_boxes (dst, op, src, + &boxes, antialias, + extents, clip); + } + + /* No fast path, exclude self-intersections and clip trapezoids. */ + /* Otherwise render the trapezoids to a mask and composite in the usual + * fashion. + */ + info.traps = traps->traps;; + info.num_traps = traps->num_traps; + info.antialias = antialias; + return _clip_and_composite (dst, op, src, + _composite_traps, &info, + extents, clip); +} + +static cairo_bool_t +box_is_aligned (const cairo_box_t *box) +{ + return + _cairo_fixed_is_integer (box->p1.x) && + _cairo_fixed_is_integer (box->p1.y) && + _cairo_fixed_is_integer (box->p2.x) && + _cairo_fixed_is_integer (box->p2.y); +} + +static inline cairo_status_t +_clip_to_boxes (cairo_clip_t **clip, + const cairo_composite_rectangles_t *extents, + cairo_box_t **boxes, + int *num_boxes) +{ + cairo_status_t status; + const cairo_rectangle_int_t *rect; + + rect = extents->is_bounded ? &extents->bounded : &extents->unbounded; + + if (*clip == NULL) + goto EXTENTS; + + status = _cairo_clip_rectangle (*clip, rect); + if (unlikely (status)) + return status; + + status = _cairo_clip_get_boxes (*clip, boxes, num_boxes); + switch ((int) status) { + case CAIRO_STATUS_SUCCESS: + if (extents->is_bounded || (*num_boxes == 1 && box_is_aligned (*boxes))) + *clip = NULL; + goto DONE; + + case CAIRO_INT_STATUS_UNSUPPORTED: + goto EXTENTS; + + default: + return status; + } + + EXTENTS: + status = CAIRO_STATUS_SUCCESS; + _cairo_box_from_rectangle (&(*boxes)[0], rect); + *num_boxes = 1; + DONE: + return status; +} + +static cairo_clip_path_t * +_clip_get_single_path (cairo_clip_t *clip) +{ + cairo_clip_path_t *iter = clip->path; + cairo_clip_path_t *path = NULL; + + do { + if ((iter->flags & CAIRO_CLIP_PATH_IS_BOX) == 0) { + if (path != NULL) + return FALSE; + + path = iter; + } + iter = iter->prev; + } while (iter != NULL); + + return path; +} + +/* high level image interface */ + +static cairo_int_status_t +_cairo_image_surface_paint (void *abstract_surface, + cairo_operator_t op, + const cairo_pattern_t *source, + cairo_clip_t *clip) +{ + cairo_image_surface_t *surface = abstract_surface; + cairo_composite_rectangles_t extents; + cairo_clip_path_t *clip_path; + cairo_clip_t local_clip; + cairo_bool_t have_clip = FALSE; + cairo_box_t boxes_stack[32], *clip_boxes = boxes_stack; + int num_boxes = ARRAY_LENGTH (boxes_stack); + cairo_status_t status; + + status = _cairo_composite_rectangles_init_for_paint (&extents, + surface->width, + surface->height, + op, source, + clip); + if (unlikely (status)) + return status; + + if (_cairo_clip_contains_rectangle (clip, &extents)) + clip = NULL; + + if (clip != NULL) { + clip = _cairo_clip_init_copy (&local_clip, clip); + have_clip = TRUE; + } + + status = _clip_to_boxes (&clip, &extents, &clip_boxes, &num_boxes); + if (unlikely (status)) { + if (have_clip) + _cairo_clip_fini (&local_clip); + + return status; + } + + /* If the clip cannot be reduced to a set of boxes, we will need to + * use a clipmask. Paint is special as it is the only operation that + * does not implicitly use a mask, so we may be able to reduce this + * operation to a fill... + */ + if (clip != NULL && + extents.is_bounded && + (clip_path = _clip_get_single_path (clip)) != NULL) + { + status = _cairo_image_surface_fill (surface, op, source, + &clip_path->path, + clip_path->fill_rule, + clip_path->tolerance, + clip_path->antialias, + NULL); + } + else + { + cairo_boxes_t boxes; + + _cairo_boxes_init_for_array (&boxes, clip_boxes, num_boxes); + status = _clip_and_composite_boxes (surface, op, source, + &boxes, CAIRO_ANTIALIAS_DEFAULT, + &extents, clip); + } + + if (clip_boxes != boxes_stack) + free (clip_boxes); + + if (have_clip) + _cairo_clip_fini (&local_clip); + + return status; +} + +static cairo_status_t +_composite_mask (void *closure, + pixman_image_t *dst, + pixman_format_code_t dst_format, + cairo_operator_t op, + const cairo_pattern_t *src_pattern, + int dst_x, + int dst_y, + const cairo_rectangle_int_t *extents, + cairo_region_t *clip_region) +{ + const cairo_pattern_t *mask_pattern = closure; + pixman_image_t *src, *mask = NULL; + int src_x = 0, src_y = 0; + int mask_x = 0, mask_y = 0; + + if (src_pattern != NULL) { + src = _pixman_image_for_pattern (src_pattern, extents, &src_x, &src_y); + if (unlikely (src == NULL)) + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + + mask = _pixman_image_for_pattern (mask_pattern, extents, &mask_x, &mask_y); + if (unlikely (mask == NULL)) { + pixman_image_unref (src); + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + } + + if (mask_pattern->has_component_alpha) + pixman_image_set_component_alpha (mask, TRUE); + } else { + src = _pixman_image_for_pattern (mask_pattern, extents, &src_x, &src_y); + if (unlikely (src == NULL)) + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + } + + pixman_image_composite (_pixman_operator (op), src, mask, dst, + extents->x + src_x, extents->y + src_y, + extents->x + mask_x, extents->y + mask_y, + extents->x - dst_x, extents->y - dst_y, + extents->width, extents->height); + + if (mask != NULL) + pixman_image_unref (mask); + pixman_image_unref (src); + + return CAIRO_STATUS_SUCCESS; +} + +static cairo_int_status_t +_cairo_image_surface_mask (void *abstract_surface, + cairo_operator_t op, + const cairo_pattern_t *source, + const cairo_pattern_t *mask, + cairo_clip_t *clip) +{ + cairo_image_surface_t *surface = abstract_surface; + cairo_composite_rectangles_t extents; + cairo_clip_t local_clip; + cairo_bool_t have_clip = FALSE; + cairo_status_t status; + + status = _cairo_composite_rectangles_init_for_mask (&extents, + surface->width, surface->height, + op, source, mask, clip); + if (unlikely (status)) + return status; + + if (_cairo_clip_contains_rectangle (clip, &extents)) + clip = NULL; + + if (clip != NULL && extents.is_bounded) { + clip = _cairo_clip_init_copy (&local_clip, clip); + status = _cairo_clip_rectangle (clip, &extents.bounded); + if (unlikely (status)) { + _cairo_clip_fini (&local_clip); + return status; + } + + have_clip = TRUE; + } + + status = _clip_and_composite (surface, op, source, + _composite_mask, (void *) mask, + &extents, clip); + + if (have_clip) + _cairo_clip_fini (&local_clip); + + return status; +} + +typedef struct { + cairo_polygon_t *polygon; + cairo_fill_rule_t fill_rule; + cairo_antialias_t antialias; +} composite_spans_info_t; + +//#define USE_BOTOR_SCAN_CONVERTER +static cairo_status_t +_composite_spans (void *closure, + pixman_image_t *dst, + pixman_format_code_t dst_format, + cairo_operator_t op, + const cairo_pattern_t *pattern, + int dst_x, + int dst_y, + const cairo_rectangle_int_t *extents, + cairo_region_t *clip_region) +{ + uint8_t mask_buf[CAIRO_STACK_BUFFER_SIZE]; + composite_spans_info_t *info = closure; + cairo_image_surface_span_renderer_t renderer; +#if USE_BOTOR_SCAN_CONVERTER + cairo_box_t box; + cairo_botor_scan_converter_t converter; +#else + cairo_scan_converter_t *converter; +#endif + pixman_image_t *mask; + cairo_status_t status; + +#if USE_BOTOR_SCAN_CONVERTER + box.p1.x = _cairo_fixed_from_int (extents->x); + box.p1.y = _cairo_fixed_from_int (extents->y); + box.p2.x = _cairo_fixed_from_int (extents->x + extents->width); + box.p2.y = _cairo_fixed_from_int (extents->y + extents->height); + _cairo_botor_scan_converter_init (&converter, &box, info->fill_rule); + status = converter.base.add_polygon (&converter.base, info->polygon); +#else + converter = _cairo_tor_scan_converter_create (extents->x, extents->y, + extents->x + extents->width, + extents->y + extents->height, + info->fill_rule); + status = converter->add_polygon (converter, info->polygon); +#endif + if (unlikely (status)) + goto CLEANUP_CONVERTER; + + /* TODO: support rendering to A1 surfaces (or: go add span + * compositing to pixman.) */ + + if (pattern == NULL && dst_format == PIXMAN_a8) { + mask = dst; + dst = NULL; + } else { + int stride = CAIRO_STRIDE_FOR_WIDTH_BPP (extents->width, 8); + uint8_t *data = mask_buf; + + if (extents->height * stride <= (int) sizeof (mask_buf)) + memset (data, 0, extents->height * stride); + else + data = NULL, stride = 0; + + mask = pixman_image_create_bits (PIXMAN_a8, + extents->width, + extents->height, + (uint32_t *) data, + stride); + if (unlikely (mask == NULL)) { + status = _cairo_error (CAIRO_STATUS_NO_MEMORY); + goto CLEANUP_CONVERTER; + } + } + + renderer.base.render_rows = _cairo_image_surface_span; + renderer.mask_stride = pixman_image_get_stride (mask); + renderer.mask_data = (uint8_t *) pixman_image_get_data (mask); + if (dst != NULL) + renderer.mask_data -= extents->y * renderer.mask_stride + extents->x; + else + renderer.mask_data -= dst_y * renderer.mask_stride + dst_x; + +#if USE_BOTOR_SCAN_CONVERTER + status = converter.base.generate (&converter.base, &renderer.base); +#else + status = converter->generate (converter, &renderer.base); +#endif + if (unlikely (status)) + goto CLEANUP_RENDERER; + + if (dst != NULL) { + pixman_image_t *src; + int src_x, src_y; + + src = _pixman_image_for_pattern (pattern, extents, &src_x, &src_y); + if (unlikely (src == NULL)) { + status = _cairo_error (CAIRO_STATUS_NO_MEMORY); + goto CLEANUP_RENDERER; + } + + pixman_image_composite (_pixman_operator (op), src, mask, dst, + extents->x + src_x, extents->y + src_y, + 0, 0, /* mask.x, mask.y */ + extents->x - dst_x, extents->y - dst_y, + extents->width, extents->height); + pixman_image_unref (src); + } + + CLEANUP_RENDERER: + if (dst != NULL) + pixman_image_unref (mask); + CLEANUP_CONVERTER: +#if USE_BOTOR_SCAN_CONVERTER + converter.base.destroy (&converter.base); +#else + converter->destroy (converter); +#endif + return status; +} + +static cairo_status_t +_clip_and_composite_polygon (cairo_image_surface_t *dst, + cairo_operator_t op, + const cairo_pattern_t *src, + cairo_polygon_t *polygon, + cairo_fill_rule_t fill_rule, + cairo_antialias_t antialias, + cairo_composite_rectangles_t *extents, + cairo_clip_t *clip) +{ + cairo_status_t status; + + if (polygon->num_edges == 0) { + cairo_traps_t traps; + + if (extents->is_bounded) + return CAIRO_STATUS_SUCCESS; + + _cairo_traps_init (&traps); + status = _clip_and_composite_trapezoids (dst, op, src, + &traps, antialias, + extents, clip); + _cairo_traps_fini (&traps); + + return status; + } + + _cairo_box_round_to_rectangle (&polygon->extents, &extents->mask); + if (! _cairo_rectangle_intersect (&extents->bounded, &extents->mask)) + return CAIRO_STATUS_SUCCESS; + + if (antialias != CAIRO_ANTIALIAS_NONE) { + composite_spans_info_t info; + + info.polygon = polygon; + info.fill_rule = fill_rule; + info.antialias = antialias; + + status = _clip_and_composite (dst, op, src, + _composite_spans, &info, + extents, clip); + } else { + cairo_traps_t traps; + + _cairo_traps_init (&traps); + + /* Fall back to trapezoid fills. */ + status = _cairo_bentley_ottmann_tessellate_polygon (&traps, + polygon, + fill_rule); + if (likely (status == CAIRO_STATUS_SUCCESS)) { + status = _clip_and_composite_trapezoids (dst, op, src, + &traps, antialias, + extents, clip); + } + + _cairo_traps_fini (&traps); + } + + return status; +} + +static cairo_int_status_t +_cairo_image_surface_stroke (void *abstract_surface, + cairo_operator_t op, + const cairo_pattern_t *source, + cairo_path_fixed_t *path, + const cairo_stroke_style_t *style, + const cairo_matrix_t *ctm, + const cairo_matrix_t *ctm_inverse, + double tolerance, + cairo_antialias_t antialias, + cairo_clip_t *clip) +{ + cairo_image_surface_t *surface = abstract_surface; + cairo_composite_rectangles_t extents; + cairo_box_t boxes_stack[32], *clip_boxes = boxes_stack; + int num_boxes = ARRAY_LENGTH (boxes_stack); + cairo_clip_t local_clip; + cairo_bool_t have_clip = FALSE; + cairo_status_t status; + + status = _cairo_composite_rectangles_init_for_stroke (&extents, + surface->width, + surface->height, + op, source, + path, style, ctm, + clip); + if (unlikely (status)) + return status; + + if (_cairo_clip_contains_rectangle (clip, &extents)) + clip = NULL; + + if (clip != NULL) { + clip = _cairo_clip_init_copy (&local_clip, clip); + have_clip = TRUE; + } + + status = _clip_to_boxes (&clip, &extents, &clip_boxes, &num_boxes); + if (unlikely (status)) { + if (have_clip) + _cairo_clip_fini (&local_clip); + + return status; + } + + status = CAIRO_INT_STATUS_UNSUPPORTED; + if (path->is_rectilinear) { + cairo_boxes_t boxes; + + _cairo_boxes_init (&boxes); + _cairo_boxes_limit (&boxes, clip_boxes, num_boxes); + + status = _cairo_path_fixed_stroke_rectilinear_to_boxes (path, + style, + ctm, + &boxes); + if (likely (status == CAIRO_STATUS_SUCCESS)) { + status = _clip_and_composite_boxes (surface, op, source, + &boxes, antialias, + &extents, clip); + } + + _cairo_boxes_fini (&boxes); + } + + if (status == CAIRO_INT_STATUS_UNSUPPORTED) { + cairo_polygon_t polygon; + + _cairo_polygon_init (&polygon); + _cairo_polygon_limit (&polygon, clip_boxes, num_boxes); + + status = _cairo_path_fixed_stroke_to_polygon (path, + style, + ctm, ctm_inverse, + tolerance, + &polygon); + if (likely (status == CAIRO_STATUS_SUCCESS)) { + status = _clip_and_composite_polygon (surface, op, source, &polygon, + CAIRO_FILL_RULE_WINDING, antialias, + &extents, clip); + } + + _cairo_polygon_fini (&polygon); + } + + if (clip_boxes != boxes_stack) + free (clip_boxes); + + if (have_clip) + _cairo_clip_fini (&local_clip); + + return status; +} + +static cairo_int_status_t +_cairo_image_surface_fill (void *abstract_surface, + cairo_operator_t op, + const cairo_pattern_t *source, + cairo_path_fixed_t *path, + cairo_fill_rule_t fill_rule, + double tolerance, + cairo_antialias_t antialias, + cairo_clip_t *clip) +{ + cairo_image_surface_t *surface = abstract_surface; + cairo_composite_rectangles_t extents; + cairo_box_t boxes_stack[32], *clip_boxes = boxes_stack; + cairo_clip_t local_clip; + cairo_bool_t have_clip = FALSE; + int num_boxes = ARRAY_LENGTH (boxes_stack); + cairo_status_t status; + + status = _cairo_composite_rectangles_init_for_fill (&extents, + surface->width, + surface->height, + op, source, path, + clip); + if (unlikely (status)) + return status; + + if (_cairo_clip_contains_rectangle (clip, &extents)) + clip = NULL; + + if (extents.is_bounded && clip != NULL) { + cairo_clip_path_t *clip_path; + + if (((clip_path = _clip_get_single_path (clip)) != NULL) && + _cairo_path_fixed_equal (&clip_path->path, path)) + { + clip = NULL; + } + } + + if (clip != NULL) { + clip = _cairo_clip_init_copy (&local_clip, clip); + have_clip = TRUE; + } + + status = _clip_to_boxes (&clip, &extents, &clip_boxes, &num_boxes); + if (unlikely (status)) { + if (have_clip) + _cairo_clip_fini (&local_clip); + + return status; + } + + if (_cairo_path_fixed_is_rectilinear_fill (path)) { + cairo_boxes_t boxes; + + _cairo_boxes_init (&boxes); + _cairo_boxes_limit (&boxes, clip_boxes, num_boxes); + + status = _cairo_path_fixed_fill_rectilinear_to_boxes (path, + fill_rule, + &boxes); + if (likely (status == CAIRO_STATUS_SUCCESS)) { + status = _clip_and_composite_boxes (surface, op, source, + &boxes, antialias, + &extents, clip); + } + + _cairo_boxes_fini (&boxes); + } else { + cairo_polygon_t polygon; + + assert (! path->is_empty_fill); + + _cairo_polygon_init (&polygon); + _cairo_polygon_limit (&polygon, clip_boxes, num_boxes); + + status = _cairo_path_fixed_fill_to_polygon (path, tolerance, &polygon); + if (likely (status == CAIRO_STATUS_SUCCESS)) { + status = _clip_and_composite_polygon (surface, op, source, &polygon, + fill_rule, antialias, + &extents, clip); + } + + _cairo_polygon_fini (&polygon); + } + + if (clip_boxes != boxes_stack) + free (clip_boxes); + + if (have_clip) + _cairo_clip_fini (&local_clip); + + return status; +} + +typedef struct { + cairo_scaled_font_t *font; + cairo_glyph_t *glyphs; + int num_glyphs; +} composite_glyphs_info_t; + +static cairo_status_t +_composite_glyphs_via_mask (void *closure, + pixman_image_t *dst, + pixman_format_code_t dst_format, + cairo_operator_t op, + const cairo_pattern_t *pattern, + int dst_x, + int dst_y, + const cairo_rectangle_int_t *extents, + cairo_region_t *clip_region) +{ + composite_glyphs_info_t *info = closure; + cairo_scaled_font_t *font = info->font; + cairo_glyph_t *glyphs = info->glyphs; + int num_glyphs = info->num_glyphs; + pixman_image_t *mask = NULL; + pixman_image_t *src; + pixman_image_t *white; + pixman_format_code_t mask_format = 0; /* silence gcc */ + cairo_status_t status; + int src_x, src_y; + int i; + + src = _pixman_image_for_pattern (pattern, extents, &src_x, &src_y); + if (unlikely (src == NULL)) + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + + white = _pixman_white_image (); + if (unlikely (white == NULL)) { + pixman_image_unref (src); + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + } + + _cairo_scaled_font_freeze_cache (font); + + for (i = 0; i < num_glyphs; i++) { + int x, y; + cairo_image_surface_t *glyph_surface; + cairo_scaled_glyph_t *scaled_glyph; + + status = _cairo_scaled_glyph_lookup (font, glyphs[i].index, + CAIRO_SCALED_GLYPH_INFO_SURFACE, + &scaled_glyph); + + if (unlikely (status)) + goto CLEANUP; + + glyph_surface = scaled_glyph->surface; + + if (glyph_surface->width == 0 || glyph_surface->height == 0) + continue; + + /* To start, create the mask using the format from the first + * glyph. Later we'll deal with different formats. */ + if (mask == NULL) { + mask_format = glyph_surface->pixman_format; + mask = pixman_image_create_bits (mask_format, + extents->width, extents->height, + NULL, 0); + if (unlikely (mask == NULL)) { + status = _cairo_error (CAIRO_STATUS_NO_MEMORY); + goto CLEANUP; + } + + if (PIXMAN_FORMAT_RGB (mask_format)) + pixman_image_set_component_alpha (mask, TRUE); + } + + /* If we have glyphs of different formats, we "upgrade" the mask + * to the wider of the formats. */ + if (glyph_surface->pixman_format != mask_format && + PIXMAN_FORMAT_BPP (mask_format) < + PIXMAN_FORMAT_BPP (glyph_surface->pixman_format)) + { + pixman_image_t *new_mask; + + mask_format = glyph_surface->pixman_format; + new_mask = pixman_image_create_bits (mask_format, + extents->width, extents->height, + NULL, 0); + if (unlikely (new_mask == NULL)) { + status = _cairo_error (CAIRO_STATUS_NO_MEMORY); + goto CLEANUP; + } + + pixman_image_composite (PIXMAN_OP_SRC, + white, mask, new_mask, + 0, 0, 0, 0, 0, 0, + extents->width, extents->height); + + pixman_image_unref (mask); + mask = new_mask; + if (PIXMAN_FORMAT_RGB (mask_format)) + pixman_image_set_component_alpha (mask, TRUE); + } + + /* round glyph locations to the nearest pixel */ + /* XXX: FRAGILE: We're ignoring device_transform scaling here. A bug? */ + x = _cairo_lround (glyphs[i].x - + glyph_surface->base.device_transform.x0); + y = _cairo_lround (glyphs[i].y - + glyph_surface->base.device_transform.y0); + if (glyph_surface->pixman_format == mask_format) { + pixman_image_composite (PIXMAN_OP_ADD, + glyph_surface->pixman_image, NULL, mask, + 0, 0, 0, 0, + x - extents->x, y - extents->y, + glyph_surface->width, + glyph_surface->height); + } else { + pixman_image_composite (PIXMAN_OP_ADD, + white, glyph_surface->pixman_image, mask, + 0, 0, 0, 0, + x - extents->x, y - extents->y, + glyph_surface->width, + glyph_surface->height); + } + } + + pixman_image_composite (_pixman_operator (op), + src, mask, dst, + extents->x + src_x, extents->y + src_y, + 0, 0, + extents->x - dst_x, extents->y - dst_y, + extents->width, extents->height); + +CLEANUP: + _cairo_scaled_font_thaw_cache (font); + pixman_image_unref (mask); + pixman_image_unref (src); + pixman_image_unref (white); + + return status; +} + +static cairo_status_t +_composite_glyphs (void *closure, + pixman_image_t *dst, + pixman_format_code_t dst_format, + cairo_operator_t op, + const cairo_pattern_t *pattern, + int dst_x, + int dst_y, + const cairo_rectangle_int_t *extents, + cairo_region_t *clip_region) +{ + composite_glyphs_info_t *info = closure; + cairo_scaled_glyph_t *glyph_cache[64]; + pixman_op_t pixman_op = _pixman_operator (op); + pixman_image_t *src = NULL; + int src_x, src_y; + cairo_status_t status; + int i; + + if (pattern != NULL) { + src = _pixman_image_for_pattern (pattern, extents, &src_x, &src_y); + if (unlikely (src == NULL)) + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + + src_x -= dst_x; + src_y -= dst_y; + } else { + src = _pixman_white_image (); + src_x = src_y = 0; + } + + memset (glyph_cache, 0, sizeof (glyph_cache)); + status = CAIRO_STATUS_SUCCESS; + + _cairo_scaled_font_freeze_cache (info->font); + for (i = 0; i < info->num_glyphs; i++) { + int x, y; + cairo_image_surface_t *glyph_surface; + cairo_scaled_glyph_t *scaled_glyph; + unsigned long glyph_index = info->glyphs[i].index; + int cache_index = glyph_index % ARRAY_LENGTH (glyph_cache); + + scaled_glyph = glyph_cache[cache_index]; + if (scaled_glyph == NULL || + _cairo_scaled_glyph_index (scaled_glyph) != glyph_index) + { + status = _cairo_scaled_glyph_lookup (info->font, glyph_index, + CAIRO_SCALED_GLYPH_INFO_SURFACE, + &scaled_glyph); + + if (unlikely (status)) + break; + + glyph_cache[cache_index] = scaled_glyph; + } + + glyph_surface = scaled_glyph->surface; + if (glyph_surface->width && glyph_surface->height) { + /* round glyph locations to the nearest pixel */ + /* XXX: FRAGILE: We're ignoring device_transform scaling here. A bug? */ + x = _cairo_lround (info->glyphs[i].x - + glyph_surface->base.device_transform.x0); + y = _cairo_lround (info->glyphs[i].y - + glyph_surface->base.device_transform.y0); + + pixman_image_composite (pixman_op, + src, glyph_surface->pixman_image, dst, + x + src_x, y + src_y, + 0, 0, + x - dst_x, y - dst_y, + glyph_surface->width, + glyph_surface->height); + } + } + _cairo_scaled_font_thaw_cache (info->font); + + pixman_image_unref (src); + + return status; +} + +static cairo_int_status_t +_cairo_image_surface_glyphs (void *abstract_surface, + cairo_operator_t op, + const cairo_pattern_t *source, + cairo_glyph_t *glyphs, + int num_glyphs, + cairo_scaled_font_t *scaled_font, + cairo_clip_t *clip, + int *num_remaining) +{ + cairo_image_surface_t *surface = abstract_surface; + cairo_composite_rectangles_t extents; + composite_glyphs_info_t glyph_info; + cairo_clip_t local_clip; + cairo_bool_t have_clip = FALSE; + cairo_bool_t overlap; + cairo_status_t status; + + status = _cairo_composite_rectangles_init_for_glyphs (&extents, + surface->width, + surface->height, + op, source, + scaled_font, + glyphs, num_glyphs, + clip, + &overlap); + if (unlikely (status)) + return status; + + if (_cairo_clip_contains_rectangle (clip, &extents)) + clip = NULL; + + if (clip != NULL && extents.is_bounded) { + clip = _cairo_clip_init_copy (&local_clip, clip); + status = _cairo_clip_rectangle (clip, &extents.bounded); + if (unlikely (status)) + return status; + + have_clip = TRUE; + } + + glyph_info.font = scaled_font; + glyph_info.glyphs = glyphs; + glyph_info.num_glyphs = num_glyphs; + + status = _clip_and_composite (surface, op, source, + overlap || extents.is_bounded == 0 ? _composite_glyphs_via_mask : _composite_glyphs, + &glyph_info, + &extents, clip); + + if (have_clip) + _cairo_clip_fini (&local_clip); + + *num_remaining = 0; + return status; +} + +static cairo_bool_t +_cairo_image_surface_get_extents (void *abstract_surface, + cairo_rectangle_int_t *rectangle) +{ + cairo_image_surface_t *surface = abstract_surface; + + rectangle->x = 0; + rectangle->y = 0; + rectangle->width = surface->width; + rectangle->height = surface->height; + + return TRUE; +} + +static void +_cairo_image_surface_get_font_options (void *abstract_surface, + cairo_font_options_t *options) +{ + _cairo_font_options_init_default (options); + + cairo_font_options_set_hint_metrics (options, CAIRO_HINT_METRICS_ON); +} + +/* legacy interface kept for compatibility until surface-fallback is removed */ +static cairo_status_t +_cairo_image_surface_acquire_dest_image (void *abstract_surface, + cairo_rectangle_int_t *interest_rect, + cairo_image_surface_t **image_out, + cairo_rectangle_int_t *image_rect_out, + void **image_extra) +{ + cairo_image_surface_t *surface = abstract_surface; + + image_rect_out->x = 0; + image_rect_out->y = 0; + image_rect_out->width = surface->width; + image_rect_out->height = surface->height; + + *image_out = surface; + *image_extra = NULL; + + return CAIRO_STATUS_SUCCESS; +} + +static void +_cairo_image_surface_release_dest_image (void *abstract_surface, + cairo_rectangle_int_t *interest_rect, + cairo_image_surface_t *image, + cairo_rectangle_int_t *image_rect, + void *image_extra) +{ +} + +static cairo_status_t +_cairo_image_surface_clone_similar (void *abstract_surface, + cairo_surface_t *src, + int src_x, + int src_y, + int width, + int height, + int *clone_offset_x, + int *clone_offset_y, + cairo_surface_t **clone_out) +{ + cairo_image_surface_t *surface = abstract_surface; + + if (src->backend == surface->base.backend) { + *clone_offset_x = *clone_offset_y = 0; + *clone_out = cairo_surface_reference (src); + + return CAIRO_STATUS_SUCCESS; + } + + return CAIRO_INT_STATUS_UNSUPPORTED; +} + +static cairo_int_status_t +_cairo_image_surface_composite (cairo_operator_t op, + const cairo_pattern_t *src_pattern, + const cairo_pattern_t *mask_pattern, + void *abstract_dst, + int src_x, + int src_y, + int mask_x, + int mask_y, + int dst_x, + int dst_y, + unsigned int width, + unsigned int height, + cairo_region_t *clip_region) +{ + cairo_image_surface_t *dst = abstract_dst; + cairo_composite_rectangles_t extents; + pixman_image_t *src; + int src_offset_x, src_offset_y; + cairo_status_t status; + + if (clip_region != NULL) { + status = _cairo_image_surface_set_clip_region (dst, clip_region); + if (unlikely (status)) + return status; + } + + extents.source.x = src_x; + extents.source.y = src_y; + extents.source.width = width; + extents.source.height = height; + + extents.mask.x = dst_x; + extents.mask.y = dst_y; + extents.mask.width = width; + extents.mask.height = height; + + extents.bounded.x = dst_x; + extents.bounded.y = dst_y; + extents.bounded.width = width; + extents.bounded.height = height; + + if (clip_region != NULL) { + cairo_region_get_extents (clip_region, &extents.unbounded); + } else { + extents.unbounded.x = 0; + extents.unbounded.y = 0; + extents.unbounded.width = dst->width; + extents.unbounded.height = dst->height; + } + + extents.is_bounded = _cairo_operator_bounded_by_either (op); + + src = _pixman_image_for_pattern (src_pattern, &extents.bounded, &src_offset_x, &src_offset_y); + if (unlikely (src == NULL)) + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + + if (mask_pattern != NULL) { + pixman_image_t *mask; + int mask_offset_x, mask_offset_y; + + mask = _pixman_image_for_pattern (mask_pattern, &extents.bounded, &mask_offset_x, &mask_offset_y); + if (unlikely (mask == NULL)) { + pixman_image_unref (src); + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + } + + pixman_image_composite (_pixman_operator (op), + src, mask, dst->pixman_image, + src_x + src_offset_x, + src_y + src_offset_y, + mask_x + mask_offset_x, + mask_y + mask_offset_y, + dst_x, dst_y, width, height); + + pixman_image_unref (mask); + } else { + pixman_image_composite (_pixman_operator (op), + src, NULL, dst->pixman_image, + src_x + src_offset_x, + src_y + src_offset_y, + 0, 0, + dst_x, dst_y, width, height); + } + + pixman_image_unref (src); + + if (! extents.is_bounded) + _cairo_image_surface_fixup_unbounded (dst, &extents, NULL); + + if (clip_region != NULL) + _cairo_image_surface_unset_clip_region (dst); + + return CAIRO_STATUS_SUCCESS; +} + +static cairo_int_status_t +_cairo_image_surface_fill_rectangles (void *abstract_surface, + cairo_operator_t op, + const cairo_color_t *color, + cairo_rectangle_int_t *rects, + int num_rects) +{ + cairo_image_surface_t *surface = abstract_surface; + + pixman_color_t pixman_color; + pixman_rectangle16_t stack_rects[CAIRO_STACK_ARRAY_LENGTH (pixman_rectangle16_t)]; + pixman_rectangle16_t *pixman_rects = stack_rects; + int i; + + cairo_int_status_t status; + + if (CAIRO_INJECT_FAULT ()) + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + + pixman_color.red = color->red_short; + pixman_color.green = color->green_short; + pixman_color.blue = color->blue_short; + pixman_color.alpha = color->alpha_short; + + if (num_rects > ARRAY_LENGTH (stack_rects)) { + pixman_rects = _cairo_malloc_ab (num_rects, sizeof (pixman_rectangle16_t)); + if (unlikely (pixman_rects == NULL)) + return _cairo_error (CAIRO_STATUS_NO_MEMORY); } for (i = 0; i < num_rects; i++) { @@ -1228,7 +4039,6 @@ _cairo_image_surface_fill_rectangles (void *abstract_surface, pixman_rects[i].height = rects[i].height; } - /* XXX: pixman_fill_region() should be implemented */ status = CAIRO_STATUS_SUCCESS; if (! pixman_image_fill_rectangles (_pixman_operator (op), surface->pixman_image, @@ -1245,42 +4055,6 @@ _cairo_image_surface_fill_rectangles (void *abstract_surface, return status; } -static pixman_format_code_t -_pixman_mask_format_from_antialias (cairo_antialias_t antialias) -{ - if (antialias == CAIRO_ANTIALIAS_NONE) - return PIXMAN_a1; - return PIXMAN_a8; -} - -static void -_pixman_add_trapezoids (pixman_image_t *image, - int dst_x, int dst_y, - const cairo_trapezoid_t *traps, - int num_traps) -{ - while (num_traps--) { - pixman_trapezoid_t trap; - - trap.top = _cairo_fixed_to_16_16 (traps->top); - trap.bottom = _cairo_fixed_to_16_16 (traps->bottom); - - trap.left.p1.x = _cairo_fixed_to_16_16 (traps->left.p1.x); - trap.left.p1.y = _cairo_fixed_to_16_16 (traps->left.p1.y); - trap.left.p2.x = _cairo_fixed_to_16_16 (traps->left.p2.x); - trap.left.p2.y = _cairo_fixed_to_16_16 (traps->left.p2.y); - - trap.right.p1.x = _cairo_fixed_to_16_16 (traps->right.p1.x); - trap.right.p1.y = _cairo_fixed_to_16_16 (traps->right.p1.y); - trap.right.p2.x = _cairo_fixed_to_16_16 (traps->right.p2.x); - trap.right.p2.y = _cairo_fixed_to_16_16 (traps->right.p2.y); - - pixman_rasterize_trapezoid (image, &trap, -dst_x, -dst_y); - - traps++; - } -} - static cairo_int_status_t _cairo_image_surface_composite_trapezoids (cairo_operator_t op, const cairo_pattern_t *pattern, @@ -1296,11 +4070,10 @@ _cairo_image_surface_composite_trapezoids (cairo_operator_t op, int num_traps, cairo_region_t *clip_region) { - cairo_surface_attributes_t attributes; cairo_image_surface_t *dst = abstract_dst; - cairo_image_surface_t *src; - cairo_int_status_t status; - pixman_image_t *mask; + cairo_composite_rectangles_t extents; + composite_traps_info_t info; + cairo_status_t status; if (height == 0 || width == 0) return CAIRO_STATUS_SUCCESS; @@ -1308,101 +4081,66 @@ _cairo_image_surface_composite_trapezoids (cairo_operator_t op, if (CAIRO_INJECT_FAULT ()) return _cairo_error (CAIRO_STATUS_NO_MEMORY); - /* Special case adding trapezoids onto a mask surface; we want to avoid - * creating an intermediate temporary mask unnecessarily. - * - * We make the assumption here that the portion of the trapezoids - * contained within the surface is bounded by [dst_x,dst_y,width,height]; - * the Cairo core code passes bounds based on the trapezoid extents. - * - * Currently the check clip_region == NULL is needed for correct - * functioning, since pixman_add_trapezoids() doesn't obey the - * surface clip, which is a libpixman bug , but there's no harm in - * falling through to the general case when the surface is clipped - * since libpixman would have to generate an intermediate mask anyways. - */ - if (op == CAIRO_OPERATOR_ADD && - clip_region == NULL && - _cairo_pattern_is_opaque_solid (pattern) && - dst->base.content == CAIRO_CONTENT_ALPHA && - antialias != CAIRO_ANTIALIAS_NONE) - { - _pixman_add_trapezoids (dst->pixman_image, 0, 0, traps, num_traps); - return CAIRO_STATUS_SUCCESS; - } + extents.source.x = src_x; + extents.source.y = src_y; + extents.source.width = width; + extents.source.height = height; - status = _cairo_image_surface_set_clip_region (dst, clip_region); - if (unlikely (status)) - return status; + extents.mask.x = dst_x; + extents.mask.y = dst_y; + extents.mask.width = width; + extents.mask.height = height; - status = _cairo_pattern_acquire_surface (pattern, &dst->base, - src_x, src_y, width, height, - CAIRO_PATTERN_ACQUIRE_NONE, - (cairo_surface_t **) &src, - &attributes); - if (unlikely (status)) - return status; + extents.bounded.x = dst_x; + extents.bounded.y = dst_y; + extents.bounded.width = width; + extents.bounded.height = height; - status = _cairo_image_surface_set_attributes (src, &attributes, - dst_x + width / 2., - dst_y + height / 2.); - if (unlikely (status)) - goto CLEANUP_SOURCE; + cairo_region_get_extents (clip_region, &extents.unbounded); + extents.is_bounded = _cairo_operator_bounded_by_either (op); - mask = pixman_image_create_bits (_pixman_mask_format_from_antialias (antialias), - width, height, NULL, 0); - if (unlikely (mask == NULL)) { - status = _cairo_error (CAIRO_STATUS_NO_MEMORY); - goto CLEANUP_SOURCE; + if (clip_region != NULL) { + status = _cairo_image_surface_set_clip_region (dst, clip_region); + if (unlikely (status)) + return status; } - _pixman_add_trapezoids (mask, dst_x, dst_y, traps, num_traps); - - pixman_image_composite (_pixman_operator (op), - src->pixman_image, - mask, - dst->pixman_image, - src_x + attributes.x_offset, - src_y + attributes.y_offset, - 0, 0, - dst_x, dst_y, - width, height); - - pixman_image_unref (mask); + info.traps = traps; + info.num_traps = num_traps; + info.antialias = antialias; + status = _composite_traps (&info, + dst->pixman_image, + dst->pixman_format, + op, + pattern, + 0, 0, + &extents.bounded, + clip_region); - if (! _cairo_operator_bounded_by_mask (op)) { - status = _cairo_surface_composite_shape_fixup_unbounded (&dst->base, - &attributes, - src->width, src->height, - width, height, - src_x, src_y, - 0, 0, - dst_x, dst_y, width, height, - clip_region); - } + if (status == CAIRO_STATUS_SUCCESS && ! extents.is_bounded) + _cairo_image_surface_fixup_unbounded (dst, &extents, NULL); - CLEANUP_SOURCE: - _cairo_pattern_release_surface (pattern, &src->base, &attributes); + if (clip_region != NULL) + _cairo_image_surface_unset_clip_region (dst); return status; } -typedef struct _cairo_image_surface_span_renderer { +typedef struct _legacy_image_surface_span_renderer { cairo_span_renderer_t base; cairo_operator_t op; const cairo_pattern_t *pattern; cairo_antialias_t antialias; + cairo_region_t *clip_region; + pixman_image_t *mask; uint8_t *mask_data; uint32_t mask_stride; - cairo_image_surface_t *src; - cairo_surface_attributes_t src_attributes; - cairo_image_surface_t *mask; cairo_image_surface_t *dst; cairo_composite_rectangles_t composite_rectangles; -} cairo_image_surface_span_renderer_t; +} legacy_image_surface_span_renderer_t; void _cairo_image_surface_span_render_row ( @@ -1446,7 +4184,7 @@ _cairo_image_surface_span_renderer_render_rows ( const cairo_half_open_span_t *spans, unsigned num_spans) { - cairo_image_surface_span_renderer_t *renderer = abstract_renderer; + legacy_image_surface_span_renderer_t *renderer = abstract_renderer; while (height--) _cairo_image_surface_span_render_row (y++, spans, num_spans, renderer->mask_data, renderer->mask_stride); return CAIRO_STATUS_SUCCESS; @@ -1455,17 +4193,11 @@ _cairo_image_surface_span_renderer_render_rows ( static void _cairo_image_surface_span_renderer_destroy (void *abstract_renderer) { - cairo_image_surface_span_renderer_t *renderer = abstract_renderer; - if (!renderer) return; - - if (renderer->src != NULL) { - _cairo_pattern_release_surface (renderer->pattern, - &renderer->src->base, - &renderer->src_attributes); - } + legacy_image_surface_span_renderer_t *renderer = abstract_renderer; + if (renderer == NULL) + return; - if (renderer->mask != NULL) - cairo_surface_destroy (&renderer->mask->base); + pixman_image_unref (renderer->mask); free (renderer); } @@ -1473,47 +4205,41 @@ _cairo_image_surface_span_renderer_destroy (void *abstract_renderer) static cairo_status_t _cairo_image_surface_span_renderer_finish (void *abstract_renderer) { - cairo_image_surface_span_renderer_t *renderer = abstract_renderer; - cairo_status_t status = CAIRO_STATUS_SUCCESS; + legacy_image_surface_span_renderer_t *renderer = abstract_renderer; + cairo_composite_rectangles_t *rects = &renderer->composite_rectangles; + cairo_image_surface_t *dst = renderer->dst; + pixman_image_t *src; + int src_x, src_y; - if (renderer->src == NULL || renderer->mask == NULL) - return CAIRO_STATUS_SUCCESS; + if (renderer->clip_region != NULL) { + cairo_status_t status; - status = cairo_surface_status (&renderer->mask->base); - if (status == CAIRO_STATUS_SUCCESS) { - cairo_composite_rectangles_t *rects = &renderer->composite_rectangles; - cairo_image_surface_t *src = renderer->src; - cairo_image_surface_t *dst = renderer->dst; - cairo_surface_attributes_t *src_attributes = &renderer->src_attributes; - int width = rects->bounded.width; - int height = rects->bounded.height; + status = _cairo_image_surface_set_clip_region (dst, renderer->clip_region); + if (unlikely (status)) + return status; + } + + src = _pixman_image_for_pattern (renderer->pattern, &rects->bounded, &src_x, &src_y); + if (src == NULL) + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + + + pixman_image_composite (_pixman_operator (renderer->op), + src, + renderer->mask, + dst->pixman_image, + rects->bounded.x + src_x, + rects->bounded.y + src_y, + 0, 0, + rects->bounded.x, rects->bounded.y, + rects->bounded.width, rects->bounded.height); + + if (! rects->is_bounded) + _cairo_image_surface_fixup_unbounded (dst, rects, NULL); + + if (renderer->clip_region != NULL) + _cairo_image_surface_unset_clip_region (dst); - pixman_image_composite (_pixman_operator (renderer->op), - src->pixman_image, - renderer->mask->pixman_image, - dst->pixman_image, - rects->bounded.x + src_attributes->x_offset, - rects->bounded.y + src_attributes->y_offset, - 0, 0, /* mask.x, mask.y */ - rects->bounded.x, rects->bounded.y, - width, height); - - if (! rects->is_bounded) { - status = _cairo_surface_composite_shape_fixup_unbounded ( - &dst->base, - src_attributes, - src->width, src->height, - width, height, - rects->bounded.x, rects->bounded.y, - 0, 0, /* mask.x, mask.y */ - rects->bounded.x, rects->bounded.y, - width, height, - dst->clip_region); - } - } - if (status != CAIRO_STATUS_SUCCESS) - return _cairo_span_renderer_set_error (abstract_renderer, - status); return CAIRO_STATUS_SUCCESS; } @@ -1539,14 +4265,10 @@ _cairo_image_surface_create_span_renderer (cairo_operator_t op, cairo_region_t *clip_region) { cairo_image_surface_t *dst = abstract_dst; - cairo_image_surface_span_renderer_t *renderer = calloc(1, sizeof(*renderer)); - cairo_status_t status; - - status = _cairo_image_surface_set_clip_region (dst, clip_region); - if (unlikely (status)) - return _cairo_span_renderer_create_in_error (status); + legacy_image_surface_span_renderer_t *renderer; - if (renderer == NULL) + renderer = calloc(1, sizeof(*renderer)); + if (unlikely (renderer == NULL)) return _cairo_span_renderer_create_in_error (CAIRO_STATUS_NO_MEMORY); renderer->base.destroy = _cairo_image_surface_span_renderer_destroy; @@ -1556,67 +4278,25 @@ _cairo_image_surface_create_span_renderer (cairo_operator_t op, renderer->pattern = pattern; renderer->antialias = antialias; renderer->dst = dst; + renderer->clip_region = clip_region; renderer->composite_rectangles = *rects; - status = _cairo_pattern_acquire_surface ( - renderer->pattern, &renderer->dst->base, - rects->bounded.x, rects->bounded.y, - rects->bounded.width, rects->bounded.height, - CAIRO_PATTERN_ACQUIRE_NONE, - (cairo_surface_t **) &renderer->src, - &renderer->src_attributes); - if (status) - goto unwind; - - status = _cairo_image_surface_set_attributes ( - renderer->src, &renderer->src_attributes, - rects->bounded.x + rects->bounded.width/2, - rects->bounded.y + rects->bounded.height/2); - if (status) - goto unwind; - /* TODO: support rendering to A1 surfaces (or: go add span * compositing to pixman.) */ - renderer->mask = (cairo_image_surface_t *) - cairo_image_surface_create (CAIRO_FORMAT_A8, - rects->bounded.width, - rects->bounded.height); - - status = cairo_surface_status (&renderer->mask->base); - - unwind: - if (status != CAIRO_STATUS_SUCCESS) { - _cairo_image_surface_span_renderer_destroy (renderer); - return _cairo_span_renderer_create_in_error (status); + renderer->mask = pixman_image_create_bits (PIXMAN_a8, + rects->bounded.width, + rects->bounded.height, + NULL, 0); + if (renderer->mask == NULL) { + free (renderer); + return _cairo_span_renderer_create_in_error (CAIRO_STATUS_NO_MEMORY); } - renderer->mask_data = renderer->mask->data - rects->bounded.x - rects->bounded.y * renderer->mask->stride; - renderer->mask_stride = renderer->mask->stride; - return &renderer->base; -} - -static cairo_bool_t -_cairo_image_surface_get_extents (void *abstract_surface, - cairo_rectangle_int_t *rectangle) -{ - cairo_image_surface_t *surface = abstract_surface; - - rectangle->x = 0; - rectangle->y = 0; - rectangle->width = surface->width; - rectangle->height = surface->height; - - return TRUE; -} + renderer->mask_stride = pixman_image_get_stride (renderer->mask); + renderer->mask_data = (uint8_t *) pixman_image_get_data (renderer->mask) - rects->bounded.x - rects->bounded.y * renderer->mask_stride; -static void -_cairo_image_surface_get_font_options (void *abstract_surface, - cairo_font_options_t *options) -{ - _cairo_font_options_init_default (options); - - cairo_font_options_set_hint_metrics (options, CAIRO_HINT_METRICS_ON); + return &renderer->base; } /** @@ -1647,6 +4327,7 @@ const cairo_surface_backend_t _cairo_image_surface_backend = { _cairo_image_surface_composite_trapezoids, _cairo_image_surface_create_span_renderer, _cairo_image_surface_check_span_renderer, + NULL, /* copy_page */ NULL, /* show_page */ _cairo_image_surface_get_extents, @@ -1657,11 +4338,12 @@ const cairo_surface_backend_t _cairo_image_surface_backend = { NULL, /* font_fini */ NULL, /* glyph_fini */ - NULL, /* paint */ - NULL, /* mask */ - NULL, /* stroke */ - NULL, /* fill */ - NULL, /* show_glyphs */ + _cairo_image_surface_paint, + _cairo_image_surface_mask, + _cairo_image_surface_stroke, + _cairo_image_surface_fill, + _cairo_image_surface_glyphs, + NULL, /* show_text_glyphs */ NULL, /* snapshot */ NULL, /* is_similar */ }; @@ -1673,7 +4355,6 @@ _cairo_image_surface_coerce (cairo_image_surface_t *surface, cairo_format_t format) { cairo_image_surface_t *clone; - cairo_surface_pattern_t pattern; cairo_status_t status; status = surface->base.status; @@ -1688,17 +4369,13 @@ _cairo_image_surface_coerce (cairo_image_surface_t *surface, if (unlikely (clone->base.status)) return clone; - _cairo_pattern_init_for_surface (&pattern, &surface->base); - status = _cairo_surface_paint (&clone->base, - CAIRO_OPERATOR_SOURCE, - &pattern.base, - NULL); - _cairo_pattern_fini (&pattern.base); - - if (unlikely (status)) { - cairo_surface_destroy (&clone->base); - return (cairo_image_surface_t *)_cairo_surface_create_in_error (status); - } + pixman_image_composite (PIXMAN_OP_SRC, + surface->pixman_image, NULL, clone->pixman_image, + 0, 0, + 0, 0, + 0, 0, + surface->width, surface->height); + clone->base.is_clear = FALSE; clone->base.device_transform = surface->base.device_transform; diff --git a/src/cairo-mutex-list-private.h b/src/cairo-mutex-list-private.h index 2f483163a..c84cebf62 100644 --- a/src/cairo-mutex-list-private.h +++ b/src/cairo-mutex-list-private.h @@ -38,6 +38,8 @@ CAIRO_MUTEX_DECLARE (_cairo_pattern_solid_surface_cache_lock) +CAIRO_MUTEX_DECLARE (_cairo_image_solid_cache_mutex) + CAIRO_MUTEX_DECLARE (_cairo_toy_font_face_mutex) CAIRO_MUTEX_DECLARE (_cairo_intern_string_mutex) CAIRO_MUTEX_DECLARE (_cairo_scaled_font_map_mutex) diff --git a/src/cairo-xcb-surface.c b/src/cairo-xcb-surface.c index faa566549..666abc994 100644 --- a/src/cairo-xcb-surface.c +++ b/src/cairo-xcb-surface.c @@ -600,14 +600,8 @@ _get_image_surface (cairo_xcb_surface_t *surface, * which takes data in an arbitrary format and converts it * to something supported by that library. */ - image = (cairo_image_surface_t *) - _cairo_image_surface_create_with_masks (data, - &masks, - extents.width, - extents.height, - bytes_per_line); - if (image->base.status) - goto FAIL; + ASSERT_NOT_REACHED; + goto FAIL; } /* Let the surface take ownership of the data */ diff --git a/src/cairoint.h b/src/cairoint.h index c2d36e0c8..6e059c4ca 100644 --- a/src/cairoint.h +++ b/src/cairoint.h @@ -833,7 +833,6 @@ struct _cairo_image_surface { int depth; pixman_image_t *pixman_image; - cairo_region_t *clip_region; unsigned owns_data : 1; unsigned transparency : 2; @@ -2162,9 +2161,15 @@ _cairo_format_bits_per_pixel (cairo_format_t format) cairo_const; cairo_private cairo_format_t _cairo_format_from_content (cairo_content_t content) cairo_const; +cairo_private cairo_format_t +_cairo_format_from_pixman_format (pixman_format_code_t pixman_format); + cairo_private cairo_content_t _cairo_content_from_format (cairo_format_t format) cairo_const; +cairo_private cairo_content_t +_cairo_content_from_pixman_format (pixman_format_code_t pixman_format); + cairo_private cairo_surface_t * _cairo_image_surface_create_for_pixman_image (pixman_image_t *pixman_image, pixman_format_code_t pixman_format); @@ -2184,25 +2189,11 @@ _cairo_image_surface_create_with_pixman_format (unsigned char *data, int height, int stride); -cairo_private cairo_surface_t * -_cairo_image_surface_create_with_masks (unsigned char *data, - cairo_format_masks_t *format, - int width, - int height, - int stride); - cairo_private cairo_surface_t * _cairo_image_surface_create_with_content (cairo_content_t content, int width, int height); -cairo_private cairo_surface_t * -_cairo_image_surface_create_for_data_with_content (unsigned char *data, - cairo_content_t content, - int width, - int height, - int stride); - cairo_private void _cairo_image_surface_assume_ownership_of_data (cairo_image_surface_t *surface); diff --git a/test/clip-fill-unbounded.argb32.ref.png b/test/clip-fill-unbounded.argb32.ref.png index 9adf992cd8518342defaad2e4b9750ecf211b90e..b87efd4fbeefbf46ee8ed7ec3b1ba6c2c8dcf9ed 100644 GIT binary patch delta 1526 zcmV{L zA`nR~mmA7-gB2Jw+5FKQx>zhxLS(|0>n~|jf$hGBKe8FE@sFajls{Tqv(}o{xfXO8 z#PRe8cgK%Id7kfc$DQqY{lo3P&*%BP?|sMjp67GVM@T6p7Jt`x5-s3f#~NseHP8@i zpdr>kL#%;@SOX2Q1{z`wG-QtIKp}*PW#X9cfNE=dkg_Lc^9hklZEfyM!;}rYE2nsv zQC5SLqrghe@Q`v`CmFyiK#*a+ z*2<|v0)O=B8Mu}kz&T(ZBYdD7HRSArl`h2tEY_k{Z@>MLS=T>vOjkE~Ei3 zK?ENI70hKT4qqbp0RYKUz)B>kjBVPtQ%IAmTu@1_&wV+okVYKNBdJHQ8pvmIi8KhD zPFOi>mh_p`0}J>TSd$b5)#;c8eX*%X=kYtx8I!rB#Br7Yoap;iV5f7<|s~8_GI7l31sPRR64lTO6 zx`44hk+Hm^GafFQpihF3UawatrM`haazG8YFmRdwcsGV~!m=Cc!gRpFDp2xN2HG1G`y(LvY$_3bzLwM#6m< zs(F>!u;Hz+J{6vtNbVQynFE5Mg6S;WtvT9mgGal6(A5FNIBi0NI+=%KE$ zTrV%L6oZ4e#Ngm9s;jp}o?p6jY3R{-pNQZ8t7=-^MjP|ph$l|lN56kG>bZ$)S0To4 zHMf6n%W21i@pzP%Snm2n%!p5)NLJRus6LTv*RDmbA31VZrln=bw6qLbTbm=#U%h%Y z^k}?KBqL*?YFf8_B5sLapU8?8FGTf;w6(QGt``> znXp-x`D$NUZVD3G?A*Ca)YpIHs+h2A*KX?SKGaEP%UOviXGLKPstN<|)slW^XBTJB zo~Ey_kN*B20m#nIA}8lX^77VDR8%x8C4I~E*k9!gu?8Aq4K&0WXoxk?5Nn_z)gYXATM delta 1536 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 diff --git a/test/clip-stroke-unbounded.argb32.ref.png b/test/clip-stroke-unbounded.argb32.ref.png index 39b9a301b0578590b735ea1d4c4ff56c1099df7e..e48537f64f0ea43c23a4739587770898b1617879 100644 GIT binary patch delta 1599 zcmV-F2Eh5J4W12qsYlGGo(m9~i% z0Y#~)t*K&f;6e=0VhW9xVvSl6{HalrrZ=sas6g1W1i5IX7u=w+CTP;6Nn_H~YKyUf zAVFoP7khR=mYX`tX%rs2dz-e(7nG@L2`ux{*1}|DoEKa>gv9X zl|H$DKS0h*Y0V65X9YeE0qYt6c?$pT(20*4^0;A?$14fY=lvPzP>GLTU_CGlbRuX( z(1_%+dn}hAy#y%toM(>f^Y(uU^z$ijl+e9w;0XuV%)d{K36KixLsE{U5eFXyGmoQ`w63fA0J=LN;xY*`piS>ft&aRD4>;>c%F4g zUU3Efg^w;a@Q=~_rX)aLY%0=Q_#5~kCUeDuIO@Td z7dDe411x{B1{z`wG{hQc$g|TYG8rQyBV4?Ak=E8WZr!@Uy?dc|1{W<#B`a$Mg@v2h zy7fKg&!6u#h4TMIrZvz@A4a?lymaXjRaI5u_U+r#>61&AyvDI(M=2^Q@{-2P!0~+; zlQU%>M%)Y>9UY~jqEehXby7#4EH5vozP_HMq$Gcxbe;@UjtK~W#|ZY4&++l+;%4CP z-Mhu<)2DH$1db4TObx??S33l2!F{#E)hEl!_Hgo~-YXtb5l=Az8gK~WAl(bno;KW-#|avS}}e;a78{D--n?iQi+j~5xI2f>-fob za|M6#^N|E0i;Ih8Pfw2=8XA&ALqoEsr$_GCu~UMWs842OER&BOJyO0#<$+-wE_;YM z;ojwrxEa{od_JrX2T6bi2|n4}+&pE>xpU_v_$KO;7cN{-O{-^MJLxzCCq1X|c)(#a z+=ro>SD9_w-Vf`;VTb^!a@Ve1Gsf-Mv6FvR(r|bf)+e`b|4=op*FF)i#IH}JxA%Hf zpGZkbN#uM>OA9F}3rR^?NJ~pgiiT&YnFLIo{XT=js~k6G=-; zS551+PsA(n>l0bCW@S{LNK;c&XBZ?KvC*x-rUxmoek?d055-AfA8A7=RAmj4V{$J`CkKI>?Hff#HSp1bqq%>Eg&j zx`V9vFQh|vd2FlXr_swCsxewjww0TyPsDI5=e-&;ZnG}))gFsp3KH5>SJ#NchYx8L z6Wl%#SyS^l2M>OwlP(tJtZ*O3#4^w*&@ojXM%)aHE@vs}OK3SO3J-l4aWiKy%=m)p zH2P+@plU$e4TMlhzqPfEuC7k5UAxAuTfYO4nVG?=RoQIVkV|1kL#%;@SOX2Q1{z`wG{hQc$baaQ_gOY;A#4Bu002ovPDHLkV1iA11^@s6 delta 1586 zcmV-22F>}N4W|u|Hh)}6L_t(|ob8)kXjDfShM(EkiiL!RNNpoUn(9S~Lj5J)nErq^ znrJkY+Dc>WO>$u*NvkF)wxAWYYT{2th0>eW2x=0tXG8R&)d~Sov4RSPLJ>->wh>Dt zhN#);#hzW)-I&ar*|SMyo|}Yo=9}+5@7bLFW^z6urIc7a69pJ;0snWC9s?PF#;Fbz zLWoo*PWcMx{Q2{oJNI+S<|`;DV8MdIk%lQ7I4lm65xk8g7XZOcBxfk$&y>us!kIH? zMPp+_s`SZCo9g{Hu!ETd*bXcr{__<5-Jy#BjV$4cQ68_PL7(?$phF!2E&z*wexM6M zGlFI$r`%&jgt-U0;hBgNcR zj$6SU;3ptJm=Q|@&y0dT-9O>U?$6&e<3PGGl2^53)k(WPyOc9T*T87yK21B5aAO32 zXveCPc71vVhB-^*EB{@$j#8tI9N--wOn}s_R}&cpeR>8i<0g{Rzze`;<+$hg0+;}F zv4mS%IdvEXefrP93>tucK_oSV_+-TKK`MZ1V2}VqFZNSOn)T@$xP&W6>H&e|TS7F^ z&WlOoLSz%98Nqi%N{fvw;i`7NF|NuDk<>DdDjd!t_!WRKZy+f|FadZ7 zY@mdFo^p+GecrdTN@${kGX&TI6ahs@ih#)dgh3!kfG-2U%x43^A#2< zt%=2$H)f1u*TRz^116I#0}y|CcKSqyVqjo^6DLm4*4EDT>sPpaJMzxpq)FM#m@$*m z(&emN`99;vkN28F`F|qA8tA1D!*2smo;=Bh4I9Lb8#jj2C#Ot#jXisIQC3#wC5@SZ z@qHLW^VB{J-wYfa9Hh3kP8>L}Uq_#;si|T2?%iZ$Way;xWT0|PNC1C4M6i)!_Qs#{ z&A@f*)`^1$58_Y<>?HD->W73^I|PN`zS`mHlhxJh*}q@!6%VP1ryv$beX_f|Tb7rvksuQF$-KO2^1*`#%GanoFp9$| z4>9}PyWHuUfh{e^qWW-2_;J;=dIoloi$i$Ga|(|K z90sF(7^-=dS+(kes6HI}2$3ztn~KPoM7UdfX?{({ocb?X$C#>*)z69gDABg?Rj^>F<|A zj)@WoD3@4X`$T`d<5!V%VM?1qL&Q`Z5kRH#f}}@ zwKfynJ`vg2_&M9QeWjBw728?SK8(a|ph2MXX?+;J8JN7CrKB&d+gV9?=)>^Mz$oz> zs>A3T-3>EU1AI3SB3t@xZS9;s-NmI#m$-iYcL4J9^O!w*E{hizQCeD>mMwkD&^$X! kxlzIzXo!<=1{D_MKNlYO^fwjPU;qFB07*qoM6N<$g17Yr8UO$Q diff --git a/test/clip-stroke-unbounded.rgb24.ref.png b/test/clip-stroke-unbounded.rgb24.ref.png index a30352ebf7e8ddfa328e1c3f32ed9c6c54d25bfe..d2880f10c409ccecb1401ec7ac1f24cc8b9e1384 100644 GIT binary patch delta 1335 zcmV-71<3m63fu~iHh&RGL_t(|ob8%jOcYlbhMzOAi%FrRz)g{ut>fO93PBPtG-)8l zizY_?n$$L=th%jHp;VM;T9d}6NpDP={v}+rfzk@3l?b!kR~wBsZHb6?M#4p-CRogZ zBtk6Pi`kW3VCS6KT{_J$Z}#e(Z{GL3^Rx4Pb3PG^#n?#DoPS=xe+|#D+u<2@J3PZ~ zhiBOB$hV#rLS#&xO%8V@6uL4smD#bifk16|_@B@RllI6Q(Y0f^0YZ=mi!cFy0Z;)upa!a-7e;LGIijm< zxDgIR42EG4=6{o>3gI9efG`LcR;6@ASM6Uq2qB2U=kQ&sD)0opg$odaL!hfUaY$Fq za0_T)z%S`zeuYucphbz&DP0A_B~T3uFer;0fJLZ*mz1;}(N!?K0|bo26ItXOjDvtz z<*ZKWDi|&W;J!R?Ijl@A*a_VYms%H9j-!QGce(S$|yx!xIpLDkzji7C{xn z;EtTtDP0A_GcX1Pa8MTc7Wm;0cr0giN>}X}?t>*b0Q=I%?1lZX1bs@BPU)%{o`hZy z&<3J^AO!XB`l=+}56_?n3{^g-bnR42!*Cl~peFG% zu>`+C|9`rbhdI@?Sr1RbCr}JK6M5WY7=x%i8r#U7Zbn}`qL$hG`XLv(62ngIE!GAC z>vXK}wo6?O&#>F!8Fo87!)}LX*zNEPyB(fkx5G2+c6f%}4$p8ZL>9>5Di>yFe>`@q zdGlu7?=R7`5>3QrE*?L=Fgt6HD`ttckAEqJI%tMws7q|*+GJM8&!GWxJHpxf4l^4oRzmzqz3jSGK?A*2M%kgok?g!P?7jE9n^ZTWos)i+Dqk?BE z`=zos+*Von&BTONpO(4R(hDQJUsd(Rty^+Z>l;8dEDQ!jph1;cipyuu0yIJd3^0&FMZLK^Or8>JSjxw(WX%_x$wq{Pgto z?(SAy7pb~IpKoz~Ub0lV6X3jB*n1Fxk8@ynpuOGHB?4hI=x=LV9d_}=2`yFkr;{fo zOO;FmC19{}F#U6Z5#R4pv$i(g+_J^g<$n?sawr%~7ul-o6u}Ut?%RQYHM4S`mO66L zr#1HAL2{qgQS*6*p{}knO)Jy1p{_1-NK`ll!#3zk4_SqDn=7TiXx!?j`qWH8eI#sIAXOJZNho0obQ- zNI;3%8u7#okbjiog>#dQO0hDvOxLGnGqto+EhUop*!~>U8!Gs4 z_wLVb+>jR!Jz((Bo;{ylyC&tdUmlwMQpwN$68yeKzf|^yQ_4e2UG?%XqYnVZPzO!W z1a(lH39~wuKLD(yYxe^{Muydpoy`k0}5E delta 1346 zcmV-I1-<&*3g-%tHh&yRL_t(|ob8%jNE}xfhMzOKYoJ=GdQ&68X0kV-QH&5m0u2%Z zmW23MXj-J2X4{a&pT#r-L+H3dD*LTzIosC&d<*G&G|$q6k;<$vwwR5|2160PKRsQ>2M7@ z9j;-gBi(#j2$3*#GCAB>XXnqCFDG{F-tzJTO-=D;C5G2WE)+oy05fn4{!VOdaq-i@ zz`IwkCZ^jyJDWPg+o2A2uNnUXdSJ>LnJv0j3|B%uq`?wQ!e0Osz%D3-V(5lp3w*Zd zDjTkXHz5Q=(0>mLQB#?4432^y1PrNC+M=uWFYSYR2*IcDb*w7z2s+^+gy1;nYEEp@ zRWn=*8U*0S_%Xl0C}>ctL}{09KLyL1%{=K^p~9=H;grxt97?xssE6$~5MOuibQ5r6hD(dBHsu7cr72thGq$|AF% z7(#Gc&T5ygg5en$hYUC-i+l~d@H@=PS?$tQdxm>p8IHoC_%R3J2rNU75~W?bYKEtv z8w50hAD)Sw%>+L*gMcphQ;E_pUFBT*3WT5@D&XZcNxT=HKohu3@LcHSBb_hMf-Aa4bX; zNZ~3M=jOgUb*g6TR^98((X<>*)4kqT3JNZrK7YM9H)oA2WQz5V$%Qhgff^`_Y~)&G zR>u$L&K0Jo$J2cwJ$>N(d383)N4(V+%1XbKIkO7>Sz2n@v*+`P390ToB_$Wf#?riA zDW|GoN!Y01$?ATom3~n)6)yn)7LvX>U3Si>iRsMrG*8_Qsqv757olnh7tH61%~??8x37Xz>fyK4Gn9< zE}c1}#p-^4_N-*7l4&3Z0<0d4|6C9V?|*lxSz8~kZrf()G60zz_xa*Q*6BJ~Fo>c1 zdSRhCvvQx7I#SW6HGcnobf4Bq<9UX`*48{t%hR;M)>dQWWLsNwpVs(2S)Y^n`2eVf z5i3`C1ghlB=ExGl(B%Zc{DTL^`14x$3gxGz86$IsLqCS@vqG3N|4&oP<=E4@@_%-W zj66N>LW{8=eG@A8X<0(jeOmb*PjsKw?a@(V{L7Y>Tp@CW_@br77?}(No`&&|e2+&$ zZGHp5Cj8TyfpNsB5O0O6g0(C$@u}$3s;sDp?$i3_&Ykd!Di*yB4d33q8{MZ>Syd&W zHa{QXpzV`WKQ-B?6suFqczs$HQ%ft= zQY48F9Z5mGp@R4J?fc}$4SDg<1p(gQzyITF*QA`*%R{4ID*D-9hTqodmrCAnOnGRk zt6m-^^Z_6n%Agvmp$xJUVOGb=2Y~f-t$qMV$gmocvl)ZO!C+rw;~ToZGdm+=3v7X% z85woD-rwB(I2g1<<+d$39j;-g!!_)5xQ3k$*Ra#^FDHS9VfOxWn*aa+07*qoM6N<$ Ef)PiJmjD0& diff --git a/test/clip-stroke.ref.png b/test/clip-stroke.ref.png index 7af2e9bd811eb30253f85ddc077b000e9d8028f5..dd5ae9a3953d2ea0a37b5e3f9eca9ccd94437352 100644 GIT binary patch delta 1405 zcmV-@1%mpk3!)20EpK&d(XM;oh5ho$^EdM`|tlb&$6}8`QK+kO38SFr+?5J_`l%_b~;?aPKPVl z>2L)*9TUuN%Lzina_K84ApXh2!)@D^^GoBNm}l#bYRf^+0G6|D8Kw56Ju zhsH2(oy%IY`UmRI5c9C5TKl)F5WH%^{WCaQ$?s{uH)G-&{?K4+Wp$dNaH<1xRFj}9XRfqI$) z+?{5)8!4hGz}xGQg58IhdtIlc7RG{C_wew32#;iAEuX%@XI1GY{{AE;j?j9D^mR`m8V)Mq%fV-WWyiR z`Xy%LB_Z9%rOt*c*y(TuI~}fIr^6NObhv_@4p*?#;R<#-T)|GqxD))S=CslfjdJoN zp??sUFB6FX6c6g`khoWEs*csvIHA)TDNZ- za$2)zC)exi85m$-fQE+T%A7f*=Ct}1!LCQuTE^psZ%2PRKKLTd8mT@Db#q#A8CMlP zEVCh}Ra$DuXmB>qpGotxjfe;9R zl<8Lr?8#~Q{f3;@kt4LXXDDlH5RVNXsg8kObc2Hq^j!;sUm z%Xp+(yvBeHZd40KYuOaYU@aT1TT2V4r3WTnzGlODLkpWWv1^y|@Q}=DZFb(w&YjAZ zmXC*tyi_dZZ{Hi|)t@uvrEEPODtPTb9;QGyFJ<=tATzJM0{}Tu~#DKOGzbNdEH=^00000 LNkvXXu0mjfAJe{A delta 1414 zcmV;11$p|S3#$u|Hh*AAL_t(|ob8)kY!p=-#ea8~j{t22+5`@&W=xSeRX&Xrk1Pn;5p{U_2@x|~JS_3o-TaqeKp(agGp?s9Y2a|?GTN*>V zpjNWA-Rr}yu(NFU-r1eHWI8AJp}q5;|GB4Wch0?YDx{Q5CV#jxy@3B4j$pgP5o~uj zg6$4Ru-!2w`DHmph_O`q$|;C{((rH>F3{PT+O6S5rLM& zh^5zfa`1s0M@=p>4(HFaX;TWkfUB{Qlm*A(asitv_P*3Q_V8NYeMj+ zUK%PHbu(jxC4W6U`y0T|PtjRSM=9UTW7i>SPn+P*46jTMh>YeH(2fXD{|>=f+?$s$ zltrBn5rB3+@y1+A2Te_>^ENgn6Qm>f$0FRP5dn@YHBiQmmKP8K+^4xzXqn&?d6Np( zdMs@%XUs)yfVa+#TeIeSs?RXyVr#kfZ&xOG)q?wTIe%Ni%n)0@R8($jWyT0!l`@c{ z#hnpewczu`NWrqJz3C=DayF>Z-ZvOs_rWd1tChzU+z9gPnb;OhExm22J<)Y_B zLqiHT{*gT(ONx^e22&J@cX%Pg(_JO zK$4tRwj}vQcAzyTuw^}83-u5>gKeRWfteOLLpgL zXvk>^AuB6oSCIm=5lMH1c*MF@W zx3Z$*RyH%*i1Myoo4m1ZPD>xwow%G4j-nqH9=WR3rCL{a#~)mqO;;BoiL}B6xQUlLLpgL zXy6^kco=e8RvC|2i&yKjz>R9bXf5k}39MzUd24B6w{*|M%hxQ}Z)jojW`CNRl$(cW zPHW2+cJ5TRG`&5H0bNM>-CbIb|TZgj-9@m%e}WO`^wvj zy<2boc=PRXh2`U|`3fCjiczf@qKiV27@bD9s* diff --git a/test/leaky-dashed-rectangle.ref.png b/test/leaky-dashed-rectangle.ref.png index c0ba7b2c07e90ccb61daae33a252067d5a888bc5..05f45846aaa1c92222adad23b5567d79414c4cc6 100644 GIT binary patch delta 312 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 delta 6133 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 diff --git a/test/scale-offset-similar.xfail.png b/test/scale-offset-similar.xfail.png index fef3a3995300e6cc2a18271ed5df93040de60bda..f0db601fcea6bc6b1ee24697a780777aece4b0a9 100644 GIT binary patch delta 6128 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 delta 6133 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 diff --git a/test/self-intersecting.ref.png b/test/self-intersecting.ref.png index 32d143987ea416495d005d3412e7504447e1b44c..d554d83eee04552a715048ddcf660bd144879f54 100644 GIT binary patch delta 121 zcmV-<0EYk70jL3xHga7_L_t(2k?oMl5x_7A1LcehqcoL9XXF9Hk*k8}#aJxDEHlHq zHUOwXH6BDDlJ+s|-dDCmWarv&=g2_GOxaOgEes6uBg%J3z8o)WMO@p>Y>%3L~+r~U2!m_Zg<+Vz+>zF3`{>eQ08-ukR z2gYPdojUIkVc$7VL~su8(V9?d9K$e_jk>|!lnXnjij<~_@gS<-Z}~8P2=@QlT`w4c VmZgWN!TJCI002ovPDHLkV1m4ZP!s?F