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>
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.
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.
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.
23 * SECTION:element-rsvgoverlay
26 * This elements overlays SVG graphics over the video. SVG data can
27 * either be specified through properties, or fed through the
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).
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.
40 * If all parameters are 0, the image is displayed without rescaling
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.
48 * ## Example launch lines
50 * gst-launch-1.0 -v videotestsrc ! videoconvert ! rsvgoverlay location=foo.svg ! videoconvert ! autovideosink
51 * ]| specifies the SVG location through the filename property.
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:
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
65 #include "gstrsvgoverlay.h"
68 #include <gst/video/video.h>
70 #include <librsvg/rsvg.h>
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 ()); \
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); \
99 #if G_BYTE_ORDER == G_LITTLE_ENDIAN
100 #define GST_RSVG_VIDEO_CAPS GST_VIDEO_CAPS_MAKE ("BGRA")
102 #define GST_RSVG_VIDEO_CAPS GST_VIDEO_CAPS_MAKE ("ARGB")
105 static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
108 GST_STATIC_CAPS (GST_RSVG_VIDEO_CAPS)
111 static GstStaticPadTemplate video_sink_template =
112 GST_STATIC_PAD_TEMPLATE ("sink",
115 GST_STATIC_CAPS (GST_RSVG_VIDEO_CAPS)
118 static GstStaticPadTemplate data_sink_template =
119 GST_STATIC_PAD_TEMPLATE ("data_sink",
122 GST_STATIC_CAPS ("image/svg+xml; image/svg; text/plain"));
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);
129 static void gst_rsvg_overlay_finalize (GObject * object);
132 gst_rsvg_overlay_set_svg_data (GstRsvgOverlay * overlay, const gchar * data,
133 gboolean consider_as_filename)
135 GstBaseTransform *btrans = GST_BASE_TRANSFORM (overlay);
137 GError *error = NULL;
139 if (overlay->handle) {
140 g_object_unref (overlay->handle);
141 overlay->handle = NULL;
142 gst_base_transform_set_passthrough (btrans, TRUE);
145 /* data may be NULL */
147 size = strlen (data);
149 /* Read data either from string or from file */
150 if (consider_as_filename)
151 overlay->handle = rsvg_handle_new_from_file (data, &error);
154 rsvg_handle_new_from_data ((guint8 *) data, size, &error);
155 if (error || overlay->handle == NULL) {
157 GST_ERROR_OBJECT (overlay, "Cannot read SVG data: %s\n%s",
158 error->message, data);
159 g_error_free (error);
161 GST_ERROR_OBJECT (overlay, "Cannot read SVG data: %s", data);
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);
178 gst_rsvg_overlay_set_property (GObject * object, guint prop_id,
179 const GValue * value, GParamSpec * pspec)
181 GstRsvgOverlay *overlay = GST_RSVG_OVERLAY (object);
183 GST_RSVG_LOCK (overlay);
188 gst_rsvg_overlay_set_svg_data (overlay, g_value_get_string (value),
194 gst_rsvg_overlay_set_svg_data (overlay, g_value_get_string (value), TRUE);
197 case PROP_FIT_TO_FRAME:
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;
206 overlay->width_relative = 1.0;
207 overlay->height_relative = 1.0;
209 overlay->width_relative = 0;
210 overlay->height_relative = 0;
216 overlay->x_offset = g_value_get_int (value);
221 overlay->y_offset = g_value_get_int (value);
224 case PROP_X_RELATIVE:
226 overlay->x_relative = g_value_get_float (value);
229 case PROP_Y_RELATIVE:
231 overlay->y_relative = g_value_get_float (value);
237 overlay->width = g_value_get_int (value);
242 overlay->height = g_value_get_int (value);
245 case PROP_WIDTH_RELATIVE:
247 overlay->width_relative = g_value_get_float (value);
250 case PROP_HEIGHT_RELATIVE:
252 overlay->height_relative = g_value_get_float (value);
258 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
263 GST_RSVG_UNLOCK (overlay);
267 gst_rsvg_overlay_get_property (GObject * object, guint prop_id, GValue * value,
270 GstRsvgOverlay *overlay = GST_RSVG_OVERLAY (object);
274 g_value_set_int (value, overlay->x_offset);
277 g_value_set_int (value, overlay->y_offset);
279 case PROP_X_RELATIVE:
280 g_value_set_float (value, overlay->x_relative);
282 case PROP_Y_RELATIVE:
283 g_value_set_float (value, overlay->y_relative);
287 g_value_set_int (value, overlay->width);
290 g_value_set_int (value, overlay->height);
292 case PROP_WIDTH_RELATIVE:
293 g_value_set_float (value, overlay->width_relative);
295 case PROP_HEIGHT_RELATIVE:
296 g_value_set_float (value, overlay->height_relative);
299 case PROP_FIT_TO_FRAME:
300 g_value_set_boolean (value, (overlay->width_relative == 1.0
301 && overlay->height_relative == 1.0));
304 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
310 gst_rsvg_overlay_data_sink_chain (GstPad * pad, GstObject * parent,
313 GstRsvgOverlay *overlay = GST_RSVG_OVERLAY (GST_PAD_PARENT (pad));
315 gst_adapter_push (overlay->adapter, buffer);
320 gst_rsvg_overlay_data_sink_event (GstPad * pad, GstObject * parent,
323 GstRsvgOverlay *overlay = GST_RSVG_OVERLAY (GST_PAD_PARENT (pad));
325 GST_LOG_OBJECT (pad, "Got %s event", GST_EVENT_TYPE_NAME (event));
327 switch (GST_EVENT_TYPE (event)) {
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);
338 gst_rsvg_overlay_set_svg_data (overlay,
339 (const gchar *) gst_adapter_take (overlay->adapter, data_size),
341 gst_adapter_clear (overlay->adapter);
343 GST_RSVG_UNLOCK (overlay);
347 case GST_EVENT_FLUSH_STOP:
348 gst_adapter_clear (overlay->adapter);
355 /* Dropping all events here */
356 gst_event_unref (event);
361 gst_rsvg_overlay_transform_frame_ip (GstVideoFilter * vfilter,
362 GstVideoFrame * frame)
364 GstRsvgOverlay *overlay = GST_RSVG_OVERLAY (vfilter);
365 cairo_surface_t *surface;
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;
372 GST_RSVG_LOCK (overlay);
373 if (!overlay->handle) {
374 GST_RSVG_UNLOCK (overlay);
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;
385 cr = cairo_create (surface);
386 if (G_UNLIKELY (!cr)) {
387 cairo_surface_destroy (surface);
388 return GST_FLOW_ERROR;
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);
395 if (!applied_y_offset && overlay->y_relative) {
396 applied_y_offset = overlay->y_relative * GST_VIDEO_FRAME_HEIGHT (frame);
398 if (!applied_width && overlay->width_relative) {
400 (int) (overlay->width_relative * GST_VIDEO_FRAME_WIDTH (frame));
402 if (!applied_height && overlay->height_relative) {
404 (int) (overlay->height_relative * GST_VIDEO_FRAME_HEIGHT (frame));
407 if (applied_x_offset || applied_y_offset) {
408 cairo_translate (cr, applied_x_offset, applied_y_offset);
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. */
417 applied_width = overlay->svg_width;
419 applied_height = overlay->svg_height;
421 cairo_scale (cr, (double) applied_width / overlay->svg_width,
422 (double) applied_height / overlay->svg_height);
424 rsvg_handle_render_cairo (overlay->handle, cr);
425 GST_RSVG_UNLOCK (overlay);
428 cairo_surface_destroy (surface);
434 gst_rsvg_overlay_stop (GstBaseTransform * btrans)
436 GstRsvgOverlay *overlay = GST_RSVG_OVERLAY (btrans);
438 if (overlay->handle) {
439 g_object_unref (overlay->handle);
440 overlay->handle = NULL;
443 gst_adapter_clear (overlay->adapter);
449 gst_rsvg_overlay_class_init (GstRsvgOverlayClass * klass)
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);
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);
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>");
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;
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));
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));
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));
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;
522 gst_rsvg_overlay_init (GstRsvgOverlay * overlay)
524 overlay->x_offset = 0;
525 overlay->y_offset = 0;
526 overlay->x_relative = 0.0;
527 overlay->y_relative = 0.0;
530 overlay->width_relative = 0.0;
531 overlay->height_relative = 0.0;
533 overlay->adapter = gst_adapter_new ();
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);
546 gst_rsvg_overlay_finalize (GObject * object)
548 GstRsvgOverlay *overlay = GST_RSVG_OVERLAY (object);
550 g_object_unref (overlay->adapter);
552 G_OBJECT_CLASS (gst_rsvg_overlay_parent_class)->finalize (object);