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-actor.c b/clutter/clutter-actor.c index 719524c91..d804e41d2 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 diff --git a/clutter/clutter-cairo-texture.c b/clutter/clutter-cairo-texture.c new file mode 100644 index 000000000..d7680787b --- /dev/null +++ b/clutter/clutter-cairo-texture.c @@ -0,0 +1,754 @@ +/* + * 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 and then + * call cairo_destroy() when done. + * + * As soon as the context is destroyed with cairo_destroy(), the contents + * of the surface 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 it will lead to 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 +}; + +#ifdef CLUTTER_ENABLE_DEBUG +#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); \ + } } G_STMT_END +#else +#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)) + +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; + + /* 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, + 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 (priv->width == 0 || priv->height == 0) + return; + +#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 void +clutter_cairo_texture_notify (GObject *object, + GParamSpec *pspec) +{ + /* 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); + } + + if (G_OBJECT_CLASS (clutter_cairo_texture_parent_class)->notify) + G_OBJECT_CLASS (clutter_cairo_texture_parent_class)->notify (object, pspec); +} + +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->notify = clutter_cairo_texture_notify; + + 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, + 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, + 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 and have the cairo surface + * match that 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 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++) + { + 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); + + clutter_warn_if_paint_fail (self); + + priv = self->priv; + + if (width < 0) + width = priv->width; + + 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; + + 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); + + clutter_warn_if_paint_fail (self); + + 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-path.c b/clutter/clutter-path.c index 973d861da..993ae5a10 100644 --- a/clutter/clutter-path.c +++ b/clutter/clutter-path.c @@ -735,6 +735,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 52969eee1..a7c27a192 100644 --- a/clutter/clutter-path.h +++ b/clutter/clutter-path.h @@ -30,49 +30,39 @@ #include #include +#include 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, @@ -101,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 { @@ -128,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 @@ -150,95 +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, + gdouble factor, + 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); - -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); - -guint clutter_path_get_position (ClutterPath *path, - gdouble progress, - 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 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); } 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" 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" 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..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 @@ -1629,3 +1631,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 diff --git a/tests/conform/test-path.c b/tests/conform/test-path.c index 2430494f4..29b79ef24 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) { @@ -523,6 +616,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 },