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);
112 /* These macros are adapted from videotestsrc.c */
113 #define I420_Y_ROWSTRIDE(width) (GST_ROUND_UP_4(width))
114 #define I420_U_ROWSTRIDE(width) (GST_ROUND_UP_8(width)/2)
115 #define I420_V_ROWSTRIDE(width) ((GST_ROUND_UP_8(I420_Y_ROWSTRIDE(width)))/2)
117 #define I420_Y_OFFSET(w,h) (0)
118 #define I420_U_OFFSET(w,h) (I420_Y_OFFSET(w,h)+(I420_Y_ROWSTRIDE(w)*GST_ROUND_UP_2(h)))
119 #define I420_V_OFFSET(w,h) (I420_U_OFFSET(w,h)+(I420_U_ROWSTRIDE(w)*GST_ROUND_UP_2(h)/2))
121 #define I420_SIZE(w,h) (I420_V_OFFSET(w,h)+(I420_V_ROWSTRIDE(w)*GST_ROUND_UP_2(h)/2))
123 GST_BOILERPLATE (GstCairoTextOverlay, gst_text_overlay, GstElement,
127 gst_text_overlay_base_init (gpointer g_class)
129 GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
131 gst_element_class_add_pad_template (element_class,
132 gst_static_pad_template_get (&cairo_text_overlay_src_template_factory));
133 gst_element_class_add_pad_template (element_class,
134 gst_static_pad_template_get (&video_sink_template_factory));
135 gst_element_class_add_pad_template (element_class,
136 gst_static_pad_template_get (&text_sink_template_factory));
138 gst_element_class_set_details_simple (element_class, "Text overlay",
139 "Filter/Editor/Video",
140 "Adds text strings on top of a video buffer",
141 "David Schleef <ds@schleef.org>");
145 gst_text_overlay_class_init (GstCairoTextOverlayClass * klass)
147 GObjectClass *gobject_class;
148 GstElementClass *gstelement_class;
150 gobject_class = (GObjectClass *) klass;
151 gstelement_class = (GstElementClass *) klass;
153 gobject_class->finalize = gst_text_overlay_finalize;
154 gobject_class->set_property = gst_text_overlay_set_property;
156 gstelement_class->change_state =
157 GST_DEBUG_FUNCPTR (gst_text_overlay_change_state);
159 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_TEXT,
160 g_param_spec_string ("text", "text",
161 "Text to be display.", "",
162 G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
163 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_SHADING,
164 g_param_spec_boolean ("shaded-background", "shaded background",
165 "Whether to shade the background under the text area", FALSE,
166 G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
167 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_VALIGN,
168 g_param_spec_string ("valign", "vertical alignment",
169 "Vertical alignment of the text. "
170 "Can be either 'baseline', 'bottom', or 'top'",
171 "baseline", G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
172 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_HALIGN,
173 g_param_spec_string ("halign", "horizontal alignment",
174 "Horizontal alignment of the text. "
175 "Can be either 'left', 'right', or 'center'",
176 "center", G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
177 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_XPAD,
178 g_param_spec_int ("xpad", "horizontal paddding",
179 "Horizontal paddding when using left/right alignment",
180 G_MININT, G_MAXINT, DEFAULT_XPAD,
181 G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
182 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_YPAD,
183 g_param_spec_int ("ypad", "vertical padding",
184 "Vertical padding when using top/bottom alignment",
185 G_MININT, G_MAXINT, DEFAULT_YPAD,
186 G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
187 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_DELTAX,
188 g_param_spec_int ("deltax", "X position modifier",
189 "Shift X position to the left or to the right. Unit is pixels.",
190 G_MININT, G_MAXINT, 0, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
191 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_DELTAY,
192 g_param_spec_int ("deltay", "Y position modifier",
193 "Shift Y position up or down. Unit is pixels.",
194 G_MININT, G_MAXINT, 0, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
195 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_FONT_DESC,
196 g_param_spec_string ("font-desc", "font description",
197 "Pango font description of font "
198 "to be used for rendering. "
199 "See documentation of "
200 "pango_font_description_from_string"
201 " for syntax.", "", G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
205 gst_text_overlay_finalize (GObject * object)
207 GstCairoTextOverlay *overlay = GST_CAIRO_TEXT_OVERLAY (object);
209 gst_collect_pads_stop (overlay->collect);
210 gst_object_unref (overlay->collect);
212 g_free (overlay->text_fill_image);
213 g_free (overlay->text_outline_image);
215 g_free (overlay->default_text);
216 g_free (overlay->font);
218 G_OBJECT_CLASS (parent_class)->finalize (object);
222 gst_text_overlay_init (GstCairoTextOverlay * overlay,
223 GstCairoTextOverlayClass * klass)
226 overlay->video_sinkpad =
227 gst_pad_new_from_static_template (&video_sink_template_factory,
229 gst_pad_set_getcaps_function (overlay->video_sinkpad,
230 GST_DEBUG_FUNCPTR (gst_text_overlay_getcaps));
231 gst_pad_set_setcaps_function (overlay->video_sinkpad,
232 GST_DEBUG_FUNCPTR (gst_text_overlay_setcaps));
233 gst_element_add_pad (GST_ELEMENT (overlay), overlay->video_sinkpad);
236 overlay->text_sinkpad =
237 gst_pad_new_from_static_template (&text_sink_template_factory,
239 gst_pad_set_link_function (overlay->text_sinkpad,
240 GST_DEBUG_FUNCPTR (gst_text_overlay_text_pad_linked));
241 gst_pad_set_unlink_function (overlay->text_sinkpad,
242 GST_DEBUG_FUNCPTR (gst_text_overlay_text_pad_unlinked));
243 gst_element_add_pad (GST_ELEMENT (overlay), overlay->text_sinkpad);
247 gst_pad_new_from_static_template
248 (&cairo_text_overlay_src_template_factory, "src");
249 gst_pad_set_getcaps_function (overlay->srcpad,
250 GST_DEBUG_FUNCPTR (gst_text_overlay_getcaps));
251 gst_element_add_pad (GST_ELEMENT (overlay), overlay->srcpad);
253 overlay->halign = GST_CAIRO_TEXT_OVERLAY_HALIGN_CENTER;
254 overlay->valign = GST_CAIRO_TEXT_OVERLAY_VALIGN_BASELINE;
255 overlay->xpad = DEFAULT_XPAD;
256 overlay->ypad = DEFAULT_YPAD;
260 overlay->default_text = g_strdup ("");
261 overlay->need_render = TRUE;
263 overlay->font = g_strdup (DEFAULT_FONT);
264 gst_text_overlay_font_init (overlay);
269 overlay->collect = gst_collect_pads_new ();
271 gst_collect_pads_set_function (overlay->collect,
272 GST_DEBUG_FUNCPTR (gst_text_overlay_collected), overlay);
274 overlay->video_collect_data = gst_collect_pads_add_pad (overlay->collect,
275 overlay->video_sinkpad, sizeof (GstCollectData));
277 /* text pad will be added when it is linked */
278 overlay->text_collect_data = NULL;
282 gst_text_overlay_font_init (GstCairoTextOverlay * overlay)
284 cairo_font_extents_t font_extents;
285 cairo_surface_t *surface;
287 gchar *font_desc, *sep;
289 font_desc = g_ascii_strdown (overlay->font, -1);
291 /* cairo_select_font_face() does not parse the size at the end, so we have
292 * to do that ourselves; same for slate and weight */
293 sep = MAX (strrchr (font_desc, ' '), strrchr (font_desc, ','));
294 if (sep != NULL && g_strtod (sep, NULL) > 0.0) {
295 /* there may be a suffix such as 'px', but we just ignore that for now */
296 overlay->scale = g_strtod (sep, NULL);
298 overlay->scale = GST_CAIRO_TEXT_OVERLAY_DEFAULT_SCALE;
300 if (strstr (font_desc, "bold"))
301 overlay->weight = CAIRO_FONT_WEIGHT_BOLD;
303 overlay->weight = CAIRO_FONT_WEIGHT_NORMAL;
305 if (strstr (font_desc, "italic"))
306 overlay->slant = CAIRO_FONT_SLANT_ITALIC;
307 else if (strstr (font_desc, "oblique"))
308 overlay->slant = CAIRO_FONT_SLANT_OBLIQUE;
310 overlay->slant = CAIRO_FONT_SLANT_NORMAL;
312 GST_LOG_OBJECT (overlay, "Font desc: '%s', scale=%f, weight=%d, slant=%d",
313 overlay->font, overlay->scale, overlay->weight, overlay->slant);
315 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 256, 256);
316 cr = cairo_create (surface);
318 cairo_select_font_face (cr, overlay->font, overlay->slant, overlay->weight);
319 cairo_set_font_size (cr, overlay->scale);
321 /* this has a static leak:
322 * http://lists.freedesktop.org/archives/cairo/2007-May/010623.html
324 cairo_font_extents (cr, &font_extents);
325 overlay->font_height = GST_ROUND_UP_2 ((guint) font_extents.height);
326 overlay->need_render = TRUE;
329 cairo_surface_destroy (surface);
334 gst_text_overlay_set_property (GObject * object, guint prop_id,
335 const GValue * value, GParamSpec * pspec)
337 GstCairoTextOverlay *overlay = GST_CAIRO_TEXT_OVERLAY (object);
339 GST_OBJECT_LOCK (overlay);
343 g_free (overlay->default_text);
344 overlay->default_text = g_value_dup_string (value);
348 overlay->want_shading = g_value_get_boolean (value);
352 const gchar *s = g_value_get_string (value);
354 if (g_ascii_strcasecmp (s, "baseline") == 0)
355 overlay->valign = GST_CAIRO_TEXT_OVERLAY_VALIGN_BASELINE;
356 else if (g_ascii_strcasecmp (s, "bottom") == 0)
357 overlay->valign = GST_CAIRO_TEXT_OVERLAY_VALIGN_BOTTOM;
358 else if (g_ascii_strcasecmp (s, "top") == 0)
359 overlay->valign = GST_CAIRO_TEXT_OVERLAY_VALIGN_TOP;
361 g_warning ("Invalid 'valign' property value: %s", s);
365 const gchar *s = g_value_get_string (value);
367 if (g_ascii_strcasecmp (s, "left") == 0)
368 overlay->halign = GST_CAIRO_TEXT_OVERLAY_HALIGN_LEFT;
369 else if (g_ascii_strcasecmp (s, "right") == 0)
370 overlay->halign = GST_CAIRO_TEXT_OVERLAY_HALIGN_RIGHT;
371 else if (g_ascii_strcasecmp (s, "center") == 0)
372 overlay->halign = GST_CAIRO_TEXT_OVERLAY_HALIGN_CENTER;
374 g_warning ("Invalid 'halign' property value: %s", s);
378 overlay->xpad = g_value_get_int (value);
382 overlay->ypad = g_value_get_int (value);
386 overlay->deltax = g_value_get_int (value);
390 overlay->deltay = g_value_get_int (value);
394 g_free (overlay->font);
395 overlay->font = g_value_dup_string (value);
396 if (overlay->font == NULL)
397 overlay->font = g_strdup (DEFAULT_FONT);
398 gst_text_overlay_font_init (overlay);
402 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
407 overlay->need_render = TRUE;
409 GST_OBJECT_UNLOCK (overlay);
413 gst_text_overlay_render_text (GstCairoTextOverlay * overlay,
414 const gchar * text, gint textlen)
416 cairo_text_extents_t extents;
417 cairo_surface_t *surface;
423 textlen = strlen (text);
425 if (!overlay->need_render) {
426 GST_DEBUG ("Using previously rendered text.");
427 g_return_if_fail (overlay->text_fill_image != NULL);
428 g_return_if_fail (overlay->text_outline_image != NULL);
432 string = g_strndup (text, textlen);
433 GST_DEBUG ("Rendering text '%s' on cairo RGBA surface", string);
435 overlay->text_fill_image =
436 g_realloc (overlay->text_fill_image,
437 4 * overlay->width * overlay->font_height);
439 surface = cairo_image_surface_create_for_data (overlay->text_fill_image,
440 CAIRO_FORMAT_ARGB32, overlay->width, overlay->font_height,
443 cr = cairo_create (surface);
445 cairo_select_font_face (cr, overlay->font, overlay->slant, overlay->weight);
446 cairo_set_font_size (cr, overlay->scale);
449 cairo_rectangle (cr, 0, 0, overlay->width, overlay->font_height);
450 cairo_set_source_rgba (cr, 0, 0, 0, 1.0);
452 cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
457 cairo_text_extents (cr, string, &extents);
458 cairo_set_source_rgba (cr, 1, 1, 1, 1.0);
460 switch (overlay->halign) {
461 case GST_CAIRO_TEXT_OVERLAY_HALIGN_LEFT:
464 case GST_CAIRO_TEXT_OVERLAY_HALIGN_CENTER:
465 x = (overlay->width - extents.width) / 2;
467 case GST_CAIRO_TEXT_OVERLAY_HALIGN_RIGHT:
468 x = overlay->width - extents.width - overlay->xpad;
473 x += overlay->deltax;
475 overlay->text_x0 = x;
476 overlay->text_x1 = x + extents.x_advance;
478 overlay->text_dy = (extents.height + extents.y_bearing);
479 y = overlay->font_height - overlay->text_dy;
481 cairo_move_to (cr, x, y);
482 cairo_show_text (cr, string);
486 cairo_surface_destroy (surface);
490 overlay->text_outline_image =
491 g_realloc (overlay->text_outline_image,
492 4 * overlay->width * overlay->font_height);
494 surface = cairo_image_surface_create_for_data (overlay->text_outline_image,
495 CAIRO_FORMAT_ARGB32, overlay->width, overlay->font_height,
498 cr = cairo_create (surface);
500 cairo_select_font_face (cr, overlay->font, overlay->slant, overlay->weight);
501 cairo_set_font_size (cr, overlay->scale);
504 cairo_rectangle (cr, 0, 0, overlay->width, overlay->font_height);
505 cairo_set_source_rgba (cr, 0, 0, 0, 1.0);
506 cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
511 cairo_move_to (cr, x, y);
512 cairo_set_source_rgba (cr, 1, 1, 1, 1.0);
513 cairo_set_line_width (cr, 1.0);
514 cairo_text_path (cr, string);
521 cairo_surface_destroy (surface);
523 overlay->need_render = FALSE;
527 gst_text_overlay_getcaps (GstPad * pad)
529 GstCairoTextOverlay *overlay;
533 overlay = GST_CAIRO_TEXT_OVERLAY (gst_pad_get_parent (pad));
535 if (pad == overlay->srcpad)
536 otherpad = overlay->video_sinkpad;
538 otherpad = overlay->srcpad;
540 /* we can do what the peer can */
541 caps = gst_pad_peer_get_caps (otherpad);
544 const GstCaps *templ;
546 GST_DEBUG_OBJECT (pad, "peer caps %" GST_PTR_FORMAT, caps);
548 /* filtered against our padtemplate */
549 templ = gst_pad_get_pad_template_caps (otherpad);
550 GST_DEBUG_OBJECT (pad, "our template %" GST_PTR_FORMAT, templ);
551 temp = gst_caps_intersect (caps, templ);
552 GST_DEBUG_OBJECT (pad, "intersected %" GST_PTR_FORMAT, temp);
553 gst_caps_unref (caps);
554 /* this is what we can do */
557 /* no peer, our padtemplate is enough then */
558 caps = gst_caps_copy (gst_pad_get_pad_template_caps (pad));
561 GST_DEBUG_OBJECT (overlay, "returning %" GST_PTR_FORMAT, caps);
563 gst_object_unref (overlay);
568 /* FIXME: upstream nego (e.g. when the video window is resized) */
571 gst_text_overlay_setcaps (GstPad * pad, GstCaps * caps)
573 GstCairoTextOverlay *overlay;
574 GstStructure *structure;
575 gboolean ret = FALSE;
578 if (!GST_PAD_IS_SINK (pad))
581 g_return_val_if_fail (gst_caps_is_fixed (caps), FALSE);
583 overlay = GST_CAIRO_TEXT_OVERLAY (gst_pad_get_parent (pad));
587 structure = gst_caps_get_structure (caps, 0);
588 fps = gst_structure_get_value (structure, "framerate");
590 if (gst_structure_get_int (structure, "width", &overlay->width) &&
591 gst_structure_get_int (structure, "height", &overlay->height) &&
593 ret = gst_pad_set_caps (overlay->srcpad, caps);
596 overlay->fps_n = gst_value_get_fraction_numerator (fps);
597 overlay->fps_d = gst_value_get_fraction_denominator (fps);
599 gst_object_unref (overlay);
604 static GstPadLinkReturn
605 gst_text_overlay_text_pad_linked (GstPad * pad, GstPad * peer)
607 GstCairoTextOverlay *overlay;
609 overlay = GST_CAIRO_TEXT_OVERLAY (GST_PAD_PARENT (pad));
611 GST_DEBUG_OBJECT (overlay, "Text pad linked");
613 if (overlay->text_collect_data == NULL) {
614 overlay->text_collect_data = gst_collect_pads_add_pad (overlay->collect,
615 overlay->text_sinkpad, sizeof (GstCollectData));
618 overlay->need_render = TRUE;
620 return GST_PAD_LINK_OK;
624 gst_text_overlay_text_pad_unlinked (GstPad * pad)
626 GstCairoTextOverlay *overlay;
628 /* don't use gst_pad_get_parent() here, will deadlock */
629 overlay = GST_CAIRO_TEXT_OVERLAY (GST_PAD_PARENT (pad));
631 GST_DEBUG_OBJECT (overlay, "Text pad unlinked");
633 if (overlay->text_collect_data) {
634 gst_collect_pads_remove_pad (overlay->collect, overlay->text_sinkpad);
635 overlay->text_collect_data = NULL;
638 overlay->need_render = TRUE;
641 #define BOX_SHADING_VAL -80
646 gst_text_overlay_shade_y (GstCairoTextOverlay * overlay, guchar * dest,
647 guint dest_stride, gint y0, gint y1)
651 x0 = CLAMP (overlay->text_x0 - BOX_XPAD, 0, overlay->width);
652 x1 = CLAMP (overlay->text_x1 + BOX_XPAD, 0, overlay->width);
654 y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
655 y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
657 for (i = y0; i < y1; ++i) {
658 for (j = x0; j < x1; ++j) {
659 gint y = dest[(i * dest_stride) + j] + BOX_SHADING_VAL;
661 dest[(i * dest_stride) + j] = CLAMP (y, 0, 255);
667 gst_text_overlay_blit_1 (GstCairoTextOverlay * overlay, guchar * dest,
668 guchar * text_image, gint val, guint dest_stride, gint y0)
675 y0 = MIN (y0, overlay->height);
676 y1 = MIN (y0 + overlay->font_height, overlay->height);
678 for (i = y0; i < y1; i++) {
679 for (j = 0; j < overlay->width; j++) {
680 x = dest[i * dest_stride + j];
681 a = text_image[4 * ((i - y0) * overlay->width + j) + 1];
682 dest[i * dest_stride + j] = (y * a + x * (255 - a)) / 255;
688 gst_text_overlay_blit_sub2x2 (GstCairoTextOverlay * overlay, guchar * dest,
689 guchar * text_image, gint val, guint dest_stride, gint y0)
695 y0 = MIN (y0, overlay->height);
696 y1 = MIN (y0 + overlay->font_height, overlay->height);
700 for (i = y0; i < y1; i += 2) {
701 for (j = 0; j < overlay->width; j += 2) {
702 x = dest[(i / 2) * dest_stride + j / 2];
703 a = (text_image[4 * ((i - y0) * overlay->width + j) + 1] +
704 text_image[4 * ((i - y0) * overlay->width + j + 1) + 1] +
705 text_image[4 * ((i - y0 + 1) * overlay->width + j) + 1] +
706 text_image[4 * ((i - y0 + 1) * overlay->width + j + 1) + 1] + 2) / 4;
707 dest[(i / 2) * dest_stride + j / 2] = (y * a + x * (255 - a)) / 255;
714 gst_text_overlay_push_frame (GstCairoTextOverlay * overlay,
715 GstBuffer * video_frame)
720 video_frame = gst_buffer_make_writable (video_frame);
722 switch (overlay->valign) {
723 case GST_CAIRO_TEXT_OVERLAY_VALIGN_BOTTOM:
724 ypos = overlay->height - overlay->font_height - overlay->ypad;
726 case GST_CAIRO_TEXT_OVERLAY_VALIGN_BASELINE:
727 ypos = overlay->height - (overlay->font_height - overlay->text_dy)
730 case GST_CAIRO_TEXT_OVERLAY_VALIGN_TOP:
731 ypos = overlay->ypad;
734 ypos = overlay->ypad;
738 ypos += overlay->deltay;
740 y = GST_BUFFER_DATA (video_frame);
741 u = y + I420_U_OFFSET (overlay->width, overlay->height);
742 v = y + I420_V_OFFSET (overlay->width, overlay->height);
744 /* shaded background box */
745 if (overlay->want_shading) {
746 gst_text_overlay_shade_y (overlay,
747 y, I420_Y_ROWSTRIDE (overlay->width),
748 ypos + overlay->text_dy, ypos + overlay->font_height);
751 /* blit outline text on video image */
752 gst_text_overlay_blit_1 (overlay,
754 overlay->text_outline_image, 0, I420_Y_ROWSTRIDE (overlay->width), ypos);
755 gst_text_overlay_blit_sub2x2 (overlay,
757 overlay->text_outline_image, 128, I420_U_ROWSTRIDE (overlay->width),
759 gst_text_overlay_blit_sub2x2 (overlay, v, overlay->text_outline_image, 128,
760 I420_V_ROWSTRIDE (overlay->width), ypos);
762 /* blit text on video image */
763 gst_text_overlay_blit_1 (overlay,
765 overlay->text_fill_image, 255, I420_Y_ROWSTRIDE (overlay->width), ypos);
766 gst_text_overlay_blit_sub2x2 (overlay,
768 overlay->text_fill_image, 128, I420_U_ROWSTRIDE (overlay->width), ypos);
769 gst_text_overlay_blit_sub2x2 (overlay,
771 overlay->text_fill_image, 128, I420_V_ROWSTRIDE (overlay->width), ypos);
773 return gst_pad_push (overlay->srcpad, video_frame);
777 gst_text_overlay_pop_video (GstCairoTextOverlay * overlay)
781 buf = gst_collect_pads_pop (overlay->collect, overlay->video_collect_data);
782 g_return_if_fail (buf != NULL);
783 gst_buffer_unref (buf);
787 gst_text_overlay_pop_text (GstCairoTextOverlay * overlay)
791 if (overlay->text_collect_data) {
792 buf = gst_collect_pads_pop (overlay->collect, overlay->text_collect_data);
793 g_return_if_fail (buf != NULL);
794 gst_buffer_unref (buf);
797 overlay->need_render = TRUE;
800 /* This function is called when there is data on all pads */
802 gst_text_overlay_collected (GstCollectPads * pads, gpointer data)
804 GstCairoTextOverlay *overlay;
805 GstFlowReturn ret = GST_FLOW_OK;
806 GstClockTime now, txt_end, frame_end;
807 GstBuffer *video_frame = NULL;
808 GstBuffer *text_buf = NULL;
812 overlay = GST_CAIRO_TEXT_OVERLAY (data);
814 GST_DEBUG ("Collecting");
816 video_frame = gst_collect_pads_peek (overlay->collect,
817 overlay->video_collect_data);
819 /* send EOS if video stream EOSed regardless of text stream */
820 if (video_frame == NULL) {
821 GST_DEBUG ("Video stream at EOS");
822 if (overlay->text_collect_data) {
823 text_buf = gst_collect_pads_pop (overlay->collect,
824 overlay->text_collect_data);
826 gst_pad_push_event (overlay->srcpad, gst_event_new_eos ());
827 ret = GST_FLOW_UNEXPECTED;
831 if (GST_BUFFER_TIMESTAMP (video_frame) == GST_CLOCK_TIME_NONE) {
832 g_warning ("%s: video frame has invalid timestamp", G_STRLOC);
835 now = GST_BUFFER_TIMESTAMP (video_frame);
837 if (GST_BUFFER_DURATION (video_frame) != GST_CLOCK_TIME_NONE) {
838 frame_end = now + GST_BUFFER_DURATION (video_frame);
839 } else if (overlay->fps_n > 0) {
840 frame_end = now + gst_util_uint64_scale_int (GST_SECOND,
841 overlay->fps_d, overlay->fps_n);
843 /* magic value, does not really matter since texts
844 * tend to span quite a few frames in practice anyway */
845 frame_end = now + GST_SECOND / 25;
848 GST_DEBUG ("Got video frame: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
849 GST_TIME_ARGS (now), GST_TIME_ARGS (frame_end));
851 /* text pad not linked? */
852 if (overlay->text_collect_data == NULL) {
853 GST_DEBUG ("Text pad not linked, rendering default text: '%s'",
854 GST_STR_NULL (overlay->default_text));
855 if (overlay->default_text && *overlay->default_text != '\0') {
856 gst_text_overlay_render_text (overlay, overlay->default_text, -1);
857 ret = gst_text_overlay_push_frame (overlay, video_frame);
859 ret = gst_pad_push (overlay->srcpad, video_frame);
861 gst_text_overlay_pop_video (overlay);
866 text_buf = gst_collect_pads_peek (overlay->collect,
867 overlay->text_collect_data);
869 /* just push the video frame if the text stream has EOSed */
870 if (text_buf == NULL) {
871 GST_DEBUG ("Text pad EOSed, just pushing video frame as is");
872 ret = gst_pad_push (overlay->srcpad, video_frame);
873 gst_text_overlay_pop_video (overlay);
878 /* if the text buffer isn't stamped right, pop it off the
879 * queue and display it for the current video frame only */
880 if (GST_BUFFER_TIMESTAMP (text_buf) == GST_CLOCK_TIME_NONE ||
881 GST_BUFFER_DURATION (text_buf) == GST_CLOCK_TIME_NONE) {
882 GST_WARNING ("Got text buffer with invalid time stamp or duration");
883 gst_text_overlay_pop_text (overlay);
884 GST_BUFFER_TIMESTAMP (text_buf) = now;
885 GST_BUFFER_DURATION (text_buf) = frame_end - now;
888 txt_end = GST_BUFFER_TIMESTAMP (text_buf) + GST_BUFFER_DURATION (text_buf);
890 GST_DEBUG ("Got text buffer: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
891 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (text_buf)), GST_TIME_ARGS (txt_end));
893 /* if the text buffer is too old, pop it off the
894 * queue and return so we get a new one next time */
896 GST_DEBUG ("Text buffer too old, popping off the queue");
897 gst_text_overlay_pop_text (overlay);
902 /* if the video frame ends before the text even starts,
903 * just push it out as is and pop it off the queue */
904 if (frame_end < GST_BUFFER_TIMESTAMP (text_buf)) {
905 GST_DEBUG ("Video buffer before text, pushing out and popping off queue");
906 ret = gst_pad_push (overlay->srcpad, video_frame);
907 gst_text_overlay_pop_video (overlay);
912 /* text duration overlaps video frame duration */
913 text = g_strndup ((gchar *) GST_BUFFER_DATA (text_buf),
914 GST_BUFFER_SIZE (text_buf));
915 g_strdelimit (text, "\n\r\t", ' ');
916 text_len = strlen (text);
919 GST_DEBUG ("Rendering text '%*s'", text_len, text);;
920 gst_text_overlay_render_text (overlay, text, text_len);
922 GST_DEBUG ("No text to render (empty buffer)");
923 gst_text_overlay_render_text (overlay, " ", 1);
928 gst_text_overlay_pop_video (overlay);
929 ret = gst_text_overlay_push_frame (overlay, video_frame);
936 gst_buffer_unref (text_buf);
939 gst_buffer_unref (video_frame);
945 static GstStateChangeReturn
946 gst_text_overlay_change_state (GstElement * element, GstStateChange transition)
948 GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
949 GstCairoTextOverlay *overlay = GST_CAIRO_TEXT_OVERLAY (element);
951 switch (transition) {
952 case GST_STATE_CHANGE_READY_TO_PAUSED:
953 gst_collect_pads_start (overlay->collect);
955 case GST_STATE_CHANGE_PAUSED_TO_READY:
956 /* need to unblock the collectpads before calling the
957 * parent change_state so that streaming can finish */
958 gst_collect_pads_stop (overlay->collect);
964 ret = parent_class->change_state (element, transition);
965 if (ret == GST_STATE_CHANGE_FAILURE)
968 switch (transition) {