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="../../../../tests/interactive/test-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-color.h"
63 #include "clutter-content-private.h"
64 #include "clutter-marshal.h"
65 #include "clutter-paint-node.h"
66 #include "clutter-paint-nodes.h"
67 #include "clutter-private.h"
69 struct _ClutterCanvasPrivate
89 static GParamSpec *obj_props[LAST_PROP] = { NULL, };
98 static guint canvas_signals[LAST_SIGNAL] = { 0, };
100 static void clutter_content_iface_init (ClutterContentIface *iface);
102 G_DEFINE_TYPE_WITH_CODE (ClutterCanvas, clutter_canvas, G_TYPE_OBJECT,
103 G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_CONTENT,
104 clutter_content_iface_init))
107 clutter_cairo_context_draw_marshaller (GClosure *closure,
108 GValue *return_value,
109 guint n_param_values,
110 const GValue *param_values,
111 gpointer invocation_hint,
112 gpointer marshal_data)
114 cairo_t *cr = g_value_get_boxed (¶m_values[1]);
118 _clutter_marshal_BOOLEAN__BOXED_INT_INT (closure,
129 clutter_canvas_finalize (GObject *gobject)
131 ClutterCanvasPrivate *priv = CLUTTER_CANVAS (gobject)->priv;
133 if (priv->buffer != NULL)
135 cogl_object_unref (priv->buffer);
139 G_OBJECT_CLASS (clutter_canvas_parent_class)->finalize (gobject);
143 clutter_canvas_set_property (GObject *gobject,
148 ClutterCanvasPrivate *priv = CLUTTER_CANVAS (gobject)->priv;
153 if (priv->width != g_value_get_int (value))
155 priv->width = g_value_get_int (value);
156 clutter_content_invalidate (CLUTTER_CONTENT (gobject));
161 if (priv->height != g_value_get_int (value))
163 priv->height = g_value_get_int (value);
164 clutter_content_invalidate (CLUTTER_CONTENT (gobject));
169 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
175 clutter_canvas_get_property (GObject *gobject,
180 ClutterCanvasPrivate *priv = CLUTTER_CANVAS (gobject)->priv;
185 g_value_set_int (value, priv->width);
189 g_value_set_int (value, priv->height);
193 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
199 clutter_canvas_class_init (ClutterCanvasClass *klass)
201 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
203 g_type_class_add_private (klass, sizeof (ClutterCanvasPrivate));
206 * ClutterCanvas:width:
208 * The width of the canvas.
212 obj_props[PROP_WIDTH] =
213 g_param_spec_int ("width",
215 P_("The width of the canvas"),
219 G_PARAM_STATIC_STRINGS);
222 * ClutterCanvas:height:
224 * The height of the canvas.
228 obj_props[PROP_HEIGHT] =
229 g_param_spec_int ("height",
231 P_("The height of the canvas"),
235 G_PARAM_STATIC_STRINGS);
238 * ClutterCanvas::draw:
239 * @canvas: the #ClutterCanvas that emitted the signal
240 * @cr: the Cairo context used to draw
241 * @width: the width of the @canvas
242 * @height: the height of the @canvas
244 * The #ClutterCanvas::draw signal is emitted each time a canvas is
247 * It is safe to connect multiple handlers to this signal: each
248 * handler invocation will be automatically protected by cairo_save()
249 * and cairo_restore() pairs.
251 * Return value: %TRUE if the signal emission should stop, and
256 canvas_signals[DRAW] =
257 g_signal_new (I_("draw"),
258 G_TYPE_FROM_CLASS (klass),
259 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE,
260 G_STRUCT_OFFSET (ClutterCanvasClass, draw),
261 _clutter_boolean_handled_accumulator, NULL,
262 clutter_cairo_context_draw_marshaller,
264 CAIRO_GOBJECT_TYPE_CONTEXT,
268 gobject_class->set_property = clutter_canvas_set_property;
269 gobject_class->get_property = clutter_canvas_get_property;
270 gobject_class->finalize = clutter_canvas_finalize;
272 g_object_class_install_properties (gobject_class, LAST_PROP, obj_props);
276 clutter_canvas_init (ClutterCanvas *self)
278 self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, CLUTTER_TYPE_CANVAS,
279 ClutterCanvasPrivate);
281 self->priv->width = -1;
282 self->priv->height = -1;
286 clutter_canvas_paint_content (ClutterContent *content,
288 ClutterPaintNode *root)
290 ClutterCanvas *self = CLUTTER_CANVAS (content);
291 ClutterPaintNode *node;
292 CoglTexture *texture;
295 guint8 paint_opacity;
296 ClutterScalingFilter min_f, mag_f;
298 if (self->priv->buffer == NULL)
301 texture = cogl_texture_new_from_bitmap (self->priv->buffer,
302 COGL_TEXTURE_NO_SLICING,
303 CLUTTER_CAIRO_FORMAT_ARGB32);
307 clutter_actor_get_content_box (actor, &box);
308 paint_opacity = clutter_actor_get_paint_opacity (actor);
309 clutter_actor_get_content_scaling_filters (actor, &min_f, &mag_f);
311 color.red = paint_opacity;
312 color.green = paint_opacity;
313 color.blue = paint_opacity;
314 color.alpha = paint_opacity;
316 node = clutter_texture_node_new (texture, &color, min_f, mag_f);
317 cogl_object_unref (texture);
319 clutter_paint_node_set_name (node, "Canvas");
320 clutter_paint_node_add_rectangle (node, &box);
321 clutter_paint_node_add_child (root, node);
322 clutter_paint_node_unref (node);
326 clutter_canvas_emit_draw (ClutterCanvas *self)
328 ClutterCanvasPrivate *priv = self->priv;
329 cairo_surface_t *surface;
330 gboolean mapped_buffer;
336 g_assert (priv->width > 0 && priv->width > 0);
338 if (priv->buffer == NULL)
342 ctx = clutter_backend_get_cogl_context (clutter_get_default_backend ());
343 priv->buffer = cogl_bitmap_new_with_size (ctx,
346 CLUTTER_CAIRO_FORMAT_ARGB32);
349 buffer = COGL_BUFFER (cogl_bitmap_get_buffer (priv->buffer));
353 cogl_buffer_set_update_hint (buffer, COGL_BUFFER_UPDATE_HINT_DYNAMIC);
355 data = cogl_buffer_map (buffer,
356 COGL_BUFFER_ACCESS_READ_WRITE,
357 COGL_BUFFER_MAP_HINT_DISCARD);
361 int bitmap_stride = cogl_bitmap_get_rowstride (priv->buffer);
363 surface = cairo_image_surface_create_for_data (data,
368 mapped_buffer = TRUE;
372 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
376 mapped_buffer = FALSE;
379 self->priv->cr = cr = cairo_create (surface);
381 g_signal_emit (self, canvas_signals[DRAW], 0,
382 cr, priv->width, priv->height,
385 self->priv->cr = NULL;
389 cogl_buffer_unmap (buffer);
392 int size = cairo_image_surface_get_stride (surface) * priv->height;
393 cogl_buffer_set_data (buffer,
395 cairo_image_surface_get_data (surface),
399 cairo_surface_destroy (surface);
403 clutter_canvas_invalidate (ClutterContent *content)
405 ClutterCanvas *self = CLUTTER_CANVAS (content);
406 ClutterCanvasPrivate *priv = self->priv;
408 if (priv->buffer != NULL)
410 cogl_object_unref (priv->buffer);
414 if (priv->width <= 0 || priv->height <= 0)
417 clutter_canvas_emit_draw (self);
421 clutter_canvas_get_preferred_size (ClutterContent *content,
425 ClutterCanvasPrivate *priv = CLUTTER_CANVAS (content)->priv;
427 if (priv->width < 0 || priv->height < 0)
431 *width = priv->width;
434 *height = priv->height;
440 clutter_content_iface_init (ClutterContentIface *iface)
442 iface->invalidate = clutter_canvas_invalidate;
443 iface->paint_content = clutter_canvas_paint_content;
444 iface->get_preferred_size = clutter_canvas_get_preferred_size;
448 * clutter_canvas_new:
450 * Creates a new instance of #ClutterCanvas.
452 * You should call clutter_canvas_set_size() to set the size of the canvas.
454 * You should call clutter_content_invalidate() every time you wish to
455 * draw the contents of the canvas.
457 * Return value: (transfer full): The newly allocated instance of
458 * #ClutterCanvas. Use g_object_unref() when done.
463 clutter_canvas_new (void)
465 return g_object_new (CLUTTER_TYPE_CANVAS, NULL);
469 * clutter_canvas_set_size:
470 * @canvas: a #ClutterCanvas
471 * @width: the width of the canvas, in pixels
472 * @height: the height of the canvas, in pixels
474 * Sets the size of the @canvas.
476 * This function will cause the @canvas to be invalidated.
481 clutter_canvas_set_size (ClutterCanvas *canvas,
486 gboolean width_changed = FALSE, height_changed = FALSE;
488 g_return_if_fail (CLUTTER_IS_CANVAS (canvas));
489 g_return_if_fail (width >= -1 && height >= -1);
491 obj = G_OBJECT (canvas);
493 g_object_freeze_notify (obj);
495 if (canvas->priv->width != width)
497 canvas->priv->width = width;
498 width_changed = TRUE;
500 g_object_notify_by_pspec (obj, obj_props[PROP_WIDTH]);
503 if (canvas->priv->height != height)
505 canvas->priv->height = height;
506 height_changed = TRUE;
508 g_object_notify_by_pspec (obj, obj_props[PROP_HEIGHT]);
511 if (width_changed || height_changed)
512 clutter_content_invalidate (CLUTTER_CONTENT (canvas));
514 g_object_thaw_notify (obj);