cairotextoverlay: forward new segment events from the sink to the source
[platform/upstream/gstreamer.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_FONT_DESC
69 };
70
71 #define DEFAULT_YPAD 25
72 #define DEFAULT_XPAD 25
73 #define DEFAULT_FONT "sans"
74
75 #define GST_CAIRO_TEXT_OVERLAY_DEFAULT_SCALE   20.0
76
77 static GstStaticPadTemplate cairo_text_overlay_src_template_factory =
78 GST_STATIC_PAD_TEMPLATE ("src",
79     GST_PAD_SRC,
80     GST_PAD_ALWAYS,
81     GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("I420"))
82     );
83
84 static GstStaticPadTemplate video_sink_template_factory =
85 GST_STATIC_PAD_TEMPLATE ("video_sink",
86     GST_PAD_SINK,
87     GST_PAD_ALWAYS,
88     GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("I420"))
89     );
90
91 static GstStaticPadTemplate text_sink_template_factory =
92 GST_STATIC_PAD_TEMPLATE ("text_sink",
93     GST_PAD_SINK,
94     GST_PAD_ALWAYS,
95     GST_STATIC_CAPS ("text/plain")
96     );
97
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,
105     GstPad * peer);
106 static void gst_text_overlay_text_pad_unlinked (GstPad * pad);
107 static GstFlowReturn gst_text_overlay_collected (GstCollectPads * pads,
108     gpointer data);
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);
113
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)
118
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))
122
123 #define I420_SIZE(w,h)     (I420_V_OFFSET(w,h)+(I420_V_ROWSTRIDE(w)*GST_ROUND_UP_2(h)/2))
124
125 GST_BOILERPLATE (GstCairoTextOverlay, gst_text_overlay, GstElement,
126     GST_TYPE_ELEMENT);
127
128 static void
129 gst_text_overlay_base_init (gpointer g_class)
130 {
131   GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
132
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));
139
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>");
144 }
145
146 static void
147 gst_text_overlay_class_init (GstCairoTextOverlayClass * klass)
148 {
149   GObjectClass *gobject_class;
150   GstElementClass *gstelement_class;
151
152   gobject_class = (GObjectClass *) klass;
153   gstelement_class = (GstElementClass *) klass;
154
155   gobject_class->finalize = gst_text_overlay_finalize;
156   gobject_class->set_property = gst_text_overlay_set_property;
157
158   gstelement_class->change_state =
159       GST_DEBUG_FUNCPTR (gst_text_overlay_change_state);
160
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));
204 }
205
206 static void
207 gst_text_overlay_finalize (GObject * object)
208 {
209   GstCairoTextOverlay *overlay = GST_CAIRO_TEXT_OVERLAY (object);
210
211   gst_collect_pads_stop (overlay->collect);
212   gst_object_unref (overlay->collect);
213
214   g_free (overlay->text_fill_image);
215   g_free (overlay->text_outline_image);
216
217   g_free (overlay->default_text);
218   g_free (overlay->font);
219
220   G_OBJECT_CLASS (parent_class)->finalize (object);
221 }
222
223 static void
224 gst_text_overlay_init (GstCairoTextOverlay * overlay,
225     GstCairoTextOverlayClass * klass)
226 {
227   /* video sink */
228   overlay->video_sinkpad =
229       gst_pad_new_from_static_template (&video_sink_template_factory,
230       "video_sink");
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);
236
237   /* text sink */
238   overlay->text_sinkpad =
239       gst_pad_new_from_static_template (&text_sink_template_factory,
240       "text_sink");
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);
246
247   /* (video) source */
248   overlay->srcpad =
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);
256
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;
261   overlay->deltax = 0;
262   overlay->deltay = 0;
263
264   overlay->default_text = g_strdup ("");
265   overlay->need_render = TRUE;
266
267   overlay->font = g_strdup (DEFAULT_FONT);
268   gst_text_overlay_font_init (overlay);
269
270   overlay->fps_n = 0;
271   overlay->fps_d = 1;
272
273   overlay->collect = gst_collect_pads_new ();
274
275   gst_collect_pads_set_function (overlay->collect,
276       GST_DEBUG_FUNCPTR (gst_text_overlay_collected), overlay);
277
278   overlay->video_collect_data = gst_collect_pads_add_pad (overlay->collect,
279       overlay->video_sinkpad, sizeof (GstCollectData));
280
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));
288
289   /* text pad will be added when it is linked */
290   overlay->text_collect_data = NULL;
291 }
292
293 static void
294 gst_text_overlay_font_init (GstCairoTextOverlay * overlay)
295 {
296   cairo_font_extents_t font_extents;
297   cairo_surface_t *surface;
298   cairo_t *cr;
299   gchar *font_desc, *sep;
300
301   font_desc = g_ascii_strdown (overlay->font, -1);
302
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);
309   } else {
310     overlay->scale = GST_CAIRO_TEXT_OVERLAY_DEFAULT_SCALE;
311   }
312   if (strstr (font_desc, "bold"))
313     overlay->weight = CAIRO_FONT_WEIGHT_BOLD;
314   else
315     overlay->weight = CAIRO_FONT_WEIGHT_NORMAL;
316
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;
321   else
322     overlay->slant = CAIRO_FONT_SLANT_NORMAL;
323
324   GST_LOG_OBJECT (overlay, "Font desc: '%s', scale=%f, weight=%d, slant=%d",
325       overlay->font, overlay->scale, overlay->weight, overlay->slant);
326
327   surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 256, 256);
328   cr = cairo_create (surface);
329
330   cairo_select_font_face (cr, overlay->font, overlay->slant, overlay->weight);
331   cairo_set_font_size (cr, overlay->scale);
332
333   /* this has a static leak:
334    * http://lists.freedesktop.org/archives/cairo/2007-May/010623.html
335    */
336   cairo_font_extents (cr, &font_extents);
337   overlay->font_height = GST_ROUND_UP_2 ((guint) font_extents.height);
338   overlay->need_render = TRUE;
339
340   cairo_destroy (cr);
341   cairo_surface_destroy (surface);
342   g_free (font_desc);
343 }
344
345 static void
346 gst_text_overlay_set_property (GObject * object, guint prop_id,
347     const GValue * value, GParamSpec * pspec)
348 {
349   GstCairoTextOverlay *overlay = GST_CAIRO_TEXT_OVERLAY (object);
350
351   GST_OBJECT_LOCK (overlay);
352
353   switch (prop_id) {
354     case ARG_TEXT:{
355       g_free (overlay->default_text);
356       overlay->default_text = g_value_dup_string (value);
357       break;
358     }
359     case ARG_SHADING:{
360       overlay->want_shading = g_value_get_boolean (value);
361       break;
362     }
363     case ARG_VALIGN:{
364       const gchar *s = g_value_get_string (value);
365
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;
372       else
373         g_warning ("Invalid 'valign' property value: %s", s);
374       break;
375     }
376     case ARG_HALIGN:{
377       const gchar *s = g_value_get_string (value);
378
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;
385       else
386         g_warning ("Invalid 'halign' property value: %s", s);
387       break;
388     }
389     case ARG_XPAD:{
390       overlay->xpad = g_value_get_int (value);
391       break;
392     }
393     case ARG_YPAD:{
394       overlay->ypad = g_value_get_int (value);
395       break;
396     }
397     case ARG_DELTAX:{
398       overlay->deltax = g_value_get_int (value);
399       break;
400     }
401     case ARG_DELTAY:{
402       overlay->deltay = g_value_get_int (value);
403       break;
404     }
405     case ARG_FONT_DESC:{
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);
411       break;
412     }
413     default:{
414       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
415       break;
416     }
417   }
418
419   overlay->need_render = TRUE;
420
421   GST_OBJECT_UNLOCK (overlay);
422 }
423
424 static void
425 gst_text_overlay_render_text (GstCairoTextOverlay * overlay,
426     const gchar * text, gint textlen)
427 {
428   cairo_text_extents_t extents;
429   cairo_surface_t *surface;
430   cairo_t *cr;
431   gchar *string;
432   double x, y;
433
434   if (textlen < 0)
435     textlen = strlen (text);
436
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);
441     return;
442   }
443
444   string = g_strndup (text, textlen);
445   GST_DEBUG ("Rendering text '%s' on cairo RGBA surface", string);
446
447   overlay->text_fill_image =
448       g_realloc (overlay->text_fill_image,
449       4 * overlay->width * overlay->font_height);
450
451   surface = cairo_image_surface_create_for_data (overlay->text_fill_image,
452       CAIRO_FORMAT_ARGB32, overlay->width, overlay->font_height,
453       overlay->width * 4);
454
455   cr = cairo_create (surface);
456
457   cairo_select_font_face (cr, overlay->font, overlay->slant, overlay->weight);
458   cairo_set_font_size (cr, overlay->scale);
459
460   cairo_save (cr);
461   cairo_rectangle (cr, 0, 0, overlay->width, overlay->font_height);
462   cairo_set_source_rgba (cr, 0, 0, 0, 1.0);
463
464   cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
465   cairo_fill (cr);
466   cairo_restore (cr);
467
468   cairo_save (cr);
469   cairo_text_extents (cr, string, &extents);
470   cairo_set_source_rgba (cr, 1, 1, 1, 1.0);
471
472   switch (overlay->halign) {
473     case GST_CAIRO_TEXT_OVERLAY_HALIGN_LEFT:
474       x = overlay->xpad;
475       break;
476     case GST_CAIRO_TEXT_OVERLAY_HALIGN_CENTER:
477       x = (overlay->width - extents.width) / 2;
478       break;
479     case GST_CAIRO_TEXT_OVERLAY_HALIGN_RIGHT:
480       x = overlay->width - extents.width - overlay->xpad;
481       break;
482     default:
483       x = 0;
484   }
485   x += overlay->deltax;
486
487   overlay->text_x0 = x;
488   overlay->text_x1 = x + extents.x_advance;
489
490   overlay->text_dy = (extents.height + extents.y_bearing);
491   y = overlay->font_height - overlay->text_dy;
492
493   cairo_move_to (cr, x, y);
494   cairo_show_text (cr, string);
495   cairo_restore (cr);
496
497   cairo_destroy (cr);
498   cairo_surface_destroy (surface);
499
500   /* ----------- */
501
502   overlay->text_outline_image =
503       g_realloc (overlay->text_outline_image,
504       4 * overlay->width * overlay->font_height);
505
506   surface = cairo_image_surface_create_for_data (overlay->text_outline_image,
507       CAIRO_FORMAT_ARGB32, overlay->width, overlay->font_height,
508       overlay->width * 4);
509
510   cr = cairo_create (surface);
511
512   cairo_select_font_face (cr, overlay->font, overlay->slant, overlay->weight);
513   cairo_set_font_size (cr, overlay->scale);
514
515   cairo_save (cr);
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);
519   cairo_fill (cr);
520   cairo_restore (cr);
521
522   cairo_save (cr);
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);
527   cairo_stroke (cr);
528   cairo_restore (cr);
529
530   g_free (string);
531
532   cairo_destroy (cr);
533   cairo_surface_destroy (surface);
534
535   overlay->need_render = FALSE;
536 }
537
538 static GstCaps *
539 gst_text_overlay_getcaps (GstPad * pad)
540 {
541   GstCairoTextOverlay *overlay;
542   GstPad *otherpad;
543   GstCaps *caps;
544
545   overlay = GST_CAIRO_TEXT_OVERLAY (gst_pad_get_parent (pad));
546
547   if (pad == overlay->srcpad)
548     otherpad = overlay->video_sinkpad;
549   else
550     otherpad = overlay->srcpad;
551
552   /* we can do what the peer can */
553   caps = gst_pad_peer_get_caps (otherpad);
554   if (caps) {
555     GstCaps *temp;
556     const GstCaps *templ;
557
558     GST_DEBUG_OBJECT (pad, "peer caps  %" GST_PTR_FORMAT, caps);
559
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 */
567     caps = temp;
568   } else {
569     /* no peer, our padtemplate is enough then */
570     caps = gst_caps_copy (gst_pad_get_pad_template_caps (pad));
571   }
572
573   GST_DEBUG_OBJECT (overlay, "returning  %" GST_PTR_FORMAT, caps);
574
575   gst_object_unref (overlay);
576
577   return caps;
578 }
579
580 /* FIXME: upstream nego (e.g. when the video window is resized) */
581
582 static gboolean
583 gst_text_overlay_setcaps (GstPad * pad, GstCaps * caps)
584 {
585   GstCairoTextOverlay *overlay;
586   GstStructure *structure;
587   gboolean ret = FALSE;
588   const GValue *fps;
589
590   if (!GST_PAD_IS_SINK (pad))
591     return TRUE;
592
593   g_return_val_if_fail (gst_caps_is_fixed (caps), FALSE);
594
595   overlay = GST_CAIRO_TEXT_OVERLAY (gst_pad_get_parent (pad));
596
597   overlay->width = 0;
598   overlay->height = 0;
599   structure = gst_caps_get_structure (caps, 0);
600   fps = gst_structure_get_value (structure, "framerate");
601
602   if (gst_structure_get_int (structure, "width", &overlay->width) &&
603       gst_structure_get_int (structure, "height", &overlay->height) &&
604       fps != NULL) {
605     ret = gst_pad_set_caps (overlay->srcpad, caps);
606   }
607
608   overlay->fps_n = gst_value_get_fraction_numerator (fps);
609   overlay->fps_d = gst_value_get_fraction_denominator (fps);
610
611   gst_object_unref (overlay);
612
613   return ret;
614 }
615
616 static GstPadLinkReturn
617 gst_text_overlay_text_pad_linked (GstPad * pad, GstPad * peer)
618 {
619   GstCairoTextOverlay *overlay;
620
621   overlay = GST_CAIRO_TEXT_OVERLAY (GST_PAD_PARENT (pad));
622
623   GST_DEBUG_OBJECT (overlay, "Text pad linked");
624
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));
628   }
629
630   overlay->need_render = TRUE;
631
632   return GST_PAD_LINK_OK;
633 }
634
635 static void
636 gst_text_overlay_text_pad_unlinked (GstPad * pad)
637 {
638   GstCairoTextOverlay *overlay;
639
640   /* don't use gst_pad_get_parent() here, will deadlock */
641   overlay = GST_CAIRO_TEXT_OVERLAY (GST_PAD_PARENT (pad));
642
643   GST_DEBUG_OBJECT (overlay, "Text pad unlinked");
644
645   if (overlay->text_collect_data) {
646     gst_collect_pads_remove_pad (overlay->collect, overlay->text_sinkpad);
647     overlay->text_collect_data = NULL;
648   }
649
650   overlay->need_render = TRUE;
651 }
652
653 #define BOX_SHADING_VAL -80
654 #define BOX_XPAD         6
655 #define BOX_YPAD         6
656
657 static inline void
658 gst_text_overlay_shade_y (GstCairoTextOverlay * overlay, guchar * dest,
659     guint dest_stride, gint y0, gint y1)
660 {
661   gint i, j, x0, x1;
662
663   x0 = CLAMP (overlay->text_x0 - BOX_XPAD, 0, overlay->width);
664   x1 = CLAMP (overlay->text_x1 + BOX_XPAD, 0, overlay->width);
665
666   y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
667   y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
668
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;
672
673       dest[(i * dest_stride) + j] = CLAMP (y, 0, 255);
674     }
675   }
676 }
677
678 static inline void
679 gst_text_overlay_blit_1 (GstCairoTextOverlay * overlay, guchar * dest,
680     guchar * text_image, gint val, guint dest_stride, gint y0)
681 {
682   gint i, j;
683   gint x, a, y;
684   gint y1;
685
686   y = val;
687   y0 = MIN (y0, overlay->height);
688   y1 = MIN (y0 + overlay->font_height, overlay->height);
689
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;
695     }
696   }
697 }
698
699 static inline void
700 gst_text_overlay_blit_sub2x2 (GstCairoTextOverlay * overlay, guchar * dest,
701     guchar * text_image, gint val, guint dest_stride, gint y0)
702 {
703   gint i, j;
704   gint x, a, y;
705   gint y1;
706
707   y0 = MIN (y0, overlay->height);
708   y1 = MIN (y0 + overlay->font_height, overlay->height);
709
710   y = val;
711
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;
720     }
721   }
722 }
723
724
725 static GstFlowReturn
726 gst_text_overlay_push_frame (GstCairoTextOverlay * overlay,
727     GstBuffer * video_frame)
728 {
729   guchar *y, *u, *v;
730   gint ypos;
731
732   video_frame = gst_buffer_make_writable (video_frame);
733
734   switch (overlay->valign) {
735     case GST_CAIRO_TEXT_OVERLAY_VALIGN_BOTTOM:
736       ypos = overlay->height - overlay->font_height - overlay->ypad;
737       break;
738     case GST_CAIRO_TEXT_OVERLAY_VALIGN_BASELINE:
739       ypos = overlay->height - (overlay->font_height - overlay->text_dy)
740           - overlay->ypad;
741       break;
742     case GST_CAIRO_TEXT_OVERLAY_VALIGN_TOP:
743       ypos = overlay->ypad;
744       break;
745     default:
746       ypos = overlay->ypad;
747       break;
748   }
749
750   ypos += overlay->deltay;
751
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);
755
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);
761   }
762
763   /* blit outline text on video image */
764   gst_text_overlay_blit_1 (overlay,
765       y,
766       overlay->text_outline_image, 0, I420_Y_ROWSTRIDE (overlay->width), ypos);
767   gst_text_overlay_blit_sub2x2 (overlay,
768       u,
769       overlay->text_outline_image, 128, I420_U_ROWSTRIDE (overlay->width),
770       ypos);
771   gst_text_overlay_blit_sub2x2 (overlay, v, overlay->text_outline_image, 128,
772       I420_V_ROWSTRIDE (overlay->width), ypos);
773
774   /* blit text on video image */
775   gst_text_overlay_blit_1 (overlay,
776       y,
777       overlay->text_fill_image, 255, I420_Y_ROWSTRIDE (overlay->width), ypos);
778   gst_text_overlay_blit_sub2x2 (overlay,
779       u,
780       overlay->text_fill_image, 128, I420_U_ROWSTRIDE (overlay->width), ypos);
781   gst_text_overlay_blit_sub2x2 (overlay,
782       v,
783       overlay->text_fill_image, 128, I420_V_ROWSTRIDE (overlay->width), ypos);
784
785   return gst_pad_push (overlay->srcpad, video_frame);
786 }
787
788 static void
789 gst_text_overlay_pop_video (GstCairoTextOverlay * overlay)
790 {
791   GstBuffer *buf;
792
793   buf = gst_collect_pads_pop (overlay->collect, overlay->video_collect_data);
794   g_return_if_fail (buf != NULL);
795   gst_buffer_unref (buf);
796 }
797
798 static void
799 gst_text_overlay_pop_text (GstCairoTextOverlay * overlay)
800 {
801   GstBuffer *buf;
802
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);
807   }
808
809   overlay->need_render = TRUE;
810 }
811
812 /* This function is called when there is data on all pads */
813 static GstFlowReturn
814 gst_text_overlay_collected (GstCollectPads * pads, gpointer data)
815 {
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;
821   gchar *text;
822   gint text_len;
823
824   overlay = GST_CAIRO_TEXT_OVERLAY (data);
825
826   GST_DEBUG ("Collecting");
827
828   video_frame = gst_collect_pads_peek (overlay->collect,
829       overlay->video_collect_data);
830
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);
837     }
838     gst_pad_push_event (overlay->srcpad, gst_event_new_eos ());
839     ret = GST_FLOW_UNEXPECTED;
840     goto done;
841   }
842
843   if (GST_BUFFER_TIMESTAMP (video_frame) == GST_CLOCK_TIME_NONE) {
844     g_warning ("%s: video frame has invalid timestamp", G_STRLOC);
845   }
846
847   now = GST_BUFFER_TIMESTAMP (video_frame);
848
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);
854   } else {
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;
858   }
859
860   GST_DEBUG ("Got video frame: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
861       GST_TIME_ARGS (now), GST_TIME_ARGS (frame_end));
862
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);
870     } else {
871       ret = gst_pad_push (overlay->srcpad, video_frame);
872     }
873     gst_text_overlay_pop_video (overlay);
874     video_frame = NULL;
875     goto done;
876   }
877
878   text_buf = gst_collect_pads_peek (overlay->collect,
879       overlay->text_collect_data);
880
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);
886     video_frame = NULL;
887     goto done;
888   }
889
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;
898   }
899
900   txt_end = GST_BUFFER_TIMESTAMP (text_buf) + GST_BUFFER_DURATION (text_buf);
901
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));
904
905   /* if the text buffer is too old, pop it off the
906    * queue and return so we get a new one next time */
907   if (txt_end < now) {
908     GST_DEBUG ("Text buffer too old, popping off the queue");
909     gst_text_overlay_pop_text (overlay);
910     ret = GST_FLOW_OK;
911     goto done;
912   }
913
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);
920     video_frame = NULL;
921     goto done;
922   }
923
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);
929
930   if (text_len > 0) {
931     GST_DEBUG ("Rendering text '%*s'", text_len, text);;
932     gst_text_overlay_render_text (overlay, text, text_len);
933   } else {
934     GST_DEBUG ("No text to render (empty buffer)");
935     gst_text_overlay_render_text (overlay, " ", 1);
936   }
937
938   g_free (text);
939
940   gst_text_overlay_pop_video (overlay);
941   ret = gst_text_overlay_push_frame (overlay, video_frame);
942   video_frame = NULL;
943   goto done;
944
945 done:
946   {
947     if (text_buf)
948       gst_buffer_unref (text_buf);
949
950     if (video_frame)
951       gst_buffer_unref (video_frame);
952
953     return ret;
954   }
955 }
956
957 static gboolean
958 gst_text_overlay_src_event (GstPad * pad, GstEvent * event)
959 {
960   GstCairoTextOverlay *overlay =
961       GST_CAIRO_TEXT_OVERLAY (gst_pad_get_parent (pad));
962   gboolean ret = TRUE;
963
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);
968   }
969   ret &= gst_pad_push_event (overlay->video_sinkpad, event);
970
971   gst_object_unref (overlay);
972   return ret;
973 }
974
975 static gboolean
976 gst_text_overlay_video_event (GstPad * pad, GstEvent * event)
977 {
978   gboolean ret = FALSE;
979   GstCairoTextOverlay *overlay = NULL;
980
981   overlay = GST_CAIRO_TEXT_OVERLAY (gst_pad_get_parent (pad));
982
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);
988   }
989
990   /* now GstCollectPads can take care of the rest, e.g. EOS */
991   ret = overlay->collect_event (pad, event);
992   gst_object_unref (overlay);
993   return ret;
994 }
995
996 static GstStateChangeReturn
997 gst_text_overlay_change_state (GstElement * element, GstStateChange transition)
998 {
999   GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
1000   GstCairoTextOverlay *overlay = GST_CAIRO_TEXT_OVERLAY (element);
1001
1002   switch (transition) {
1003     case GST_STATE_CHANGE_READY_TO_PAUSED:
1004       gst_collect_pads_start (overlay->collect);
1005       break;
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);
1010       break;
1011     default:
1012       break;
1013   }
1014
1015   ret = parent_class->change_state (element, transition);
1016   if (ret == GST_STATE_CHANGE_FAILURE)
1017     return ret;
1018
1019   switch (transition) {
1020     default:
1021       break;
1022   }
1023
1024   return ret;
1025 }