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
23 * cairooverlay renders an overlay using a application provided render function.
25 * The full example can be found in tests/examples/cairo/cairo_overlay.c
27 * <title>Example code</title>
30 * #include <gst/gst.h>
31 * #include <gst/video/video.h>
39 * } CairoOverlayState;
44 * prepare_overlay (GstElement * overlay, GstCaps * caps, gpointer user_data)
46 * CairoOverlayState *state = (CairoOverlayState *)user_data;
48 * gst_video_format_parse_caps (caps, NULL, &state->width, &state->height);
49 * state->valid = TRUE;
53 * draw_overlay (GstElement * overlay, cairo_t * cr, guint64 timestamp,
54 * guint64 duration, gpointer user_data)
56 * CairoOverlayState *s = (CairoOverlayState *)user_data;
62 * scale = 2*(((timestamp/(int)1e7) % 70)+30)/100.0;
63 * cairo_translate(cr, s->width/2, (s->height/2)-30);
64 * cairo_scale (cr, scale, scale);
66 * cairo_move_to (cr, 0, 0);
67 * cairo_curve_to (cr, 0,-30, -50,-30, -50,0);
68 * cairo_curve_to (cr, -50,30, 0,35, 0,60 );
69 * cairo_curve_to (cr, 0,35, 50,30, 50,0 ); *
70 * cairo_curve_to (cr, 50,-30, 0,-30, 0,0 );
71 * cairo_set_source_rgba (cr, 0.9, 0.0, 0.1, 0.7);
77 * cairo_overlay = gst_element_factory_make ("cairooverlay", "overlay");
79 * g_signal_connect (cairo_overlay, "draw", G_CALLBACK (draw_overlay),
81 * g_signal_connect (cairo_overlay, "caps-changed",
82 * G_CALLBACK (prepare_overlay), overlay_state);
93 #include "gstcairooverlay.h"
95 #include <gst/video/video.h>
99 /* RGB16 is native-endianness in GStreamer */
100 #if G_BYTE_ORDER == G_LITTLE_ENDIAN
101 #define TEMPLATE_CAPS GST_VIDEO_CAPS_MAKE("{ BGRx, BGRA, RGB16 }")
103 #define TEMPLATE_CAPS GST_VIDEO_CAPS_MAKE("{ xRGB, ARGB, RGB16 }")
106 static GstStaticPadTemplate gst_cairo_overlay_src_template =
107 GST_STATIC_PAD_TEMPLATE ("src",
110 GST_STATIC_CAPS (TEMPLATE_CAPS)
113 static GstStaticPadTemplate gst_cairo_overlay_sink_template =
114 GST_STATIC_PAD_TEMPLATE ("sink",
117 GST_STATIC_CAPS (TEMPLATE_CAPS)
120 G_DEFINE_TYPE (GstCairoOverlay, gst_cairo_overlay, GST_TYPE_BASE_TRANSFORM);
125 PROP_DRAW_ON_TRANSPARENT_SURFACE,
128 #define DEFAULT_DRAW_ON_TRANSPARENT_SURFACE (FALSE)
137 static guint gst_cairo_overlay_signals[N_SIGNALS];
140 gst_cairo_overlay_set_property (GObject * object, guint property_id,
141 const GValue * value, GParamSpec * pspec)
143 GstCairoOverlay *overlay = GST_CAIRO_OVERLAY (object);
145 GST_OBJECT_LOCK (overlay);
147 switch (property_id) {
148 case PROP_DRAW_ON_TRANSPARENT_SURFACE:
149 overlay->draw_on_transparent_surface = g_value_get_boolean (value);
152 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
156 GST_OBJECT_UNLOCK (overlay);
160 gst_cairo_overlay_get_property (GObject * object, guint property_id,
161 GValue * value, GParamSpec * pspec)
163 GstCairoOverlay *overlay = GST_CAIRO_OVERLAY (object);
165 GST_OBJECT_LOCK (overlay);
167 switch (property_id) {
168 case PROP_DRAW_ON_TRANSPARENT_SURFACE:
169 g_value_set_boolean (value, overlay->draw_on_transparent_surface);
172 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
176 GST_OBJECT_UNLOCK (overlay);
180 gst_cairo_overlay_query (GstBaseTransform * trans, GstPadDirection direction,
183 GstCairoOverlay *overlay = GST_CAIRO_OVERLAY (trans);
185 switch (GST_QUERY_TYPE (query)) {
186 case GST_QUERY_ALLOCATION:
188 /* We're always running in passthrough mode, which means that
189 * basetransform just passes through ALLOCATION queries and
190 * never ever calls BaseTransform::decide_allocation().
192 * We hook into the query handling for that reason
194 overlay->attach_compo_to_buffer = FALSE;
196 if (!GST_BASE_TRANSFORM_CLASS (gst_cairo_overlay_parent_class)->query
197 (trans, direction, query)) {
201 overlay->attach_compo_to_buffer = gst_query_find_allocation_meta (query,
202 GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE, NULL);
208 GST_BASE_TRANSFORM_CLASS (gst_cairo_overlay_parent_class)->query
209 (trans, direction, query);
214 gst_cairo_overlay_set_caps (GstBaseTransform * trans, GstCaps * in_caps,
217 GstCairoOverlay *overlay = GST_CAIRO_OVERLAY (trans);
219 if (!gst_video_info_from_caps (&overlay->info, in_caps))
222 g_signal_emit (overlay, gst_cairo_overlay_signals[SIGNAL_CAPS_CHANGED], 0,
228 /* Copy from video-overlay-composition.c */
230 gst_video_overlay_rectangle_premultiply_0 (GstVideoFrame * frame)
233 int width = GST_VIDEO_FRAME_WIDTH (frame);
234 int height = GST_VIDEO_FRAME_HEIGHT (frame);
235 int stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0);
236 guint8 *data = GST_VIDEO_FRAME_PLANE_DATA (frame, 0);
238 for (j = 0; j < height; ++j) {
243 for (i = 0; i < width; ++i) {
245 line[1] = line[1] * a / 255;
246 line[2] = line[2] * a / 255;
247 line[3] = line[3] * a / 255;
253 /* Copy from video-overlay-composition.c */
255 gst_video_overlay_rectangle_premultiply_3 (GstVideoFrame * frame)
258 int width = GST_VIDEO_FRAME_WIDTH (frame);
259 int height = GST_VIDEO_FRAME_HEIGHT (frame);
260 int stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0);
261 guint8 *data = GST_VIDEO_FRAME_PLANE_DATA (frame, 0);
263 for (j = 0; j < height; ++j) {
268 for (i = 0; i < width; ++i) {
270 line[0] = line[0] * a / 255;
271 line[1] = line[1] * a / 255;
272 line[2] = line[2] * a / 255;
278 /* Copy from video-overlay-composition.c */
280 gst_video_overlay_rectangle_premultiply (GstVideoFrame * frame)
284 alpha_offset = GST_VIDEO_FRAME_COMP_POFFSET (frame, 3);
285 switch (alpha_offset) {
287 gst_video_overlay_rectangle_premultiply_0 (frame);
290 gst_video_overlay_rectangle_premultiply_3 (frame);
293 g_assert_not_reached ();
298 /* Copy from video-overlay-composition.c */
300 gst_video_overlay_rectangle_unpremultiply_0 (GstVideoFrame * frame)
303 int width = GST_VIDEO_FRAME_WIDTH (frame);
304 int height = GST_VIDEO_FRAME_HEIGHT (frame);
305 int stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0);
306 guint8 *data = GST_VIDEO_FRAME_PLANE_DATA (frame, 0);
308 for (j = 0; j < height; ++j) {
313 for (i = 0; i < width; ++i) {
316 line[1] = MIN ((line[1] * 255 + a / 2) / a, 255);
317 line[2] = MIN ((line[2] * 255 + a / 2) / a, 255);
318 line[3] = MIN ((line[3] * 255 + a / 2) / a, 255);
325 /* Copy from video-overlay-composition.c */
327 gst_video_overlay_rectangle_unpremultiply_3 (GstVideoFrame * frame)
330 int width = GST_VIDEO_FRAME_WIDTH (frame);
331 int height = GST_VIDEO_FRAME_HEIGHT (frame);
332 int stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0);
333 guint8 *data = GST_VIDEO_FRAME_PLANE_DATA (frame, 0);
335 for (j = 0; j < height; ++j) {
340 for (i = 0; i < width; ++i) {
343 line[0] = MIN ((line[0] * 255 + a / 2) / a, 255);
344 line[1] = MIN ((line[1] * 255 + a / 2) / a, 255);
345 line[2] = MIN ((line[2] * 255 + a / 2) / a, 255);
352 /* Copy from video-overlay-composition.c */
354 gst_video_overlay_rectangle_unpremultiply (GstVideoFrame * frame)
358 alpha_offset = GST_VIDEO_FRAME_COMP_POFFSET (frame, 3);
359 switch (alpha_offset) {
361 gst_video_overlay_rectangle_unpremultiply_0 (frame);
364 gst_video_overlay_rectangle_unpremultiply_3 (frame);
367 g_assert_not_reached ();
373 gst_cairo_overlay_transform_ip (GstBaseTransform * trans, GstBuffer * buf)
375 GstCairoOverlay *overlay = GST_CAIRO_OVERLAY (trans);
377 cairo_surface_t *surface;
379 cairo_format_t format;
380 gboolean draw_on_transparent_surface = overlay->draw_on_transparent_surface;
382 switch (GST_VIDEO_INFO_FORMAT (&overlay->info)) {
383 case GST_VIDEO_FORMAT_ARGB:
384 case GST_VIDEO_FORMAT_BGRA:
385 format = CAIRO_FORMAT_ARGB32;
387 case GST_VIDEO_FORMAT_xRGB:
388 case GST_VIDEO_FORMAT_BGRx:
389 format = CAIRO_FORMAT_RGB24;
391 case GST_VIDEO_FORMAT_RGB16:
392 format = CAIRO_FORMAT_RGB16_565;
396 GST_WARNING ("No matching cairo format for %s",
397 gst_video_format_to_string (GST_VIDEO_INFO_FORMAT (&overlay->info)));
398 return GST_FLOW_ERROR;
402 /* If we need to map the buffer writable, do so */
403 if (!draw_on_transparent_surface || !overlay->attach_compo_to_buffer) {
404 if (!gst_video_frame_map (&frame, &overlay->info, buf, GST_MAP_READWRITE)) {
405 return GST_FLOW_ERROR;
411 if (draw_on_transparent_surface) {
413 cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
414 GST_VIDEO_INFO_WIDTH (&overlay->info),
415 GST_VIDEO_INFO_HEIGHT (&overlay->info));
417 if (format == CAIRO_FORMAT_ARGB32)
418 gst_video_overlay_rectangle_premultiply (&frame);
421 cairo_image_surface_create_for_data (GST_VIDEO_FRAME_PLANE_DATA (&frame,
422 0), format, GST_VIDEO_FRAME_WIDTH (&frame),
423 GST_VIDEO_FRAME_HEIGHT (&frame), GST_VIDEO_FRAME_PLANE_STRIDE (&frame,
427 if (G_UNLIKELY (!surface))
428 return GST_FLOW_ERROR;
430 cr = cairo_create (surface);
431 if (G_UNLIKELY (!cr)) {
432 cairo_surface_destroy (surface);
433 return GST_FLOW_ERROR;
436 g_signal_emit (overlay, gst_cairo_overlay_signals[SIGNAL_DRAW], 0,
437 cr, GST_BUFFER_PTS (buf), GST_BUFFER_DURATION (buf), NULL);
441 if (draw_on_transparent_surface) {
443 GstBuffer *surface_buffer;
444 GstVideoOverlayRectangle *rect;
445 GstVideoOverlayComposition *composition;
446 gsize offset[GST_VIDEO_MAX_PLANES] = { 0, };
447 gint stride[GST_VIDEO_MAX_PLANES] = { 0, };
450 cairo_image_surface_get_height (surface) *
451 cairo_image_surface_get_stride (surface);
452 stride[0] = cairo_image_surface_get_stride (surface);
454 /* Create a GstVideoOverlayComposition for blending, this handles
455 * pre-multiplied alpha correctly */
457 gst_buffer_new_wrapped_full (0, cairo_image_surface_get_data (surface),
458 size, 0, size, surface, (GDestroyNotify) cairo_surface_destroy);
459 gst_buffer_add_video_meta_full (surface_buffer, GST_VIDEO_FRAME_FLAG_NONE,
461 G_LITTLE_ENDIAN ? GST_VIDEO_FORMAT_BGRA : GST_VIDEO_FORMAT_ARGB),
462 GST_VIDEO_INFO_WIDTH (&overlay->info),
463 GST_VIDEO_INFO_HEIGHT (&overlay->info), 1, offset, stride);
465 gst_video_overlay_rectangle_new_raw (surface_buffer, 0, 0,
466 GST_VIDEO_INFO_WIDTH (&overlay->info),
467 GST_VIDEO_INFO_HEIGHT (&overlay->info),
468 GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA);
469 gst_buffer_unref (surface_buffer);
471 if (overlay->attach_compo_to_buffer) {
472 GstVideoOverlayCompositionMeta *composition_meta;
474 composition_meta = gst_buffer_get_video_overlay_composition_meta (buf);
475 if (composition_meta) {
476 GstVideoOverlayComposition *merged_composition =
477 gst_video_overlay_composition_copy (composition_meta->overlay);
478 gst_video_overlay_composition_add_rectangle (merged_composition, rect);
479 gst_video_overlay_composition_unref (composition_meta->overlay);
480 composition_meta->overlay = merged_composition;
481 gst_video_overlay_rectangle_unref (rect);
483 composition = gst_video_overlay_composition_new (rect);
484 gst_video_overlay_rectangle_unref (rect);
485 gst_buffer_add_video_overlay_composition_meta (buf, composition);
486 gst_video_overlay_composition_unref (composition);
489 composition = gst_video_overlay_composition_new (rect);
490 gst_video_overlay_rectangle_unref (rect);
491 gst_video_overlay_composition_blend (composition, &frame);
492 gst_video_overlay_composition_unref (composition);
495 cairo_surface_destroy (surface);
496 if (format == CAIRO_FORMAT_ARGB32)
497 gst_video_overlay_rectangle_unpremultiply (&frame);
501 gst_video_frame_unmap (&frame);
508 gst_cairo_overlay_class_init (GstCairoOverlayClass * klass)
510 GstBaseTransformClass *btrans_class;
511 GstElementClass *element_class;
512 GObjectClass *gobject_class;
514 btrans_class = (GstBaseTransformClass *) klass;
515 element_class = (GstElementClass *) klass;
516 gobject_class = (GObjectClass *) klass;
518 btrans_class->set_caps = gst_cairo_overlay_set_caps;
519 btrans_class->transform_ip = gst_cairo_overlay_transform_ip;
520 btrans_class->query = gst_cairo_overlay_query;
522 gobject_class->set_property = gst_cairo_overlay_set_property;
523 gobject_class->get_property = gst_cairo_overlay_get_property;
525 g_object_class_install_property (gobject_class,
526 PROP_DRAW_ON_TRANSPARENT_SURFACE,
527 g_param_spec_boolean ("draw-on-transparent-surface",
528 "Draw on transparent surface",
529 "Let the draw signal work on a transparent surface "
530 "and blend the results with the video at a later time",
531 DEFAULT_DRAW_ON_TRANSPARENT_SURFACE,
532 GST_PARAM_CONTROLLABLE | GST_PARAM_MUTABLE_PLAYING | G_PARAM_READWRITE
533 | G_PARAM_STATIC_STRINGS));
536 * GstCairoOverlay::draw:
537 * @overlay: Overlay element emitting the signal.
538 * @cr: Cairo context to draw to.
539 * @timestamp: Timestamp (see #GstClockTime) of the current buffer.
540 * @duration: Duration (see #GstClockTime) of the current buffer.
542 * This signal is emitted when the overlay should be drawn.
544 gst_cairo_overlay_signals[SIGNAL_DRAW] =
545 g_signal_new ("draw",
546 G_TYPE_FROM_CLASS (klass),
551 g_cclosure_marshal_generic,
552 G_TYPE_NONE, 3, CAIRO_GOBJECT_TYPE_CONTEXT, G_TYPE_UINT64, G_TYPE_UINT64);
555 * GstCairoOverlay::caps-changed:
556 * @overlay: Overlay element emitting the signal.
557 * @caps: The #GstCaps of the element.
559 * This signal is emitted when the caps of the element has changed.
561 gst_cairo_overlay_signals[SIGNAL_CAPS_CHANGED] =
562 g_signal_new ("caps-changed",
563 G_TYPE_FROM_CLASS (klass),
565 0, NULL, NULL, g_cclosure_marshal_generic, 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)