/* -*- Mode: c; tab-width: 8; c-basic-offset: 4; indent-tabs-mode: t; -*- */ /* cairo - a vector graphics library with display and print output * * Copyright © 2002 University of Southern California * Copyright © 2005 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it either under the terms of the GNU Lesser General Public * License version 2.1 as published by the Free Software Foundation * (the "LGPL") or, at your option, under the terms of the Mozilla * Public License Version 1.1 (the "MPL"). If you do not alter this * notice, a recipient may use your version of this file under either * the MPL or the LGPL. * * You should have received a copy of the LGPL along with this library * in the file COPYING-LGPL-2.1; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * You should have received a copy of the MPL along with this library * in the file COPYING-MPL-1.1 * * The contents of this file are subject to the Mozilla Public License * Version 1.1 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY * OF ANY KIND, either express or implied. See the LGPL or the MPL for * the specific language governing rights and limitations. * * The Original Code is the cairo graphics library. * * The Initial Developer of the Original Code is University of Southern * California. * * Contributor(s): * Carl D. Worth * Joonas Pihlaja * Chris Wilson */ #include "cairoint.h" #include "cairo-surface-fallback-private.h" #include "cairo-clip-private.h" #include "cairo-region-private.h" #include "cairo-spans-private.h" typedef struct { cairo_surface_t *dst; cairo_rectangle_int_t extents; cairo_image_surface_t *image; cairo_rectangle_int_t image_rect; void *image_extra; } fallback_state_t; /** * _fallback_init: * * Acquire destination image surface needed for an image-based * fallback. * * Return value: %CAIRO_INT_STATUS_NOTHING_TO_DO if the extents are not * visible, %CAIRO_STATUS_SUCCESS if some portion is visible and all * went well, or some error status otherwise. **/ static cairo_int_status_t _fallback_init (fallback_state_t *state, cairo_surface_t *dst, int x, int y, int width, int height) { cairo_status_t status; state->extents.x = x; state->extents.y = y; state->extents.width = width; state->extents.height = height; state->dst = dst; status = _cairo_surface_acquire_dest_image (dst, &state->extents, &state->image, &state->image_rect, &state->image_extra); if (unlikely (status)) return status; /* XXX: This NULL value tucked away in state->image is a rather * ugly interface. Cleaner would be to push the * CAIRO_INT_STATUS_NOTHING_TO_DO value down into * _cairo_surface_acquire_dest_image and its backend * counterparts. */ assert (state->image != NULL); return CAIRO_STATUS_SUCCESS; } static void _fallback_fini (fallback_state_t *state) { _cairo_surface_release_dest_image (state->dst, &state->extents, state->image, &state->image_rect, state->image_extra); } typedef cairo_status_t (*cairo_draw_func_t) (void *closure, cairo_operator_t op, const cairo_pattern_t *src, cairo_surface_t *dst, int dst_x, int dst_y, const cairo_rectangle_int_t *extents, cairo_region_t *clip_region); static cairo_status_t _create_composite_mask_pattern (cairo_surface_pattern_t *mask_pattern, cairo_clip_t *clip, cairo_draw_func_t draw_func, void *draw_closure, cairo_surface_t *dst, const cairo_rectangle_int_t *extents) { cairo_surface_t *mask; cairo_region_t *clip_region = NULL; cairo_solid_pattern_t solid; cairo_status_t status; cairo_bool_t 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); clip_surface = status == CAIRO_INT_STATUS_UNSUPPORTED; if (clip_region && cairo_region_num_rectangles (clip_region) == 1) clip_region = NULL; } /* We need to use solid here, because to use CAIRO_OPERATOR_SOURCE with * a mask (as called via _cairo_surface_mask) triggers assertion failures. */ mask = _cairo_surface_create_similar_solid (dst, CAIRO_CONTENT_ALPHA, extents->width, extents->height, CAIRO_COLOR_TRANSPARENT, TRUE); if (unlikely (mask->status)) return mask->status; _cairo_pattern_init_solid (&solid, CAIRO_COLOR_WHITE, CAIRO_CONTENT_ALPHA); status = draw_func (draw_closure, CAIRO_OPERATOR_ADD, &solid.base, mask, extents->x, extents->y, extents, clip_region); if (unlikely (status)) goto CLEANUP_SURFACE; if (clip_surface) status = _cairo_clip_combine_with_surface (clip, mask, extents); _cairo_pattern_init_for_surface (mask_pattern, mask); CLEANUP_SURFACE: cairo_surface_destroy (mask); return status; } /* 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 *src, cairo_draw_func_t draw_func, void *draw_closure, cairo_surface_t *dst, const cairo_rectangle_int_t *extents) { cairo_surface_pattern_t mask_pattern; cairo_status_t status; status = _create_composite_mask_pattern (&mask_pattern, clip, draw_func, draw_closure, dst, extents); if (likely (status == CAIRO_STATUS_SUCCESS)) { status = _cairo_surface_composite (op, src, &mask_pattern.base, dst, extents->x, extents->y, 0, 0, extents->x, extents->y, extents->width, extents->height, NULL); _cairo_pattern_fini (&mask_pattern.base); } return status; } /* 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, cairo_draw_func_t draw_func, void *draw_closure, cairo_surface_t *dst, const cairo_rectangle_int_t *extents) { cairo_surface_t *intermediate; cairo_surface_pattern_t pattern; cairo_surface_pattern_t clip_pattern; cairo_surface_t *clip_surface; cairo_status_t status; /* We'd be better off here creating a surface identical in format * to dst, but we have no way of getting that information. * A CAIRO_CONTENT_CLONE or something might be useful. */ intermediate = _cairo_surface_create_similar_scratch (dst, CAIRO_CONTENT_COLOR_ALPHA, extents->width, extents->height); if (intermediate == NULL) { intermediate = _cairo_image_surface_create_with_content (CAIRO_CONTENT_COLOR_ALPHA, extents->width, extents->width); } if (unlikely (intermediate->status)) return intermediate->status; /* Initialize the intermediate surface from the destination surface */ _cairo_pattern_init_for_surface (&pattern, dst); status = _cairo_surface_composite (CAIRO_OPERATOR_SOURCE, &pattern.base, NULL, intermediate, extents->x, extents->y, 0, 0, 0, 0, extents->width, extents->height, NULL); _cairo_pattern_fini (&pattern.base); if (unlikely (status)) goto CLEANUP_SURFACE; status = (*draw_func) (draw_closure, op, src, intermediate, extents->x, extents->y, extents, NULL); if (unlikely (status)) goto CLEANUP_SURFACE; assert (clip->path != NULL); clip_surface = _cairo_clip_get_surface (clip, dst); if (unlikely (clip_surface->status)) goto CLEANUP_SURFACE; _cairo_pattern_init_for_surface (&clip_pattern, clip_surface); cairo_surface_destroy (clip_surface); /* Combine that with the clip */ status = _cairo_surface_composite (CAIRO_OPERATOR_DEST_IN, &clip_pattern.base, NULL, intermediate, extents->x - clip->path->extents.x, extents->y - clip->path->extents.y, 0, 0, 0, 0, extents->width, extents->height, NULL); if (unlikely (status)) goto CLEANUP_SURFACE; /* Punch the clip out of the destination */ status = _cairo_surface_composite (CAIRO_OPERATOR_DEST_OUT, &clip_pattern.base, NULL, dst, extents->x - clip->path->extents.x, extents->y - clip->path->extents.y, 0, 0, extents->x, extents->y, extents->width, extents->height, NULL); if (unlikely (status)) goto CLEANUP_SURFACE; /* Now add the two results together */ _cairo_pattern_init_for_surface (&pattern, intermediate); status = _cairo_surface_composite (CAIRO_OPERATOR_ADD, &pattern.base, NULL, dst, 0, 0, 0, 0, extents->x, extents->y, extents->width, extents->height, NULL); _cairo_pattern_fini (&pattern.base); CLEANUP_SURFACE: _cairo_pattern_fini (&clip_pattern.base); cairo_surface_destroy (intermediate); 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 *src, cairo_draw_func_t draw_func, void *draw_closure, cairo_surface_t *dst, const cairo_rectangle_int_t *extents) { cairo_surface_pattern_t mask_pattern; cairo_region_t *clip_region = NULL; cairo_status_t status; if (clip != NULL) { status = _cairo_clip_get_region (clip, &clip_region); assert (! _cairo_status_is_error (status)); if (unlikely (status == CAIRO_INT_STATUS_NOTHING_TO_DO)) return CAIRO_STATUS_SUCCESS; /* a solitary clip rectangle is already accommodated by extents */ if (clip_region && cairo_region_num_rectangles (clip_region) == 1) clip_region = NULL; } /* Create a surface that is mask IN clip */ status = _create_composite_mask_pattern (&mask_pattern, clip, draw_func, draw_closure, dst, extents); if (unlikely (status)) return status; /* Compute dest' = dest OUT (mask IN clip) */ status = _cairo_surface_composite (CAIRO_OPERATOR_DEST_OUT, &mask_pattern.base, NULL, dst, 0, 0, 0, 0, extents->x, extents->y, extents->width, extents->height, clip_region); if (unlikely (status)) goto CLEANUP_MASK_PATTERN; /* Now compute (src IN (mask IN clip)) ADD dest' */ status = _cairo_surface_composite (CAIRO_OPERATOR_ADD, src, &mask_pattern.base, dst, extents->x, extents->y, 0, 0, extents->x, extents->y, extents->width, extents->height, clip_region); CLEANUP_MASK_PATTERN: _cairo_pattern_fini (&mask_pattern.base); return status; } static int _cairo_rectangle_empty (const cairo_rectangle_int_t *rect) { return rect->width == 0 || rect->height == 0; } /** * _clip_and_composite: * @clip: a #cairo_clip_t * @op: the operator to draw with * @src: source pattern * @draw_func: function that can be called to draw with the mask onto a surface. * @draw_closure: data to pass to @draw_func. * @dst: destination surface * @extents: rectangle holding a bounding box for the operation; this * rectangle will be used as the size for the temporary * surface. * * When there is a surface clip, we typically need to create an intermediate * surface. This function handles the logic of creating a temporary surface * drawing to it, then compositing the result onto the target surface. * * @draw_func is to called to draw the mask; it will be called no more * than once. * * Return value: %CAIRO_STATUS_SUCCESS if the drawing succeeded. **/ static cairo_status_t _clip_and_composite (cairo_clip_t *clip, cairo_operator_t op, const cairo_pattern_t *src, cairo_draw_func_t draw_func, void *draw_closure, cairo_surface_t *dst, const cairo_rectangle_int_t *extents) { cairo_solid_pattern_t solid_pattern; cairo_status_t status; if (_cairo_rectangle_empty (extents)) /* Nothing to do */ return CAIRO_STATUS_SUCCESS; if (op == CAIRO_OPERATOR_CLEAR) { _cairo_pattern_init_solid (&solid_pattern, CAIRO_COLOR_WHITE, CAIRO_CONTENT_COLOR); src = &solid_pattern.base; op = CAIRO_OPERATOR_DEST_OUT; } if (op == CAIRO_OPERATOR_SOURCE) { status = _clip_and_composite_source (clip, src, draw_func, draw_closure, dst, extents); } else { cairo_bool_t clip_surface = FALSE; cairo_region_t *clip_region = NULL; if (clip != NULL) { status = _cairo_clip_get_region (clip, &clip_region); assert (! _cairo_status_is_error (status)); if (unlikely (status == CAIRO_INT_STATUS_NOTHING_TO_DO)) return CAIRO_STATUS_SUCCESS; clip_surface = status == CAIRO_INT_STATUS_UNSUPPORTED; } if (clip_surface) { if (_cairo_operator_bounded_by_mask (op)) { status = _clip_and_composite_with_mask (clip, op, src, draw_func, draw_closure, dst, extents); } else { status = _clip_and_composite_combine (clip, op, src, draw_func, draw_closure, dst, extents); } } else { /* a solitary clip rectangle is already accommodated by extents */ if (clip_region && cairo_region_num_rectangles (clip_region) == 1) clip_region = NULL; status = draw_func (draw_closure, op, src, dst, 0, 0, extents, clip_region); } } return status; } /* Composites a region representing a set of trapezoids. */ static cairo_status_t _composite_trap_region (cairo_clip_t *clip, const cairo_pattern_t *src, cairo_operator_t op, cairo_surface_t *dst, cairo_region_t *trap_region, const cairo_rectangle_int_t *extents) { cairo_status_t status; cairo_solid_pattern_t solid_pattern; cairo_surface_pattern_t mask_pattern; cairo_pattern_t *mask = NULL; int mask_x = 0, mask_y =0; if (clip != NULL) { cairo_surface_t *clip_surface = NULL; const cairo_rectangle_int_t *clip_extents; clip_surface = _cairo_clip_get_surface (clip, dst); if (unlikely (clip_surface->status)) return clip_surface->status; if (op == CAIRO_OPERATOR_CLEAR) { _cairo_pattern_init_solid (&solid_pattern, CAIRO_COLOR_WHITE, CAIRO_CONTENT_COLOR); src = &solid_pattern.base; op = CAIRO_OPERATOR_DEST_OUT; } _cairo_pattern_init_for_surface (&mask_pattern, clip_surface); cairo_surface_destroy (clip_surface); clip_extents = _cairo_clip_get_extents (clip); mask_x = extents->x - clip_extents->x; mask_y = extents->y - clip_extents->y; mask = &mask_pattern.base; } /* reduce a solitary clipping region to the extents */ if (cairo_region_num_rectangles (trap_region) == 1) trap_region = NULL; status = _cairo_surface_composite (op, src, mask, dst, extents->x, extents->y, mask_x, mask_y, extents->x, extents->y, extents->width, extents->height, trap_region); if (mask != NULL) _cairo_pattern_fini (mask); return status; } typedef struct { cairo_traps_t *traps; cairo_antialias_t antialias; } cairo_composite_traps_info_t; static cairo_status_t _composite_traps_draw_func (void *closure, cairo_operator_t op, const cairo_pattern_t *src, cairo_surface_t *dst, int dst_x, int dst_y, const cairo_rectangle_int_t *extents, cairo_region_t *clip_region) { cairo_composite_traps_info_t *info = closure; if (dst_x != 0 || dst_y != 0) _cairo_traps_translate (info->traps, - dst_x, - dst_y); return _cairo_surface_composite_trapezoids (op, src, dst, info->antialias, extents->x, extents->y, extents->x - dst_x, extents->y - dst_y, extents->width, extents->height, info->traps->traps, info->traps->num_traps, clip_region); } enum { HAS_CLEAR_REGION = 0x1, }; static cairo_status_t _clip_and_composite_region (const cairo_pattern_t *src, cairo_operator_t op, cairo_surface_t *dst, cairo_region_t *trap_region, cairo_clip_t *clip, cairo_rectangle_int_t *extents) { cairo_region_t clear_region; unsigned int has_region = 0; cairo_status_t status; if (! _cairo_operator_bounded_by_mask (op) && clip == NULL) { /* If we optimize drawing with an unbounded operator to * _cairo_surface_fill_rectangles() or to drawing with a * clip region, then we have an additional region to clear. */ _cairo_region_init_rectangle (&clear_region, extents); status = cairo_region_subtract (&clear_region, trap_region); if (unlikely (status)) return status; if (! cairo_region_is_empty (&clear_region)) has_region |= HAS_CLEAR_REGION; } if ((src->type == CAIRO_PATTERN_TYPE_SOLID || op == CAIRO_OPERATOR_CLEAR) && clip == NULL) { const cairo_color_t *color; if (op == CAIRO_OPERATOR_CLEAR) color = CAIRO_COLOR_TRANSPARENT; else color = &((cairo_solid_pattern_t *)src)->color; /* Solid rectangles special case */ status = _cairo_surface_fill_region (dst, op, color, trap_region); } else { /* For a simple rectangle, we can just use composite(), for more * rectangles, we have to set a clip region. The cost of rasterizing * trapezoids is pretty high for most backends currently, so it's * worthwhile even if a region is needed. * * If we have a clip surface, we set it as the mask; this only works * for bounded operators other than SOURCE; for unbounded operators, * clip and mask cannot be interchanged. For SOURCE, the operator * as implemented by the backends is different in its handling * of the mask then what we want. * * CAIRO_INT_STATUS_UNSUPPORTED will be returned if the region has * more than rectangle and the destination doesn't support clip * regions. In that case, we fall through. */ status = _composite_trap_region (clip, src, op, dst, trap_region, extents); } if (has_region & HAS_CLEAR_REGION) { if (status == CAIRO_STATUS_SUCCESS) { status = _cairo_surface_fill_region (dst, CAIRO_OPERATOR_CLEAR, CAIRO_COLOR_TRANSPARENT, &clear_region); } _cairo_region_fini (&clear_region); } return status; } /* avoid using region code to re-validate boxes */ static cairo_status_t _fill_rectangles (cairo_surface_t *dst, cairo_operator_t op, const cairo_pattern_t *src, cairo_traps_t *traps, cairo_clip_t *clip) { const cairo_color_t *color; cairo_rectangle_int_t stack_rects[CAIRO_STACK_ARRAY_LENGTH (cairo_rectangle_int_t)]; cairo_rectangle_int_t *rects = stack_rects; cairo_status_t status; int i; if (! traps->is_rectilinear || ! traps->maybe_region) return CAIRO_INT_STATUS_UNSUPPORTED; /* XXX: convert clip region to geometric boxes? */ if (clip != NULL) return CAIRO_INT_STATUS_UNSUPPORTED; /* XXX: fallback for the region_subtract() operation */ if (! _cairo_operator_bounded_by_mask (op)) return CAIRO_INT_STATUS_UNSUPPORTED; if (! (src->type == CAIRO_PATTERN_TYPE_SOLID || op == CAIRO_OPERATOR_CLEAR)) return 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 { status = _cairo_bentley_ottmann_tessellate_rectilinear_traps (traps, CAIRO_FILL_RULE_WINDING); } if (unlikely (status)) return status; } for (i = 0; i < traps->num_traps; i++) { if (! _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 CAIRO_INT_STATUS_UNSUPPORTED; } } if (traps->num_traps > ARRAY_LENGTH (stack_rects)) { rects = _cairo_malloc_ab (traps->num_traps, sizeof (cairo_rectangle_int_t)); if (unlikely (rects == NULL)) return _cairo_error (CAIRO_STATUS_NO_MEMORY); } for (i = 0; i < traps->num_traps; i++) { int x1 = _cairo_fixed_integer_part (traps->traps[i].left.p1.x); int y1 = _cairo_fixed_integer_part (traps->traps[i].top); int x2 = _cairo_fixed_integer_part (traps->traps[i].right.p1.x); int y2 = _cairo_fixed_integer_part (traps->traps[i].bottom); rects[i].x = x1; rects[i].y = y1; rects[i].width = x2 - x1; rects[i].height = y2 - y1; } if (op == CAIRO_OPERATOR_CLEAR) color = CAIRO_COLOR_TRANSPARENT; else color = &((cairo_solid_pattern_t *)src)->color; status = _cairo_surface_fill_rectangles (dst, op, color, rects, i); if (rects != stack_rects) free (rects); return status; } /* Warning: This call modifies the coordinates of traps */ static cairo_status_t _clip_and_composite_trapezoids (const cairo_pattern_t *src, cairo_operator_t op, cairo_surface_t *dst, cairo_traps_t *traps, cairo_antialias_t antialias, cairo_clip_t *clip, cairo_rectangle_int_t *extents) { cairo_composite_traps_info_t traps_info; cairo_region_t *clip_region = NULL; cairo_bool_t clip_surface = FALSE; cairo_status_t status; if (traps->num_traps == 0 && _cairo_operator_bounded_by_mask (op)) return CAIRO_STATUS_SUCCESS; if (clip != NULL) { status = _cairo_clip_get_region (clip, &clip_region); if (unlikely (_cairo_status_is_error (status))) return status; if (unlikely (status == CAIRO_INT_STATUS_NOTHING_TO_DO)) return CAIRO_STATUS_SUCCESS; clip_surface = status == CAIRO_INT_STATUS_UNSUPPORTED; } /* 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 (! clip_surface || (_cairo_operator_bounded_by_mask (op) && op != CAIRO_OPERATOR_SOURCE)) { cairo_region_t *trap_region = NULL; status = _fill_rectangles (dst, op, src, traps, clip); if (status != CAIRO_INT_STATUS_UNSUPPORTED) return status; status = _cairo_traps_extract_region (traps, &trap_region); if (unlikely (_cairo_status_is_error (status))) return status; if (trap_region != NULL) { status = cairo_region_intersect_rectangle (trap_region, extents); if (unlikely (status)) { cairo_region_destroy (trap_region); return status; } if (clip_region != NULL) { status = cairo_region_intersect (trap_region, clip_region); if (unlikely (status)) { cairo_region_destroy (trap_region); return status; } } if (_cairo_operator_bounded_by_mask (op)) { cairo_rectangle_int_t trap_extents; cairo_region_get_extents (trap_region, &trap_extents); if (! _cairo_rectangle_intersect (extents, &trap_extents)) { cairo_region_destroy (trap_region); return CAIRO_STATUS_SUCCESS; } } status = _clip_and_composite_region (src, op, dst, trap_region, clip_surface ? clip : NULL, extents); cairo_region_destroy (trap_region); if (likely (status != CAIRO_INT_STATUS_UNSUPPORTED)) return status; } } /* No fast path, exclude self-intersections and clip trapezoids. */ 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; } /* Otherwise render the trapezoids to a mask and composite in the usual * fashion. */ traps_info.traps = traps; traps_info.antialias = antialias; return _clip_and_composite (clip, op, src, _composite_traps_draw_func, &traps_info, dst, extents); } typedef struct { cairo_polygon_t *polygon; cairo_fill_rule_t fill_rule; cairo_antialias_t antialias; } cairo_composite_spans_info_t; static cairo_status_t _composite_spans_draw_func (void *closure, cairo_operator_t op, const cairo_pattern_t *src, cairo_surface_t *dst, int dst_x, int dst_y, const cairo_rectangle_int_t *extents, cairo_region_t *clip_region) { cairo_composite_rectangles_t rects; cairo_composite_spans_info_t *info = closure; _cairo_composite_rectangles_init (&rects, extents->x, extents->y, extents->width, extents->height); /* The incoming dst_x/y are where we're pretending the origin of * the dst surface is -- *not* the offset of a rectangle where * we'd like to place the result. */ rects.dst.x -= dst_x; rects.dst.y -= dst_y; return _cairo_surface_composite_polygon (dst, op, src, info->fill_rule, info->antialias, &rects, info->polygon, clip_region); } static cairo_status_t _rectangle_intersect_clip (cairo_rectangle_int_t *extents, cairo_clip_t *clip) { if (clip != NULL) { if (! _cairo_rectangle_intersect (extents, _cairo_clip_get_extents (clip))) { return CAIRO_INT_STATUS_NOTHING_TO_DO; } return _cairo_clip_rectangle (clip, extents); } else if (_cairo_rectangle_empty (extents)) return CAIRO_INT_STATUS_NOTHING_TO_DO; return CAIRO_STATUS_SUCCESS; } static cairo_bool_t _clip_contains_rectangle (cairo_clip_t *clip, const cairo_rectangle_int_t *rect) { cairo_clip_path_t *clip_path; clip_path = clip->path; if (clip_path->extents.x > rect->x || clip_path->extents.y > rect->y || clip_path->extents.x + clip_path->extents.width < rect->x + rect->width || clip_path->extents.y + clip_path->extents.height < rect->y + rect->height) { return FALSE; } do { cairo_box_t box; if (! _cairo_path_fixed_is_box (&clip_path->path, &box)) return FALSE; if (box.p1.x > _cairo_fixed_from_int (rect->x) || box.p1.y > _cairo_fixed_from_int (rect->y) || box.p2.x < _cairo_fixed_from_int (rect->x + rect->width) || box.p2.y < _cairo_fixed_from_int (rect->y + rect->height)) { return FALSE; } } while ((clip_path = clip_path->prev) != NULL); return TRUE; } static inline cairo_status_t _clip_to_boxes (cairo_clip_t **clip, const cairo_rectangle_int_t *extents, cairo_bool_t is_bounded, cairo_box_t **boxes, int *num_boxes) { cairo_status_t status; if (*clip == NULL) { status = CAIRO_STATUS_SUCCESS; goto EXTENTS; } status = _cairo_clip_get_boxes (*clip, boxes, num_boxes); switch ((int) status) { case CAIRO_STATUS_SUCCESS: if (is_bounded) *clip = NULL; goto DONE; case CAIRO_INT_STATUS_UNSUPPORTED: status = CAIRO_STATUS_SUCCESS; goto EXTENTS; } EXTENTS: _cairo_box_from_rectangle (&(*boxes)[0], extents); *num_boxes = 1; DONE: return status; } cairo_status_t _cairo_surface_fallback_paint (cairo_surface_t *surface, cairo_operator_t op, const cairo_pattern_t *source, cairo_clip_t *clip) { cairo_status_t status; cairo_rectangle_int_t extents; cairo_bool_t is_bounded; cairo_clip_path_t *clip_path = clip ? clip->path : NULL; cairo_box_t boxes_stack[32], *boxes = boxes_stack; int num_boxes = ARRAY_LENGTH (boxes_stack); cairo_traps_t traps; is_bounded = _cairo_surface_get_extents (surface, &extents); assert (is_bounded || clip); is_bounded = FALSE; if (_cairo_operator_bounded_by_source (op)) { cairo_rectangle_int_t source_extents; _cairo_pattern_get_extents (source, &source_extents); if (! _cairo_rectangle_intersect (&extents, &source_extents)) return CAIRO_STATUS_SUCCESS; is_bounded = TRUE; } if (is_bounded && clip != NULL && _clip_contains_rectangle (clip, &extents)) clip = NULL; status = _rectangle_intersect_clip (&extents, clip); if (unlikely (status)) { if (status == CAIRO_INT_STATUS_NOTHING_TO_DO) status = CAIRO_STATUS_SUCCESS; return status; } status = _clip_to_boxes (&clip, &extents, is_bounded, &boxes, &num_boxes); if (unlikely (status)) { if (status == CAIRO_INT_STATUS_NOTHING_TO_DO) status = CAIRO_STATUS_SUCCESS; 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 && clip_path->prev == NULL && _cairo_operator_bounded_by_mask (op)) { return _cairo_surface_fill (surface, op, source, &clip_path->path, clip_path->fill_rule, clip_path->tolerance, clip_path->antialias, NULL); } status = _cairo_traps_init_boxes (&traps, boxes, num_boxes); if (unlikely (status)) goto CLEANUP_BOXES; status = _clip_and_composite_trapezoids (source, op, surface, &traps, CAIRO_ANTIALIAS_DEFAULT, clip, &extents); _cairo_traps_fini (&traps); CLEANUP_BOXES: if (boxes != boxes_stack) free (boxes); return status; } static cairo_status_t _cairo_surface_mask_draw_func (void *closure, cairo_operator_t op, const cairo_pattern_t *src, cairo_surface_t *dst, int dst_x, int dst_y, const cairo_rectangle_int_t *extents, cairo_region_t *clip_region) { cairo_pattern_t *mask = closure; if (src) { return _cairo_surface_composite (op, src, mask, dst, extents->x, extents->y, extents->x, extents->y, extents->x - dst_x, extents->y - dst_y, extents->width, extents->height, clip_region); } else { return _cairo_surface_composite (op, mask, NULL, dst, extents->x, extents->y, 0, 0, /* unused */ extents->x - dst_x, extents->y - dst_y, extents->width, extents->height, clip_region); } } cairo_status_t _cairo_surface_fallback_mask (cairo_surface_t *surface, cairo_operator_t op, const cairo_pattern_t *source, const cairo_pattern_t *mask, cairo_clip_t *clip) { cairo_rectangle_int_t extents; cairo_bool_t is_bounded; cairo_status_t status; is_bounded = _cairo_surface_get_extents (surface, &extents); assert (is_bounded || clip); is_bounded = FALSE; if (_cairo_operator_bounded_by_source (op)) { cairo_rectangle_int_t source_extents; _cairo_pattern_get_extents (source, &source_extents); if (! _cairo_rectangle_intersect (&extents, &source_extents)) return CAIRO_STATUS_SUCCESS; is_bounded = TRUE; } if (_cairo_operator_bounded_by_mask (op)) { cairo_rectangle_int_t mask_extents; _cairo_pattern_get_extents (mask, &mask_extents); if (! _cairo_rectangle_intersect (&extents, &mask_extents)) return CAIRO_STATUS_SUCCESS; is_bounded = TRUE; } if (is_bounded && clip != NULL && _clip_contains_rectangle (clip, &extents)) clip = NULL; status = _rectangle_intersect_clip (&extents, clip); if (status) { if (status == CAIRO_INT_STATUS_NOTHING_TO_DO) status = CAIRO_STATUS_SUCCESS; return status; } return _clip_and_composite (clip, op, source, _cairo_surface_mask_draw_func, (void *) mask, surface, &extents); } cairo_status_t _cairo_surface_fallback_stroke (cairo_surface_t *surface, cairo_operator_t op, const cairo_pattern_t *source, cairo_path_fixed_t *path, cairo_stroke_style_t *stroke_style, cairo_matrix_t *ctm, cairo_matrix_t *ctm_inverse, double tolerance, cairo_antialias_t antialias, cairo_clip_t *clip) { cairo_polygon_t polygon; cairo_traps_t traps; cairo_box_t boxes_stack[32], *boxes = boxes_stack; int num_boxes = ARRAY_LENGTH (boxes_stack); cairo_rectangle_int_t extents; cairo_bool_t is_bounded; cairo_status_t status; is_bounded = _cairo_surface_get_extents (surface, &extents); assert (is_bounded || clip); is_bounded = FALSE; if (_cairo_operator_bounded_by_source (op)) { cairo_rectangle_int_t source_extents; _cairo_pattern_get_extents (source, &source_extents); if (! _cairo_rectangle_intersect (&extents, &source_extents)) return CAIRO_STATUS_SUCCESS; is_bounded = TRUE; } if (_cairo_operator_bounded_by_mask (op)) { cairo_rectangle_int_t path_extents; _cairo_path_fixed_approximate_stroke_extents (path, stroke_style, ctm, &path_extents); if (! _cairo_rectangle_intersect (&extents, &path_extents)) return CAIRO_STATUS_SUCCESS; is_bounded = TRUE; } if (is_bounded && clip != NULL && _clip_contains_rectangle (clip, &extents)) clip = NULL; status = _rectangle_intersect_clip (&extents, clip); if (unlikely (status)) { if (status == CAIRO_INT_STATUS_NOTHING_TO_DO) status = CAIRO_STATUS_SUCCESS; return status; } status = _clip_to_boxes (&clip, &extents, is_bounded, &boxes, &num_boxes); if (unlikely (status)) { if (status == CAIRO_INT_STATUS_NOTHING_TO_DO) status = CAIRO_STATUS_SUCCESS; return status; } _cairo_polygon_init (&polygon); _cairo_polygon_limit (&polygon, boxes, num_boxes); _cairo_traps_init (&traps); _cairo_traps_limit (&traps, boxes, num_boxes); if (path->is_rectilinear) { status = _cairo_path_fixed_stroke_rectilinear_to_traps (path, stroke_style, ctm, &traps); if (likely (status == CAIRO_STATUS_SUCCESS)) goto DO_TRAPS; if (_cairo_status_is_error (status)) goto CLEANUP; } status = _cairo_path_fixed_stroke_to_polygon (path, stroke_style, ctm, ctm_inverse, tolerance, &polygon); if (unlikely (status)) goto CLEANUP; if (polygon.num_edges == 0) goto DO_TRAPS; if (_cairo_operator_bounded_by_mask (op)) { cairo_rectangle_int_t polygon_extents; _cairo_box_round_to_rectangle (&polygon.extents, &polygon_extents); if (! _cairo_rectangle_intersect (&extents, &polygon_extents)) goto CLEANUP; } if (_cairo_surface_check_span_renderer (op, source, surface, antialias)) { cairo_composite_spans_info_t info; info.polygon = &polygon; info.fill_rule = CAIRO_FILL_RULE_WINDING; info.antialias = antialias; status = _clip_and_composite (clip, op, source, _composite_spans_draw_func, &info, surface, &extents); goto CLEANUP; } /* Fall back to trapezoid fills. */ status = _cairo_bentley_ottmann_tessellate_polygon (&traps, &polygon, CAIRO_FILL_RULE_WINDING); if (unlikely (status)) goto CLEANUP; DO_TRAPS: status = _clip_and_composite_trapezoids (source, op, surface, &traps, antialias, clip, &extents); CLEANUP: _cairo_traps_fini (&traps); _cairo_polygon_fini (&polygon); if (boxes != boxes_stack) free (boxes); return status; } cairo_status_t _cairo_surface_fallback_fill (cairo_surface_t *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_polygon_t polygon; cairo_traps_t traps; cairo_box_t boxes_stack[32], *boxes = boxes_stack; int num_boxes = ARRAY_LENGTH (boxes_stack); cairo_rectangle_int_t extents; cairo_bool_t is_bounded; cairo_bool_t is_rectilinear; cairo_status_t status; is_bounded = _cairo_surface_get_extents (surface, &extents); assert (is_bounded || clip); is_bounded = FALSE; if (_cairo_operator_bounded_by_source (op)) { cairo_rectangle_int_t source_extents; _cairo_pattern_get_extents (source, &source_extents); if (! _cairo_rectangle_intersect (&extents, &source_extents)) return CAIRO_STATUS_SUCCESS; is_bounded = TRUE; } if (_cairo_operator_bounded_by_mask (op)) { cairo_rectangle_int_t path_extents; _cairo_path_fixed_approximate_fill_extents (path, &path_extents); if (! _cairo_rectangle_intersect (&extents, &path_extents)) return CAIRO_STATUS_SUCCESS; is_bounded = TRUE; } if (is_bounded) { if (clip != NULL && _clip_contains_rectangle (clip, &extents)) clip = NULL; if (clip != NULL && clip->path->prev == NULL && _cairo_path_fixed_equal (&clip->path->path, path)) { clip = NULL; } } status = _rectangle_intersect_clip (&extents, clip); if (unlikely (status)) { if (status == CAIRO_INT_STATUS_NOTHING_TO_DO) status = CAIRO_STATUS_SUCCESS; return status; } status = _clip_to_boxes (&clip, &extents, is_bounded, &boxes, &num_boxes); if (unlikely (status)) { if (status == CAIRO_INT_STATUS_NOTHING_TO_DO) status = CAIRO_STATUS_SUCCESS; return status; } _cairo_traps_init (&traps); _cairo_traps_limit (&traps, boxes, num_boxes); _cairo_polygon_init (&polygon); _cairo_polygon_limit (&polygon, boxes, num_boxes); if (path->is_empty_fill) goto DO_TRAPS; is_rectilinear = _cairo_path_fixed_is_rectilinear_fill (path); if (is_rectilinear) { status = _cairo_path_fixed_fill_rectilinear_to_traps (path, fill_rule, &traps); if (likely (status == CAIRO_STATUS_SUCCESS)) goto DO_TRAPS; if (_cairo_status_is_error (status)) goto CLEANUP; } status = _cairo_path_fixed_fill_to_polygon (path, tolerance, &polygon); if (unlikely (status)) goto CLEANUP; if (polygon.num_edges == 0) goto DO_TRAPS; if (_cairo_operator_bounded_by_mask (op)) { cairo_rectangle_int_t polygon_extents; _cairo_box_round_to_rectangle (&polygon.extents, &polygon_extents); if (! _cairo_rectangle_intersect (&extents, &polygon_extents)) goto CLEANUP; } if (is_rectilinear) { status = _cairo_bentley_ottmann_tessellate_rectilinear_polygon (&traps, &polygon, fill_rule); if (likely (status == CAIRO_STATUS_SUCCESS)) goto DO_TRAPS; if (unlikely (_cairo_status_is_error (status))) goto CLEANUP; } if (_cairo_surface_check_span_renderer (op, source, surface, antialias)) { cairo_composite_spans_info_t info; info.polygon = &polygon; info.fill_rule = fill_rule; info.antialias = antialias; status = _clip_and_composite (clip, op, source, _composite_spans_draw_func, &info, surface, &extents); goto CLEANUP; } /* Fall back to trapezoid fills. */ status = _cairo_bentley_ottmann_tessellate_polygon (&traps, &polygon, fill_rule); if (unlikely (status)) goto CLEANUP; DO_TRAPS: status = _clip_and_composite_trapezoids (source, op, surface, &traps, antialias, clip, &extents); CLEANUP: _cairo_traps_fini (&traps); _cairo_polygon_fini (&polygon); if (boxes != boxes_stack) free (boxes); return status; } typedef struct { cairo_scaled_font_t *font; cairo_glyph_t *glyphs; int num_glyphs; } cairo_show_glyphs_info_t; static cairo_status_t _cairo_surface_old_show_glyphs_draw_func (void *closure, cairo_operator_t op, const cairo_pattern_t *src, cairo_surface_t *dst, int dst_x, int dst_y, const cairo_rectangle_int_t *extents, cairo_region_t *clip_region) { cairo_show_glyphs_info_t *glyph_info = closure; cairo_status_t status; /* Modifying the glyph array is fine because we know that this function * will be called only once, and we've already made a copy of the * glyphs in the wrapper. */ if (dst_x != 0 || dst_y != 0) { int i; for (i = 0; i < glyph_info->num_glyphs; ++i) { ((cairo_glyph_t *) glyph_info->glyphs)[i].x -= dst_x; ((cairo_glyph_t *) glyph_info->glyphs)[i].y -= dst_y; } } status = _cairo_surface_old_show_glyphs (glyph_info->font, op, src, dst, extents->x, extents->y, extents->x - dst_x, extents->y - dst_y, extents->width, extents->height, glyph_info->glyphs, glyph_info->num_glyphs, clip_region); if (status != CAIRO_INT_STATUS_UNSUPPORTED) return status; return _cairo_scaled_font_show_glyphs (glyph_info->font, op, src, dst, extents->x, extents->y, extents->x - dst_x, extents->y - dst_y, extents->width, extents->height, glyph_info->glyphs, glyph_info->num_glyphs, clip_region); } cairo_status_t _cairo_surface_fallback_show_glyphs (cairo_surface_t *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) { cairo_status_t status; cairo_rectangle_int_t extents; cairo_show_glyphs_info_t glyph_info; cairo_bool_t is_bounded; is_bounded = _cairo_surface_get_extents (surface, &extents); assert (is_bounded || clip); is_bounded = FALSE; if (_cairo_operator_bounded_by_source (op)) { cairo_rectangle_int_t source_extents; _cairo_pattern_get_extents (source, &source_extents); if (! _cairo_rectangle_intersect (&extents, &source_extents)) return CAIRO_STATUS_SUCCESS; is_bounded = TRUE; } if (_cairo_operator_bounded_by_mask (op)) { cairo_rectangle_int_t glyph_extents; status = _cairo_scaled_font_glyph_device_extents (scaled_font, glyphs, num_glyphs, &glyph_extents, NULL); if (unlikely (status)) return status; if (! _cairo_rectangle_intersect (&extents, &glyph_extents)) return CAIRO_STATUS_SUCCESS; is_bounded = TRUE; } if (is_bounded && clip != NULL && _clip_contains_rectangle (clip, &extents)) clip = NULL; status = _rectangle_intersect_clip (&extents, clip); if (status) { if (status == CAIRO_INT_STATUS_NOTHING_TO_DO) status = CAIRO_STATUS_SUCCESS; return status; } glyph_info.font = scaled_font; glyph_info.glyphs = glyphs; glyph_info.num_glyphs = num_glyphs; return _clip_and_composite (clip, op, source, _cairo_surface_old_show_glyphs_draw_func, &glyph_info, surface, &extents); } cairo_surface_t * _cairo_surface_fallback_snapshot (cairo_surface_t *surface) { cairo_surface_t *snapshot; cairo_status_t status; cairo_format_t format; cairo_surface_pattern_t pattern; cairo_image_surface_t *image; void *image_extra; status = _cairo_surface_acquire_source_image (surface, &image, &image_extra); if (unlikely (status)) return _cairo_surface_create_in_error (status); format = image->format; if (format == CAIRO_FORMAT_INVALID) { /* Non-standard images formats can be generated when retrieving * images from unusual xservers, for example. */ format = _cairo_format_from_content (image->base.content); } snapshot = cairo_image_surface_create (format, image->width, image->height); if (cairo_surface_status (snapshot)) { _cairo_surface_release_source_image (surface, image, image_extra); return snapshot; } _cairo_pattern_init_for_surface (&pattern, &image->base); status = _cairo_surface_paint (snapshot, CAIRO_OPERATOR_SOURCE, &pattern.base, NULL); _cairo_pattern_fini (&pattern.base); _cairo_surface_release_source_image (surface, image, image_extra); if (unlikely (status)) { cairo_surface_destroy (snapshot); return _cairo_surface_create_in_error (status); } return snapshot; } cairo_status_t _cairo_surface_fallback_composite (cairo_operator_t op, const cairo_pattern_t *src, const cairo_pattern_t *mask, cairo_surface_t *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) { fallback_state_t state; cairo_region_t *fallback_region = NULL; cairo_status_t status; status = _fallback_init (&state, dst, dst_x, dst_y, width, height); if (unlikely (status)) { if (status == CAIRO_INT_STATUS_NOTHING_TO_DO) status = CAIRO_STATUS_SUCCESS; return status; } /* We know this will never fail with the image backend; but * instead of calling into it directly, we call * _cairo_surface_composite so that we get the correct device * offset handling. */ if (clip_region != NULL && (state.image_rect.x || state.image_rect.y)) { fallback_region = cairo_region_copy (clip_region); status = fallback_region->status; if (unlikely (status)) goto FAIL; cairo_region_translate (fallback_region, -state.image_rect.x, -state.image_rect.y); clip_region = fallback_region; } status = _cairo_surface_composite (op, src, mask, &state.image->base, src_x, src_y, mask_x, mask_y, dst_x - state.image_rect.x, dst_y - state.image_rect.y, width, height, clip_region); FAIL: if (fallback_region != NULL) cairo_region_destroy (fallback_region); _fallback_fini (&state); return status; } cairo_status_t _cairo_surface_fallback_fill_rectangles (cairo_surface_t *surface, cairo_operator_t op, const cairo_color_t *color, cairo_rectangle_int_t *rects, int num_rects) { fallback_state_t state; cairo_rectangle_int_t *offset_rects = NULL; cairo_status_t status; int x1, y1, x2, y2; int i; assert (surface->snapshot_of == NULL); if (num_rects <= 0) return CAIRO_STATUS_SUCCESS; /* Compute the bounds of the rectangles, so that we know what area of the * destination surface to fetch */ x1 = rects[0].x; y1 = rects[0].y; x2 = rects[0].x + rects[0].width; y2 = rects[0].y + rects[0].height; for (i = 1; i < num_rects; i++) { if (rects[i].x < x1) x1 = rects[i].x; if (rects[i].y < y1) y1 = rects[i].y; if ((int) (rects[i].x + rects[i].width) > x2) x2 = rects[i].x + rects[i].width; if ((int) (rects[i].y + rects[i].height) > y2) y2 = rects[i].y + rects[i].height; } status = _fallback_init (&state, surface, x1, y1, x2 - x1, y2 - y1); if (unlikely (status)) { if (status == CAIRO_INT_STATUS_NOTHING_TO_DO) status = CAIRO_STATUS_SUCCESS; return status; } /* If the fetched image isn't at 0,0, we need to offset the rectangles */ if (state.image_rect.x != 0 || state.image_rect.y != 0) { offset_rects = _cairo_malloc_ab (num_rects, sizeof (cairo_rectangle_int_t)); if (unlikely (offset_rects == NULL)) { status = _cairo_error (CAIRO_STATUS_NO_MEMORY); goto DONE; } for (i = 0; i < num_rects; i++) { offset_rects[i].x = rects[i].x - state.image_rect.x; offset_rects[i].y = rects[i].y - state.image_rect.y; offset_rects[i].width = rects[i].width; offset_rects[i].height = rects[i].height; } rects = offset_rects; } status = _cairo_surface_fill_rectangles (&state.image->base, op, color, rects, num_rects); free (offset_rects); DONE: _fallback_fini (&state); return status; } cairo_status_t _cairo_surface_fallback_composite_trapezoids (cairo_operator_t op, const cairo_pattern_t *pattern, cairo_surface_t *dst, cairo_antialias_t antialias, int src_x, int src_y, int dst_x, int dst_y, unsigned int width, unsigned int height, cairo_trapezoid_t *traps, int num_traps, cairo_region_t *clip_region) { fallback_state_t state; cairo_region_t *fallback_region = NULL; cairo_trapezoid_t *offset_traps = NULL; cairo_status_t status; status = _fallback_init (&state, dst, dst_x, dst_y, width, height); if (unlikely (status)) { if (status == CAIRO_INT_STATUS_NOTHING_TO_DO) status = CAIRO_STATUS_SUCCESS; return status; } /* If the destination image isn't at 0,0, we need to offset the trapezoids */ if (state.image_rect.x != 0 || state.image_rect.y != 0) { offset_traps = _cairo_malloc_ab (num_traps, sizeof (cairo_trapezoid_t)); if (offset_traps == NULL) { status = _cairo_error (CAIRO_STATUS_NO_MEMORY); goto FAIL; } _cairo_trapezoid_array_translate_and_scale (offset_traps, traps, num_traps, - state.image_rect.x, - state.image_rect.y, 1.0, 1.0); traps = offset_traps; /* similarly we need to adjust the region */ if (clip_region != NULL) { fallback_region = cairo_region_copy (clip_region); status = fallback_region->status; if (unlikely (status)) goto FAIL; cairo_region_translate (fallback_region, -state.image_rect.x, -state.image_rect.y); clip_region = fallback_region; } } status = _cairo_surface_composite_trapezoids (op, pattern, &state.image->base, antialias, src_x, src_y, dst_x - state.image_rect.x, dst_y - state.image_rect.y, width, height, traps, num_traps, clip_region); if (offset_traps != NULL) free (offset_traps); FAIL: if (fallback_region != NULL) cairo_region_destroy (fallback_region); _fallback_fini (&state); return status; } cairo_status_t _cairo_surface_fallback_clone_similar (cairo_surface_t *surface, cairo_surface_t *src, cairo_content_t content, int src_x, int src_y, int width, int height, int *clone_offset_x, int *clone_offset_y, cairo_surface_t **clone_out) { cairo_surface_t *new_surface; cairo_surface_pattern_t pattern; cairo_status_t status; new_surface = _cairo_surface_create_similar_scratch (surface, src->content & content, width, height); if (new_surface == NULL) return CAIRO_INT_STATUS_UNSUPPORTED; if (unlikely (new_surface->status)) return new_surface->status; /* We have to copy these here, so that the coordinate spaces are correct */ new_surface->device_transform = src->device_transform; new_surface->device_transform_inverse = src->device_transform_inverse; _cairo_pattern_init_for_surface (&pattern, src); cairo_matrix_init_translate (&pattern.base.matrix, src_x, src_y); pattern.base.filter = CAIRO_FILTER_NEAREST; status = _cairo_surface_paint (new_surface, CAIRO_OPERATOR_SOURCE, &pattern.base, NULL); _cairo_pattern_fini (&pattern.base); if (unlikely (status)) { cairo_surface_destroy (new_surface); return status; } *clone_offset_x = src_x; *clone_offset_y = src_y; *clone_out = new_surface; return CAIRO_STATUS_SUCCESS; }