docs: fix unnecessary ampersand, < and > escaping in code blocks
[platform/upstream/gstreamer.git] / subprojects / gst-plugins-good / ext / cairo / gstcairooverlay.c
1 /* GStreamer
2  * Copyright (C) <2011> Jon Nordby <jononor@gmail.com>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19
20 /**
21  * SECTION:element-cairooverlay
22  * @title: cairooverlay
23  *
24  * cairooverlay renders an overlay using a application provided render function.
25  *
26  * The full example can be found in tests/examples/cairo/cairo_overlay.c
27  *
28  * ## Example code
29  * |[
30  *
31  * #include <gst/gst.h>
32  * #include <gst/video/video.h>
33  *
34  * ...
35  *
36  * typedef struct {
37  *   gboolean valid;
38  *   int width;
39  *   int height;
40  * } CairoOverlayState;
41  *
42  * ...
43  *
44  * static void
45  * prepare_overlay (GstElement * overlay, GstCaps * caps, gpointer user_data)
46  * {
47  *   CairoOverlayState *state = (CairoOverlayState *)user_data;
48  *
49  *   gst_video_format_parse_caps (caps, NULL, &state->width, &state->height);
50  *   state->valid = TRUE;
51  * }
52  *
53  * static void
54  * draw_overlay (GstElement * overlay, cairo_t * cr, guint64 timestamp,
55  *   guint64 duration, gpointer user_data)
56  * {
57  *   CairoOverlayState *s = (CairoOverlayState *)user_data;
58  *   double scale;
59  *
60  *   if (!s->valid)
61  *     return;
62  *
63  *   scale = 2*(((timestamp/(int)1e7) % 70)+30)/100.0;
64  *   cairo_translate(cr, s->width/2, (s->height/2)-30);
65  *   cairo_scale (cr, scale, scale);
66  *
67  *   cairo_move_to (cr, 0, 0);
68  *   cairo_curve_to (cr, 0,-30, -50,-30, -50,0);
69  *   cairo_curve_to (cr, -50,30, 0,35, 0,60 );
70  *   cairo_curve_to (cr, 0,35, 50,30, 50,0 ); *
71  *   cairo_curve_to (cr, 50,-30, 0,-30, 0,0 );
72  *   cairo_set_source_rgba (cr, 0.9, 0.0, 0.1, 0.7);
73  *   cairo_fill (cr);
74  * }
75  *
76  * ...
77  *
78  * cairo_overlay = gst_element_factory_make (&quot;cairooverlay&quot;, &quot;overlay&quot;);
79  *
80  * g_signal_connect (cairo_overlay, &quot;draw&quot;, G_CALLBACK (draw_overlay),
81  *   overlay_state);
82  * g_signal_connect (cairo_overlay, &quot;caps-changed&quot;,
83  *   G_CALLBACK (prepare_overlay), overlay_state);
84  * ...
85  *
86  * ]|
87  *
88  */
89
90 #ifdef HAVE_CONFIG_H
91 #include "config.h"
92 #endif
93
94 #include "gstcairooverlay.h"
95
96 #include <gst/video/video.h>
97
98 #include <cairo.h>
99
100 /* RGB16 is native-endianness in GStreamer */
101 #if G_BYTE_ORDER == G_LITTLE_ENDIAN
102 #define TEMPLATE_CAPS GST_VIDEO_CAPS_MAKE("{ BGRx, BGRA, RGB16 }")
103 #else
104 #define TEMPLATE_CAPS GST_VIDEO_CAPS_MAKE("{ xRGB, ARGB, RGB16 }")
105 #endif
106
107 GST_DEBUG_CATEGORY (cairo_debug);
108
109 static GstStaticPadTemplate gst_cairo_overlay_src_template =
110 GST_STATIC_PAD_TEMPLATE ("src",
111     GST_PAD_SRC,
112     GST_PAD_ALWAYS,
113     GST_STATIC_CAPS (TEMPLATE_CAPS)
114     );
115
116 static GstStaticPadTemplate gst_cairo_overlay_sink_template =
117 GST_STATIC_PAD_TEMPLATE ("sink",
118     GST_PAD_SINK,
119     GST_PAD_ALWAYS,
120     GST_STATIC_CAPS (TEMPLATE_CAPS)
121     );
122
123 G_DEFINE_TYPE (GstCairoOverlay, gst_cairo_overlay, GST_TYPE_BASE_TRANSFORM);
124 GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (cairooverlay, "cairooverlay",
125     GST_RANK_NONE, GST_TYPE_CAIRO_OVERLAY, GST_DEBUG_CATEGORY_INIT (cairo_debug,
126         "cairo", 0, "Cairo elements"););
127 enum
128 {
129   PROP_0,
130   PROP_DRAW_ON_TRANSPARENT_SURFACE,
131 };
132
133 #define DEFAULT_DRAW_ON_TRANSPARENT_SURFACE (FALSE)
134
135 enum
136 {
137   SIGNAL_DRAW,
138   SIGNAL_CAPS_CHANGED,
139   N_SIGNALS
140 };
141
142 static guint gst_cairo_overlay_signals[N_SIGNALS];
143
144 static void
145 gst_cairo_overlay_set_property (GObject * object, guint property_id,
146     const GValue * value, GParamSpec * pspec)
147 {
148   GstCairoOverlay *overlay = GST_CAIRO_OVERLAY (object);
149
150   GST_OBJECT_LOCK (overlay);
151
152   switch (property_id) {
153     case PROP_DRAW_ON_TRANSPARENT_SURFACE:
154       overlay->draw_on_transparent_surface = g_value_get_boolean (value);
155       break;
156     default:
157       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
158       break;
159   }
160
161   GST_OBJECT_UNLOCK (overlay);
162 }
163
164 static void
165 gst_cairo_overlay_get_property (GObject * object, guint property_id,
166     GValue * value, GParamSpec * pspec)
167 {
168   GstCairoOverlay *overlay = GST_CAIRO_OVERLAY (object);
169
170   GST_OBJECT_LOCK (overlay);
171
172   switch (property_id) {
173     case PROP_DRAW_ON_TRANSPARENT_SURFACE:
174       g_value_set_boolean (value, overlay->draw_on_transparent_surface);
175       break;
176     default:
177       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
178       break;
179   }
180
181   GST_OBJECT_UNLOCK (overlay);
182 }
183
184 static gboolean
185 gst_cairo_overlay_query (GstBaseTransform * trans, GstPadDirection direction,
186     GstQuery * query)
187 {
188   GstCairoOverlay *overlay = GST_CAIRO_OVERLAY (trans);
189
190   switch (GST_QUERY_TYPE (query)) {
191     case GST_QUERY_ALLOCATION:
192     {
193       /* We're always running in passthrough mode, which means that
194        * basetransform just passes through ALLOCATION queries and
195        * never ever calls BaseTransform::decide_allocation().
196        *
197        * We hook into the query handling for that reason
198        */
199       overlay->attach_compo_to_buffer = FALSE;
200
201       if (!GST_BASE_TRANSFORM_CLASS (gst_cairo_overlay_parent_class)->query
202           (trans, direction, query)) {
203         return FALSE;
204       }
205
206       overlay->attach_compo_to_buffer = gst_query_find_allocation_meta (query,
207           GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE, NULL);
208
209       return TRUE;
210     }
211     default:
212       return
213           GST_BASE_TRANSFORM_CLASS (gst_cairo_overlay_parent_class)->query
214           (trans, direction, query);
215   }
216 }
217
218 static gboolean
219 gst_cairo_overlay_set_caps (GstBaseTransform * trans, GstCaps * in_caps,
220     GstCaps * out_caps)
221 {
222   GstCairoOverlay *overlay = GST_CAIRO_OVERLAY (trans);
223
224   if (!gst_video_info_from_caps (&overlay->info, in_caps))
225     return FALSE;
226
227   g_signal_emit (overlay, gst_cairo_overlay_signals[SIGNAL_CAPS_CHANGED], 0,
228       in_caps, NULL);
229
230   return TRUE;
231 }
232
233 /* Copy from video-overlay-composition.c */
234 static void
235 gst_video_overlay_rectangle_premultiply_0 (GstVideoFrame * frame)
236 {
237   int i, j;
238   int width = GST_VIDEO_FRAME_WIDTH (frame);
239   int height = GST_VIDEO_FRAME_HEIGHT (frame);
240   int stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0);
241   guint8 *data = GST_VIDEO_FRAME_PLANE_DATA (frame, 0);
242
243   for (j = 0; j < height; ++j) {
244     guint8 *line;
245
246     line = data;
247     line += stride * j;
248     for (i = 0; i < width; ++i) {
249       int a = line[0];
250       line[1] = line[1] * a / 255;
251       line[2] = line[2] * a / 255;
252       line[3] = line[3] * a / 255;
253       line += 4;
254     }
255   }
256 }
257
258 /* Copy from video-overlay-composition.c */
259 static void
260 gst_video_overlay_rectangle_premultiply_3 (GstVideoFrame * frame)
261 {
262   int i, j;
263   int width = GST_VIDEO_FRAME_WIDTH (frame);
264   int height = GST_VIDEO_FRAME_HEIGHT (frame);
265   int stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0);
266   guint8 *data = GST_VIDEO_FRAME_PLANE_DATA (frame, 0);
267
268   for (j = 0; j < height; ++j) {
269     guint8 *line;
270
271     line = data;
272     line += stride * j;
273     for (i = 0; i < width; ++i) {
274       int a = line[3];
275       line[0] = line[0] * a / 255;
276       line[1] = line[1] * a / 255;
277       line[2] = line[2] * a / 255;
278       line += 4;
279     }
280   }
281 }
282
283 /* Copy from video-overlay-composition.c */
284 static void
285 gst_video_overlay_rectangle_premultiply (GstVideoFrame * frame)
286 {
287   gint alpha_offset;
288
289   alpha_offset = GST_VIDEO_FRAME_COMP_POFFSET (frame, 3);
290   switch (alpha_offset) {
291     case 0:
292       gst_video_overlay_rectangle_premultiply_0 (frame);
293       break;
294     case 3:
295       gst_video_overlay_rectangle_premultiply_3 (frame);
296       break;
297     default:
298       g_assert_not_reached ();
299       break;
300   }
301 }
302
303 /* Copy from video-overlay-composition.c */
304 static void
305 gst_video_overlay_rectangle_unpremultiply_0 (GstVideoFrame * frame)
306 {
307   int i, j;
308   int width = GST_VIDEO_FRAME_WIDTH (frame);
309   int height = GST_VIDEO_FRAME_HEIGHT (frame);
310   int stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0);
311   guint8 *data = GST_VIDEO_FRAME_PLANE_DATA (frame, 0);
312
313   for (j = 0; j < height; ++j) {
314     guint8 *line;
315
316     line = data;
317     line += stride * j;
318     for (i = 0; i < width; ++i) {
319       int a = line[0];
320       if (a) {
321         line[1] = MIN ((line[1] * 255 + a / 2) / a, 255);
322         line[2] = MIN ((line[2] * 255 + a / 2) / a, 255);
323         line[3] = MIN ((line[3] * 255 + a / 2) / a, 255);
324       }
325       line += 4;
326     }
327   }
328 }
329
330 /* Copy from video-overlay-composition.c */
331 static void
332 gst_video_overlay_rectangle_unpremultiply_3 (GstVideoFrame * frame)
333 {
334   int i, j;
335   int width = GST_VIDEO_FRAME_WIDTH (frame);
336   int height = GST_VIDEO_FRAME_HEIGHT (frame);
337   int stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0);
338   guint8 *data = GST_VIDEO_FRAME_PLANE_DATA (frame, 0);
339
340   for (j = 0; j < height; ++j) {
341     guint8 *line;
342
343     line = data;
344     line += stride * j;
345     for (i = 0; i < width; ++i) {
346       int a = line[3];
347       if (a) {
348         line[0] = MIN ((line[0] * 255 + a / 2) / a, 255);
349         line[1] = MIN ((line[1] * 255 + a / 2) / a, 255);
350         line[2] = MIN ((line[2] * 255 + a / 2) / a, 255);
351       }
352       line += 4;
353     }
354   }
355 }
356
357 /* Copy from video-overlay-composition.c */
358 static void
359 gst_video_overlay_rectangle_unpremultiply (GstVideoFrame * frame)
360 {
361   gint alpha_offset;
362
363   alpha_offset = GST_VIDEO_FRAME_COMP_POFFSET (frame, 3);
364   switch (alpha_offset) {
365     case 0:
366       gst_video_overlay_rectangle_unpremultiply_0 (frame);
367       break;
368     case 3:
369       gst_video_overlay_rectangle_unpremultiply_3 (frame);
370       break;
371     default:
372       g_assert_not_reached ();
373       break;
374   }
375 }
376
377 static GstFlowReturn
378 gst_cairo_overlay_transform_ip (GstBaseTransform * trans, GstBuffer * buf)
379 {
380   GstCairoOverlay *overlay = GST_CAIRO_OVERLAY (trans);
381   GstVideoFrame frame;
382   cairo_surface_t *surface;
383   cairo_t *cr;
384   cairo_format_t format;
385   gboolean draw_on_transparent_surface = overlay->draw_on_transparent_surface;
386
387   switch (GST_VIDEO_INFO_FORMAT (&overlay->info)) {
388     case GST_VIDEO_FORMAT_ARGB:
389     case GST_VIDEO_FORMAT_BGRA:
390       format = CAIRO_FORMAT_ARGB32;
391       break;
392     case GST_VIDEO_FORMAT_xRGB:
393     case GST_VIDEO_FORMAT_BGRx:
394       format = CAIRO_FORMAT_RGB24;
395       break;
396     case GST_VIDEO_FORMAT_RGB16:
397       format = CAIRO_FORMAT_RGB16_565;
398       break;
399     default:
400     {
401       GST_WARNING ("No matching cairo format for %s",
402           gst_video_format_to_string (GST_VIDEO_INFO_FORMAT (&overlay->info)));
403       return GST_FLOW_ERROR;
404     }
405   }
406
407   /* If we need to map the buffer writable, do so */
408   if (!draw_on_transparent_surface || !overlay->attach_compo_to_buffer) {
409     if (!gst_video_frame_map (&frame, &overlay->info, buf, GST_MAP_READWRITE)) {
410       return GST_FLOW_ERROR;
411     }
412   } else {
413     frame.buffer = NULL;
414   }
415
416   if (draw_on_transparent_surface) {
417     surface =
418         cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
419         GST_VIDEO_INFO_WIDTH (&overlay->info),
420         GST_VIDEO_INFO_HEIGHT (&overlay->info));
421   } else {
422     if (format == CAIRO_FORMAT_ARGB32)
423       gst_video_overlay_rectangle_premultiply (&frame);
424
425     surface =
426         cairo_image_surface_create_for_data (GST_VIDEO_FRAME_PLANE_DATA (&frame,
427             0), format, GST_VIDEO_FRAME_WIDTH (&frame),
428         GST_VIDEO_FRAME_HEIGHT (&frame), GST_VIDEO_FRAME_PLANE_STRIDE (&frame,
429             0));
430   }
431
432   if (G_UNLIKELY (!surface))
433     return GST_FLOW_ERROR;
434
435   cr = cairo_create (surface);
436   if (G_UNLIKELY (!cr)) {
437     cairo_surface_destroy (surface);
438     return GST_FLOW_ERROR;
439   }
440
441   g_signal_emit (overlay, gst_cairo_overlay_signals[SIGNAL_DRAW], 0,
442       cr, GST_BUFFER_PTS (buf), GST_BUFFER_DURATION (buf), NULL);
443
444   cairo_destroy (cr);
445
446   if (draw_on_transparent_surface) {
447     guint size;
448     GstBuffer *surface_buffer;
449     GstVideoOverlayRectangle *rect;
450     GstVideoOverlayComposition *composition;
451     gsize offset[GST_VIDEO_MAX_PLANES] = { 0, };
452     gint stride[GST_VIDEO_MAX_PLANES] = { 0, };
453
454     size =
455         cairo_image_surface_get_height (surface) *
456         cairo_image_surface_get_stride (surface);
457     stride[0] = cairo_image_surface_get_stride (surface);
458
459     /* Create a GstVideoOverlayComposition for blending, this handles
460      * pre-multiplied alpha correctly */
461     surface_buffer =
462         gst_buffer_new_wrapped_full (0, cairo_image_surface_get_data (surface),
463         size, 0, size, surface, (GDestroyNotify) cairo_surface_destroy);
464     gst_buffer_add_video_meta_full (surface_buffer, GST_VIDEO_FRAME_FLAG_NONE,
465         (G_BYTE_ORDER ==
466             G_LITTLE_ENDIAN ? GST_VIDEO_FORMAT_BGRA : GST_VIDEO_FORMAT_ARGB),
467         GST_VIDEO_INFO_WIDTH (&overlay->info),
468         GST_VIDEO_INFO_HEIGHT (&overlay->info), 1, offset, stride);
469     rect =
470         gst_video_overlay_rectangle_new_raw (surface_buffer, 0, 0,
471         GST_VIDEO_INFO_WIDTH (&overlay->info),
472         GST_VIDEO_INFO_HEIGHT (&overlay->info),
473         GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA);
474     gst_buffer_unref (surface_buffer);
475
476     if (overlay->attach_compo_to_buffer) {
477       GstVideoOverlayCompositionMeta *composition_meta;
478
479       composition_meta = gst_buffer_get_video_overlay_composition_meta (buf);
480       if (composition_meta) {
481         GstVideoOverlayComposition *merged_composition =
482             gst_video_overlay_composition_copy (composition_meta->overlay);
483         gst_video_overlay_composition_add_rectangle (merged_composition, rect);
484         gst_video_overlay_composition_unref (composition_meta->overlay);
485         composition_meta->overlay = merged_composition;
486         gst_video_overlay_rectangle_unref (rect);
487       } else {
488         composition = gst_video_overlay_composition_new (rect);
489         gst_video_overlay_rectangle_unref (rect);
490         gst_buffer_add_video_overlay_composition_meta (buf, composition);
491         gst_video_overlay_composition_unref (composition);
492       }
493     } else {
494       composition = gst_video_overlay_composition_new (rect);
495       gst_video_overlay_rectangle_unref (rect);
496       gst_video_overlay_composition_blend (composition, &frame);
497       gst_video_overlay_composition_unref (composition);
498     }
499   } else {
500     cairo_surface_destroy (surface);
501     if (format == CAIRO_FORMAT_ARGB32)
502       gst_video_overlay_rectangle_unpremultiply (&frame);
503   }
504
505   if (frame.buffer) {
506     gst_video_frame_unmap (&frame);
507   }
508
509   return GST_FLOW_OK;
510 }
511
512 static void
513 gst_cairo_overlay_class_init (GstCairoOverlayClass * klass)
514 {
515   GstBaseTransformClass *btrans_class;
516   GstElementClass *element_class;
517   GObjectClass *gobject_class;
518
519   btrans_class = (GstBaseTransformClass *) klass;
520   element_class = (GstElementClass *) klass;
521   gobject_class = (GObjectClass *) klass;
522
523   btrans_class->set_caps = gst_cairo_overlay_set_caps;
524   btrans_class->transform_ip = gst_cairo_overlay_transform_ip;
525   btrans_class->query = gst_cairo_overlay_query;
526
527   gobject_class->set_property = gst_cairo_overlay_set_property;
528   gobject_class->get_property = gst_cairo_overlay_get_property;
529
530   g_object_class_install_property (gobject_class,
531       PROP_DRAW_ON_TRANSPARENT_SURFACE,
532       g_param_spec_boolean ("draw-on-transparent-surface",
533           "Draw on transparent surface",
534           "Let the draw signal work on a transparent surface "
535           "and blend the results with the video at a later time",
536           DEFAULT_DRAW_ON_TRANSPARENT_SURFACE,
537           GST_PARAM_CONTROLLABLE | GST_PARAM_MUTABLE_PLAYING | G_PARAM_READWRITE
538           | G_PARAM_STATIC_STRINGS));
539
540   /**
541    * GstCairoOverlay::draw:
542    * @overlay: Overlay element emitting the signal.
543    * @cr: Cairo context to draw to.
544    * @timestamp: Timestamp (see #GstClockTime) of the current buffer.
545    * @duration: Duration (see #GstClockTime) of the current buffer.
546    *
547    * This signal is emitted when the overlay should be drawn.
548    */
549   gst_cairo_overlay_signals[SIGNAL_DRAW] =
550       g_signal_new ("draw",
551       G_TYPE_FROM_CLASS (klass),
552       0, 0, NULL, NULL, NULL,
553       G_TYPE_NONE, 3, CAIRO_GOBJECT_TYPE_CONTEXT, G_TYPE_UINT64, G_TYPE_UINT64);
554
555   /**
556    * GstCairoOverlay::caps-changed:
557    * @overlay: Overlay element emitting the signal.
558    * @caps: The #GstCaps of the element.
559    *
560    * This signal is emitted when the caps of the element has changed.
561    */
562   gst_cairo_overlay_signals[SIGNAL_CAPS_CHANGED] =
563       g_signal_new ("caps-changed",
564       G_TYPE_FROM_CLASS (klass),
565       0, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_CAPS);
566
567   gst_element_class_set_static_metadata (element_class, "Cairo overlay",
568       "Filter/Editor/Video",
569       "Render overlay on a video stream using Cairo",
570       "Jon Nordby <jononor@gmail.com>");
571
572   gst_element_class_add_static_pad_template (element_class,
573       &gst_cairo_overlay_sink_template);
574   gst_element_class_add_static_pad_template (element_class,
575       &gst_cairo_overlay_src_template);
576 }
577
578 static void
579 gst_cairo_overlay_init (GstCairoOverlay * overlay)
580 {
581   /* nothing to do */
582 }