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
gst_rsvg_overlay_set_svg_data(GstRsvgOverlay * overlay,const gchar * data,gboolean consider_as_filename)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
gst_rsvg_overlay_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)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
gst_rsvg_overlay_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)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
gst_rsvg_overlay_data_sink_chain(GstPad * pad,GstObject * parent,GstBuffer * buffer)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
gst_rsvg_overlay_data_sink_event(GstPad * pad,GstObject * parent,GstEvent * event)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
gst_rsvg_overlay_transform_frame_ip(GstVideoFilter * vfilter,GstVideoFrame * frame)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
gst_rsvg_overlay_stop(GstBaseTransform * btrans)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
gst_rsvg_overlay_class_init(GstRsvgOverlayClass * klass)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
gst_rsvg_overlay_init(GstRsvgOverlay * overlay)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
gst_rsvg_overlay_finalize(GObject * object)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 }
554