From c86b12ac95b3cf7d0ab7a57f469f9449f157f2ac Mon Sep 17 00:00:00 2001 From: Olivier Aubert Date: Mon, 22 Nov 2010 15:10:26 +0200 Subject: rsvgoverlay: scalable and relative svg overlay with cairo Add a cairo+librsvg based overlay element to the rsvg plugin. --- ext/rsvg/Makefile.am | 4 +- ext/rsvg/gstrsvg.c | 12 +- ext/rsvg/gstrsvgoverlay.c | 394 ++++++++++++++++++++++++++++++++++++++++++++++ ext/rsvg/gstrsvgoverlay.h | 77 +++++++++ 4 files changed, 480 insertions(+), 7 deletions(-) create mode 100644 ext/rsvg/gstrsvgoverlay.c create mode 100644 ext/rsvg/gstrsvgoverlay.h diff --git a/ext/rsvg/Makefile.am b/ext/rsvg/Makefile.am index 3b2f43c1f..d1b04b943 100644 --- a/ext/rsvg/Makefile.am +++ b/ext/rsvg/Makefile.am @@ -1,6 +1,6 @@ plugin_LTLIBRARIES = libgstrsvg.la -libgstrsvg_la_SOURCES = gstrsvg.c gstrsvgdec.c +libgstrsvg_la_SOURCES = gstrsvg.c gstrsvgdec.c gstrsvgoverlay.c libgstrsvg_la_CFLAGS = \ $(GST_PLUGINS_BASE_CFLAGS) \ $(GST_CFLAGS) \ @@ -13,4 +13,4 @@ libgstrsvg_la_LIBADD = \ libgstrsvg_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) $(LIBM) libgstrsvg_la_LIBTOOLFLAGS = --tag=disable-static -noinst_HEADERS = gstrsvgdec.h +noinst_HEADERS = gstrsvgdec.h gstrsvgoverlay.h diff --git a/ext/rsvg/gstrsvg.c b/ext/rsvg/gstrsvg.c index a7c600694..1318e7098 100644 --- a/ext/rsvg/gstrsvg.c +++ b/ext/rsvg/gstrsvg.c @@ -1,5 +1,6 @@ /* GStreamer * Copyright (C) <2009> Sebastian Dröge + * (C) 2010 Olivier Aubert * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -23,17 +24,18 @@ #include #include "gstrsvgdec.h" +#include "gstrsvgoverlay.h" static gboolean plugin_init (GstPlugin * plugin) { rsvg_init (); - if (!gst_element_register (plugin, "rsvgdec", GST_RANK_PRIMARY, - GST_TYPE_RSVG_DEC)) - return FALSE; - - return TRUE; + return (gst_element_register (plugin, "rsvgoverlay", + GST_RANK_NONE, GST_TYPE_RSVG_OVERLAY) + && + gst_element_register (plugin, "rsvgdec", GST_RANK_PRIMARY, + GST_TYPE_RSVG_DEC)); } GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, diff --git a/ext/rsvg/gstrsvgoverlay.c b/ext/rsvg/gstrsvgoverlay.c new file mode 100644 index 000000000..d5c707824 --- /dev/null +++ b/ext/rsvg/gstrsvgoverlay.c @@ -0,0 +1,394 @@ +/* GStreamer + * Copyright (C) 2010 Olivier Aubert + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * SECTION:element-rsvgoverlay + * + * This elements overlays SVG graphics over the video. SVG data can + * either be specified through properties, or fed through the + * data-sink pad. + * + * Note: setting the x or y parameter to a non-zero value will implicitly disable the fit-to-frame behaviour. + * + * + * + * Example launch lines + * |[ + * gst-launch -v videotestsrc ! ffmpegcolorspace ! rsvgoverlay location=foo.svg ! ffmpegcolorspace ! autovideosink + * ]| specifies the SVG location through the filename property. + * |[ + * gst-launch -v videotestsrc ! ffmpegcolorspace ! rsvgoverlay name=overlay ! ffmpegcolorspace ! autovideosink filesrc location=foo.svg ! image/svg ! overlay.data_sink + * ]| does the same by feeding data through the data_sink pad. You can also specify the SVG data itself as parameter: + * |[ + * gst-launch -v videotestsrc ! ffmpegcolorspace ! rsvgoverlay data='' ! ffmpegcolorspace ! autovideosink + * ]| + * + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "gstrsvgoverlay.h" +#include + +#include + +#include +#include + +enum +{ + PROP_0, + PROP_DATA, + PROP_FILENAME, + PROP_FIT_TO_FRAME, + PROP_X, + PROP_Y, +}; + +#define GST_RSVG_LOCK(overlay) G_STMT_START { \ + GST_LOG_OBJECT (overlay, "Locking rsvgoverlay from thread %p", g_thread_self ()); \ + g_static_mutex_lock (&overlay->rsvg_lock); \ + GST_LOG_OBJECT (overlay, "Locked rsvgoverlay from thread %p", g_thread_self ()); \ +} G_STMT_END + +#define GST_RSVG_UNLOCK(overlay) G_STMT_START { \ + GST_LOG_OBJECT (overlay, "Unlocking rsvgoverlay from thread %p", g_thread_self ()); \ + g_static_mutex_unlock (&overlay->rsvg_lock); \ +} G_STMT_END + +#if G_BYTE_ORDER == G_LITTLE_ENDIAN +#define GST_RSVG_VIDEO_CAPS GST_VIDEO_CAPS_BGRA +#else +#define GST_RSVG_VIDEO_CAPS GST_VIDEO_CAPS_ARGB +#endif + +static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (GST_RSVG_VIDEO_CAPS) + ); + +static GstStaticPadTemplate video_sink_template = +GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (GST_RSVG_VIDEO_CAPS) + ); + +static GstStaticPadTemplate data_sink_template = + GST_STATIC_PAD_TEMPLATE ("data_sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("image/svg+xml; image/svg; text/plain")); + +GST_BOILERPLATE (GstRsvgOverlay, gst_rsvg_overlay, GstVideoFilter, + GST_TYPE_VIDEO_FILTER); + +static void +gst_rsvg_overlay_set_svg_data (GstRsvgOverlay * overlay, const gchar * data, + gboolean consider_as_filename) +{ + GstBaseTransform *btrans = GST_BASE_TRANSFORM (overlay); + gsize size; + GError *error = NULL; + + if (overlay->handle) { + g_object_unref (overlay->handle); + overlay->handle = NULL; + gst_base_transform_set_passthrough (btrans, TRUE); + } + + /* data may be NULL */ + if (data) { + size = strlen (data); + if (size) { + /* Read data either from string or from file */ + if (consider_as_filename) + overlay->handle = rsvg_handle_new_from_file (data, &error); + else + overlay->handle = + rsvg_handle_new_from_data ((guint8 *) data, size, &error); + if (error) { + GST_ERROR_OBJECT (overlay, "Cannot read SVG data: %s\n%s", + error->message, data); + g_error_free (error); + } else { + /* Get SVG dimension. */ + RsvgDimensionData svg_dimension; + rsvg_handle_get_dimensions (overlay->handle, &svg_dimension); + overlay->width = svg_dimension.width; + overlay->height = svg_dimension.height; + gst_base_transform_set_passthrough (btrans, FALSE); + } + } + } +} + +static void +gst_rsvg_overlay_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstRsvgOverlay *overlay = GST_RSVG_OVERLAY (object); + + GST_RSVG_LOCK (overlay); + + switch (prop_id) { + case PROP_DATA: + { + gst_rsvg_overlay_set_svg_data (overlay, g_value_get_string (value), + FALSE); + break; + } + case PROP_FILENAME: + { + gst_rsvg_overlay_set_svg_data (overlay, g_value_get_string (value), TRUE); + break; + } + case PROP_FIT_TO_FRAME: + { + overlay->fit_to_frame = g_value_get_boolean (value); + break; + } + case PROP_X: + { + overlay->x_offset = g_value_get_int (value); + break; + } + case PROP_Y: + { + overlay->y_offset = g_value_get_int (value); + break; + } + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + } + + GST_RSVG_UNLOCK (overlay); +} + +static void +gst_rsvg_overlay_get_property (GObject * object, guint prop_id, GValue * value, + GParamSpec * pspec) +{ + GstRsvgOverlay *overlay = GST_RSVG_OVERLAY (object); + + switch (prop_id) { + case PROP_X: + g_value_set_uint (value, overlay->x_offset); + break; + case PROP_Y: + g_value_set_uint (value, overlay->y_offset); + break; + case PROP_FIT_TO_FRAME: + g_value_set_boolean (value, overlay->fit_to_frame); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GstFlowReturn +gst_rsvg_overlay_data_sink_chain (GstPad * pad, GstBuffer * buffer) +{ + GstRsvgOverlay *overlay = GST_RSVG_OVERLAY (GST_PAD_PARENT (pad)); + + gst_adapter_push (overlay->adapter, buffer); + return GST_FLOW_OK; +} + +static gboolean +gst_rsvg_overlay_data_sink_event (GstPad * pad, GstEvent * event) +{ + GstRsvgOverlay *overlay = GST_RSVG_OVERLAY (GST_PAD_PARENT (pad)); + + GST_LOG_OBJECT (pad, "Got %s event", GST_EVENT_TYPE_NAME (event)); + + switch (GST_EVENT_TYPE (event)) { + + case GST_EVENT_EOS:{ + guint data_size; + + GST_RSVG_LOCK (overlay); + /* FIXME: rsvgdec looks for in data to determine the end + of SVG code. Should we really do the same here? IOW, could + data be sent and _pushed() before the EOS gets processed? */ + data_size = gst_adapter_available (overlay->adapter); + if (data_size) { + gst_rsvg_overlay_set_svg_data (overlay, + (const gchar *) gst_adapter_take (overlay->adapter, data_size), + FALSE); + gst_adapter_clear (overlay->adapter); + } + GST_RSVG_UNLOCK (overlay); + } + + case GST_EVENT_FLUSH_START: + gst_adapter_clear (overlay->adapter); + break; + + default: + break; + } + + /* Dropping all events here */ + gst_event_unref (event); + return TRUE; +} + +static gboolean +gst_rsvg_overlay_set_caps (GstBaseTransform * btrans, GstCaps * incaps, + GstCaps * outcaps) +{ + GstRsvgOverlay *overlay = GST_RSVG_OVERLAY (btrans); + + return G_LIKELY (gst_video_format_parse_caps (incaps, + &overlay->caps_format, &overlay->caps_width, &overlay->caps_height)); +} + +static GstFlowReturn +gst_rsvg_overlay_transform_ip (GstBaseTransform * btrans, GstBuffer * buf) +{ + GstRsvgOverlay *overlay = GST_RSVG_OVERLAY (btrans); + cairo_surface_t *surface; + cairo_t *cr; + + GST_RSVG_LOCK (overlay); + if (!overlay->handle) { + GST_RSVG_UNLOCK (overlay); + return GST_FLOW_OK; + } + + surface = + cairo_image_surface_create_for_data (GST_BUFFER_DATA (buf), + CAIRO_FORMAT_ARGB32, overlay->caps_width, overlay->caps_height, + overlay->caps_width * 4); + if (G_UNLIKELY (!surface)) + return GST_FLOW_ERROR; + + cr = cairo_create (surface); + if (G_UNLIKELY (!cr)) { + cairo_surface_destroy (surface); + return GST_FLOW_ERROR; + } + + /* If x or y offset is specified, do not fit-to-frame. */ + if (overlay->x_offset || overlay->y_offset) + cairo_translate (cr, (double) overlay->x_offset, + (double) overlay->y_offset); + else if (overlay->fit_to_frame && overlay->width && overlay->height) + cairo_scale (cr, (float) overlay->caps_width / overlay->width, + (float) overlay->caps_height / overlay->height); + + rsvg_handle_render_cairo (overlay->handle, cr); + GST_RSVG_UNLOCK (overlay); + + cairo_destroy (cr); + cairo_surface_destroy (surface); + + return GST_FLOW_OK; +} + +static gboolean +gst_rsvg_overlay_stop (GstBaseTransform * btrans) +{ + GstRsvgOverlay *overlay = GST_RSVG_OVERLAY (btrans); + + if (overlay->handle) { + g_object_unref (overlay->handle); + g_object_unref (overlay->adapter); + overlay->handle = NULL; + } + + return TRUE; +} + +static void +gst_rsvg_overlay_base_init (gpointer klass) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&src_template)); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&video_sink_template)); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&data_sink_template)); + + gst_element_class_set_details_simple (element_class, "RSVG overlay", + "Filter/Editor/Video", + "Overlays SVG graphics over a video stream", + "Olivier Aubert "); +} + +static void +gst_rsvg_overlay_class_init (GstRsvgOverlayClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + GstBaseTransformClass *basetransform_class = GST_BASE_TRANSFORM_CLASS (klass); + + gobject_class->set_property = gst_rsvg_overlay_set_property; + gobject_class->get_property = gst_rsvg_overlay_get_property; + + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DATA, + g_param_spec_string ("data", "data", "SVG data.", "", + G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_FILENAME, + g_param_spec_string ("location", "location", "SVG file location.", "", + G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_FIT_TO_FRAME, + g_param_spec_boolean ("fit-to-frame", "fit to frame", + "Fit the SVG to fill the whole frame.", TRUE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_X, + g_param_spec_int ("x", "x offset", + "Specify an x offset.", 0, G_MAXINT, 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_Y, + g_param_spec_int ("y", "y offset", + "Specify a y offset.", 0, G_MAXINT, 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + basetransform_class->set_caps = gst_rsvg_overlay_set_caps; + basetransform_class->transform_ip = gst_rsvg_overlay_transform_ip; + basetransform_class->stop = gst_rsvg_overlay_stop; + basetransform_class->passthrough_on_same_caps = FALSE; +} + +static void +gst_rsvg_overlay_init (GstRsvgOverlay * overlay, GstRsvgOverlayClass * klass) +{ + overlay->fit_to_frame = 1; + overlay->adapter = gst_adapter_new (); + + /* data sink */ + overlay->data_sinkpad = + gst_pad_new_from_static_template (&data_sink_template, "data_sink"); + gst_pad_set_chain_function (overlay->data_sinkpad, + GST_DEBUG_FUNCPTR (gst_rsvg_overlay_data_sink_chain)); + gst_pad_set_event_function (overlay->data_sinkpad, + GST_DEBUG_FUNCPTR (gst_rsvg_overlay_data_sink_event)); + gst_element_add_pad (GST_ELEMENT (overlay), overlay->data_sinkpad); +} diff --git a/ext/rsvg/gstrsvgoverlay.h b/ext/rsvg/gstrsvgoverlay.h new file mode 100644 index 000000000..a0125444c --- /dev/null +++ b/ext/rsvg/gstrsvgoverlay.h @@ -0,0 +1,77 @@ +/* GStreamer + * Copyright (C) 2010 Olivier Aubert + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_RSVG_OVERLAY_H__ +#define __GST_RSVG_OVERLAY_H__ + +#include +#include +#include +#include +#include + +G_BEGIN_DECLS +#define GST_TYPE_RSVG_OVERLAY (gst_rsvg_overlay_get_type()) +#define GST_RSVG_OVERLAY(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_RSVG_OVERLAY,GstRsvgOverlay)) +#define GST_RSVG_OVERLAY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_RSVG_OVERLAY,GstRsvgOverlayClass)) +#define GST_IS_RSVG_OVERLAY(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_RSVG_OVERLAY)) +#define GST_IS_RSVG_OVERLAY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_RSVG_OVERLAY)) +typedef struct _GstRsvgOverlay GstRsvgOverlay; +typedef struct _GstRsvgOverlayClass GstRsvgOverlayClass; + +/** + * GstRsvgOverlay: + * + * Opaque object data structure. + */ +struct _GstRsvgOverlay +{ + GstVideoFilter element; + + /* < private > */ + GStaticMutex rsvg_lock; + + RsvgHandle *handle; + + /* width and height of the SVG data */ + int width; + int height; + /* Should we fit the SVG to the dimension ? */ + gboolean fit_to_frame; + + int x_offset; + int y_offset; + + GstVideoFormat caps_format; + int caps_width; + int caps_height; + + GstPad *data_sinkpad; + GstAdapter *adapter; +}; + +struct _GstRsvgOverlayClass +{ + GstVideoFilterClass parent_class; +}; + +GType gst_rsvg_overlay_get_type (void); + +G_END_DECLS +#endif /* __GST_RSVG_OVERLAY_H__ */ -- cgit v1.2.1