Tizen 2.0 Release
[framework/multimedia/gst-plugins-good0.10.git] / ext / cairo / gsttextoverlay.c
1 /* GStreamer
2  * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
3  * Copyright (C) <2003> David Schleef <ds@schleef.org>
4  *
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.
9  *
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.
14  *
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.
19  */
20 /**
21  * SECTION:element-cairotextoverlay
22  *
23  * cairotextoverlay renders the text on top of the video frames.
24  *
25  * <refsect2>
26  * <title>Example launch line</title>
27  * |[
28  * gst-launch videotestsrc ! cairotextoverlay text="hello" ! autovideosink
29  * ]|
30  * </refsect2>
31  */
32
33 #ifdef HAVE_CONFIG_H
34 #include <config.h>
35 #endif
36 #include <string.h>
37 #include <gst/video/video.h>
38 #include "gsttextoverlay.h"
39
40 #include <cairo.h>
41
42 /* FIXME:
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
52  */
53
54 GST_DEBUG_CATEGORY_EXTERN (cairo_debug);
55 #define GST_CAT_DEFAULT cairo_debug
56
57 enum
58 {
59   ARG_0,
60   ARG_TEXT,
61   ARG_SHADING,
62   ARG_VALIGN,
63   ARG_HALIGN,
64   ARG_XPAD,
65   ARG_YPAD,
66   ARG_DELTAX,
67   ARG_DELTAY,
68   ARG_SILENT,
69   ARG_FONT_DESC
70 };
71
72 #define DEFAULT_YPAD 25
73 #define DEFAULT_XPAD 25
74 #define DEFAULT_FONT "sans"
75 #define DEFAULT_SILENT FALSE
76
77 #define GST_CAIRO_TEXT_OVERLAY_DEFAULT_SCALE   20.0
78
79 static GstStaticPadTemplate cairo_text_overlay_src_template_factory =
80 GST_STATIC_PAD_TEMPLATE ("src",
81     GST_PAD_SRC,
82     GST_PAD_ALWAYS,
83     GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("I420"))
84     );
85
86 static GstStaticPadTemplate video_sink_template_factory =
87 GST_STATIC_PAD_TEMPLATE ("video_sink",
88     GST_PAD_SINK,
89     GST_PAD_ALWAYS,
90     GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("I420"))
91     );
92
93 static GstStaticPadTemplate text_sink_template_factory =
94 GST_STATIC_PAD_TEMPLATE ("text_sink",
95     GST_PAD_SINK,
96     GST_PAD_ALWAYS,
97     GST_STATIC_CAPS ("text/plain")
98     );
99
100 static void gst_text_overlay_set_property (GObject * object,
101     guint prop_id, const GValue * value, GParamSpec * pspec);
102 static GstStateChangeReturn gst_text_overlay_change_state (GstElement * element,
103     GstStateChange transition);
104 static GstCaps *gst_text_overlay_getcaps (GstPad * pad);
105 static gboolean gst_text_overlay_setcaps (GstPad * pad, GstCaps * caps);
106 static GstPadLinkReturn gst_text_overlay_text_pad_linked (GstPad * pad,
107     GstPad * peer);
108 static void gst_text_overlay_text_pad_unlinked (GstPad * pad);
109 static GstFlowReturn gst_text_overlay_collected (GstCollectPads * pads,
110     gpointer data);
111 static void gst_text_overlay_finalize (GObject * object);
112 static void gst_text_overlay_font_init (GstCairoTextOverlay * overlay);
113 static gboolean gst_text_overlay_src_event (GstPad * pad, GstEvent * event);
114 static gboolean gst_text_overlay_video_event (GstPad * pad, GstEvent * event);
115
116 /* These macros are adapted from videotestsrc.c */
117 #define I420_Y_ROWSTRIDE(width) (GST_ROUND_UP_4(width))
118 #define I420_U_ROWSTRIDE(width) (GST_ROUND_UP_8(width)/2)
119 #define I420_V_ROWSTRIDE(width) ((GST_ROUND_UP_8(I420_Y_ROWSTRIDE(width)))/2)
120
121 #define I420_Y_OFFSET(w,h) (0)
122 #define I420_U_OFFSET(w,h) (I420_Y_OFFSET(w,h)+(I420_Y_ROWSTRIDE(w)*GST_ROUND_UP_2(h)))
123 #define I420_V_OFFSET(w,h) (I420_U_OFFSET(w,h)+(I420_U_ROWSTRIDE(w)*GST_ROUND_UP_2(h)/2))
124
125 #define I420_SIZE(w,h)     (I420_V_OFFSET(w,h)+(I420_V_ROWSTRIDE(w)*GST_ROUND_UP_2(h)/2))
126
127 GST_BOILERPLATE (GstCairoTextOverlay, gst_text_overlay, GstElement,
128     GST_TYPE_ELEMENT);
129
130 static void
131 gst_text_overlay_base_init (gpointer g_class)
132 {
133   GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
134
135   gst_element_class_add_static_pad_template (element_class,
136       &cairo_text_overlay_src_template_factory);
137   gst_element_class_add_static_pad_template (element_class,
138       &video_sink_template_factory);
139   gst_element_class_add_static_pad_template (element_class,
140       &text_sink_template_factory);
141
142   gst_element_class_set_details_simple (element_class, "Text overlay",
143       "Filter/Editor/Video",
144       "Adds text strings on top of a video buffer",
145       "David Schleef <ds@schleef.org>");
146 }
147
148 static void
149 gst_text_overlay_class_init (GstCairoTextOverlayClass * klass)
150 {
151   GObjectClass *gobject_class;
152   GstElementClass *gstelement_class;
153
154   gobject_class = (GObjectClass *) klass;
155   gstelement_class = (GstElementClass *) klass;
156
157   gobject_class->finalize = gst_text_overlay_finalize;
158   gobject_class->set_property = gst_text_overlay_set_property;
159
160   gstelement_class->change_state =
161       GST_DEBUG_FUNCPTR (gst_text_overlay_change_state);
162
163   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_TEXT,
164       g_param_spec_string ("text", "text",
165           "Text to be display.", "",
166           G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
167   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_SHADING,
168       g_param_spec_boolean ("shaded-background", "shaded background",
169           "Whether to shade the background under the text area", FALSE,
170           G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
171   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_VALIGN,
172       g_param_spec_string ("valign", "vertical alignment",
173           "Vertical alignment of the text. "
174           "Can be either 'baseline', 'bottom', or 'top'",
175           "baseline", G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
176   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_HALIGN,
177       g_param_spec_string ("halign", "horizontal alignment",
178           "Horizontal alignment of the text. "
179           "Can be either 'left', 'right', or 'center'",
180           "center", G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
181   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_XPAD,
182       g_param_spec_int ("xpad", "horizontal paddding",
183           "Horizontal paddding when using left/right alignment",
184           G_MININT, G_MAXINT, DEFAULT_XPAD,
185           G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
186   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_YPAD,
187       g_param_spec_int ("ypad", "vertical padding",
188           "Vertical padding when using top/bottom alignment",
189           G_MININT, G_MAXINT, DEFAULT_YPAD,
190           G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
191   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_DELTAX,
192       g_param_spec_int ("deltax", "X position modifier",
193           "Shift X position to the left or to the right. 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_DELTAY,
196       g_param_spec_int ("deltay", "Y position modifier",
197           "Shift Y position up or down. Unit is pixels.",
198           G_MININT, G_MAXINT, 0, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
199   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_FONT_DESC,
200       g_param_spec_string ("font-desc", "font description",
201           "Pango font description of font "
202           "to be used for rendering. "
203           "See documentation of "
204           "pango_font_description_from_string"
205           " for syntax.", "", G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
206   /* FIXME 0.11: rename to "visible" or "text-visible" or "render-text" */
207   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_SILENT,
208       g_param_spec_boolean ("silent", "silent",
209           "Whether to render the text string",
210           DEFAULT_SILENT, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
211 }
212
213 static void
214 gst_text_overlay_finalize (GObject * object)
215 {
216   GstCairoTextOverlay *overlay = GST_CAIRO_TEXT_OVERLAY (object);
217
218   gst_collect_pads_stop (overlay->collect);
219   gst_object_unref (overlay->collect);
220
221   g_free (overlay->text_fill_image);
222   g_free (overlay->text_outline_image);
223
224   g_free (overlay->default_text);
225   g_free (overlay->font);
226
227   G_OBJECT_CLASS (parent_class)->finalize (object);
228 }
229
230 static void
231 gst_text_overlay_init (GstCairoTextOverlay * overlay,
232     GstCairoTextOverlayClass * klass)
233 {
234   /* video sink */
235   overlay->video_sinkpad =
236       gst_pad_new_from_static_template (&video_sink_template_factory,
237       "video_sink");
238   gst_pad_set_getcaps_function (overlay->video_sinkpad,
239       GST_DEBUG_FUNCPTR (gst_text_overlay_getcaps));
240   gst_pad_set_setcaps_function (overlay->video_sinkpad,
241       GST_DEBUG_FUNCPTR (gst_text_overlay_setcaps));
242   gst_element_add_pad (GST_ELEMENT (overlay), overlay->video_sinkpad);
243
244   /* text sink */
245   overlay->text_sinkpad =
246       gst_pad_new_from_static_template (&text_sink_template_factory,
247       "text_sink");
248   gst_pad_set_link_function (overlay->text_sinkpad,
249       GST_DEBUG_FUNCPTR (gst_text_overlay_text_pad_linked));
250   gst_pad_set_unlink_function (overlay->text_sinkpad,
251       GST_DEBUG_FUNCPTR (gst_text_overlay_text_pad_unlinked));
252   gst_element_add_pad (GST_ELEMENT (overlay), overlay->text_sinkpad);
253
254   /* (video) source */
255   overlay->srcpad =
256       gst_pad_new_from_static_template
257       (&cairo_text_overlay_src_template_factory, "src");
258   gst_pad_set_getcaps_function (overlay->srcpad,
259       GST_DEBUG_FUNCPTR (gst_text_overlay_getcaps));
260   gst_pad_set_event_function (overlay->srcpad,
261       GST_DEBUG_FUNCPTR (gst_text_overlay_src_event));
262   gst_element_add_pad (GST_ELEMENT (overlay), overlay->srcpad);
263
264   overlay->halign = GST_CAIRO_TEXT_OVERLAY_HALIGN_CENTER;
265   overlay->valign = GST_CAIRO_TEXT_OVERLAY_VALIGN_BASELINE;
266   overlay->xpad = DEFAULT_XPAD;
267   overlay->ypad = DEFAULT_YPAD;
268   overlay->deltax = 0;
269   overlay->deltay = 0;
270
271   overlay->default_text = g_strdup ("");
272   overlay->need_render = TRUE;
273
274   overlay->font = g_strdup (DEFAULT_FONT);
275   gst_text_overlay_font_init (overlay);
276
277   overlay->silent = DEFAULT_SILENT;
278
279   overlay->fps_n = 0;
280   overlay->fps_d = 1;
281
282   overlay->collect = gst_collect_pads_new ();
283
284   gst_collect_pads_set_function (overlay->collect,
285       GST_DEBUG_FUNCPTR (gst_text_overlay_collected), overlay);
286
287   overlay->video_collect_data = gst_collect_pads_add_pad (overlay->collect,
288       overlay->video_sinkpad, sizeof (GstCollectData));
289
290   /* FIXME: hacked way to override/extend the event function of
291    * GstCollectPads; because it sets its own event function giving the
292    * element no access to events. Nicked from avimux. */
293   overlay->collect_event =
294       (GstPadEventFunction) GST_PAD_EVENTFUNC (overlay->video_sinkpad);
295   gst_pad_set_event_function (overlay->video_sinkpad,
296       GST_DEBUG_FUNCPTR (gst_text_overlay_video_event));
297
298   /* text pad will be added when it is linked */
299   overlay->text_collect_data = NULL;
300 }
301
302 static void
303 gst_text_overlay_font_init (GstCairoTextOverlay * overlay)
304 {
305   cairo_font_extents_t font_extents;
306   cairo_surface_t *surface;
307   cairo_t *cr;
308   gchar *font_desc, *sep;
309
310   font_desc = g_ascii_strdown (overlay->font, -1);
311
312   /* cairo_select_font_face() does not parse the size at the end, so we have
313    * to do that ourselves; same for slate and weight */
314   sep = MAX (strrchr (font_desc, ' '), strrchr (font_desc, ','));
315   if (sep != NULL && g_strtod (sep, NULL) > 0.0) {
316     /* there may be a suffix such as 'px', but we just ignore that for now */
317     overlay->scale = g_strtod (sep, NULL);
318   } else {
319     overlay->scale = GST_CAIRO_TEXT_OVERLAY_DEFAULT_SCALE;
320   }
321   if (strstr (font_desc, "bold"))
322     overlay->weight = CAIRO_FONT_WEIGHT_BOLD;
323   else
324     overlay->weight = CAIRO_FONT_WEIGHT_NORMAL;
325
326   if (strstr (font_desc, "italic"))
327     overlay->slant = CAIRO_FONT_SLANT_ITALIC;
328   else if (strstr (font_desc, "oblique"))
329     overlay->slant = CAIRO_FONT_SLANT_OBLIQUE;
330   else
331     overlay->slant = CAIRO_FONT_SLANT_NORMAL;
332
333   GST_LOG_OBJECT (overlay, "Font desc: '%s', scale=%f, weight=%d, slant=%d",
334       overlay->font, overlay->scale, overlay->weight, overlay->slant);
335
336   surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 256, 256);
337   cr = cairo_create (surface);
338
339   cairo_select_font_face (cr, overlay->font, overlay->slant, overlay->weight);
340   cairo_set_font_size (cr, overlay->scale);
341
342   /* this has a static leak:
343    * http://lists.freedesktop.org/archives/cairo/2007-May/010623.html
344    */
345   cairo_font_extents (cr, &font_extents);
346   overlay->font_height = GST_ROUND_UP_2 ((guint) font_extents.height);
347   overlay->need_render = TRUE;
348
349   cairo_destroy (cr);
350   cairo_surface_destroy (surface);
351   g_free (font_desc);
352 }
353
354 static void
355 gst_text_overlay_set_property (GObject * object, guint prop_id,
356     const GValue * value, GParamSpec * pspec)
357 {
358   GstCairoTextOverlay *overlay = GST_CAIRO_TEXT_OVERLAY (object);
359
360   GST_OBJECT_LOCK (overlay);
361
362   switch (prop_id) {
363     case ARG_TEXT:{
364       g_free (overlay->default_text);
365       overlay->default_text = g_value_dup_string (value);
366       break;
367     }
368     case ARG_SHADING:{
369       overlay->want_shading = g_value_get_boolean (value);
370       break;
371     }
372     case ARG_VALIGN:{
373       const gchar *s = g_value_get_string (value);
374
375       if (g_ascii_strcasecmp (s, "baseline") == 0)
376         overlay->valign = GST_CAIRO_TEXT_OVERLAY_VALIGN_BASELINE;
377       else if (g_ascii_strcasecmp (s, "bottom") == 0)
378         overlay->valign = GST_CAIRO_TEXT_OVERLAY_VALIGN_BOTTOM;
379       else if (g_ascii_strcasecmp (s, "top") == 0)
380         overlay->valign = GST_CAIRO_TEXT_OVERLAY_VALIGN_TOP;
381       else
382         g_warning ("Invalid 'valign' property value: %s", s);
383       break;
384     }
385     case ARG_HALIGN:{
386       const gchar *s = g_value_get_string (value);
387
388       if (g_ascii_strcasecmp (s, "left") == 0)
389         overlay->halign = GST_CAIRO_TEXT_OVERLAY_HALIGN_LEFT;
390       else if (g_ascii_strcasecmp (s, "right") == 0)
391         overlay->halign = GST_CAIRO_TEXT_OVERLAY_HALIGN_RIGHT;
392       else if (g_ascii_strcasecmp (s, "center") == 0)
393         overlay->halign = GST_CAIRO_TEXT_OVERLAY_HALIGN_CENTER;
394       else
395         g_warning ("Invalid 'halign' property value: %s", s);
396       break;
397     }
398     case ARG_XPAD:{
399       overlay->xpad = g_value_get_int (value);
400       break;
401     }
402     case ARG_YPAD:{
403       overlay->ypad = g_value_get_int (value);
404       break;
405     }
406     case ARG_DELTAX:{
407       overlay->deltax = g_value_get_int (value);
408       break;
409     }
410     case ARG_DELTAY:{
411       overlay->deltay = g_value_get_int (value);
412       break;
413     }
414     case ARG_FONT_DESC:{
415       g_free (overlay->font);
416       overlay->font = g_value_dup_string (value);
417       if (overlay->font == NULL)
418         overlay->font = g_strdup (DEFAULT_FONT);
419       gst_text_overlay_font_init (overlay);
420       break;
421     }
422     case ARG_SILENT:
423       overlay->silent = g_value_get_boolean (value);
424       break;
425     default:{
426       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
427       break;
428     }
429   }
430
431   overlay->need_render = TRUE;
432
433   GST_OBJECT_UNLOCK (overlay);
434 }
435
436 static void
437 gst_text_overlay_render_text (GstCairoTextOverlay * overlay,
438     const gchar * text, gint textlen)
439 {
440   cairo_text_extents_t extents;
441   cairo_surface_t *surface;
442   cairo_t *cr;
443   gchar *string;
444   double x, y;
445
446   if (overlay->silent) {
447     GST_DEBUG_OBJECT (overlay, "Silent mode, not rendering");
448     return;
449   }
450
451   if (textlen < 0)
452     textlen = strlen (text);
453
454   if (!overlay->need_render) {
455     GST_DEBUG ("Using previously rendered text.");
456     g_return_if_fail (overlay->text_fill_image != NULL);
457     g_return_if_fail (overlay->text_outline_image != NULL);
458     return;
459   }
460
461   string = g_strndup (text, textlen);
462   GST_DEBUG ("Rendering text '%s' on cairo RGBA surface", string);
463
464   overlay->text_fill_image =
465       g_realloc (overlay->text_fill_image,
466       4 * overlay->width * overlay->font_height);
467
468   surface = cairo_image_surface_create_for_data (overlay->text_fill_image,
469       CAIRO_FORMAT_ARGB32, overlay->width, overlay->font_height,
470       overlay->width * 4);
471
472   cr = cairo_create (surface);
473
474   cairo_select_font_face (cr, overlay->font, overlay->slant, overlay->weight);
475   cairo_set_font_size (cr, overlay->scale);
476
477   cairo_save (cr);
478   cairo_rectangle (cr, 0, 0, overlay->width, overlay->font_height);
479   cairo_set_source_rgba (cr, 0, 0, 0, 1.0);
480
481   cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
482   cairo_fill (cr);
483   cairo_restore (cr);
484
485   cairo_save (cr);
486   cairo_text_extents (cr, string, &extents);
487   cairo_set_source_rgba (cr, 1, 1, 1, 1.0);
488
489   switch (overlay->halign) {
490     case GST_CAIRO_TEXT_OVERLAY_HALIGN_LEFT:
491       x = overlay->xpad;
492       break;
493     case GST_CAIRO_TEXT_OVERLAY_HALIGN_CENTER:
494       x = (overlay->width - extents.width) / 2;
495       break;
496     case GST_CAIRO_TEXT_OVERLAY_HALIGN_RIGHT:
497       x = overlay->width - extents.width - overlay->xpad;
498       break;
499     default:
500       x = 0;
501   }
502   x += overlay->deltax;
503
504   overlay->text_x0 = x;
505   overlay->text_x1 = x + extents.x_advance;
506
507   overlay->text_dy = (extents.height + extents.y_bearing);
508   y = overlay->font_height - overlay->text_dy;
509
510   cairo_move_to (cr, x, y);
511   cairo_show_text (cr, string);
512   cairo_restore (cr);
513
514   cairo_destroy (cr);
515   cairo_surface_destroy (surface);
516
517   /* ----------- */
518
519   overlay->text_outline_image =
520       g_realloc (overlay->text_outline_image,
521       4 * overlay->width * overlay->font_height);
522
523   surface = cairo_image_surface_create_for_data (overlay->text_outline_image,
524       CAIRO_FORMAT_ARGB32, overlay->width, overlay->font_height,
525       overlay->width * 4);
526
527   cr = cairo_create (surface);
528
529   cairo_select_font_face (cr, overlay->font, overlay->slant, overlay->weight);
530   cairo_set_font_size (cr, overlay->scale);
531
532   cairo_save (cr);
533   cairo_rectangle (cr, 0, 0, overlay->width, overlay->font_height);
534   cairo_set_source_rgba (cr, 0, 0, 0, 1.0);
535   cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
536   cairo_fill (cr);
537   cairo_restore (cr);
538
539   cairo_save (cr);
540   cairo_move_to (cr, x, y);
541   cairo_set_source_rgba (cr, 1, 1, 1, 1.0);
542   cairo_set_line_width (cr, 1.0);
543   cairo_text_path (cr, string);
544   cairo_stroke (cr);
545   cairo_restore (cr);
546
547   g_free (string);
548
549   cairo_destroy (cr);
550   cairo_surface_destroy (surface);
551
552   overlay->need_render = FALSE;
553 }
554
555 static GstCaps *
556 gst_text_overlay_getcaps (GstPad * pad)
557 {
558   GstCairoTextOverlay *overlay;
559   GstPad *otherpad;
560   GstCaps *caps;
561
562   overlay = GST_CAIRO_TEXT_OVERLAY (gst_pad_get_parent (pad));
563
564   if (pad == overlay->srcpad)
565     otherpad = overlay->video_sinkpad;
566   else
567     otherpad = overlay->srcpad;
568
569   /* we can do what the peer can */
570   caps = gst_pad_peer_get_caps (otherpad);
571   if (caps) {
572     GstCaps *temp;
573     const GstCaps *templ;
574
575     GST_DEBUG_OBJECT (pad, "peer caps  %" GST_PTR_FORMAT, caps);
576
577     /* filtered against our padtemplate */
578     templ = gst_pad_get_pad_template_caps (otherpad);
579     GST_DEBUG_OBJECT (pad, "our template  %" GST_PTR_FORMAT, templ);
580     temp = gst_caps_intersect (caps, templ);
581     GST_DEBUG_OBJECT (pad, "intersected %" GST_PTR_FORMAT, temp);
582     gst_caps_unref (caps);
583     /* this is what we can do */
584     caps = temp;
585   } else {
586     /* no peer, our padtemplate is enough then */
587     caps = gst_caps_copy (gst_pad_get_pad_template_caps (pad));
588   }
589
590   GST_DEBUG_OBJECT (overlay, "returning  %" GST_PTR_FORMAT, caps);
591
592   gst_object_unref (overlay);
593
594   return caps;
595 }
596
597 /* FIXME: upstream nego (e.g. when the video window is resized) */
598
599 static gboolean
600 gst_text_overlay_setcaps (GstPad * pad, GstCaps * caps)
601 {
602   GstCairoTextOverlay *overlay;
603   GstStructure *structure;
604   gboolean ret = FALSE;
605   const GValue *fps;
606
607   if (!GST_PAD_IS_SINK (pad))
608     return TRUE;
609
610   g_return_val_if_fail (gst_caps_is_fixed (caps), FALSE);
611
612   overlay = GST_CAIRO_TEXT_OVERLAY (gst_pad_get_parent (pad));
613
614   overlay->width = 0;
615   overlay->height = 0;
616   structure = gst_caps_get_structure (caps, 0);
617   fps = gst_structure_get_value (structure, "framerate");
618
619   if (gst_structure_get_int (structure, "width", &overlay->width) &&
620       gst_structure_get_int (structure, "height", &overlay->height) &&
621       fps != NULL) {
622     ret = gst_pad_set_caps (overlay->srcpad, caps);
623   }
624
625   overlay->fps_n = gst_value_get_fraction_numerator (fps);
626   overlay->fps_d = gst_value_get_fraction_denominator (fps);
627
628   gst_object_unref (overlay);
629
630   return ret;
631 }
632
633 static GstPadLinkReturn
634 gst_text_overlay_text_pad_linked (GstPad * pad, GstPad * peer)
635 {
636   GstCairoTextOverlay *overlay;
637
638   overlay = GST_CAIRO_TEXT_OVERLAY (GST_PAD_PARENT (pad));
639
640   GST_DEBUG_OBJECT (overlay, "Text pad linked");
641
642   if (overlay->text_collect_data == NULL) {
643     overlay->text_collect_data = gst_collect_pads_add_pad (overlay->collect,
644         overlay->text_sinkpad, sizeof (GstCollectData));
645   }
646
647   overlay->need_render = TRUE;
648
649   return GST_PAD_LINK_OK;
650 }
651
652 static void
653 gst_text_overlay_text_pad_unlinked (GstPad * pad)
654 {
655   GstCairoTextOverlay *overlay;
656
657   /* don't use gst_pad_get_parent() here, will deadlock */
658   overlay = GST_CAIRO_TEXT_OVERLAY (GST_PAD_PARENT (pad));
659
660   GST_DEBUG_OBJECT (overlay, "Text pad unlinked");
661
662   if (overlay->text_collect_data) {
663     gst_collect_pads_remove_pad (overlay->collect, overlay->text_sinkpad);
664     overlay->text_collect_data = NULL;
665   }
666
667   overlay->need_render = TRUE;
668 }
669
670 #define BOX_SHADING_VAL -80
671 #define BOX_XPAD         6
672 #define BOX_YPAD         6
673
674 static inline void
675 gst_text_overlay_shade_y (GstCairoTextOverlay * overlay, guchar * dest,
676     guint dest_stride, gint y0, gint y1)
677 {
678   gint i, j, x0, x1;
679
680   x0 = CLAMP (overlay->text_x0 - BOX_XPAD, 0, overlay->width);
681   x1 = CLAMP (overlay->text_x1 + BOX_XPAD, 0, overlay->width);
682
683   y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
684   y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
685
686   for (i = y0; i < y1; ++i) {
687     for (j = x0; j < x1; ++j) {
688       gint y = dest[(i * dest_stride) + j] + BOX_SHADING_VAL;
689
690       dest[(i * dest_stride) + j] = CLAMP (y, 0, 255);
691     }
692   }
693 }
694
695 static inline void
696 gst_text_overlay_blit_1 (GstCairoTextOverlay * overlay, guchar * dest,
697     guchar * text_image, gint val, guint dest_stride, gint y0)
698 {
699   gint i, j;
700   gint x, a, y;
701   gint y1;
702
703   y = val;
704   y0 = MIN (y0, overlay->height);
705   y1 = MIN (y0 + overlay->font_height, overlay->height);
706
707   for (i = y0; i < y1; i++) {
708     for (j = 0; j < overlay->width; j++) {
709       x = dest[i * dest_stride + j];
710       a = text_image[4 * ((i - y0) * overlay->width + j) + 1];
711       dest[i * dest_stride + j] = (y * a + x * (255 - a)) / 255;
712     }
713   }
714 }
715
716 static inline void
717 gst_text_overlay_blit_sub2x2 (GstCairoTextOverlay * overlay, guchar * dest,
718     guchar * text_image, gint val, guint dest_stride, gint y0)
719 {
720   gint i, j;
721   gint x, a, y;
722   gint y1;
723
724   y0 = MIN (y0, overlay->height);
725   y1 = MIN (y0 + overlay->font_height, overlay->height);
726
727   y = val;
728
729   for (i = y0; i < y1; i += 2) {
730     for (j = 0; j < overlay->width; j += 2) {
731       x = dest[(i / 2) * dest_stride + j / 2];
732       a = (text_image[4 * ((i - y0) * overlay->width + j) + 1] +
733           text_image[4 * ((i - y0) * overlay->width + j + 1) + 1] +
734           text_image[4 * ((i - y0 + 1) * overlay->width + j) + 1] +
735           text_image[4 * ((i - y0 + 1) * overlay->width + j + 1) + 1] + 2) / 4;
736       dest[(i / 2) * dest_stride + j / 2] = (y * a + x * (255 - a)) / 255;
737     }
738   }
739 }
740
741
742 static GstFlowReturn
743 gst_text_overlay_push_frame (GstCairoTextOverlay * overlay,
744     GstBuffer * video_frame)
745 {
746   guchar *y, *u, *v;
747   gint ypos;
748
749   video_frame = gst_buffer_make_writable (video_frame);
750
751   switch (overlay->valign) {
752     case GST_CAIRO_TEXT_OVERLAY_VALIGN_BOTTOM:
753       ypos = overlay->height - overlay->font_height - overlay->ypad;
754       break;
755     case GST_CAIRO_TEXT_OVERLAY_VALIGN_BASELINE:
756       ypos = overlay->height - (overlay->font_height - overlay->text_dy)
757           - overlay->ypad;
758       break;
759     case GST_CAIRO_TEXT_OVERLAY_VALIGN_TOP:
760       ypos = overlay->ypad;
761       break;
762     default:
763       ypos = overlay->ypad;
764       break;
765   }
766
767   ypos += overlay->deltay;
768
769   y = GST_BUFFER_DATA (video_frame);
770   u = y + I420_U_OFFSET (overlay->width, overlay->height);
771   v = y + I420_V_OFFSET (overlay->width, overlay->height);
772
773   /* shaded background box */
774   if (overlay->want_shading) {
775     gst_text_overlay_shade_y (overlay,
776         y, I420_Y_ROWSTRIDE (overlay->width),
777         ypos + overlay->text_dy, ypos + overlay->font_height);
778   }
779
780   /* blit outline text on video image */
781   gst_text_overlay_blit_1 (overlay,
782       y,
783       overlay->text_outline_image, 0, I420_Y_ROWSTRIDE (overlay->width), ypos);
784   gst_text_overlay_blit_sub2x2 (overlay,
785       u,
786       overlay->text_outline_image, 128, I420_U_ROWSTRIDE (overlay->width),
787       ypos);
788   gst_text_overlay_blit_sub2x2 (overlay, v, overlay->text_outline_image, 128,
789       I420_V_ROWSTRIDE (overlay->width), ypos);
790
791   /* blit text on video image */
792   gst_text_overlay_blit_1 (overlay,
793       y,
794       overlay->text_fill_image, 255, I420_Y_ROWSTRIDE (overlay->width), ypos);
795   gst_text_overlay_blit_sub2x2 (overlay,
796       u,
797       overlay->text_fill_image, 128, I420_U_ROWSTRIDE (overlay->width), ypos);
798   gst_text_overlay_blit_sub2x2 (overlay,
799       v,
800       overlay->text_fill_image, 128, I420_V_ROWSTRIDE (overlay->width), ypos);
801
802   return gst_pad_push (overlay->srcpad, video_frame);
803 }
804
805 static void
806 gst_text_overlay_pop_video (GstCairoTextOverlay * overlay)
807 {
808   GstBuffer *buf;
809
810   buf = gst_collect_pads_pop (overlay->collect, overlay->video_collect_data);
811   g_return_if_fail (buf != NULL);
812   gst_buffer_unref (buf);
813 }
814
815 static void
816 gst_text_overlay_pop_text (GstCairoTextOverlay * overlay)
817 {
818   GstBuffer *buf;
819
820   if (overlay->text_collect_data) {
821     buf = gst_collect_pads_pop (overlay->collect, overlay->text_collect_data);
822     g_return_if_fail (buf != NULL);
823     gst_buffer_unref (buf);
824   }
825
826   overlay->need_render = TRUE;
827 }
828
829 /* This function is called when there is data on all pads */
830 static GstFlowReturn
831 gst_text_overlay_collected (GstCollectPads * pads, gpointer data)
832 {
833   GstCairoTextOverlay *overlay;
834   GstFlowReturn ret = GST_FLOW_OK;
835   GstClockTime now, txt_end, frame_end;
836   GstBuffer *video_frame = NULL;
837   GstBuffer *text_buf = NULL;
838   gchar *text;
839   gint text_len;
840
841   overlay = GST_CAIRO_TEXT_OVERLAY (data);
842
843   GST_DEBUG ("Collecting");
844
845   video_frame = gst_collect_pads_peek (overlay->collect,
846       overlay->video_collect_data);
847
848   /* send EOS if video stream EOSed regardless of text stream */
849   if (video_frame == NULL) {
850     GST_DEBUG ("Video stream at EOS");
851     if (overlay->text_collect_data) {
852       text_buf = gst_collect_pads_pop (overlay->collect,
853           overlay->text_collect_data);
854     }
855     gst_pad_push_event (overlay->srcpad, gst_event_new_eos ());
856     ret = GST_FLOW_UNEXPECTED;
857     goto done;
858   }
859
860   if (GST_BUFFER_TIMESTAMP (video_frame) == GST_CLOCK_TIME_NONE) {
861     g_warning ("%s: video frame has invalid timestamp", G_STRLOC);
862   }
863
864   now = GST_BUFFER_TIMESTAMP (video_frame);
865
866   if (GST_BUFFER_DURATION (video_frame) != GST_CLOCK_TIME_NONE) {
867     frame_end = now + GST_BUFFER_DURATION (video_frame);
868   } else if (overlay->fps_n > 0) {
869     frame_end = now + gst_util_uint64_scale_int (GST_SECOND,
870         overlay->fps_d, overlay->fps_n);
871   } else {
872     /* magic value, does not really matter since texts
873      * tend to span quite a few frames in practice anyway */
874     frame_end = now + GST_SECOND / 25;
875   }
876
877   GST_DEBUG ("Got video frame: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
878       GST_TIME_ARGS (now), GST_TIME_ARGS (frame_end));
879
880   /* text pad not linked? */
881   if (overlay->text_collect_data == NULL) {
882     GST_DEBUG ("Text pad not linked, rendering default text: '%s'",
883         GST_STR_NULL (overlay->default_text));
884     if (overlay->default_text && *overlay->default_text != '\0') {
885       gst_text_overlay_render_text (overlay, overlay->default_text, -1);
886       ret = gst_text_overlay_push_frame (overlay, video_frame);
887     } else {
888       ret = gst_pad_push (overlay->srcpad, video_frame);
889     }
890     gst_text_overlay_pop_video (overlay);
891     video_frame = NULL;
892     goto done;
893   }
894
895   text_buf = gst_collect_pads_peek (overlay->collect,
896       overlay->text_collect_data);
897
898   /* just push the video frame if the text stream has EOSed */
899   if (text_buf == NULL) {
900     GST_DEBUG ("Text pad EOSed, just pushing video frame as is");
901     ret = gst_pad_push (overlay->srcpad, video_frame);
902     gst_text_overlay_pop_video (overlay);
903     video_frame = NULL;
904     goto done;
905   }
906
907   /* if the text buffer isn't stamped right, pop it off the
908    *  queue and display it for the current video frame only */
909   if (GST_BUFFER_TIMESTAMP (text_buf) == GST_CLOCK_TIME_NONE ||
910       GST_BUFFER_DURATION (text_buf) == GST_CLOCK_TIME_NONE) {
911     GST_WARNING ("Got text buffer with invalid time stamp or duration");
912     gst_text_overlay_pop_text (overlay);
913     GST_BUFFER_TIMESTAMP (text_buf) = now;
914     GST_BUFFER_DURATION (text_buf) = frame_end - now;
915   }
916
917   txt_end = GST_BUFFER_TIMESTAMP (text_buf) + GST_BUFFER_DURATION (text_buf);
918
919   GST_DEBUG ("Got text buffer: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
920       GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (text_buf)), GST_TIME_ARGS (txt_end));
921
922   /* if the text buffer is too old, pop it off the
923    * queue and return so we get a new one next time */
924   if (txt_end < now) {
925     GST_DEBUG ("Text buffer too old, popping off the queue");
926     gst_text_overlay_pop_text (overlay);
927     ret = GST_FLOW_OK;
928     goto done;
929   }
930
931   /* if the video frame ends before the text even starts,
932    * just push it out as is and pop it off the queue */
933   if (frame_end < GST_BUFFER_TIMESTAMP (text_buf)) {
934     GST_DEBUG ("Video buffer before text, pushing out and popping off queue");
935     ret = gst_pad_push (overlay->srcpad, video_frame);
936     gst_text_overlay_pop_video (overlay);
937     video_frame = NULL;
938     goto done;
939   }
940
941   /* text duration overlaps video frame duration */
942   text = g_strndup ((gchar *) GST_BUFFER_DATA (text_buf),
943       GST_BUFFER_SIZE (text_buf));
944   g_strdelimit (text, "\n\r\t", ' ');
945   text_len = strlen (text);
946
947   if (text_len > 0) {
948     GST_DEBUG ("Rendering text '%*s'", text_len, text);;
949     gst_text_overlay_render_text (overlay, text, text_len);
950   } else {
951     GST_DEBUG ("No text to render (empty buffer)");
952     gst_text_overlay_render_text (overlay, " ", 1);
953   }
954
955   g_free (text);
956
957   gst_text_overlay_pop_video (overlay);
958   ret = gst_text_overlay_push_frame (overlay, video_frame);
959   video_frame = NULL;
960   goto done;
961
962 done:
963   {
964     if (text_buf)
965       gst_buffer_unref (text_buf);
966
967     if (video_frame)
968       gst_buffer_unref (video_frame);
969
970     return ret;
971   }
972 }
973
974 static gboolean
975 gst_text_overlay_src_event (GstPad * pad, GstEvent * event)
976 {
977   GstCairoTextOverlay *overlay =
978       GST_CAIRO_TEXT_OVERLAY (gst_pad_get_parent (pad));
979   gboolean ret = TRUE;
980
981   /* forward events to the video sink, and, if it is linked, the text sink */
982   if (overlay->text_collect_data) {
983     gst_event_ref (event);
984     ret &= gst_pad_push_event (overlay->text_sinkpad, event);
985   }
986   ret &= gst_pad_push_event (overlay->video_sinkpad, event);
987
988   gst_object_unref (overlay);
989   return ret;
990 }
991
992 static gboolean
993 gst_text_overlay_video_event (GstPad * pad, GstEvent * event)
994 {
995   gboolean ret = FALSE;
996   GstCairoTextOverlay *overlay = NULL;
997
998   overlay = GST_CAIRO_TEXT_OVERLAY (gst_pad_get_parent (pad));
999
1000   if (GST_EVENT_TYPE (event) == GST_EVENT_NEWSEGMENT) {
1001     GST_DEBUG_OBJECT (overlay,
1002         "received new segment on video sink pad, forwarding");
1003     gst_event_ref (event);
1004     gst_pad_push_event (overlay->srcpad, event);
1005   }
1006
1007   /* now GstCollectPads can take care of the rest, e.g. EOS */
1008   ret = overlay->collect_event (pad, event);
1009   gst_object_unref (overlay);
1010   return ret;
1011 }
1012
1013 static GstStateChangeReturn
1014 gst_text_overlay_change_state (GstElement * element, GstStateChange transition)
1015 {
1016   GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
1017   GstCairoTextOverlay *overlay = GST_CAIRO_TEXT_OVERLAY (element);
1018
1019   switch (transition) {
1020     case GST_STATE_CHANGE_READY_TO_PAUSED:
1021       gst_collect_pads_start (overlay->collect);
1022       break;
1023     case GST_STATE_CHANGE_PAUSED_TO_READY:
1024       /* need to unblock the collectpads before calling the
1025        * parent change_state so that streaming can finish */
1026       gst_collect_pads_stop (overlay->collect);
1027       break;
1028     default:
1029       break;
1030   }
1031
1032   ret = parent_class->change_state (element, transition);
1033   if (ret == GST_STATE_CHANGE_FAILURE)
1034     return ret;
1035
1036   switch (transition) {
1037     default:
1038       break;
1039   }
1040
1041   return ret;
1042 }