[cairo-commit] 3 commits - src/cairo.c src/cairo-clip.c src/cairo-clip-private.h src/cairo-gstate.c src/cairo-gstate-private.h src/cairo.h src/cairoint.h test/Makefile.am test/push-group.c test/push-group-ref.png test/push-group-rgb24-ref.png

Carl Worth cworth at kemper.freedesktop.org
Thu May 4 03:07:39 PDT 2006


 src/cairo-clip-private.h      |    5 +
 src/cairo-clip.c              |   77 +++++++++++++++-
 src/cairo-gstate-private.h    |    4 
 src/cairo-gstate.c            |  194 ++++++++++++++++++++++++++----------------
 src/cairo.c                   |  157 ++++++++++++++++++++++++++++++---
 src/cairo.h                   |   51 ++++++-----
 src/cairoint.h                |   19 ++++
 test/Makefile.am              |    3 
 test/push-group-ref.png       |binary
 test/push-group-rgb24-ref.png |binary
 test/push-group.c             |  119 +++++++++++++++++++++++++
 11 files changed, 517 insertions(+), 112 deletions(-)

New commits:
diff-tree fb7f7c2f27f0823d7702f960204d6e638d697624 (from 7fa3c6eee5a19d3486a200a6a14b96210f2b6dab)
Author: Vladimir Vukicevic <vladimir at pobox.com>
Date:   Tue Mar 14 15:56:08 2006 -0800

    Fix up clip at pop_group time, to keep it in surface backend coordinates
    
    We keep the clip in surface-backend coordinates always, so it needs
    fixing whenever we change the target surface out in the gstate.  The
    only place this happens is in push_group, so fix it as part of
    gstate_redirect().

diff --git a/src/cairo-clip-private.h b/src/cairo-clip-private.h
index 91cc9e9..fb5f615 100644
--- a/src/cairo-clip-private.h
+++ b/src/cairo-clip-private.h
@@ -87,6 +87,11 @@ _cairo_clip_fini (cairo_clip_t *clip);
 cairo_private void
 _cairo_clip_init_copy (cairo_clip_t *clip, cairo_clip_t *other);
 
+cairo_private void
+_cairo_clip_init_deep_copy (cairo_clip_t    *clip,
+                            cairo_clip_t    *other,
+                            cairo_surface_t *target);
+
 cairo_private cairo_status_t
 _cairo_clip_reset (cairo_clip_t *clip);
 
diff --git a/src/cairo-clip.c b/src/cairo-clip.c
index 7edb915..669c8b0 100644
--- a/src/cairo-clip.c
+++ b/src/cairo-clip.c
@@ -234,8 +234,7 @@ _cairo_clip_intersect_path (cairo_clip_t
 			    cairo_path_fixed_t *path,
 			    cairo_fill_rule_t   fill_rule,
 			    double              tolerance,
-			    cairo_antialias_t   antialias,
-			    cairo_surface_t    *target)
+			    cairo_antialias_t   antialias)
 {
     cairo_clip_path_t *clip_path;
     cairo_status_t status;
@@ -259,7 +258,6 @@ _cairo_clip_intersect_path (cairo_clip_t
     clip_path->antialias = antialias;
     clip_path->prev = clip->path;
     clip->path = clip_path;
-    clip->serial = _cairo_surface_allocate_clip_serial (target);
 
     return CAIRO_STATUS_SUCCESS;
 }
@@ -447,10 +445,14 @@ _cairo_clip_clip (cairo_clip_t       *cl
     
     status = _cairo_clip_intersect_path (clip,
 					 path, fill_rule, tolerance,
-					 antialias, target);
+					 antialias);
+    if (status == CAIRO_STATUS_SUCCESS)
+        clip->serial = _cairo_surface_allocate_clip_serial (target);
+
     if (status != CAIRO_INT_STATUS_UNSUPPORTED)
 	return status;
 
+
     _cairo_traps_init (&traps);
     status = _cairo_path_fixed_fill_to_traps (path,
 					      fill_rule,
@@ -472,3 +474,70 @@ _cairo_clip_clip (cairo_clip_t       *cl
 
     return status;
 }
+
+void
+_cairo_clip_translate (cairo_clip_t  *clip,
+                       cairo_fixed_t  tx,
+                       cairo_fixed_t  ty)
+{
+    if (clip->region) {
+        pixman_region_translate (clip->region,
+                                 _cairo_fixed_integer_part (tx),
+                                 _cairo_fixed_integer_part (ty));
+    }
+
+    if (clip->surface) {
+        clip->surface_rect.x += _cairo_fixed_integer_part (tx);
+        clip->surface_rect.y += _cairo_fixed_integer_part (ty);
+    }
+
+    if (clip->path) {
+        cairo_clip_path_t *clip_path = clip->path;
+        while (clip_path) {
+            _cairo_path_fixed_offset (&clip_path->path, tx, ty);
+            clip_path = clip_path->prev;
+        }
+    }
+}
+
+static void
+_cairo_clip_path_reapply_clip_path (cairo_clip_t      *clip,
+                                    cairo_clip_path_t *clip_path)
+{
+    if (clip_path->prev)
+        _cairo_clip_path_reapply_clip_path (clip, clip_path->prev);
+
+    _cairo_clip_intersect_path (clip,
+                                &clip_path->path,
+                                clip_path->fill_rule,
+                                clip_path->tolerance,
+                                clip_path->antialias);
+}
+
+void
+_cairo_clip_init_deep_copy (cairo_clip_t    *clip,
+                            cairo_clip_t    *other,
+                            cairo_surface_t *target)
+{
+    _cairo_clip_init (clip, target);
+
+    if (other->mode != clip->mode) {
+        /* We should reapply the original clip path in this case, and let
+         * whatever the right handling is happen */
+    } else {
+        if (other->region) {
+            clip->region = pixman_region_create ();
+            pixman_region_copy (clip->region, other->region);
+        }
+
+        if (other->surface) {
+            _cairo_surface_clone_similar (target, clip->surface, &clip->surface);
+            clip->surface_rect = other->surface_rect;
+        }
+
+        if (other->path) {
+            _cairo_clip_path_reapply_clip_path (clip, other->path);
+        }
+    }
+}
+
diff --git a/src/cairo-gstate.c b/src/cairo-gstate.c
index bf027e3..6251a35 100644
--- a/src/cairo-gstate.c
+++ b/src/cairo-gstate.c
@@ -303,30 +303,14 @@ _cairo_gstate_redirect_target (cairo_gst
      * since its ref is now owned by gstate->parent_target */
     gstate->target = cairo_surface_reference (child);
 
-    /* Check that the new surface's clip mode is compatible */
-    if (gstate->clip.mode != _cairo_surface_get_clip_mode (child)) {
-	/* clip is not compatible; try to recreate it */
-	/* XXX - saving the clip path always might be useful here,
-	 * so that we could recover non-CLIP_MODE_PATH clips */
-	if (gstate->clip.mode == CAIRO_CLIP_MODE_PATH) {
-	    cairo_clip_t saved_clip = gstate->clip;
-
-	    _cairo_clip_init (&gstate->clip, child);
-
-	    /* unwind the path and re-apply */
-	    _cairo_gstate_recursive_apply_clip_path (gstate, saved_clip.path);
-
-	    _cairo_clip_fini (&saved_clip);
-	} else {
-	    /* uh, not sure what to do here.. */
-	    _cairo_clip_fini (&gstate->clip);
-	    _cairo_clip_init (&gstate->clip, child);
-	}
-    } else {
-	/* clip is compatible; allocate a new serial for the new surface. */
-	if (gstate->clip.serial)
-	    gstate->clip.serial = _cairo_surface_allocate_clip_serial (child);
-    }
+    _cairo_clip_fini (&gstate->clip);
+    _cairo_clip_init_deep_copy (&gstate->clip, &gstate->next->clip, child);
+
+    /* The clip is in surface backend coordinates for the previous target;
+     * translate it into the child's backend coordinates. */
+    _cairo_clip_translate (&gstate->clip,
+                           _cairo_fixed_from_double (child->device_x_offset - gstate->parent_target->device_x_offset),
+                           _cairo_fixed_from_double (child->device_y_offset - gstate->parent_target->device_y_offset));
 }
 
 /**
diff --git a/src/cairo.c b/src/cairo.c
index 4619e54..ba3b7bf 100644
--- a/src/cairo.c
+++ b/src/cairo.c
@@ -385,8 +385,13 @@ cairo_push_group_with_content (cairo_t *
 	goto bail;
 
     /* Set device offsets on the new surface so that logically it appears at
-     * the same location on the parent surface. */
-    cairo_surface_set_device_offset (group_surface, -extents.x, -extents.y);
+     * the same location on the parent surface -- when we pop_group this,
+     * the source pattern will get fixed up for the appropriate target surface
+     * device offsets, so we want to set our own surface offsets from /that/,
+     * and not from the device origin. */
+    cairo_surface_set_device_offset (group_surface,
+                                     cr->gstate->target->device_x_offset - extents.x,
+                                     cr->gstate->target->device_y_offset - extents.y);
 
     /* create a new gstate for the redirect */
     cairo_save (cr);
diff-tree 7fa3c6eee5a19d3486a200a6a14b96210f2b6dab (from ee02c1b91452e5b34af4f02d7132840a4bf44fe1)
Author: Vladimir Vukicevic <vladimir at pobox.com>
Date:   Wed Mar 1 17:19:45 2006 +0100

    Add push-group test and reference images

diff --git a/test/push-group-ref.png b/test/push-group-ref.png
new file mode 100644
index 0000000..ea66f0a
Binary files /dev/null and b/test/push-group-ref.png differ
diff --git a/test/push-group-rgb24-ref.png b/test/push-group-rgb24-ref.png
new file mode 100644
index 0000000..5d2cbbe
Binary files /dev/null and b/test/push-group-rgb24-ref.png differ
diff --git a/test/push-group.c b/test/push-group.c
new file mode 100644
index 0000000..e88c145
--- /dev/null
+++ b/test/push-group.c
@@ -0,0 +1,119 @@
+/*
+ * Copyright © 2005 Mozilla Corporation
+ *
+ * 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
+ * Mozilla Corporation not be used in advertising or publicity pertaining to
+ * distribution of the software without specific, written prior
+ * permission. Mozilla Corporation makes no representations about the
+ * suitability of this software for any purpose.  It is provided "as
+ * is" without express or implied warranty.
+ *
+ * MOZILLA CORPORATION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
+ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL MOZILLA CORPORATION 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: Vladimir Vukicevic <vladimir at pobox.com>
+ */
+
+
+#include "cairo-test.h"
+
+
+#define UNIT_SIZE 100
+#define PAD 5
+#define INNER_PAD 10
+
+#define WIDTH (UNIT_SIZE + PAD) + PAD
+#define HEIGHT (UNIT_SIZE + PAD) + PAD
+
+cairo_test_t test = {
+    "push-group",
+    "Verify that cairo_push_group works.",
+    WIDTH, HEIGHT
+};
+
+static cairo_test_status_t
+draw (cairo_t *cr, int width, int height)
+{
+    cairo_pattern_t *gradient;
+    int i, j;
+
+    gradient = cairo_pattern_create_linear (UNIT_SIZE - (INNER_PAD*2), 0,
+                                            UNIT_SIZE - (INNER_PAD*2), UNIT_SIZE - (INNER_PAD*2));
+    cairo_pattern_add_color_stop_rgba (gradient, 0.0, 0.3, 0.3, 0.3, 1.0);
+    cairo_pattern_add_color_stop_rgba (gradient, 1.0, 1.0, 1.0, 1.0, 1.0);
+
+    for (j = 0; j < 1; j++) {
+        for (i = 0; i < 1; i++) {
+            double x = (i * UNIT_SIZE) + (i + 1) * PAD;
+            double y = (j * UNIT_SIZE) + (j + 1) * PAD;
+            cairo_pattern_t *group_pattern;
+
+            cairo_save (cr);
+
+            cairo_translate (cr, x, y);
+
+            /* draw a gradient background */
+            cairo_save (cr);
+            cairo_translate (cr, INNER_PAD, INNER_PAD);
+            cairo_new_path (cr);
+            cairo_rectangle (cr, 0, 0,
+                             UNIT_SIZE - (INNER_PAD*2), UNIT_SIZE - (INNER_PAD*2));
+            cairo_set_source (cr, gradient);
+            cairo_fill (cr);
+            cairo_restore (cr);
+
+            /* clip to the unit size */
+            cairo_rectangle (cr, 0, 0,
+                             UNIT_SIZE, UNIT_SIZE);
+            cairo_clip (cr);
+
+            cairo_rectangle (cr, 0, 0,
+                             UNIT_SIZE, UNIT_SIZE);
+            cairo_set_source_rgba (cr, 0, 0, 0, 1);
+            cairo_set_line_width (cr, 2);
+            cairo_stroke (cr);
+
+            /* start a group */
+            cairo_push_group (cr);
+
+            /* draw diamond */
+            cairo_move_to (cr, UNIT_SIZE / 2, 0);
+            cairo_line_to (cr, UNIT_SIZE    , UNIT_SIZE / 2);
+            cairo_line_to (cr, UNIT_SIZE / 2, UNIT_SIZE);
+            cairo_line_to (cr, 0            , UNIT_SIZE / 2);
+            cairo_close_path (cr);
+            cairo_set_source_rgba (cr, 0, 0, 1, 1);
+            cairo_fill (cr);
+
+            /* draw circle */
+            cairo_arc (cr,
+                       UNIT_SIZE / 2, UNIT_SIZE / 2,
+                       UNIT_SIZE / 3.5,
+                       0, M_PI * 2);
+            cairo_set_source_rgba (cr, 1, 0, 0, 1);
+            cairo_fill (cr);
+
+            cairo_pop_group_to_source (cr);
+            cairo_paint_with_alpha (cr, 0.5);
+
+            cairo_restore (cr);
+        }
+    }
+
+    return CAIRO_TEST_SUCCESS;
+}
+
+int
+main (void)
+{
+    return cairo_test (&test, draw);
+}
diff-tree ee02c1b91452e5b34af4f02d7132840a4bf44fe1 (from 5e708b38e00f6b2066028c60a5526bf5d5e66356)
Author: Vladimir Vukicevic <vladimir at pobox.com>
Date:   Wed Feb 15 13:46:53 2006 -0800

    Implement push_group/pop_group
    
    This patch adds the following API calls:
    
     cairo_push_group
     cairo_push_group_with_content
     cairo_pop_group
     cairo_pop_group_to_source
     cairo_get_group_target
    
    These methods support implementing layers on top of a cairo context,
    allowing for drawing with transparency and temporary buffering.
    cairo_get_group_target allows an app to get access to the actual surface
    created by the last push_group call, in case itneeds to perform native
    drawing to it.

diff --git a/src/cairo-gstate-private.h b/src/cairo-gstate-private.h
index a5d4c06..8cbe238 100644
--- a/src/cairo-gstate-private.h
+++ b/src/cairo-gstate-private.h
@@ -55,7 +55,9 @@ struct _cairo_gstate {
 
     cairo_clip_t clip;
 
-    cairo_surface_t *target;
+    cairo_surface_t *target;		/* The target to which all rendering is directed */
+    cairo_surface_t *parent_target;	/* The previous target which was receiving rendering */
+    cairo_surface_t *original_target;	/* The original target the initial gstate was created with */
 
     cairo_matrix_t ctm;
     cairo_matrix_t ctm_inverse;
diff --git a/src/cairo-gstate.c b/src/cairo-gstate.c
index 1a4ca6a..bf027e3 100644
--- a/src/cairo-gstate.c
+++ b/src/cairo-gstate.c
@@ -119,6 +119,8 @@ _cairo_gstate_init (cairo_gstate_t  *gst
     _cairo_clip_init (&gstate->clip, target);
 
     gstate->target = cairo_surface_reference (target);
+    gstate->parent_target = NULL;
+    gstate->original_target = cairo_surface_reference (target);
 
     _cairo_gstate_identity_matrix (gstate);
     gstate->source_ctm_inverse = gstate->ctm_inverse;
@@ -166,6 +168,9 @@ _cairo_gstate_init_copy (cairo_gstate_t 
     _cairo_clip_init_copy (&gstate->clip, &other->clip);
 
     gstate->target = cairo_surface_reference (other->target);
+    /* parent_target is always set to NULL; it's only ever set by redirect_target */
+    gstate->parent_target = NULL;
+    gstate->original_target = cairo_surface_reference (other->original_target);
 
     gstate->ctm = other->ctm;
     gstate->ctm_inverse = other->ctm_inverse;
@@ -194,6 +199,12 @@ _cairo_gstate_fini (cairo_gstate_t *gsta
     cairo_surface_destroy (gstate->target);
     gstate->target = NULL;
 
+    cairo_surface_destroy (gstate->parent_target);
+    gstate->parent_target = NULL;
+
+    cairo_surface_destroy (gstate->original_target);
+    gstate->target = NULL;
+
     cairo_pattern_destroy (gstate->source);
     gstate->source = NULL;
 }
@@ -241,94 +252,153 @@ _cairo_gstate_clone (cairo_gstate_t *oth
     return gstate;
 }
 
-/* Push rendering off to an off-screen group. */
-/* XXX: Rethinking this API
-cairo_status_t
-_cairo_gstate_begin_group (cairo_gstate_t *gstate)
+static cairo_status_t
+_cairo_gstate_recursive_apply_clip_path (cairo_gstate_t *gstate,
+					 cairo_clip_path_t *cpath)
 {
-    Pixmap pix;
-    unsigned int width, height;
-
-    gstate->parent_surface = gstate->target;
-
-    width = _cairo_surface_get_width (gstate->target);
-    height = _cairo_surface_get_height (gstate->target);
+    cairo_status_t status;
 
-    pix = XCreatePixmap (gstate->dpy,
-			 _cairo_surface_get_drawable (gstate->target),
-			 width, height,
-			 _cairo_surface_get_depth (gstate->target));
-    if (pix == 0)
-	return CAIRO_STATUS_NO_MEMORY;
+    if (cpath == NULL)
+	return CAIRO_STATUS_SUCCESS;
 
-    gstate->target = cairo_surface_create (gstate->dpy);
-    if (gstate->target->status)
-	return gstate->target->status;
-
-    _cairo_surface_set_drawableWH (gstate->target, pix, width, height);
-
-    status = _cairo_surface_fill_rectangle (gstate->target,
-                                   CAIRO_OPERATOR_CLEAR,
-				   CAIRO_COLOR_TRANSPARENT,
-				   0, 0,
-			           _cairo_surface_get_width (gstate->target),
-				   _cairo_surface_get_height (gstate->target));
-    if (status)				 
-        return status;
+    status = _cairo_gstate_recursive_apply_clip_path (gstate, cpath->prev);
+    if (status)
+	return status;
 
-    return CAIRO_STATUS_SUCCESS;
+    return _cairo_clip_clip (&gstate->clip,
+			     &cpath->path,
+			     cpath->fill_rule,
+			     cpath->tolerance,
+			     cpath->antialias,
+			     gstate->target);
 }
-*/
 
-/* Complete the current offscreen group, composing its contents onto the parent surface. */
-/* XXX: Rethinking this API
-cairo_status_t
-_cairo_gstate_end_group (cairo_gstate_t *gstate)
+/**
+ * _cairo_gstate_redirect_target:
+ * @gstate: a #cairo_gstate_t
+ * @child: the new child target
+ *
+ * Redirect @gstate rendering to a "child" target. The original
+ * "parent" target with which the gstate was created will not be
+ * affected. See _cairo_gstate_get_target().
+ *
+ * Unless the redirected target has the same device offsets as the
+ * original #cairo_t target, the clip will be INVALID after this call,
+ * and the caller should either recreate or reset the clip.
+ **/
+void
+_cairo_gstate_redirect_target (cairo_gstate_t *gstate, cairo_surface_t *child)
 {
-    Pixmap pix;
-    cairo_color_t mask_color;
-    cairo_surface_t mask;
-
-    if (gstate->parent_surface == NULL)
-	return CAIRO_STATUS_INVALID_POP_GROUP;
-
-    _cairo_surface_init (&mask, gstate->dpy);
-    _cairo_color_init (&mask_color);
-
-    _cairo_surface_set_solid_color (&mask, &mask_color);
-
-    * XXX: This could be made much more efficient by using
-       _cairo_surface_get_damaged_width/Height if cairo_surface_t actually kept
-       track of such informaton. *
-    _cairo_surface_composite (gstate->op,
-			      gstate->target,
-			      mask,
-			      gstate->parent_surface,
-			      0, 0,
-			      0, 0,
-			      0, 0,
-			      _cairo_surface_get_width (gstate->target),
-			      _cairo_surface_get_height (gstate->target));
-
-    _cairo_surface_fini (&mask);
-
-    pix = _cairo_surface_get_drawable (gstate->target);
-    XFreePixmap (gstate->dpy, pix);
-
-    cairo_surface_destroy (gstate->target);
-    gstate->target = gstate->parent_surface;
-    gstate->parent_surface = NULL;
+    /* If this gstate is already redirected, this is an error; we need a
+     * new gstate to be able to redirect */
+    assert (gstate->parent_target == NULL);
+
+    /* Set up our new parent_target based on our current target;
+     * gstate->parent_target will take the ref that is held by gstate->target
+     */
+    cairo_surface_destroy (gstate->parent_target);
+    gstate->parent_target = gstate->target;
+
+    /* Now set up our new target; we overwrite gstate->target directly,
+     * since its ref is now owned by gstate->parent_target */
+    gstate->target = cairo_surface_reference (child);
+
+    /* Check that the new surface's clip mode is compatible */
+    if (gstate->clip.mode != _cairo_surface_get_clip_mode (child)) {
+	/* clip is not compatible; try to recreate it */
+	/* XXX - saving the clip path always might be useful here,
+	 * so that we could recover non-CLIP_MODE_PATH clips */
+	if (gstate->clip.mode == CAIRO_CLIP_MODE_PATH) {
+	    cairo_clip_t saved_clip = gstate->clip;
+
+	    _cairo_clip_init (&gstate->clip, child);
+
+	    /* unwind the path and re-apply */
+	    _cairo_gstate_recursive_apply_clip_path (gstate, saved_clip.path);
+
+	    _cairo_clip_fini (&saved_clip);
+	} else {
+	    /* uh, not sure what to do here.. */
+	    _cairo_clip_fini (&gstate->clip);
+	    _cairo_clip_init (&gstate->clip, child);
+	}
+    } else {
+	/* clip is compatible; allocate a new serial for the new surface. */
+	if (gstate->clip.serial)
+	    gstate->clip.serial = _cairo_surface_allocate_clip_serial (child);
+    }
+}
 
-    return CAIRO_STATUS_SUCCESS;
+/**
+ * _cairo_gstate_is_redirected
+ * @gstate: a #cairo_gstate_t
+ *
+ * Return value: TRUE if the gstate is redirected to a traget
+ * different than the original, FALSE otherwise.
+ **/
+cairo_bool_t
+_cairo_gstate_is_redirected (cairo_gstate_t *gstate)
+{
+    return (gstate->target != gstate->original_target);
 }
-*/
 
+/**
+ * _cairo_gstate_get_target:
+ * @gstate: a #cairo_gstate_t
+ *
+ * Return the current drawing target; if drawing is not redirected,
+ * this will be the same as _cairo_gstate_get_original_target().
+ *
+ * Return value: the current target surface
+ **/
 cairo_surface_t *
 _cairo_gstate_get_target (cairo_gstate_t *gstate)
 {
     return gstate->target;
 }
 
+/**
+ * _cairo_gstate_get_parent_target:
+ * @gstate: a #cairo_gstate_t
+ *
+ * Return the parent surface of the current drawing target surface;
+ * if this particular gstate isn't a redirect gstate, this will return NULL.
+ **/
+cairo_surface_t *
+_cairo_gstate_get_parent_target (cairo_gstate_t *gstate)
+{
+    return gstate->parent_target;
+}
+
+/**
+ * _cairo_gstate_get_original_target:
+ * @gstate: a #cairo_gstate_t
+ *
+ * Return the original target with which @gstate was created. This
+ * function always returns the original target independent of any
+ * child target that may have been set with
+ * _cairo_gstate_redirect_target.
+ *
+ * Return value: the original target surface
+ **/
+cairo_surface_t *
+_cairo_gstate_get_original_target (cairo_gstate_t *gstate)
+{
+    return gstate->original_target;
+}
+
+/**
+ * _cairo_gstate_get_clip:
+ * @gstate: a #cairo_gstate_t
+ *
+ * Return value: a pointer to the gstate's cairo_clip_t structure.
+ */
+cairo_clip_t *
+_cairo_gstate_get_clip (cairo_gstate_t *gstate)
+{
+    return &gstate->clip;
+}
+
 cairo_status_t
 _cairo_gstate_set_source (cairo_gstate_t  *gstate,
 			  cairo_pattern_t *source)
diff --git a/src/cairo.c b/src/cairo.c
index a829dd7..4619e54 100644
--- a/src/cairo.c
+++ b/src/cairo.c
@@ -330,33 +330,133 @@ cairo_restore (cairo_t *cr)
 }
 slim_hidden_def(cairo_restore);
 
-/* XXX: I want to rethink this API
+/**
+ * cairo_push_group:
+ * @cr: a cairo context
+ *
+ * Pushes a CAIRO_CONTENT_COLOR_ALPHA temporary surface onto
+ * the rendering stack, redirecting all rendering into it.
+ * See cairo_push_group_with_content().
+ */
+
 void
 cairo_push_group (cairo_t *cr)
 {
-    if (cr->status)
-	return;
+    cairo_push_group_with_content (cr, CAIRO_CONTENT_COLOR_ALPHA);
+}
+slim_hidden_def(cairo_push_group);
 
-    cr->status = cairoPush (cr);
-    if (cr->status)
-	return;
+/**
+ * cairo_push_group_with_content:
+ * @cr: a cairo context
+ * @content: a %cairo_content_t indicating the type of group that
+ *           will be created
+ *
+ * Pushes a temporary surface onto the rendering stack, redirecting
+ * all rendering into it.  The surface dimensions are the size of
+ * the current clipping bounding box.  Initially, this surface
+ * is painted with CAIRO_OPERATOR_CLEAR.
+ *
+ * cairo_push_group() calls cairo_save() so that any changes to the
+ * graphics state will not be visible after cairo_pop_group() or
+ * cairo_pop_group_with_alpha().  See cairo_pop_group() and
+ * cairo_pop_group_with_alpha().
+ */
 
-    cr->status = _cairo_gstate_begin_group (cr->gstate);
+void
+cairo_push_group_with_content (cairo_t *cr, cairo_content_t content)
+{
+    cairo_status_t status;
+    cairo_rectangle_t extents;
+    cairo_surface_t *group_surface = NULL;
+
+    /* Get the extents that we'll use in creating our new group surface */
+    _cairo_surface_get_extents (_cairo_gstate_get_target (cr->gstate), &extents);
+    status = _cairo_clip_intersect_to_rectangle (_cairo_gstate_get_clip (cr->gstate), &extents);
+    if (status != CAIRO_STATUS_SUCCESS)
+	goto bail;
+
+    group_surface = cairo_surface_create_similar (_cairo_gstate_get_target (cr->gstate),
+						  content,
+						  extents.width,
+						  extents.height);
+    status = cairo_surface_status (group_surface);
+    if (status)
+	goto bail;
+
+    /* Set device offsets on the new surface so that logically it appears at
+     * the same location on the parent surface. */
+    cairo_surface_set_device_offset (group_surface, -extents.x, -extents.y);
+
+    /* create a new gstate for the redirect */
+    cairo_save (cr);
+    if (cr->status)
+	goto bail;
+
+    _cairo_gstate_redirect_target (cr->gstate, group_surface);
+
+bail:
+    cairo_surface_destroy (group_surface);
+    if (status)
+	_cairo_set_error (cr, status);
 }
+slim_hidden_def(cairo_push_group_with_content);
 
-void
+cairo_pattern_t *
 cairo_pop_group (cairo_t *cr)
 {
-    if (cr->status)
-	return;
+    cairo_surface_t *group_surface, *parent_target;
+    cairo_pattern_t *group_pattern = NULL;
+    cairo_matrix_t group_matrix;
+
+    /* Grab the active surfaces */
+    group_surface = _cairo_gstate_get_target (cr->gstate);
+    parent_target = _cairo_gstate_get_parent_target (cr->gstate);
+
+    /* Verify that we are at the right nesting level */
+    if (parent_target == NULL) {
+	_cairo_set_error (cr, CAIRO_STATUS_INVALID_POP_GROUP);
+	return NULL;
+    }
+
+    /* We need to save group_surface before we restore; we don't need
+     * to reference parent_target and original_target, since the
+     * gstate will still hold refs to them once we restore. */
+    cairo_surface_reference (group_surface);
+
+    cairo_restore (cr);
 
-    cr->status = _cairo_gstate_end_group (cr->gstate);
     if (cr->status)
-	return;
+	goto done;
+
+    group_pattern = cairo_pattern_create_for_surface (group_surface);
+    if (!group_pattern) {
+        cr->status = CAIRO_STATUS_NO_MEMORY;
+        goto done;
+    }
+
+    _cairo_gstate_get_matrix (cr->gstate, &group_matrix);
+    cairo_pattern_set_matrix (group_pattern, &group_matrix);
+done:
+    cairo_surface_destroy (group_surface);
 
-    cr->status = cairoPop (cr);
+    return group_pattern;
 }
-*/
+slim_hidden_def(cairo_pop_group);
+
+void
+cairo_pop_group_to_source (cairo_t *cr)
+{
+    cairo_pattern_t *group_pattern;
+
+    group_pattern = cairo_pop_group (cr);
+    if (!group_pattern)
+        return;
+
+    cairo_set_source (cr, group_pattern);
+    cairo_pattern_destroy (group_pattern);
+}
+slim_hidden_def(cairo_pop_group_to_source);
 
 /**
  * cairo_set_operator:
@@ -2495,6 +2595,30 @@ cairo_get_target (cairo_t *cr)
     if (cr->status)
 	return (cairo_surface_t*) &_cairo_surface_nil;
 
+    return _cairo_gstate_get_original_target (cr->gstate);
+}
+
+/**
+ * cairo_get_group_target:
+ * @cr: a cairo context
+ *
+ * Gets the target surface for the current transparency group
+ * started by the last cairo_push_group() call on the cairo
+ * context.
+ *
+ * This function may return NULL if there is no transparency
+ * group on the target.
+ *
+ * Return value: the target group surface, or NULL if none.  This
+ * object is owned by cairo. To keep a reference to it, you must call
+ * cairo_surface_reference().
+ **/
+cairo_surface_t *
+cairo_get_group_target (cairo_t *cr)
+{
+    if (cr->status)
+	return (cairo_surface_t*) &_cairo_surface_nil;
+
     return _cairo_gstate_get_target (cr->gstate);
 }
 
diff --git a/src/cairo.h b/src/cairo.h
index c1f191c..936006e 100644
--- a/src/cairo.h
+++ b/src/cairo.h
@@ -199,6 +199,26 @@ typedef enum _cairo_status {
 } cairo_status_t;
 
 /**
+ * cairo_content_t
+ * @CAIRO_CONTENT_COLOR: The surface will hold color content only.
+ * @CAIRO_CONTENT_ALPHA: The surface will hold alpha content only.
+ * @CAIRO_CONTENT_COLOR_ALPHA: The surface will hold color and alpha content.
+ *
+ * @cairo_content_t is used to describe the content that a surface will
+ * contain, whether color information, alpha information (translucence
+ * vs. opacity), or both.
+ *
+ * Note: The large values here are designed to keep cairo_content_t
+ * values distinct from cairo_format_t values so that the
+ * implementation can detect the error if users confuse the two types.
+ */
+typedef enum _cairo_content {
+    CAIRO_CONTENT_COLOR		= 0x1000,
+    CAIRO_CONTENT_ALPHA		= 0x2000,
+    CAIRO_CONTENT_COLOR_ALPHA	= 0x3000
+} cairo_content_t;
+
+/**
  * cairo_write_func_t:
  * @closure: the output closure
  * @data: the buffer containing the data to write
@@ -254,13 +274,17 @@ cairo_save (cairo_t *cr);
 cairo_public void
 cairo_restore (cairo_t *cr);
 
-/* XXX: I want to rethink this API
 cairo_public void
 cairo_push_group (cairo_t *cr);
 
 cairo_public void
+cairo_push_group_with_content (cairo_t *cr, cairo_content_t content);
+
+cairo_public cairo_pattern_t *
 cairo_pop_group (cairo_t *cr);
-*/
+
+cairo_public void
+cairo_pop_group_to_source (cairo_t *cr);
 
 /* Modify state */
 
@@ -1046,6 +1070,9 @@ cairo_get_matrix (cairo_t *cr, cairo_mat
 cairo_public cairo_surface_t *
 cairo_get_target (cairo_t *cr);
 
+cairo_public cairo_surface_t *
+cairo_get_group_target (cairo_t *cr);
+
 typedef enum _cairo_path_data_type {
     CAIRO_PATH_MOVE_TO,
     CAIRO_PATH_LINE_TO,
@@ -1171,26 +1198,6 @@ cairo_status_to_string (cairo_status_t s
 
 /* Surface manipulation */
 
-/**
- * cairo_content_t
- * @CAIRO_CONTENT_COLOR: The surface will hold color content only.
- * @CAIRO_CONTENT_ALPHA: The surface will hold alpha content only.
- * @CAIRO_CONTENT_COLOR_ALPHA: The surface will hold color and alpha content.
- *
- * @cairo_content_t is used to describe the content that a surface will
- * contain, whether color information, alpha information (translucence
- * vs. opacity), or both.
- *
- * Note: The large values here are designed to keep cairo_content_t
- * values distinct from cairo_format_t values so that the
- * implementation can detect the error if users confuse the two types.
- */
-typedef enum _cairo_content {
-    CAIRO_CONTENT_COLOR		= 0x1000,
-    CAIRO_CONTENT_ALPHA		= 0x2000,
-    CAIRO_CONTENT_COLOR_ALPHA	= 0x3000
-} cairo_content_t;
-
 cairo_public cairo_surface_t *
 cairo_surface_create_similar (cairo_surface_t  *other,
 			      cairo_content_t	content,
diff --git a/src/cairoint.h b/src/cairoint.h
index 09d2e65..16ac9ca 100644
--- a/src/cairoint.h
+++ b/src/cairoint.h
@@ -1089,9 +1089,24 @@ _cairo_gstate_destroy (cairo_gstate_t *g
 cairo_private cairo_gstate_t *
 _cairo_gstate_clone (cairo_gstate_t *gstate);
 
+cairo_private cairo_bool_t
+_cairo_gstate_is_redirected (cairo_gstate_t *gstate);
+
+cairo_private void
+_cairo_gstate_redirect_target (cairo_gstate_t *gstate, cairo_surface_t *child);
+
 cairo_private cairo_surface_t *
 _cairo_gstate_get_target (cairo_gstate_t *gstate);
 
+cairo_private cairo_surface_t *
+_cairo_gstate_get_parent_target (cairo_gstate_t *gstate);
+
+cairo_private cairo_surface_t *
+_cairo_gstate_get_original_target (cairo_gstate_t *gstate);
+
+cairo_private cairo_clip_t *
+_cairo_gstate_get_clip (cairo_gstate_t *gstate);
+
 cairo_private cairo_status_t
 _cairo_gstate_set_source (cairo_gstate_t *gstate, cairo_pattern_t *source);
 
@@ -2282,6 +2297,10 @@ slim_hidden_proto(cairo_restore)
 slim_hidden_proto(cairo_save)
 slim_hidden_proto(cairo_stroke_preserve)
 slim_hidden_proto(cairo_surface_destroy)
+slim_hidden_proto(cairo_push_group)
+slim_hidden_proto(cairo_push_group_with_content)
+slim_hidden_proto(cairo_pop_group)
+slim_hidden_proto(cairo_pop_group_to_source)
 
 CAIRO_END_DECLS
 
diff --git a/test/Makefile.am b/test/Makefile.am
index 1a4a15c..d1ace03 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -67,7 +67,8 @@ trap-clip			\
 unantialiased-shapes		\
 unbounded-operator		\
 user-data			\
-rel-path
+rel-path                        \
+push-group
 
 # Then we have a collection of tests that are only run if certain
 # features are compiled into cairo


More information about the cairo-commit mailing list