4 * An OpenGL based 'interactive canvas' library.
6 * Copyright (C) 2012 Intel Corporation.
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
22 * Emmanuele Bassi <ebassi@linux.intel.com>
26 * SECTION:clutter-canvas
27 * @Title: ClutterCanvas
28 * @Short_Description: Content for 2D painting
29 * @See_Also: #ClutterContent
31 * The #ClutterCanvas class is a #ClutterContent implementation that allows
32 * drawing using the Cairo API on a 2D surface.
34 * In order to draw on a #ClutterCanvas, you should connect a handler to the
35 * #ClutterCanvas::draw signal; the signal will receive a #cairo_t context
36 * that can be used to draw. #ClutterCanvas will emit the #ClutterCanvas::draw
37 * signal when invalidated using clutter_content_invalidate().
39 * <informalexample id="canvas-example">
41 * <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" parse="text" href="../../../../examples/canvas.c">
42 * <xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback>
47 * #ClutterCanvas is available since Clutter 1.10.
54 #include <cogl/cogl.h>
55 #include <cairo-gobject.h>
57 #include "clutter-canvas.h"
59 #define CLUTTER_ENABLE_EXPERIMENTAL_API
61 #include "clutter-backend.h"
62 #include "clutter-cairo.h"
63 #include "clutter-color.h"
64 #include "clutter-content-private.h"
65 #include "clutter-marshal.h"
66 #include "clutter-paint-node.h"
67 #include "clutter-paint-nodes.h"
68 #include "clutter-private.h"
70 struct _ClutterCanvasPrivate
90 static GParamSpec *obj_props[LAST_PROP] = { NULL, };
99 static guint canvas_signals[LAST_SIGNAL] = { 0, };
101 static void clutter_content_iface_init (ClutterContentIface *iface);
103 G_DEFINE_TYPE_WITH_CODE (ClutterCanvas, clutter_canvas, G_TYPE_OBJECT,
104 G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_CONTENT,
105 clutter_content_iface_init))
108 clutter_cairo_context_draw_marshaller (GClosure *closure,
109 GValue *return_value,
110 guint n_param_values,
111 const GValue *param_values,
112 gpointer invocation_hint,
113 gpointer marshal_data)
115 cairo_t *cr = g_value_get_boxed (¶m_values[1]);
119 _clutter_marshal_BOOLEAN__BOXED_INT_INT (closure,
130 clutter_canvas_finalize (GObject *gobject)
132 ClutterCanvasPrivate *priv = CLUTTER_CANVAS (gobject)->priv;
134 if (priv->buffer != NULL)
136 cogl_object_unref (priv->buffer);
140 G_OBJECT_CLASS (clutter_canvas_parent_class)->finalize (gobject);
144 clutter_canvas_set_property (GObject *gobject,
149 ClutterCanvasPrivate *priv = CLUTTER_CANVAS (gobject)->priv;
154 if (priv->width != g_value_get_int (value))
156 priv->width = g_value_get_int (value);
157 clutter_content_invalidate (CLUTTER_CONTENT (gobject));
162 if (priv->height != g_value_get_int (value))
164 priv->height = g_value_get_int (value);
165 clutter_content_invalidate (CLUTTER_CONTENT (gobject));
170 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
176 clutter_canvas_get_property (GObject *gobject,
181 ClutterCanvasPrivate *priv = CLUTTER_CANVAS (gobject)->priv;
186 g_value_set_int (value, priv->width);
190 g_value_set_int (value, priv->height);
194 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
200 clutter_canvas_class_init (ClutterCanvasClass *klass)
202 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
204 g_type_class_add_private (klass, sizeof (ClutterCanvasPrivate));
207 * ClutterCanvas:width:
209 * The width of the canvas.
213 obj_props[PROP_WIDTH] =
214 g_param_spec_int ("width",
216 P_("The width of the canvas"),
220 G_PARAM_STATIC_STRINGS);
223 * ClutterCanvas:height:
225 * The height of the canvas.
229 obj_props[PROP_HEIGHT] =
230 g_param_spec_int ("height",
232 P_("The height of the canvas"),
236 G_PARAM_STATIC_STRINGS);
239 * ClutterCanvas::draw:
240 * @canvas: the #ClutterCanvas that emitted the signal
241 * @cr: the Cairo context used to draw
242 * @width: the width of the @canvas
243 * @height: the height of the @canvas
245 * The #ClutterCanvas::draw signal is emitted each time a canvas is
248 * It is safe to connect multiple handlers to this signal: each
249 * handler invocation will be automatically protected by cairo_save()
250 * and cairo_restore() pairs.
252 * Return value: %TRUE if the signal emission should stop, and
257 canvas_signals[DRAW] =
258 g_signal_new (I_("draw"),
259 G_TYPE_FROM_CLASS (klass),
260 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE,
261 G_STRUCT_OFFSET (ClutterCanvasClass, draw),
262 _clutter_boolean_handled_accumulator, NULL,
263 clutter_cairo_context_draw_marshaller,
265 CAIRO_GOBJECT_TYPE_CONTEXT,
269 gobject_class->set_property = clutter_canvas_set_property;
270 gobject_class->get_property = clutter_canvas_get_property;
271 gobject_class->finalize = clutter_canvas_finalize;
273 g_object_class_install_properties (gobject_class, LAST_PROP, obj_props);
277 clutter_canvas_init (ClutterCanvas *self)
279 self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, CLUTTER_TYPE_CANVAS,
280 ClutterCanvasPrivate);
282 self->priv->width = -1;
283 self->priv->height = -1;
287 clutter_canvas_paint_content (ClutterContent *content,
289 ClutterPaintNode *root)
291 ClutterCanvas *self = CLUTTER_CANVAS (content);
292 ClutterPaintNode *node;
293 CoglTexture *texture;
296 guint8 paint_opacity;
297 ClutterScalingFilter min_f, mag_f;
299 if (self->priv->buffer == NULL)
302 texture = cogl_texture_new_from_bitmap (self->priv->buffer,
303 COGL_TEXTURE_NO_SLICING,
304 CLUTTER_CAIRO_FORMAT_ARGB32);
308 clutter_actor_get_content_box (actor, &box);
309 paint_opacity = clutter_actor_get_paint_opacity (actor);
310 clutter_actor_get_content_scaling_filters (actor, &min_f, &mag_f);
312 color.red = paint_opacity;
313 color.green = paint_opacity;
314 color.blue = paint_opacity;
315 color.alpha = paint_opacity;
317 node = clutter_texture_node_new (texture, &color, min_f, mag_f);
318 cogl_object_unref (texture);
320 clutter_paint_node_set_name (node, "Canvas");
321 clutter_paint_node_add_rectangle (node, &box);
322 clutter_paint_node_add_child (root, node);
323 clutter_paint_node_unref (node);
327 clutter_canvas_emit_draw (ClutterCanvas *self)
329 ClutterCanvasPrivate *priv = self->priv;
330 cairo_surface_t *surface;
331 gboolean mapped_buffer;
337 g_assert (priv->width > 0 && priv->width > 0);
339 if (priv->buffer == NULL)
343 ctx = clutter_backend_get_cogl_context (clutter_get_default_backend ());
344 priv->buffer = cogl_bitmap_new_with_size (ctx,
347 CLUTTER_CAIRO_FORMAT_ARGB32);
350 buffer = COGL_BUFFER (cogl_bitmap_get_buffer (priv->buffer));
354 cogl_buffer_set_update_hint (buffer, COGL_BUFFER_UPDATE_HINT_DYNAMIC);
356 data = cogl_buffer_map (buffer,
357 COGL_BUFFER_ACCESS_READ_WRITE,
358 COGL_BUFFER_MAP_HINT_DISCARD);
362 int bitmap_stride = cogl_bitmap_get_rowstride (priv->buffer);
364 surface = cairo_image_surface_create_for_data (data,
369 mapped_buffer = TRUE;
373 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
377 mapped_buffer = FALSE;
380 self->priv->cr = cr = cairo_create (surface);
382 g_signal_emit (self, canvas_signals[DRAW], 0,
383 cr, priv->width, priv->height,
386 self->priv->cr = NULL;
390 cogl_buffer_unmap (buffer);
393 int size = cairo_image_surface_get_stride (surface) * priv->height;
394 cogl_buffer_set_data (buffer,
396 cairo_image_surface_get_data (surface),
400 cairo_surface_destroy (surface);
404 clutter_canvas_invalidate (ClutterContent *content)
406 ClutterCanvas *self = CLUTTER_CANVAS (content);
407 ClutterCanvasPrivate *priv = self->priv;
409 if (priv->buffer != NULL)
411 cogl_object_unref (priv->buffer);
415 if (priv->width <= 0 || priv->height <= 0)
418 clutter_canvas_emit_draw (self);
422 clutter_canvas_get_preferred_size (ClutterContent *content,
426 ClutterCanvasPrivate *priv = CLUTTER_CANVAS (content)->priv;
428 if (priv->width < 0 || priv->height < 0)
432 *width = priv->width;
435 *height = priv->height;
441 clutter_content_iface_init (ClutterContentIface *iface)
443 iface->invalidate = clutter_canvas_invalidate;
444 iface->paint_content = clutter_canvas_paint_content;
445 iface->get_preferred_size = clutter_canvas_get_preferred_size;
449 * clutter_canvas_new:
451 * Creates a new instance of #ClutterCanvas.
453 * You should call clutter_canvas_set_size() to set the size of the canvas.
455 * You should call clutter_content_invalidate() every time you wish to
456 * draw the contents of the canvas.
458 * Return value: (transfer full): The newly allocated instance of
459 * #ClutterCanvas. Use g_object_unref() when done.
464 clutter_canvas_new (void)
466 return g_object_new (CLUTTER_TYPE_CANVAS, NULL);
470 * clutter_canvas_set_size:
471 * @canvas: a #ClutterCanvas
472 * @width: the width of the canvas, in pixels
473 * @height: the height of the canvas, in pixels
475 * Sets the size of the @canvas.
477 * This function will cause the @canvas to be invalidated.
482 clutter_canvas_set_size (ClutterCanvas *canvas,
487 gboolean width_changed = FALSE, height_changed = FALSE;
489 g_return_if_fail (CLUTTER_IS_CANVAS (canvas));
490 g_return_if_fail (width >= -1 && height >= -1);
492 obj = G_OBJECT (canvas);
494 g_object_freeze_notify (obj);
496 if (canvas->priv->width != width)
498 canvas->priv->width = width;
499 width_changed = TRUE;
501 g_object_notify_by_pspec (obj, obj_props[PROP_WIDTH]);
504 if (canvas->priv->height != height)
506 canvas->priv->height = height;
507 height_changed = TRUE;
509 g_object_notify_by_pspec (obj, obj_props[PROP_HEIGHT]);
512 if (width_changed || height_changed)
513 clutter_content_invalidate (CLUTTER_CONTENT (canvas));
515 g_object_thaw_notify (obj);