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
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