1 /*
2  * Clutter
3  *
4  * An OpenGL based 'interactive canvas' library.
5  *
6  * Authored By: Emmanuele Bassi <ebassi@linux.intel.com>
7  *              Matthew Allum <mallum@o-hand.com>
8  *              Chris Lord <chris@o-hand.com>
9  *              Iain Holmes <iain@o-hand.com>
10  *              Neil Roberts <neil@linux.intel.com>
11  *
12  * Copyright (C) 2008, 2009, 2010, 2011  Intel Corporation.
13  *
14  * This library is free software; you can redistribute it and/or
15  * modify it under the terms of the GNU Lesser General Public
16  * License as published by the Free Software Foundation; either
17  * version 2 of the License, or (at your option) any later version.
18  *
19  * This library is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
22  * Lesser General Public License for more details.
23  *
24  * You should have received a copy of the GNU Lesser General Public
25  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
26  */
27 
28 /**
29  * SECTION:clutter-cairo-texture
30  * @short_description: Texture with Cairo integration
31  *
32  * #ClutterCairoTexture is a #ClutterTexture that displays the contents
33  * of a Cairo context. The #ClutterCairoTexture actor will create a
34  * Cairo image surface which will then be uploaded to a GL texture when
35  * needed.
36  *
37  * Since #ClutterCairoTexture uses a Cairo image surface
38  * internally all the drawing operations will be performed in
39  * software and not using hardware acceleration. This can lead to
40  * performance degradation if the contents of the texture change
41  * frequently.
42  *
43  * In order to use a #ClutterCairoTexture you should connect to the
44  * #ClutterCairoTexture::draw signal; the signal is emitted each time
45  * the #ClutterCairoTexture has been told to invalidate its contents,
46  * by using clutter_cairo_texture_invalidate_rectangle() or its
47  * sister function, clutter_cairo_texture_invalidate().
48  *
49  * Each callback to the #ClutterCairoTexture::draw signal will receive
50  * a #cairo_t context which can be used for drawing; the Cairo context
51  * is owned by the #ClutterCairoTexture and should not be destroyed
52  * explicitly.
53  *
54  * #ClutterCairoTexture is available since Clutter 1.0.
55  *
56  * #ClutterCairoTexture is deprecated since Clutter 1.12. You should
57  * use #ClutterCanvas instead.
58  */
59 
60 #ifdef HAVE_CONFIG_H
61 #include "config.h"
62 #endif
63 
64 #include <math.h>
65 
66 #include <cairo-gobject.h>
67 
68 #define CLUTTER_DISABLE_DEPRECATION_WARNINGS
69 #include "deprecated/clutter-texture.h"
70 #include "deprecated/clutter-cairo-texture.h"
71 
72 #include "clutter-cairo-texture.h"
73 
74 #include "clutter-actor-private.h"
75 #include "clutter-cairo.h"
76 #include "clutter-color.h"
77 #include "clutter-debug.h"
78 #include "clutter-marshal.h"
79 #include "clutter-private.h"
80 
81 struct _ClutterCairoTexturePrivate
82 {
83   cairo_surface_t *cr_surface;
84 
85   guint surface_width;
86   guint surface_height;
87 
88   cairo_t *cr_context;
89 
90   guint auto_resize : 1;
91 };
92 
93 enum
94 {
95   PROP_0,
96 
97   PROP_SURFACE_WIDTH,
98   PROP_SURFACE_HEIGHT,
99   PROP_AUTO_RESIZE,
100 
101   PROP_LAST
102 };
103 
104 enum
105 {
106   CREATE_SURFACE,
107   DRAW,
108 
109   LAST_SIGNAL
110 };
111 
112 static GParamSpec *obj_props[PROP_LAST] = { NULL, };
113 
114 static guint cairo_signals[LAST_SIGNAL] = { 0, };
115 
116 G_DEFINE_TYPE_WITH_PRIVATE (ClutterCairoTexture,
117                             clutter_cairo_texture,
118                             CLUTTER_TYPE_TEXTURE)
119 
120 #ifdef CLUTTER_ENABLE_DEBUG
121 #define clutter_warn_if_paint_fail(obj)                 G_STMT_START {  \
122   if (CLUTTER_ACTOR_IN_PAINT (obj)) {                                   \
123     g_warning ("%s should not be called during the paint sequence "     \
124                "of a ClutterCairoTexture as it will likely cause "      \
125                "performance issues.", G_STRFUNC);                       \
126   }                                                     } G_STMT_END
127 #else
128 #define clutter_warn_if_paint_fail(obj)         /* void */
129 #endif /* CLUTTER_ENABLE_DEBUG */
130 
131 typedef struct {
132   ClutterCairoTexture *texture;
133 
134   cairo_rectangle_int_t rect;
135 
136   guint is_clipped : 1;
137 } DrawContext;
138 
139 static const cairo_user_data_key_t clutter_cairo_texture_context_key;
140 
141 static DrawContext *
draw_context_create(ClutterCairoTexture * texture)142 draw_context_create (ClutterCairoTexture *texture)
143 {
144   DrawContext *context = g_slice_new0 (DrawContext);
145 
146   context->texture = g_object_ref (texture);
147 
148   return context;
149 }
150 
151 static void
draw_context_destroy(gpointer data)152 draw_context_destroy (gpointer data)
153 {
154   if (G_LIKELY (data != NULL))
155     {
156       DrawContext *context = data;
157 
158       g_object_unref (context->texture);
159 
160       g_slice_free (DrawContext, data);
161     }
162 }
163 
164 static void
clutter_cairo_texture_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)165 clutter_cairo_texture_set_property (GObject      *object,
166                                     guint         prop_id,
167                                     const GValue *value,
168                                     GParamSpec   *pspec)
169 {
170   ClutterCairoTexturePrivate *priv;
171 
172   priv = CLUTTER_CAIRO_TEXTURE (object)->priv;
173 
174   switch (prop_id)
175     {
176     case PROP_SURFACE_WIDTH:
177       /* we perform the resize on notify to coalesce separate
178        * surface-width/surface-height property set
179        */
180       priv->surface_width = g_value_get_uint (value);
181       break;
182 
183     case PROP_SURFACE_HEIGHT:
184       priv->surface_height = g_value_get_uint (value);
185       break;
186 
187     case PROP_AUTO_RESIZE:
188       clutter_cairo_texture_set_auto_resize (CLUTTER_CAIRO_TEXTURE (object),
189                                              g_value_get_boolean (value));
190       break;
191 
192     default:
193       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
194       break;
195   }
196 }
197 
198 static void
clutter_cairo_texture_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)199 clutter_cairo_texture_get_property (GObject    *object,
200                                     guint       prop_id,
201                                     GValue     *value,
202                                     GParamSpec *pspec)
203 {
204   ClutterCairoTexturePrivate *priv;
205 
206   priv = CLUTTER_CAIRO_TEXTURE (object)->priv;
207 
208   switch (prop_id)
209     {
210     case PROP_SURFACE_WIDTH:
211       g_value_set_uint (value, priv->surface_width);
212       break;
213 
214     case PROP_SURFACE_HEIGHT:
215       g_value_set_uint (value, priv->surface_height);
216       break;
217 
218     case PROP_AUTO_RESIZE:
219       g_value_set_boolean (value, priv->auto_resize);
220       break;
221 
222     default:
223       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
224       break;
225     }
226 }
227 
228 static void
clutter_cairo_texture_finalize(GObject * object)229 clutter_cairo_texture_finalize (GObject *object)
230 {
231   ClutterCairoTexturePrivate *priv = CLUTTER_CAIRO_TEXTURE (object)->priv;
232 
233   if (priv->cr_surface != NULL)
234     {
235       cairo_surface_t *surface = priv->cr_surface;
236 
237       priv->cr_surface = NULL;
238 
239       cairo_surface_finish (surface);
240       cairo_surface_destroy (surface);
241     }
242 
243   G_OBJECT_CLASS (clutter_cairo_texture_parent_class)->finalize (object);
244 }
245 
246 static cairo_surface_t *
get_surface(ClutterCairoTexture * self)247 get_surface (ClutterCairoTexture *self)
248 {
249   ClutterCairoTexturePrivate *priv = self->priv;
250 
251   if (priv->cr_surface == NULL)
252     {
253       g_signal_emit (self, cairo_signals[CREATE_SURFACE], 0,
254                      priv->surface_width,
255                      priv->surface_height,
256                      &priv->cr_surface);
257     }
258 
259   return priv->cr_surface;
260 }
261 
262 static void
clutter_cairo_texture_context_destroy(void * data)263 clutter_cairo_texture_context_destroy (void *data)
264 {
265   DrawContext *ctxt = data;
266   ClutterCairoTexture *cairo = ctxt->texture;
267   ClutterCairoTexturePrivate *priv = cairo->priv;
268   guint8 *cairo_data;
269   gint cairo_width, cairo_height, cairo_stride;
270   gint surface_width, surface_height;
271   CoglHandle cogl_texture;
272 
273   if (priv->cr_surface == NULL)
274     {
275       /* the surface went away before we could use it */
276       draw_context_destroy (ctxt);
277       return;
278     }
279 
280   /* for any other surface type, we presume that there exists a native
281    * communication between Cairo and GL that is triggered by cairo_destroy().
282    *
283    * for instance, cairo-drm will flush the outstanding modifications to the
284    * surface upon context destruction and so the texture is automatically
285    * updated.
286    */
287   if (cairo_surface_get_type (priv->cr_surface) != CAIRO_SURFACE_TYPE_IMAGE)
288     goto out;
289 
290   surface_width  = cairo_image_surface_get_width (priv->cr_surface);
291   surface_height = cairo_image_surface_get_height (priv->cr_surface);
292 
293   cairo_width  = MIN (ctxt->rect.width, surface_width);
294   cairo_height = MIN (ctxt->rect.height, surface_height);
295 
296   cogl_texture = clutter_texture_get_cogl_texture (CLUTTER_TEXTURE (cairo));
297   if (cairo_width == 0 ||
298       cairo_height == 0 ||
299       cogl_texture == COGL_INVALID_HANDLE)
300     {
301       draw_context_destroy (ctxt);
302       return;
303     }
304 
305   cairo_stride = cairo_image_surface_get_stride (priv->cr_surface);
306   cairo_data = cairo_image_surface_get_data (priv->cr_surface);
307   cairo_data += cairo_stride * ctxt->rect.y;
308   cairo_data += 4 * ctxt->rect.x;
309 
310   cogl_texture_set_region (cogl_texture,
311                            0, 0,
312                            ctxt->rect.x, ctxt->rect.y,
313                            cairo_width, cairo_height,
314                            cairo_width, cairo_height,
315                            CLUTTER_CAIRO_FORMAT_ARGB32,
316                            cairo_stride,
317                            cairo_data);
318 
319 out:
320   draw_context_destroy (ctxt);
321   clutter_actor_queue_redraw (CLUTTER_ACTOR (cairo));
322 }
323 
324 static inline void
clutter_cairo_texture_emit_draw(ClutterCairoTexture * self,DrawContext * ctxt)325 clutter_cairo_texture_emit_draw (ClutterCairoTexture        *self,
326                                  DrawContext *ctxt)
327 {
328   gboolean result;
329   cairo_t *cr;
330 
331   /* 0x0 surfaces don't need a ::draw */
332   if (self->priv->surface_width == 0 ||
333       self->priv->surface_height == 0)
334     return;
335 
336   /* if the size is !0 then we must have a surface */
337   g_assert (self->priv->cr_surface != NULL);
338 
339   cr = cairo_create (self->priv->cr_surface);
340 
341   if (ctxt->is_clipped)
342     {
343       cairo_rectangle (cr,
344                        ctxt->rect.x,
345                        ctxt->rect.y,
346                        ctxt->rect.width,
347                        ctxt->rect.height);
348       cairo_clip (cr);
349     }
350 
351   /* store the cairo_t as a guard */
352   self->priv->cr_context = cr;
353 
354   g_signal_emit (self, cairo_signals[DRAW], 0, cr, &result);
355 
356   self->priv->cr_context = NULL;
357 
358   clutter_cairo_texture_context_destroy (ctxt);
359 
360   cairo_destroy (cr);
361 }
362 
363 static inline void
clutter_cairo_texture_surface_resize_internal(ClutterCairoTexture * cairo)364 clutter_cairo_texture_surface_resize_internal (ClutterCairoTexture *cairo)
365 {
366   ClutterCairoTexturePrivate *priv = cairo->priv;
367 
368   if (priv->cr_surface != NULL)
369     {
370       cairo_surface_t *surface = priv->cr_surface;
371 
372       /* if the surface is an image one, and the size is already the
373        * same, then we don't need to do anything
374        */
375       if (cairo_surface_get_type (surface) != CAIRO_SURFACE_TYPE_IMAGE)
376         {
377           gint surface_width = cairo_image_surface_get_width (surface);
378           gint surface_height = cairo_image_surface_get_height (surface);
379 
380           if (priv->surface_width == surface_width &&
381               priv->surface_height == surface_height)
382             return;
383         }
384 
385       cairo_surface_finish (surface);
386       cairo_surface_destroy (surface);
387       priv->cr_surface = NULL;
388     }
389 
390   if (priv->surface_width == 0 ||
391       priv->surface_height == 0)
392     return;
393 
394   g_signal_emit (cairo, cairo_signals[CREATE_SURFACE], 0,
395                  priv->surface_width,
396                  priv->surface_height,
397                  &priv->cr_surface);
398 }
399 
400 static void
clutter_cairo_texture_notify(GObject * object,GParamSpec * pspec)401 clutter_cairo_texture_notify (GObject    *object,
402                               GParamSpec *pspec)
403 {
404   /* When the surface width or height changes then resize the cairo
405      surface. This is done here instead of directly in set_property so
406      that if both the width and height properties are set using a
407      single call to g_object_set then the surface will only be resized
408      once because the notifications will be frozen in between */
409 
410   if (obj_props[PROP_SURFACE_WIDTH]->name == pspec->name ||
411       obj_props[PROP_SURFACE_HEIGHT]->name == pspec->name)
412     {
413       ClutterCairoTexture *cairo = CLUTTER_CAIRO_TEXTURE (object);
414 
415       clutter_cairo_texture_surface_resize_internal (cairo);
416     }
417 
418   if (G_OBJECT_CLASS (clutter_cairo_texture_parent_class)->notify)
419     G_OBJECT_CLASS (clutter_cairo_texture_parent_class)->notify (object, pspec);
420 }
421 
422 static void
clutter_cairo_texture_get_preferred_width(ClutterActor * actor,gfloat for_height,gfloat * min_width,gfloat * natural_width)423 clutter_cairo_texture_get_preferred_width (ClutterActor *actor,
424                                            gfloat        for_height,
425                                            gfloat       *min_width,
426                                            gfloat       *natural_width)
427 {
428   ClutterCairoTexturePrivate *priv = CLUTTER_CAIRO_TEXTURE (actor)->priv;
429 
430   if (min_width)
431     *min_width = 0;
432 
433   if (natural_width)
434     *natural_width = (gfloat) priv->surface_width;
435 }
436 
437 static void
clutter_cairo_texture_get_preferred_height(ClutterActor * actor,gfloat for_width,gfloat * min_height,gfloat * natural_height)438 clutter_cairo_texture_get_preferred_height (ClutterActor *actor,
439                                             gfloat        for_width,
440                                             gfloat       *min_height,
441                                             gfloat       *natural_height)
442 {
443   ClutterCairoTexturePrivate *priv = CLUTTER_CAIRO_TEXTURE (actor)->priv;
444 
445   if (min_height)
446     *min_height = 0;
447 
448   if (natural_height)
449     *natural_height = (gfloat) priv->surface_height;
450 }
451 
452 static void
clutter_cairo_texture_allocate(ClutterActor * self,const ClutterActorBox * allocation,ClutterAllocationFlags flags)453 clutter_cairo_texture_allocate (ClutterActor           *self,
454                                 const ClutterActorBox  *allocation,
455                                 ClutterAllocationFlags  flags)
456 {
457   ClutterCairoTexturePrivate *priv = CLUTTER_CAIRO_TEXTURE (self)->priv;
458   ClutterActorClass *parent_class;
459 
460   parent_class = CLUTTER_ACTOR_CLASS (clutter_cairo_texture_parent_class);
461   parent_class->allocate (self, allocation, flags);
462 
463   if (priv->auto_resize)
464     {
465       ClutterCairoTexture *texture = CLUTTER_CAIRO_TEXTURE (self);
466       gfloat width, height;
467 
468       clutter_actor_box_get_size (allocation, &width, &height);
469 
470       priv->surface_width = ceilf (width);
471       priv->surface_height = ceilf (height);
472 
473       clutter_cairo_texture_surface_resize_internal (texture);
474       clutter_cairo_texture_invalidate (texture);
475     }
476 }
477 
478 static gboolean
clutter_cairo_texture_get_paint_volume(ClutterActor * self,ClutterPaintVolume * volume)479 clutter_cairo_texture_get_paint_volume (ClutterActor       *self,
480                                         ClutterPaintVolume *volume)
481 {
482   return _clutter_actor_set_default_paint_volume (self,
483                                                   CLUTTER_TYPE_CAIRO_TEXTURE,
484                                                   volume);
485 }
486 
487 static cairo_surface_t *
clutter_cairo_texture_create_surface(ClutterCairoTexture * self,guint width,guint height)488 clutter_cairo_texture_create_surface (ClutterCairoTexture *self,
489                                       guint                width,
490                                       guint                height)
491 {
492   cairo_surface_t *surface;
493   guint cairo_stride;
494   guint8 *cairo_data;
495   CoglHandle cogl_texture;
496 
497   surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
498                                         width,
499                                         height);
500 
501   cairo_stride = cairo_image_surface_get_stride (surface);
502   cairo_data = cairo_image_surface_get_data (surface);
503 
504   /* create a backing Cogl texture */
505   cogl_texture = cogl_texture_new_from_data (width, height,
506                                              COGL_TEXTURE_NONE,
507                                              CLUTTER_CAIRO_FORMAT_ARGB32,
508                                              COGL_PIXEL_FORMAT_ANY,
509                                              cairo_stride,
510                                              cairo_data);
511   clutter_texture_set_cogl_texture (CLUTTER_TEXTURE (self), cogl_texture);
512   cogl_handle_unref (cogl_texture);
513 
514   return surface;
515 }
516 
517 static gboolean
create_surface_accum(GSignalInvocationHint * ihint,GValue * return_accu,const GValue * handler_return,gpointer data)518 create_surface_accum (GSignalInvocationHint *ihint,
519                       GValue                *return_accu,
520                       const GValue          *handler_return,
521                       gpointer               data)
522 {
523   g_value_copy (handler_return, return_accu);
524 
525   /* stop on the first non-NULL return value */
526   return g_value_get_boxed (handler_return) == NULL;
527 }
528 
529 static void
clutter_cairo_texture_draw_marshaller(GClosure * closure,GValue * return_value,guint n_param_values,const GValue * param_values,gpointer invocation_hint,gpointer marshal_data)530 clutter_cairo_texture_draw_marshaller (GClosure     *closure,
531                                        GValue       *return_value,
532                                        guint         n_param_values,
533                                        const GValue *param_values,
534                                        gpointer      invocation_hint,
535                                        gpointer      marshal_data)
536 {
537   cairo_t *cr = g_value_get_boxed (&param_values[1]);
538 
539   cairo_save (cr);
540 
541   _clutter_marshal_BOOLEAN__BOXED (closure,
542                                    return_value,
543                                    n_param_values,
544                                    param_values,
545                                    invocation_hint,
546                                    marshal_data);
547 
548   cairo_restore (cr);
549 }
550 
551 static void
clutter_cairo_texture_class_init(ClutterCairoTextureClass * klass)552 clutter_cairo_texture_class_init (ClutterCairoTextureClass *klass)
553 {
554   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
555   ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
556 
557   gobject_class->finalize     = clutter_cairo_texture_finalize;
558   gobject_class->set_property = clutter_cairo_texture_set_property;
559   gobject_class->get_property = clutter_cairo_texture_get_property;
560   gobject_class->notify       = clutter_cairo_texture_notify;
561 
562   actor_class->get_paint_volume =
563     clutter_cairo_texture_get_paint_volume;
564   actor_class->get_preferred_width =
565     clutter_cairo_texture_get_preferred_width;
566   actor_class->get_preferred_height =
567     clutter_cairo_texture_get_preferred_height;
568   actor_class->allocate =
569     clutter_cairo_texture_allocate;
570 
571   klass->create_surface = clutter_cairo_texture_create_surface;
572 
573   /**
574    * ClutterCairoTexture:surface-width:
575    *
576    * The width of the Cairo surface used by the #ClutterCairoTexture
577    * actor, in pixels.
578    *
579    * Since: 1.0
580    *
581    * Deprecated: 1.12
582    */
583   obj_props[PROP_SURFACE_WIDTH] =
584     g_param_spec_uint ("surface-width",
585                        P_("Surface Width"),
586                        P_("The width of the Cairo surface"),
587                        0, G_MAXUINT,
588                        0,
589                        CLUTTER_PARAM_READWRITE |
590                        G_PARAM_DEPRECATED);
591   /**
592    * ClutterCairoTexture:surface-height:
593    *
594    * The height of the Cairo surface used by the #ClutterCairoTexture
595    * actor, in pixels.
596    *
597    * Since: 1.0
598    *
599    * Deprecated: 1.12
600    */
601   obj_props[PROP_SURFACE_HEIGHT] =
602     g_param_spec_uint ("surface-height",
603                        P_("Surface Height"),
604                        P_("The height of the Cairo surface"),
605                        0, G_MAXUINT,
606                        0,
607                        CLUTTER_PARAM_READWRITE |
608                        G_PARAM_DEPRECATED);
609 
610   /**
611    * ClutterCairoTexture:auto-resize:
612    *
613    * Controls whether the #ClutterCairoTexture should automatically
614    * resize the Cairo surface whenever the actor's allocation changes.
615    * If :auto-resize is set to %TRUE the surface contents will also
616    * be invalidated automatically.
617    *
618    * Since: 1.8
619    *
620    * Deprecated: 1.12
621    */
622   obj_props[PROP_AUTO_RESIZE] =
623     g_param_spec_boolean ("auto-resize",
624                           P_("Auto Resize"),
625                           P_("Whether the surface should match the allocation"),
626                           FALSE,
627                           CLUTTER_PARAM_READWRITE |
628                           G_PARAM_DEPRECATED);
629 
630   g_object_class_install_properties (gobject_class, PROP_LAST, obj_props);
631 
632   /**
633    * ClutterCairoTexture::create-surface:
634    * @texture: the #ClutterCairoTexture that emitted the signal
635    * @width: the width of the surface to create
636    * @height: the height of the surface to create
637    *
638    * The ::create-surface signal is emitted when a #ClutterCairoTexture
639    * news its surface (re)created, which happens either when the Cairo
640    * context is created with clutter_cairo_texture_create() or
641    * clutter_cairo_texture_create_region(), or when the surface is resized
642    * through clutter_cairo_texture_set_surface_size().
643    *
644    * The first signal handler that returns a non-%NULL, valid surface will
645    * stop any further signal emission, and the returned surface will be
646    * the one used.
647    *
648    * Return value: the newly created #cairo_surface_t for the texture
649    *
650    * Since: 1.6
651    *
652    * Deprecated: 1.12
653    */
654   cairo_signals[CREATE_SURFACE] =
655     g_signal_new (I_("create-surface"),
656                   G_TYPE_FROM_CLASS (klass),
657                   G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE,
658                   G_STRUCT_OFFSET (ClutterCairoTextureClass, create_surface),
659                   create_surface_accum, NULL,
660                   _clutter_marshal_BOXED__UINT_UINT,
661                   CAIRO_GOBJECT_TYPE_SURFACE, 2,
662                   G_TYPE_UINT,
663                   G_TYPE_UINT);
664 
665   /**
666    * ClutterCairoTexture::draw:
667    * @texture: the #ClutterCairoTexture that emitted the signal
668    * @cr: the Cairo context to use to draw
669    *
670    * The ::draw signal is emitted each time a #ClutterCairoTexture has
671    * been invalidated.
672    *
673    * The passed Cairo context passed will be clipped to the invalidated
674    * area.
675    *
676    * It is safe to connect multiple callbacks to this signals; the state
677    * of the Cairo context passed to each callback is automatically saved
678    * and restored, so it's not necessary to call cairo_save() and
679    * cairo_restore().
680    *
681    * Return value: %TRUE if the signal emission should stop, and %FALSE
682    *   to continue
683    *
684    * Since: 1.8
685    *
686    * Deprecated: 1.12
687    */
688   cairo_signals[DRAW] =
689     g_signal_new (I_("draw"),
690                   G_TYPE_FROM_CLASS (klass),
691                   G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE,
692                   G_STRUCT_OFFSET (ClutterCairoTextureClass, draw),
693                   _clutter_boolean_handled_accumulator, NULL,
694                   clutter_cairo_texture_draw_marshaller,
695                   G_TYPE_BOOLEAN, 1,
696                   CAIRO_GOBJECT_TYPE_CONTEXT);
697 }
698 
699 static void
clutter_cairo_texture_init(ClutterCairoTexture * self)700 clutter_cairo_texture_init (ClutterCairoTexture *self)
701 {
702   self->priv = clutter_cairo_texture_get_instance_private (self);
703 
704   /* FIXME - we are hardcoding the format; it would be good to have
705    * a :surface-format construct-only property for creating
706    * textures with a different format and have the cairo surface
707    * match that format
708    *
709    * priv->format = CAIRO_FORMAT_ARGB32;
710    */
711 
712   /* the Cairo surface is responsible for driving the size of
713    * the texture; if we let sync_size to its default of TRUE,
714    * the Texture will try to queue a relayout every time we
715    * change the size of the Cairo surface - which is not what
716    * we want
717    */
718   clutter_texture_set_sync_size (CLUTTER_TEXTURE (self), FALSE);
719 }
720 
721 /**
722  * clutter_cairo_texture_new:
723  * @width: the width of the surface
724  * @height: the height of the surface
725  *
726  * Creates a new #ClutterCairoTexture actor, with a surface of @width by
727  * @height pixels.
728  *
729  * Return value: the newly created #ClutterCairoTexture actor
730  *
731  * Since: 1.0
732  *
733  * Deprecated: 1.12: Use #ClutterCanvas instead
734  */
735 ClutterActor*
clutter_cairo_texture_new(guint width,guint height)736 clutter_cairo_texture_new (guint width,
737                            guint height)
738 {
739   return g_object_new (CLUTTER_TYPE_CAIRO_TEXTURE,
740                        "surface-width", width,
741                        "surface-height", height,
742                        NULL);
743 }
744 
745 static void
intersect_rectangles(cairo_rectangle_int_t * a,cairo_rectangle_int_t * b,cairo_rectangle_int_t * inter)746 intersect_rectangles (cairo_rectangle_int_t *a,
747 		      cairo_rectangle_int_t *b,
748 		      cairo_rectangle_int_t *inter)
749 {
750   gint dest_x, dest_y;
751   gint dest_width, dest_height;
752 
753   dest_x = MAX (a->x, b->x);
754   dest_y = MAX (a->y, b->y);
755   dest_width = MIN (a->x + a->width, b->x + b->width) - dest_x;
756   dest_height = MIN (a->y + a->height, b->y + b->height) - dest_y;
757 
758   if (dest_width > 0 && dest_height > 0)
759     {
760       inter->x = dest_x;
761       inter->y = dest_y;
762       inter->width = dest_width;
763       inter->height = dest_height;
764     }
765   else
766     {
767       inter->x = 0;
768       inter->y = 0;
769       inter->width = 0;
770       inter->height = 0;
771     }
772 }
773 
774 static cairo_t *
clutter_cairo_texture_create_region_internal(ClutterCairoTexture * self,gint x_offset,gint y_offset,gint width,gint height)775 clutter_cairo_texture_create_region_internal (ClutterCairoTexture *self,
776                                               gint                 x_offset,
777                                               gint                 y_offset,
778                                               gint                 width,
779                                               gint                 height)
780 {
781   ClutterCairoTexturePrivate *priv = self->priv;
782   cairo_rectangle_int_t region, area, inter;
783   cairo_surface_t *surface;
784   DrawContext *ctxt;
785   cairo_t *cr;
786 
787   if (width < 0)
788     width = priv->surface_width;
789 
790   if (height < 0)
791     height = priv->surface_height;
792 
793   if (width == 0 || height == 0)
794     {
795       g_warning ("Unable to create a context for an image surface of "
796                  "width %d and height %d. Set the surface size to be "
797                  "at least 1 pixel by 1 pixel.",
798                  width, height);
799       return NULL;
800     }
801 
802   surface = get_surface (self);
803 
804   ctxt = draw_context_create (self);
805 
806   region.x = x_offset;
807   region.y = y_offset;
808   region.width = width;
809   region.height = height;
810 
811   area.x = 0;
812   area.y = 0;
813   area.width = priv->surface_width;
814   area.height = priv->surface_height;
815 
816   /* Limit the region to the visible rectangle */
817   intersect_rectangles (&area, &region, &inter);
818 
819   ctxt->rect = inter;
820 
821   cr = cairo_create (surface);
822   cairo_set_user_data (cr, &clutter_cairo_texture_context_key,
823 		       ctxt,
824                        clutter_cairo_texture_context_destroy);
825 
826   return cr;
827 }
828 
829 /**
830  * clutter_cairo_texture_create_region:
831  * @self: a #ClutterCairoTexture
832  * @x_offset: offset of the region on the X axis
833  * @y_offset: offset of the region on the Y axis
834  * @width: width of the region, or -1 for the full surface width
835  * @height: height of the region, or -1 for the full surface height
836  *
837  * Creates a new Cairo context that will updat the region defined
838  * by @x_offset, @y_offset, @width and @height.
839  *
840  * Do not call this function within the paint virtual
841  * function or from a callback to the #ClutterActor::paint
842  * signal.
843  *
844  * Return value: a newly created Cairo context. Use cairo_destroy()
845  *   to upload the contents of the context when done drawing
846  *
847  * Since: 1.0
848  *
849  * Deprecated: 1.8: Use the #ClutterCairoTexture::draw signal and
850  *   clutter_cairo_texture_invalidate_rectangle() to obtain a
851  *   clipped Cairo context for 2D drawing.
852  */
853 cairo_t *
clutter_cairo_texture_create_region(ClutterCairoTexture * self,gint x_offset,gint y_offset,gint width,gint height)854 clutter_cairo_texture_create_region (ClutterCairoTexture *self,
855                                      gint                 x_offset,
856                                      gint                 y_offset,
857                                      gint                 width,
858                                      gint                 height)
859 {
860   g_return_val_if_fail (CLUTTER_IS_CAIRO_TEXTURE (self), NULL);
861 
862   clutter_warn_if_paint_fail (self);
863 
864   return clutter_cairo_texture_create_region_internal (self,
865                                                        x_offset, y_offset,
866                                                        width, height);
867 }
868 
869 /**
870  * clutter_cairo_texture_invalidate_rectangle:
871  * @self: a #ClutterCairoTexture
872  * @rect: (allow-none): a rectangle with the area to invalida,
873  *   or %NULL to perform an unbounded invalidation
874  *
875  * Invalidates a rectangular region of a #ClutterCairoTexture.
876  *
877  * The invalidation will cause the #ClutterCairoTexture::draw signal
878  * to be emitted.
879  *
880  * See also: clutter_cairo_texture_invalidate()
881  *
882  * Since: 1.8
883  * Deprecated: 1.12: Use #ClutterCanvas instead
884  */
885 void
clutter_cairo_texture_invalidate_rectangle(ClutterCairoTexture * self,cairo_rectangle_int_t * rect)886 clutter_cairo_texture_invalidate_rectangle (ClutterCairoTexture   *self,
887                                             cairo_rectangle_int_t *rect)
888 {
889   DrawContext *ctxt = NULL;
890 
891   g_return_if_fail (CLUTTER_IS_CAIRO_TEXTURE (self));
892 
893   if (self->priv->cr_context != NULL)
894     {
895       g_warning ("It is not possible to invalidate a Cairo texture"
896                  "while drawing into it.");
897       return;
898     }
899 
900   ctxt = draw_context_create (self);
901 
902   if (rect != NULL)
903     {
904       cairo_rectangle_int_t area, inter;
905 
906       area.x = 0;
907       area.y = 0;
908       area.width = self->priv->surface_width;
909       area.height = self->priv->surface_height;
910 
911       /* Limit the region to the visible rectangle */
912       intersect_rectangles (&area, rect, &inter);
913 
914       ctxt->is_clipped = TRUE;
915       ctxt->rect = inter;
916     }
917   else
918     {
919       ctxt->is_clipped = FALSE;
920       ctxt->rect.x = ctxt->rect.y = 0;
921       ctxt->rect.width = self->priv->surface_width;
922       ctxt->rect.height = self->priv->surface_height;
923     }
924 
925   /* XXX - it might be good to move the emission inside the paint cycle
926    * using a repaint function, to avoid blocking inside this function
927    */
928   clutter_cairo_texture_emit_draw (self, ctxt);
929 }
930 
931 /**
932  * clutter_cairo_texture_invalidate:
933  * @self: a #ClutterCairoTexture
934  *
935  * Invalidates the whole surface of a #ClutterCairoTexture.
936  *
937  * This function will cause the #ClutterCairoTexture::draw signal
938  * to be emitted.
939  *
940  * See also: clutter_cairo_texture_invalidate_rectangle()
941  *
942  * Since: 1.8
943  * Deprecated: 1.12: Use #ClutterCanvas instead
944  */
945 void
clutter_cairo_texture_invalidate(ClutterCairoTexture * self)946 clutter_cairo_texture_invalidate (ClutterCairoTexture *self)
947 {
948   g_return_if_fail (CLUTTER_IS_CAIRO_TEXTURE (self));
949 
950   clutter_cairo_texture_invalidate_rectangle (self, NULL);
951 }
952 
953 /**
954  * clutter_cairo_texture_create:
955  * @self: a #ClutterCairoTexture
956  *
957  * Creates a new Cairo context for the @cairo texture. It is
958  * similar to using clutter_cairo_texture_create_region() with @x_offset
959  * and @y_offset of 0, @width equal to the @cairo texture surface width
960  * and @height equal to the @cairo texture surface height.
961  *
962  * Do not call this function within the paint virtual
963  * function or from a callback to the #ClutterActor::paint
964  * signal.
965  *
966  * Return value: a newly created Cairo context. Use cairo_destroy()
967  *   to upload the contents of the context when done drawing
968  *
969  * Since: 1.0
970  *
971  * Deprecated: 1.8: Use the #ClutterCairoTexture::draw signal and
972  *   the clutter_cairo_texture_invalidate() function to obtain a
973  *   Cairo context for 2D drawing.
974  */
975 cairo_t *
clutter_cairo_texture_create(ClutterCairoTexture * self)976 clutter_cairo_texture_create (ClutterCairoTexture *self)
977 {
978   g_return_val_if_fail (CLUTTER_IS_CAIRO_TEXTURE (self), NULL);
979 
980   clutter_warn_if_paint_fail (self);
981 
982   return clutter_cairo_texture_create_region_internal (self, 0, 0, -1, -1);
983 }
984 
985 /**
986  * clutter_cairo_texture_set_surface_size:
987  * @self: a #ClutterCairoTexture
988  * @width: the new width of the surface
989  * @height: the new height of the surface
990  *
991  * Resizes the Cairo surface used by @self to @width and @height.
992  *
993  * This function will not invalidate the contents of the Cairo
994  * texture: you will have to explicitly call either
995  * clutter_cairo_texture_invalidate_rectangle() or
996  * clutter_cairo_texture_invalidate().
997  *
998  * Since: 1.0
999  * Deprecated: 1.12: Use #ClutterCanvas instead
1000  */
1001 void
clutter_cairo_texture_set_surface_size(ClutterCairoTexture * self,guint width,guint height)1002 clutter_cairo_texture_set_surface_size (ClutterCairoTexture *self,
1003                                         guint                width,
1004                                         guint                height)
1005 {
1006   ClutterCairoTexturePrivate *priv;
1007 
1008   g_return_if_fail (CLUTTER_IS_CAIRO_TEXTURE (self));
1009 
1010   priv = self->priv;
1011 
1012   if (width == priv->surface_width &&
1013       height == priv->surface_height)
1014     return;
1015 
1016   g_object_freeze_notify (G_OBJECT (self));
1017 
1018   if (priv->surface_width != width)
1019     {
1020       priv->surface_width = width;
1021       g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_SURFACE_WIDTH]);
1022     }
1023 
1024   if (priv->surface_height != height)
1025     {
1026       priv->surface_height = height;
1027       g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_SURFACE_HEIGHT]);
1028     }
1029 
1030   clutter_cairo_texture_surface_resize_internal (self);
1031 
1032   g_object_thaw_notify (G_OBJECT (self));
1033 }
1034 
1035 /**
1036  * clutter_cairo_texture_get_surface_size:
1037  * @self: a #ClutterCairoTexture
1038  * @width: (out): return location for the surface width, or %NULL
1039  * @height: (out): return location for the surface height, or %NULL
1040  *
1041  * Retrieves the surface width and height for @self.
1042  *
1043  * Since: 1.0
1044  * Deprecated: 1.12: Use #ClutterCanvas instead
1045  */
1046 void
clutter_cairo_texture_get_surface_size(ClutterCairoTexture * self,guint * width,guint * height)1047 clutter_cairo_texture_get_surface_size (ClutterCairoTexture *self,
1048                                         guint               *width,
1049                                         guint               *height)
1050 {
1051   g_return_if_fail (CLUTTER_IS_CAIRO_TEXTURE (self));
1052 
1053   if (width)
1054     *width = self->priv->surface_width;
1055 
1056   if (height)
1057     *height = self->priv->surface_height;
1058 }
1059 
1060 /**
1061  * clutter_cairo_texture_clear:
1062  * @self: a #ClutterCairoTexture
1063  *
1064  * Clears @self's internal drawing surface, so that the next upload
1065  * will replace the previous contents of the #ClutterCairoTexture
1066  * rather than adding to it.
1067  *
1068  * Calling this function from within a #ClutterCairoTexture::draw
1069  * signal handler will clear the invalidated area.
1070  *
1071  * Since: 1.0
1072  * Deprecated: 1.12: Use #ClutterCanvas instead
1073  */
1074 void
clutter_cairo_texture_clear(ClutterCairoTexture * self)1075 clutter_cairo_texture_clear (ClutterCairoTexture *self)
1076 {
1077   ClutterCairoTexturePrivate *priv;
1078   cairo_t *cr;
1079 
1080   g_return_if_fail (CLUTTER_IS_CAIRO_TEXTURE (self));
1081 
1082   priv = self->priv;
1083 
1084   /* if we got called outside of a ::draw signal handler
1085    * then we clear the whole surface by creating a temporary
1086    * cairo_t; otherwise, we clear the current cairo_t, which
1087    * will take into account the clip region.
1088    */
1089   if (priv->cr_context == NULL)
1090     {
1091       cairo_surface_t *surface;
1092 
1093       surface = get_surface (self);
1094 
1095       cr = cairo_create (surface);
1096     }
1097   else
1098     cr = priv->cr_context;
1099 
1100   cairo_save (cr);
1101 
1102   cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
1103   cairo_paint (cr);
1104 
1105   cairo_restore (cr);
1106 
1107   if (priv->cr_context == NULL)
1108     cairo_destroy (cr);
1109 }
1110 
1111 /**
1112  * clutter_cairo_texture_set_auto_resize:
1113  * @self: a #ClutterCairoTexture
1114  * @value: %TRUE if the #ClutterCairoTexture should bind the surface
1115  *   size to the allocation
1116  *
1117  * Sets whether the #ClutterCairoTexture should ensure that the
1118  * backing Cairo surface used matches the allocation assigned to
1119  * the actor. If the allocation changes, the contents of the
1120  * #ClutterCairoTexture will also be invalidated automatically.
1121  *
1122  * Since: 1.8
1123  * Deprecated: 1.12: Use #ClutterCanvas instead
1124  */
1125 void
clutter_cairo_texture_set_auto_resize(ClutterCairoTexture * self,gboolean value)1126 clutter_cairo_texture_set_auto_resize (ClutterCairoTexture *self,
1127                                        gboolean             value)
1128 {
1129   ClutterCairoTexturePrivate *priv;
1130 
1131   g_return_if_fail (CLUTTER_IS_CAIRO_TEXTURE (self));
1132 
1133   value = !!value;
1134 
1135   priv = self->priv;
1136 
1137   if (priv->auto_resize == value)
1138     return;
1139 
1140   priv->auto_resize = value;
1141 
1142   clutter_actor_queue_relayout (CLUTTER_ACTOR (self));
1143 
1144   g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_AUTO_RESIZE]);
1145 }
1146 
1147 /**
1148  * clutter_cairo_texture_get_auto_resize:
1149  * @self: a #ClutterCairoTexture
1150  *
1151  * Retrieves the value set using clutter_cairo_texture_set_auto_resize().
1152  *
1153  * Return value: %TRUE if the #ClutterCairoTexture should track the
1154  *   allocation, and %FALSE otherwise
1155  *
1156  * Since: 1.8
1157  * Deprecated: 1.12: Use #ClutterCanvas instead
1158  */
1159 gboolean
clutter_cairo_texture_get_auto_resize(ClutterCairoTexture * self)1160 clutter_cairo_texture_get_auto_resize (ClutterCairoTexture *self)
1161 {
1162   g_return_val_if_fail (CLUTTER_IS_CAIRO_TEXTURE (self), FALSE);
1163 
1164   return self->priv->auto_resize;
1165 }
1166