/* -*- 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. * Copyright © 2009 Chris Wilson * * 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., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, 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 * Kristian Høgsberg * Chris Wilson */ #include "cairoint.h" #include "cairo-clip-private.h" #include "cairo-error-private.h" #include "cairo-freed-pool-private.h" #include "cairo-gstate-private.h" #include "cairo-path-fixed-private.h" #include "cairo-composite-rectangles-private.h" #include "cairo-region-private.h" #if HAS_FREED_POOL static freed_pool_t clip_path_pool; #endif static cairo_clip_path_t * _cairo_clip_path_create (cairo_clip_t *clip) { cairo_clip_path_t *clip_path; clip_path = _freed_pool_get (&clip_path_pool); if (unlikely (clip_path == NULL)) { clip_path = malloc (sizeof (cairo_clip_path_t)); if (unlikely (clip_path == NULL)) return NULL; } CAIRO_REFERENCE_COUNT_INIT (&clip_path->ref_count, 1); clip_path->flags = 0; clip_path->region = NULL; clip_path->surface = NULL; clip_path->prev = clip->path; clip->path = clip_path; return clip_path; } static cairo_clip_path_t * _cairo_clip_path_reference (cairo_clip_path_t *clip_path) { assert (CAIRO_REFERENCE_COUNT_HAS_REFERENCE (&clip_path->ref_count)); _cairo_reference_count_inc (&clip_path->ref_count); return clip_path; } static void _cairo_clip_path_destroy (cairo_clip_path_t *clip_path) { assert (CAIRO_REFERENCE_COUNT_HAS_REFERENCE (&clip_path->ref_count)); if (! _cairo_reference_count_dec_and_test (&clip_path->ref_count)) return; _cairo_path_fixed_fini (&clip_path->path); if (clip_path->region != NULL) cairo_region_destroy (clip_path->region); if (clip_path->surface != NULL) cairo_surface_destroy (clip_path->surface); if (clip_path->prev != NULL) _cairo_clip_path_destroy (clip_path->prev); _freed_pool_put (&clip_path_pool, clip_path); } void _cairo_clip_init (cairo_clip_t *clip) { clip->all_clipped = FALSE; clip->path = NULL; } static void _cairo_clip_set_all_clipped (cairo_clip_t *clip) { clip->all_clipped = TRUE; if (clip->path != NULL) { _cairo_clip_path_destroy (clip->path); clip->path = NULL; } } static cairo_status_t _cairo_clip_intersect_rectangle (cairo_clip_t *clip, const cairo_rectangle_int_t *rect) { cairo_clip_path_t *clip_path; cairo_status_t status; if (clip->path != NULL) { if (rect->x <= clip->path->extents.x && rect->y <= clip->path->extents.y && rect->x + rect->width >= clip->path->extents.x + clip->path->extents.width && rect->y + rect->height >= clip->path->extents.y + clip->path->extents.height) { return CAIRO_STATUS_SUCCESS; } } clip_path = _cairo_clip_path_create (clip); if (unlikely (clip_path == NULL)) return _cairo_error (CAIRO_STATUS_NO_MEMORY); _cairo_path_fixed_init (&clip_path->path); status = _cairo_path_fixed_move_to (&clip_path->path, _cairo_fixed_from_int (rect->x), _cairo_fixed_from_int (rect->y)); assert (status == CAIRO_STATUS_SUCCESS); status = _cairo_path_fixed_rel_line_to (&clip_path->path, _cairo_fixed_from_int (rect->width), _cairo_fixed_from_int (0)); assert (status == CAIRO_STATUS_SUCCESS); status = _cairo_path_fixed_rel_line_to (&clip_path->path, _cairo_fixed_from_int (0), _cairo_fixed_from_int (rect->height)); assert (status == CAIRO_STATUS_SUCCESS); status = _cairo_path_fixed_rel_line_to (&clip_path->path, _cairo_fixed_from_int (-rect->width), _cairo_fixed_from_int (0)); assert (status == CAIRO_STATUS_SUCCESS); status = _cairo_path_fixed_close_path (&clip_path->path); assert (status == CAIRO_STATUS_SUCCESS); clip_path->fill_rule = CAIRO_FILL_RULE_WINDING; clip_path->tolerance = 1; clip_path->antialias = CAIRO_ANTIALIAS_DEFAULT; clip_path->flags |= CAIRO_CLIP_PATH_IS_BOX; clip_path->extents = *rect; if (clip_path->prev != NULL) { if (! _cairo_rectangle_intersect (&clip_path->extents, &clip_path->prev->extents)) { _cairo_clip_set_all_clipped (clip); } } /* could preallocate the region if it proves worthwhile */ return CAIRO_STATUS_SUCCESS; } cairo_clip_t * _cairo_clip_init_copy (cairo_clip_t *clip, cairo_clip_t *other) { if (other != NULL) { clip->all_clipped = other->all_clipped; if (other->path == NULL) { clip->path = NULL; if (! clip->all_clipped) clip = NULL; } else { clip->path = _cairo_clip_path_reference (other->path); } } else { _cairo_clip_init (clip); clip = NULL; } return clip; } void _cairo_clip_reset (cairo_clip_t *clip) { clip->all_clipped = FALSE; if (clip->path != NULL) { _cairo_clip_path_destroy (clip->path); clip->path = NULL; } } static cairo_status_t _cairo_clip_intersect_path (cairo_clip_t *clip, const cairo_path_fixed_t *path, cairo_fill_rule_t fill_rule, double tolerance, cairo_antialias_t antialias) { cairo_clip_path_t *clip_path; cairo_status_t status; cairo_rectangle_int_t extents; cairo_box_t box; cairo_bool_t is_box = FALSE; if (clip->path != NULL) { if (clip->path->fill_rule == fill_rule && (_cairo_path_fixed_fill_is_rectilinear (path) || tolerance == clip->path->tolerance) && antialias == clip->path->antialias && _cairo_path_fixed_equal (&clip->path->path, path)) { return CAIRO_STATUS_SUCCESS; } } _cairo_path_fixed_approximate_clip_extents (path, &extents); if (extents.width == 0 || extents.height == 0) { _cairo_clip_set_all_clipped (clip); return CAIRO_STATUS_SUCCESS; } is_box = _cairo_path_fixed_is_box (path, &box); if (clip->path != NULL) { if (! _cairo_rectangle_intersect (&extents, &clip->path->extents)) { _cairo_clip_set_all_clipped (clip); return CAIRO_STATUS_SUCCESS; } /* does this clip wholly subsume the others? */ if (is_box && box.p1.x <= _cairo_fixed_from_int (clip->path->extents.x) && box.p2.x >= _cairo_fixed_from_int (clip->path->extents.x + clip->path->extents.width) && box.p1.y <= _cairo_fixed_from_int (clip->path->extents.y) && box.p2.y >= _cairo_fixed_from_int (clip->path->extents.y + clip->path->extents.height)) { return CAIRO_STATUS_SUCCESS; } } clip_path = _cairo_clip_path_create (clip); if (unlikely (clip_path == NULL)) return _cairo_error (CAIRO_STATUS_NO_MEMORY); status = _cairo_path_fixed_init_copy (&clip_path->path, path); if (unlikely (status)) { clip->path = clip->path->prev; _cairo_clip_path_destroy (clip_path); return status; } clip_path->extents = extents; clip_path->fill_rule = fill_rule; clip_path->tolerance = tolerance; clip_path->antialias = antialias; if (is_box) clip_path->flags |= CAIRO_CLIP_PATH_IS_BOX; return CAIRO_STATUS_SUCCESS; } cairo_bool_t _cairo_clip_equal (const cairo_clip_t *clip_a, const cairo_clip_t *clip_b) { const cairo_clip_path_t *clip_path_a, *clip_path_b; clip_path_a = clip_a->path; clip_path_b = clip_b->path; while (clip_path_a && clip_path_b) { if (clip_path_a == clip_path_b) return TRUE; if (clip_path_a->fill_rule != clip_path_b->fill_rule) return FALSE; if (clip_path_a->tolerance != clip_path_b->tolerance) return FALSE; if (clip_path_a->antialias != clip_path_b->antialias) return FALSE; if (! _cairo_path_fixed_equal (&clip_path_a->path, &clip_path_b->path)) return FALSE; clip_path_a = clip_path_a->prev; clip_path_b = clip_path_b->prev; } return clip_path_a == clip_path_b; /* ie both NULL */ } cairo_status_t _cairo_clip_clip (cairo_clip_t *clip, const cairo_path_fixed_t *path, cairo_fill_rule_t fill_rule, double tolerance, cairo_antialias_t antialias) { if (clip->all_clipped) return CAIRO_STATUS_SUCCESS; /* catch the empty clip path */ if (_cairo_path_fixed_fill_is_empty (path)) { _cairo_clip_set_all_clipped (clip); return CAIRO_STATUS_SUCCESS; } return _cairo_clip_intersect_path (clip, path, fill_rule, tolerance, antialias); } cairo_status_t _cairo_clip_rectangle (cairo_clip_t *clip, const cairo_rectangle_int_t *rectangle) { if (clip->all_clipped) return CAIRO_STATUS_SUCCESS; if (rectangle->width == 0 || rectangle->height == 0) { _cairo_clip_set_all_clipped (clip); return CAIRO_STATUS_SUCCESS; } /* if a smaller clip has already been set, ignore the new path */ if (clip->path != NULL) { if (rectangle->x <= clip->path->extents.x && rectangle->y <= clip->path->extents.y && rectangle->x + rectangle->width >= clip->path->extents.x + clip->path->extents.width && rectangle->y + rectangle->height >= clip->path->extents.y + clip->path->extents.height) { return CAIRO_STATUS_SUCCESS; } } return _cairo_clip_intersect_rectangle (clip, rectangle); } static cairo_status_t _cairo_clip_path_reapply_clip_path_transform (cairo_clip_t *clip, cairo_clip_path_t *other_path, const cairo_matrix_t *matrix) { cairo_status_t status; cairo_clip_path_t *clip_path; cairo_bool_t is_empty; if (other_path->prev != NULL) { status = _cairo_clip_path_reapply_clip_path_transform (clip, other_path->prev, matrix); if (unlikely (status)) return status; } clip_path = _cairo_clip_path_create (clip); if (unlikely (clip_path == NULL)) return _cairo_error (CAIRO_STATUS_NO_MEMORY); status = _cairo_path_fixed_init_copy (&clip_path->path, &other_path->path); if (unlikely (status)) { clip->path = clip->path->prev; _cairo_clip_path_destroy (clip_path); return status; } _cairo_path_fixed_transform (&clip_path->path, matrix); _cairo_path_fixed_approximate_clip_extents (&clip_path->path, &clip_path->extents); if (clip_path->prev != NULL) { is_empty = _cairo_rectangle_intersect (&clip_path->extents, &clip_path->prev->extents); } clip_path->fill_rule = other_path->fill_rule; clip_path->tolerance = other_path->tolerance; clip_path->antialias = other_path->antialias; return CAIRO_STATUS_SUCCESS; } static cairo_status_t _cairo_clip_path_reapply_clip_path_translate (cairo_clip_t *clip, cairo_clip_path_t *other_path, int tx, int ty) { cairo_status_t status; cairo_clip_path_t *clip_path; if (other_path->prev != NULL) { status = _cairo_clip_path_reapply_clip_path_translate (clip, other_path->prev, tx, ty); if (unlikely (status)) return status; } clip_path = _cairo_clip_path_create (clip); if (unlikely (clip_path == NULL)) return _cairo_error (CAIRO_STATUS_NO_MEMORY); status = _cairo_path_fixed_init_copy (&clip_path->path, &other_path->path); if (unlikely (status)) { clip->path = clip->path->prev; _cairo_clip_path_destroy (clip_path); return status; } _cairo_path_fixed_translate (&clip_path->path, _cairo_fixed_from_int (tx), _cairo_fixed_from_int (ty)); clip_path->fill_rule = other_path->fill_rule; clip_path->tolerance = other_path->tolerance; clip_path->antialias = other_path->antialias; clip_path->flags = other_path->flags; if (other_path->region != NULL) { clip_path->region = cairo_region_copy (other_path->region); status = clip_path->region->status; if (unlikely (status)) { clip->path = clip->path->prev; _cairo_clip_path_destroy (clip_path); return status; } cairo_region_translate (clip_path->region, tx, ty); } clip_path->surface = cairo_surface_reference (other_path->surface); clip_path->extents = other_path->extents; clip_path->extents.x += tx; clip_path->extents.y += ty; return CAIRO_STATUS_SUCCESS; } cairo_status_t _cairo_clip_init_copy_transformed (cairo_clip_t *clip, cairo_clip_t *other, const cairo_matrix_t *matrix) { cairo_status_t status = CAIRO_STATUS_SUCCESS; int tx, ty; if (other == NULL) { _cairo_clip_init (clip); return CAIRO_STATUS_SUCCESS; } if (other->all_clipped) { _cairo_clip_init (clip); clip->all_clipped = TRUE; return CAIRO_STATUS_SUCCESS; } if (_cairo_matrix_is_identity (matrix)) { _cairo_clip_init_copy (clip, other); return CAIRO_STATUS_SUCCESS; } if (other->path != NULL) { _cairo_clip_init (clip); /* if we only need to translate, so we can reuse the caches... */ /* XXX we still loose the benefit of constructs when the copy is * deleted though. Indirect clip_paths? */ if (_cairo_matrix_is_integer_translation (matrix, &tx, &ty)) { status = _cairo_clip_path_reapply_clip_path_translate (clip, other->path, tx, ty); } else { status = _cairo_clip_path_reapply_clip_path_transform (clip, other->path, matrix); if (clip->path->extents.width == 0 && clip->path->extents.height == 0) { _cairo_clip_set_all_clipped (clip); } } } return status; } static cairo_status_t _cairo_clip_apply_clip_path (cairo_clip_t *clip, const cairo_clip_path_t *path) { cairo_status_t status; if (path->prev != NULL) status = _cairo_clip_apply_clip_path (clip, path->prev); return _cairo_clip_intersect_path (clip, &path->path, path->fill_rule, path->tolerance, path->antialias); } cairo_status_t _cairo_clip_apply_clip (cairo_clip_t *clip, const cairo_clip_t *other) { cairo_status_t status; if (clip->all_clipped) return CAIRO_STATUS_SUCCESS; if (other->all_clipped) { _cairo_clip_set_all_clipped (clip); return CAIRO_STATUS_SUCCESS; } status = CAIRO_STATUS_SUCCESS; if (other->path != NULL) status = _cairo_clip_apply_clip_path (clip, other->path); return status; } static inline cairo_bool_t _clip_paths_are_rectilinear (cairo_clip_path_t *clip_path) { while (clip_path != NULL) { if (! _cairo_path_fixed_fill_is_rectilinear (&clip_path->path)) return FALSE; clip_path = clip_path->prev; } return TRUE; } static cairo_int_status_t _cairo_clip_path_to_region_geometric (cairo_clip_path_t *clip_path) { cairo_traps_t traps; cairo_box_t stack_boxes[CAIRO_STACK_ARRAY_LENGTH (cairo_box_t)]; cairo_box_t *boxes = stack_boxes; cairo_status_t status; int n; /* If we have nothing to intersect with this path, then it cannot * magically be reduced into a region. */ if (clip_path->prev == NULL) goto UNSUPPORTED; /* Start simple... Intersect some boxes with an arbitrary path. */ if (! _cairo_path_fixed_fill_is_rectilinear (&clip_path->path)) goto UNSUPPORTED; if (clip_path->prev->prev != NULL) goto UNSUPPORTED; _cairo_traps_init (&traps); _cairo_box_from_rectangle (&boxes[0], &clip_path->extents); _cairo_traps_limit (&traps, boxes, 1); status = _cairo_path_fixed_fill_rectilinear_to_traps (&clip_path->path, clip_path->fill_rule, &traps); if (unlikely (_cairo_status_is_error (status))) return status; if (status == CAIRO_INT_STATUS_UNSUPPORTED) goto UNSUPPORTED; if (traps.num_traps > ARRAY_LENGTH (stack_boxes)) { boxes = _cairo_malloc_ab (traps.num_traps, sizeof (cairo_box_t)); if (unlikely (boxes == NULL)) return _cairo_error (CAIRO_STATUS_NO_MEMORY); } for (n = 0; n < traps.num_traps; n++) { boxes[n].p1.x = traps.traps[n].left.p1.x; boxes[n].p1.y = traps.traps[n].top; boxes[n].p2.x = traps.traps[n].right.p1.x; boxes[n].p2.y = traps.traps[n].bottom; } _cairo_traps_clear (&traps); _cairo_traps_limit (&traps, boxes, n); status = _cairo_path_fixed_fill_to_traps (&clip_path->prev->path, clip_path->prev->fill_rule, clip_path->prev->tolerance, &traps); if (boxes != stack_boxes) free (boxes); if (unlikely (status)) return status; status = _cairo_traps_extract_region (&traps, &clip_path->region); _cairo_traps_fini (&traps); if (status == CAIRO_INT_STATUS_UNSUPPORTED) goto UNSUPPORTED; if (unlikely (status)) return status; clip_path->flags |= CAIRO_CLIP_PATH_HAS_REGION; return CAIRO_STATUS_SUCCESS; UNSUPPORTED: clip_path->flags |= CAIRO_CLIP_PATH_REGION_IS_UNSUPPORTED; return CAIRO_INT_STATUS_UNSUPPORTED; } static cairo_int_status_t _cairo_clip_path_to_region (cairo_clip_path_t *clip_path) { cairo_int_status_t status; cairo_region_t *prev = NULL; if (clip_path->flags & (CAIRO_CLIP_PATH_HAS_REGION | CAIRO_CLIP_PATH_REGION_IS_UNSUPPORTED)) { return clip_path->flags & CAIRO_CLIP_PATH_REGION_IS_UNSUPPORTED ? CAIRO_INT_STATUS_UNSUPPORTED : CAIRO_STATUS_SUCCESS; } if (! _cairo_path_fixed_fill_maybe_region (&clip_path->path)) return _cairo_clip_path_to_region_geometric (clip_path); /* first retrieve the region for our antecedents */ if (clip_path->prev != NULL) { status = _cairo_clip_path_to_region (clip_path->prev); if (status) { if (status == CAIRO_INT_STATUS_UNSUPPORTED) return _cairo_clip_path_to_region_geometric (clip_path); return status; } prev = clip_path->prev->region; } /* now extract the region for ourselves */ clip_path->region = _cairo_path_fixed_fill_rectilinear_to_region (&clip_path->path, clip_path->fill_rule, &clip_path->extents); assert (clip_path->region != NULL); status = clip_path->region->status; if (unlikely (status)) return status; if (prev != NULL) { status = cairo_region_intersect (clip_path->region, prev); if (unlikely (status)) return status; } clip_path->flags |= CAIRO_CLIP_PATH_HAS_REGION; return CAIRO_STATUS_SUCCESS; } static inline int pot (int v) { v--; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; v++; return v; } /* XXX there is likely a faster method! ;-) */ static cairo_status_t _region_clip_to_boxes (const cairo_region_t *region, cairo_box_t **boxes, int *num_boxes, int *size_boxes) { cairo_traps_t traps; cairo_status_t status; int n, num_rects; _cairo_traps_init (&traps); _cairo_traps_limit (&traps, *boxes, *num_boxes); traps.is_rectilinear = TRUE; traps.is_rectangular = TRUE; num_rects = cairo_region_num_rectangles (region); for (n = 0; n < num_rects; n++) { cairo_rectangle_int_t rect; cairo_point_t p1, p2; cairo_region_get_rectangle (region, n, &rect); p1.x = _cairo_fixed_from_int (rect.x); p1.y = _cairo_fixed_from_int (rect.y); p2.x = _cairo_fixed_from_int (rect.x + rect.width); p2.y = _cairo_fixed_from_int (rect.y + rect.height); status = _cairo_traps_tessellate_rectangle (&traps, &p1, &p2); if (unlikely (status)) goto CLEANUP; } status = _cairo_bentley_ottmann_tessellate_rectangular_traps (&traps, CAIRO_FILL_RULE_WINDING); if (unlikely (status)) goto CLEANUP; n = *size_boxes; if (n < 0) n = -n; if (traps.num_traps > n) { cairo_box_t *new_boxes; new_boxes = _cairo_malloc_ab (traps.num_traps, sizeof (cairo_box_t)); if (unlikely (new_boxes == NULL)) { status = _cairo_error (CAIRO_STATUS_NO_MEMORY); goto CLEANUP; } if (*size_boxes > 0) free (*boxes); *boxes = new_boxes; *size_boxes = traps.num_traps; } for (n = 0; n < traps.num_traps; n++) { (*boxes)[n].p1.x = traps.traps[n].left.p1.x; (*boxes)[n].p1.y = traps.traps[n].top; (*boxes)[n].p2.x = traps.traps[n].right.p1.x; (*boxes)[n].p2.y = traps.traps[n].bottom; } *num_boxes = n; CLEANUP: _cairo_traps_fini (&traps); return status; } static cairo_status_t _rectilinear_clip_to_boxes (const cairo_path_fixed_t *path, cairo_fill_rule_t fill_rule, cairo_box_t **boxes, int *num_boxes, int *size_boxes) { cairo_polygon_t polygon; cairo_traps_t traps; cairo_status_t status; _cairo_traps_init (&traps); _cairo_traps_limit (&traps, *boxes, *num_boxes); _cairo_polygon_init (&polygon, *boxes, *num_boxes); status = _cairo_path_fixed_fill_rectilinear_to_traps (path, fill_rule, &traps); if (unlikely (_cairo_status_is_error (status))) goto CLEANUP; if (status == CAIRO_STATUS_SUCCESS) goto BOXES; /* tolerance will be ignored as the path is rectilinear */ status = _cairo_path_fixed_fill_to_polygon (path, 0., &polygon); if (unlikely (status)) goto CLEANUP; if (polygon.num_edges == 0) { *num_boxes = 0; } else { status = _cairo_bentley_ottmann_tessellate_rectilinear_polygon (&traps, &polygon, fill_rule); if (likely (status == CAIRO_STATUS_SUCCESS)) { int i; BOXES: i = *size_boxes; if (i < 0) i = -i; if (traps.num_traps > i) { cairo_box_t *new_boxes; int new_size; new_size = pot (traps.num_traps); new_boxes = _cairo_malloc_ab (new_size, sizeof (cairo_box_t)); if (unlikely (new_boxes == NULL)) { status = _cairo_error (CAIRO_STATUS_NO_MEMORY); goto CLEANUP; } if (*size_boxes > 0) free (*boxes); *boxes = new_boxes; *size_boxes = new_size; } for (i = 0; i < traps.num_traps; i++) { (*boxes)[i].p1.x = traps.traps[i].left.p1.x; (*boxes)[i].p1.y = traps.traps[i].top; (*boxes)[i].p2.x = traps.traps[i].right.p1.x; (*boxes)[i].p2.y = traps.traps[i].bottom; } *num_boxes = i; } } CLEANUP: _cairo_polygon_fini (&polygon); _cairo_traps_fini (&traps); return status; } static cairo_int_status_t _cairo_clip_path_to_boxes (cairo_clip_path_t *clip_path, cairo_box_t **boxes, int *count) { int size = -*count; int num_boxes = 0; cairo_status_t status; if (clip_path->region != NULL) { int num_rects, n; num_rects = cairo_region_num_rectangles (clip_path->region); if (num_rects > -size) { cairo_box_t *new_boxes; new_boxes = _cairo_malloc_ab (num_rects, sizeof (cairo_box_t)); if (unlikely (new_boxes == NULL)) return _cairo_error (CAIRO_STATUS_NO_MEMORY); *boxes = new_boxes; } for (n = 0; n < num_rects; n++) { cairo_rectangle_int_t rect; cairo_region_get_rectangle (clip_path->region, n, &rect); (*boxes)[n].p1.x = _cairo_fixed_from_int (rect.x); (*boxes)[n].p1.y = _cairo_fixed_from_int (rect.y); (*boxes)[n].p2.x = _cairo_fixed_from_int (rect.x + rect.width); (*boxes)[n].p2.y = _cairo_fixed_from_int (rect.y + rect.height); } *count = num_rects; return CAIRO_STATUS_SUCCESS; } /* keep it simple at first */ if (! _clip_paths_are_rectilinear (clip_path)) return CAIRO_INT_STATUS_UNSUPPORTED; assert (-size >= 1); if (_cairo_path_fixed_is_box (&clip_path->path, *boxes)) { num_boxes = 1; } else { status = _rectilinear_clip_to_boxes (&clip_path->path, clip_path->fill_rule, boxes, &num_boxes, &size); if (unlikely (status)) return status; } while (num_boxes > 0 && (clip_path = clip_path->prev) != NULL) { cairo_box_t box; if (clip_path->region != NULL) { status = _region_clip_to_boxes (clip_path->region, boxes, &num_boxes, &size); if (unlikely (status)) return status; break; } else if (_cairo_path_fixed_is_box (&clip_path->path, &box)) { int i, j; for (i = j = 0; i < num_boxes; i++) { if (j != i) (*boxes)[j] = (*boxes)[i]; if (box.p1.x > (*boxes)[j].p1.x) (*boxes)[j].p1.x = box.p1.x; if (box.p2.x < (*boxes)[j].p2.x) (*boxes)[j].p2.x = box.p2.x; if (box.p1.y > (*boxes)[j].p1.y) (*boxes)[j].p1.y = box.p1.y; if (box.p2.y < (*boxes)[j].p2.y) (*boxes)[j].p2.y = box.p2.y; j += (*boxes)[j].p2.x > (*boxes)[j].p1.x && (*boxes)[j].p2.y > (*boxes)[j].p1.y; } num_boxes = j; } else { status = _rectilinear_clip_to_boxes (&clip_path->path, clip_path->fill_rule, boxes, &num_boxes, &size); if (unlikely (status)) return status; } } *count = num_boxes; return CAIRO_STATUS_SUCCESS; } static cairo_surface_t * _cairo_clip_path_get_surface (cairo_clip_path_t *clip_path, cairo_surface_t *target, int *tx, int *ty) { const cairo_rectangle_int_t *clip_extents = &clip_path->extents; cairo_bool_t need_translate; cairo_surface_t *surface; cairo_clip_path_t *prev; cairo_status_t status; while (clip_path->prev != NULL && clip_path->flags & CAIRO_CLIP_PATH_IS_BOX && _cairo_path_fixed_fill_maybe_region (&clip_path->path)) { clip_path = clip_path->prev; } clip_extents = &clip_path->extents; if (clip_path->surface != NULL && clip_path->surface->backend == target->backend) { *tx = clip_extents->x; *ty = clip_extents->y; return clip_path->surface; } surface = _cairo_surface_create_similar_scratch (target, CAIRO_CONTENT_ALPHA, clip_extents->width, clip_extents->height); if (surface == NULL) { surface = cairo_image_surface_create (CAIRO_FORMAT_A8, clip_extents->width, clip_extents->height); } if (unlikely (surface->status)) return surface; need_translate = clip_extents->x | clip_extents->y; if (clip_path->flags & CAIRO_CLIP_PATH_IS_BOX && _cairo_path_fixed_fill_maybe_region (&clip_path->path)) { status = _cairo_surface_paint (surface, CAIRO_OPERATOR_SOURCE, &_cairo_pattern_white.base, NULL); if (unlikely (status)) goto BAIL; } else { status = _cairo_surface_paint (surface, CAIRO_OPERATOR_CLEAR, &_cairo_pattern_clear.base, NULL); if (unlikely (status)) goto BAIL; if (need_translate) { _cairo_path_fixed_translate (&clip_path->path, _cairo_fixed_from_int (-clip_extents->x), _cairo_fixed_from_int (-clip_extents->y)); } status = _cairo_surface_fill (surface, CAIRO_OPERATOR_ADD, &_cairo_pattern_white.base, &clip_path->path, clip_path->fill_rule, clip_path->tolerance, clip_path->antialias, NULL); if (need_translate) { _cairo_path_fixed_translate (&clip_path->path, _cairo_fixed_from_int (clip_extents->x), _cairo_fixed_from_int (clip_extents->y)); } if (unlikely (status)) goto BAIL; } prev = clip_path->prev; while (prev != NULL) { if (prev->flags & CAIRO_CLIP_PATH_IS_BOX && _cairo_path_fixed_fill_maybe_region (&prev->path)) { /* a simple box only affects the extents */ } else if (_cairo_path_fixed_fill_is_rectilinear (&prev->path) || prev->surface == NULL || prev->surface->backend != target->backend) { if (need_translate) { _cairo_path_fixed_translate (&prev->path, _cairo_fixed_from_int (-clip_extents->x), _cairo_fixed_from_int (-clip_extents->y)); } status = _cairo_surface_fill (surface, CAIRO_OPERATOR_IN, &_cairo_pattern_white.base, &prev->path, prev->fill_rule, prev->tolerance, prev->antialias, NULL); if (need_translate) { _cairo_path_fixed_translate (&prev->path, _cairo_fixed_from_int (clip_extents->x), _cairo_fixed_from_int (clip_extents->y)); } if (unlikely (status)) goto BAIL; } else { cairo_surface_pattern_t pattern; cairo_surface_t *prev_surface; int prev_tx, prev_ty; prev_surface = _cairo_clip_path_get_surface (prev, target, &prev_tx, &prev_ty); status = prev_surface->status; if (unlikely (status)) goto BAIL; _cairo_pattern_init_for_surface (&pattern, prev_surface); pattern.base.filter = CAIRO_FILTER_NEAREST; cairo_matrix_init_translate (&pattern.base.matrix, clip_extents->x - prev_tx, clip_extents->y - prev_ty); status = _cairo_surface_paint (surface, CAIRO_OPERATOR_IN, &pattern.base, NULL); _cairo_pattern_fini (&pattern.base); if (unlikely (status)) goto BAIL; break; } prev = prev->prev; } *tx = clip_extents->x; *ty = clip_extents->y; cairo_surface_destroy (clip_path->surface); return clip_path->surface = surface; BAIL: cairo_surface_destroy (surface); return _cairo_surface_create_in_error (status); } cairo_bool_t _cairo_clip_contains_rectangle (cairo_clip_t *clip, const cairo_rectangle_int_t *rect) { cairo_clip_path_t *clip_path; /* clip == NULL means no clip, so the clip contains everything */ if (clip == NULL) return TRUE; 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 ((clip_path->flags & CAIRO_CLIP_PATH_IS_BOX) == 0) return FALSE; 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; } cairo_bool_t _cairo_clip_contains_extents (cairo_clip_t *clip, const cairo_composite_rectangles_t *extents) { const cairo_rectangle_int_t *rect; rect = extents->is_bounded ? &extents->bounded : &extents->unbounded; return _cairo_clip_contains_rectangle (clip, rect); } void _cairo_debug_print_clip (FILE *stream, cairo_clip_t *clip) { cairo_clip_path_t *clip_path; if (clip == NULL) { fprintf (stream, "no clip\n"); return; } if (clip->all_clipped) { fprintf (stream, "clip: all-clipped\n"); return; } if (clip->path == NULL) { fprintf (stream, "clip: empty\n"); return; } fprintf (stream, "clip:\n"); clip_path = clip->path; do { fprintf (stream, "path: has region? %s, has surface? %s, aa=%d, tolerance=%f, rule=%d: ", clip_path->region == NULL ? "no" : "yes", clip_path->surface == NULL ? "no" : "yes", clip_path->antialias, clip_path->tolerance, clip_path->fill_rule); _cairo_debug_print_path (stream, &clip_path->path); fprintf (stream, "\n"); } while ((clip_path = clip_path->prev) != NULL); } cairo_surface_t * _cairo_clip_get_surface (cairo_clip_t *clip, cairo_surface_t *target, int *tx, int *ty) { /* XXX is_clear -> all_clipped */ assert (clip->path != NULL); return _cairo_clip_path_get_surface (clip->path, target, tx, ty); } cairo_status_t _cairo_clip_combine_with_surface (cairo_clip_t *clip, cairo_surface_t *dst, int dst_x, int dst_y) { cairo_clip_path_t *clip_path = clip->path; cairo_bool_t need_translate; cairo_status_t status; assert (clip_path != NULL); need_translate = dst_x | dst_y; do { if (clip_path->surface != NULL && clip_path->surface->backend == dst->backend) { cairo_surface_pattern_t pattern; _cairo_pattern_init_for_surface (&pattern, clip_path->surface); cairo_matrix_init_translate (&pattern.base.matrix, dst_x - clip_path->extents.x, dst_y - clip_path->extents.y); pattern.base.filter = CAIRO_FILTER_NEAREST; status = _cairo_surface_paint (dst, CAIRO_OPERATOR_IN, &pattern.base, NULL); _cairo_pattern_fini (&pattern.base); return status; } if (clip_path->flags & CAIRO_CLIP_PATH_IS_BOX && _cairo_path_fixed_fill_maybe_region (&clip_path->path)) { continue; } if (need_translate) { _cairo_path_fixed_translate (&clip_path->path, _cairo_fixed_from_int (-dst_x), _cairo_fixed_from_int (-dst_y)); } status = _cairo_surface_fill (dst, CAIRO_OPERATOR_IN, &_cairo_pattern_white.base, &clip_path->path, clip_path->fill_rule, clip_path->tolerance, clip_path->antialias, NULL); if (need_translate) { _cairo_path_fixed_translate (&clip_path->path, _cairo_fixed_from_int (dst_x), _cairo_fixed_from_int (dst_y)); } if (unlikely (status)) return status; } while ((clip_path = clip_path->prev) != NULL); return CAIRO_STATUS_SUCCESS; } static const cairo_rectangle_int_t _cairo_empty_rectangle_int = { 0, 0, 0, 0 }; const cairo_rectangle_int_t * _cairo_clip_get_extents (const cairo_clip_t *clip) { if (clip->all_clipped) return &_cairo_empty_rectangle_int; if (clip->path == NULL) return NULL; return &clip->path->extents; } void _cairo_clip_drop_cache (cairo_clip_t *clip) { cairo_clip_path_t *clip_path; if (clip->path == NULL) return; clip_path = clip->path; do { if (clip_path->region != NULL) { cairo_region_destroy (clip_path->region); clip_path->region = NULL; } if (clip_path->surface != NULL) { cairo_surface_destroy (clip_path->surface); clip_path->surface = NULL; } clip_path->flags &= ~CAIRO_CLIP_PATH_HAS_REGION; } while ((clip_path = clip_path->prev) != NULL); } const cairo_rectangle_list_t _cairo_rectangles_nil = { CAIRO_STATUS_NO_MEMORY, NULL, 0 }; static const cairo_rectangle_list_t _cairo_rectangles_not_representable = { CAIRO_STATUS_CLIP_NOT_REPRESENTABLE, NULL, 0 }; static cairo_bool_t _cairo_clip_int_rect_to_user (cairo_gstate_t *gstate, cairo_rectangle_int_t *clip_rect, cairo_rectangle_t *user_rect) { cairo_bool_t is_tight; double x1 = clip_rect->x; double y1 = clip_rect->y; double x2 = clip_rect->x + (int) clip_rect->width; double y2 = clip_rect->y + (int) clip_rect->height; _cairo_gstate_backend_to_user_rectangle (gstate, &x1, &y1, &x2, &y2, &is_tight); user_rect->x = x1; user_rect->y = y1; user_rect->width = x2 - x1; user_rect->height = y2 - y1; return is_tight; } cairo_int_status_t _cairo_clip_get_region (cairo_clip_t *clip, cairo_region_t **region) { cairo_int_status_t status; if (clip->all_clipped) goto CLIPPED; assert (clip->path != NULL); status = _cairo_clip_path_to_region (clip->path); if (status) return status; if (cairo_region_is_empty (clip->path->region)) { _cairo_clip_set_all_clipped (clip); goto CLIPPED; } if (region) *region = clip->path->region; return CAIRO_STATUS_SUCCESS; CLIPPED: if (region) *region = NULL; return CAIRO_INT_STATUS_NOTHING_TO_DO; } cairo_int_status_t _cairo_clip_get_boxes (cairo_clip_t *clip, cairo_box_t **boxes, int *count) { cairo_int_status_t status; if (clip->all_clipped) return CAIRO_INT_STATUS_NOTHING_TO_DO; assert (clip->path != NULL); status = _cairo_clip_path_to_boxes (clip->path, boxes, count); if (status) return status; if (*count == 0) { _cairo_clip_set_all_clipped (clip); return CAIRO_INT_STATUS_NOTHING_TO_DO; } return CAIRO_STATUS_SUCCESS; } 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 void intersect_with_boxes (cairo_composite_rectangles_t *extents, cairo_box_t *boxes, int num_boxes) { cairo_rectangle_int_t rect; cairo_box_t box; cairo_bool_t is_empty; box.p1.x = box.p1.y = INT_MIN; box.p2.x = box.p2.y = INT_MAX; while (num_boxes--) { if (boxes->p1.x < box.p1.x) box.p1.x = boxes->p1.x; if (boxes->p1.y < box.p1.y) box.p1.y = boxes->p1.y; if (boxes->p2.x > box.p2.x) box.p2.x = boxes->p2.x; if (boxes->p2.y > box.p2.y) box.p2.y = boxes->p2.y; } _cairo_box_round_to_rectangle (&box, &rect); is_empty = _cairo_rectangle_intersect (&extents->bounded, &rect); is_empty = _cairo_rectangle_intersect (&extents->unbounded, &rect); } cairo_status_t _cairo_clip_to_boxes (cairo_clip_t **clip, 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: intersect_with_boxes (extents, *boxes, *num_boxes); if (rect->width == 0 || rect->height == 0 || 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_rectangle_list_t * _cairo_rectangle_list_create_in_error (cairo_status_t status) { cairo_rectangle_list_t *list; if (status == CAIRO_STATUS_NO_MEMORY) return (cairo_rectangle_list_t*) &_cairo_rectangles_nil; if (status == CAIRO_STATUS_CLIP_NOT_REPRESENTABLE) return (cairo_rectangle_list_t*) &_cairo_rectangles_not_representable; list = malloc (sizeof (*list)); if (unlikely (list == NULL)) { _cairo_error_throw (status); return (cairo_rectangle_list_t*) &_cairo_rectangles_nil; } list->status = status; list->rectangles = NULL; list->num_rectangles = 0; return list; } cairo_rectangle_list_t * _cairo_clip_copy_rectangle_list (cairo_clip_t *clip, cairo_gstate_t *gstate) { #define ERROR_LIST(S) _cairo_rectangle_list_create_in_error (_cairo_error (S)) cairo_rectangle_list_t *list; cairo_rectangle_t *rectangles = NULL; cairo_region_t *region = NULL; cairo_int_status_t status; int n_rects = 0; int i; if (clip->all_clipped) goto DONE; if (!clip->path) return ERROR_LIST (CAIRO_STATUS_CLIP_NOT_REPRESENTABLE); status = _cairo_clip_get_region (clip, ®ion); if (status == CAIRO_INT_STATUS_NOTHING_TO_DO) { goto DONE; } else if (status == CAIRO_INT_STATUS_UNSUPPORTED) { return ERROR_LIST (CAIRO_STATUS_CLIP_NOT_REPRESENTABLE); } else if (unlikely (status)) { return ERROR_LIST (status); } n_rects = cairo_region_num_rectangles (region); if (n_rects) { rectangles = _cairo_malloc_ab (n_rects, sizeof (cairo_rectangle_t)); if (unlikely (rectangles == NULL)) { return ERROR_LIST (CAIRO_STATUS_NO_MEMORY); } for (i = 0; i < n_rects; ++i) { cairo_rectangle_int_t clip_rect; cairo_region_get_rectangle (region, i, &clip_rect); if (! _cairo_clip_int_rect_to_user (gstate, &clip_rect, &rectangles[i])) { free (rectangles); return ERROR_LIST (CAIRO_STATUS_CLIP_NOT_REPRESENTABLE); } } } DONE: list = malloc (sizeof (cairo_rectangle_list_t)); if (unlikely (list == NULL)) { free (rectangles); return ERROR_LIST (CAIRO_STATUS_NO_MEMORY); } list->status = CAIRO_STATUS_SUCCESS; list->rectangles = rectangles; list->num_rectangles = n_rects; return list; #undef ERROR_LIST } /** * cairo_rectangle_list_destroy: * @rectangle_list: a rectangle list, as obtained from cairo_copy_clip_rectangle_list() * * Unconditionally frees @rectangle_list and all associated * references. After this call, the @rectangle_list pointer must not * be dereferenced. * * Since: 1.4 **/ void cairo_rectangle_list_destroy (cairo_rectangle_list_t *rectangle_list) { if (rectangle_list == NULL || rectangle_list == &_cairo_rectangles_nil || rectangle_list == &_cairo_rectangles_not_representable) return; free (rectangle_list->rectangles); free (rectangle_list); } void _cairo_clip_reset_static_data (void) { _freed_pool_reset (&clip_path_pool); }