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='&lt;svg viewBox="0 0 800 600"&gt;&lt;image x="80%" y="80%" width="10%" height="10%" xlink:href="foo.jpg" /&gt;&lt;/svg&gt;' ! 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 
127 static void gst_rsvg_overlay_finalize (GObject * object);
128 
129 static void
gst_rsvg_overlay_set_svg_data(GstRsvgOverlay * overlay,const gchar * data,gboolean consider_as_filename)130 gst_rsvg_overlay_set_svg_data (GstRsvgOverlay * overlay, const gchar * data,
131     gboolean consider_as_filename)
132 {
133   GstBaseTransform *btrans = GST_BASE_TRANSFORM (overlay);
134   gsize size;
135   GError *error = NULL;
136 
137   if (overlay->handle) {
138     g_object_unref (overlay->handle);
139     overlay->handle = NULL;
140     gst_base_transform_set_passthrough (btrans, TRUE);
141   }
142 
143   /* data may be NULL */
144   if (data) {
145     size = strlen (data);
146     if (size) {
147       /* Read data either from string or from file */
148       if (consider_as_filename)
149         overlay->handle = rsvg_handle_new_from_file (data, &error);
150       else
151         overlay->handle =
152             rsvg_handle_new_from_data ((guint8 *) data, size, &error);
153       if (error || overlay->handle == NULL) {
154         if (error) {
155           GST_ERROR_OBJECT (overlay, "Cannot read SVG data: %s\n%s",
156               error->message, data);
157           g_error_free (error);
158         } else {
159           GST_ERROR_OBJECT (overlay, "Cannot read SVG data: %s", data);
160         }
161       } else {
162         /* Get SVG dimension. */
163         RsvgDimensionData svg_dimension;
164         rsvg_handle_get_dimensions (overlay->handle, &svg_dimension);
165         overlay->svg_width = svg_dimension.width;
166         overlay->svg_height = svg_dimension.height;
167         gst_base_transform_set_passthrough (btrans, FALSE);
168         GST_INFO_OBJECT (overlay, "updated SVG, %d x %d", overlay->svg_width,
169             overlay->svg_height);
170       }
171     }
172   }
173 }
174 
175 static void
gst_rsvg_overlay_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)176 gst_rsvg_overlay_set_property (GObject * object, guint prop_id,
177     const GValue * value, GParamSpec * pspec)
178 {
179   GstRsvgOverlay *overlay = GST_RSVG_OVERLAY (object);
180 
181   GST_RSVG_LOCK (overlay);
182 
183   switch (prop_id) {
184     case PROP_DATA:
185     {
186       gst_rsvg_overlay_set_svg_data (overlay, g_value_get_string (value),
187           FALSE);
188       break;
189     }
190     case PROP_LOCATION:
191     {
192       gst_rsvg_overlay_set_svg_data (overlay, g_value_get_string (value), TRUE);
193       break;
194     }
195     case PROP_FIT_TO_FRAME:
196     {
197       if (g_value_get_boolean (value)) {
198         overlay->x_offset = 0;
199         overlay->y_offset = 0;
200         overlay->x_relative = 0.0;
201         overlay->y_relative = 0.0;
202         overlay->width = 0;
203         overlay->height = 0;
204         overlay->width_relative = 1.0;
205         overlay->height_relative = 1.0;
206       } else {
207         overlay->width_relative = 0;
208         overlay->height_relative = 0;
209       }
210       break;
211     }
212     case PROP_X:
213     {
214       overlay->x_offset = g_value_get_int (value);
215       break;
216     }
217     case PROP_Y:
218     {
219       overlay->y_offset = g_value_get_int (value);
220       break;
221     }
222     case PROP_X_RELATIVE:
223     {
224       overlay->x_relative = g_value_get_float (value);
225       break;
226     }
227     case PROP_Y_RELATIVE:
228     {
229       overlay->y_relative = g_value_get_float (value);
230       break;
231     }
232 
233     case PROP_WIDTH:
234     {
235       overlay->width = g_value_get_int (value);
236       break;
237     }
238     case PROP_HEIGHT:
239     {
240       overlay->height = g_value_get_int (value);
241       break;
242     }
243     case PROP_WIDTH_RELATIVE:
244     {
245       overlay->width_relative = g_value_get_float (value);
246       break;
247     }
248     case PROP_HEIGHT_RELATIVE:
249     {
250       overlay->height_relative = g_value_get_float (value);
251       break;
252     }
253 
254     default:
255     {
256       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
257       break;
258     }
259   }
260 
261   GST_RSVG_UNLOCK (overlay);
262 }
263 
264 static void
gst_rsvg_overlay_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)265 gst_rsvg_overlay_get_property (GObject * object, guint prop_id, GValue * value,
266     GParamSpec * pspec)
267 {
268   GstRsvgOverlay *overlay = GST_RSVG_OVERLAY (object);
269 
270   switch (prop_id) {
271     case PROP_X:
272       g_value_set_int (value, overlay->x_offset);
273       break;
274     case PROP_Y:
275       g_value_set_int (value, overlay->y_offset);
276       break;
277     case PROP_X_RELATIVE:
278       g_value_set_float (value, overlay->x_relative);
279       break;
280     case PROP_Y_RELATIVE:
281       g_value_set_float (value, overlay->y_relative);
282       break;
283 
284     case PROP_WIDTH:
285       g_value_set_int (value, overlay->width);
286       break;
287     case PROP_HEIGHT:
288       g_value_set_int (value, overlay->height);
289       break;
290     case PROP_WIDTH_RELATIVE:
291       g_value_set_float (value, overlay->width_relative);
292       break;
293     case PROP_HEIGHT_RELATIVE:
294       g_value_set_float (value, overlay->height_relative);
295       break;
296 
297     case PROP_FIT_TO_FRAME:
298       g_value_set_boolean (value, (overlay->width_relative == 1.0
299               && overlay->height_relative == 1.0));
300       break;
301     default:
302       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
303       break;
304   }
305 }
306 
307 static GstFlowReturn
gst_rsvg_overlay_data_sink_chain(GstPad * pad,GstObject * parent,GstBuffer * buffer)308 gst_rsvg_overlay_data_sink_chain (GstPad * pad, GstObject * parent,
309     GstBuffer * buffer)
310 {
311   GstRsvgOverlay *overlay = GST_RSVG_OVERLAY (GST_PAD_PARENT (pad));
312 
313   gst_adapter_push (overlay->adapter, buffer);
314   return GST_FLOW_OK;
315 }
316 
317 static gboolean
gst_rsvg_overlay_data_sink_event(GstPad * pad,GstObject * parent,GstEvent * event)318 gst_rsvg_overlay_data_sink_event (GstPad * pad, GstObject * parent,
319     GstEvent * event)
320 {
321   GstRsvgOverlay *overlay = GST_RSVG_OVERLAY (GST_PAD_PARENT (pad));
322 
323   GST_LOG_OBJECT (pad, "Got %s event", GST_EVENT_TYPE_NAME (event));
324 
325   switch (GST_EVENT_TYPE (event)) {
326 
327     case GST_EVENT_EOS:{
328       guint data_size;
329 
330       GST_RSVG_LOCK (overlay);
331       /* FIXME: rsvgdec looks for </svg> in data to determine the end
332          of SVG code. Should we really do the same here? IOW, could
333          data be sent and _pushed() before the EOS gets processed? */
334       data_size = gst_adapter_available (overlay->adapter);
335       if (data_size) {
336         gst_rsvg_overlay_set_svg_data (overlay,
337             (const gchar *) gst_adapter_take (overlay->adapter, data_size),
338             FALSE);
339         gst_adapter_clear (overlay->adapter);
340       }
341       GST_RSVG_UNLOCK (overlay);
342     }
343       break;
344 
345     case GST_EVENT_FLUSH_STOP:
346       gst_adapter_clear (overlay->adapter);
347       break;
348 
349     default:
350       break;
351   }
352 
353   /* Dropping all events here */
354   gst_event_unref (event);
355   return TRUE;
356 }
357 
358 static GstFlowReturn
gst_rsvg_overlay_transform_frame_ip(GstVideoFilter * vfilter,GstVideoFrame * frame)359 gst_rsvg_overlay_transform_frame_ip (GstVideoFilter * vfilter,
360     GstVideoFrame * frame)
361 {
362   GstRsvgOverlay *overlay = GST_RSVG_OVERLAY (vfilter);
363   cairo_surface_t *surface;
364   cairo_t *cr;
365   double applied_x_offset = (double) overlay->x_offset;
366   double applied_y_offset = (double) overlay->y_offset;
367   int applied_width = overlay->width;
368   int applied_height = overlay->height;
369 
370   GST_RSVG_LOCK (overlay);
371   if (!overlay->handle) {
372     GST_RSVG_UNLOCK (overlay);
373     return GST_FLOW_OK;
374   }
375 
376   surface =
377       cairo_image_surface_create_for_data (GST_VIDEO_FRAME_PLANE_DATA (frame,
378           0), CAIRO_FORMAT_ARGB32, GST_VIDEO_FRAME_WIDTH (frame),
379       GST_VIDEO_FRAME_HEIGHT (frame), GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0));
380   if (G_UNLIKELY (!surface))
381     return GST_FLOW_ERROR;
382 
383   cr = cairo_create (surface);
384   if (G_UNLIKELY (!cr)) {
385     cairo_surface_destroy (surface);
386     return GST_FLOW_ERROR;
387   }
388 
389   /* Compute relative dimensions if absolute dimensions are not set */
390   if (!applied_x_offset && overlay->x_relative) {
391     applied_x_offset = overlay->x_relative * GST_VIDEO_FRAME_WIDTH (frame);
392   }
393   if (!applied_y_offset && overlay->y_relative) {
394     applied_y_offset = overlay->y_relative * GST_VIDEO_FRAME_HEIGHT (frame);
395   }
396   if (!applied_width && overlay->width_relative) {
397     applied_width =
398         (int) (overlay->width_relative * GST_VIDEO_FRAME_WIDTH (frame));
399   }
400   if (!applied_height && overlay->height_relative) {
401     applied_height =
402         (int) (overlay->height_relative * GST_VIDEO_FRAME_HEIGHT (frame));
403   }
404 
405   if (applied_x_offset || applied_y_offset) {
406     cairo_translate (cr, applied_x_offset, applied_y_offset);
407   }
408 
409   /* Scale when necessary, i.e. an absolute or relative dimension has been specified. */
410   if ((applied_width || applied_height) && overlay->svg_width
411       && overlay->svg_height) {
412     /* If may happen that only one of the dimension is specified. Use
413        the original SVG size for the other dimension. */
414     if (!applied_width)
415       applied_width = overlay->svg_width;
416     if (!applied_height)
417       applied_height = overlay->svg_height;
418 
419     cairo_scale (cr, (double) applied_width / overlay->svg_width,
420         (double) applied_height / overlay->svg_height);
421   }
422   rsvg_handle_render_cairo (overlay->handle, cr);
423   GST_RSVG_UNLOCK (overlay);
424 
425   cairo_destroy (cr);
426   cairo_surface_destroy (surface);
427 
428   return GST_FLOW_OK;
429 }
430 
431 static gboolean
gst_rsvg_overlay_stop(GstBaseTransform * btrans)432 gst_rsvg_overlay_stop (GstBaseTransform * btrans)
433 {
434   GstRsvgOverlay *overlay = GST_RSVG_OVERLAY (btrans);
435 
436   if (overlay->handle) {
437     g_object_unref (overlay->handle);
438     overlay->handle = NULL;
439   }
440 
441   gst_adapter_clear (overlay->adapter);
442 
443   return TRUE;
444 }
445 
446 static void
gst_rsvg_overlay_class_init(GstRsvgOverlayClass * klass)447 gst_rsvg_overlay_class_init (GstRsvgOverlayClass * klass)
448 {
449   GObjectClass *gobject_class = (GObjectClass *) klass;
450   GstBaseTransformClass *basetransform_class = GST_BASE_TRANSFORM_CLASS (klass);
451   GstVideoFilterClass *videofilter_class = GST_VIDEO_FILTER_CLASS (klass);
452   GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
453 
454   gst_element_class_add_static_pad_template (element_class, &src_template);
455   gst_element_class_add_static_pad_template (element_class,
456       &video_sink_template);
457   gst_element_class_add_static_pad_template (element_class,
458       &data_sink_template);
459 
460   gst_element_class_set_static_metadata (element_class, "RSVG overlay",
461       "Filter/Editor/Video",
462       "Overlays SVG graphics over a video stream",
463       "Olivier Aubert <olivier.aubert@liris.cnrs.fr>");
464 
465   gobject_class->set_property = gst_rsvg_overlay_set_property;
466   gobject_class->get_property = gst_rsvg_overlay_get_property;
467   gobject_class->finalize = gst_rsvg_overlay_finalize;
468 
469   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DATA,
470       g_param_spec_string ("data", "data", "SVG data.", "",
471           G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
472   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_LOCATION,
473       g_param_spec_string ("location", "location", "SVG file location.", "",
474           G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
475   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_FIT_TO_FRAME,
476       g_param_spec_boolean ("fit-to-frame", "fit to frame",
477           "Fit the SVG to fill the whole frame.", TRUE,
478           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
479 
480   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_X,
481       g_param_spec_int ("x", "x offset",
482           "Specify an x offset.", -G_MAXINT, G_MAXINT, 0,
483           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
484   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_Y,
485       g_param_spec_int ("y", "y offset",
486           "Specify a y offset.", -G_MAXINT, G_MAXINT, 0,
487           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
488   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_X_RELATIVE,
489       g_param_spec_float ("x-relative", "x relative offset",
490           "Specify an x offset relative to the display size.", -G_MAXFLOAT,
491           G_MAXFLOAT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
492   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_Y_RELATIVE,
493       g_param_spec_float ("y-relative", "y relative offset",
494           "Specify a y offset relative to the display size.", -G_MAXFLOAT,
495           G_MAXFLOAT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
496 
497   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WIDTH,
498       g_param_spec_int ("width", "width",
499           "Specify a width in pixels.", -G_MAXINT, G_MAXINT, 0,
500           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
501   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_HEIGHT,
502       g_param_spec_int ("height", "height",
503           "Specify a height in pixels.", -G_MAXINT, G_MAXINT, 0,
504           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
505   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WIDTH_RELATIVE,
506       g_param_spec_float ("width-relative", "relative width",
507           "Specify a width relative to the display size.", -G_MAXFLOAT,
508           G_MAXFLOAT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
509   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_HEIGHT_RELATIVE,
510       g_param_spec_float ("height-relative", "relative height",
511           "Specify a height relative to the display size.", -G_MAXFLOAT,
512           G_MAXFLOAT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
513 
514   videofilter_class->transform_frame_ip = gst_rsvg_overlay_transform_frame_ip;
515   basetransform_class->stop = gst_rsvg_overlay_stop;
516   basetransform_class->passthrough_on_same_caps = FALSE;
517 }
518 
519 static void
gst_rsvg_overlay_init(GstRsvgOverlay * overlay)520 gst_rsvg_overlay_init (GstRsvgOverlay * overlay)
521 {
522   overlay->x_offset = 0;
523   overlay->y_offset = 0;
524   overlay->x_relative = 0.0;
525   overlay->y_relative = 0.0;
526   overlay->width = 0;
527   overlay->height = 0;
528   overlay->width_relative = 0.0;
529   overlay->height_relative = 0.0;
530 
531   overlay->adapter = gst_adapter_new ();
532 
533   /* data sink */
534   overlay->data_sinkpad =
535       gst_pad_new_from_static_template (&data_sink_template, "data_sink");
536   gst_pad_set_chain_function (overlay->data_sinkpad,
537       GST_DEBUG_FUNCPTR (gst_rsvg_overlay_data_sink_chain));
538   gst_pad_set_event_function (overlay->data_sinkpad,
539       GST_DEBUG_FUNCPTR (gst_rsvg_overlay_data_sink_event));
540   gst_element_add_pad (GST_ELEMENT (overlay), overlay->data_sinkpad);
541 }
542 
543 static void
gst_rsvg_overlay_finalize(GObject * object)544 gst_rsvg_overlay_finalize (GObject * object)
545 {
546   GstRsvgOverlay *overlay = GST_RSVG_OVERLAY (object);
547 
548   g_object_unref (overlay->adapter);
549 
550   G_OBJECT_CLASS (gst_rsvg_overlay_parent_class)->finalize (object);
551 }
552