docs: fix unnecessary ampersand, < and > escaping in code blocks
[platform/upstream/gstreamer.git] / subprojects / gst-plugins-bad / ext / rsvg / gstrsvgoverlay.c
1 /* GStreamer
2  * Copyright (C) 2010 Olivier Aubert <olivier.aubert@liris.cnrs.fr>
3  * Copyright (C) 2013 Collabora Ltda
4  *  Author: Luciana Fujii Pontello <luciana.fujii@collabora.com>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  */
21
22 /**
23  * SECTION:element-rsvgoverlay
24  * @title: rsvgoverlay
25  *
26  * This elements overlays SVG graphics over the video. SVG data can
27  * either be specified through properties, or fed through the
28  * data-sink pad.
29  *
30  * Position and dimension of the SVG graphics can be achieved by
31  * specifying appropriate dimensions in the SVG file itself, but
32  * shortcuts are provided by the element to specify x/y position and
33  * width/height dimension, both in absolute form (pixels) and in
34  * relative form (percentage of video dimension).
35  *
36  * For any measure (x/y/width/height), the absolute value (in pixels)
37  * takes precedence over the relative one if both are
38  * specified. Absolute values must be set to 0 to disable them.
39  *
40  * If all parameters are 0, the image is displayed without rescaling
41  * at (0, 0) position.
42  *
43  * The fit-to-frame property is a shortcut for displaying the SVG
44  * overlay at (0, 0) position filling the whole screen. It modifies
45  * the values of the x/y/width/height attributes, by setting
46  * height-/width-relative to 1.0. and all other attributes to 0.
47  *
48  * ## Example launch lines
49  * |[
50  * gst-launch-1.0 -v videotestsrc ! videoconvert ! rsvgoverlay location=foo.svg ! videoconvert ! autovideosink
51  * ]| specifies the SVG location through the filename property.
52  * |[
53  * gst-launch-1.0 -v videotestsrc ! videoconvert ! rsvgoverlay name=overlay ! videoconvert ! autovideosink filesrc location=foo.svg ! image/svg ! overlay.data_sink
54  * ]| does the same by feeding data through the data_sink pad. You can also specify the SVG data itself as parameter:
55  * |[
56  * gst-launch-1.0 -v videotestsrc ! videoconvert ! rsvgoverlay data='<svg viewBox="0 0 800 600"><image x="80%" y="80%" width="10%" height="10%" xlink:href="foo.jpg" /></svg>' ! videoconvert ! autovideosink
57  * ]|
58  *
59  */
60
61 #ifdef HAVE_CONFIG_H
62 #  include "config.h"
63 #endif
64
65 #include "gstrsvgoverlay.h"
66 #include <string.h>
67
68 #include <gst/video/video.h>
69
70 #include <librsvg/rsvg.h>
71
72 enum
73 {
74   PROP_0,
75   PROP_DATA,
76   PROP_LOCATION,
77   PROP_FIT_TO_FRAME,
78   PROP_X,
79   PROP_Y,
80   PROP_X_RELATIVE,
81   PROP_Y_RELATIVE,
82   PROP_WIDTH,
83   PROP_HEIGHT,
84   PROP_WIDTH_RELATIVE,
85   PROP_HEIGHT_RELATIVE
86 };
87
88 #define GST_RSVG_LOCK(overlay) G_STMT_START { \
89   GST_LOG_OBJECT (overlay, "Locking rsvgoverlay from thread %p", g_thread_self ()); \
90   g_mutex_lock (&overlay->rsvg_lock); \
91   GST_LOG_OBJECT (overlay, "Locked rsvgoverlay from thread %p", g_thread_self ()); \
92 } G_STMT_END
93
94 #define GST_RSVG_UNLOCK(overlay) G_STMT_START { \
95   GST_LOG_OBJECT (overlay, "Unlocking rsvgoverlay from thread %p", g_thread_self ()); \
96   g_mutex_unlock (&overlay->rsvg_lock); \
97 } G_STMT_END
98
99 #if G_BYTE_ORDER == G_LITTLE_ENDIAN
100 #define GST_RSVG_VIDEO_CAPS GST_VIDEO_CAPS_MAKE ("BGRA")
101 #else
102 #define GST_RSVG_VIDEO_CAPS GST_VIDEO_CAPS_MAKE ("ARGB")
103 #endif
104
105 static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
106     GST_PAD_SRC,
107     GST_PAD_ALWAYS,
108     GST_STATIC_CAPS (GST_RSVG_VIDEO_CAPS)
109     );
110
111 static GstStaticPadTemplate video_sink_template =
112 GST_STATIC_PAD_TEMPLATE ("sink",
113     GST_PAD_SINK,
114     GST_PAD_ALWAYS,
115     GST_STATIC_CAPS (GST_RSVG_VIDEO_CAPS)
116     );
117
118 static GstStaticPadTemplate data_sink_template =
119     GST_STATIC_PAD_TEMPLATE ("data_sink",
120     GST_PAD_SINK,
121     GST_PAD_ALWAYS,
122     GST_STATIC_CAPS ("image/svg+xml; image/svg; text/plain"));
123
124 #define gst_rsv_overlay_parent_class parent_class
125 G_DEFINE_TYPE (GstRsvgOverlay, gst_rsvg_overlay, GST_TYPE_VIDEO_FILTER);
126 GST_ELEMENT_REGISTER_DEFINE (rsvgoverlay, "rsvgoverlay", GST_RANK_NONE,
127     GST_TYPE_RSVG_OVERLAY);
128
129 static void gst_rsvg_overlay_finalize (GObject * object);
130
131 static void
132 gst_rsvg_overlay_set_svg_data (GstRsvgOverlay * overlay, const gchar * data,
133     gboolean consider_as_filename)
134 {
135   GstBaseTransform *btrans = GST_BASE_TRANSFORM (overlay);
136   gsize size;
137   GError *error = NULL;
138
139   if (overlay->handle) {
140     g_object_unref (overlay->handle);
141     overlay->handle = NULL;
142     gst_base_transform_set_passthrough (btrans, TRUE);
143   }
144
145   /* data may be NULL */
146   if (data) {
147     size = strlen (data);
148     if (size) {
149       /* Read data either from string or from file */
150       if (consider_as_filename)
151         overlay->handle = rsvg_handle_new_from_file (data, &error);
152       else
153         overlay->handle =
154             rsvg_handle_new_from_data ((guint8 *) data, size, &error);
155       if (error || overlay->handle == NULL) {
156         if (error) {
157           GST_ERROR_OBJECT (overlay, "Cannot read SVG data: %s\n%s",
158               error->message, data);
159           g_error_free (error);
160         } else {
161           GST_ERROR_OBJECT (overlay, "Cannot read SVG data: %s", data);
162         }
163       } else {
164         /* Get SVG dimension. */
165         RsvgDimensionData svg_dimension;
166         rsvg_handle_get_dimensions (overlay->handle, &svg_dimension);
167         overlay->svg_width = svg_dimension.width;
168         overlay->svg_height = svg_dimension.height;
169         gst_base_transform_set_passthrough (btrans, FALSE);
170         GST_INFO_OBJECT (overlay, "updated SVG, %d x %d", overlay->svg_width,
171             overlay->svg_height);
172       }
173     }
174   }
175 }
176
177 static void
178 gst_rsvg_overlay_set_property (GObject * object, guint prop_id,
179     const GValue * value, GParamSpec * pspec)
180 {
181   GstRsvgOverlay *overlay = GST_RSVG_OVERLAY (object);
182
183   GST_RSVG_LOCK (overlay);
184
185   switch (prop_id) {
186     case PROP_DATA:
187     {
188       gst_rsvg_overlay_set_svg_data (overlay, g_value_get_string (value),
189           FALSE);
190       break;
191     }
192     case PROP_LOCATION:
193     {
194       gst_rsvg_overlay_set_svg_data (overlay, g_value_get_string (value), TRUE);
195       break;
196     }
197     case PROP_FIT_TO_FRAME:
198     {
199       if (g_value_get_boolean (value)) {
200         overlay->x_offset = 0;
201         overlay->y_offset = 0;
202         overlay->x_relative = 0.0;
203         overlay->y_relative = 0.0;
204         overlay->width = 0;
205         overlay->height = 0;
206         overlay->width_relative = 1.0;
207         overlay->height_relative = 1.0;
208       } else {
209         overlay->width_relative = 0;
210         overlay->height_relative = 0;
211       }
212       break;
213     }
214     case PROP_X:
215     {
216       overlay->x_offset = g_value_get_int (value);
217       break;
218     }
219     case PROP_Y:
220     {
221       overlay->y_offset = g_value_get_int (value);
222       break;
223     }
224     case PROP_X_RELATIVE:
225     {
226       overlay->x_relative = g_value_get_float (value);
227       break;
228     }
229     case PROP_Y_RELATIVE:
230     {
231       overlay->y_relative = g_value_get_float (value);
232       break;
233     }
234
235     case PROP_WIDTH:
236     {
237       overlay->width = g_value_get_int (value);
238       break;
239     }
240     case PROP_HEIGHT:
241     {
242       overlay->height = g_value_get_int (value);
243       break;
244     }
245     case PROP_WIDTH_RELATIVE:
246     {
247       overlay->width_relative = g_value_get_float (value);
248       break;
249     }
250     case PROP_HEIGHT_RELATIVE:
251     {
252       overlay->height_relative = g_value_get_float (value);
253       break;
254     }
255
256     default:
257     {
258       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
259       break;
260     }
261   }
262
263   GST_RSVG_UNLOCK (overlay);
264 }
265
266 static void
267 gst_rsvg_overlay_get_property (GObject * object, guint prop_id, GValue * value,
268     GParamSpec * pspec)
269 {
270   GstRsvgOverlay *overlay = GST_RSVG_OVERLAY (object);
271
272   switch (prop_id) {
273     case PROP_X:
274       g_value_set_int (value, overlay->x_offset);
275       break;
276     case PROP_Y:
277       g_value_set_int (value, overlay->y_offset);
278       break;
279     case PROP_X_RELATIVE:
280       g_value_set_float (value, overlay->x_relative);
281       break;
282     case PROP_Y_RELATIVE:
283       g_value_set_float (value, overlay->y_relative);
284       break;
285
286     case PROP_WIDTH:
287       g_value_set_int (value, overlay->width);
288       break;
289     case PROP_HEIGHT:
290       g_value_set_int (value, overlay->height);
291       break;
292     case PROP_WIDTH_RELATIVE:
293       g_value_set_float (value, overlay->width_relative);
294       break;
295     case PROP_HEIGHT_RELATIVE:
296       g_value_set_float (value, overlay->height_relative);
297       break;
298
299     case PROP_FIT_TO_FRAME:
300       g_value_set_boolean (value, (overlay->width_relative == 1.0
301               && overlay->height_relative == 1.0));
302       break;
303     default:
304       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
305       break;
306   }
307 }
308
309 static GstFlowReturn
310 gst_rsvg_overlay_data_sink_chain (GstPad * pad, GstObject * parent,
311     GstBuffer * buffer)
312 {
313   GstRsvgOverlay *overlay = GST_RSVG_OVERLAY (GST_PAD_PARENT (pad));
314
315   gst_adapter_push (overlay->adapter, buffer);
316   return GST_FLOW_OK;
317 }
318
319 static gboolean
320 gst_rsvg_overlay_data_sink_event (GstPad * pad, GstObject * parent,
321     GstEvent * event)
322 {
323   GstRsvgOverlay *overlay = GST_RSVG_OVERLAY (GST_PAD_PARENT (pad));
324
325   GST_LOG_OBJECT (pad, "Got %s event", GST_EVENT_TYPE_NAME (event));
326
327   switch (GST_EVENT_TYPE (event)) {
328
329     case GST_EVENT_EOS:{
330       guint data_size;
331
332       GST_RSVG_LOCK (overlay);
333       /* FIXME: rsvgdec looks for </svg> in data to determine the end
334          of SVG code. Should we really do the same here? IOW, could
335          data be sent and _pushed() before the EOS gets processed? */
336       data_size = gst_adapter_available (overlay->adapter);
337       if (data_size) {
338         gst_rsvg_overlay_set_svg_data (overlay,
339             (const gchar *) gst_adapter_take (overlay->adapter, data_size),
340             FALSE);
341         gst_adapter_clear (overlay->adapter);
342       }
343       GST_RSVG_UNLOCK (overlay);
344     }
345       break;
346
347     case GST_EVENT_FLUSH_STOP:
348       gst_adapter_clear (overlay->adapter);
349       break;
350
351     default:
352       break;
353   }
354
355   /* Dropping all events here */
356   gst_event_unref (event);
357   return TRUE;
358 }
359
360 static GstFlowReturn
361 gst_rsvg_overlay_transform_frame_ip (GstVideoFilter * vfilter,
362     GstVideoFrame * frame)
363 {
364   GstRsvgOverlay *overlay = GST_RSVG_OVERLAY (vfilter);
365   cairo_surface_t *surface;
366   cairo_t *cr;
367   double applied_x_offset = (double) overlay->x_offset;
368   double applied_y_offset = (double) overlay->y_offset;
369   int applied_width = overlay->width;
370   int applied_height = overlay->height;
371
372   GST_RSVG_LOCK (overlay);
373   if (!overlay->handle) {
374     GST_RSVG_UNLOCK (overlay);
375     return GST_FLOW_OK;
376   }
377
378   surface =
379       cairo_image_surface_create_for_data (GST_VIDEO_FRAME_PLANE_DATA (frame,
380           0), CAIRO_FORMAT_ARGB32, GST_VIDEO_FRAME_WIDTH (frame),
381       GST_VIDEO_FRAME_HEIGHT (frame), GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0));
382   if (G_UNLIKELY (!surface))
383     return GST_FLOW_ERROR;
384
385   cr = cairo_create (surface);
386   if (G_UNLIKELY (!cr)) {
387     cairo_surface_destroy (surface);
388     return GST_FLOW_ERROR;
389   }
390
391   /* Compute relative dimensions if absolute dimensions are not set */
392   if (!applied_x_offset && overlay->x_relative) {
393     applied_x_offset = overlay->x_relative * GST_VIDEO_FRAME_WIDTH (frame);
394   }
395   if (!applied_y_offset && overlay->y_relative) {
396     applied_y_offset = overlay->y_relative * GST_VIDEO_FRAME_HEIGHT (frame);
397   }
398   if (!applied_width && overlay->width_relative) {
399     applied_width =
400         (int) (overlay->width_relative * GST_VIDEO_FRAME_WIDTH (frame));
401   }
402   if (!applied_height && overlay->height_relative) {
403     applied_height =
404         (int) (overlay->height_relative * GST_VIDEO_FRAME_HEIGHT (frame));
405   }
406
407   if (applied_x_offset || applied_y_offset) {
408     cairo_translate (cr, applied_x_offset, applied_y_offset);
409   }
410
411   /* Scale when necessary, i.e. an absolute or relative dimension has been specified. */
412   if ((applied_width || applied_height) && overlay->svg_width
413       && overlay->svg_height) {
414     /* If may happen that only one of the dimension is specified. Use
415        the original SVG size for the other dimension. */
416     if (!applied_width)
417       applied_width = overlay->svg_width;
418     if (!applied_height)
419       applied_height = overlay->svg_height;
420
421     cairo_scale (cr, (double) applied_width / overlay->svg_width,
422         (double) applied_height / overlay->svg_height);
423   }
424   rsvg_handle_render_cairo (overlay->handle, cr);
425   GST_RSVG_UNLOCK (overlay);
426
427   cairo_destroy (cr);
428   cairo_surface_destroy (surface);
429
430   return GST_FLOW_OK;
431 }
432
433 static gboolean
434 gst_rsvg_overlay_stop (GstBaseTransform * btrans)
435 {
436   GstRsvgOverlay *overlay = GST_RSVG_OVERLAY (btrans);
437
438   if (overlay->handle) {
439     g_object_unref (overlay->handle);
440     overlay->handle = NULL;
441   }
442
443   gst_adapter_clear (overlay->adapter);
444
445   return TRUE;
446 }
447
448 static void
449 gst_rsvg_overlay_class_init (GstRsvgOverlayClass * klass)
450 {
451   GObjectClass *gobject_class = (GObjectClass *) klass;
452   GstBaseTransformClass *basetransform_class = GST_BASE_TRANSFORM_CLASS (klass);
453   GstVideoFilterClass *videofilter_class = GST_VIDEO_FILTER_CLASS (klass);
454   GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
455
456   gst_element_class_add_static_pad_template (element_class, &src_template);
457   gst_element_class_add_static_pad_template (element_class,
458       &video_sink_template);
459   gst_element_class_add_static_pad_template (element_class,
460       &data_sink_template);
461
462   gst_element_class_set_static_metadata (element_class, "RSVG overlay",
463       "Filter/Editor/Video",
464       "Overlays SVG graphics over a video stream",
465       "Olivier Aubert <olivier.aubert@liris.cnrs.fr>");
466
467   gobject_class->set_property = gst_rsvg_overlay_set_property;
468   gobject_class->get_property = gst_rsvg_overlay_get_property;
469   gobject_class->finalize = gst_rsvg_overlay_finalize;
470
471   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DATA,
472       g_param_spec_string ("data", "data", "SVG data.", "",
473           G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
474   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_LOCATION,
475       g_param_spec_string ("location", "location", "SVG file location.", "",
476           G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
477   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_FIT_TO_FRAME,
478       g_param_spec_boolean ("fit-to-frame", "fit to frame",
479           "Fit the SVG to fill the whole frame.", TRUE,
480           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
481
482   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_X,
483       g_param_spec_int ("x", "x offset",
484           "Specify an x offset.", -G_MAXINT, G_MAXINT, 0,
485           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
486   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_Y,
487       g_param_spec_int ("y", "y offset",
488           "Specify a y offset.", -G_MAXINT, G_MAXINT, 0,
489           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
490   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_X_RELATIVE,
491       g_param_spec_float ("x-relative", "x relative offset",
492           "Specify an x offset relative to the display size.", -G_MAXFLOAT,
493           G_MAXFLOAT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
494   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_Y_RELATIVE,
495       g_param_spec_float ("y-relative", "y relative offset",
496           "Specify a y offset relative to the display size.", -G_MAXFLOAT,
497           G_MAXFLOAT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
498
499   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WIDTH,
500       g_param_spec_int ("width", "width",
501           "Specify a width in pixels.", -G_MAXINT, G_MAXINT, 0,
502           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
503   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_HEIGHT,
504       g_param_spec_int ("height", "height",
505           "Specify a height in pixels.", -G_MAXINT, G_MAXINT, 0,
506           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
507   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WIDTH_RELATIVE,
508       g_param_spec_float ("width-relative", "relative width",
509           "Specify a width relative to the display size.", -G_MAXFLOAT,
510           G_MAXFLOAT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
511   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_HEIGHT_RELATIVE,
512       g_param_spec_float ("height-relative", "relative height",
513           "Specify a height relative to the display size.", -G_MAXFLOAT,
514           G_MAXFLOAT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
515
516   videofilter_class->transform_frame_ip = gst_rsvg_overlay_transform_frame_ip;
517   basetransform_class->stop = gst_rsvg_overlay_stop;
518   basetransform_class->passthrough_on_same_caps = FALSE;
519 }
520
521 static void
522 gst_rsvg_overlay_init (GstRsvgOverlay * overlay)
523 {
524   overlay->x_offset = 0;
525   overlay->y_offset = 0;
526   overlay->x_relative = 0.0;
527   overlay->y_relative = 0.0;
528   overlay->width = 0;
529   overlay->height = 0;
530   overlay->width_relative = 0.0;
531   overlay->height_relative = 0.0;
532
533   overlay->adapter = gst_adapter_new ();
534
535   /* data sink */
536   overlay->data_sinkpad =
537       gst_pad_new_from_static_template (&data_sink_template, "data_sink");
538   gst_pad_set_chain_function (overlay->data_sinkpad,
539       GST_DEBUG_FUNCPTR (gst_rsvg_overlay_data_sink_chain));
540   gst_pad_set_event_function (overlay->data_sinkpad,
541       GST_DEBUG_FUNCPTR (gst_rsvg_overlay_data_sink_event));
542   gst_element_add_pad (GST_ELEMENT (overlay), overlay->data_sinkpad);
543 }
544
545 static void
546 gst_rsvg_overlay_finalize (GObject * object)
547 {
548   GstRsvgOverlay *overlay = GST_RSVG_OVERLAY (object);
549
550   g_object_unref (overlay->adapter);
551
552   G_OBJECT_CLASS (gst_rsvg_overlay_parent_class)->finalize (object);
553 }