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