2 * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
3 * Copyright (C) <2003> David Schleef <ds@schleef.org>
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
15 * You should have received a copy of the GNU Library General Public
16 * License along with this library; if not, write to the
17 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 * Boston, MA 02111-1307, USA.
21 * SECTION:element-cairotextoverlay
23 * cairotextoverlay renders the text on top of the video frames.
26 * <title>Example launch line</title>
28 * gst-launch videotestsrc ! cairotextoverlay text="hello" ! autovideosink
37 #include <gst/video/video.h>
38 #include "gsttextoverlay.h"
43 * - calculating the position of the shading rectangle is
44 * not really right (try with text "L"), to say the least.
45 * Seems to work at least with latin script though.
46 * - check final x/y position and text width/height so that
47 * we don't do out-of-memory access when blitting the text.
48 * Also, we do not want to blit over the right or left margin.
49 * - what about text with newline characters? Cairo doesn't deal
50 * with that (we'd need to fix text_height usage for that as well)
51 * - upstream caps renegotiation, ie. when video window gets resized
54 GST_DEBUG_CATEGORY_EXTERN (cairo_debug);
55 #define GST_CAT_DEFAULT cairo_debug
71 #define DEFAULT_YPAD 25
72 #define DEFAULT_XPAD 25
73 #define DEFAULT_FONT "sans"
75 #define GST_CAIRO_TEXT_OVERLAY_DEFAULT_SCALE 20.0
77 static GstStaticPadTemplate cairo_text_overlay_src_template_factory =
78 GST_STATIC_PAD_TEMPLATE ("src",
81 GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("I420"))
84 static GstStaticPadTemplate video_sink_template_factory =
85 GST_STATIC_PAD_TEMPLATE ("video_sink",
88 GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("I420"))
91 static GstStaticPadTemplate text_sink_template_factory =
92 GST_STATIC_PAD_TEMPLATE ("text_sink",
95 GST_STATIC_CAPS ("text/plain")
98 static void gst_text_overlay_set_property (GObject * object,
99 guint prop_id, const GValue * value, GParamSpec * pspec);
100 static GstStateChangeReturn gst_text_overlay_change_state (GstElement * element,
101 GstStateChange transition);
102 static GstCaps *gst_text_overlay_getcaps (GstPad * pad);
103 static gboolean gst_text_overlay_setcaps (GstPad * pad, GstCaps * caps);
104 static GstPadLinkReturn gst_text_overlay_text_pad_linked (GstPad * pad,
106 static void gst_text_overlay_text_pad_unlinked (GstPad * pad);
107 static GstFlowReturn gst_text_overlay_collected (GstCollectPads * pads,
109 static void gst_text_overlay_finalize (GObject * object);
110 static void gst_text_overlay_font_init (GstCairoTextOverlay * overlay);
111 static gboolean gst_text_overlay_src_event (GstPad * pad, GstEvent * event);
112 static gboolean gst_text_overlay_video_event (GstPad * pad, GstEvent * event);
114 /* These macros are adapted from videotestsrc.c */
115 #define I420_Y_ROWSTRIDE(width) (GST_ROUND_UP_4(width))
116 #define I420_U_ROWSTRIDE(width) (GST_ROUND_UP_8(width)/2)
117 #define I420_V_ROWSTRIDE(width) ((GST_ROUND_UP_8(I420_Y_ROWSTRIDE(width)))/2)
119 #define I420_Y_OFFSET(w,h) (0)
120 #define I420_U_OFFSET(w,h) (I420_Y_OFFSET(w,h)+(I420_Y_ROWSTRIDE(w)*GST_ROUND_UP_2(h)))
121 #define I420_V_OFFSET(w,h) (I420_U_OFFSET(w,h)+(I420_U_ROWSTRIDE(w)*GST_ROUND_UP_2(h)/2))
123 #define I420_SIZE(w,h) (I420_V_OFFSET(w,h)+(I420_V_ROWSTRIDE(w)*GST_ROUND_UP_2(h)/2))
125 GST_BOILERPLATE (GstCairoTextOverlay, gst_text_overlay, GstElement,
129 gst_text_overlay_base_init (gpointer g_class)
131 GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
133 gst_element_class_add_pad_template (element_class,
134 gst_static_pad_template_get (&cairo_text_overlay_src_template_factory));
135 gst_element_class_add_pad_template (element_class,
136 gst_static_pad_template_get (&video_sink_template_factory));
137 gst_element_class_add_pad_template (element_class,
138 gst_static_pad_template_get (&text_sink_template_factory));
140 gst_element_class_set_details_simple (element_class, "Text overlay",
141 "Filter/Editor/Video",
142 "Adds text strings on top of a video buffer",
143 "David Schleef <ds@schleef.org>");
147 gst_text_overlay_class_init (GstCairoTextOverlayClass * klass)
149 GObjectClass *gobject_class;
150 GstElementClass *gstelement_class;
152 gobject_class = (GObjectClass *) klass;
153 gstelement_class = (GstElementClass *) klass;
155 gobject_class->finalize = gst_text_overlay_finalize;
156 gobject_class->set_property = gst_text_overlay_set_property;
158 gstelement_class->change_state =
159 GST_DEBUG_FUNCPTR (gst_text_overlay_change_state);
161 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_TEXT,
162 g_param_spec_string ("text", "text",
163 "Text to be display.", "",
164 G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
165 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_SHADING,
166 g_param_spec_boolean ("shaded-background", "shaded background",
167 "Whether to shade the background under the text area", FALSE,
168 G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
169 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_VALIGN,
170 g_param_spec_string ("valign", "vertical alignment",
171 "Vertical alignment of the text. "
172 "Can be either 'baseline', 'bottom', or 'top'",
173 "baseline", G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
174 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_HALIGN,
175 g_param_spec_string ("halign", "horizontal alignment",
176 "Horizontal alignment of the text. "
177 "Can be either 'left', 'right', or 'center'",
178 "center", G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
179 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_XPAD,
180 g_param_spec_int ("xpad", "horizontal paddding",
181 "Horizontal paddding when using left/right alignment",
182 G_MININT, G_MAXINT, DEFAULT_XPAD,
183 G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
184 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_YPAD,
185 g_param_spec_int ("ypad", "vertical padding",
186 "Vertical padding when using top/bottom alignment",
187 G_MININT, G_MAXINT, DEFAULT_YPAD,
188 G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
189 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_DELTAX,
190 g_param_spec_int ("deltax", "X position modifier",
191 "Shift X position to the left or to the right. Unit is pixels.",
192 G_MININT, G_MAXINT, 0, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
193 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_DELTAY,
194 g_param_spec_int ("deltay", "Y position modifier",
195 "Shift Y position up or down. Unit is pixels.",
196 G_MININT, G_MAXINT, 0, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
197 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_FONT_DESC,
198 g_param_spec_string ("font-desc", "font description",
199 "Pango font description of font "
200 "to be used for rendering. "
201 "See documentation of "
202 "pango_font_description_from_string"
203 " for syntax.", "", G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
207 gst_text_overlay_finalize (GObject * object)
209 GstCairoTextOverlay *overlay = GST_CAIRO_TEXT_OVERLAY (object);
211 gst_collect_pads_stop (overlay->collect);
212 gst_object_unref (overlay->collect);
214 g_free (overlay->text_fill_image);
215 g_free (overlay->text_outline_image);
217 g_free (overlay->default_text);
218 g_free (overlay->font);
220 G_OBJECT_CLASS (parent_class)->finalize (object);
224 gst_text_overlay_init (GstCairoTextOverlay * overlay,
225 GstCairoTextOverlayClass * klass)
228 overlay->video_sinkpad =
229 gst_pad_new_from_static_template (&video_sink_template_factory,
231 gst_pad_set_getcaps_function (overlay->video_sinkpad,
232 GST_DEBUG_FUNCPTR (gst_text_overlay_getcaps));
233 gst_pad_set_setcaps_function (overlay->video_sinkpad,
234 GST_DEBUG_FUNCPTR (gst_text_overlay_setcaps));
235 gst_element_add_pad (GST_ELEMENT (overlay), overlay->video_sinkpad);
238 overlay->text_sinkpad =
239 gst_pad_new_from_static_template (&text_sink_template_factory,
241 gst_pad_set_link_function (overlay->text_sinkpad,
242 GST_DEBUG_FUNCPTR (gst_text_overlay_text_pad_linked));
243 gst_pad_set_unlink_function (overlay->text_sinkpad,
244 GST_DEBUG_FUNCPTR (gst_text_overlay_text_pad_unlinked));
245 gst_element_add_pad (GST_ELEMENT (overlay), overlay->text_sinkpad);
249 gst_pad_new_from_static_template
250 (&cairo_text_overlay_src_template_factory, "src");
251 gst_pad_set_getcaps_function (overlay->srcpad,
252 GST_DEBUG_FUNCPTR (gst_text_overlay_getcaps));
253 gst_pad_set_event_function (overlay->srcpad,
254 GST_DEBUG_FUNCPTR (gst_text_overlay_src_event));
255 gst_element_add_pad (GST_ELEMENT (overlay), overlay->srcpad);
257 overlay->halign = GST_CAIRO_TEXT_OVERLAY_HALIGN_CENTER;
258 overlay->valign = GST_CAIRO_TEXT_OVERLAY_VALIGN_BASELINE;
259 overlay->xpad = DEFAULT_XPAD;
260 overlay->ypad = DEFAULT_YPAD;
264 overlay->default_text = g_strdup ("");
265 overlay->need_render = TRUE;
267 overlay->font = g_strdup (DEFAULT_FONT);
268 gst_text_overlay_font_init (overlay);
273 overlay->collect = gst_collect_pads_new ();
275 gst_collect_pads_set_function (overlay->collect,
276 GST_DEBUG_FUNCPTR (gst_text_overlay_collected), overlay);
278 overlay->video_collect_data = gst_collect_pads_add_pad (overlay->collect,
279 overlay->video_sinkpad, sizeof (GstCollectData));
281 /* FIXME: hacked way to override/extend the event function of
282 * GstCollectPads; because it sets its own event function giving the
283 * element no access to events. Nicked from avimux. */
284 overlay->collect_event =
285 (GstPadEventFunction) GST_PAD_EVENTFUNC (overlay->video_sinkpad);
286 gst_pad_set_event_function (overlay->video_sinkpad,
287 GST_DEBUG_FUNCPTR (gst_text_overlay_video_event));
289 /* text pad will be added when it is linked */
290 overlay->text_collect_data = NULL;
294 gst_text_overlay_font_init (GstCairoTextOverlay * overlay)
296 cairo_font_extents_t font_extents;
297 cairo_surface_t *surface;
299 gchar *font_desc, *sep;
301 font_desc = g_ascii_strdown (overlay->font, -1);
303 /* cairo_select_font_face() does not parse the size at the end, so we have
304 * to do that ourselves; same for slate and weight */
305 sep = MAX (strrchr (font_desc, ' '), strrchr (font_desc, ','));
306 if (sep != NULL && g_strtod (sep, NULL) > 0.0) {
307 /* there may be a suffix such as 'px', but we just ignore that for now */
308 overlay->scale = g_strtod (sep, NULL);
310 overlay->scale = GST_CAIRO_TEXT_OVERLAY_DEFAULT_SCALE;
312 if (strstr (font_desc, "bold"))
313 overlay->weight = CAIRO_FONT_WEIGHT_BOLD;
315 overlay->weight = CAIRO_FONT_WEIGHT_NORMAL;
317 if (strstr (font_desc, "italic"))
318 overlay->slant = CAIRO_FONT_SLANT_ITALIC;
319 else if (strstr (font_desc, "oblique"))
320 overlay->slant = CAIRO_FONT_SLANT_OBLIQUE;
322 overlay->slant = CAIRO_FONT_SLANT_NORMAL;
324 GST_LOG_OBJECT (overlay, "Font desc: '%s', scale=%f, weight=%d, slant=%d",
325 overlay->font, overlay->scale, overlay->weight, overlay->slant);
327 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 256, 256);
328 cr = cairo_create (surface);
330 cairo_select_font_face (cr, overlay->font, overlay->slant, overlay->weight);
331 cairo_set_font_size (cr, overlay->scale);
333 /* this has a static leak:
334 * http://lists.freedesktop.org/archives/cairo/2007-May/010623.html
336 cairo_font_extents (cr, &font_extents);
337 overlay->font_height = GST_ROUND_UP_2 ((guint) font_extents.height);
338 overlay->need_render = TRUE;
341 cairo_surface_destroy (surface);
346 gst_text_overlay_set_property (GObject * object, guint prop_id,
347 const GValue * value, GParamSpec * pspec)
349 GstCairoTextOverlay *overlay = GST_CAIRO_TEXT_OVERLAY (object);
351 GST_OBJECT_LOCK (overlay);
355 g_free (overlay->default_text);
356 overlay->default_text = g_value_dup_string (value);
360 overlay->want_shading = g_value_get_boolean (value);
364 const gchar *s = g_value_get_string (value);
366 if (g_ascii_strcasecmp (s, "baseline") == 0)
367 overlay->valign = GST_CAIRO_TEXT_OVERLAY_VALIGN_BASELINE;
368 else if (g_ascii_strcasecmp (s, "bottom") == 0)
369 overlay->valign = GST_CAIRO_TEXT_OVERLAY_VALIGN_BOTTOM;
370 else if (g_ascii_strcasecmp (s, "top") == 0)
371 overlay->valign = GST_CAIRO_TEXT_OVERLAY_VALIGN_TOP;
373 g_warning ("Invalid 'valign' property value: %s", s);
377 const gchar *s = g_value_get_string (value);
379 if (g_ascii_strcasecmp (s, "left") == 0)
380 overlay->halign = GST_CAIRO_TEXT_OVERLAY_HALIGN_LEFT;
381 else if (g_ascii_strcasecmp (s, "right") == 0)
382 overlay->halign = GST_CAIRO_TEXT_OVERLAY_HALIGN_RIGHT;
383 else if (g_ascii_strcasecmp (s, "center") == 0)
384 overlay->halign = GST_CAIRO_TEXT_OVERLAY_HALIGN_CENTER;
386 g_warning ("Invalid 'halign' property value: %s", s);
390 overlay->xpad = g_value_get_int (value);
394 overlay->ypad = g_value_get_int (value);
398 overlay->deltax = g_value_get_int (value);
402 overlay->deltay = g_value_get_int (value);
406 g_free (overlay->font);
407 overlay->font = g_value_dup_string (value);
408 if (overlay->font == NULL)
409 overlay->font = g_strdup (DEFAULT_FONT);
410 gst_text_overlay_font_init (overlay);
414 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
419 overlay->need_render = TRUE;
421 GST_OBJECT_UNLOCK (overlay);
425 gst_text_overlay_render_text (GstCairoTextOverlay * overlay,
426 const gchar * text, gint textlen)
428 cairo_text_extents_t extents;
429 cairo_surface_t *surface;
435 textlen = strlen (text);
437 if (!overlay->need_render) {
438 GST_DEBUG ("Using previously rendered text.");
439 g_return_if_fail (overlay->text_fill_image != NULL);
440 g_return_if_fail (overlay->text_outline_image != NULL);
444 string = g_strndup (text, textlen);
445 GST_DEBUG ("Rendering text '%s' on cairo RGBA surface", string);
447 overlay->text_fill_image =
448 g_realloc (overlay->text_fill_image,
449 4 * overlay->width * overlay->font_height);
451 surface = cairo_image_surface_create_for_data (overlay->text_fill_image,
452 CAIRO_FORMAT_ARGB32, overlay->width, overlay->font_height,
455 cr = cairo_create (surface);
457 cairo_select_font_face (cr, overlay->font, overlay->slant, overlay->weight);
458 cairo_set_font_size (cr, overlay->scale);
461 cairo_rectangle (cr, 0, 0, overlay->width, overlay->font_height);
462 cairo_set_source_rgba (cr, 0, 0, 0, 1.0);
464 cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
469 cairo_text_extents (cr, string, &extents);
470 cairo_set_source_rgba (cr, 1, 1, 1, 1.0);
472 switch (overlay->halign) {
473 case GST_CAIRO_TEXT_OVERLAY_HALIGN_LEFT:
476 case GST_CAIRO_TEXT_OVERLAY_HALIGN_CENTER:
477 x = (overlay->width - extents.width) / 2;
479 case GST_CAIRO_TEXT_OVERLAY_HALIGN_RIGHT:
480 x = overlay->width - extents.width - overlay->xpad;
485 x += overlay->deltax;
487 overlay->text_x0 = x;
488 overlay->text_x1 = x + extents.x_advance;
490 overlay->text_dy = (extents.height + extents.y_bearing);
491 y = overlay->font_height - overlay->text_dy;
493 cairo_move_to (cr, x, y);
494 cairo_show_text (cr, string);
498 cairo_surface_destroy (surface);
502 overlay->text_outline_image =
503 g_realloc (overlay->text_outline_image,
504 4 * overlay->width * overlay->font_height);
506 surface = cairo_image_surface_create_for_data (overlay->text_outline_image,
507 CAIRO_FORMAT_ARGB32, overlay->width, overlay->font_height,
510 cr = cairo_create (surface);
512 cairo_select_font_face (cr, overlay->font, overlay->slant, overlay->weight);
513 cairo_set_font_size (cr, overlay->scale);
516 cairo_rectangle (cr, 0, 0, overlay->width, overlay->font_height);
517 cairo_set_source_rgba (cr, 0, 0, 0, 1.0);
518 cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
523 cairo_move_to (cr, x, y);
524 cairo_set_source_rgba (cr, 1, 1, 1, 1.0);
525 cairo_set_line_width (cr, 1.0);
526 cairo_text_path (cr, string);
533 cairo_surface_destroy (surface);
535 overlay->need_render = FALSE;
539 gst_text_overlay_getcaps (GstPad * pad)
541 GstCairoTextOverlay *overlay;
545 overlay = GST_CAIRO_TEXT_OVERLAY (gst_pad_get_parent (pad));
547 if (pad == overlay->srcpad)
548 otherpad = overlay->video_sinkpad;
550 otherpad = overlay->srcpad;
552 /* we can do what the peer can */
553 caps = gst_pad_peer_get_caps (otherpad);
556 const GstCaps *templ;
558 GST_DEBUG_OBJECT (pad, "peer caps %" GST_PTR_FORMAT, caps);
560 /* filtered against our padtemplate */
561 templ = gst_pad_get_pad_template_caps (otherpad);
562 GST_DEBUG_OBJECT (pad, "our template %" GST_PTR_FORMAT, templ);
563 temp = gst_caps_intersect (caps, templ);
564 GST_DEBUG_OBJECT (pad, "intersected %" GST_PTR_FORMAT, temp);
565 gst_caps_unref (caps);
566 /* this is what we can do */
569 /* no peer, our padtemplate is enough then */
570 caps = gst_caps_copy (gst_pad_get_pad_template_caps (pad));
573 GST_DEBUG_OBJECT (overlay, "returning %" GST_PTR_FORMAT, caps);
575 gst_object_unref (overlay);
580 /* FIXME: upstream nego (e.g. when the video window is resized) */
583 gst_text_overlay_setcaps (GstPad * pad, GstCaps * caps)
585 GstCairoTextOverlay *overlay;
586 GstStructure *structure;
587 gboolean ret = FALSE;
590 if (!GST_PAD_IS_SINK (pad))
593 g_return_val_if_fail (gst_caps_is_fixed (caps), FALSE);
595 overlay = GST_CAIRO_TEXT_OVERLAY (gst_pad_get_parent (pad));
599 structure = gst_caps_get_structure (caps, 0);
600 fps = gst_structure_get_value (structure, "framerate");
602 if (gst_structure_get_int (structure, "width", &overlay->width) &&
603 gst_structure_get_int (structure, "height", &overlay->height) &&
605 ret = gst_pad_set_caps (overlay->srcpad, caps);
608 overlay->fps_n = gst_value_get_fraction_numerator (fps);
609 overlay->fps_d = gst_value_get_fraction_denominator (fps);
611 gst_object_unref (overlay);
616 static GstPadLinkReturn
617 gst_text_overlay_text_pad_linked (GstPad * pad, GstPad * peer)
619 GstCairoTextOverlay *overlay;
621 overlay = GST_CAIRO_TEXT_OVERLAY (GST_PAD_PARENT (pad));
623 GST_DEBUG_OBJECT (overlay, "Text pad linked");
625 if (overlay->text_collect_data == NULL) {
626 overlay->text_collect_data = gst_collect_pads_add_pad (overlay->collect,
627 overlay->text_sinkpad, sizeof (GstCollectData));
630 overlay->need_render = TRUE;
632 return GST_PAD_LINK_OK;
636 gst_text_overlay_text_pad_unlinked (GstPad * pad)
638 GstCairoTextOverlay *overlay;
640 /* don't use gst_pad_get_parent() here, will deadlock */
641 overlay = GST_CAIRO_TEXT_OVERLAY (GST_PAD_PARENT (pad));
643 GST_DEBUG_OBJECT (overlay, "Text pad unlinked");
645 if (overlay->text_collect_data) {
646 gst_collect_pads_remove_pad (overlay->collect, overlay->text_sinkpad);
647 overlay->text_collect_data = NULL;
650 overlay->need_render = TRUE;
653 #define BOX_SHADING_VAL -80
658 gst_text_overlay_shade_y (GstCairoTextOverlay * overlay, guchar * dest,
659 guint dest_stride, gint y0, gint y1)
663 x0 = CLAMP (overlay->text_x0 - BOX_XPAD, 0, overlay->width);
664 x1 = CLAMP (overlay->text_x1 + BOX_XPAD, 0, overlay->width);
666 y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
667 y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
669 for (i = y0; i < y1; ++i) {
670 for (j = x0; j < x1; ++j) {
671 gint y = dest[(i * dest_stride) + j] + BOX_SHADING_VAL;
673 dest[(i * dest_stride) + j] = CLAMP (y, 0, 255);
679 gst_text_overlay_blit_1 (GstCairoTextOverlay * overlay, guchar * dest,
680 guchar * text_image, gint val, guint dest_stride, gint y0)
687 y0 = MIN (y0, overlay->height);
688 y1 = MIN (y0 + overlay->font_height, overlay->height);
690 for (i = y0; i < y1; i++) {
691 for (j = 0; j < overlay->width; j++) {
692 x = dest[i * dest_stride + j];
693 a = text_image[4 * ((i - y0) * overlay->width + j) + 1];
694 dest[i * dest_stride + j] = (y * a + x * (255 - a)) / 255;
700 gst_text_overlay_blit_sub2x2 (GstCairoTextOverlay * overlay, guchar * dest,
701 guchar * text_image, gint val, guint dest_stride, gint y0)
707 y0 = MIN (y0, overlay->height);
708 y1 = MIN (y0 + overlay->font_height, overlay->height);
712 for (i = y0; i < y1; i += 2) {
713 for (j = 0; j < overlay->width; j += 2) {
714 x = dest[(i / 2) * dest_stride + j / 2];
715 a = (text_image[4 * ((i - y0) * overlay->width + j) + 1] +
716 text_image[4 * ((i - y0) * overlay->width + j + 1) + 1] +
717 text_image[4 * ((i - y0 + 1) * overlay->width + j) + 1] +
718 text_image[4 * ((i - y0 + 1) * overlay->width + j + 1) + 1] + 2) / 4;
719 dest[(i / 2) * dest_stride + j / 2] = (y * a + x * (255 - a)) / 255;
726 gst_text_overlay_push_frame (GstCairoTextOverlay * overlay,
727 GstBuffer * video_frame)
732 video_frame = gst_buffer_make_writable (video_frame);
734 switch (overlay->valign) {
735 case GST_CAIRO_TEXT_OVERLAY_VALIGN_BOTTOM:
736 ypos = overlay->height - overlay->font_height - overlay->ypad;
738 case GST_CAIRO_TEXT_OVERLAY_VALIGN_BASELINE:
739 ypos = overlay->height - (overlay->font_height - overlay->text_dy)
742 case GST_CAIRO_TEXT_OVERLAY_VALIGN_TOP:
743 ypos = overlay->ypad;
746 ypos = overlay->ypad;
750 ypos += overlay->deltay;
752 y = GST_BUFFER_DATA (video_frame);
753 u = y + I420_U_OFFSET (overlay->width, overlay->height);
754 v = y + I420_V_OFFSET (overlay->width, overlay->height);
756 /* shaded background box */
757 if (overlay->want_shading) {
758 gst_text_overlay_shade_y (overlay,
759 y, I420_Y_ROWSTRIDE (overlay->width),
760 ypos + overlay->text_dy, ypos + overlay->font_height);
763 /* blit outline text on video image */
764 gst_text_overlay_blit_1 (overlay,
766 overlay->text_outline_image, 0, I420_Y_ROWSTRIDE (overlay->width), ypos);
767 gst_text_overlay_blit_sub2x2 (overlay,
769 overlay->text_outline_image, 128, I420_U_ROWSTRIDE (overlay->width),
771 gst_text_overlay_blit_sub2x2 (overlay, v, overlay->text_outline_image, 128,
772 I420_V_ROWSTRIDE (overlay->width), ypos);
774 /* blit text on video image */
775 gst_text_overlay_blit_1 (overlay,
777 overlay->text_fill_image, 255, I420_Y_ROWSTRIDE (overlay->width), ypos);
778 gst_text_overlay_blit_sub2x2 (overlay,
780 overlay->text_fill_image, 128, I420_U_ROWSTRIDE (overlay->width), ypos);
781 gst_text_overlay_blit_sub2x2 (overlay,
783 overlay->text_fill_image, 128, I420_V_ROWSTRIDE (overlay->width), ypos);
785 return gst_pad_push (overlay->srcpad, video_frame);
789 gst_text_overlay_pop_video (GstCairoTextOverlay * overlay)
793 buf = gst_collect_pads_pop (overlay->collect, overlay->video_collect_data);
794 g_return_if_fail (buf != NULL);
795 gst_buffer_unref (buf);
799 gst_text_overlay_pop_text (GstCairoTextOverlay * overlay)
803 if (overlay->text_collect_data) {
804 buf = gst_collect_pads_pop (overlay->collect, overlay->text_collect_data);
805 g_return_if_fail (buf != NULL);
806 gst_buffer_unref (buf);
809 overlay->need_render = TRUE;
812 /* This function is called when there is data on all pads */
814 gst_text_overlay_collected (GstCollectPads * pads, gpointer data)
816 GstCairoTextOverlay *overlay;
817 GstFlowReturn ret = GST_FLOW_OK;
818 GstClockTime now, txt_end, frame_end;
819 GstBuffer *video_frame = NULL;
820 GstBuffer *text_buf = NULL;
824 overlay = GST_CAIRO_TEXT_OVERLAY (data);
826 GST_DEBUG ("Collecting");
828 video_frame = gst_collect_pads_peek (overlay->collect,
829 overlay->video_collect_data);
831 /* send EOS if video stream EOSed regardless of text stream */
832 if (video_frame == NULL) {
833 GST_DEBUG ("Video stream at EOS");
834 if (overlay->text_collect_data) {
835 text_buf = gst_collect_pads_pop (overlay->collect,
836 overlay->text_collect_data);
838 gst_pad_push_event (overlay->srcpad, gst_event_new_eos ());
839 ret = GST_FLOW_UNEXPECTED;
843 if (GST_BUFFER_TIMESTAMP (video_frame) == GST_CLOCK_TIME_NONE) {
844 g_warning ("%s: video frame has invalid timestamp", G_STRLOC);
847 now = GST_BUFFER_TIMESTAMP (video_frame);
849 if (GST_BUFFER_DURATION (video_frame) != GST_CLOCK_TIME_NONE) {
850 frame_end = now + GST_BUFFER_DURATION (video_frame);
851 } else if (overlay->fps_n > 0) {
852 frame_end = now + gst_util_uint64_scale_int (GST_SECOND,
853 overlay->fps_d, overlay->fps_n);
855 /* magic value, does not really matter since texts
856 * tend to span quite a few frames in practice anyway */
857 frame_end = now + GST_SECOND / 25;
860 GST_DEBUG ("Got video frame: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
861 GST_TIME_ARGS (now), GST_TIME_ARGS (frame_end));
863 /* text pad not linked? */
864 if (overlay->text_collect_data == NULL) {
865 GST_DEBUG ("Text pad not linked, rendering default text: '%s'",
866 GST_STR_NULL (overlay->default_text));
867 if (overlay->default_text && *overlay->default_text != '\0') {
868 gst_text_overlay_render_text (overlay, overlay->default_text, -1);
869 ret = gst_text_overlay_push_frame (overlay, video_frame);
871 ret = gst_pad_push (overlay->srcpad, video_frame);
873 gst_text_overlay_pop_video (overlay);
878 text_buf = gst_collect_pads_peek (overlay->collect,
879 overlay->text_collect_data);
881 /* just push the video frame if the text stream has EOSed */
882 if (text_buf == NULL) {
883 GST_DEBUG ("Text pad EOSed, just pushing video frame as is");
884 ret = gst_pad_push (overlay->srcpad, video_frame);
885 gst_text_overlay_pop_video (overlay);
890 /* if the text buffer isn't stamped right, pop it off the
891 * queue and display it for the current video frame only */
892 if (GST_BUFFER_TIMESTAMP (text_buf) == GST_CLOCK_TIME_NONE ||
893 GST_BUFFER_DURATION (text_buf) == GST_CLOCK_TIME_NONE) {
894 GST_WARNING ("Got text buffer with invalid time stamp or duration");
895 gst_text_overlay_pop_text (overlay);
896 GST_BUFFER_TIMESTAMP (text_buf) = now;
897 GST_BUFFER_DURATION (text_buf) = frame_end - now;
900 txt_end = GST_BUFFER_TIMESTAMP (text_buf) + GST_BUFFER_DURATION (text_buf);
902 GST_DEBUG ("Got text buffer: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
903 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (text_buf)), GST_TIME_ARGS (txt_end));
905 /* if the text buffer is too old, pop it off the
906 * queue and return so we get a new one next time */
908 GST_DEBUG ("Text buffer too old, popping off the queue");
909 gst_text_overlay_pop_text (overlay);
914 /* if the video frame ends before the text even starts,
915 * just push it out as is and pop it off the queue */
916 if (frame_end < GST_BUFFER_TIMESTAMP (text_buf)) {
917 GST_DEBUG ("Video buffer before text, pushing out and popping off queue");
918 ret = gst_pad_push (overlay->srcpad, video_frame);
919 gst_text_overlay_pop_video (overlay);
924 /* text duration overlaps video frame duration */
925 text = g_strndup ((gchar *) GST_BUFFER_DATA (text_buf),
926 GST_BUFFER_SIZE (text_buf));
927 g_strdelimit (text, "\n\r\t", ' ');
928 text_len = strlen (text);
931 GST_DEBUG ("Rendering text '%*s'", text_len, text);;
932 gst_text_overlay_render_text (overlay, text, text_len);
934 GST_DEBUG ("No text to render (empty buffer)");
935 gst_text_overlay_render_text (overlay, " ", 1);
940 gst_text_overlay_pop_video (overlay);
941 ret = gst_text_overlay_push_frame (overlay, video_frame);
948 gst_buffer_unref (text_buf);
951 gst_buffer_unref (video_frame);
958 gst_text_overlay_src_event (GstPad * pad, GstEvent * event)
960 GstCairoTextOverlay *overlay =
961 GST_CAIRO_TEXT_OVERLAY (gst_pad_get_parent (pad));
964 /* forward events to the video sink, and, if it is linked, the text sink */
965 if (overlay->text_collect_data) {
966 gst_event_ref (event);
967 ret &= gst_pad_push_event (overlay->text_sinkpad, event);
969 ret &= gst_pad_push_event (overlay->video_sinkpad, event);
971 gst_object_unref (overlay);
976 gst_text_overlay_video_event (GstPad * pad, GstEvent * event)
978 gboolean ret = FALSE;
979 GstCairoTextOverlay *overlay = NULL;
981 overlay = GST_CAIRO_TEXT_OVERLAY (gst_pad_get_parent (pad));
983 if (GST_EVENT_TYPE (event) == GST_EVENT_NEWSEGMENT) {
984 GST_DEBUG_OBJECT (overlay,
985 "received new segment on video sink pad, forwarding");
986 gst_event_ref (event);
987 gst_pad_push_event (overlay->srcpad, event);
990 /* now GstCollectPads can take care of the rest, e.g. EOS */
991 ret = overlay->collect_event (pad, event);
992 gst_object_unref (overlay);
996 static GstStateChangeReturn
997 gst_text_overlay_change_state (GstElement * element, GstStateChange transition)
999 GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
1000 GstCairoTextOverlay *overlay = GST_CAIRO_TEXT_OVERLAY (element);
1002 switch (transition) {
1003 case GST_STATE_CHANGE_READY_TO_PAUSED:
1004 gst_collect_pads_start (overlay->collect);
1006 case GST_STATE_CHANGE_PAUSED_TO_READY:
1007 /* need to unblock the collectpads before calling the
1008 * parent change_state so that streaming can finish */
1009 gst_collect_pads_stop (overlay->collect);
1015 ret = parent_class->change_state (element, transition);
1016 if (ret == GST_STATE_CHANGE_FAILURE)
1019 switch (transition) {