From 49e762a291cb38873ecb5992c1d6d3e91f9ae9c8 Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Thu, 11 Dec 2008 15:22:42 +0000 Subject: [PATCH 01/13] Require Cairo as a Clutter dependency Cairo has been an indirect dependency for Clutter since 0.8, through the PangoCairo API. Now we explicitly depend on Cairo in order to merge the clutter-cairo API into Clutter core. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 4ca24aa38..0df692407 100644 --- a/configure.ac +++ b/configure.ac @@ -520,7 +520,7 @@ fi AC_SUBST(JSON_PREFIX) AM_CONDITIONAL(LOCAL_JSON_GLIB, test "x$have_json" = "xno") -CLUTTER_REQUIRES="pangocairo >= 1.18 gobject-2.0 >= 2.16 gthread-2.0 gmodule-no-export-2.0 $BACKEND_PC_FILES $JSON_GLIB_PC" +CLUTTER_REQUIRES="cairo >= 1.4 pangocairo >= 1.18 gobject-2.0 >= 2.16 gthread-2.0 gmodule-no-export-2.0 $BACKEND_PC_FILES $JSON_GLIB_PC" if test "x$imagebackend" = "xgdk-pixbuf"; then CLUTTER_REQUIRES="$CLUTTER_REQUIRES gdk-pixbuf-2.0" From e934ad03ca7880561dd282d8cc6abc29ba0c00f5 Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Thu, 11 Dec 2008 15:24:07 +0000 Subject: [PATCH 02/13] Add ClutterCairoTexture Move the ClutterCairo actor from a separate library to an in-tree actor. ClutterCairoTexture is a simple texture subclass that allows you to retrieve a Cairo context for a private image surface. When the Cairo context is destroyed it will cause the image surface contents to be uploaded to a GL texture. The image surface used is not hardware accelerated. --- clutter/Makefile.am | 2 + clutter/clutter-cairo-texture.c | 721 ++++++++++++++++++++++++++++++++ clutter/clutter-cairo-texture.h | 92 ++++ clutter/clutter.h | 1 + 4 files changed, 816 insertions(+) create mode 100644 clutter/clutter-cairo-texture.c create mode 100644 clutter/clutter-cairo-texture.h diff --git a/clutter/Makefile.am b/clutter/Makefile.am index 97c9907ad..4db7c9fe2 100644 --- a/clutter/Makefile.am +++ b/clutter/Makefile.am @@ -57,6 +57,7 @@ source_h = \ $(srcdir)/clutter-behaviour-rotate.h \ $(srcdir)/clutter-behaviour-scale.h \ $(srcdir)/clutter-binding-pool.h \ + $(srcdir)/clutter-cairo-texture.h \ $(srcdir)/clutter-child-meta.h \ $(srcdir)/clutter-clone-texture.h \ $(srcdir)/clutter-color.h \ @@ -148,6 +149,7 @@ source_c = \ clutter-behaviour-scale.c \ clutter-bezier.c \ clutter-binding-pool.c \ + clutter-cairo-texture.c \ clutter-child-meta.c \ clutter-clone-texture.c \ clutter-color.c \ diff --git a/clutter/clutter-cairo-texture.c b/clutter/clutter-cairo-texture.c new file mode 100644 index 000000000..42597ae0a --- /dev/null +++ b/clutter/clutter-cairo-texture.c @@ -0,0 +1,721 @@ +/* + * Clutter + * + * An OpenGL based 'interactive canvas' library. + * + * Authored By: Emmanuele Bassi + * Matthew Allum + * Chris Lord + * Iain Holmes + * Neil Roberts + * + * Copyright (C) 2008 Intel Corporation. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * SECTION:clutter-cairo-texture + * @short_description: Texture with Cairo integration + * + * #ClutterCairoTexture is a #ClutterTexture that displays the contents + * of a Cairo context. The #ClutterCairoTexture actor will create a + * Cairo image surface which will then be uploaded to a GL texture when + * needed. + * + * #ClutterCairoTexture will provide a #cairo_t context by using the + * clutter_cairo_texture_create() and clutter_cairo_texture_create_region() + * functions; you can use the Cairo API to draw on the context. + * + * As soon as the context is destroyed with cairo_destroy(), the contents + * of the context will be uploaded into the #ClutterCairoTexture actor: + * + * |[ + * cairo_t *cr; + * + * cr = clutter_cairo_texture_create (CLUTTER_CAIRO_TEXTURE (texture)); + * + * /* draw on the context */ + * + * cairo_destroy (cr); + * ]| + * + * Note that you should never use the code above inside the + * #ClutterActor::paint or #ClutterActor::pick virtual functions or + * signal handlers because of performance degradation. + * + * Since #ClutterCairoTexture uses a Cairo image surface + * internally all the drawing operations will be performed in + * software and not using hardware acceleration. This can lead to + * performance degradation if the contents of the texture change + * frequently. + * + * #ClutterCairoTexture is available since Clutter 1.0. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "clutter-cairo-texture.h" +#include "clutter-debug.h" +#include "clutter-private.h" + +G_DEFINE_TYPE (ClutterCairoTexture, + clutter_cairo_texture, + CLUTTER_TYPE_TEXTURE); + +enum +{ + PROP_0, + + PROP_SURFACE_WIDTH, + PROP_SURFACE_HEIGHT +}; + +#define CLUTTER_CAIRO_TEXTURE_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CLUTTER_TYPE_CAIRO_TEXTURE, ClutterCairoTexturePrivate)) + +struct _ClutterCairoTexturePrivate +{ + cairo_format_t format; + + cairo_surface_t *cr_surface; + guchar *cr_surface_data; + + guint width; + guint height; + guint rowstride; +}; + +typedef struct +{ + gint x; + gint y; + guint width; + guint height; +} ClutterCairoTextureRectangle; + +typedef struct +{ + ClutterCairoTexture *cairo; + ClutterCairoTextureRectangle rect; +} ClutterCairoTextureContext; + +static const cairo_user_data_key_t clutter_cairo_texture_surface_key; +static const cairo_user_data_key_t clutter_cairo_texture_context_key; + +static void +clutter_cairo_texture_surface_destroy (void *data) +{ + ClutterCairoTexture *cairo = data; + + cairo->priv->cr_surface = NULL; +} + +static void +clutter_cairo_texture_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + ClutterCairoTexturePrivate *priv; + + priv = CLUTTER_CAIRO_TEXTURE (object)->priv; + + switch (prop_id) + { + case PROP_SURFACE_WIDTH: + priv->width = g_value_get_uint (value); + break; + + case PROP_SURFACE_HEIGHT: + priv->height = g_value_get_uint (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +clutter_cairo_texture_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + ClutterCairoTexturePrivate *priv; + + priv = CLUTTER_CAIRO_TEXTURE (object)->priv; + + switch (prop_id) + { + case PROP_SURFACE_WIDTH: + g_value_set_uint (value, priv->width); + break; + + case PROP_SURFACE_HEIGHT: + g_value_set_uint (value, priv->height); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +clutter_cairo_texture_finalize (GObject *object) +{ + ClutterCairoTexturePrivate *priv = CLUTTER_CAIRO_TEXTURE (object)->priv; + + if (priv->cr_surface) + { + cairo_surface_t *surface = priv->cr_surface; + + cairo_surface_finish (priv->cr_surface); + cairo_surface_set_user_data (priv->cr_surface, + &clutter_cairo_texture_surface_key, + NULL, NULL); + cairo_surface_destroy (surface); + + priv->cr_surface = NULL; + } + + if (priv->cr_surface_data) + { + g_free (priv->cr_surface_data); + priv->cr_surface_data = NULL; + } + + G_OBJECT_CLASS (clutter_cairo_texture_parent_class)->finalize (object); +} + +static inline void +clutter_cairo_texture_surface_resize_internal (ClutterCairoTexture *cairo) +{ + ClutterCairoTexturePrivate *priv = cairo->priv; + + if (priv->cr_surface) + { + cairo_surface_t *surface = priv->cr_surface; + + cairo_surface_finish (surface); + cairo_surface_set_user_data (surface, + &clutter_cairo_texture_surface_key, + NULL, NULL); + cairo_surface_destroy (surface); + + priv->cr_surface = NULL; + } + + if (priv->cr_surface_data) + { + g_free (priv->cr_surface_data); + priv->cr_surface_data = NULL; + } + +#if CAIRO_VERSION > 106000 + priv->rowstride = cairo_format_stride_for_width (priv->format, priv->width); +#else + /* poor man's version of cairo_format_stride_for_width() */ + switch (priv->format) + { + case CAIRO_FORMAT_ARGB32: + case CAIRO_FORMAT_RGB24: + priv->rowstride = priv->width * 4; + break; + + case CAIRO_FORMAT_A8: + case CAIRO_FORMAT_A1: + priv->rowstride = priv->width; + break; + + default: + g_assert_not_reached (); + break; + } +#endif /* CAIRO_VERSION > 106000 */ + + priv->cr_surface_data = g_malloc0 (priv->height * priv->rowstride); + priv->cr_surface = + cairo_image_surface_create_for_data (priv->cr_surface_data, + priv->format, + priv->width, priv->height, + priv->rowstride); + + cairo_surface_set_user_data (priv->cr_surface, + &clutter_cairo_texture_surface_key, + cairo, + clutter_cairo_texture_surface_destroy); + + /* The texture data will be all zeroes so we can use it to create a + * blank Cogl texture even though its in a different format + */ + clutter_texture_set_from_rgb_data (CLUTTER_TEXTURE (cairo), + priv->cr_surface_data, + TRUE, priv->width, priv->height, + priv->rowstride, + 4, 0, NULL); +} + +static GObject * +clutter_cairo_texture_constructor (GType gtype, + guint n_properties, + GObjectConstructParam *properties) +{ + GObjectClass *parent_class; + GObject *obj; + ClutterCairoTexture *cairo; + ClutterCairoTexturePrivate *priv; + + parent_class = G_OBJECT_CLASS (clutter_cairo_texture_parent_class); + obj = parent_class->constructor (gtype, n_properties, properties); + + /* Now all of the object properties are set */ + cairo = CLUTTER_CAIRO_TEXTURE (obj); + priv = cairo->priv; + + if (!priv->width || !priv->height) + { + g_warning ("Unable to create the Cairo surface: invalid size (%dx%d)", + priv->width, + priv->height); + return obj; + } + + clutter_cairo_texture_surface_resize_internal (cairo); + + return obj; +} + +static void +clutter_cairo_texture_get_preferred_width (ClutterActor *actor, + ClutterUnit for_height, + ClutterUnit *min_width, + ClutterUnit *natural_width) +{ + ClutterCairoTexturePrivate *priv = CLUTTER_CAIRO_TEXTURE (actor)->priv; + + if (min_width) + *min_width = 0; + + if (natural_width) + *natural_width = CLUTTER_UNITS_FROM_DEVICE (priv->width); +} + +static void +clutter_cairo_texture_get_preferred_height (ClutterActor *actor, + ClutterUnit for_width, + ClutterUnit *min_height, + ClutterUnit *natural_height) +{ + ClutterCairoTexturePrivate *priv = CLUTTER_CAIRO_TEXTURE (actor)->priv; + + if (min_height) + *min_height = 0; + + if (natural_height) + *natural_height = CLUTTER_UNITS_FROM_DEVICE (priv->height); +} + +static void +clutter_cairo_texture_class_init (ClutterCairoTextureClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass); + + gobject_class->finalize = clutter_cairo_texture_finalize; + gobject_class->set_property = clutter_cairo_texture_set_property; + gobject_class->get_property = clutter_cairo_texture_get_property; + gobject_class->constructor = clutter_cairo_texture_constructor; + + actor_class->get_preferred_width = + clutter_cairo_texture_get_preferred_width; + actor_class->get_preferred_height = + clutter_cairo_texture_get_preferred_height; + + g_type_class_add_private (gobject_class, sizeof (ClutterCairoTexturePrivate)); + + /** + * ClutterCairoTexture:surface-width: + * + * The width of the Cairo surface used by the #ClutterCairoTexture + * actor, in pixels. + * + * Since: 1.0 + */ + g_object_class_install_property (gobject_class, + PROP_SURFACE_WIDTH, + g_param_spec_uint ("surface-width", + "Surface-Width", + "Surface Width", + 0, G_MAXUINT, + 0, + G_PARAM_CONSTRUCT_ONLY | + CLUTTER_PARAM_READWRITE)); + /** + * ClutterCairoTexture:surface-height: + * + * The height of the Cairo surface used by the #ClutterCairoTexture + * actor, in pixels. + * + * Since: 1.0 + */ + g_object_class_install_property (gobject_class, + PROP_SURFACE_HEIGHT, + g_param_spec_uint ("surface-height", + "Surface-Height", + "Surface Height", + 0, G_MAXUINT, + 0, + G_PARAM_CONSTRUCT_ONLY | + CLUTTER_PARAM_READWRITE)); +} + +static void +clutter_cairo_texture_init (ClutterCairoTexture *self) +{ + ClutterCairoTexturePrivate *priv; + + self->priv = priv = CLUTTER_CAIRO_TEXTURE_GET_PRIVATE (self); + + /* FIXME - we are hardcoding the format; it would be good to have + * a :surface-format construct-only property for creating + * textures with a different format + */ + priv->format = CAIRO_FORMAT_ARGB32; +} + +/** + * clutter_cairo_texture_new: + * @width: the width of the surface + * @height: the height of the surface + * + * Creates a new #ClutterCairoTexture actor, with a surface of @width by + * @height pixels. + * + * Return value: the newly created #ClutterCairoTexture actor + * + * Since: 1.0 + */ +ClutterActor* +clutter_cairo_texture_new (guint width, + guint height) +{ + return g_object_new (CLUTTER_TYPE_CAIRO_TEXTURE, + "surface-width", width, + "surface-height", height, + NULL); +} + +static void +clutter_cairo_texture_context_destroy (void *data) +{ + ClutterCairoTextureContext *ctxt = data; + ClutterCairoTexture *cairo = ctxt->cairo; + ClutterCairoTexturePrivate *priv; + + gint cairo_width, cairo_height, cairo_rowstride; + gint surface_width, surface_height; + guchar *pixbuf_data, *dst, *cairo_data; + guint *src, pixbuf_rowstride; + gint x, y; + + priv = CLUTTER_CAIRO_TEXTURE_GET_PRIVATE (cairo); + + if (!priv->cr_surface) + return; + + surface_width = cairo_image_surface_get_width (priv->cr_surface); + surface_height = cairo_image_surface_get_height (priv->cr_surface); + + cairo_width = MIN (ctxt->rect.width, surface_width); + cairo_height = MIN (ctxt->rect.height, surface_height); + + if (!cairo_width || !cairo_height) + { + g_free (ctxt); + return; + } + + cairo_rowstride = priv->rowstride; + cairo_data = priv->cr_surface_data; + pixbuf_data = g_malloc (cairo_width * cairo_height * 4); + pixbuf_rowstride = cairo_width * 4; + + /* BAH BAH BAH ! un-pre-multiply alpha... + * + * FIXME: Need to figure out if GL has a premult texture + * format... or go back to battling glitz + */ + for (y = 0; y < cairo_height; y++) + { + src = (unsigned int *) (cairo_data + + ((y + ctxt->rect.y) * cairo_rowstride) + + (ctxt->rect.x * 4)); + dst = pixbuf_data + y * pixbuf_rowstride; + + for (x = 0; x < cairo_width; x++) + { + guchar alpha = (*src >> 24) & 0xff; + + if (alpha == 0) + dst[0] = dst[1] = dst[2] = dst[3] = alpha; + else + { + dst[0] = (((*src >> 16) & 0xff) * 255 ) / alpha; + dst[1] = (((*src >> 8) & 0xff) * 255 ) / alpha; + dst[2] = (((*src >> 0) & 0xff) * 255 ) / alpha; + dst[3] = alpha; + } + + dst += 4; + src++; + } + } + + clutter_texture_set_area_from_rgb_data (CLUTTER_TEXTURE (cairo), + pixbuf_data, + TRUE, + ctxt->rect.x, + ctxt->rect.y, + cairo_width, cairo_height, + pixbuf_rowstride, + 4, 0, NULL); + + g_free (pixbuf_data); + g_free (ctxt); + + if (CLUTTER_ACTOR_IS_VISIBLE (cairo)) + clutter_actor_queue_redraw (CLUTTER_ACTOR (cairo)); +} + +static void +intersect_rectangles (ClutterCairoTextureRectangle *a, + ClutterCairoTextureRectangle *b, + ClutterCairoTextureRectangle *inter) +{ + gint dest_x, dest_y; + gint dest_width, dest_height; + + dest_x = MAX (a->x, b->x); + dest_y = MAX (a->y, b->y); + dest_width = MIN (a->x + a->width, b->x + b->width) - dest_x; + dest_height = MIN (a->y + a->height, b->y + b->height) - dest_y; + + if (dest_width > 0 && dest_height > 0) + { + inter->x = dest_x; + inter->y = dest_y; + inter->width = dest_width; + inter->height = dest_height; + } + else + { + inter->x = 0; + inter->y = 0; + inter->width = 0; + inter->height = 0; + } +} + +/** + * clutter_cairo_texture_create_region: + * @cairo: a #ClutterCairoTexture + * @x_offset: offset of the region on the X axis + * @y_offset: offset of the region on the Y axis + * @width: width of the region, or -1 for the full surface width + * @height: height of the region, or -1 for the full surface height + * + * Creates a new Cairo context that will updat the region defined + * by @x_offset, @y_offset, @width and @height. + * + * Return value: a newly created Cairo context. Use cairo_destroy() + * to upload the contents of the context when done drawing. + * + * Since: 1.0 + */ +cairo_t * +clutter_cairo_texture_create_region (ClutterCairoTexture *self, + gint x_offset, + gint y_offset, + gint width, + gint height) +{ + ClutterCairoTexturePrivate *priv; + ClutterCairoTextureContext *ctxt; + ClutterCairoTextureRectangle region, area, inter; + cairo_t *cr; + + g_return_val_if_fail (CLUTTER_IS_CAIRO_TEXTURE (self), NULL); + + priv = self->priv; + + if (width < 0) + width = priv->width; + + if (height < 0) + height = priv->height; + + ctxt = g_new0 (ClutterCairoTextureContext, 1); + ctxt->cairo = self; + + region.x = x_offset; + region.y = y_offset; + region.width = width; + region.height = height; + + area.x = 0; + area.y = 0; + area.width = priv->width; + area.height = priv->height; + + /* Limit the region to the visible rectangle */ + intersect_rectangles (&area, ®ion, &inter); + + ctxt->rect.x = inter.x; + ctxt->rect.y = inter.y; + ctxt->rect.width = inter.width; + ctxt->rect.height = inter.height; + + cr = cairo_create (priv->cr_surface); + cairo_set_user_data (cr, &clutter_cairo_texture_context_key, + ctxt, clutter_cairo_texture_context_destroy); + + return cr; +} + +/** + * clutter_cairo_texture_create: + * @cairo: a #ClutterCairoTexture + * + * Creates a new Cairo context for the @cairo texture. It is + * similar to using clutter_cairo_texture_create_region() with @x_offset + * and @y_offset of 0, @width equal to the @cairo texture surface width + * and @height equal to the @cairo texture surface height. + * + * Return value: a newly created Cairo context. Use cairo_destroy() + * to upload the contents of the context when done drawing. + * + * Since: 1.0 + */ +cairo_t * +clutter_cairo_texture_create (ClutterCairoTexture *self) +{ + g_return_val_if_fail (CLUTTER_IS_CAIRO_TEXTURE (self), NULL); + + return clutter_cairo_texture_create_region (self, 0, 0, -1, -1); +} + +/** + * clutter_cairo_set_source_color: + * @cr: a Cairo context + * @color: a #ClutterColor + * + * Utility function for setting the source color of @cr using + * a #ClutterColor. + * + * Since: 1.0 + */ +void +clutter_cairo_set_source_color (cairo_t *cr, + const ClutterColor *color) +{ + g_return_if_fail (cr != NULL); + g_return_if_fail (color != NULL); + + if (color->alpha == 0xff) + cairo_set_source_rgb (cr, + color->red / 255.0, + color->green / 255.0, + color->blue / 255.0); + else + cairo_set_source_rgba (cr, + color->red / 255.0, + color->green / 255.0, + color->blue / 255.0, + color->alpha / 255.0); +} + +/** + * clutter_cairo_texture_surface_set_size: + * @self: a #ClutterCairoTexture + * @width: the new width of the surface + * @height: the new height of the surface + * + * Resizes the Cairo surface used by @self to @width and @height. + * + * Since: 1.0 + */ +void +clutter_cairo_texture_set_surface_size (ClutterCairoTexture *self, + guint width, + guint height) +{ + ClutterCairoTexturePrivate *priv; + + g_return_if_fail (CLUTTER_IS_CAIRO_TEXTURE (self)); + + priv = self->priv; + + if (width == priv->width && height == priv->height) + return; + + g_object_freeze_notify (G_OBJECT (self)); + + if (priv->width != width) + { + priv->width = width; + g_object_notify (G_OBJECT (self), "surface-width"); + } + + if (priv->height != height) + { + priv->height = height; + g_object_notify (G_OBJECT (self), "surface-height"); + } + + clutter_cairo_texture_surface_resize_internal (self); + + g_object_thaw_notify (G_OBJECT (self)); +} + +/** + * clutter_cairo_texture_get_surface_size: + * @self: a #ClutterCairoTexture + * @width: return location for the surface width, or %NULL + * @height: return location for the surface height, or %NULL + * + * Retrieves the surface width and height for @self. + * + * Since: 1.0 + */ +void +clutter_cairo_texture_get_surface_size (ClutterCairoTexture *self, + guint *width, + guint *height) +{ + g_return_if_fail (CLUTTER_IS_CAIRO_TEXTURE (self)); + + if (width) + *width = self->priv->width; + + if (height) + *height = self->priv->height; +} diff --git a/clutter/clutter-cairo-texture.h b/clutter/clutter-cairo-texture.h new file mode 100644 index 000000000..51af958c7 --- /dev/null +++ b/clutter/clutter-cairo-texture.h @@ -0,0 +1,92 @@ +/* + * Clutter + * + * An OpenGL based 'interactive canvas' library. + * + * Authored By: Emmanuele Bassi + * Matthew Allum + * Chris Lord + * Iain Holmes + * Neil Roberts + * + * Copyright (C) 2008 Intel Corporation. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#if !defined(__CLUTTER_H_INSIDE__) && !defined(CLUTTER_COMPILATION) +#error "Only can be included directly." +#endif + +#ifndef __CLUTTER_CAIRO_TEXTURE_H__ +#define __CLUTTER_CAIRO_TEXTURE_H__ + +#include +#include + +G_BEGIN_DECLS + +#define CLUTTER_TYPE_CAIRO_TEXTURE (clutter_cairo_texture_get_type ()) +#define CLUTTER_CAIRO_TEXTURE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CLUTTER_TYPE_CAIRO_TEXTURE, ClutterCairoTexture)) +#define CLUTTER_CAIRO_TEXTURE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CLUTTER_TYPE_CAIRO_TEXTURE, ClutterCairoTextureClass)) +#define CLUTTER_IS_CAIRO_TEXTURE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CLUTTER_TYPE_CAIRO_TEXTURE)) +#define CLUTTER_IS_CAIRO_TEXTURE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CLUTTER_TYPE_CAIRO_TEXTURE)) +#define CLUTTER_CAIRO_TEXTURE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CLUTTER_TYPE_CAIRO_TEXTURE, ClutterCairoTextureClass)) + +typedef struct _ClutterCairoTexture ClutterCairoTexture; +typedef struct _ClutterCairoTextureClass ClutterCairoTextureClass; +typedef struct _ClutterCairoTexturePrivate ClutterCairoTexturePrivate; + +struct _ClutterCairoTexture +{ + /*< private >*/ + ClutterTexture parent_instance; + + ClutterCairoTexturePrivate *priv; +}; + +struct _ClutterCairoTextureClass +{ + /*< private >*/ + ClutterTextureClass parent_class; + + void (*_clutter_cairo_1) (void); + void (*_clutter_cairo_2) (void); + void (*_clutter_cairo_3) (void); + void (*_clutter_cairo_4) (void); +}; + +GType clutter_cairo_texture_get_type (void) G_GNUC_CONST; +ClutterActor *clutter_cairo_texture_new (guint width, + guint height); +cairo_t * clutter_cairo_texture_create_region (ClutterCairoTexture *self, + gint x_offset, + gint y_offset, + gint width, + gint height); +cairo_t * clutter_cairo_texture_create (ClutterCairoTexture *self); +void clutter_cairo_texture_set_surface_size (ClutterCairoTexture *self, + guint width, + guint height); +void clutter_cairo_texture_get_surface_size (ClutterCairoTexture *self, + guint *width, + guint *height); + +void clutter_cairo_set_source_color (cairo_t *cr, + const ClutterColor *color); + +G_END_DECLS + +#endif /* __CLUTTER_CAIRO_TEXTURE_H__ */ diff --git a/clutter/clutter.h b/clutter/clutter.h index 56c1e1789..15208ce17 100644 --- a/clutter/clutter.h +++ b/clutter/clutter.h @@ -40,6 +40,7 @@ #include "clutter-behaviour-rotate.h" #include "clutter-behaviour-scale.h" #include "clutter-binding-pool.h" +#include "clutter-cairo-texture.h" #include "clutter-child-meta.h" #include "clutter-clone-texture.h" #include "clutter-color.h" From 99bcf731cb168846de1bf922285f52bcb3c40a81 Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Thu, 11 Dec 2008 15:37:21 +0000 Subject: [PATCH 03/13] [docs] Add ClutterCairoTexture to the API reference --- doc/reference/clutter/clutter-docs.xml | 1 + doc/reference/clutter/clutter-sections.txt | 29 ++++++++++++++++++++++ doc/reference/clutter/clutter.types | 1 + 3 files changed, 31 insertions(+) diff --git a/doc/reference/clutter/clutter-docs.xml b/doc/reference/clutter/clutter-docs.xml index 90b7d0d8a..ffd06a561 100644 --- a/doc/reference/clutter/clutter-docs.xml +++ b/doc/reference/clutter/clutter-docs.xml @@ -57,6 +57,7 @@ + diff --git a/doc/reference/clutter/clutter-sections.txt b/doc/reference/clutter/clutter-sections.txt index dd4061fc2..befd9466b 100644 --- a/doc/reference/clutter/clutter-sections.txt +++ b/doc/reference/clutter/clutter-sections.txt @@ -1629,3 +1629,32 @@ clutter_binding_pool_unblock_action clutter_binding_pool_activate + +
+ClutterCairoTexture +clutter-cairo-texture +ClutterCairoTexture +ClutterCairoTextureClass +clutter_cairo_texture_new +clutter_cairo_texture_set_surface_size +clutter_cairo_texture_get_surface_size + + +clutter_cairo_texture_create +clutter_cairo_texture_create_region + + +clutter_cairo_set_source_color + + +CLUTTER_TYPE_CAIRO_TEXTURE +CLUTTER_CAIRO_TEXTURE +CLUTTER_IS_CAIRO_TEXTURE +CLUTTER_CAIRO_TEXTURE_CLASS +CLUTTER_IS_CAIRO_TEXTURE_CLASS +CLUTTER_CAIRO_TEXTURE_GET_CLASS + + +ClutterCairoTexturePrivate +clutter_cairo_texture_get_type +
diff --git a/doc/reference/clutter/clutter.types b/doc/reference/clutter/clutter.types index 2e660766c..2bac85c6e 100644 --- a/doc/reference/clutter/clutter.types +++ b/doc/reference/clutter/clutter.types @@ -28,3 +28,4 @@ clutter_list_model_get_type clutter_score_get_type clutter_shader_get_type clutter_child_meta_get_type +clutter_cairo_texture_get_type From 3f2c9fe6b31af32ad643cce9154e17096eed432c Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Thu, 11 Dec 2008 15:39:28 +0000 Subject: [PATCH 04/13] Set the IN_PAINT private flag When calling clutter_actor_paint() we should be setting the CLUTTER_ACTOR_IN_PAINT private flag. This allows signalling to each Actor subclass that we are effectively in the middle of a paint sequence. Actor subclasses can check for this private flag and act based on its presence - for instance to avoid recursion, or to detect performance degradation cases. --- clutter/clutter-actor.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/clutter/clutter-actor.c b/clutter/clutter-actor.c index 424144436..b3567e8c9 100644 --- a/clutter/clutter-actor.c +++ b/clutter/clutter-actor.c @@ -1453,6 +1453,9 @@ clutter_actor_paint (ClutterActor *self) } } + /* mark that we are in the paint process */ + CLUTTER_SET_PRIVATE_FLAGS (self, CLUTTER_ACTOR_IN_PAINT); + cogl_push_matrix(); _clutter_actor_apply_modelview_transform (self); @@ -1492,6 +1495,9 @@ clutter_actor_paint (ClutterActor *self) cogl_clip_unset(); cogl_pop_matrix(); + + /* paint sequence complete */ + CLUTTER_UNSET_PRIVATE_FLAGS (self, CLUTTER_ACTOR_IN_PAINT); } /* fixed point, unit based rotation setter, to be used by From 1b88122873afac513449b03c0658ab3802c84f3d Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Thu, 11 Dec 2008 15:41:25 +0000 Subject: [PATCH 05/13] Do not set the IN_PAINT flag inside the Stage paint Since the CLUTTER_ACTOR_IN_PAINT private flag is set as part of the paint process by clutter_actor_paint(), there is no need to set it inside the ClutterStage paint function. --- clutter/clutter-stage.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/clutter/clutter-stage.c b/clutter/clutter-stage.c index f2243ea09..7e5abe76f 100644 --- a/clutter/clutter-stage.c +++ b/clutter/clutter-stage.c @@ -213,8 +213,6 @@ clutter_stage_paint (ClutterActor *self) ClutterStagePrivate *priv = CLUTTER_STAGE (self)->priv; CoglColor stage_color; - CLUTTER_SET_PRIVATE_FLAGS (self, CLUTTER_ACTOR_IN_PAINT); - CLUTTER_NOTE (PAINT, "Initializing stage paint"); cogl_color_set_from_4ub (&stage_color, @@ -235,8 +233,6 @@ clutter_stage_paint (ClutterActor *self) CLUTTER_NOTE (PAINT, "Proxying the paint to the stage implementation"); clutter_actor_paint (priv->impl); - CLUTTER_UNSET_PRIVATE_FLAGS (self, CLUTTER_ACTOR_IN_PAINT); - /* this will take care of painting every child */ CLUTTER_ACTOR_CLASS (clutter_stage_parent_class)->paint (self); } From cf9dea7cf6e079a8897c7b0d92da030c88cb964f Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Thu, 11 Dec 2008 15:48:43 +0000 Subject: [PATCH 06/13] Print a warning when creating a cairo_t while painting If you create a Cairo context in the middle of a paint run and then you destroy it, the CairoTexture will have to upload the contents of the image surface to the GL pipeline. This usually leads to slow downs and general performance degradation. ClutterCairoTexture will warn to the console if Clutter has been compiled with the debug messages and if create() or create_region() are called while an actor is in the middle of a paint. --- clutter/clutter-cairo-texture.c | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/clutter/clutter-cairo-texture.c b/clutter/clutter-cairo-texture.c index 42597ae0a..1aa15b75e 100644 --- a/clutter/clutter-cairo-texture.c +++ b/clutter/clutter-cairo-texture.c @@ -53,9 +53,10 @@ * cairo_destroy (cr); * ]| * - * Note that you should never use the code above inside the + * Note that you should never use the code above inside the * #ClutterActor::paint or #ClutterActor::pick virtual functions or - * signal handlers because of performance degradation. + * signal handlers because it will lead to performance + * degradation. * * Since #ClutterCairoTexture uses a Cairo image surface * internally all the drawing operations will be performed in @@ -88,6 +89,18 @@ enum PROP_SURFACE_HEIGHT }; +#ifdef CLUTTER_ENABLE_DEBUG +#define clutter_return_val_if_paint_fail(obj,retval) G_STMT_START { \ + if (CLUTTER_PRIVATE_FLAGS ((obj)) & CLUTTER_ACTOR_IN_PAINT) { \ + g_warning ("%s should not be called during the paint sequence " \ + "of a ClutterCairoTexture as it will likely cause " \ + "performance issues.", G_STRFUNC); \ + return (retval); \ + } } G_STMT_END +#else +#define clutter_return_val_if_paint_fail(obj,retval) /* void */ +#endif /* CLUTTER_ENABLE_DEBUG */ + #define CLUTTER_CAIRO_TEXTURE_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CLUTTER_TYPE_CAIRO_TEXTURE, ClutterCairoTexturePrivate)) struct _ClutterCairoTexturePrivate @@ -564,6 +577,7 @@ clutter_cairo_texture_create_region (ClutterCairoTexture *self, cairo_t *cr; g_return_val_if_fail (CLUTTER_IS_CAIRO_TEXTURE (self), NULL); + clutter_return_val_if_paint_fail (self, NULL); priv = self->priv; @@ -619,6 +633,7 @@ cairo_t * clutter_cairo_texture_create (ClutterCairoTexture *self) { g_return_val_if_fail (CLUTTER_IS_CAIRO_TEXTURE (self), NULL); + clutter_return_val_if_paint_fail (self, NULL); return clutter_cairo_texture_create_region (self, 0, 0, -1, -1); } From 4d34872deab56c9b341742386c1740bf15b2a83c Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Thu, 11 Dec 2008 15:51:24 +0000 Subject: [PATCH 07/13] Small documentation fixes Fix the CairoTexture description, and some of the comments inside the code, especially with regards to the alpha channel unpremultiplication that we have to perform each time we upload the image surface to GL. --- clutter/clutter-cairo-texture.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/clutter/clutter-cairo-texture.c b/clutter/clutter-cairo-texture.c index 1aa15b75e..8e3b6c974 100644 --- a/clutter/clutter-cairo-texture.c +++ b/clutter/clutter-cairo-texture.c @@ -38,10 +38,11 @@ * * #ClutterCairoTexture will provide a #cairo_t context by using the * clutter_cairo_texture_create() and clutter_cairo_texture_create_region() - * functions; you can use the Cairo API to draw on the context. + * functions; you can use the Cairo API to draw on the context and then + * call cairo_destroy() when done. * * As soon as the context is destroyed with cairo_destroy(), the contents - * of the context will be uploaded into the #ClutterCairoTexture actor: + * of the surface will be uploaded into the #ClutterCairoTexture actor: * * |[ * cairo_t *cr; @@ -410,7 +411,8 @@ clutter_cairo_texture_init (ClutterCairoTexture *self) /* FIXME - we are hardcoding the format; it would be good to have * a :surface-format construct-only property for creating - * textures with a different format + * textures with a different format and have the cairo surface + * match that format */ priv->format = CAIRO_FORMAT_ARGB32; } @@ -475,7 +477,12 @@ clutter_cairo_texture_context_destroy (void *data) /* BAH BAH BAH ! un-pre-multiply alpha... * * FIXME: Need to figure out if GL has a premult texture - * format... or go back to battling glitz + * format, or we need to change the order of the + * paint sequence in Clutter. or go back to battling + * glitz (ugh). + * + * in theory, this could be moved to a shader, but apparently + * the performance gain is not really worth it. */ for (y = 0; y < cairo_height; y++) { From 0e1a3c21248cf3ab14211bee8c93e0677769a723 Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Fri, 12 Dec 2008 11:42:16 +0000 Subject: [PATCH 08/13] Warn instead of returning in the IN_PAINT check It's conceivable that a warning should be deemed enough, instead of just returning a NULL cairo_t when creating a context for the CairoTexture actor. --- clutter/clutter-cairo-texture.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/clutter/clutter-cairo-texture.c b/clutter/clutter-cairo-texture.c index 8e3b6c974..2343a9afa 100644 --- a/clutter/clutter-cairo-texture.c +++ b/clutter/clutter-cairo-texture.c @@ -91,15 +91,14 @@ enum }; #ifdef CLUTTER_ENABLE_DEBUG -#define clutter_return_val_if_paint_fail(obj,retval) G_STMT_START { \ +#define clutter_warn_if_paint_fail(obj) G_STMT_START { \ if (CLUTTER_PRIVATE_FLAGS ((obj)) & CLUTTER_ACTOR_IN_PAINT) { \ g_warning ("%s should not be called during the paint sequence " \ "of a ClutterCairoTexture as it will likely cause " \ "performance issues.", G_STRFUNC); \ - return (retval); \ } } G_STMT_END #else -#define clutter_return_val_if_paint_fail(obj,retval) /* void */ +#define clutter_warn_if_paint_fail(obj) /* void */ #endif /* CLUTTER_ENABLE_DEBUG */ #define CLUTTER_CAIRO_TEXTURE_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CLUTTER_TYPE_CAIRO_TEXTURE, ClutterCairoTexturePrivate)) @@ -584,7 +583,8 @@ clutter_cairo_texture_create_region (ClutterCairoTexture *self, cairo_t *cr; g_return_val_if_fail (CLUTTER_IS_CAIRO_TEXTURE (self), NULL); - clutter_return_val_if_paint_fail (self, NULL); + + clutter_warn_if_paint_fail (self); priv = self->priv; @@ -640,7 +640,8 @@ cairo_t * clutter_cairo_texture_create (ClutterCairoTexture *self) { g_return_val_if_fail (CLUTTER_IS_CAIRO_TEXTURE (self), NULL); - clutter_return_val_if_paint_fail (self, NULL); + + clutter_warn_if_paint_fail (self); return clutter_cairo_texture_create_region (self, 0, 0, -1, -1); } From a462d19e1438dcb738362e63ddcb30a77ba5cdc2 Mon Sep 17 00:00:00 2001 From: Neil Roberts Date: Fri, 5 Dec 2008 18:37:31 +0000 Subject: [PATCH 09/13] Add clutter_path_to_cairo_path and clutter_path_add_cairo_path See bug #1325. Added doc for new cairo path functions to clutter-sections.txt --- clutter/clutter-path.c | 115 +++++++++++++++++++++ clutter/clutter-path.h | 7 ++ doc/reference/clutter/clutter-sections.txt | 2 + 3 files changed, 124 insertions(+) diff --git a/clutter/clutter-path.c b/clutter/clutter-path.c index 76481f417..4f373f9bb 100644 --- a/clutter/clutter-path.c +++ b/clutter/clutter-path.c @@ -736,6 +736,121 @@ clutter_path_add_node (ClutterPath *path, clutter_path_add_node_full (path, node_full); } +/** + * clutter_path_add_cairo_path: + * @path: a #ClutterPath + * @cpath: a Cairo path + * + * Add the nodes of the Cairo path to the end of @path. + * + * Since: 1.0 + */ +void +clutter_path_add_cairo_path (ClutterPath *path, + const cairo_path_t *cpath) +{ + int num_data; + const cairo_path_data_t *p; + + g_return_if_fail (CLUTTER_IS_PATH (path)); + g_return_if_fail (cpath != NULL); + + /* Iterate over each command in the cairo path */ + for (num_data = cpath->num_data, p = cpath->data; + num_data > 0; + num_data -= p->header.length, p += p->header.length) + { + switch (p->header.type) + { + case CAIRO_PATH_MOVE_TO: + g_assert (p->header.length >= 2); + + clutter_path_add_move_to (path, p[1].point.x, p[1].point.y); + break; + + case CAIRO_PATH_LINE_TO: + g_assert (p->header.length >= 2); + + clutter_path_add_line_to (path, p[1].point.x, p[1].point.y); + break; + + case CAIRO_PATH_CURVE_TO: + g_assert (p->header.length >= 4); + + clutter_path_add_curve_to (path, + p[1].point.x, p[1].point.y, + p[2].point.x, p[2].point.y, + p[3].point.x, p[3].point.y); + break; + + case CAIRO_PATH_CLOSE_PATH: + clutter_path_add_close (path); + } + } +} + +static void +clutter_path_add_node_to_cairo_path (const ClutterPathNode *node, + gpointer data) +{ + cairo_t *cr = data; + + switch (node->type) + { + case CLUTTER_PATH_MOVE_TO: + cairo_move_to (cr, node->points[0].x, node->points[0].y); + break; + + case CLUTTER_PATH_LINE_TO: + cairo_line_to (cr, node->points[0].x, node->points[0].y); + break; + + case CLUTTER_PATH_CURVE_TO: + cairo_curve_to (cr, + node->points[0].x, node->points[0].y, + node->points[1].x, node->points[1].y, + node->points[2].x, node->points[2].y); + break; + + case CLUTTER_PATH_REL_MOVE_TO: + cairo_rel_move_to (cr, node->points[0].x, node->points[0].y); + break; + + case CLUTTER_PATH_REL_LINE_TO: + cairo_rel_line_to (cr, node->points[0].x, node->points[0].y); + break; + + case CLUTTER_PATH_REL_CURVE_TO: + cairo_rel_curve_to (cr, + node->points[0].x, node->points[0].y, + node->points[1].x, node->points[1].y, + node->points[2].x, node->points[2].y); + break; + + case CLUTTER_PATH_CLOSE: + cairo_close_path (cr); + } +} + +/** + * clutter_path_to_cairo_path: + * @path: a #ClutterPath + * @cr: a Cairo context + * + * Add the nodes of the ClutterPath to the path in the Cairo context. + * + * Since: 1.0 + */ +void +clutter_path_to_cairo_path (ClutterPath *path, + cairo_t *cr) +{ + g_return_if_fail (CLUTTER_IS_PATH (path)); + g_return_if_fail (cr != NULL); + + clutter_path_foreach (path, clutter_path_add_node_to_cairo_path, cr); +} + /** * clutter_path_get_n_nodes: * @path: a #ClutterPath diff --git a/clutter/clutter-path.h b/clutter/clutter-path.h index 08ab77f6b..5e5210b9c 100644 --- a/clutter/clutter-path.h +++ b/clutter/clutter-path.h @@ -30,6 +30,7 @@ #include #include +#include G_BEGIN_DECLS @@ -195,6 +196,9 @@ gboolean clutter_path_add_string (ClutterPath *path, void clutter_path_add_node (ClutterPath *path, const ClutterPathNode *node); +void clutter_path_add_cairo_path (ClutterPath *path, + const cairo_path_t *cpath); + guint clutter_path_get_n_nodes (ClutterPath *path); void clutter_path_get_node (ClutterPath *path, @@ -225,6 +229,9 @@ gboolean clutter_path_set_description (ClutterPath *path, void clutter_path_clear (ClutterPath *path); +void clutter_path_to_cairo_path (ClutterPath *path, + cairo_t *cr); + guint clutter_path_get_position (ClutterPath *path, guint alpha, ClutterKnot *position); diff --git a/doc/reference/clutter/clutter-sections.txt b/doc/reference/clutter/clutter-sections.txt index befd9466b..6e4f3f82d 100644 --- a/doc/reference/clutter/clutter-sections.txt +++ b/doc/reference/clutter/clutter-sections.txt @@ -702,6 +702,7 @@ clutter_path_add_rel_curve_to clutter_path_add_close clutter_path_add_string clutter_path_add_node +clutter_path_add_cairo_path clutter_path_get_n_nodes clutter_path_get_node clutter_path_get_nodes @@ -711,6 +712,7 @@ clutter_path_remove_node clutter_path_replace_node clutter_path_get_description clutter_path_set_description +clutter_path_to_cairo_path clutter_path_clear clutter_path_get_position clutter_path_get_length From 965ec2762000543998e83e07f2f4690830231e2a Mon Sep 17 00:00:00 2001 From: Neil Roberts Date: Fri, 5 Dec 2008 18:37:46 +0000 Subject: [PATCH 10/13] Add a test case for the new cairo path functions The nodes of the test path have been reordered because Cairo coalesces multiple move operations into a single move so the comparison would fail if the two move nodes are consecutive. --- tests/conform/test-path.c | 104 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 99 insertions(+), 5 deletions(-) diff --git a/tests/conform/test-path.c b/tests/conform/test-path.c index 0933917a4..a0b6a68ca 100644 --- a/tests/conform/test-path.c +++ b/tests/conform/test-path.c @@ -1,4 +1,5 @@ #include +#include #include #include @@ -26,16 +27,19 @@ struct _CallbackData }; static const char path_desc[] = - "M 21 22 m 23 24 " - "L 25 26 l 27 28 " - "C 29 30 31 32 33 34 c 35 36 37 38 39 40 " + "M 21 22 " + "L 25 26 " + "C 29 30 31 32 33 34 " + "m 23 24 " + "l 27 28 " + "c 35 36 37 38 39 40 " "z"; static const ClutterPathNode path_nodes[] = { { CLUTTER_PATH_MOVE_TO, { { 21, 22 }, { 0, 0 }, { 0, 0 } } }, - { CLUTTER_PATH_REL_MOVE_TO, { { 23, 24 }, { 0, 0 }, { 0, 0 } } }, { CLUTTER_PATH_LINE_TO, { { 25, 26 }, { 0, 0 }, { 0, 0 } } }, - { CLUTTER_PATH_REL_LINE_TO, { { 27, 28 }, { 0, 0 }, { 0, 0 } } }, { CLUTTER_PATH_CURVE_TO, { { 29, 30 }, { 31, 32 }, { 33, 34 } } }, + { CLUTTER_PATH_REL_MOVE_TO, { { 23, 24 }, { 0, 0 }, { 0, 0 } } }, + { CLUTTER_PATH_REL_LINE_TO, { { 27, 28 }, { 0, 0 }, { 0, 0 } } }, { CLUTTER_PATH_REL_CURVE_TO, { { 35, 36 }, { 37, 38 }, { 39, 40 } } }, { CLUTTER_PATH_CLOSE, { { 0, 0 }, { 0, 0 }, { 0, 0 } } } }; @@ -391,6 +395,95 @@ path_test_get_description (CallbackData *data) return ret; } +static gboolean +path_test_convert_to_cairo_path (CallbackData *data) +{ + cairo_surface_t *surface; + cairo_t *cr; + cairo_path_t *cpath; + guint i, j; + ClutterKnot path_start = { 0, 0 }, last_point = { 0, 0 }; + + /* Create a temporary image surface and context to hold the cairo + path */ + surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 10, 10); + cr = cairo_create (surface); + + /* Convert to a cairo path */ + clutter_path_to_cairo_path (data->path, cr); + + /* Get a copy of the cairo path data */ + cpath = cairo_copy_path (cr); + + /* Convert back to a clutter path */ + clutter_path_clear (data->path); + clutter_path_add_cairo_path (data->path, cpath); + + /* The relative nodes will have been converted to absolute so we + need to reflect this in the node array for comparison */ + for (i = 0; i < data->n_nodes; i++) + { + switch (data->nodes[i].type) + { + case CLUTTER_PATH_MOVE_TO: + path_start = last_point = data->nodes[i].points[0]; + break; + + case CLUTTER_PATH_LINE_TO: + last_point = data->nodes[i].points[0]; + break; + + case CLUTTER_PATH_CURVE_TO: + last_point = data->nodes[i].points[2]; + break; + + case CLUTTER_PATH_REL_MOVE_TO: + last_point.x += data->nodes[i].points[0].x; + last_point.y += data->nodes[i].points[0].y; + data->nodes[i].points[0] = last_point; + data->nodes[i].type = CLUTTER_PATH_MOVE_TO; + path_start = last_point; + break; + + case CLUTTER_PATH_REL_LINE_TO: + last_point.x += data->nodes[i].points[0].x; + last_point.y += data->nodes[i].points[0].y; + data->nodes[i].points[0] = last_point; + data->nodes[i].type = CLUTTER_PATH_LINE_TO; + break; + + case CLUTTER_PATH_REL_CURVE_TO: + for (j = 0; j < 3; j++) + { + data->nodes[i].points[j].x += last_point.x; + data->nodes[i].points[j].y += last_point.y; + } + last_point = data->nodes[i].points[2]; + data->nodes[i].type = CLUTTER_PATH_CURVE_TO; + break; + + case CLUTTER_PATH_CLOSE: + last_point = path_start; + + /* Cairo always adds a move to after every close so we need + to insert one here */ + memmove (data->nodes + i + 2, data->nodes + i + 1, + (data->n_nodes - i - 1) * sizeof (ClutterPathNode)); + data->nodes[i + 1].type = CLUTTER_PATH_MOVE_TO; + data->nodes[i + 1].points[0] = last_point; + data->n_nodes++; + break; + } + } + + /* Free the cairo resources */ + cairo_path_destroy (cpath); + cairo_destroy (cr); + cairo_surface_destroy (surface); + + return TRUE; +} + static gboolean float_fuzzy_equals (float fa, float fb) { @@ -522,6 +615,7 @@ path_tests[] = { "Replace a node", path_test_replace }, { "Set description", path_test_set_description }, { "Get description", path_test_get_description }, + { "Convert to cairo path and back", path_test_convert_to_cairo_path }, { "Clear", path_test_clear }, { "Get position", path_test_get_position }, { "Check node boxed type", path_test_boxed_type }, From ca310d49cab2b021471d4d094c1802fee7d705af Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Mon, 15 Dec 2008 15:00:37 +0000 Subject: [PATCH 11/13] Re-indent ClutterPath header Match the indentation and style of the other Clutter headers. --- clutter/clutter-path.h | 234 ++++++++++++++++++----------------------- 1 file changed, 100 insertions(+), 134 deletions(-) diff --git a/clutter/clutter-path.h b/clutter/clutter-path.h index 5e5210b9c..bdf5dfe76 100644 --- a/clutter/clutter-path.h +++ b/clutter/clutter-path.h @@ -34,46 +34,35 @@ G_BEGIN_DECLS -#define CLUTTER_TYPE_PATH \ - (clutter_path_get_type()) -#define CLUTTER_PATH(obj) \ - (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ - CLUTTER_TYPE_PATH, \ - ClutterPath)) -#define CLUTTER_PATH_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_CAST ((klass), \ - CLUTTER_TYPE_PATH, \ - ClutterPathClass)) -#define CLUTTER_IS_PATH(obj) \ - (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \ - CLUTTER_TYPE_PATH)) -#define CLUTTER_IS_PATH_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_TYPE ((klass), \ - CLUTTER_TYPE_PATH)) -#define CLUTTER_PATH_GET_CLASS(obj) \ - (G_TYPE_INSTANCE_GET_CLASS ((obj), \ - CLUTTER_TYPE_PATH, \ - ClutterPathClass)) +#define CLUTTER_TYPE_PATH (clutter_path_get_type ()) +#define CLUTTER_TYPE_PATH_NODE (clutter_path_node_get_type ()) +#define CLUTTER_PATH(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CLUTTER_TYPE_PATH, ClutterPath)) +#define CLUTTER_PATH_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CLUTTER_TYPE_PATH, ClutterPathClass)) +#define CLUTTER_IS_PATH(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CLUTTER_TYPE_PATH)) +#define CLUTTER_IS_PATH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CLUTTER_TYPE_PATH)) +#define CLUTTER_PATH_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CLUTTER_TYPE_PATH, ClutterPathClass)) -#define CLUTTER_TYPE_PATH_NODE (clutter_path_node_get_type ()) - -#define CLUTTER_PATH_RELATIVE 32 +#define CLUTTER_PATH_RELATIVE (32) /** * ClutterPathNodeType: * @CLUTTER_PATH_MOVE_TO: jump to the given position * @CLUTTER_PATH_LINE_TO: create a line from the last node to the - * given position + * given position * @CLUTTER_PATH_CURVE_TO: bezier curve using the last position and - * three control points. + * three control points. * @CLUTTER_PATH_CLOSE: create a line from the last node to the last - * %CLUTTER_PATH_MOVE_TO node. + * %CLUTTER_PATH_MOVE_TO node. * @CLUTTER_PATH_REL_MOVE_TO: same as %CLUTTER_PATH_MOVE_TO but with - * coordinates relative to the last node. + * coordinates relative to the last node. * @CLUTTER_PATH_REL_LINE_TO: same as %CLUTTER_PATH_LINE_TO but with - * coordinates relative to the last node. + * coordinates relative to the last node. * @CLUTTER_PATH_REL_CURVE_TO: same as %CLUTTER_PATH_CURVE_TO but with - * coordinates relative to the last node. + * coordinates relative to the last node. + * + * Types of nodes in a #ClutterPath. + * + * Since: 1.0 */ typedef enum { CLUTTER_PATH_MOVE_TO = 0, @@ -102,24 +91,15 @@ typedef struct _ClutterPathNode ClutterPathNode; * Since: 1.0 */ typedef void (* ClutterPathCallback) (const ClutterPathNode *node, - gpointer data); - -/** - * ClutterPathClass: - * - * The #ClutterPathClass struct contains only private data. - */ -struct _ClutterPathClass -{ - /*< private >*/ - GInitiallyUnownedClass parent_class; -}; + gpointer data); /** * ClutterPath: * * The #ClutterPath struct contains only private data and should * be accessed with the functions below. + * + * Since: 1.0 */ struct _ClutterPath { @@ -129,6 +109,19 @@ struct _ClutterPath ClutterPathPrivate *priv; }; +/** + * ClutterPathClass: + * + * The #ClutterPathClass struct contains only private data. + * + * Since: 1.0 + */ +struct _ClutterPathClass +{ + /*< private >*/ + GInitiallyUnownedClass parent_class; +}; + /** * ClutterPathNode: * @type: the node's type @@ -151,101 +144,74 @@ struct _ClutterPathNode }; GType clutter_path_get_type (void) G_GNUC_CONST; +GType clutter_path_node_get_type (void) G_GNUC_CONST; -ClutterPath *clutter_path_new (void); +ClutterPath *clutter_path_new (void); +ClutterPath *clutter_path_new_with_description (const gchar *desc); +void clutter_path_add_move_to (ClutterPath *path, + gint x, + gint y); +void clutter_path_add_rel_move_to (ClutterPath *path, + gint x, + gint y); +void clutter_path_add_line_to (ClutterPath *path, + gint x, + gint y); +void clutter_path_add_rel_line_to (ClutterPath *path, + gint x, + gint y); +void clutter_path_add_curve_to (ClutterPath *path, + gint x1, + gint y1, + gint x2, + gint y2, + gint x3, + gint y3); +void clutter_path_add_rel_curve_to (ClutterPath *path, + gint x1, + gint y1, + gint x2, + gint y2, + gint x3, + gint y3); +void clutter_path_add_close (ClutterPath *path); +gboolean clutter_path_add_string (ClutterPath *path, + const gchar *str); +void clutter_path_add_node (ClutterPath *path, + const ClutterPathNode *node); +void clutter_path_add_cairo_path (ClutterPath *path, + const cairo_path_t *cpath); +guint clutter_path_get_n_nodes (ClutterPath *path); +void clutter_path_get_node (ClutterPath *path, + guint index_, + ClutterPathNode *node); +GSList * clutter_path_get_nodes (ClutterPath *path); +void clutter_path_foreach (ClutterPath *path, + ClutterPathCallback callback, + gpointer user_data); +void clutter_path_insert_node (ClutterPath *path, + gint index_, + const ClutterPathNode *node); +void clutter_path_remove_node (ClutterPath *path, + guint index_); +void clutter_path_replace_node (ClutterPath *path, + guint index_, + const ClutterPathNode *node); +gchar * clutter_path_get_description (ClutterPath *path); +gboolean clutter_path_set_description (ClutterPath *path, + const gchar *str); +void clutter_path_clear (ClutterPath *path); +void clutter_path_to_cairo_path (ClutterPath *path, + cairo_t *cr); +guint clutter_path_get_position (ClutterPath *path, + guint alpha, + ClutterKnot *position); +guint clutter_path_get_length (ClutterPath *path); -ClutterPath *clutter_path_new_with_description (const gchar *desc); - -void clutter_path_add_move_to (ClutterPath *path, - gint x, - gint y); - -void clutter_path_add_rel_move_to (ClutterPath *path, - gint x, - gint y); - -void clutter_path_add_line_to (ClutterPath *path, - gint x, - gint y); - -void clutter_path_add_rel_line_to (ClutterPath *path, - gint x, - gint y); - -void clutter_path_add_curve_to (ClutterPath *path, - gint x1, - gint y1, - gint x2, - gint y2, - gint x3, - gint y3); - -void clutter_path_add_rel_curve_to (ClutterPath *path, - gint x1, - gint y1, - gint x2, - gint y2, - gint x3, - gint y3); - -void clutter_path_add_close (ClutterPath *path); - -gboolean clutter_path_add_string (ClutterPath *path, - const gchar *str); - -void clutter_path_add_node (ClutterPath *path, - const ClutterPathNode *node); - -void clutter_path_add_cairo_path (ClutterPath *path, - const cairo_path_t *cpath); - -guint clutter_path_get_n_nodes (ClutterPath *path); - -void clutter_path_get_node (ClutterPath *path, - guint index, - ClutterPathNode *node); - -GSList *clutter_path_get_nodes (ClutterPath *path); - -void clutter_path_foreach (ClutterPath *path, - ClutterPathCallback callback, - gpointer user_data); - -void clutter_path_insert_node (ClutterPath *path, - gint index, - const ClutterPathNode *node); - -void clutter_path_remove_node (ClutterPath *path, - guint index); - -void clutter_path_replace_node (ClutterPath *path, - guint index, - const ClutterPathNode *node); - -gchar *clutter_path_get_description (ClutterPath *path); - -gboolean clutter_path_set_description (ClutterPath *path, - const gchar *str); - -void clutter_path_clear (ClutterPath *path); - -void clutter_path_to_cairo_path (ClutterPath *path, - cairo_t *cr); - -guint clutter_path_get_position (ClutterPath *path, - guint alpha, - ClutterKnot *position); - -guint clutter_path_get_length (ClutterPath *path); - -ClutterPathNode *clutter_path_node_copy (const ClutterPathNode *node); - -void clutter_path_node_free (ClutterPathNode *node); - -gboolean clutter_path_node_equal (const ClutterPathNode *node_a, - const ClutterPathNode *node_b); - -GType clutter_path_node_get_type (void); +ClutterPathNode *clutter_path_node_copy (const ClutterPathNode *node); +void clutter_path_node_free (ClutterPathNode *node); +gboolean clutter_path_node_equal (const ClutterPathNode *node_a, + const ClutterPathNode *node_b); G_END_DECLS From e130e0c9b30ee688b6dcd9d1fece05ea88b558e7 Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Wed, 17 Dec 2008 15:40:33 +0000 Subject: [PATCH 12/13] [cairo-texture] Silently discard 0x0 surfaces The current CairoTexture can be created with a surface size of 0 by 0 pixels, but a warning will be printed. Worse, the surface can be resized to be 0 by 0 pixels without a warning. The :surface-width and :surface-height properties accept a minimum value of 0, and not check is performed on either the constructor or set_surface_size() parameters to enforce the "greater than zero" rule. The correct and consistent behaviour is to allow a 0 by 0 pixels surface size everywhere; inside surface_resize_internal(), the current surface will be destroyed and if either :surface-width or :surface-height are set to 0, the resizing terminates. Attempting to create a Cairo context from a CairoTexture with either :surface-width or :surface-height set to 0 will result in a warning. This allows: - creating a CairoTexture with :surface-width or :surface-height set to zero and delaying the surface resize at a later point; - resizing the surface to 0 by 0 pixels to destroy the image surface used internally; - increase the consistency in the usage of CairoTexture. --- clutter/clutter-cairo-texture.c | 46 +++++++++++++++------------------ 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/clutter/clutter-cairo-texture.c b/clutter/clutter-cairo-texture.c index 2343a9afa..cb170ea41 100644 --- a/clutter/clutter-cairo-texture.c +++ b/clutter/clutter-cairo-texture.c @@ -243,6 +243,9 @@ clutter_cairo_texture_surface_resize_internal (ClutterCairoTexture *cairo) priv->cr_surface_data = NULL; } + if (priv->width == 0 || priv->height == 0) + return; + #if CAIRO_VERSION > 106000 priv->rowstride = cairo_format_stride_for_width (priv->format, priv->width); #else @@ -287,34 +290,15 @@ clutter_cairo_texture_surface_resize_internal (ClutterCairoTexture *cairo) 4, 0, NULL); } -static GObject * -clutter_cairo_texture_constructor (GType gtype, - guint n_properties, - GObjectConstructParam *properties) +static void +clutter_cairo_texture_constructed (GObject *gobject) { - GObjectClass *parent_class; - GObject *obj; - ClutterCairoTexture *cairo; - ClutterCairoTexturePrivate *priv; - - parent_class = G_OBJECT_CLASS (clutter_cairo_texture_parent_class); - obj = parent_class->constructor (gtype, n_properties, properties); - - /* Now all of the object properties are set */ - cairo = CLUTTER_CAIRO_TEXTURE (obj); - priv = cairo->priv; - - if (!priv->width || !priv->height) - { - g_warning ("Unable to create the Cairo surface: invalid size (%dx%d)", - priv->width, - priv->height); - return obj; - } + ClutterCairoTexture *cairo = CLUTTER_CAIRO_TEXTURE (gobject); clutter_cairo_texture_surface_resize_internal (cairo); - return obj; + if (G_OBJECT_CLASS (clutter_cairo_texture_parent_class)->constructed) + G_OBJECT_CLASS (clutter_cairo_texture_parent_class)->constructed (gobject); } static void @@ -356,7 +340,7 @@ clutter_cairo_texture_class_init (ClutterCairoTextureClass *klass) gobject_class->finalize = clutter_cairo_texture_finalize; gobject_class->set_property = clutter_cairo_texture_set_property; gobject_class->get_property = clutter_cairo_texture_get_property; - gobject_class->constructor = clutter_cairo_texture_constructor; + gobject_class->constructed = clutter_cairo_texture_constructed; actor_class->get_preferred_width = clutter_cairo_texture_get_preferred_width; @@ -594,6 +578,18 @@ clutter_cairo_texture_create_region (ClutterCairoTexture *self, if (height < 0) height = priv->height; + if (width == 0 || height == 0) + { + g_warning ("Unable to create a context for an image surface of " + "width %d and height %d. Set the surface size to be " + "at least 1 pixel by 1 pixel.", + width, height); + return NULL; + } + + if (!priv->cr_surface) + return NULL; + ctxt = g_new0 (ClutterCairoTextureContext, 1); ctxt->cairo = self; From 64de0411eaaffd5237c45475645005d282db05cf Mon Sep 17 00:00:00 2001 From: Neil Roberts Date: Fri, 19 Dec 2008 13:15:26 +0000 Subject: [PATCH 13/13] [cairo-texture] Remove the construct only restriction on surface size It is possible to change the surface size after construction with clutter_cairo_texture_set_surface_size so it doesn't seem right to restrict changing the properties. clutter_cairo_texture_resize_surface_internal is called in a handler for the notify signal. It is called there rather than directly in the set_property handler so that changing both properties in a single g_object_set will only cause one resize. The constructed override is no longer needed. resize_surface_internal will now bail out if the size of the surface is already the right size. --- clutter/clutter-cairo-texture.c | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/clutter/clutter-cairo-texture.c b/clutter/clutter-cairo-texture.c index cb170ea41..d7680787b 100644 --- a/clutter/clutter-cairo-texture.c +++ b/clutter/clutter-cairo-texture.c @@ -228,6 +228,12 @@ clutter_cairo_texture_surface_resize_internal (ClutterCairoTexture *cairo) { cairo_surface_t *surface = priv->cr_surface; + /* If the surface is already the right size then don't bother + doing anything */ + if (priv->width == cairo_image_surface_get_width (priv->cr_surface) + && priv->height == cairo_image_surface_get_height (priv->cr_surface)) + return; + cairo_surface_finish (surface); cairo_surface_set_user_data (surface, &clutter_cairo_texture_surface_key, @@ -291,14 +297,24 @@ clutter_cairo_texture_surface_resize_internal (ClutterCairoTexture *cairo) } static void -clutter_cairo_texture_constructed (GObject *gobject) +clutter_cairo_texture_notify (GObject *object, + GParamSpec *pspec) { - ClutterCairoTexture *cairo = CLUTTER_CAIRO_TEXTURE (gobject); + /* When the surface width or height changes then resize the cairo + surface. This is done here instead of directly in set_property so + that if both the width and height properties are set using a + single call to g_object_set then the surface will only be resized + once because the notifications will be frozen in between */ + if (!strcmp ("surface-width", pspec->name) + || !strcmp ("surface-height", pspec->name)) + { + ClutterCairoTexture *cairo = CLUTTER_CAIRO_TEXTURE (object); - clutter_cairo_texture_surface_resize_internal (cairo); + clutter_cairo_texture_surface_resize_internal (cairo); + } - if (G_OBJECT_CLASS (clutter_cairo_texture_parent_class)->constructed) - G_OBJECT_CLASS (clutter_cairo_texture_parent_class)->constructed (gobject); + if (G_OBJECT_CLASS (clutter_cairo_texture_parent_class)->notify) + G_OBJECT_CLASS (clutter_cairo_texture_parent_class)->notify (object, pspec); } static void @@ -340,7 +356,7 @@ clutter_cairo_texture_class_init (ClutterCairoTextureClass *klass) gobject_class->finalize = clutter_cairo_texture_finalize; gobject_class->set_property = clutter_cairo_texture_set_property; gobject_class->get_property = clutter_cairo_texture_get_property; - gobject_class->constructed = clutter_cairo_texture_constructed; + gobject_class->notify = clutter_cairo_texture_notify; actor_class->get_preferred_width = clutter_cairo_texture_get_preferred_width; @@ -364,7 +380,6 @@ clutter_cairo_texture_class_init (ClutterCairoTextureClass *klass) "Surface Width", 0, G_MAXUINT, 0, - G_PARAM_CONSTRUCT_ONLY | CLUTTER_PARAM_READWRITE)); /** * ClutterCairoTexture:surface-height: @@ -381,7 +396,6 @@ clutter_cairo_texture_class_init (ClutterCairoTextureClass *klass) "Surface Height", 0, G_MAXUINT, 0, - G_PARAM_CONSTRUCT_ONLY | CLUTTER_PARAM_READWRITE)); }