gstreamer/subprojects/gst-plugins-good/ext/cairo/gstcairooverlay.c

583 lines
17 KiB
C

/* GStreamer
* Copyright (C) <2011> Jon Nordby <jononor@gmail.com>
*
* 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., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
/**
* SECTION:element-cairooverlay
* @title: cairooverlay
*
* cairooverlay renders an overlay using a application provided render function.
*
* The full example can be found in tests/examples/cairo/cairo_overlay.c
*
* ## Example code
* |[
*
* #include <gst/gst.h>
* #include <gst/video/video.h>
*
* ...
*
* typedef struct {
* gboolean valid;
* int width;
* int height;
* } CairoOverlayState;
*
* ...
*
* static void
* prepare_overlay (GstElement * overlay, GstCaps * caps, gpointer user_data)
* {
* CairoOverlayState *state = (CairoOverlayState *)user_data;
*
* gst_video_format_parse_caps (caps, NULL, &state->width, &state->height);
* state->valid = TRUE;
* }
*
* static void
* draw_overlay (GstElement * overlay, cairo_t * cr, guint64 timestamp,
* guint64 duration, gpointer user_data)
* {
* CairoOverlayState *s = (CairoOverlayState *)user_data;
* double scale;
*
* if (!s->valid)
* return;
*
* scale = 2*(((timestamp/(int)1e7) % 70)+30)/100.0;
* cairo_translate(cr, s->width/2, (s->height/2)-30);
* cairo_scale (cr, scale, scale);
*
* cairo_move_to (cr, 0, 0);
* cairo_curve_to (cr, 0,-30, -50,-30, -50,0);
* cairo_curve_to (cr, -50,30, 0,35, 0,60 );
* cairo_curve_to (cr, 0,35, 50,30, 50,0 ); *
* cairo_curve_to (cr, 50,-30, 0,-30, 0,0 );
* cairo_set_source_rgba (cr, 0.9, 0.0, 0.1, 0.7);
* cairo_fill (cr);
* }
*
* ...
*
* cairo_overlay = gst_element_factory_make (&quot;cairooverlay&quot;, &quot;overlay&quot;);
*
* g_signal_connect (cairo_overlay, &quot;draw&quot;, G_CALLBACK (draw_overlay),
* overlay_state);
* g_signal_connect (cairo_overlay, &quot;caps-changed&quot;,
* G_CALLBACK (prepare_overlay), overlay_state);
* ...
*
* ]|
*
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstcairooverlay.h"
#include <gst/video/video.h>
#include <cairo.h>
/* RGB16 is native-endianness in GStreamer */
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
#define TEMPLATE_CAPS GST_VIDEO_CAPS_MAKE("{ BGRx, BGRA, RGB16 }")
#else
#define TEMPLATE_CAPS GST_VIDEO_CAPS_MAKE("{ xRGB, ARGB, RGB16 }")
#endif
GST_DEBUG_CATEGORY (cairo_debug);
static GstStaticPadTemplate gst_cairo_overlay_src_template =
GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS (TEMPLATE_CAPS)
);
static GstStaticPadTemplate gst_cairo_overlay_sink_template =
GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS (TEMPLATE_CAPS)
);
G_DEFINE_TYPE (GstCairoOverlay, gst_cairo_overlay, GST_TYPE_BASE_TRANSFORM);
GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (cairooverlay, "cairooverlay",
GST_RANK_NONE, GST_TYPE_CAIRO_OVERLAY, GST_DEBUG_CATEGORY_INIT (cairo_debug,
"cairo", 0, "Cairo elements"););
enum
{
PROP_0,
PROP_DRAW_ON_TRANSPARENT_SURFACE,
};
#define DEFAULT_DRAW_ON_TRANSPARENT_SURFACE (FALSE)
enum
{
SIGNAL_DRAW,
SIGNAL_CAPS_CHANGED,
N_SIGNALS
};
static guint gst_cairo_overlay_signals[N_SIGNALS];
static void
gst_cairo_overlay_set_property (GObject * object, guint property_id,
const GValue * value, GParamSpec * pspec)
{
GstCairoOverlay *overlay = GST_CAIRO_OVERLAY (object);
GST_OBJECT_LOCK (overlay);
switch (property_id) {
case PROP_DRAW_ON_TRANSPARENT_SURFACE:
overlay->draw_on_transparent_surface = g_value_get_boolean (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
GST_OBJECT_UNLOCK (overlay);
}
static void
gst_cairo_overlay_get_property (GObject * object, guint property_id,
GValue * value, GParamSpec * pspec)
{
GstCairoOverlay *overlay = GST_CAIRO_OVERLAY (object);
GST_OBJECT_LOCK (overlay);
switch (property_id) {
case PROP_DRAW_ON_TRANSPARENT_SURFACE:
g_value_set_boolean (value, overlay->draw_on_transparent_surface);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
GST_OBJECT_UNLOCK (overlay);
}
static gboolean
gst_cairo_overlay_query (GstBaseTransform * trans, GstPadDirection direction,
GstQuery * query)
{
GstCairoOverlay *overlay = GST_CAIRO_OVERLAY (trans);
switch (GST_QUERY_TYPE (query)) {
case GST_QUERY_ALLOCATION:
{
/* We're always running in passthrough mode, which means that
* basetransform just passes through ALLOCATION queries and
* never ever calls BaseTransform::decide_allocation().
*
* We hook into the query handling for that reason
*/
overlay->attach_compo_to_buffer = FALSE;
if (!GST_BASE_TRANSFORM_CLASS (gst_cairo_overlay_parent_class)->query
(trans, direction, query)) {
return FALSE;
}
overlay->attach_compo_to_buffer = gst_query_find_allocation_meta (query,
GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE, NULL);
return TRUE;
}
default:
return
GST_BASE_TRANSFORM_CLASS (gst_cairo_overlay_parent_class)->query
(trans, direction, query);
}
}
static gboolean
gst_cairo_overlay_set_caps (GstBaseTransform * trans, GstCaps * in_caps,
GstCaps * out_caps)
{
GstCairoOverlay *overlay = GST_CAIRO_OVERLAY (trans);
if (!gst_video_info_from_caps (&overlay->info, in_caps))
return FALSE;
g_signal_emit (overlay, gst_cairo_overlay_signals[SIGNAL_CAPS_CHANGED], 0,
in_caps, NULL);
return TRUE;
}
/* Copy from video-overlay-composition.c */
static void
gst_video_overlay_rectangle_premultiply_0 (GstVideoFrame * frame)
{
int i, j;
int width = GST_VIDEO_FRAME_WIDTH (frame);
int height = GST_VIDEO_FRAME_HEIGHT (frame);
int stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0);
guint8 *data = GST_VIDEO_FRAME_PLANE_DATA (frame, 0);
for (j = 0; j < height; ++j) {
guint8 *line;
line = data;
line += stride * j;
for (i = 0; i < width; ++i) {
int a = line[0];
line[1] = line[1] * a / 255;
line[2] = line[2] * a / 255;
line[3] = line[3] * a / 255;
line += 4;
}
}
}
/* Copy from video-overlay-composition.c */
static void
gst_video_overlay_rectangle_premultiply_3 (GstVideoFrame * frame)
{
int i, j;
int width = GST_VIDEO_FRAME_WIDTH (frame);
int height = GST_VIDEO_FRAME_HEIGHT (frame);
int stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0);
guint8 *data = GST_VIDEO_FRAME_PLANE_DATA (frame, 0);
for (j = 0; j < height; ++j) {
guint8 *line;
line = data;
line += stride * j;
for (i = 0; i < width; ++i) {
int a = line[3];
line[0] = line[0] * a / 255;
line[1] = line[1] * a / 255;
line[2] = line[2] * a / 255;
line += 4;
}
}
}
/* Copy from video-overlay-composition.c */
static void
gst_video_overlay_rectangle_premultiply (GstVideoFrame * frame)
{
gint alpha_offset;
alpha_offset = GST_VIDEO_FRAME_COMP_POFFSET (frame, 3);
switch (alpha_offset) {
case 0:
gst_video_overlay_rectangle_premultiply_0 (frame);
break;
case 3:
gst_video_overlay_rectangle_premultiply_3 (frame);
break;
default:
g_assert_not_reached ();
break;
}
}
/* Copy from video-overlay-composition.c */
static void
gst_video_overlay_rectangle_unpremultiply_0 (GstVideoFrame * frame)
{
int i, j;
int width = GST_VIDEO_FRAME_WIDTH (frame);
int height = GST_VIDEO_FRAME_HEIGHT (frame);
int stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0);
guint8 *data = GST_VIDEO_FRAME_PLANE_DATA (frame, 0);
for (j = 0; j < height; ++j) {
guint8 *line;
line = data;
line += stride * j;
for (i = 0; i < width; ++i) {
int a = line[0];
if (a) {
line[1] = MIN ((line[1] * 255 + a / 2) / a, 255);
line[2] = MIN ((line[2] * 255 + a / 2) / a, 255);
line[3] = MIN ((line[3] * 255 + a / 2) / a, 255);
}
line += 4;
}
}
}
/* Copy from video-overlay-composition.c */
static void
gst_video_overlay_rectangle_unpremultiply_3 (GstVideoFrame * frame)
{
int i, j;
int width = GST_VIDEO_FRAME_WIDTH (frame);
int height = GST_VIDEO_FRAME_HEIGHT (frame);
int stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0);
guint8 *data = GST_VIDEO_FRAME_PLANE_DATA (frame, 0);
for (j = 0; j < height; ++j) {
guint8 *line;
line = data;
line += stride * j;
for (i = 0; i < width; ++i) {
int a = line[3];
if (a) {
line[0] = MIN ((line[0] * 255 + a / 2) / a, 255);
line[1] = MIN ((line[1] * 255 + a / 2) / a, 255);
line[2] = MIN ((line[2] * 255 + a / 2) / a, 255);
}
line += 4;
}
}
}
/* Copy from video-overlay-composition.c */
static void
gst_video_overlay_rectangle_unpremultiply (GstVideoFrame * frame)
{
gint alpha_offset;
alpha_offset = GST_VIDEO_FRAME_COMP_POFFSET (frame, 3);
switch (alpha_offset) {
case 0:
gst_video_overlay_rectangle_unpremultiply_0 (frame);
break;
case 3:
gst_video_overlay_rectangle_unpremultiply_3 (frame);
break;
default:
g_assert_not_reached ();
break;
}
}
static GstFlowReturn
gst_cairo_overlay_transform_ip (GstBaseTransform * trans, GstBuffer * buf)
{
GstCairoOverlay *overlay = GST_CAIRO_OVERLAY (trans);
GstVideoFrame frame;
cairo_surface_t *surface;
cairo_t *cr;
cairo_format_t format;
gboolean draw_on_transparent_surface = overlay->draw_on_transparent_surface;
switch (GST_VIDEO_INFO_FORMAT (&overlay->info)) {
case GST_VIDEO_FORMAT_ARGB:
case GST_VIDEO_FORMAT_BGRA:
format = CAIRO_FORMAT_ARGB32;
break;
case GST_VIDEO_FORMAT_xRGB:
case GST_VIDEO_FORMAT_BGRx:
format = CAIRO_FORMAT_RGB24;
break;
case GST_VIDEO_FORMAT_RGB16:
format = CAIRO_FORMAT_RGB16_565;
break;
default:
{
GST_WARNING ("No matching cairo format for %s",
gst_video_format_to_string (GST_VIDEO_INFO_FORMAT (&overlay->info)));
return GST_FLOW_ERROR;
}
}
/* If we need to map the buffer writable, do so */
if (!draw_on_transparent_surface || !overlay->attach_compo_to_buffer) {
if (!gst_video_frame_map (&frame, &overlay->info, buf, GST_MAP_READWRITE)) {
return GST_FLOW_ERROR;
}
} else {
frame.buffer = NULL;
}
if (draw_on_transparent_surface) {
surface =
cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
GST_VIDEO_INFO_WIDTH (&overlay->info),
GST_VIDEO_INFO_HEIGHT (&overlay->info));
} else {
if (format == CAIRO_FORMAT_ARGB32)
gst_video_overlay_rectangle_premultiply (&frame);
surface =
cairo_image_surface_create_for_data (GST_VIDEO_FRAME_PLANE_DATA (&frame,
0), format, GST_VIDEO_FRAME_WIDTH (&frame),
GST_VIDEO_FRAME_HEIGHT (&frame), GST_VIDEO_FRAME_PLANE_STRIDE (&frame,
0));
}
if (G_UNLIKELY (!surface))
return GST_FLOW_ERROR;
cr = cairo_create (surface);
if (G_UNLIKELY (!cr)) {
cairo_surface_destroy (surface);
return GST_FLOW_ERROR;
}
g_signal_emit (overlay, gst_cairo_overlay_signals[SIGNAL_DRAW], 0,
cr, GST_BUFFER_PTS (buf), GST_BUFFER_DURATION (buf), NULL);
cairo_destroy (cr);
if (draw_on_transparent_surface) {
guint size;
GstBuffer *surface_buffer;
GstVideoOverlayRectangle *rect;
GstVideoOverlayComposition *composition;
gsize offset[GST_VIDEO_MAX_PLANES] = { 0, };
gint stride[GST_VIDEO_MAX_PLANES] = { 0, };
size =
cairo_image_surface_get_height (surface) *
cairo_image_surface_get_stride (surface);
stride[0] = cairo_image_surface_get_stride (surface);
/* Create a GstVideoOverlayComposition for blending, this handles
* pre-multiplied alpha correctly */
surface_buffer =
gst_buffer_new_wrapped_full (0, cairo_image_surface_get_data (surface),
size, 0, size, surface, (GDestroyNotify) cairo_surface_destroy);
gst_buffer_add_video_meta_full (surface_buffer, GST_VIDEO_FRAME_FLAG_NONE,
(G_BYTE_ORDER ==
G_LITTLE_ENDIAN ? GST_VIDEO_FORMAT_BGRA : GST_VIDEO_FORMAT_ARGB),
GST_VIDEO_INFO_WIDTH (&overlay->info),
GST_VIDEO_INFO_HEIGHT (&overlay->info), 1, offset, stride);
rect =
gst_video_overlay_rectangle_new_raw (surface_buffer, 0, 0,
GST_VIDEO_INFO_WIDTH (&overlay->info),
GST_VIDEO_INFO_HEIGHT (&overlay->info),
GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA);
gst_buffer_unref (surface_buffer);
if (overlay->attach_compo_to_buffer) {
GstVideoOverlayCompositionMeta *composition_meta;
composition_meta = gst_buffer_get_video_overlay_composition_meta (buf);
if (composition_meta) {
GstVideoOverlayComposition *merged_composition =
gst_video_overlay_composition_copy (composition_meta->overlay);
gst_video_overlay_composition_add_rectangle (merged_composition, rect);
gst_video_overlay_composition_unref (composition_meta->overlay);
composition_meta->overlay = merged_composition;
gst_video_overlay_rectangle_unref (rect);
} else {
composition = gst_video_overlay_composition_new (rect);
gst_video_overlay_rectangle_unref (rect);
gst_buffer_add_video_overlay_composition_meta (buf, composition);
gst_video_overlay_composition_unref (composition);
}
} else {
composition = gst_video_overlay_composition_new (rect);
gst_video_overlay_rectangle_unref (rect);
gst_video_overlay_composition_blend (composition, &frame);
gst_video_overlay_composition_unref (composition);
}
} else {
cairo_surface_destroy (surface);
if (format == CAIRO_FORMAT_ARGB32)
gst_video_overlay_rectangle_unpremultiply (&frame);
}
if (frame.buffer) {
gst_video_frame_unmap (&frame);
}
return GST_FLOW_OK;
}
static void
gst_cairo_overlay_class_init (GstCairoOverlayClass * klass)
{
GstBaseTransformClass *btrans_class;
GstElementClass *element_class;
GObjectClass *gobject_class;
btrans_class = (GstBaseTransformClass *) klass;
element_class = (GstElementClass *) klass;
gobject_class = (GObjectClass *) klass;
btrans_class->set_caps = gst_cairo_overlay_set_caps;
btrans_class->transform_ip = gst_cairo_overlay_transform_ip;
btrans_class->query = gst_cairo_overlay_query;
gobject_class->set_property = gst_cairo_overlay_set_property;
gobject_class->get_property = gst_cairo_overlay_get_property;
g_object_class_install_property (gobject_class,
PROP_DRAW_ON_TRANSPARENT_SURFACE,
g_param_spec_boolean ("draw-on-transparent-surface",
"Draw on transparent surface",
"Let the draw signal work on a transparent surface "
"and blend the results with the video at a later time",
DEFAULT_DRAW_ON_TRANSPARENT_SURFACE,
GST_PARAM_CONTROLLABLE | GST_PARAM_MUTABLE_PLAYING | G_PARAM_READWRITE
| G_PARAM_STATIC_STRINGS));
/**
* GstCairoOverlay::draw:
* @overlay: Overlay element emitting the signal.
* @cr: Cairo context to draw to.
* @timestamp: Timestamp (see #GstClockTime) of the current buffer.
* @duration: Duration (see #GstClockTime) of the current buffer.
*
* This signal is emitted when the overlay should be drawn.
*/
gst_cairo_overlay_signals[SIGNAL_DRAW] =
g_signal_new ("draw",
G_TYPE_FROM_CLASS (klass),
0, 0, NULL, NULL, NULL,
G_TYPE_NONE, 3, CAIRO_GOBJECT_TYPE_CONTEXT, G_TYPE_UINT64, G_TYPE_UINT64);
/**
* GstCairoOverlay::caps-changed:
* @overlay: Overlay element emitting the signal.
* @caps: The #GstCaps of the element.
*
* This signal is emitted when the caps of the element has changed.
*/
gst_cairo_overlay_signals[SIGNAL_CAPS_CHANGED] =
g_signal_new ("caps-changed",
G_TYPE_FROM_CLASS (klass),
0, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_CAPS);
gst_element_class_set_static_metadata (element_class, "Cairo overlay",
"Filter/Editor/Video",
"Render overlay on a video stream using Cairo",
"Jon Nordby <jononor@gmail.com>");
gst_element_class_add_static_pad_template (element_class,
&gst_cairo_overlay_sink_template);
gst_element_class_add_static_pad_template (element_class,
&gst_cairo_overlay_src_template);
}
static void
gst_cairo_overlay_init (GstCairoOverlay * overlay)
{
/* nothing to do */
}