1 /* GStreamer
2 * Copyright (C) <2011> Jon Nordby <jononor@gmail.com>
3 *
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.
8 *
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.
13 *
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.
18 */
19
20 /**
21 * SECTION:element-cairooverlay
22 * @title: cairooverlay
23 *
24 * cairooverlay renders an overlay using a application provided render function.
25 *
26 * The full example can be found in tests/examples/cairo/cairo_overlay.c
27 *
28 * ## Example code
29 * |[
30 *
31 * #include <gst/gst.h>
32 * #include <gst/video/video.h>
33 *
34 * ...
35 *
36 * typedef struct {
37 * gboolean valid;
38 * int width;
39 * int height;
40 * } CairoOverlayState;
41 *
42 * ...
43 *
44 * static void
45 * prepare_overlay (GstElement * overlay, GstCaps * caps, gpointer user_data)
46 * {
47 * CairoOverlayState *state = (CairoOverlayState *)user_data;
48 *
49 * gst_video_format_parse_caps (caps, NULL, &state->width, &state->height);
50 * state->valid = TRUE;
51 * }
52 *
53 * static void
54 * draw_overlay (GstElement * overlay, cairo_t * cr, guint64 timestamp,
55 * guint64 duration, gpointer user_data)
56 * {
57 * CairoOverlayState *s = (CairoOverlayState *)user_data;
58 * double scale;
59 *
60 * if (!s->valid)
61 * return;
62 *
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);
66 *
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);
73 * cairo_fill (cr);
74 * }
75 *
76 * ...
77 *
78 * cairo_overlay = gst_element_factory_make ("cairooverlay", "overlay");
79 *
80 * g_signal_connect (cairo_overlay, "draw", G_CALLBACK (draw_overlay),
81 * overlay_state);
82 * g_signal_connect (cairo_overlay, "caps-changed",
83 * G_CALLBACK (prepare_overlay), overlay_state);
84 * ...
85 *
86 * ]|
87 *
88 */
89
90 #ifdef HAVE_CONFIG_H
91 #include "config.h"
92 #endif
93
94 #include "gstcairooverlay.h"
95
96 #include <gst/video/video.h>
97
98 #include <cairo.h>
99
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 }")
103 #else
104 #define TEMPLATE_CAPS GST_VIDEO_CAPS_MAKE("{ xRGB, ARGB, RGB16 }")
105 #endif
106
107 GST_DEBUG_CATEGORY (cairo_debug);
108
109 static GstStaticPadTemplate gst_cairo_overlay_src_template =
110 GST_STATIC_PAD_TEMPLATE ("src",
111 GST_PAD_SRC,
112 GST_PAD_ALWAYS,
113 GST_STATIC_CAPS (TEMPLATE_CAPS)
114 );
115
116 static GstStaticPadTemplate gst_cairo_overlay_sink_template =
117 GST_STATIC_PAD_TEMPLATE ("sink",
118 GST_PAD_SINK,
119 GST_PAD_ALWAYS,
120 GST_STATIC_CAPS (TEMPLATE_CAPS)
121 );
122
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"););
127 enum
128 {
129 PROP_0,
130 PROP_DRAW_ON_TRANSPARENT_SURFACE,
131 };
132
133 #define DEFAULT_DRAW_ON_TRANSPARENT_SURFACE (FALSE)
134
135 enum
136 {
137 SIGNAL_DRAW,
138 SIGNAL_CAPS_CHANGED,
139 N_SIGNALS
140 };
141
142 static guint gst_cairo_overlay_signals[N_SIGNALS];
143
144 static void
gst_cairo_overlay_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)145 gst_cairo_overlay_set_property (GObject * object, guint property_id,
146 const GValue * value, GParamSpec * pspec)
147 {
148 GstCairoOverlay *overlay = GST_CAIRO_OVERLAY (object);
149
150 GST_OBJECT_LOCK (overlay);
151
152 switch (property_id) {
153 case PROP_DRAW_ON_TRANSPARENT_SURFACE:
154 overlay->draw_on_transparent_surface = g_value_get_boolean (value);
155 break;
156 default:
157 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
158 break;
159 }
160
161 GST_OBJECT_UNLOCK (overlay);
162 }
163
164 static void
gst_cairo_overlay_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)165 gst_cairo_overlay_get_property (GObject * object, guint property_id,
166 GValue * value, GParamSpec * pspec)
167 {
168 GstCairoOverlay *overlay = GST_CAIRO_OVERLAY (object);
169
170 GST_OBJECT_LOCK (overlay);
171
172 switch (property_id) {
173 case PROP_DRAW_ON_TRANSPARENT_SURFACE:
174 g_value_set_boolean (value, overlay->draw_on_transparent_surface);
175 break;
176 default:
177 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
178 break;
179 }
180
181 GST_OBJECT_UNLOCK (overlay);
182 }
183
184 static gboolean
gst_cairo_overlay_query(GstBaseTransform * trans,GstPadDirection direction,GstQuery * query)185 gst_cairo_overlay_query (GstBaseTransform * trans, GstPadDirection direction,
186 GstQuery * query)
187 {
188 GstCairoOverlay *overlay = GST_CAIRO_OVERLAY (trans);
189
190 switch (GST_QUERY_TYPE (query)) {
191 case GST_QUERY_ALLOCATION:
192 {
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().
196 *
197 * We hook into the query handling for that reason
198 */
199 overlay->attach_compo_to_buffer = FALSE;
200
201 if (!GST_BASE_TRANSFORM_CLASS (gst_cairo_overlay_parent_class)->query
202 (trans, direction, query)) {
203 return FALSE;
204 }
205
206 overlay->attach_compo_to_buffer = gst_query_find_allocation_meta (query,
207 GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE, NULL);
208
209 return TRUE;
210 }
211 default:
212 return
213 GST_BASE_TRANSFORM_CLASS (gst_cairo_overlay_parent_class)->query
214 (trans, direction, query);
215 }
216 }
217
218 static gboolean
gst_cairo_overlay_set_caps(GstBaseTransform * trans,GstCaps * in_caps,GstCaps * out_caps)219 gst_cairo_overlay_set_caps (GstBaseTransform * trans, GstCaps * in_caps,
220 GstCaps * out_caps)
221 {
222 GstCairoOverlay *overlay = GST_CAIRO_OVERLAY (trans);
223
224 if (!gst_video_info_from_caps (&overlay->info, in_caps))
225 return FALSE;
226
227 g_signal_emit (overlay, gst_cairo_overlay_signals[SIGNAL_CAPS_CHANGED], 0,
228 in_caps, NULL);
229
230 return TRUE;
231 }
232
233 /* Copy from video-overlay-composition.c */
234 static void
gst_video_overlay_rectangle_premultiply_0(GstVideoFrame * frame)235 gst_video_overlay_rectangle_premultiply_0 (GstVideoFrame * frame)
236 {
237 int i, j;
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);
242
243 for (j = 0; j < height; ++j) {
244 guint8 *line;
245
246 line = data;
247 line += stride * j;
248 for (i = 0; i < width; ++i) {
249 int a = line[0];
250 line[1] = line[1] * a / 255;
251 line[2] = line[2] * a / 255;
252 line[3] = line[3] * a / 255;
253 line += 4;
254 }
255 }
256 }
257
258 /* Copy from video-overlay-composition.c */
259 static void
gst_video_overlay_rectangle_premultiply_3(GstVideoFrame * frame)260 gst_video_overlay_rectangle_premultiply_3 (GstVideoFrame * frame)
261 {
262 int i, j;
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);
267
268 for (j = 0; j < height; ++j) {
269 guint8 *line;
270
271 line = data;
272 line += stride * j;
273 for (i = 0; i < width; ++i) {
274 int a = line[3];
275 line[0] = line[0] * a / 255;
276 line[1] = line[1] * a / 255;
277 line[2] = line[2] * a / 255;
278 line += 4;
279 }
280 }
281 }
282
283 /* Copy from video-overlay-composition.c */
284 static void
gst_video_overlay_rectangle_premultiply(GstVideoFrame * frame)285 gst_video_overlay_rectangle_premultiply (GstVideoFrame * frame)
286 {
287 gint alpha_offset;
288
289 alpha_offset = GST_VIDEO_FRAME_COMP_POFFSET (frame, 3);
290 switch (alpha_offset) {
291 case 0:
292 gst_video_overlay_rectangle_premultiply_0 (frame);
293 break;
294 case 3:
295 gst_video_overlay_rectangle_premultiply_3 (frame);
296 break;
297 default:
298 g_assert_not_reached ();
299 break;
300 }
301 }
302
303 /* Copy from video-overlay-composition.c */
304 static void
gst_video_overlay_rectangle_unpremultiply_0(GstVideoFrame * frame)305 gst_video_overlay_rectangle_unpremultiply_0 (GstVideoFrame * frame)
306 {
307 int i, j;
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);
312
313 for (j = 0; j < height; ++j) {
314 guint8 *line;
315
316 line = data;
317 line += stride * j;
318 for (i = 0; i < width; ++i) {
319 int a = line[0];
320 if (a) {
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);
324 }
325 line += 4;
326 }
327 }
328 }
329
330 /* Copy from video-overlay-composition.c */
331 static void
gst_video_overlay_rectangle_unpremultiply_3(GstVideoFrame * frame)332 gst_video_overlay_rectangle_unpremultiply_3 (GstVideoFrame * frame)
333 {
334 int i, j;
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);
339
340 for (j = 0; j < height; ++j) {
341 guint8 *line;
342
343 line = data;
344 line += stride * j;
345 for (i = 0; i < width; ++i) {
346 int a = line[3];
347 if (a) {
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);
351 }
352 line += 4;
353 }
354 }
355 }
356
357 /* Copy from video-overlay-composition.c */
358 static void
gst_video_overlay_rectangle_unpremultiply(GstVideoFrame * frame)359 gst_video_overlay_rectangle_unpremultiply (GstVideoFrame * frame)
360 {
361 gint alpha_offset;
362
363 alpha_offset = GST_VIDEO_FRAME_COMP_POFFSET (frame, 3);
364 switch (alpha_offset) {
365 case 0:
366 gst_video_overlay_rectangle_unpremultiply_0 (frame);
367 break;
368 case 3:
369 gst_video_overlay_rectangle_unpremultiply_3 (frame);
370 break;
371 default:
372 g_assert_not_reached ();
373 break;
374 }
375 }
376
377 static GstFlowReturn
gst_cairo_overlay_transform_ip(GstBaseTransform * trans,GstBuffer * buf)378 gst_cairo_overlay_transform_ip (GstBaseTransform * trans, GstBuffer * buf)
379 {
380 GstCairoOverlay *overlay = GST_CAIRO_OVERLAY (trans);
381 GstVideoFrame frame;
382 cairo_surface_t *surface;
383 cairo_t *cr;
384 cairo_format_t format;
385 gboolean draw_on_transparent_surface = overlay->draw_on_transparent_surface;
386
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;
391 break;
392 case GST_VIDEO_FORMAT_xRGB:
393 case GST_VIDEO_FORMAT_BGRx:
394 format = CAIRO_FORMAT_RGB24;
395 break;
396 case GST_VIDEO_FORMAT_RGB16:
397 format = CAIRO_FORMAT_RGB16_565;
398 break;
399 default:
400 {
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;
404 }
405 }
406
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;
411 }
412 } else {
413 frame.buffer = NULL;
414 }
415
416 if (draw_on_transparent_surface) {
417 surface =
418 cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
419 GST_VIDEO_INFO_WIDTH (&overlay->info),
420 GST_VIDEO_INFO_HEIGHT (&overlay->info));
421 } else {
422 if (format == CAIRO_FORMAT_ARGB32)
423 gst_video_overlay_rectangle_premultiply (&frame);
424
425 surface =
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,
429 0));
430 }
431
432 if (G_UNLIKELY (!surface))
433 return GST_FLOW_ERROR;
434
435 cr = cairo_create (surface);
436 if (G_UNLIKELY (!cr)) {
437 cairo_surface_destroy (surface);
438 return GST_FLOW_ERROR;
439 }
440
441 g_signal_emit (overlay, gst_cairo_overlay_signals[SIGNAL_DRAW], 0,
442 cr, GST_BUFFER_PTS (buf), GST_BUFFER_DURATION (buf), NULL);
443
444 cairo_destroy (cr);
445
446 if (draw_on_transparent_surface) {
447 guint size;
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, };
453
454 size =
455 cairo_image_surface_get_height (surface) *
456 cairo_image_surface_get_stride (surface);
457 stride[0] = cairo_image_surface_get_stride (surface);
458
459 /* Create a GstVideoOverlayComposition for blending, this handles
460 * pre-multiplied alpha correctly */
461 surface_buffer =
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,
465 (G_BYTE_ORDER ==
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);
469 rect =
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);
475
476 if (overlay->attach_compo_to_buffer) {
477 GstVideoOverlayCompositionMeta *composition_meta;
478
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);
487 } else {
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);
492 }
493 } else {
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);
498 }
499 } else {
500 cairo_surface_destroy (surface);
501 if (format == CAIRO_FORMAT_ARGB32)
502 gst_video_overlay_rectangle_unpremultiply (&frame);
503 }
504
505 if (frame.buffer) {
506 gst_video_frame_unmap (&frame);
507 }
508
509 return GST_FLOW_OK;
510 }
511
512 static void
gst_cairo_overlay_class_init(GstCairoOverlayClass * klass)513 gst_cairo_overlay_class_init (GstCairoOverlayClass * klass)
514 {
515 GstBaseTransformClass *btrans_class;
516 GstElementClass *element_class;
517 GObjectClass *gobject_class;
518
519 btrans_class = (GstBaseTransformClass *) klass;
520 element_class = (GstElementClass *) klass;
521 gobject_class = (GObjectClass *) klass;
522
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;
526
527 gobject_class->set_property = gst_cairo_overlay_set_property;
528 gobject_class->get_property = gst_cairo_overlay_get_property;
529
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));
539
540 /**
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.
546 *
547 * This signal is emitted when the overlay should be drawn.
548 */
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);
554
555 /**
556 * GstCairoOverlay::caps-changed:
557 * @overlay: Overlay element emitting the signal.
558 * @caps: The #GstCaps of the element.
559 *
560 * This signal is emitted when the caps of the element has changed.
561 */
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);
566
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>");
571
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);
576 }
577
578 static void
gst_cairo_overlay_init(GstCairoOverlay * overlay)579 gst_cairo_overlay_init (GstCairoOverlay * overlay)
580 {
581 /* nothing to do */
582 }
583