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