2 * Copyright (C) <2011> Jon Nordby <jononor@gmail.com>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
21 * SECTION:element-cairooverlay
22 * @title: cairooverlay
24 * cairooverlay renders an overlay using a application provided render function.
26 * The full example can be found in tests/examples/cairo/cairo_overlay.c
31 * #include <gst/gst.h>
32 * #include <gst/video/video.h>
40 * } CairoOverlayState;
45 * prepare_overlay (GstElement * overlay, GstCaps * caps, gpointer user_data)
47 * CairoOverlayState *state = (CairoOverlayState *)user_data;
49 * gst_video_format_parse_caps (caps, NULL, &state->width, &state->height);
50 * state->valid = TRUE;
54 * draw_overlay (GstElement * overlay, cairo_t * cr, guint64 timestamp,
55 * guint64 duration, gpointer user_data)
57 * CairoOverlayState *s = (CairoOverlayState *)user_data;
63 * scale = 2*(((timestamp/(int)1e7) % 70)+30)/100.0;
64 * cairo_translate(cr, s->width/2, (s->height/2)-30);
65 * cairo_scale (cr, scale, scale);
67 * cairo_move_to (cr, 0, 0);
68 * cairo_curve_to (cr, 0,-30, -50,-30, -50,0);
69 * cairo_curve_to (cr, -50,30, 0,35, 0,60 );
70 * cairo_curve_to (cr, 0,35, 50,30, 50,0 ); *
71 * cairo_curve_to (cr, 50,-30, 0,-30, 0,0 );
72 * cairo_set_source_rgba (cr, 0.9, 0.0, 0.1, 0.7);
78 * cairo_overlay = gst_element_factory_make ("cairooverlay", "overlay");
80 * g_signal_connect (cairo_overlay, "draw", G_CALLBACK (draw_overlay),
82 * g_signal_connect (cairo_overlay, "caps-changed",
83 * G_CALLBACK (prepare_overlay), overlay_state);
94 #include "gstcairooverlay.h"
96 #include <gst/video/video.h>
100 /* RGB16 is native-endianness in GStreamer */
101 #if G_BYTE_ORDER == G_LITTLE_ENDIAN
102 #define TEMPLATE_CAPS GST_VIDEO_CAPS_MAKE("{ BGRx, BGRA, RGB16 }")
104 #define TEMPLATE_CAPS GST_VIDEO_CAPS_MAKE("{ xRGB, ARGB, RGB16 }")
107 GST_DEBUG_CATEGORY (cairo_debug);
109 static GstStaticPadTemplate gst_cairo_overlay_src_template =
110 GST_STATIC_PAD_TEMPLATE ("src",
113 GST_STATIC_CAPS (TEMPLATE_CAPS)
116 static GstStaticPadTemplate gst_cairo_overlay_sink_template =
117 GST_STATIC_PAD_TEMPLATE ("sink",
120 GST_STATIC_CAPS (TEMPLATE_CAPS)
123 G_DEFINE_TYPE (GstCairoOverlay, gst_cairo_overlay, GST_TYPE_BASE_TRANSFORM);
124 GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (cairooverlay, "cairooverlay",
125 GST_RANK_NONE, GST_TYPE_CAIRO_OVERLAY, GST_DEBUG_CATEGORY_INIT (cairo_debug,
126 "cairo", 0, "Cairo elements"););
130 PROP_DRAW_ON_TRANSPARENT_SURFACE,
133 #define DEFAULT_DRAW_ON_TRANSPARENT_SURFACE (FALSE)
142 static guint gst_cairo_overlay_signals[N_SIGNALS];
145 gst_cairo_overlay_set_property (GObject * object, guint property_id,
146 const GValue * value, GParamSpec * pspec)
148 GstCairoOverlay *overlay = GST_CAIRO_OVERLAY (object);
150 GST_OBJECT_LOCK (overlay);
152 switch (property_id) {
153 case PROP_DRAW_ON_TRANSPARENT_SURFACE:
154 overlay->draw_on_transparent_surface = g_value_get_boolean (value);
157 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
161 GST_OBJECT_UNLOCK (overlay);
165 gst_cairo_overlay_get_property (GObject * object, guint property_id,
166 GValue * value, GParamSpec * pspec)
168 GstCairoOverlay *overlay = GST_CAIRO_OVERLAY (object);
170 GST_OBJECT_LOCK (overlay);
172 switch (property_id) {
173 case PROP_DRAW_ON_TRANSPARENT_SURFACE:
174 g_value_set_boolean (value, overlay->draw_on_transparent_surface);
177 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
181 GST_OBJECT_UNLOCK (overlay);
185 gst_cairo_overlay_query (GstBaseTransform * trans, GstPadDirection direction,
188 GstCairoOverlay *overlay = GST_CAIRO_OVERLAY (trans);
190 switch (GST_QUERY_TYPE (query)) {
191 case GST_QUERY_ALLOCATION:
193 /* We're always running in passthrough mode, which means that
194 * basetransform just passes through ALLOCATION queries and
195 * never ever calls BaseTransform::decide_allocation().
197 * We hook into the query handling for that reason
199 overlay->attach_compo_to_buffer = FALSE;
201 if (!GST_BASE_TRANSFORM_CLASS (gst_cairo_overlay_parent_class)->query
202 (trans, direction, query)) {
206 overlay->attach_compo_to_buffer = gst_query_find_allocation_meta (query,
207 GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE, NULL);
213 GST_BASE_TRANSFORM_CLASS (gst_cairo_overlay_parent_class)->query
214 (trans, direction, query);
219 gst_cairo_overlay_set_caps (GstBaseTransform * trans, GstCaps * in_caps,
222 GstCairoOverlay *overlay = GST_CAIRO_OVERLAY (trans);
224 if (!gst_video_info_from_caps (&overlay->info, in_caps))
227 g_signal_emit (overlay, gst_cairo_overlay_signals[SIGNAL_CAPS_CHANGED], 0,
233 /* Copy from video-overlay-composition.c */
235 gst_video_overlay_rectangle_premultiply_0 (GstVideoFrame * frame)
238 int width = GST_VIDEO_FRAME_WIDTH (frame);
239 int height = GST_VIDEO_FRAME_HEIGHT (frame);
240 int stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0);
241 guint8 *data = GST_VIDEO_FRAME_PLANE_DATA (frame, 0);
243 for (j = 0; j < height; ++j) {
248 for (i = 0; i < width; ++i) {
250 line[1] = line[1] * a / 255;
251 line[2] = line[2] * a / 255;
252 line[3] = line[3] * a / 255;
258 /* Copy from video-overlay-composition.c */
260 gst_video_overlay_rectangle_premultiply_3 (GstVideoFrame * frame)
263 int width = GST_VIDEO_FRAME_WIDTH (frame);
264 int height = GST_VIDEO_FRAME_HEIGHT (frame);
265 int stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0);
266 guint8 *data = GST_VIDEO_FRAME_PLANE_DATA (frame, 0);
268 for (j = 0; j < height; ++j) {
273 for (i = 0; i < width; ++i) {
275 line[0] = line[0] * a / 255;
276 line[1] = line[1] * a / 255;
277 line[2] = line[2] * a / 255;
283 /* Copy from video-overlay-composition.c */
285 gst_video_overlay_rectangle_premultiply (GstVideoFrame * frame)
289 alpha_offset = GST_VIDEO_FRAME_COMP_POFFSET (frame, 3);
290 switch (alpha_offset) {
292 gst_video_overlay_rectangle_premultiply_0 (frame);
295 gst_video_overlay_rectangle_premultiply_3 (frame);
298 g_assert_not_reached ();
303 /* Copy from video-overlay-composition.c */
305 gst_video_overlay_rectangle_unpremultiply_0 (GstVideoFrame * frame)
308 int width = GST_VIDEO_FRAME_WIDTH (frame);
309 int height = GST_VIDEO_FRAME_HEIGHT (frame);
310 int stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0);
311 guint8 *data = GST_VIDEO_FRAME_PLANE_DATA (frame, 0);
313 for (j = 0; j < height; ++j) {
318 for (i = 0; i < width; ++i) {
321 line[1] = MIN ((line[1] * 255 + a / 2) / a, 255);
322 line[2] = MIN ((line[2] * 255 + a / 2) / a, 255);
323 line[3] = MIN ((line[3] * 255 + a / 2) / a, 255);
330 /* Copy from video-overlay-composition.c */
332 gst_video_overlay_rectangle_unpremultiply_3 (GstVideoFrame * frame)
335 int width = GST_VIDEO_FRAME_WIDTH (frame);
336 int height = GST_VIDEO_FRAME_HEIGHT (frame);
337 int stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0);
338 guint8 *data = GST_VIDEO_FRAME_PLANE_DATA (frame, 0);
340 for (j = 0; j < height; ++j) {
345 for (i = 0; i < width; ++i) {
348 line[0] = MIN ((line[0] * 255 + a / 2) / a, 255);
349 line[1] = MIN ((line[1] * 255 + a / 2) / a, 255);
350 line[2] = MIN ((line[2] * 255 + a / 2) / a, 255);
357 /* Copy from video-overlay-composition.c */
359 gst_video_overlay_rectangle_unpremultiply (GstVideoFrame * frame)
363 alpha_offset = GST_VIDEO_FRAME_COMP_POFFSET (frame, 3);
364 switch (alpha_offset) {
366 gst_video_overlay_rectangle_unpremultiply_0 (frame);
369 gst_video_overlay_rectangle_unpremultiply_3 (frame);
372 g_assert_not_reached ();
378 gst_cairo_overlay_transform_ip (GstBaseTransform * trans, GstBuffer * buf)
380 GstCairoOverlay *overlay = GST_CAIRO_OVERLAY (trans);
382 cairo_surface_t *surface;
384 cairo_format_t format;
385 gboolean draw_on_transparent_surface = overlay->draw_on_transparent_surface;
387 switch (GST_VIDEO_INFO_FORMAT (&overlay->info)) {
388 case GST_VIDEO_FORMAT_ARGB:
389 case GST_VIDEO_FORMAT_BGRA:
390 format = CAIRO_FORMAT_ARGB32;
392 case GST_VIDEO_FORMAT_xRGB:
393 case GST_VIDEO_FORMAT_BGRx:
394 format = CAIRO_FORMAT_RGB24;
396 case GST_VIDEO_FORMAT_RGB16:
397 format = CAIRO_FORMAT_RGB16_565;
401 GST_WARNING ("No matching cairo format for %s",
402 gst_video_format_to_string (GST_VIDEO_INFO_FORMAT (&overlay->info)));
403 return GST_FLOW_ERROR;
407 /* If we need to map the buffer writable, do so */
408 if (!draw_on_transparent_surface || !overlay->attach_compo_to_buffer) {
409 if (!gst_video_frame_map (&frame, &overlay->info, buf, GST_MAP_READWRITE)) {
410 return GST_FLOW_ERROR;
416 if (draw_on_transparent_surface) {
418 cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
419 GST_VIDEO_INFO_WIDTH (&overlay->info),
420 GST_VIDEO_INFO_HEIGHT (&overlay->info));
422 if (format == CAIRO_FORMAT_ARGB32)
423 gst_video_overlay_rectangle_premultiply (&frame);
426 cairo_image_surface_create_for_data (GST_VIDEO_FRAME_PLANE_DATA (&frame,
427 0), format, GST_VIDEO_FRAME_WIDTH (&frame),
428 GST_VIDEO_FRAME_HEIGHT (&frame), GST_VIDEO_FRAME_PLANE_STRIDE (&frame,
432 if (G_UNLIKELY (!surface))
433 return GST_FLOW_ERROR;
435 cr = cairo_create (surface);
436 if (G_UNLIKELY (!cr)) {
437 cairo_surface_destroy (surface);
438 return GST_FLOW_ERROR;
441 g_signal_emit (overlay, gst_cairo_overlay_signals[SIGNAL_DRAW], 0,
442 cr, GST_BUFFER_PTS (buf), GST_BUFFER_DURATION (buf), NULL);
446 if (draw_on_transparent_surface) {
448 GstBuffer *surface_buffer;
449 GstVideoOverlayRectangle *rect;
450 GstVideoOverlayComposition *composition;
451 gsize offset[GST_VIDEO_MAX_PLANES] = { 0, };
452 gint stride[GST_VIDEO_MAX_PLANES] = { 0, };
455 cairo_image_surface_get_height (surface) *
456 cairo_image_surface_get_stride (surface);
457 stride[0] = cairo_image_surface_get_stride (surface);
459 /* Create a GstVideoOverlayComposition for blending, this handles
460 * pre-multiplied alpha correctly */
462 gst_buffer_new_wrapped_full (0, cairo_image_surface_get_data (surface),
463 size, 0, size, surface, (GDestroyNotify) cairo_surface_destroy);
464 gst_buffer_add_video_meta_full (surface_buffer, GST_VIDEO_FRAME_FLAG_NONE,
466 G_LITTLE_ENDIAN ? GST_VIDEO_FORMAT_BGRA : GST_VIDEO_FORMAT_ARGB),
467 GST_VIDEO_INFO_WIDTH (&overlay->info),
468 GST_VIDEO_INFO_HEIGHT (&overlay->info), 1, offset, stride);
470 gst_video_overlay_rectangle_new_raw (surface_buffer, 0, 0,
471 GST_VIDEO_INFO_WIDTH (&overlay->info),
472 GST_VIDEO_INFO_HEIGHT (&overlay->info),
473 GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA);
474 gst_buffer_unref (surface_buffer);
476 if (overlay->attach_compo_to_buffer) {
477 GstVideoOverlayCompositionMeta *composition_meta;
479 composition_meta = gst_buffer_get_video_overlay_composition_meta (buf);
480 if (composition_meta) {
481 GstVideoOverlayComposition *merged_composition =
482 gst_video_overlay_composition_copy (composition_meta->overlay);
483 gst_video_overlay_composition_add_rectangle (merged_composition, rect);
484 gst_video_overlay_composition_unref (composition_meta->overlay);
485 composition_meta->overlay = merged_composition;
486 gst_video_overlay_rectangle_unref (rect);
488 composition = gst_video_overlay_composition_new (rect);
489 gst_video_overlay_rectangle_unref (rect);
490 gst_buffer_add_video_overlay_composition_meta (buf, composition);
491 gst_video_overlay_composition_unref (composition);
494 composition = gst_video_overlay_composition_new (rect);
495 gst_video_overlay_rectangle_unref (rect);
496 gst_video_overlay_composition_blend (composition, &frame);
497 gst_video_overlay_composition_unref (composition);
500 cairo_surface_destroy (surface);
501 if (format == CAIRO_FORMAT_ARGB32)
502 gst_video_overlay_rectangle_unpremultiply (&frame);
506 gst_video_frame_unmap (&frame);
513 gst_cairo_overlay_class_init (GstCairoOverlayClass * klass)
515 GstBaseTransformClass *btrans_class;
516 GstElementClass *element_class;
517 GObjectClass *gobject_class;
519 btrans_class = (GstBaseTransformClass *) klass;
520 element_class = (GstElementClass *) klass;
521 gobject_class = (GObjectClass *) klass;
523 btrans_class->set_caps = gst_cairo_overlay_set_caps;
524 btrans_class->transform_ip = gst_cairo_overlay_transform_ip;
525 btrans_class->query = gst_cairo_overlay_query;
527 gobject_class->set_property = gst_cairo_overlay_set_property;
528 gobject_class->get_property = gst_cairo_overlay_get_property;
530 g_object_class_install_property (gobject_class,
531 PROP_DRAW_ON_TRANSPARENT_SURFACE,
532 g_param_spec_boolean ("draw-on-transparent-surface",
533 "Draw on transparent surface",
534 "Let the draw signal work on a transparent surface "
535 "and blend the results with the video at a later time",
536 DEFAULT_DRAW_ON_TRANSPARENT_SURFACE,
537 GST_PARAM_CONTROLLABLE | GST_PARAM_MUTABLE_PLAYING | G_PARAM_READWRITE
538 | G_PARAM_STATIC_STRINGS));
541 * GstCairoOverlay::draw:
542 * @overlay: Overlay element emitting the signal.
543 * @cr: Cairo context to draw to.
544 * @timestamp: Timestamp (see #GstClockTime) of the current buffer.
545 * @duration: Duration (see #GstClockTime) of the current buffer.
547 * This signal is emitted when the overlay should be drawn.
549 gst_cairo_overlay_signals[SIGNAL_DRAW] =
550 g_signal_new ("draw",
551 G_TYPE_FROM_CLASS (klass),
552 0, 0, NULL, NULL, NULL,
553 G_TYPE_NONE, 3, CAIRO_GOBJECT_TYPE_CONTEXT, G_TYPE_UINT64, G_TYPE_UINT64);
556 * GstCairoOverlay::caps-changed:
557 * @overlay: Overlay element emitting the signal.
558 * @caps: The #GstCaps of the element.
560 * This signal is emitted when the caps of the element has changed.
562 gst_cairo_overlay_signals[SIGNAL_CAPS_CHANGED] =
563 g_signal_new ("caps-changed",
564 G_TYPE_FROM_CLASS (klass),
565 0, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_CAPS);
567 gst_element_class_set_static_metadata (element_class, "Cairo overlay",
568 "Filter/Editor/Video",
569 "Render overlay on a video stream using Cairo",
570 "Jon Nordby <jononor@gmail.com>");
572 gst_element_class_add_static_pad_template (element_class,
573 &gst_cairo_overlay_sink_template);
574 gst_element_class_add_static_pad_template (element_class,
575 &gst_cairo_overlay_src_template);
579 gst_cairo_overlay_init (GstCairoOverlay * overlay)