diff --git a/ChangeLog b/ChangeLog index cf15bbbe1..0516c5c31 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,63 @@ +2005-08-08 Owen Taylor + + * src/cairo-gstate.c (_cairo_operator_bounded): Add a function to test + whether a cairo_operator_t is bounded (does nothing for 0 src/mask) + + * src/cairo-surface.c (_cairo_surface_composite_fixup_unbounded) cairoint.h: + Add a helper function to take clearing areas that are outside the source/mask + but are cleared by unbounded operations. + + * src/cairo-image-surface.c (_cairo_image_surface_composite) + src/cairo-xlib-surface.c (_cairo_xlib_surface_composite): Use + _cairo_surface_composite_fixup_unbounded() as needed. + + * src/cairo-image-surface.c src/cairint.h: Keep track of whether the surface + has a clip or not ... we need this for determining when we can bypass + an intermediate mask for composite_trapezoids(). + + * src/cairo-image-surface.c (_cairo_image_surface_composite_trapezoids): + Create an intermediate mask of the right size with pixman_add_trapezoids() + and composite that. + + * src/cairo-xlib-surface.c (_cairo_xlib_surface_composite_trapezoids): + When rendering with an unbounded operator, create the intermediate mask + ourselves and render with ADD to that, then composite the result. + + * src/cairo-ft-font.c (_cairo_ft_scaled_font_show_glyphs): Create an + intermediate surface the size of the extents, render the glyphs to that + then composite the results. + + * src/cairo-xlib-surface.c (glyphset_cache_entry_t): Add the size of the glyph + + * src/cairo-xlib-surface.c (_show_glyphs_fixup_unbounded): Compute the size + of the glyph mask, then use _cairo_surface_composite_fixup_unbounded(). + + * src/cairo-xlib-surface.c (_cairo_xlib_surface_show_glyphs32): Use the right + mask format. (Unrelated bugfix) + + * src/cairo-gstate.c (_cairo_gstate_clip_and_composite): New function taking + a drawing function as a parameter to encapsulate shared logic between + compositing trapezoid, glyphs, and masks. + + * src/cairo-gstate.c (_cairo_gstate_mask, + _cairo_surface_clip_and_composite_trapezoids, _cairo_gstate_show_glyphs): + Use _cairo_gstate_clip_and_composite(). Also fix extents computations for + unbounded operators. + + * src/cairo-clip.c src/cairo-clip-private.h (_cairo_clip_combine_to_surface): + Add the destination as an extra parameter to allow combining to an intermediate + surface. + + * tests/unbounded-operator.c tests/Makefile.am: Add a test for the + operation of the 6 unbounded operators against different shapes. + + * tests/clip-operator.c tests/Makefile.am: Add a test that tests + surface clipping with different shapes against all the operators. + + * test/composite-integer-translate-over-repeat.c (draw): Make use OVER + like the name and description. With fixed semantics, SOURCE does something + different. + 2005-08-06 Carl Worth * BUGS: Remove several bugs that have been fixed. diff --git a/src/cairo-clip-private.h b/src/cairo-clip-private.h index f8846b5c9..a56f57983 100644 --- a/src/cairo-clip-private.h +++ b/src/cairo-clip-private.h @@ -111,8 +111,11 @@ _cairo_clip_intersect_to_region (cairo_clip_t *clip, pixman_region16_t *region); cairo_private cairo_status_t -_cairo_clip_combine_to_surface (cairo_clip_t *clip, - cairo_surface_t *intermediate, - cairo_rectangle_t *extents); +_cairo_clip_combine_to_surface (cairo_clip_t *clip, + cairo_operator_t operator, + cairo_surface_t *dst, + int dst_x, + int dst_y, + const cairo_rectangle_t *extents); #endif /* CAIRO_CLIP_PRIVATE_H */ diff --git a/src/cairo-clip.c b/src/cairo-clip.c index c211e2b8a..a4b8d5332 100644 --- a/src/cairo-clip.c +++ b/src/cairo-clip.c @@ -208,28 +208,32 @@ _cairo_clip_intersect_to_region (cairo_clip_t *clip, return CAIRO_STATUS_SUCCESS; } -/* Combines clip->surface using the IN operator with the given - * intermediate surface, which corresponds to the rectangle of the - * destination space given by @extents. +/* Combines the region of clip->surface given by extents in + * device backend coordinates into the given temporary surface, + * which has its origin at dst_x, dst_y in backend coordinates */ cairo_status_t -_cairo_clip_combine_to_surface (cairo_clip_t *clip, - cairo_surface_t *intermediate, - cairo_rectangle_t *extents) +_cairo_clip_combine_to_surface (cairo_clip_t *clip, + cairo_operator_t operator, + cairo_surface_t *dst, + int dst_x, + int dst_y, + const cairo_rectangle_t *extents) { cairo_pattern_union_t pattern; cairo_status_t status; _cairo_pattern_init_for_surface (&pattern.surface, clip->surface); - status = _cairo_surface_composite (CAIRO_OPERATOR_IN, + status = _cairo_surface_composite (operator, &pattern.base, NULL, - intermediate, + dst, extents->x - clip->surface_rect.x, extents->y - clip->surface_rect.y, 0, 0, - 0, 0, + extents->x - dst_x, + extents->y - dst_y, extents->width, extents->height); _cairo_pattern_fini (&pattern.base); diff --git a/src/cairo-ft-font.c b/src/cairo-ft-font.c index 8f7b2e947..36a0c6742 100644 --- a/src/cairo-ft-font.c +++ b/src/cairo-ft-font.c @@ -1844,6 +1844,26 @@ _cairo_ft_scaled_font_glyph_bbox (void *abstract_font, return CAIRO_STATUS_SUCCESS; } +static cairo_format_t +_select_text_mask_format (cairo_bool_t have_a1_glyphs, + cairo_bool_t have_a8_glyphs, + cairo_bool_t have_argb32_glyphs) +{ + if (have_a8_glyphs) + return CAIRO_FORMAT_A8; + + if (have_a1_glyphs && have_argb32_glyphs) + return CAIRO_FORMAT_A8; + + if (have_a1_glyphs) + return CAIRO_FORMAT_A1; + + if (have_argb32_glyphs) + return CAIRO_FORMAT_ARGB32; + + /* when there are no glyphs to draw, just pick something */ + return CAIRO_FORMAT_A8; +} static cairo_status_t _cairo_ft_scaled_font_show_glyphs (void *abstract_font, @@ -1859,12 +1879,16 @@ _cairo_ft_scaled_font_show_glyphs (void *abstract_font, const cairo_glyph_t *glyphs, int num_glyphs) { - cairo_image_glyph_cache_entry_t *img; + cairo_image_glyph_cache_entry_t **entries; cairo_cache_t *cache; cairo_glyph_cache_key_t key; cairo_ft_scaled_font_t *scaled_font = abstract_font; cairo_surface_pattern_t glyph_pattern; - cairo_status_t status; + cairo_surface_t *mask; + cairo_surface_pattern_t mask_pattern; + cairo_format_t mask_format = CAIRO_FORMAT_A1; + cairo_status_t status = CAIRO_STATUS_SUCCESS; + cairo_bool_t have_a1_glyphs, have_a8_glyphs, have_argb32_glyphs; int x, y; int i; @@ -1884,44 +1908,98 @@ _cairo_ft_scaled_font_show_glyphs (void *abstract_font, key.scale = scaled_font->base.scale; key.flags = scaled_font->load_flags; + entries = malloc (num_glyphs * sizeof (cairo_image_glyph_cache_entry_t)); + if (!entries) + goto CLEANUP_CACHE; + + have_a1_glyphs = FALSE; + have_a8_glyphs = FALSE; + have_argb32_glyphs = FALSE; + for (i = 0; i < num_glyphs; i++) { - img = NULL; + entries[i] = NULL; key.index = glyphs[i].index; - if (_cairo_cache_lookup (cache, &key, (void **) &img, NULL) - != CAIRO_STATUS_SUCCESS - || img == NULL - || img->image == NULL) + if (_cairo_cache_lookup (cache, &key, (void **) &entries[i], NULL) != CAIRO_STATUS_SUCCESS) + continue; + + switch (entries[i]->image->format) { + case CAIRO_FORMAT_A1: + have_a1_glyphs = TRUE; + break; + case CAIRO_FORMAT_A8: + have_a8_glyphs = TRUE; + break; + case CAIRO_FORMAT_ARGB32: + have_argb32_glyphs = TRUE; + break; + default: + break; + } + } + + mask_format = _select_text_mask_format (have_a1_glyphs, have_a8_glyphs, have_argb32_glyphs); + + mask = cairo_image_surface_create (mask_format, width, height); + if (!mask) + goto CLEANUP_ENTRIES; + + status = _cairo_surface_fill_rectangle (mask, CAIRO_OPERATOR_SOURCE, + CAIRO_COLOR_TRANSPARENT, + 0, 0, width, height); + if (status) + goto CLEANUP_MASK; + + for (i = 0; i < num_glyphs; i++) + { + if (entries[i] == NULL + || entries[i]->image == NULL) continue; x = (int) floor (glyphs[i].x + 0.5); y = (int) floor (glyphs[i].y + 0.5); - _cairo_pattern_init_for_surface (&glyph_pattern, &(img->image->base)); + _cairo_pattern_init_for_surface (&glyph_pattern, &(entries[i]->image->base)); - status = _cairo_surface_composite (operator, pattern, + status = _cairo_surface_composite (CAIRO_OPERATOR_ADD, pattern, &glyph_pattern.base, - surface, - x + img->size.x, - y + img->size.y, + mask, + x + entries[i]->size.x, + y + entries[i]->size.y, 0, 0, - x + img->size.x, - y + img->size.y, - (double) img->size.width, - (double) img->size.height); + x + entries[i]->size.x - dest_x, + y + entries[i]->size.y - dest_y, + entries[i]->size.width, + entries[i]->size.height); _cairo_pattern_fini (&glyph_pattern.base); - if (status) { - _cairo_unlock_global_image_glyph_cache (); - return status; - } - } + if (status) + goto CLEANUP_MASK; + } + _cairo_pattern_init_for_surface (&mask_pattern, mask); + + status = _cairo_surface_composite (operator, pattern, &mask_pattern.base, + surface, + source_x, source_y, + 0, 0, + dest_x, dest_y, + width, height); + + _cairo_pattern_fini (&mask_pattern.base); + + CLEANUP_MASK: + cairo_surface_destroy (mask); + + CLEANUP_ENTRIES: + free (entries); + + CLEANUP_CACHE: _cairo_unlock_global_image_glyph_cache (); - return CAIRO_STATUS_SUCCESS; + return status; } diff --git a/src/cairo-gstate.c b/src/cairo-gstate.c index 134586a5f..1e005f847 100644 --- a/src/cairo-gstate.c +++ b/src/cairo-gstate.c @@ -705,6 +705,257 @@ _cairo_gstate_paint (cairo_gstate_t *gstate) return CAIRO_STATUS_SUCCESS; } +/** + * _cairo_operator_bounded: + * @operator: a #cairo_operator_t + * + * A bounded operator is one where a source or mask pixel + * of zero results in no effect on the destination image. + * + * Unbounded operators often require special handling; if you, for + * example, draw trapezoids with an unbounded operator, the effect + * extends past the bounding box of the trapezoids. + * + * Return value: %TRUE if the operator is bounded + **/ +cairo_bool_t +_cairo_operator_bounded (cairo_operator_t operator) +{ + switch (operator) { + case CAIRO_OPERATOR_OVER: + case CAIRO_OPERATOR_ATOP: + case CAIRO_OPERATOR_DEST: + case CAIRO_OPERATOR_DEST_OVER: + case CAIRO_OPERATOR_DEST_OUT: + case CAIRO_OPERATOR_XOR: + case CAIRO_OPERATOR_ADD: + case CAIRO_OPERATOR_SATURATE: + return TRUE; + case CAIRO_OPERATOR_CLEAR: + case CAIRO_OPERATOR_SOURCE: + case CAIRO_OPERATOR_OUT: + case CAIRO_OPERATOR_IN: + case CAIRO_OPERATOR_DEST_IN: + case CAIRO_OPERATOR_DEST_ATOP: + return FALSE; + } + + ASSERT_NOT_REACHED; +} + +typedef cairo_status_t (*cairo_draw_func_t) (void *closure, + cairo_operator_t operator, + cairo_pattern_t *src, + cairo_surface_t *dst, + int dst_x, + int dst_y, + const cairo_rectangle_t *extents); + +/* Handles compositing with a clip surface when the operator allows + * us to combine the clip with the mask + */ +static cairo_status_t +_cairo_gstate_clip_and_composite_with_mask (cairo_clip_t *clip, + cairo_operator_t operator, + cairo_pattern_t *src, + cairo_draw_func_t draw_func, + void *draw_closure, + cairo_surface_t *dst, + const cairo_rectangle_t *extents) +{ + cairo_surface_t *intermediate; + cairo_surface_pattern_t intermediate_pattern; + cairo_status_t status; + + intermediate = cairo_surface_create_similar (clip->surface, + CAIRO_CONTENT_ALPHA, + extents->width, + extents->height); + if (intermediate->status) + return CAIRO_STATUS_NO_MEMORY; + + status = (*draw_func) (draw_closure, CAIRO_OPERATOR_SOURCE, + NULL, intermediate, + extents->x, extents->y, + extents); + if (status) + goto CLEANUP_SURFACE; + + status = _cairo_clip_combine_to_surface (clip, CAIRO_OPERATOR_IN, + intermediate, + extents->x, extents->y, + extents); + if (status) + goto CLEANUP_SURFACE; + + _cairo_pattern_init_for_surface (&intermediate_pattern, intermediate); + + status = _cairo_surface_composite (operator, + src, &intermediate_pattern.base, dst, + extents->x, extents->y, + 0, 0, + extents->x, extents->y, + extents->width, extents->height); + + _cairo_pattern_fini (&intermediate_pattern.base); + + CLEANUP_SURFACE: + cairo_surface_destroy (intermediate); + + return status; +} + +/* Handles compositing with a clip surface when the operator allows + * us to combine the clip with the mask + */ +static cairo_status_t +_cairo_gstate_clip_and_composite_combine (cairo_clip_t *clip, + cairo_operator_t operator, + cairo_pattern_t *src, + cairo_draw_func_t draw_func, + void *draw_closure, + cairo_surface_t *dst, + const cairo_rectangle_t *extents) +{ + cairo_surface_t *intermediate; + cairo_surface_pattern_t dst_pattern; + cairo_surface_pattern_t intermediate_pattern; + 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 (dst, + CAIRO_CONTENT_COLOR_ALPHA, + extents->width, + extents->height); + if (intermediate->status) + return CAIRO_STATUS_NO_MEMORY; + + /* Initialize the intermediate surface from the destination surface + */ + _cairo_pattern_init_for_surface (&dst_pattern, dst); + + status = _cairo_surface_composite (CAIRO_OPERATOR_SOURCE, + &dst_pattern.base, NULL, intermediate, + extents->x, extents->y, + 0, 0, + 0, 0, + extents->width, extents->height); + + _cairo_pattern_fini (&dst_pattern.base); + + if (status) + goto CLEANUP_SURFACE; + + status = (*draw_func) (draw_closure, operator, + src, intermediate, + extents->x, extents->y, + extents); + if (status) + goto CLEANUP_SURFACE; + + /* Combine that with the clip + */ + status = _cairo_clip_combine_to_surface (clip, CAIRO_OPERATOR_DEST_IN, + intermediate, + extents->x, extents->y, + extents); + if (status) + goto CLEANUP_SURFACE; + + /* Punch the clip out of the destination + */ + status = _cairo_clip_combine_to_surface (clip, CAIRO_OPERATOR_DEST_OUT, + dst, + 0, 0, + extents); + if (status) + goto CLEANUP_SURFACE; + + /* Now add the two results together + */ + _cairo_pattern_init_for_surface (&intermediate_pattern, intermediate); + + status = _cairo_surface_composite (CAIRO_OPERATOR_ADD, + &intermediate_pattern.base, NULL, dst, + 0, 0, + 0, 0, + extents->x, extents->y, + extents->width, extents->height); + + _cairo_pattern_fini (&intermediate_pattern.base); + + CLEANUP_SURFACE: + cairo_surface_destroy (intermediate); + + return status; +} + +static int +_cairo_rectangle_empty (const cairo_rectangle_t *rect) +{ + return rect->width == 0 || rect->height == 0; +} + +/** + * _cairo_gstate_clip_and_composite: + * @gstate: a #cairo_gstate_t + * @operator: 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 +_cairo_gstate_clip_and_composite (cairo_clip_t *clip, + cairo_operator_t operator, + cairo_pattern_t *src, + cairo_draw_func_t draw_func, + void *draw_closure, + cairo_surface_t *dst, + const cairo_rectangle_t *extents) +{ + if (_cairo_rectangle_empty (extents)) + /* Nothing to do */ + return CAIRO_STATUS_SUCCESS; + + if (clip->surface) + { + if (_cairo_operator_bounded (operator)) + return _cairo_gstate_clip_and_composite_with_mask (clip, operator, + src, + draw_func, draw_closure, + dst, extents); + else + return _cairo_gstate_clip_and_composite_combine (clip, operator, + src, + draw_func, draw_closure, + dst, extents); + } + else + { + return (*draw_func) (draw_closure, operator, + src, dst, + 0, 0, + extents); + } +} + + static cairo_status_t _get_mask_extents (cairo_gstate_t *gstate, cairo_pattern_t *mask, @@ -714,7 +965,8 @@ _get_mask_extents (cairo_gstate_t *gstate, /* * XXX should take mask extents into account, but - * that involves checking the transform... For now, + * that involves checking the transform and + * _cairo_operator_bounded (operator)... For now, * be lazy and just use the destination extents */ status = _cairo_surface_get_extents (gstate->target, extents); @@ -724,16 +976,40 @@ _get_mask_extents (cairo_gstate_t *gstate, return _cairo_clip_intersect_to_rectangle (&gstate->clip, extents); } +static cairo_status_t +_cairo_gstate_mask_draw_func (void *closure, + cairo_operator_t operator, + cairo_pattern_t *src, + cairo_surface_t *dst, + int dst_x, + int dst_y, + const cairo_rectangle_t *extents) +{ + cairo_pattern_t *mask = closure; + + if (src) + return _cairo_surface_composite (operator, + src, mask, dst, + extents->x, extents->y, + extents->x, extents->y, + extents->x - dst_x, extents->y - dst_y, + extents->width, extents->height); + else + return _cairo_surface_composite (operator, + mask, NULL, dst, + extents->x, extents->y, + 0, 0, /* unused */ + extents->x - dst_x, extents->y - dst_y, + extents->width, extents->height); +} + cairo_status_t _cairo_gstate_mask (cairo_gstate_t *gstate, cairo_pattern_t *mask) { cairo_rectangle_t extents; cairo_pattern_union_t source_pattern, mask_pattern; - cairo_surface_pattern_t intermediate_pattern; - cairo_pattern_t *effective_mask; cairo_status_t status; - int mask_x, mask_y; if (mask->status) return mask->status; @@ -745,64 +1021,17 @@ _cairo_gstate_mask (cairo_gstate_t *gstate, if (status) return status; - _get_mask_extents (gstate, mask, &extents); - - if (gstate->clip.surface) { - /* When there is clip surface, we'll need to create a - * temporary surface that combines the clip and mask - */ - cairo_surface_t *intermediate; - - intermediate = cairo_surface_create_similar (gstate->clip.surface, - CAIRO_CONTENT_ALPHA, - extents.width, - extents.height); - if (intermediate->status) - return CAIRO_STATUS_NO_MEMORY; - - status = _cairo_surface_composite (CAIRO_OPERATOR_SOURCE, - mask, NULL, intermediate, - extents.x, extents.y, - 0, 0, - 0, 0, - extents.width, extents.height); - if (status) { - cairo_surface_destroy (intermediate); - return status; - } - - status = _cairo_clip_combine_to_surface (&gstate->clip, intermediate, &extents); - if (status) { - cairo_surface_destroy (intermediate); - return status; - } - - _cairo_pattern_init_for_surface (&intermediate_pattern, intermediate); - cairo_surface_destroy (intermediate); - - effective_mask = &intermediate_pattern.base; - mask_x = extents.x; - mask_y = extents.y; - - } else { - effective_mask = mask; - mask_x = mask_y = 0; - } - _cairo_gstate_copy_transformed_source (gstate, &source_pattern.base); - _cairo_gstate_copy_transformed_mask (gstate, &mask_pattern.base, effective_mask); + _cairo_gstate_copy_transformed_mask (gstate, &mask_pattern.base, mask); + + _get_mask_extents (gstate, &mask_pattern.base, &extents); + + status = _cairo_gstate_clip_and_composite (&gstate->clip, gstate->operator, + &source_pattern.base, + _cairo_gstate_mask_draw_func, &mask_pattern.base, + gstate->target, + &extents); - status = _cairo_surface_composite (gstate->operator, - &source_pattern.base, - &mask_pattern.base, - gstate->target, - extents.x, extents.y, - extents.x - mask_x, extents.y - mask_y, - extents.x, extents.y, - extents.width, extents.height); - - if (gstate->clip.surface) - _cairo_pattern_fini (&intermediate_pattern.base); _cairo_pattern_fini (&source_pattern.base); _cairo_pattern_fini (&mask_pattern.base); @@ -916,12 +1145,6 @@ _cairo_rectangle_intersect (cairo_rectangle_t *dest, cairo_rectangle_t *src) } } -static int -_cairo_rectangle_empty (cairo_rectangle_t *rect) -{ - return rect->width == 0 || rect->height == 0; -} - /* Composites a region representing a set of trapezoids. */ static cairo_status_t @@ -971,73 +1194,6 @@ _composite_trap_region (cairo_clip_t *clip, return status; } -/* Composites a set of trapezoids in the case where we need to create - * an intermediate surface to handle clip->surface - * - * Warning: This call modifies the coordinates of traps - */ -static cairo_status_t -_composite_traps_intermediate_surface (cairo_clip_t *clip, - cairo_pattern_t *src, - cairo_operator_t operator, - cairo_surface_t *dst, - cairo_traps_t *traps, - cairo_rectangle_t *extents) -{ - cairo_pattern_union_t pattern; - cairo_surface_t *intermediate; - cairo_surface_pattern_t intermediate_pattern; - cairo_status_t status; - - _cairo_traps_translate (traps, -extents->x, -extents->y); - - intermediate = _cairo_surface_create_similar_solid (clip->surface, - CAIRO_CONTENT_ALPHA, - extents->width, - extents->height, - CAIRO_COLOR_TRANSPARENT); - if (intermediate->status) - return CAIRO_STATUS_NO_MEMORY; - - _cairo_pattern_init_solid (&pattern.solid, CAIRO_COLOR_WHITE); - - status = _cairo_surface_composite_trapezoids (CAIRO_OPERATOR_ADD, - &pattern.base, - intermediate, - extents->x, extents->y, - 0, 0, - extents->width, - extents->height, - traps->traps, - traps->num_traps); - _cairo_pattern_fini (&pattern.base); - - if (status) - goto out; - - status = _cairo_clip_combine_to_surface (clip, intermediate, extents); - if (status) - goto out; - - _cairo_pattern_init_for_surface (&intermediate_pattern, intermediate); - - status = _cairo_surface_composite (operator, - src, - &intermediate_pattern.base, - dst, - extents->x, extents->y, - 0, 0, - extents->x, extents->y, - extents->width, extents->height); - - _cairo_pattern_fini (&intermediate_pattern.base); - - out: - cairo_surface_destroy (intermediate); - - return status; -} - /* Composites a region representing a set of trapezoids in the * case of a solid source (so we can use * _cairo_surface_fill_rectangles). @@ -1077,27 +1233,34 @@ _composite_trap_region_solid (cairo_clip_t *clip, return status; } -/* Composites a set of trapezoids in the general case where - clip->surface == NULL - */ static cairo_status_t -_composite_traps (cairo_clip_t *clip, - cairo_pattern_t *src, - cairo_operator_t operator, - cairo_surface_t *dst, - cairo_traps_t *traps, - cairo_rectangle_t *extents) +_composite_traps_draw_func (void *closure, + cairo_operator_t operator, + cairo_pattern_t *src, + cairo_surface_t *dst, + int dst_x, + int dst_y, + const cairo_rectangle_t *extents) { + cairo_traps_t *traps = closure; + cairo_pattern_union_t pattern; cairo_status_t status; + + if (dst_x != 0 || dst_y != 0) + _cairo_traps_translate (traps, - dst_x, - dst_y); + _cairo_pattern_init_solid (&pattern.solid, CAIRO_COLOR_WHITE); + if (!src) + src = &pattern.base; + status = _cairo_surface_composite_trapezoids (operator, src, dst, - extents->x, extents->y, - extents->x, extents->y, - extents->width, - extents->height, + extents->x, extents->y, + extents->x - dst_x, extents->y - dst_y, + extents->width, extents->height, traps->traps, traps->num_traps); + _cairo_pattern_fini (&pattern.base); return status; } @@ -1134,65 +1297,65 @@ _cairo_surface_clip_and_composite_trapezoids (cairo_pattern_t *src, if (status) return status; - if (trap_region) { - status = _cairo_clip_intersect_to_region (clip, trap_region); - _region_rect_extents (trap_region, &extents); - } else { - cairo_box_t trap_extents; - _cairo_traps_extents (traps, &trap_extents); - _cairo_box_round_to_rectangle (&trap_extents, &extents); + if (_cairo_operator_bounded (operator)) + { + if (trap_region) { + status = _cairo_clip_intersect_to_region (clip, trap_region); + _region_rect_extents (trap_region, &extents); + } else { + cairo_box_t trap_extents; + _cairo_traps_extents (traps, &trap_extents); + _cairo_box_round_to_rectangle (&trap_extents, &extents); + status = _cairo_clip_intersect_to_rectangle (clip, &extents); + } + } + else + { + status = _cairo_surface_get_extents (dst, &extents); + if (status) + return status; status = _cairo_clip_intersect_to_rectangle (clip, &extents); + if (status) + return status; } if (status) goto out; - if (_cairo_rectangle_empty (&extents)) - /* Nothing to do */ - goto out; - - if (clip->surface) { - if (trap_region) { - /* If we are compositing a set of rectangles, we can set them as the - * clip region for the destination surface and use the clip surface - * as the mask. A clip region might not be supported, in which case - * we fall through to the next method - */ - status = _composite_trap_region (clip, src, operator, dst, - trap_region, &extents); - if (status != CAIRO_INT_STATUS_UNSUPPORTED) - goto out; - } - - /* Handle a clip surface by creating an intermediate surface. */ - status = _composite_traps_intermediate_surface (clip, src, operator, - dst, traps, &extents); - } else { - /* No clip surface */ - if (trap_region && src->type == CAIRO_PATTERN_SOLID) { - /* Solid rectangles are handled specially */ + if (trap_region && _cairo_operator_bounded (operator)) + { + if (src->type == CAIRO_PATTERN_SOLID && + !clip->surface && + trap_region-> + { + /* Solid rectangles special case */ status = _composite_trap_region_solid (clip, (cairo_solid_pattern_t *)src, operator, dst, trap_region); - } else { - if (trap_region) { - /* 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. - */ - status = _composite_trap_region (clip, src, operator, dst, - trap_region, &extents); - if (status != CAIRO_INT_STATUS_UNSUPPORTED) - goto out; - - /* If a clip regions aren't supported, fall through */ - } - - status = _composite_traps (clip, src, operator, - dst, traps, &extents); + goto out; } + + /* 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. + * + * 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, operator, dst, + trap_region, &extents); + if (status != CAIRO_INT_STATUS_UNSUPPORTED) + goto out; } + status = _cairo_gstate_clip_and_composite (clip, operator, src, + _composite_traps_draw_func, traps, + dst, + &extents); + out: if (trap_region) pixman_region_destroy (trap_region); @@ -1209,7 +1372,7 @@ _cairo_gstate_clip_and_composite_trapezoids (cairo_gstate_t *gstate, cairo_status_t status; _cairo_gstate_copy_transformed_source (gstate, &pattern.base); - + status = _cairo_surface_clip_and_composite_trapezoids (&pattern.base, gstate->operator, gstate->target, @@ -1681,6 +1844,58 @@ _cairo_gstate_glyph_extents (cairo_gstate_t *gstate, return CAIRO_STATUS_SUCCESS; } +typedef struct { + cairo_scaled_font_t *font; + cairo_glyph_t *glyphs; + int num_glyphs; +} cairo_show_glyphs_info_t; + +static cairo_status_t +_cairo_gstate_show_glyphs_draw_func (void *closure, + cairo_operator_t operator, + cairo_pattern_t *src, + cairo_surface_t *dst, + int dst_x, + int dst_y, + const cairo_rectangle_t *extents) +{ + cairo_show_glyphs_info_t *glyph_info = closure; + cairo_pattern_union_t pattern; + 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) + { + glyph_info->glyphs[i].x -= dst_x; + glyph_info->glyphs[i].y -= dst_y; + } + } + + _cairo_pattern_init_solid (&pattern.solid, CAIRO_COLOR_WHITE); + if (!src) + src = &pattern.base; + + status = _cairo_scaled_font_show_glyphs (glyph_info->font, + operator, + 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); + + if (src == &pattern.base) + _cairo_pattern_fini (&pattern.base); + + return status; +} + cairo_status_t _cairo_gstate_show_glyphs (cairo_gstate_t *gstate, cairo_glyph_t *glyphs, @@ -1692,6 +1907,7 @@ _cairo_gstate_show_glyphs (cairo_gstate_t *gstate, cairo_pattern_union_t pattern; cairo_box_t bbox; cairo_rectangle_t extents; + cairo_show_glyphs_info_t glyph_info; if (gstate->source->status) return gstate->source->status; @@ -1715,113 +1931,42 @@ _cairo_gstate_show_glyphs (cairo_gstate_t *gstate, &transformed_glyphs[i].x, &transformed_glyphs[i].y); } - - status = _cairo_scaled_font_glyph_bbox (gstate->scaled_font, - transformed_glyphs, num_glyphs, - &bbox); - _cairo_box_round_to_rectangle (&bbox, &extents); - if (status) - goto CLEANUP_GLYPHS; - - if (gstate->clip.surface) + if (_cairo_operator_bounded (gstate->operator)) { - cairo_surface_t *intermediate; - cairo_surface_pattern_t intermediate_pattern; - - _cairo_rectangle_intersect (&extents, &gstate->clip.surface_rect); - - /* Shortcut if empty */ - if (_cairo_rectangle_empty (&extents)) { - status = CAIRO_STATUS_SUCCESS; - goto BAIL1; - } - - intermediate = _cairo_surface_create_similar_solid (gstate->clip.surface, - CAIRO_CONTENT_ALPHA, - extents.width, - extents.height, - CAIRO_COLOR_TRANSPARENT); - if (intermediate->status) { - status = CAIRO_STATUS_NO_MEMORY; - goto BAIL1; - } - - /* move the glyphs again, from dev space to intermediate space */ - for (i = 0; i < num_glyphs; ++i) - { - transformed_glyphs[i].x -= extents.x; - transformed_glyphs[i].y -= extents.y; - } - - _cairo_pattern_init_solid (&pattern.solid, CAIRO_COLOR_WHITE); - - status = _cairo_scaled_font_show_glyphs (gstate->scaled_font, - CAIRO_OPERATOR_ADD, - &pattern.base, intermediate, - extents.x, extents.y, - 0, 0, - extents.width, extents.height, - transformed_glyphs, num_glyphs); - - _cairo_pattern_fini (&pattern.base); - + status = _cairo_scaled_font_glyph_bbox (gstate->scaled_font, + transformed_glyphs, num_glyphs, + &bbox); if (status) - goto BAIL2; - - _cairo_pattern_init_for_surface (&pattern.surface, - gstate->clip.surface); - - status = _cairo_surface_composite (CAIRO_OPERATOR_IN, - &pattern.base, - NULL, - intermediate, - extents.x - gstate->clip.surface_rect.x, - extents.y - gstate->clip.surface_rect.y, - 0, 0, - 0, 0, - extents.width, extents.height); - - _cairo_pattern_fini (&pattern.base); - - if (status) - goto BAIL2; - - _cairo_pattern_init_for_surface (&intermediate_pattern, intermediate); - _cairo_gstate_copy_transformed_source (gstate, &pattern.base); - - status = _cairo_surface_composite (gstate->operator, - &pattern.base, - &intermediate_pattern.base, - gstate->target, - extents.x, extents.y, - 0, 0, - extents.x, extents.y, - extents.width, extents.height); - _cairo_pattern_fini (&pattern.base); - _cairo_pattern_fini (&intermediate_pattern.base); - - BAIL2: - cairo_surface_destroy (intermediate); - BAIL1: - ; + goto CLEANUP_GLYPHS; + + _cairo_box_round_to_rectangle (&bbox, &extents); } else { - _cairo_pattern_init_copy (&pattern.base, gstate->source); - _cairo_gstate_copy_transformed_source (gstate, &pattern.base); - - status = _cairo_scaled_font_show_glyphs (gstate->scaled_font, - gstate->operator, &pattern.base, - gstate->target, - extents.x, extents.y, - extents.x, extents.y, - extents.width, extents.height, - transformed_glyphs, num_glyphs); - - _cairo_pattern_fini (&pattern.base); + status = _cairo_surface_get_extents (gstate->target, &extents); + if (status) + goto CLEANUP_GLYPHS; } + status = _cairo_clip_intersect_to_rectangle (&gstate->clip, &extents); + if (status) + goto CLEANUP_GLYPHS; + + _cairo_gstate_copy_transformed_source (gstate, &pattern.base); + + glyph_info.font = gstate->scaled_font; + glyph_info.glyphs = transformed_glyphs; + glyph_info.num_glyphs = num_glyphs; + + status = _cairo_gstate_clip_and_composite (&gstate->clip, gstate->operator, + &pattern.base, + _cairo_gstate_show_glyphs_draw_func, &glyph_info, + gstate->target, + &extents); + + _cairo_pattern_fini (&pattern.base); + CLEANUP_GLYPHS: free (transformed_glyphs); diff --git a/src/cairo-image-surface.c b/src/cairo-image-surface.c index e86ed172b..7c284b467 100644 --- a/src/cairo-image-surface.c +++ b/src/cairo-image-surface.c @@ -69,7 +69,8 @@ _cairo_image_surface_create_for_pixman_image (pixman_image_t *pixman_image, surface->format = format; surface->data = (unsigned char *) pixman_image_get_data (pixman_image); - surface->owns_data = 0; + surface->owns_data = FALSE; + surface->has_clip = FALSE; surface->width = pixman_image_get_width (pixman_image); surface->height = pixman_image_get_height (pixman_image); @@ -613,6 +614,16 @@ _cairo_image_surface_composite (cairo_operator_t operator, } } + if (!_cairo_operator_bounded (operator)) + _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); + if (mask) _cairo_pattern_release_surface (mask_pattern, &mask->base, &mask_attr); @@ -644,6 +655,48 @@ _cairo_image_surface_fill_rectangles (void *abstract_surface, return CAIRO_STATUS_SUCCESS; } +static pixman_image_t * +_create_mask_image (int width, + int height) +{ + pixman_image_t *image; + pixman_color_t pixman_color = { 0, 0, 0, 0 }; /* transparent */ + pixman_rectangle_t rect; + pixman_format_t *format; + + format = pixman_format_create (PIXMAN_FORMAT_NAME_A8); + if (!format) + return NULL; + + image = pixman_image_create (format, width, height); + if (!image) + return NULL; + + rect.x = 0; + rect.y = 0; + rect.width = width; + rect.height = height; + + pixman_fill_rectangles (PIXMAN_OPERATOR_SRC, image, + &pixman_color, &rect, 1); + + return image; +} + +static cairo_bool_t +_cairo_image_surface_is_alpha_only (cairo_image_surface_t *surface) +{ + int bpp, alpha, red, green, blue; + + if (surface->format != (cairo_format_t) -1) + return surface->format == CAIRO_FORMAT_A1 || surface->format == CAIRO_FORMAT_A8; + + pixman_format_get_masks (pixman_image_get_format (surface->pixman_image), + &bpp, &alpha, &red, &green, &blue); + + return red == 0 && blue == 0 && green == 0; +} + static cairo_int_status_t _cairo_image_surface_composite_trapezoids (cairo_operator_t operator, cairo_pattern_t *pattern, @@ -661,8 +714,30 @@ _cairo_image_surface_composite_trapezoids (cairo_operator_t operator, cairo_image_surface_t *dst = abstract_dst; cairo_image_surface_t *src; cairo_int_status_t status; - int render_reference_x, render_reference_y; - int render_src_x, render_src_y; + pixman_image_t *mask; + + /* Special case adding trapezoids onto a mask surface; we want to avoid + * creating an intermediate temporary mask unecessarily. + * + * 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 surface->has_clip 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 (operator == CAIRO_OPERATOR_ADD && + _cairo_pattern_is_opaque_solid (pattern) && + _cairo_image_surface_is_alpha_only (dst) && + !dst->has_clip) + { + pixman_add_trapezoids (dst->pixman_image, 0, 0, + (pixman_trapezoid_t *) traps, num_traps); + return CAIRO_STATUS_SUCCESS; + } status = _cairo_pattern_acquire_surface (pattern, &dst->base, src_x, src_y, width, height, @@ -671,28 +746,35 @@ _cairo_image_surface_composite_trapezoids (cairo_operator_t operator, if (status) return status; - if (traps[0].left.p1.y < traps[0].left.p2.y) { - render_reference_x = _cairo_fixed_integer_floor (traps[0].left.p1.x); - render_reference_y = _cairo_fixed_integer_floor (traps[0].left.p1.y); - } else { - render_reference_x = _cairo_fixed_integer_floor (traps[0].left.p2.x); - render_reference_y = _cairo_fixed_integer_floor (traps[0].left.p2.y); - } + status = _cairo_image_surface_set_attributes (src, &attributes); + if (status) + goto CLEANUP_SOURCE; - render_src_x = src_x + render_reference_x - dst_x; - render_src_y = src_y + render_reference_y - dst_y; + mask = _create_mask_image (width, height); + if (!mask) { + status = CAIRO_STATUS_NO_MEMORY; + goto CLEANUP_MASK; + } /* XXX: The pixman_trapezoid_t cast is evil and needs to go away * somehow. */ - status = _cairo_image_surface_set_attributes (src, &attributes); - if (status == CAIRO_STATUS_SUCCESS) - pixman_composite_trapezoids (_pixman_operator (operator), - src->pixman_image, - dst->pixman_image, - render_src_x + attributes.x_offset, - render_src_y + attributes.y_offset, - (pixman_trapezoid_t *) traps, num_traps); + pixman_add_trapezoids (mask, - dst_x, - dst_y, + (pixman_trapezoid_t *) traps, num_traps); + + pixman_composite (_pixman_operator (operator), + 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); + + CLEANUP_MASK: + pixman_image_destroy (mask); + CLEANUP_SOURCE: _cairo_pattern_release_surface (pattern, &src->base, &attributes); return status; @@ -713,6 +795,8 @@ _cairo_image_surface_set_clip_region (cairo_image_surface_t *surface, { pixman_image_set_clip_region (surface->pixman_image, region); + surface->has_clip = region != NULL; + return CAIRO_STATUS_SUCCESS; } diff --git a/src/cairo-surface.c b/src/cairo-surface.c index 52804ebb8..5349eb52d 100644 --- a/src/cairo-surface.c +++ b/src/cairo-surface.c @@ -1396,3 +1396,154 @@ _cairo_surface_show_glyphs (cairo_scaled_font_t *scaled_font, return status; } + +/** + * _cairo_surface_composite_fixup_unbounded: + * @dst: the destination surface + * @src_attr: source surface attributes (from _cairo_pattern_acquire_surface()) + * @src_width: width of source surface + * @src_height: height of source surface + * @mask_attr: mask surface attributes or %NULL if no mask + * @mask_width: width of mask surface + * @mask_height: height of mask surface + * @src_x: @src_x from _cairo_surface_composite() + * @src_y: @src_y from _cairo_surface_composite() + * @mask_x: @mask_x from _cairo_surface_composite() + * @mask_y: @mask_y from _cairo_surface_composite() + * @dst_x: @dst_x from _cairo_surface_composite() + * @dst_y: @dst_y from _cairo_surface_composite() + * @width: @width from _cairo_surface_composite() + * @height: @height_x from _cairo_surface_composite() + * + * Eeek! Too many parameters! This is a helper function to take care of fixing + * up for bugs in libpixman and RENDER where, when asked to composite an + * untransformed surface with an unbounded operator (like CLEAR or SOURCE) + * only the region inside both the source and the mask is affected. + * This function clears the region that should have been drawn but was wasn't. + **/ +void +_cairo_surface_composite_fixup_unbounded (cairo_surface_t *dst, + cairo_surface_attributes_t *src_attr, + int src_width, + int src_height, + cairo_surface_attributes_t *mask_attr, + int mask_width, + int mask_height, + 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_bool_t have_src = TRUE; + cairo_bool_t have_mask = mask_attr != NULL; + cairo_rectangle_t dst_rectangle; + cairo_rectangle_t drawn_rectangle; + cairo_rectangle_t rects[4]; + int num_rects = 0; + + /* The RENDER/libpixman operators are clipped to the bounds of the untransformed, + * non-repeating sources and masks. Other sources and masks can be ignored. + */ + if (!_cairo_matrix_is_integer_translation (&src_attr->matrix, NULL, NULL) || + src_attr->extend != CAIRO_EXTEND_NONE) + have_src = FALSE; + + if (have_mask && + (!_cairo_matrix_is_integer_translation (&mask_attr->matrix, NULL, NULL) || + mask_attr->extend != CAIRO_EXTEND_NONE)) + have_mask = FALSE; + + /* The area that was drawn is the area in the destination rectangle but not within + * the source or the mask. + */ + dst_rectangle.x = dst_x; + dst_rectangle.y = dst_y; + dst_rectangle.width = width; + dst_rectangle.height = height; + + drawn_rectangle = dst_rectangle; + + if (have_src) { + cairo_rectangle_t src_rectangle; + + src_rectangle.x = (dst_x - (src_x + src_attr->x_offset)); + src_rectangle.y = (dst_y - (src_y + src_attr->y_offset)); + src_rectangle.width = src_width; + src_rectangle.height = src_height; + + _cairo_rectangle_intersect (&drawn_rectangle, &src_rectangle); + } + + if (have_mask) { + cairo_rectangle_t mask_rectangle; + + mask_rectangle.x = (dst_x - (mask_x + mask_attr->x_offset)); + mask_rectangle.y = (dst_y - (mask_y + mask_attr->y_offset)); + mask_rectangle.width = mask_width; + mask_rectangle.height = mask_height; + + _cairo_rectangle_intersect (&drawn_rectangle, &mask_rectangle); + } + + /* Now compute the area that is in dst_rectangle but not in drawn_rectangle; + * this is the area we must clear; This computation could be done with + * regions, but the clumsiness of the libpixman API makes this easier. + */ + if (drawn_rectangle.width == 0 || drawn_rectangle.height == 0) + { + rects[num_rects].x = dst_rectangle.x; + rects[num_rects].y = dst_rectangle.y; + rects[num_rects].width = dst_rectangle.width; + rects[num_rects].height = dst_rectangle.height; + + num_rects++; + } + else + { + if (dst_rectangle.y < drawn_rectangle.y) { + rects[num_rects].x = dst_rectangle.x; + rects[num_rects].y = dst_rectangle.y; + rects[num_rects].width = dst_rectangle.width; + rects[num_rects].height = drawn_rectangle.y - dst_rectangle.y; + + num_rects++; + } + + if (dst_rectangle.x < drawn_rectangle.x) { + rects[num_rects].x = dst_rectangle.x; + rects[num_rects].y = drawn_rectangle.y; + rects[num_rects].width = drawn_rectangle.x - dst_rectangle.x; + rects[num_rects].height = drawn_rectangle.height; + + num_rects++; + } + + if (dst_rectangle.x + dst_rectangle.width > drawn_rectangle.x + drawn_rectangle.width) { + rects[num_rects].x = drawn_rectangle.x + drawn_rectangle.width; + rects[num_rects].y = drawn_rectangle.y; + rects[num_rects].width = (dst_rectangle.x + dst_rectangle.width) - (drawn_rectangle.x + drawn_rectangle.width); + rects[num_rects].height = drawn_rectangle.height; + + num_rects++; + } + + if (dst_rectangle.y + dst_rectangle.height > drawn_rectangle.y + drawn_rectangle.height) { + rects[num_rects].x = dst_rectangle.x; + rects[num_rects].y = drawn_rectangle.y + drawn_rectangle.height; + rects[num_rects].width = dst_rectangle.width; + rects[num_rects].height = (dst_rectangle.y + dst_rectangle.height) - (drawn_rectangle.y + drawn_rectangle.height); + + num_rects++; + } + } + + if (num_rects > 0) { + _cairo_surface_fill_rectangles (dst, CAIRO_OPERATOR_SOURCE, CAIRO_COLOR_TRANSPARENT, + rects, num_rects); + } +} + diff --git a/src/cairo-xlib-surface.c b/src/cairo-xlib-surface.c index 4f4e47e3d..fc6ff8b83 100644 --- a/src/cairo-xlib-surface.c +++ b/src/cairo-xlib-surface.c @@ -1137,6 +1137,16 @@ _cairo_xlib_surface_composite (cairo_operator_t operator, dst_x, dst_y, width, height); } + + if (!_cairo_operator_bounded (operator)) + _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); break; case DO_XCOPYAREA: @@ -1214,6 +1224,96 @@ _cairo_xlib_surface_fill_rectangles (void *abstract_surface, return CAIRO_STATUS_SUCCESS; } +/* Creates an A8 picture of size @width x @height, initialized with @color + */ +static Picture +_create_a8_picture (cairo_xlib_surface_t *surface, + XRenderColor *color, + int width, + int height, + cairo_bool_t repeat) +{ + XRenderPictureAttributes pa; + unsigned long mask = 0; + + Pixmap pixmap = XCreatePixmap (surface->dpy, surface->drawable, + width, height, + 8); + Picture picture; + + if (repeat) { + pa.repeat = TRUE; + mask = CPRepeat; + } + + picture = XRenderCreatePicture (surface->dpy, pixmap, + XRenderFindStandardFormat (surface->dpy, PictStandardA8), + mask, &pa); + XRenderFillRectangle (surface->dpy, PictOpSrc, picture, color, + 0, 0, width, height); + XFreePixmap (surface->dpy, pixmap); + + return picture; +} + +/* Creates a temporary mask for the trapezoids covering the area + * [@dst_x, @dst_y, @width, @height] of the destination surface. + */ +static Picture +_create_trapezoid_mask (cairo_xlib_surface_t *dst, + cairo_trapezoid_t *traps, + int num_traps, + int dst_x, + int dst_y, + int width, + int height) + +{ + XRenderColor transparent = { 0, 0, 0, 0 }; + XRenderColor solid = { 0xffff, 0xffff, 0xffff, 0xffff }; + Picture mask_picture, solid_picture; + XTrapezoid *offset_traps; + int i; + + /* This would be considerably simpler using XRenderAddTraps(), but since + * we are only using this in the unbounded-operator case, we stick with + * XRenderCompositeTrapezoids, which is available on older versions + * of RENDER rather than conditionalizing. We should still hit an + * optimization that avoids creating another intermediate surface on + * the servers that have XRenderAddTraps(). + */ + mask_picture = _create_a8_picture (dst, &transparent, width, height, FALSE); + solid_picture = _create_a8_picture (dst, &solid, width, height, TRUE); + + offset_traps = malloc (sizeof (XTrapezoid) * num_traps); + if (!offset_traps) + return None; + + for (i = 0; i < num_traps; i++) { + offset_traps[i].top = traps[i].top - 0x10000 * dst_y; + offset_traps[i].bottom = traps[i].bottom - 0x10000 * dst_y; + offset_traps[i].left.p1.x = traps[i].left.p1.x - 0x10000 * dst_x; + offset_traps[i].left.p1.y = traps[i].left.p1.y - 0x10000 * dst_y; + offset_traps[i].left.p2.x = traps[i].left.p2.x - 0x10000 * dst_x; + offset_traps[i].left.p2.y = traps[i].left.p2.y - 0x10000 * dst_y; + offset_traps[i].right.p1.x = traps[i].right.p1.x - 0x10000 * dst_x; + offset_traps[i].right.p1.y = traps[i].right.p1.y - 0x10000 * dst_y; + offset_traps[i].right.p2.x = traps[i].right.p2.x - 0x10000 * dst_x; + offset_traps[i].right.p2.y = traps[i].right.p2.y - 0x10000 * dst_y; + } + + XRenderCompositeTrapezoids (dst->dpy, PictOpAdd, + solid_picture, mask_picture, + XRenderFindStandardFormat (dst->dpy, PictStandardA8), + 0, 0, + offset_traps, num_traps); + + XRenderFreePicture (dst->dpy, solid_picture); + free (offset_traps); + + return mask_picture; +} + static cairo_int_status_t _cairo_xlib_surface_composite_trapezoids (cairo_operator_t operator, cairo_pattern_t *pattern, @@ -1266,10 +1366,43 @@ _cairo_xlib_surface_composite_trapezoids (cairo_operator_t operator, render_src_x = src_x + render_reference_x - dst_x; render_src_y = src_y + render_reference_y - dst_y; - /* XXX: The XTrapezoid cast is evil and needs to go away somehow. */ _cairo_xlib_surface_ensure_dst_picture (dst); status = _cairo_xlib_surface_set_attributes (src, &attributes); - if (status == CAIRO_STATUS_SUCCESS) + if (status) + goto FAIL; + + if (!_cairo_operator_bounded (operator)) { + /* XRenderCompositeTrapezoids() creates a mask only large enough for the + * trapezoids themselves, but if the operator is unbounded, then we need + * to actually composite all the way out to the bounds, so we create + * the mask and composite ourselves. There actually would + * be benefit to doing this in all cases, since RENDER implementations + * will frequently create a too temporary big mask, ignoring destination + * bounds and clip. (XRenderAddTraps() could be used to make creating + * the mask somewhat cheaper.) + */ + Picture mask_picture = _create_trapezoid_mask (dst, traps, num_traps, + dst_x, dst_y, width, height); + if (!mask_picture) { + status = CAIRO_STATUS_NO_MEMORY; + goto FAIL; + } + + XRenderComposite (dst->dpy, + _render_operator (operator), + src->src_picture, + mask_picture, + dst->dst_picture, + src_x + attributes.x_offset, + src_y + attributes.y_offset, + 0, 0, + dst_x, dst_y, + width, height); + + XRenderFreePicture (dst->dpy, mask_picture); + + } else { + /* XXX: The XTrapezoid cast is evil and needs to go away somehow. */ XRenderCompositeTrapezoids (dst->dpy, _render_operator (operator), src->src_picture, dst->dst_picture, @@ -1277,6 +1410,7 @@ _cairo_xlib_surface_composite_trapezoids (cairo_operator_t operator, render_src_x + attributes.x_offset, render_src_y + attributes.y_offset, (XTrapezoid *) traps, num_traps); + } FAIL: _cairo_pattern_release_surface (pattern, &src->base, &attributes); @@ -1735,6 +1869,7 @@ typedef struct { cairo_glyph_cache_key_t key; GlyphSet glyphset; Glyph glyph; + cairo_glyph_size_t size; } glyphset_cache_entry_t; static Glyph @@ -1789,6 +1924,7 @@ _xlib_glyphset_cache_create_entry (void *abstract_cache, entry->glyph = None; entry->glyphset = None; entry->key.base.memory = 0; + entry->size.x = entry->size.y = entry->size.width = entry->size.height = 0; goto out; } @@ -1797,6 +1933,8 @@ _xlib_glyphset_cache_create_entry (void *abstract_cache, data = im->image->data; + entry->size = im->size; + glyph_info.width = im->size.width; glyph_info.height = im->size.height; @@ -2123,7 +2261,7 @@ _cairo_xlib_surface_show_glyphs32 (cairo_scaled_font_t *scaled_font, _render_operator (operator), src->src_picture, self->dst_picture, - cache->a8_pict_format, + mask_format, source_x, source_y, 0, 0, elts, count); @@ -2351,7 +2489,70 @@ _cairo_xlib_surface_show_glyphs8 (cairo_scaled_font_t *scaled_font, return CAIRO_STATUS_NO_MEMORY; } +/* Handles clearing the regions that are outside of the temporary + * mask created by XRenderCompositeText[N] but should be affected + * by an unbounded operator like CAIRO_OPERATOR_SOURCE. + */ +static void +_show_glyphs_fixup_unbounded (cairo_xlib_surface_t *self, + cairo_surface_attributes_t *src_attr, + cairo_xlib_surface_t *src, + const cairo_glyph_t *glyphs, + glyphset_cache_entry_t **entries, + int num_glyphs, + int src_x, + int src_y, + int dst_x, + int dst_y, + int width, + int height) +{ + cairo_surface_attributes_t mask_attr; + int x1 = INT_MAX; + int x2 = INT_MIN; + int y1 = INT_MAX; + int y2 = INT_MIN; + int i; + /* Compute the size of the glyph mask as the bounding box + * of all the glyphs. + */ + for (i = 0; i < num_glyphs; ++i) { + int thisX, thisY; + + if (entries[i] == NULL || !entries[i]->glyph) + continue; + + thisX = (int) floor (glyphs[i].x + 0.5); + thisY = (int) floor (glyphs[i].y + 0.5); + + if (thisX + entries[i]->size.x < x1) + x1 = thisX + entries[i]->size.x; + if (thisX + entries[i]->size.x + entries[i]->size.width > x2) + x2 = thisX + entries[i]->size.x + entries[i]->size.width; + if (thisY + entries[i]->size.y < y1) + y1 = thisY + entries[i]->size.y; + if (thisY + entries[i]->size.y + entries[i]->size.height > y2) + y2 = thisY + entries[i]->size.y + entries[i]->size.height; + } + + if (x1 >= x2 || y1 >= y2) + x1 = x2 = y1 = y2 = 0; + + cairo_matrix_init_identity (&mask_attr.matrix); + mask_attr.extend = CAIRO_EXTEND_NONE; + mask_attr.filter = CAIRO_FILTER_NEAREST; + mask_attr.x_offset = 0; + mask_attr.y_offset = 0; + + _cairo_surface_composite_fixup_unbounded (&self->base, + src_attr, src->width, src->height, + &mask_attr, x2 - x1, y2 - y1, + src_x, src_y, + dst_x - x1, dst_y - y1, + dst_x, dst_y, width, height); +} + static cairo_int_status_t _cairo_xlib_surface_show_glyphs (cairo_scaled_font_t *scaled_font, cairo_operator_t operator, @@ -2462,6 +2663,13 @@ _cairo_xlib_surface_show_glyphs (cairo_scaled_font_t *scaled_font, glyphs, entries, num_glyphs); } + if (!_cairo_operator_bounded (operator)) + _show_glyphs_fixup_unbounded (self, + &attributes, src, + glyphs, entries, num_glyphs, + source_x, source_y, + dest_x, dest_y, width, height); + UNLOCK: _unlock_xlib_glyphset_caches (cache); diff --git a/src/cairoint.h b/src/cairoint.h index f6dabd816..b0a78784c 100644 --- a/src/cairoint.h +++ b/src/cairoint.h @@ -879,7 +879,9 @@ struct _cairo_image_surface { /* libic-specific fields */ cairo_format_t format; unsigned char *data; - int owns_data; + cairo_bool_t owns_data; + cairo_bool_t has_clip; + int width; int height; @@ -1312,6 +1314,8 @@ _cairo_gstate_glyph_path (cairo_gstate_t *gstate, int num_glyphs, cairo_path_fixed_t *path); +cairo_bool_t +_cairo_operator_bounded (cairo_operator_t operator); /* cairo_color.c */ cairo_private const cairo_color_t * @@ -1695,6 +1699,23 @@ _cairo_surface_show_glyphs (cairo_scaled_font_t *scaled_font, const cairo_glyph_t *glyphs, int num_glyphs); +void +_cairo_surface_composite_fixup_unbounded (cairo_surface_t *dst, + cairo_surface_attributes_t *src_attr, + int src_width, + int src_height, + cairo_surface_attributes_t *mask_attr, + int mask_width, + int mask_height, + 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_image_surface.c */ cairo_private cairo_format_t diff --git a/test/Makefile.am b/test/Makefile.am index 8a2db8524..29ccb8ecc 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -2,6 +2,7 @@ TESTS = \ a8-mask \ clip-nesting \ +clip-operator \ clip-twice \ composite-integer-translate-source \ composite-integer-translate-over \ @@ -41,6 +42,7 @@ text-rotate \ transforms \ translate-show-surface \ trap-clip \ +unbounded-operator \ user-data \ rel-path @@ -62,6 +64,7 @@ endif EXTRA_DIST = \ a8-mask.png \ clip-nesting-ref.png \ +clip-operator-ref.png \ clip-twice-ref.png \ composite-integer-translate-source-ref.png \ composite-integer-translate-over-ref.png \ @@ -96,6 +99,7 @@ text-antialias-none-ref.png \ transforms-ref.png \ translate-show-surface-ref.png \ trap-clip-ref.png \ +unbounded-operator-ref.png \ rel-path-ref.png # Any test for which the code committed to CVS is expected to fail @@ -153,6 +157,7 @@ LDADDS = libcairotest.la $(top_builddir)/src/libcairo.la # from autogen.sh. My, but this is painful... a8_mask_LDADD = $(LDADDS) clip_nesting_LDADD = $(LDADDS) +clip_operator_LDADD = $(LDADDS) clip_twice_LDADD = $(LDADDS) composite_integer_translate_source_LDADD = $(LDADDS) composite_integer_translate_over_LDADD = $(LDADDS) @@ -195,6 +200,7 @@ text_rotate_LDADD = $(LDADDS) transforms_LDADD = $(LDADDS) translate_show_surface_LDADD = $(LDADDS) trap_clip_LDADD = $(LDADDS) +unbounded_operator_LDADD = $(LDADDS) user_data_LDADD = $(LDADDS) rel_path_LDADD = $(LDADDS) xlib_surface_LDADD = $(LDADDS) diff --git a/test/clip-operator.c b/test/clip-operator.c new file mode 100644 index 000000000..6cc5216c2 --- /dev/null +++ b/test/clip-operator.c @@ -0,0 +1,205 @@ +/* + * Copyright © 2005 Red Hat, Inc. + * + * Permission to use, copy, modify, distribute, and sell this software + * and its documentation for any purpose is hereby granted without + * fee, provided that the above copyright notice appear in all copies + * and that both that copyright notice and this permission notice + * appear in supporting documentation, and that the name of + * Red Hat, Inc. not be used in advertising or publicity pertaining to + * distribution of the software without specific, written prior + * permission. Red Hat, Inc. makes no representations about the + * suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * RED HAT, INC. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL RED HAT, INC. BE LIABLE FOR ANY SPECIAL, + * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR + * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Author: Kristian Høgsberg + */ + +#include +#include "cairo-test.h" +#include + +#define WIDTH 64 +#define HEIGHT 64 +#define PAD 10 + +const char png_filename[] = "romedalen.png"; + +static void +draw_mask (cairo_t *cr, int x, int y) +{ + cairo_surface_t *mask_surface; + cairo_t *cr2; + + double width = (int)(0.9 * WIDTH); + double height = (int)(0.9 * HEIGHT); + x += 0.05 * WIDTH; + y += 0.05 * HEIGHT; + + mask_surface = cairo_surface_create_similar (cairo_get_target (cr), + CAIRO_CONTENT_ALPHA, + width, height); + cr2 = cairo_create (mask_surface); + + cairo_save (cr2); + cairo_set_source_rgba (cr2, 0, 0, 0, 0); /* transparent */ + cairo_set_operator (cr2, CAIRO_OPERATOR_SOURCE); + cairo_paint (cr2); + cairo_restore (cr2); + + cairo_set_source_rgb (cr2, 1, 1, 1); /* white */ + + cairo_arc (cr2, 0.5 * width, 0.5 * height, 0.45 * height, 0, 2 * M_PI); + cairo_fill (cr2); + + cairo_destroy (cr2); + + cairo_mask_surface (cr, mask_surface, x, y); + + cairo_surface_destroy (mask_surface); +} + +static void +draw_glyphs (cairo_t *cr, int x, int y) +{ + cairo_text_extents_t extents; + + cairo_set_font_size (cr, 0.8 * HEIGHT); + + cairo_text_extents (cr, "FG", &extents); + cairo_move_to (cr, + x + (WIDTH - extents.width) / 2 - extents.x_bearing, + y + (HEIGHT - extents.height) / 2 - extents.y_bearing); + cairo_show_text (cr, "FG"); +} + +static void +draw_polygon (cairo_t *cr, int x, int y) +{ + double width = (int)(0.9 * WIDTH); + double height = (int)(0.9 * HEIGHT); + x += 0.05 * WIDTH; + y += 0.05 * HEIGHT; + + cairo_new_path (cr); + cairo_move_to (cr, x, y); + cairo_line_to (cr, x, y + height); + cairo_line_to (cr, x + width / 2, y + 3 * height / 4); + cairo_line_to (cr, x + width, y + height); + cairo_line_to (cr, x + width, y); + cairo_line_to (cr, x + width / 2, y + height / 4); + cairo_close_path (cr); + cairo_fill (cr); +} + +static void +draw_rects (cairo_t *cr, int x, int y) +{ + double block_width = (int)(0.33 * WIDTH + 0.5); + double block_height = (int)(0.33 * HEIGHT + 0.5); + int i, j; + + for (i = 0; i < 3; i++) + for (j = 0; j < 3; j++) + if ((i + j) % 2 == 0) + cairo_rectangle (cr, + x + block_width * i, y + block_height * j, + block_width, block_height); + + cairo_fill (cr); +} + +static void (*draw_funcs[])(cairo_t *cr, int x, int y) = { + draw_mask, + draw_glyphs, + draw_polygon, + draw_rects +}; + +#define N_OPERATORS (1 + CAIRO_OPERATOR_SATURATE - CAIRO_OPERATOR_CLEAR) + +#define ARRAY_SIZE(a) (sizeof (a) / sizeof ((a)[0])) +#define IMAGE_WIDTH (N_OPERATORS * (WIDTH + PAD) + PAD) +#define IMAGE_HEIGHT (ARRAY_SIZE (draw_funcs) * (HEIGHT + PAD) + PAD) + +static cairo_test_t test = { + "clip-operator", + "Surface clipping with different operators", + IMAGE_WIDTH, IMAGE_HEIGHT +}; + +static cairo_test_status_t +draw (cairo_t *cr, int width, int height) +{ + int j, x, y; + cairo_operator_t op; + cairo_font_options_t *font_options; + cairo_pattern_t *pattern; + + cairo_select_font_face (cr, "Bitstream Vera Sans", + CAIRO_FONT_SLANT_NORMAL, + CAIRO_FONT_WEIGHT_NORMAL); + cairo_set_font_size (cr, 0.9 * HEIGHT); + + font_options = cairo_font_options_create (); + + cairo_font_options_set_hint_style (font_options, CAIRO_HINT_STYLE_NONE); + cairo_font_options_set_antialias (font_options, CAIRO_ANTIALIAS_GRAY); + + cairo_set_font_options (cr, font_options); + cairo_font_options_destroy (font_options); + + for (j = 0; j < ARRAY_SIZE (draw_funcs); j++) { + for (op = CAIRO_OPERATOR_CLEAR; op <= CAIRO_OPERATOR_SATURATE; op++) { + x = op * (WIDTH + PAD) + PAD; + y = j * (HEIGHT + PAD) + PAD; + + cairo_save (cr); + + pattern = cairo_pattern_create_linear (x + WIDTH, y, + x, y + HEIGHT); + cairo_pattern_add_color_stop_rgba (pattern, 0.2, + 0.0, 0.0, 1.0, 1.0); /* Solid blue */ + cairo_pattern_add_color_stop_rgba (pattern, 0.8, + 0.0, 0.0, 1.0, 0.0); /* Transparent blue */ + cairo_set_source (cr, pattern); + cairo_pattern_destroy (pattern); + + cairo_rectangle (cr, x, y, WIDTH, HEIGHT); + cairo_fill (cr); + + cairo_set_operator (cr, op); + cairo_set_source_rgb (cr, 1.0, 0.0, 0.0); + + cairo_move_to (cr, x, y); + cairo_line_to (cr, x + WIDTH, y); + cairo_line_to (cr, x, y + HEIGHT); + cairo_clip (cr); + + draw_funcs[j] (cr, x, y); + if (cairo_status (cr)) + cairo_test_log ("%d %d HERE!\n", op, j); + + cairo_restore (cr); + } + } + + if (cairo_status (cr) != CAIRO_STATUS_SUCCESS) + cairo_test_log ("%d %d .HERE!\n", op, j); + + return CAIRO_TEST_SUCCESS; +} + +int +main (void) +{ + return cairo_test (&test, draw); +} diff --git a/test/composite-integer-translate-over-repeat.c b/test/composite-integer-translate-over-repeat.c index ed55f63a6..61a0ea71a 100644 --- a/test/composite-integer-translate-over-repeat.c +++ b/test/composite-integer-translate-over-repeat.c @@ -44,7 +44,7 @@ draw (cairo_t *cr, int width, int height) cairo_fill (cr); cairo_translate (cr, OFFSET, OFFSET); - cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); + cairo_set_operator (cr, CAIRO_OPERATOR_OVER); cairo_set_source (cr, pat); cairo_rectangle (cr, 0, 0, SIZE - OFFSET, SIZE - OFFSET); cairo_fill (cr); diff --git a/test/unbounded-operator.c b/test/unbounded-operator.c new file mode 100644 index 000000000..45535b976 --- /dev/null +++ b/test/unbounded-operator.c @@ -0,0 +1,201 @@ +/* + * Copyright © 2005 Red Hat, Inc. + * + * Permission to use, copy, modify, distribute, and sell this software + * and its documentation for any purpose is hereby granted without + * fee, provided that the above copyright notice appear in all copies + * and that both that copyright notice and this permission notice + * appear in supporting documentation, and that the name of + * Red Hat, Inc. not be used in advertising or publicity pertaining to + * distribution of the software without specific, written prior + * permission. Red Hat, Inc. makes no representations about the + * suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * RED HAT, INC. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL RED HAT, INC. BE LIABLE FOR ANY SPECIAL, + * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR + * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Authors: Kristian Høgsberg + * Owen Taylor + */ + +#include +#include "cairo-test.h" +#include + +#define WIDTH 64 +#define HEIGHT 64 +#define PAD 10 + +static void +draw_mask (cairo_t *cr, int x, int y) +{ + cairo_surface_t *mask_surface; + cairo_t *cr2; + + double width = (int)(0.9 * WIDTH); + double height = (int)(0.9 * HEIGHT); + x += 0.05 * WIDTH; + y += 0.05 * HEIGHT; + + mask_surface = cairo_surface_create_similar (cairo_get_target (cr), + CAIRO_CONTENT_ALPHA, + width, height); + cr2 = cairo_create (mask_surface); + + cairo_save (cr2); + cairo_set_source_rgba (cr2, 0, 0, 0, 0); /* transparent */ + cairo_set_operator (cr2, CAIRO_OPERATOR_SOURCE); + cairo_paint (cr2); + cairo_restore (cr2); + + cairo_set_source_rgb (cr2, 1, 1, 1); /* white */ + + cairo_arc (cr2, 0.5 * width, 0.5 * height, 0.45 * height, 0, 2 * M_PI); + cairo_fill (cr2); + + cairo_destroy (cr2); + + cairo_mask_surface (cr, mask_surface, x, y); + + cairo_surface_destroy (mask_surface); +} + +static void +draw_glyphs (cairo_t *cr, int x, int y) +{ + cairo_text_extents_t extents; + + cairo_set_font_size (cr, 0.8 * HEIGHT); + + cairo_text_extents (cr, "FG", &extents); + cairo_move_to (cr, + x + (WIDTH - extents.width) / 2 - extents.x_bearing, + y + (HEIGHT - extents.height) / 2 - extents.y_bearing); + cairo_show_text (cr, "FG"); +} + +static void +draw_polygon (cairo_t *cr, int x, int y) +{ + double width = (int)(0.9 * WIDTH); + double height = (int)(0.9 * HEIGHT); + x += 0.05 * WIDTH; + y += 0.05 * HEIGHT; + + cairo_new_path (cr); + cairo_move_to (cr, x, y); + cairo_line_to (cr, x, y + height); + cairo_line_to (cr, x + width / 2, y + 3 * height / 4); + cairo_line_to (cr, x + width, y + height); + cairo_line_to (cr, x + width, y); + cairo_line_to (cr, x + width / 2, y + height / 4); + cairo_close_path (cr); + cairo_fill (cr); +} + +static void +draw_rects (cairo_t *cr, int x, int y) +{ + double block_width = (int)(0.33 * WIDTH + 0.5); + double block_height = (int)(0.33 * HEIGHT + 0.5); + int i, j; + + for (i = 0; i < 3; i++) + for (j = 0; j < 3; j++) + if ((i + j) % 2 == 0) + cairo_rectangle (cr, + x + block_width * i, y + block_height * j, + block_width, block_height); + + cairo_fill (cr); +} + +static void (*draw_funcs[])(cairo_t *cr, int x, int y) = { + draw_mask, + draw_glyphs, + draw_polygon, + draw_rects +}; + +static cairo_operator_t operators[] = { + CAIRO_OPERATOR_CLEAR, CAIRO_OPERATOR_SOURCE, CAIRO_OPERATOR_IN, + CAIRO_OPERATOR_OUT, CAIRO_OPERATOR_DEST_IN, CAIRO_OPERATOR_DEST_ATOP +}; + +#define ARRAY_SIZE(a) (sizeof (a) / sizeof ((a)[0])) +#define IMAGE_WIDTH (ARRAY_SIZE (operators) * (WIDTH + PAD) + PAD) +#define IMAGE_HEIGHT (ARRAY_SIZE (draw_funcs) * (HEIGHT + PAD) + PAD) + +static cairo_test_t test = { + "unbounded-operator", + "Operators with an effect for transparent source/mask", + IMAGE_WIDTH, IMAGE_HEIGHT +}; + +static cairo_test_status_t +draw (cairo_t *cr, int width, int height) +{ + int i, j, x, y; + cairo_font_options_t *font_options; + cairo_pattern_t *pattern; + + cairo_select_font_face (cr, "Bitstream Vera Sans", + CAIRO_FONT_SLANT_NORMAL, + CAIRO_FONT_WEIGHT_NORMAL); + + font_options = cairo_font_options_create (); + + cairo_font_options_set_hint_style (font_options, CAIRO_HINT_STYLE_NONE); + cairo_font_options_set_antialias (font_options, CAIRO_ANTIALIAS_GRAY); + + cairo_set_font_options (cr, font_options); + cairo_font_options_destroy (font_options); + + for (j = 0; j < ARRAY_SIZE (draw_funcs); j++) { + for (i = 0; i < ARRAY_SIZE (operators); i++) { + x = i * (WIDTH + PAD) + PAD; + y = j * (HEIGHT + PAD) + PAD; + + cairo_save (cr); + + pattern = cairo_pattern_create_linear (x + WIDTH, y, + x, y + HEIGHT); + cairo_pattern_add_color_stop_rgba (pattern, 0.2, + 0.0, 0.0, 1.0, 1.0); /* Solid blue */ + cairo_pattern_add_color_stop_rgba (pattern, 0.8, + 0.0, 0.0, 1.0, 0.0); /* Transparent blue */ + cairo_set_source (cr, pattern); + cairo_pattern_destroy (pattern); + + cairo_rectangle (cr, x, y, WIDTH, HEIGHT); + cairo_fill_preserve (cr); + cairo_clip (cr); + + cairo_set_operator (cr, operators[i]); + cairo_set_source_rgb (cr, 1.0, 0.0, 0.0); + + draw_funcs[j] (cr, x, y); + if (cairo_status (cr)) + cairo_test_log ("%d %d HERE!\n", i, j); + + cairo_restore (cr); + } + } + + if (cairo_status (cr) != CAIRO_STATUS_SUCCESS) + cairo_test_log ("%d %d .HERE!\n", i, j); + + return CAIRO_TEST_SUCCESS; +} + +int +main (void) +{ + return cairo_test (&test, draw); +}