Merge branch 'master' into 0.11
[platform/upstream/gstreamer.git] / ext / pango / gstbasetextoverlay.c
1 /* GStreamer
2  * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
3  * Copyright (C) <2003> David Schleef <ds@schleef.org>
4  * Copyright (C) <2006> Julien Moutte <julien@moutte.net>
5  * Copyright (C) <2006> Zeeshan Ali <zeeshan.ali@nokia.com>
6  * Copyright (C) <2006-2008> Tim-Philipp Müller <tim centricular net>
7  * Copyright (C) <2009> Young-Ho Cha <ganadist@gmail.com>
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Library General Public
11  * License as published by the Free Software Foundation; either
12  * version 2 of the License, or (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Library General Public License for more details.
18  *
19  * You should have received a copy of the GNU Library General Public
20  * License along with this library; if not, write to the
21  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22  * Boston, MA 02111-1307, USA.
23  */
24
25 /**
26  * SECTION:element-textoverlay
27  * @see_also: #GstTextRender, #GstClockOverlay, #GstTimeOverlay, #GstSubParse
28  *
29  * This plugin renders text on top of a video stream. This can be either
30  * static text or text from buffers received on the text sink pad, e.g.
31  * as produced by the subparse element. If the text sink pad is not linked,
32  * the text set via the "text" property will be rendered. If the text sink
33  * pad is linked, text will be rendered as it is received on that pad,
34  * honouring and matching the buffer timestamps of both input streams.
35  *
36  * The text can contain newline characters and text wrapping is enabled by
37  * default.
38  *
39  * <refsect2>
40  * <title>Example launch lines</title>
41  * |[
42  * gst-launch -v videotestsrc ! textoverlay text="Room A" valign=top halign=left ! xvimagesink
43  * ]| Here is a simple pipeline that displays a static text in the top left
44  * corner of the video picture
45  * |[
46  * gst-launch -v filesrc location=subtitles.srt ! subparse ! txt.   videotestsrc ! timeoverlay ! textoverlay name=txt shaded-background=yes ! xvimagesink
47  * ]| Here is another pipeline that displays subtitles from an .srt subtitle
48  * file, centered at the bottom of the picture and with a rectangular shading
49  * around the text in the background:
50  * <para>
51  * If you do not have such a subtitle file, create one looking like this
52  * in a text editor:
53  * |[
54  * 1
55  * 00:00:03,000 --> 00:00:05,000
56  * Hello? (3-5s)
57  *
58  * 2
59  * 00:00:08,000 --> 00:00:13,000
60  * Yes, this is a subtitle. Don&apos;t
61  * you like it? (8-13s)
62  *
63  * 3
64  * 00:00:18,826 --> 00:01:02,886
65  * Uh? What are you talking about?
66  * I don&apos;t understand  (18-62s)
67  * ]|
68  * </para>
69  * </refsect2>
70  */
71
72 /* FIXME: alloc segment as part of instance struct */
73
74 #ifdef HAVE_CONFIG_H
75 #include <config.h>
76 #endif
77
78 #include <gst/video/video.h>
79
80 #include "gstbasetextoverlay.h"
81 #include "gsttextoverlay.h"
82 #include "gsttimeoverlay.h"
83 #include "gstclockoverlay.h"
84 #include "gsttextrender.h"
85 #include <string.h>
86
87 /* FIXME:
88  *  - use proper strides and offset for I420
89  *  - if text is wider than the video picture, it does not get
90  *    clipped properly during blitting (if wrapping is disabled)
91  *  - make 'shading_value' a property (or enum:  light/normal/dark/verydark)?
92  */
93
94 GST_DEBUG_CATEGORY (pango_debug);
95 #define GST_CAT_DEFAULT pango_debug
96
97 #define DEFAULT_PROP_TEXT       ""
98 #define DEFAULT_PROP_SHADING    FALSE
99 #define DEFAULT_PROP_VALIGNMENT GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE
100 #define DEFAULT_PROP_HALIGNMENT GST_BASE_TEXT_OVERLAY_HALIGN_CENTER
101 #define DEFAULT_PROP_VALIGN     "baseline"
102 #define DEFAULT_PROP_HALIGN     "center"
103 #define DEFAULT_PROP_XPAD       25
104 #define DEFAULT_PROP_YPAD       25
105 #define DEFAULT_PROP_DELTAX     0
106 #define DEFAULT_PROP_DELTAY     0
107 #define DEFAULT_PROP_XPOS       0.5
108 #define DEFAULT_PROP_YPOS       0.5
109 #define DEFAULT_PROP_WRAP_MODE  GST_BASE_TEXT_OVERLAY_WRAP_MODE_WORD_CHAR
110 #define DEFAULT_PROP_FONT_DESC  ""
111 #define DEFAULT_PROP_SILENT     FALSE
112 #define DEFAULT_PROP_LINE_ALIGNMENT GST_BASE_TEXT_OVERLAY_LINE_ALIGN_CENTER
113 #define DEFAULT_PROP_WAIT_TEXT  TRUE
114 #define DEFAULT_PROP_AUTO_ADJUST_SIZE TRUE
115 #define DEFAULT_PROP_VERTICAL_RENDER  FALSE
116 #define DEFAULT_PROP_COLOR      0xffffffff
117 #define DEFAULT_PROP_OUTLINE_COLOR 0xff000000
118
119 /* make a property of me */
120 #define DEFAULT_SHADING_VALUE    -80
121
122 #define MINIMUM_OUTLINE_OFFSET 1.0
123 #define DEFAULT_SCALE_BASIS    640
124
125 #define COMP_Y(ret, r, g, b) \
126 { \
127    ret = (int) (((19595 * r) >> 16) + ((38470 * g) >> 16) + ((7471 * b) >> 16)); \
128    ret = CLAMP (ret, 0, 255); \
129 }
130
131 #define COMP_U(ret, r, g, b) \
132 { \
133    ret = (int) (-((11059 * r) >> 16) - ((21709 * g) >> 16) + ((32768 * b) >> 16) + 128); \
134    ret = CLAMP (ret, 0, 255); \
135 }
136
137 #define COMP_V(ret, r, g, b) \
138 { \
139    ret = (int) (((32768 * r) >> 16) - ((27439 * g) >> 16) - ((5329 * b) >> 16) + 128); \
140    ret = CLAMP (ret, 0, 255); \
141 }
142
143 #define BLEND(ret, alpha, v0, v1) \
144 { \
145         ret = (v0 * alpha + v1 * (255 - alpha)) / 255; \
146 }
147
148 #define OVER(ret, alphaA, Ca, alphaB, Cb, alphaNew)     \
149 { \
150     gint _tmp; \
151     _tmp = (Ca * alphaA + Cb * alphaB * (255 - alphaA) / 255) / alphaNew; \
152     ret = CLAMP (_tmp, 0, 255); \
153 }
154
155 #if G_BYTE_ORDER == G_LITTLE_ENDIAN
156 # define CAIRO_ARGB_A 3
157 # define CAIRO_ARGB_R 2
158 # define CAIRO_ARGB_G 1
159 # define CAIRO_ARGB_B 0
160 #else
161 # define CAIRO_ARGB_A 0
162 # define CAIRO_ARGB_R 1
163 # define CAIRO_ARGB_G 2
164 # define CAIRO_ARGB_B 3
165 #endif
166
167 enum
168 {
169   PROP_0,
170   PROP_TEXT,
171   PROP_SHADING,
172   PROP_VALIGN,                  /* deprecated */
173   PROP_HALIGN,                  /* deprecated */
174   PROP_HALIGNMENT,
175   PROP_VALIGNMENT,
176   PROP_XPAD,
177   PROP_YPAD,
178   PROP_DELTAX,
179   PROP_DELTAY,
180   PROP_XPOS,
181   PROP_YPOS,
182   PROP_WRAP_MODE,
183   PROP_FONT_DESC,
184   PROP_SILENT,
185   PROP_LINE_ALIGNMENT,
186   PROP_WAIT_TEXT,
187   PROP_AUTO_ADJUST_SIZE,
188   PROP_VERTICAL_RENDER,
189   PROP_COLOR,
190   PROP_SHADOW,
191   PROP_OUTLINE_COLOR,
192   PROP_LAST
193 };
194
195 static GstStaticPadTemplate src_template_factory =
196     GST_STATIC_PAD_TEMPLATE ("src",
197     GST_PAD_SRC,
198     GST_PAD_ALWAYS,
199     GST_STATIC_CAPS (GST_VIDEO_CAPS_BGRx ";"
200         GST_VIDEO_CAPS_RGBx ";"
201         GST_VIDEO_CAPS_xRGB ";"
202         GST_VIDEO_CAPS_xBGR ";"
203         GST_VIDEO_CAPS_RGBA ";"
204         GST_VIDEO_CAPS_BGRA ";"
205         GST_VIDEO_CAPS_ARGB ";"
206         GST_VIDEO_CAPS_ABGR ";"
207         GST_VIDEO_CAPS_YUV ("{AYUV, I420, UYVY, NV12, NV21}"))
208     );
209
210 static GstStaticPadTemplate video_sink_template_factory =
211     GST_STATIC_PAD_TEMPLATE ("video_sink",
212     GST_PAD_SINK,
213     GST_PAD_ALWAYS,
214     GST_STATIC_CAPS (GST_VIDEO_CAPS_BGRx ";"
215         GST_VIDEO_CAPS_RGBx ";"
216         GST_VIDEO_CAPS_xRGB ";"
217         GST_VIDEO_CAPS_xBGR ";"
218         GST_VIDEO_CAPS_RGBA ";"
219         GST_VIDEO_CAPS_BGRA ";"
220         GST_VIDEO_CAPS_ARGB ";"
221         GST_VIDEO_CAPS_ABGR ";"
222         GST_VIDEO_CAPS_YUV ("{AYUV, I420, UYVY, NV12, NV21}"))
223     );
224
225 #define GST_TYPE_BASE_TEXT_OVERLAY_VALIGN (gst_base_text_overlay_valign_get_type())
226 static GType
227 gst_base_text_overlay_valign_get_type (void)
228 {
229   static GType base_text_overlay_valign_type = 0;
230   static const GEnumValue base_text_overlay_valign[] = {
231     {GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE, "baseline", "baseline"},
232     {GST_BASE_TEXT_OVERLAY_VALIGN_BOTTOM, "bottom", "bottom"},
233     {GST_BASE_TEXT_OVERLAY_VALIGN_TOP, "top", "top"},
234     {GST_BASE_TEXT_OVERLAY_VALIGN_POS, "position", "position"},
235     {GST_BASE_TEXT_OVERLAY_VALIGN_CENTER, "center", "center"},
236     {0, NULL, NULL},
237   };
238
239   if (!base_text_overlay_valign_type) {
240     base_text_overlay_valign_type =
241         g_enum_register_static ("GstBaseTextOverlayVAlign",
242         base_text_overlay_valign);
243   }
244   return base_text_overlay_valign_type;
245 }
246
247 #define GST_TYPE_BASE_TEXT_OVERLAY_HALIGN (gst_base_text_overlay_halign_get_type())
248 static GType
249 gst_base_text_overlay_halign_get_type (void)
250 {
251   static GType base_text_overlay_halign_type = 0;
252   static const GEnumValue base_text_overlay_halign[] = {
253     {GST_BASE_TEXT_OVERLAY_HALIGN_LEFT, "left", "left"},
254     {GST_BASE_TEXT_OVERLAY_HALIGN_CENTER, "center", "center"},
255     {GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT, "right", "right"},
256     {GST_BASE_TEXT_OVERLAY_HALIGN_POS, "position", "position"},
257     {0, NULL, NULL},
258   };
259
260   if (!base_text_overlay_halign_type) {
261     base_text_overlay_halign_type =
262         g_enum_register_static ("GstBaseTextOverlayHAlign",
263         base_text_overlay_halign);
264   }
265   return base_text_overlay_halign_type;
266 }
267
268
269 #define GST_TYPE_BASE_TEXT_OVERLAY_WRAP_MODE (gst_base_text_overlay_wrap_mode_get_type())
270 static GType
271 gst_base_text_overlay_wrap_mode_get_type (void)
272 {
273   static GType base_text_overlay_wrap_mode_type = 0;
274   static const GEnumValue base_text_overlay_wrap_mode[] = {
275     {GST_BASE_TEXT_OVERLAY_WRAP_MODE_NONE, "none", "none"},
276     {GST_BASE_TEXT_OVERLAY_WRAP_MODE_WORD, "word", "word"},
277     {GST_BASE_TEXT_OVERLAY_WRAP_MODE_CHAR, "char", "char"},
278     {GST_BASE_TEXT_OVERLAY_WRAP_MODE_WORD_CHAR, "wordchar", "wordchar"},
279     {0, NULL, NULL},
280   };
281
282   if (!base_text_overlay_wrap_mode_type) {
283     base_text_overlay_wrap_mode_type =
284         g_enum_register_static ("GstBaseTextOverlayWrapMode",
285         base_text_overlay_wrap_mode);
286   }
287   return base_text_overlay_wrap_mode_type;
288 }
289
290 #define GST_TYPE_BASE_TEXT_OVERLAY_LINE_ALIGN (gst_base_text_overlay_line_align_get_type())
291 static GType
292 gst_base_text_overlay_line_align_get_type (void)
293 {
294   static GType base_text_overlay_line_align_type = 0;
295   static const GEnumValue base_text_overlay_line_align[] = {
296     {GST_BASE_TEXT_OVERLAY_LINE_ALIGN_LEFT, "left", "left"},
297     {GST_BASE_TEXT_OVERLAY_LINE_ALIGN_CENTER, "center", "center"},
298     {GST_BASE_TEXT_OVERLAY_LINE_ALIGN_RIGHT, "right", "right"},
299     {0, NULL, NULL}
300   };
301
302   if (!base_text_overlay_line_align_type) {
303     base_text_overlay_line_align_type =
304         g_enum_register_static ("GstBaseTextOverlayLineAlign",
305         base_text_overlay_line_align);
306   }
307   return base_text_overlay_line_align_type;
308 }
309
310 #define GST_BASE_TEXT_OVERLAY_GET_COND(ov) (((GstBaseTextOverlay *)ov)->cond)
311 #define GST_BASE_TEXT_OVERLAY_WAIT(ov)     (g_cond_wait (GST_BASE_TEXT_OVERLAY_GET_COND (ov), GST_OBJECT_GET_LOCK (ov)))
312 #define GST_BASE_TEXT_OVERLAY_SIGNAL(ov)   (g_cond_signal (GST_BASE_TEXT_OVERLAY_GET_COND (ov)))
313 #define GST_BASE_TEXT_OVERLAY_BROADCAST(ov)(g_cond_broadcast (GST_BASE_TEXT_OVERLAY_GET_COND (ov)))
314
315 static GstElementClass *parent_class = NULL;
316 static void gst_base_text_overlay_base_init (gpointer g_class);
317 static void gst_base_text_overlay_class_init (GstBaseTextOverlayClass * klass);
318 static void gst_base_text_overlay_init (GstBaseTextOverlay * overlay,
319     GstBaseTextOverlayClass * klass);
320
321 static GstStateChangeReturn gst_base_text_overlay_change_state (GstElement *
322     element, GstStateChange transition);
323
324 static GstCaps *gst_base_text_overlay_getcaps (GstPad * pad, GstCaps * filter);
325 static gboolean gst_base_text_overlay_setcaps (GstPad * pad, GstCaps * caps);
326 static gboolean gst_base_text_overlay_setcaps_txt (GstPad * pad,
327     GstCaps * caps);
328 static gboolean gst_base_text_overlay_src_event (GstPad * pad,
329     GstEvent * event);
330 static gboolean gst_base_text_overlay_src_query (GstPad * pad,
331     GstQuery * query);
332
333 static gboolean gst_base_text_overlay_video_event (GstPad * pad,
334     GstEvent * event);
335 static GstFlowReturn gst_base_text_overlay_video_chain (GstPad * pad,
336     GstBuffer * buffer);
337
338 static gboolean gst_base_text_overlay_text_event (GstPad * pad,
339     GstEvent * event);
340 static GstFlowReturn gst_base_text_overlay_text_chain (GstPad * pad,
341     GstBuffer * buffer);
342 static GstPadLinkReturn gst_base_text_overlay_text_pad_link (GstPad * pad,
343     GstPad * peer);
344 static void gst_base_text_overlay_text_pad_unlink (GstPad * pad);
345 static void gst_base_text_overlay_pop_text (GstBaseTextOverlay * overlay);
346 static void gst_base_text_overlay_update_render_mode (GstBaseTextOverlay *
347     overlay);
348
349 static void gst_base_text_overlay_finalize (GObject * object);
350 static void gst_base_text_overlay_set_property (GObject * object, guint prop_id,
351     const GValue * value, GParamSpec * pspec);
352 static void gst_base_text_overlay_get_property (GObject * object, guint prop_id,
353     GValue * value, GParamSpec * pspec);
354 static void
355 gst_base_text_overlay_adjust_values_with_fontdesc (GstBaseTextOverlay * overlay,
356     PangoFontDescription * desc);
357
358 GType
359 gst_base_text_overlay_get_type (void)
360 {
361   static GType type = 0;
362
363   if (g_once_init_enter ((gsize *) & type)) {
364     static const GTypeInfo info = {
365       sizeof (GstBaseTextOverlayClass),
366       (GBaseInitFunc) gst_base_text_overlay_base_init,
367       NULL,
368       (GClassInitFunc) gst_base_text_overlay_class_init,
369       NULL,
370       NULL,
371       sizeof (GstBaseTextOverlay),
372       0,
373       (GInstanceInitFunc) gst_base_text_overlay_init,
374     };
375
376     g_once_init_leave ((gsize *) & type,
377         g_type_register_static (GST_TYPE_ELEMENT, "GstBaseTextOverlay", &info,
378             0));
379   }
380
381   return type;
382 }
383
384 static gchar *
385 gst_base_text_overlay_get_text (GstBaseTextOverlay * overlay,
386     GstBuffer * video_frame)
387 {
388   return g_strdup (overlay->default_text);
389 }
390
391 static void
392 gst_base_text_overlay_base_init (gpointer g_class)
393 {
394   GstBaseTextOverlayClass *klass = GST_BASE_TEXT_OVERLAY_CLASS (g_class);
395   PangoFontMap *fontmap;
396
397   /* Only lock for the subclasses here, the base class
398    * doesn't have this mutex yet and it's not necessary
399    * here */
400   if (klass->pango_lock)
401     g_mutex_lock (klass->pango_lock);
402   fontmap = pango_cairo_font_map_get_default ();
403   klass->pango_context =
404       pango_cairo_font_map_create_context (PANGO_CAIRO_FONT_MAP (fontmap));
405   if (klass->pango_lock)
406     g_mutex_unlock (klass->pango_lock);
407 }
408
409 static void
410 gst_base_text_overlay_class_init (GstBaseTextOverlayClass * klass)
411 {
412   GObjectClass *gobject_class;
413   GstElementClass *gstelement_class;
414
415   gobject_class = (GObjectClass *) klass;
416   gstelement_class = (GstElementClass *) klass;
417
418   parent_class = g_type_class_peek_parent (klass);
419
420   gobject_class->finalize = gst_base_text_overlay_finalize;
421   gobject_class->set_property = gst_base_text_overlay_set_property;
422   gobject_class->get_property = gst_base_text_overlay_get_property;
423
424   gst_element_class_add_pad_template (gstelement_class,
425       gst_static_pad_template_get (&src_template_factory));
426   gst_element_class_add_pad_template (gstelement_class,
427       gst_static_pad_template_get (&video_sink_template_factory));
428
429   gstelement_class->change_state =
430       GST_DEBUG_FUNCPTR (gst_base_text_overlay_change_state);
431
432   klass->pango_lock = g_mutex_new ();
433
434   klass->get_text = gst_base_text_overlay_get_text;
435
436   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TEXT,
437       g_param_spec_string ("text", "text",
438           "Text to be display.", DEFAULT_PROP_TEXT,
439           G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
440   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SHADING,
441       g_param_spec_boolean ("shaded-background", "shaded background",
442           "Whether to shade the background under the text area",
443           DEFAULT_PROP_SHADING, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
444   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VALIGNMENT,
445       g_param_spec_enum ("valignment", "vertical alignment",
446           "Vertical alignment of the text", GST_TYPE_BASE_TEXT_OVERLAY_VALIGN,
447           DEFAULT_PROP_VALIGNMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
448   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_HALIGNMENT,
449       g_param_spec_enum ("halignment", "horizontal alignment",
450           "Horizontal alignment of the text", GST_TYPE_BASE_TEXT_OVERLAY_HALIGN,
451           DEFAULT_PROP_HALIGNMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
452   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VALIGN,
453       g_param_spec_string ("valign", "vertical alignment",
454           "Vertical alignment of the text (deprecated; use valignment)",
455           DEFAULT_PROP_VALIGN, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
456   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_HALIGN,
457       g_param_spec_string ("halign", "horizontal alignment",
458           "Horizontal alignment of the text (deprecated; use halignment)",
459           DEFAULT_PROP_HALIGN, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
460   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_XPAD,
461       g_param_spec_int ("xpad", "horizontal paddding",
462           "Horizontal paddding when using left/right alignment", 0, G_MAXINT,
463           DEFAULT_PROP_XPAD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
464   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_YPAD,
465       g_param_spec_int ("ypad", "vertical padding",
466           "Vertical padding when using top/bottom alignment", 0, G_MAXINT,
467           DEFAULT_PROP_YPAD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
468   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DELTAX,
469       g_param_spec_int ("deltax", "X position modifier",
470           "Shift X position to the left or to the right. Unit is pixels.",
471           G_MININT, G_MAXINT, DEFAULT_PROP_DELTAX,
472           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
473   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DELTAY,
474       g_param_spec_int ("deltay", "Y position modifier",
475           "Shift Y position up or down. Unit is pixels.", G_MININT, G_MAXINT,
476           DEFAULT_PROP_DELTAY, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
477   /**
478    * GstBaseTextOverlay:xpos
479    *
480    * Horizontal position of the rendered text when using positioned alignment.
481    *
482    * Since: 0.10.31
483    **/
484   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_XPOS,
485       g_param_spec_double ("xpos", "horizontal position",
486           "Horizontal position when using position alignment", 0, 1.0,
487           DEFAULT_PROP_XPOS,
488           G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
489   /**
490    * GstBaseTextOverlay:ypos
491    *
492    * Vertical position of the rendered text when using positioned alignment.
493    *
494    * Since: 0.10.31
495    **/
496   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_YPOS,
497       g_param_spec_double ("ypos", "vertical position",
498           "Vertical position when using position alignment", 0, 1.0,
499           DEFAULT_PROP_YPOS,
500           G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
501   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WRAP_MODE,
502       g_param_spec_enum ("wrap-mode", "wrap mode",
503           "Whether to wrap the text and if so how.",
504           GST_TYPE_BASE_TEXT_OVERLAY_WRAP_MODE, DEFAULT_PROP_WRAP_MODE,
505           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
506   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_FONT_DESC,
507       g_param_spec_string ("font-desc", "font description",
508           "Pango font description of font to be used for rendering. "
509           "See documentation of pango_font_description_from_string "
510           "for syntax.", DEFAULT_PROP_FONT_DESC,
511           G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
512   /**
513    * GstBaseTextOverlay:color
514    *
515    * Color of the rendered text.
516    *
517    * Since: 0.10.31
518    **/
519   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_COLOR,
520       g_param_spec_uint ("color", "Color",
521           "Color to use for text (big-endian ARGB).", 0, G_MAXUINT32,
522           DEFAULT_PROP_COLOR,
523           G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
524   /**
525    * GstTextOverlay:outline-color
526    *
527    * Color of the outline of the rendered text.
528    *
529    * Since: 0.10.35
530    **/
531   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_OUTLINE_COLOR,
532       g_param_spec_uint ("outline-color", "Text Outline Color",
533           "Color to use for outline the text (big-endian ARGB).", 0,
534           G_MAXUINT32, DEFAULT_PROP_OUTLINE_COLOR,
535           G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
536
537   /**
538    * GstBaseTextOverlay:line-alignment
539    *
540    * Alignment of text lines relative to each other (for multi-line text)
541    *
542    * Since: 0.10.15
543    **/
544   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_LINE_ALIGNMENT,
545       g_param_spec_enum ("line-alignment", "line alignment",
546           "Alignment of text lines relative to each other.",
547           GST_TYPE_BASE_TEXT_OVERLAY_LINE_ALIGN, DEFAULT_PROP_LINE_ALIGNMENT,
548           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
549   /**
550    * GstBaseTextOverlay:silent
551    *
552    * If set, no text is rendered. Useful to switch off text rendering
553    * temporarily without removing the textoverlay element from the pipeline.
554    *
555    * Since: 0.10.15
556    **/
557   /* FIXME 0.11: rename to "visible" or "text-visible" or "render-text" */
558   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SILENT,
559       g_param_spec_boolean ("silent", "silent",
560           "Whether to render the text string",
561           DEFAULT_PROP_SILENT,
562           G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
563   /**
564    * GstBaseTextOverlay:wait-text
565    *
566    * If set, the video will block until a subtitle is received on the text pad.
567    * If video and subtitles are sent in sync, like from the same demuxer, this
568    * property should be set.
569    *
570    * Since: 0.10.20
571    **/
572   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WAIT_TEXT,
573       g_param_spec_boolean ("wait-text", "Wait Text",
574           "Whether to wait for subtitles",
575           DEFAULT_PROP_WAIT_TEXT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
576
577   g_object_class_install_property (G_OBJECT_CLASS (klass),
578       PROP_AUTO_ADJUST_SIZE, g_param_spec_boolean ("auto-resize", "auto resize",
579           "Automatically adjust font size to screen-size.",
580           DEFAULT_PROP_AUTO_ADJUST_SIZE,
581           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
582
583   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VERTICAL_RENDER,
584       g_param_spec_boolean ("vertical-render", "vertical render",
585           "Vertical Render.", DEFAULT_PROP_VERTICAL_RENDER,
586           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
587 }
588
589 static void
590 gst_base_text_overlay_finalize (GObject * object)
591 {
592   GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
593
594   g_free (overlay->default_text);
595
596   if (overlay->text_image) {
597     g_free (overlay->text_image);
598     overlay->text_image = NULL;
599   }
600
601   if (overlay->layout) {
602     g_object_unref (overlay->layout);
603     overlay->layout = NULL;
604   }
605
606   if (overlay->text_buffer) {
607     gst_buffer_unref (overlay->text_buffer);
608     overlay->text_buffer = NULL;
609   }
610
611   if (overlay->cond) {
612     g_cond_free (overlay->cond);
613     overlay->cond = NULL;
614   }
615
616   G_OBJECT_CLASS (parent_class)->finalize (object);
617 }
618
619 static void
620 gst_base_text_overlay_init (GstBaseTextOverlay * overlay,
621     GstBaseTextOverlayClass * klass)
622 {
623   GstPadTemplate *template;
624   PangoFontDescription *desc;
625
626   /* video sink */
627   template = gst_static_pad_template_get (&video_sink_template_factory);
628   overlay->video_sinkpad = gst_pad_new_from_template (template, "video_sink");
629   gst_object_unref (template);
630   gst_pad_set_getcaps_function (overlay->video_sinkpad,
631       GST_DEBUG_FUNCPTR (gst_base_text_overlay_getcaps));
632   gst_pad_set_setcaps_function (overlay->video_sinkpad,
633       GST_DEBUG_FUNCPTR (gst_base_text_overlay_setcaps));
634   gst_pad_set_event_function (overlay->video_sinkpad,
635       GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_event));
636   gst_pad_set_chain_function (overlay->video_sinkpad,
637       GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_chain));
638   gst_element_add_pad (GST_ELEMENT (overlay), overlay->video_sinkpad);
639
640   template =
641       gst_element_class_get_pad_template (GST_ELEMENT_CLASS (klass),
642       "text_sink");
643   if (template) {
644     /* text sink */
645     overlay->text_sinkpad = gst_pad_new_from_template (template, "text_sink");
646     gst_object_unref (template);
647
648     gst_pad_set_setcaps_function (overlay->text_sinkpad,
649         GST_DEBUG_FUNCPTR (gst_base_text_overlay_setcaps_txt));
650     gst_pad_set_event_function (overlay->text_sinkpad,
651         GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_event));
652     gst_pad_set_chain_function (overlay->text_sinkpad,
653         GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_chain));
654     gst_pad_set_link_function (overlay->text_sinkpad,
655         GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_pad_link));
656     gst_pad_set_unlink_function (overlay->text_sinkpad,
657         GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_pad_unlink));
658     gst_element_add_pad (GST_ELEMENT (overlay), overlay->text_sinkpad);
659   }
660
661   /* (video) source */
662   template = gst_static_pad_template_get (&src_template_factory);
663   overlay->srcpad = gst_pad_new_from_template (template, "src");
664   gst_object_unref (template);
665   gst_pad_set_getcaps_function (overlay->srcpad,
666       GST_DEBUG_FUNCPTR (gst_base_text_overlay_getcaps));
667   gst_pad_set_event_function (overlay->srcpad,
668       GST_DEBUG_FUNCPTR (gst_base_text_overlay_src_event));
669   gst_pad_set_query_function (overlay->srcpad,
670       GST_DEBUG_FUNCPTR (gst_base_text_overlay_src_query));
671   gst_element_add_pad (GST_ELEMENT (overlay), overlay->srcpad);
672
673   g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
674   overlay->line_align = DEFAULT_PROP_LINE_ALIGNMENT;
675   overlay->layout =
676       pango_layout_new (GST_BASE_TEXT_OVERLAY_GET_CLASS
677       (overlay)->pango_context);
678   desc =
679       pango_context_get_font_description (GST_BASE_TEXT_OVERLAY_GET_CLASS
680       (overlay)->pango_context);
681   gst_base_text_overlay_adjust_values_with_fontdesc (overlay, desc);
682
683   overlay->color = DEFAULT_PROP_COLOR;
684   overlay->outline_color = DEFAULT_PROP_OUTLINE_COLOR;
685   overlay->halign = DEFAULT_PROP_HALIGNMENT;
686   overlay->valign = DEFAULT_PROP_VALIGNMENT;
687   overlay->xpad = DEFAULT_PROP_XPAD;
688   overlay->ypad = DEFAULT_PROP_YPAD;
689   overlay->deltax = DEFAULT_PROP_DELTAX;
690   overlay->deltay = DEFAULT_PROP_DELTAY;
691   overlay->xpos = DEFAULT_PROP_XPOS;
692   overlay->ypos = DEFAULT_PROP_YPOS;
693
694   overlay->wrap_mode = DEFAULT_PROP_WRAP_MODE;
695
696   overlay->want_shading = DEFAULT_PROP_SHADING;
697   overlay->shading_value = DEFAULT_SHADING_VALUE;
698   overlay->silent = DEFAULT_PROP_SILENT;
699   overlay->wait_text = DEFAULT_PROP_WAIT_TEXT;
700   overlay->auto_adjust_size = DEFAULT_PROP_AUTO_ADJUST_SIZE;
701
702   overlay->default_text = g_strdup (DEFAULT_PROP_TEXT);
703   overlay->need_render = TRUE;
704   overlay->text_image = NULL;
705   overlay->use_vertical_render = DEFAULT_PROP_VERTICAL_RENDER;
706   gst_base_text_overlay_update_render_mode (overlay);
707
708   overlay->fps_n = 0;
709   overlay->fps_d = 1;
710
711   overlay->text_buffer = NULL;
712   overlay->text_linked = FALSE;
713   overlay->cond = g_cond_new ();
714   gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
715   g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
716 }
717
718 static void
719 gst_base_text_overlay_update_wrap_mode (GstBaseTextOverlay * overlay)
720 {
721   if (overlay->wrap_mode == GST_BASE_TEXT_OVERLAY_WRAP_MODE_NONE) {
722     GST_DEBUG_OBJECT (overlay, "Set wrap mode NONE");
723     pango_layout_set_width (overlay->layout, -1);
724   } else {
725     int width;
726
727     if (overlay->auto_adjust_size) {
728       width = DEFAULT_SCALE_BASIS * PANGO_SCALE;
729       if (overlay->use_vertical_render) {
730         width = width * (overlay->height - overlay->ypad * 2) / overlay->width;
731       }
732     } else {
733       width =
734           (overlay->use_vertical_render ? overlay->height : overlay->width) *
735           PANGO_SCALE;
736     }
737
738     GST_DEBUG_OBJECT (overlay, "Set layout width %d", overlay->width);
739     GST_DEBUG_OBJECT (overlay, "Set wrap mode    %d", overlay->wrap_mode);
740     pango_layout_set_width (overlay->layout, width);
741     pango_layout_set_wrap (overlay->layout, (PangoWrapMode) overlay->wrap_mode);
742   }
743 }
744
745 static void
746 gst_base_text_overlay_update_render_mode (GstBaseTextOverlay * overlay)
747 {
748   PangoMatrix matrix = PANGO_MATRIX_INIT;
749   PangoContext *context = pango_layout_get_context (overlay->layout);
750
751   if (overlay->use_vertical_render) {
752     pango_matrix_rotate (&matrix, -90);
753     pango_context_set_base_gravity (context, PANGO_GRAVITY_AUTO);
754     pango_context_set_matrix (context, &matrix);
755     pango_layout_set_alignment (overlay->layout, PANGO_ALIGN_LEFT);
756   } else {
757     pango_context_set_base_gravity (context, PANGO_GRAVITY_SOUTH);
758     pango_context_set_matrix (context, &matrix);
759     pango_layout_set_alignment (overlay->layout, overlay->line_align);
760   }
761 }
762
763 static gboolean
764 gst_base_text_overlay_setcaps_txt (GstPad * pad, GstCaps * caps)
765 {
766   GstBaseTextOverlay *overlay;
767   GstStructure *structure;
768
769   overlay = GST_BASE_TEXT_OVERLAY (gst_pad_get_parent (pad));
770
771   structure = gst_caps_get_structure (caps, 0);
772   overlay->have_pango_markup =
773       gst_structure_has_name (structure, "text/x-pango-markup");
774
775   gst_object_unref (overlay);
776
777   return TRUE;
778 }
779
780 /* FIXME: upstream nego (e.g. when the video window is resized) */
781
782 static gboolean
783 gst_base_text_overlay_setcaps (GstPad * pad, GstCaps * caps)
784 {
785   GstBaseTextOverlay *overlay;
786   GstStructure *structure;
787   gboolean ret = FALSE;
788   const GValue *fps;
789
790   if (!GST_PAD_IS_SINK (pad))
791     return TRUE;
792
793   g_return_val_if_fail (gst_caps_is_fixed (caps), FALSE);
794
795   overlay = GST_BASE_TEXT_OVERLAY (gst_pad_get_parent (pad));
796
797   overlay->width = 0;
798   overlay->height = 0;
799   structure = gst_caps_get_structure (caps, 0);
800   fps = gst_structure_get_value (structure, "framerate");
801
802   if (fps
803       && gst_video_format_parse_caps (caps, &overlay->format, &overlay->width,
804           &overlay->height)) {
805     ret = gst_pad_set_caps (overlay->srcpad, caps);
806   }
807
808   overlay->fps_n = gst_value_get_fraction_numerator (fps);
809   overlay->fps_d = gst_value_get_fraction_denominator (fps);
810
811   if (ret) {
812     GST_OBJECT_LOCK (overlay);
813     g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
814     gst_base_text_overlay_update_wrap_mode (overlay);
815     g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
816     GST_OBJECT_UNLOCK (overlay);
817   }
818
819   gst_object_unref (overlay);
820
821   return ret;
822 }
823
824 static void
825 gst_base_text_overlay_set_property (GObject * object, guint prop_id,
826     const GValue * value, GParamSpec * pspec)
827 {
828   GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
829
830   GST_OBJECT_LOCK (overlay);
831   switch (prop_id) {
832     case PROP_TEXT:
833       g_free (overlay->default_text);
834       overlay->default_text = g_value_dup_string (value);
835       overlay->need_render = TRUE;
836       break;
837     case PROP_SHADING:
838       overlay->want_shading = g_value_get_boolean (value);
839       break;
840     case PROP_XPAD:
841       overlay->xpad = g_value_get_int (value);
842       break;
843     case PROP_YPAD:
844       overlay->ypad = g_value_get_int (value);
845       break;
846     case PROP_DELTAX:
847       overlay->deltax = g_value_get_int (value);
848       break;
849     case PROP_DELTAY:
850       overlay->deltay = g_value_get_int (value);
851       break;
852     case PROP_XPOS:
853       overlay->xpos = g_value_get_double (value);
854       break;
855     case PROP_YPOS:
856       overlay->ypos = g_value_get_double (value);
857       break;
858     case PROP_HALIGN:{
859       const gchar *s = g_value_get_string (value);
860
861       if (s && g_ascii_strcasecmp (s, "left") == 0)
862         overlay->halign = GST_BASE_TEXT_OVERLAY_HALIGN_LEFT;
863       else if (s && g_ascii_strcasecmp (s, "center") == 0)
864         overlay->halign = GST_BASE_TEXT_OVERLAY_HALIGN_CENTER;
865       else if (s && g_ascii_strcasecmp (s, "right") == 0)
866         overlay->halign = GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT;
867       else
868         g_warning ("Invalid value '%s' for textoverlay property 'halign'",
869             GST_STR_NULL (s));
870       break;
871     }
872     case PROP_VALIGN:{
873       const gchar *s = g_value_get_string (value);
874
875       if (s && g_ascii_strcasecmp (s, "baseline") == 0)
876         overlay->valign = GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE;
877       else if (s && g_ascii_strcasecmp (s, "bottom") == 0)
878         overlay->valign = GST_BASE_TEXT_OVERLAY_VALIGN_BOTTOM;
879       else if (s && g_ascii_strcasecmp (s, "top") == 0)
880         overlay->valign = GST_BASE_TEXT_OVERLAY_VALIGN_TOP;
881       else
882         g_warning ("Invalid value '%s' for textoverlay property 'valign'",
883             GST_STR_NULL (s));
884       break;
885     }
886     case PROP_VALIGNMENT:
887       overlay->valign = g_value_get_enum (value);
888       break;
889     case PROP_HALIGNMENT:
890       overlay->halign = g_value_get_enum (value);
891       break;
892     case PROP_WRAP_MODE:
893       overlay->wrap_mode = g_value_get_enum (value);
894       g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
895       gst_base_text_overlay_update_wrap_mode (overlay);
896       g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
897       break;
898     case PROP_FONT_DESC:
899     {
900       PangoFontDescription *desc;
901       const gchar *fontdesc_str;
902
903       fontdesc_str = g_value_get_string (value);
904       g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
905       desc = pango_font_description_from_string (fontdesc_str);
906       if (desc) {
907         GST_LOG_OBJECT (overlay, "font description set: %s", fontdesc_str);
908         pango_layout_set_font_description (overlay->layout, desc);
909         gst_base_text_overlay_adjust_values_with_fontdesc (overlay, desc);
910         pango_font_description_free (desc);
911       } else {
912         GST_WARNING_OBJECT (overlay, "font description parse failed: %s",
913             fontdesc_str);
914       }
915       g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
916       break;
917     }
918     case PROP_COLOR:
919       overlay->color = g_value_get_uint (value);
920       break;
921     case PROP_OUTLINE_COLOR:
922       overlay->outline_color = g_value_get_uint (value);
923       break;
924     case PROP_SILENT:
925       overlay->silent = g_value_get_boolean (value);
926       break;
927     case PROP_LINE_ALIGNMENT:
928       overlay->line_align = g_value_get_enum (value);
929       g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
930       pango_layout_set_alignment (overlay->layout,
931           (PangoAlignment) overlay->line_align);
932       g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
933       break;
934     case PROP_WAIT_TEXT:
935       overlay->wait_text = g_value_get_boolean (value);
936       break;
937     case PROP_AUTO_ADJUST_SIZE:
938       overlay->auto_adjust_size = g_value_get_boolean (value);
939       overlay->need_render = TRUE;
940       break;
941     case PROP_VERTICAL_RENDER:
942       overlay->use_vertical_render = g_value_get_boolean (value);
943       g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
944       gst_base_text_overlay_update_render_mode (overlay);
945       g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
946       overlay->need_render = TRUE;
947       break;
948     default:
949       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
950       break;
951   }
952
953   overlay->need_render = TRUE;
954   GST_OBJECT_UNLOCK (overlay);
955 }
956
957 static void
958 gst_base_text_overlay_get_property (GObject * object, guint prop_id,
959     GValue * value, GParamSpec * pspec)
960 {
961   GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
962
963   GST_OBJECT_LOCK (overlay);
964   switch (prop_id) {
965     case PROP_TEXT:
966       g_value_set_string (value, overlay->default_text);
967       break;
968     case PROP_SHADING:
969       g_value_set_boolean (value, overlay->want_shading);
970       break;
971     case PROP_XPAD:
972       g_value_set_int (value, overlay->xpad);
973       break;
974     case PROP_YPAD:
975       g_value_set_int (value, overlay->ypad);
976       break;
977     case PROP_DELTAX:
978       g_value_set_int (value, overlay->deltax);
979       break;
980     case PROP_DELTAY:
981       g_value_set_int (value, overlay->deltay);
982       break;
983     case PROP_XPOS:
984       g_value_set_double (value, overlay->xpos);
985       break;
986     case PROP_YPOS:
987       g_value_set_double (value, overlay->ypos);
988       break;
989     case PROP_VALIGNMENT:
990       g_value_set_enum (value, overlay->valign);
991       break;
992     case PROP_HALIGNMENT:
993       g_value_set_enum (value, overlay->halign);
994       break;
995     case PROP_WRAP_MODE:
996       g_value_set_enum (value, overlay->wrap_mode);
997       break;
998     case PROP_SILENT:
999       g_value_set_boolean (value, overlay->silent);
1000       break;
1001     case PROP_LINE_ALIGNMENT:
1002       g_value_set_enum (value, overlay->line_align);
1003       break;
1004     case PROP_WAIT_TEXT:
1005       g_value_set_boolean (value, overlay->wait_text);
1006       break;
1007     case PROP_AUTO_ADJUST_SIZE:
1008       g_value_set_boolean (value, overlay->auto_adjust_size);
1009       break;
1010     case PROP_VERTICAL_RENDER:
1011       g_value_set_boolean (value, overlay->use_vertical_render);
1012       break;
1013     case PROP_COLOR:
1014       g_value_set_uint (value, overlay->color);
1015       break;
1016     case PROP_OUTLINE_COLOR:
1017       g_value_set_uint (value, overlay->outline_color);
1018       break;
1019     default:
1020       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1021       break;
1022   }
1023
1024   overlay->need_render = TRUE;
1025   GST_OBJECT_UNLOCK (overlay);
1026 }
1027
1028 static gboolean
1029 gst_base_text_overlay_src_query (GstPad * pad, GstQuery * query)
1030 {
1031   gboolean ret = FALSE;
1032   GstBaseTextOverlay *overlay = NULL;
1033
1034   overlay = GST_BASE_TEXT_OVERLAY (gst_pad_get_parent (pad));
1035
1036   ret = gst_pad_peer_query (overlay->video_sinkpad, query);
1037
1038   gst_object_unref (overlay);
1039
1040   return ret;
1041 }
1042
1043 static gboolean
1044 gst_base_text_overlay_src_event (GstPad * pad, GstEvent * event)
1045 {
1046   gboolean ret = FALSE;
1047   GstBaseTextOverlay *overlay = NULL;
1048
1049   overlay = GST_BASE_TEXT_OVERLAY (gst_pad_get_parent (pad));
1050
1051   switch (GST_EVENT_TYPE (event)) {
1052     case GST_EVENT_SEEK:{
1053       GstSeekFlags flags;
1054
1055       /* We don't handle seek if we have not text pad */
1056       if (!overlay->text_linked) {
1057         GST_DEBUG_OBJECT (overlay, "seek received, pushing upstream");
1058         ret = gst_pad_push_event (overlay->video_sinkpad, event);
1059         goto beach;
1060       }
1061
1062       GST_DEBUG_OBJECT (overlay, "seek received, driving from here");
1063
1064       gst_event_parse_seek (event, NULL, NULL, &flags, NULL, NULL, NULL, NULL);
1065
1066       /* Flush downstream, only for flushing seek */
1067       if (flags & GST_SEEK_FLAG_FLUSH)
1068         gst_pad_push_event (overlay->srcpad, gst_event_new_flush_start ());
1069
1070       /* Mark ourself as flushing, unblock chains */
1071       GST_OBJECT_LOCK (overlay);
1072       overlay->video_flushing = TRUE;
1073       overlay->text_flushing = TRUE;
1074       gst_base_text_overlay_pop_text (overlay);
1075       GST_OBJECT_UNLOCK (overlay);
1076
1077       /* Seek on each sink pad */
1078       gst_event_ref (event);
1079       ret = gst_pad_push_event (overlay->video_sinkpad, event);
1080       if (ret) {
1081         ret = gst_pad_push_event (overlay->text_sinkpad, event);
1082       } else {
1083         gst_event_unref (event);
1084       }
1085       break;
1086     }
1087     default:
1088       if (overlay->text_linked) {
1089         gst_event_ref (event);
1090         ret = gst_pad_push_event (overlay->video_sinkpad, event);
1091         gst_pad_push_event (overlay->text_sinkpad, event);
1092       } else {
1093         ret = gst_pad_push_event (overlay->video_sinkpad, event);
1094       }
1095       break;
1096   }
1097
1098 beach:
1099   gst_object_unref (overlay);
1100
1101   return ret;
1102 }
1103
1104 static GstCaps *
1105 gst_base_text_overlay_getcaps (GstPad * pad, GstCaps * filter)
1106 {
1107   GstBaseTextOverlay *overlay;
1108   GstPad *otherpad;
1109   GstCaps *caps;
1110
1111   overlay = GST_BASE_TEXT_OVERLAY (gst_pad_get_parent (pad));
1112
1113   if (pad == overlay->srcpad)
1114     otherpad = overlay->video_sinkpad;
1115   else
1116     otherpad = overlay->srcpad;
1117
1118   /* we can do what the peer can */
1119   caps = gst_pad_peer_get_caps (otherpad, filter);
1120   if (caps) {
1121     GstCaps *temp, *templ;
1122
1123     GST_DEBUG_OBJECT (pad, "peer caps  %" GST_PTR_FORMAT, caps);
1124
1125     /* filtered against our padtemplate */
1126     templ = gst_pad_get_pad_template_caps (otherpad);
1127     GST_DEBUG_OBJECT (pad, "our template  %" GST_PTR_FORMAT, templ);
1128     temp = gst_caps_intersect_full (caps, templ, GST_CAPS_INTERSECT_FIRST);
1129     GST_DEBUG_OBJECT (pad, "intersected %" GST_PTR_FORMAT, temp);
1130     gst_caps_unref (caps);
1131     gst_caps_unref (templ);
1132     /* this is what we can do */
1133     caps = temp;
1134   } else {
1135     /* no peer, our padtemplate is enough then */
1136     caps = gst_pad_get_pad_template_caps (pad);
1137     if (filter) {
1138       GstCaps *intersection;
1139
1140       intersection =
1141           gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
1142       gst_caps_unref (caps);
1143       caps = intersection;
1144     }
1145   }
1146
1147   GST_DEBUG_OBJECT (overlay, "returning  %" GST_PTR_FORMAT, caps);
1148
1149   gst_object_unref (overlay);
1150
1151   return caps;
1152 }
1153
1154 static void
1155 gst_base_text_overlay_adjust_values_with_fontdesc (GstBaseTextOverlay * overlay,
1156     PangoFontDescription * desc)
1157 {
1158   gint font_size = pango_font_description_get_size (desc) / PANGO_SCALE;
1159   overlay->shadow_offset = (double) (font_size) / 13.0;
1160   overlay->outline_offset = (double) (font_size) / 15.0;
1161   if (overlay->outline_offset < MINIMUM_OUTLINE_OFFSET)
1162     overlay->outline_offset = MINIMUM_OUTLINE_OFFSET;
1163 }
1164
1165 #define CAIRO_UNPREMULTIPLY(a,r,g,b) G_STMT_START { \
1166   b = (a > 0) ? MIN ((b * 255 + a / 2) / a, 255) : 0; \
1167   g = (a > 0) ? MIN ((g * 255 + a / 2) / a, 255) : 0; \
1168   r = (a > 0) ? MIN ((r * 255 + a / 2) / a, 255) : 0; \
1169 } G_STMT_END
1170
1171 static inline void
1172 gst_base_text_overlay_blit_1 (GstBaseTextOverlay * overlay, guchar * dest,
1173     gint xpos, gint ypos, guchar * text_image, guint dest_stride)
1174 {
1175   gint i, j = 0;
1176   gint x, y;
1177   guchar r, g, b, a;
1178   guchar *pimage;
1179   guchar *py;
1180   gint width = overlay->image_width;
1181   gint height = overlay->image_height;
1182
1183   if (xpos < 0) {
1184     xpos = 0;
1185   }
1186
1187   if (xpos + width > overlay->width) {
1188     width = overlay->width - xpos;
1189   }
1190
1191   if (ypos + height > overlay->height) {
1192     height = overlay->height - ypos;
1193   }
1194
1195   dest += (ypos / 1) * dest_stride;
1196
1197   for (i = 0; i < height; i++) {
1198     pimage = text_image + 4 * (i * overlay->image_width);
1199     py = dest + i * dest_stride + xpos;
1200     for (j = 0; j < width; j++) {
1201       b = pimage[CAIRO_ARGB_B];
1202       g = pimage[CAIRO_ARGB_G];
1203       r = pimage[CAIRO_ARGB_R];
1204       a = pimage[CAIRO_ARGB_A];
1205       CAIRO_UNPREMULTIPLY (a, r, g, b);
1206
1207       pimage += 4;
1208       if (a == 0) {
1209         py++;
1210         continue;
1211       }
1212       COMP_Y (y, r, g, b);
1213       x = *py;
1214       BLEND (*py++, a, y, x);
1215     }
1216   }
1217 }
1218
1219 static inline void
1220 gst_base_text_overlay_blit_sub2x2cbcr (GstBaseTextOverlay * overlay,
1221     guchar * destcb, guchar * destcr, gint xpos, gint ypos, guchar * text_image,
1222     guint destcb_stride, guint destcr_stride, guint pix_stride)
1223 {
1224   gint i, j;
1225   gint x, cb, cr;
1226   gushort r, g, b, a;
1227   gushort r1, g1, b1, a1;
1228   guchar *pimage1, *pimage2;
1229   guchar *pcb, *pcr;
1230   gint width = overlay->image_width - 2;
1231   gint height = overlay->image_height - 2;
1232
1233   xpos *= pix_stride;
1234
1235   if (xpos < 0) {
1236     xpos = 0;
1237   }
1238
1239   if (xpos + width > overlay->width) {
1240     width = overlay->width - xpos;
1241   }
1242
1243   if (ypos + height > overlay->height) {
1244     height = overlay->height - ypos;
1245   }
1246
1247   destcb += (ypos / 2) * destcb_stride;
1248   destcr += (ypos / 2) * destcr_stride;
1249
1250   for (i = 0; i < height; i += 2) {
1251     pimage1 = text_image + 4 * (i * overlay->image_width);
1252     pimage2 = pimage1 + 4 * overlay->image_width;
1253     pcb = destcb + (i / 2) * destcb_stride + xpos / 2;
1254     pcr = destcr + (i / 2) * destcr_stride + xpos / 2;
1255     for (j = 0; j < width; j += 2) {
1256       b = pimage1[CAIRO_ARGB_B];
1257       g = pimage1[CAIRO_ARGB_G];
1258       r = pimage1[CAIRO_ARGB_R];
1259       a = pimage1[CAIRO_ARGB_A];
1260       CAIRO_UNPREMULTIPLY (a, r, g, b);
1261       pimage1 += 4;
1262
1263       b1 = pimage1[CAIRO_ARGB_B];
1264       g1 = pimage1[CAIRO_ARGB_G];
1265       r1 = pimage1[CAIRO_ARGB_R];
1266       a1 = pimage1[CAIRO_ARGB_A];
1267       CAIRO_UNPREMULTIPLY (a1, r1, g1, b1);
1268       b += b1;
1269       g += g1;
1270       r += r1;
1271       a += a1;
1272       pimage1 += 4;
1273
1274       b1 = pimage2[CAIRO_ARGB_B];
1275       g1 = pimage2[CAIRO_ARGB_G];
1276       r1 = pimage2[CAIRO_ARGB_R];
1277       a1 = pimage2[CAIRO_ARGB_A];
1278       CAIRO_UNPREMULTIPLY (a1, r1, g1, b1);
1279       b += b1;
1280       g += g1;
1281       r += r1;
1282       a += a1;
1283       pimage2 += 4;
1284
1285       /* + 2 for rounding */
1286       b1 = pimage2[CAIRO_ARGB_B];
1287       g1 = pimage2[CAIRO_ARGB_G];
1288       r1 = pimage2[CAIRO_ARGB_R];
1289       a1 = pimage2[CAIRO_ARGB_A];
1290       CAIRO_UNPREMULTIPLY (a1, r1, g1, b1);
1291       b += b1 + 2;
1292       g += g1 + 2;
1293       r += r1 + 2;
1294       a += a1 + 2;
1295       pimage2 += 4;
1296
1297       b /= 4;
1298       g /= 4;
1299       r /= 4;
1300       a /= 4;
1301
1302       if (a == 0) {
1303         pcb += pix_stride;
1304         pcr += pix_stride;
1305         continue;
1306       }
1307       COMP_U (cb, r, g, b);
1308       COMP_V (cr, r, g, b);
1309
1310       x = *pcb;
1311       BLEND (*pcb, a, cb, x);
1312       x = *pcr;
1313       BLEND (*pcr, a, cr, x);
1314
1315       pcb += pix_stride;
1316       pcr += pix_stride;
1317     }
1318   }
1319 }
1320
1321 static void
1322 gst_base_text_overlay_render_pangocairo (GstBaseTextOverlay * overlay,
1323     const gchar * string, gint textlen)
1324 {
1325   cairo_t *cr;
1326   cairo_surface_t *surface;
1327   PangoRectangle ink_rect, logical_rect;
1328   cairo_matrix_t cairo_matrix;
1329   int width, height;
1330   double scalef = 1.0;
1331   double a, r, g, b;
1332
1333   g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1334
1335   if (overlay->auto_adjust_size) {
1336     /* 640 pixel is default */
1337     scalef = (double) (overlay->width) / DEFAULT_SCALE_BASIS;
1338   }
1339   pango_layout_set_width (overlay->layout, -1);
1340   /* set text on pango layout */
1341   pango_layout_set_markup (overlay->layout, string, textlen);
1342
1343   /* get subtitle image size */
1344   pango_layout_get_pixel_extents (overlay->layout, &ink_rect, &logical_rect);
1345
1346   width = (logical_rect.width + overlay->shadow_offset) * scalef;
1347
1348   if (width + overlay->deltax >
1349       (overlay->use_vertical_render ? overlay->height : overlay->width)) {
1350     /*
1351      * subtitle image width is larger then overlay width
1352      * so rearrange overlay wrap mode.
1353      */
1354     gst_base_text_overlay_update_wrap_mode (overlay);
1355     pango_layout_get_pixel_extents (overlay->layout, &ink_rect, &logical_rect);
1356     width = overlay->width;
1357   }
1358
1359   height =
1360       (logical_rect.height + logical_rect.y + overlay->shadow_offset) * scalef;
1361   if (height > overlay->height) {
1362     height = overlay->height;
1363   }
1364   if (overlay->use_vertical_render) {
1365     PangoRectangle rect;
1366     PangoContext *context;
1367     PangoMatrix matrix = PANGO_MATRIX_INIT;
1368     int tmp;
1369
1370     context = pango_layout_get_context (overlay->layout);
1371
1372     pango_matrix_rotate (&matrix, -90);
1373
1374     rect.x = rect.y = 0;
1375     rect.width = width;
1376     rect.height = height;
1377     pango_matrix_transform_pixel_rectangle (&matrix, &rect);
1378     matrix.x0 = -rect.x;
1379     matrix.y0 = -rect.y;
1380
1381     pango_context_set_matrix (context, &matrix);
1382
1383     cairo_matrix.xx = matrix.xx;
1384     cairo_matrix.yx = matrix.yx;
1385     cairo_matrix.xy = matrix.xy;
1386     cairo_matrix.yy = matrix.yy;
1387     cairo_matrix.x0 = matrix.x0;
1388     cairo_matrix.y0 = matrix.y0;
1389     cairo_matrix_scale (&cairo_matrix, scalef, scalef);
1390
1391     tmp = height;
1392     height = width;
1393     width = tmp;
1394   } else {
1395     cairo_matrix_init_scale (&cairo_matrix, scalef, scalef);
1396   }
1397
1398   /* reallocate surface */
1399   overlay->text_image = g_realloc (overlay->text_image, 4 * width * height);
1400
1401   surface = cairo_image_surface_create_for_data (overlay->text_image,
1402       CAIRO_FORMAT_ARGB32, width, height, width * 4);
1403   cr = cairo_create (surface);
1404
1405   /* clear surface */
1406   cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
1407   cairo_paint (cr);
1408
1409   cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
1410
1411   if (overlay->want_shading)
1412     cairo_paint_with_alpha (cr, overlay->shading_value);
1413
1414   /* apply transformations */
1415   cairo_set_matrix (cr, &cairo_matrix);
1416
1417   /* FIXME: We use show_layout everywhere except for the surface
1418    * because it's really faster and internally does all kinds of
1419    * caching. Unfortunately we have to paint to a cairo path for
1420    * the outline and this is slow. Once Pango supports user fonts
1421    * we should use them, see
1422    * https://bugzilla.gnome.org/show_bug.cgi?id=598695
1423    *
1424    * Idea would the be, to create a cairo user font that
1425    * does shadow, outline, text painting in the
1426    * render_glyph function.
1427    */
1428
1429   /* draw shadow text */
1430   cairo_save (cr);
1431   cairo_translate (cr, overlay->shadow_offset, overlay->shadow_offset);
1432   cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.5);
1433   pango_cairo_show_layout (cr, overlay->layout);
1434   cairo_restore (cr);
1435
1436   a = (overlay->outline_color >> 24) & 0xff;
1437   r = (overlay->outline_color >> 16) & 0xff;
1438   g = (overlay->outline_color >> 8) & 0xff;
1439   b = (overlay->outline_color >> 0) & 0xff;
1440
1441   /* draw outline text */
1442   cairo_save (cr);
1443   cairo_set_source_rgba (cr, r / 255.0, g / 255.0, b / 255.0, a / 255.0);
1444   cairo_set_line_width (cr, overlay->outline_offset);
1445   pango_cairo_layout_path (cr, overlay->layout);
1446   cairo_stroke (cr);
1447   cairo_restore (cr);
1448
1449   a = (overlay->color >> 24) & 0xff;
1450   r = (overlay->color >> 16) & 0xff;
1451   g = (overlay->color >> 8) & 0xff;
1452   b = (overlay->color >> 0) & 0xff;
1453
1454   /* draw text */
1455   cairo_save (cr);
1456   cairo_set_source_rgba (cr, r / 255.0, g / 255.0, b / 255.0, a / 255.0);
1457   pango_cairo_show_layout (cr, overlay->layout);
1458   cairo_restore (cr);
1459
1460   cairo_destroy (cr);
1461   cairo_surface_destroy (surface);
1462   overlay->image_width = width;
1463   overlay->image_height = height;
1464   overlay->baseline_y = ink_rect.y;
1465   g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1466 }
1467
1468 #define BOX_XPAD         6
1469 #define BOX_YPAD         6
1470
1471 static inline void
1472 gst_base_text_overlay_shade_planar_Y (GstBaseTextOverlay * overlay,
1473     guchar * dest, gint x0, gint x1, gint y0, gint y1)
1474 {
1475   gint i, j, dest_stride;
1476
1477   dest_stride = gst_video_format_get_row_stride (overlay->format, 0,
1478       overlay->width);
1479
1480   x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);
1481   x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);
1482
1483   y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
1484   y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
1485
1486   for (i = y0; i < y1; ++i) {
1487     for (j = x0; j < x1; ++j) {
1488       gint y = dest[(i * dest_stride) + j] + overlay->shading_value;
1489
1490       dest[(i * dest_stride) + j] = CLAMP (y, 0, 255);
1491     }
1492   }
1493 }
1494
1495 static inline void
1496 gst_base_text_overlay_shade_packed_Y (GstBaseTextOverlay * overlay,
1497     guchar * dest, gint x0, gint x1, gint y0, gint y1)
1498 {
1499   gint i, j;
1500   guint dest_stride, pixel_stride, component_offset;
1501
1502   dest_stride = gst_video_format_get_row_stride (overlay->format, 0,
1503       overlay->width);
1504   pixel_stride = gst_video_format_get_pixel_stride (overlay->format, 0);
1505   component_offset =
1506       gst_video_format_get_component_offset (overlay->format, 0, overlay->width,
1507       overlay->height);
1508
1509   x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);
1510   x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);
1511
1512   y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
1513   y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
1514
1515   if (x0 != 0)
1516     x0 = gst_video_format_get_component_width (overlay->format, 0, x0);
1517   if (x1 != 0)
1518     x1 = gst_video_format_get_component_width (overlay->format, 0, x1);
1519
1520   if (y0 != 0)
1521     y0 = gst_video_format_get_component_height (overlay->format, 0, y0);
1522   if (y1 != 0)
1523     y1 = gst_video_format_get_component_height (overlay->format, 0, y1);
1524
1525   for (i = y0; i < y1; i++) {
1526     for (j = x0; j < x1; j++) {
1527       gint y;
1528       gint y_pos;
1529
1530       y_pos = (i * dest_stride) + j * pixel_stride + component_offset;
1531       y = dest[y_pos] + overlay->shading_value;
1532
1533       dest[y_pos] = CLAMP (y, 0, 255);
1534     }
1535   }
1536 }
1537
1538 #define gst_base_text_overlay_shade_BGRx gst_base_text_overlay_shade_xRGB
1539 #define gst_base_text_overlay_shade_RGBx gst_base_text_overlay_shade_xRGB
1540 #define gst_base_text_overlay_shade_xBGR gst_base_text_overlay_shade_xRGB
1541 static inline void
1542 gst_base_text_overlay_shade_xRGB (GstBaseTextOverlay * overlay, guchar * dest,
1543     gint x0, gint x1, gint y0, gint y1)
1544 {
1545   gint i, j;
1546
1547   x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);
1548   x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);
1549
1550   y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
1551   y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
1552
1553   for (i = y0; i < y1; i++) {
1554     for (j = x0; j < x1; j++) {
1555       gint y, y_pos, k;
1556
1557       y_pos = (i * 4 * overlay->width) + j * 4;
1558       for (k = 0; k < 4; k++) {
1559         y = dest[y_pos + k] + overlay->shading_value;
1560         dest[y_pos + k] = CLAMP (y, 0, 255);
1561       }
1562     }
1563   }
1564 }
1565
1566 #define ARGB_SHADE_FUNCTION(name, OFFSET)       \
1567 static inline void \
1568 gst_base_text_overlay_shade_##name (GstBaseTextOverlay * overlay, guchar * dest, \
1569 gint x0, gint x1, gint y0, gint y1) \
1570 { \
1571   gint i, j;\
1572   \
1573   x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);\
1574   x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);\
1575   \
1576   y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);\
1577   y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);\
1578   \
1579   for (i = y0; i < y1; i++) {\
1580     for (j = x0; j < x1; j++) {\
1581       gint y, y_pos, k;\
1582       y_pos = (i * 4 * overlay->width) + j * 4;\
1583       for (k = OFFSET; k < 3+OFFSET; k++) {\
1584         y = dest[y_pos + k] + overlay->shading_value;\
1585         dest[y_pos + k] = CLAMP (y, 0, 255);\
1586       }\
1587     }\
1588   }\
1589 }
1590 ARGB_SHADE_FUNCTION (ARGB, 1);
1591 ARGB_SHADE_FUNCTION (ABGR, 1);
1592 ARGB_SHADE_FUNCTION (RGBA, 0);
1593 ARGB_SHADE_FUNCTION (BGRA, 0);
1594
1595
1596 /* FIXME:
1597  *  - use proper strides and offset for I420
1598  *  - don't draw over the edge of the picture (try a longer
1599  *    text with a huge font size)
1600  */
1601
1602 static inline void
1603 gst_base_text_overlay_blit_NV12_NV21 (GstBaseTextOverlay * overlay,
1604     guint8 * yuv_pixels, gint xpos, gint ypos)
1605 {
1606   int y_stride, uv_stride;
1607   int u_offset, v_offset;
1608   int h, w;
1609
1610   /* because U/V is 2x2 subsampled, we need to round, either up or down,
1611    * to a boundary of integer number of U/V pixels:
1612    */
1613   xpos = GST_ROUND_UP_2 (xpos);
1614   ypos = GST_ROUND_UP_2 (ypos);
1615
1616   w = overlay->width;
1617   h = overlay->height;
1618
1619   y_stride = gst_video_format_get_row_stride (overlay->format, 0, w);
1620   uv_stride = gst_video_format_get_row_stride (overlay->format, 1, w);
1621   u_offset = gst_video_format_get_component_offset (overlay->format, 1, w, h);
1622   v_offset = gst_video_format_get_component_offset (overlay->format, 2, w, h);
1623
1624   gst_base_text_overlay_blit_1 (overlay, yuv_pixels, xpos, ypos,
1625       overlay->text_image, y_stride);
1626   gst_base_text_overlay_blit_sub2x2cbcr (overlay, yuv_pixels + u_offset,
1627       yuv_pixels + v_offset, xpos, ypos, overlay->text_image, uv_stride,
1628       uv_stride, 2);
1629 }
1630
1631 static inline void
1632 gst_base_text_overlay_blit_I420 (GstBaseTextOverlay * overlay,
1633     guint8 * yuv_pixels, gint xpos, gint ypos)
1634 {
1635   int y_stride, u_stride, v_stride;
1636   int u_offset, v_offset;
1637   int h, w;
1638
1639   /* because U/V is 2x2 subsampled, we need to round, either up or down,
1640    * to a boundary of integer number of U/V pixels:
1641    */
1642   xpos = GST_ROUND_UP_2 (xpos);
1643   ypos = GST_ROUND_UP_2 (ypos);
1644
1645   w = overlay->width;
1646   h = overlay->height;
1647
1648   y_stride = gst_video_format_get_row_stride (GST_VIDEO_FORMAT_I420, 0, w);
1649   u_stride = gst_video_format_get_row_stride (GST_VIDEO_FORMAT_I420, 1, w);
1650   v_stride = gst_video_format_get_row_stride (GST_VIDEO_FORMAT_I420, 2, w);
1651   u_offset =
1652       gst_video_format_get_component_offset (GST_VIDEO_FORMAT_I420, 1, w, h);
1653   v_offset =
1654       gst_video_format_get_component_offset (GST_VIDEO_FORMAT_I420, 2, w, h);
1655
1656   gst_base_text_overlay_blit_1 (overlay, yuv_pixels, xpos, ypos,
1657       overlay->text_image, y_stride);
1658   gst_base_text_overlay_blit_sub2x2cbcr (overlay, yuv_pixels + u_offset,
1659       yuv_pixels + v_offset, xpos, ypos, overlay->text_image, u_stride,
1660       v_stride, 1);
1661 }
1662
1663 static inline void
1664 gst_base_text_overlay_blit_UYVY (GstBaseTextOverlay * overlay,
1665     guint8 * yuv_pixels, gint xpos, gint ypos)
1666 {
1667   int a0, r0, g0, b0;
1668   int a1, r1, g1, b1;
1669   int y0, y1, u, v;
1670   int i, j;
1671   int h, w;
1672   guchar *pimage, *dest;
1673
1674   /* because U/V is 2x horizontally subsampled, we need to round to a
1675    * boundary of integer number of U/V pixels in x dimension:
1676    */
1677   xpos = GST_ROUND_UP_2 (xpos);
1678
1679   w = overlay->image_width - 2;
1680   h = overlay->image_height - 2;
1681
1682   if (xpos < 0) {
1683     xpos = 0;
1684   }
1685
1686   if (xpos + w > overlay->width) {
1687     w = overlay->width - xpos;
1688   }
1689
1690   if (ypos + h > overlay->height) {
1691     h = overlay->height - ypos;
1692   }
1693
1694   for (i = 0; i < h; i++) {
1695     pimage = overlay->text_image + i * overlay->image_width * 4;
1696     dest = yuv_pixels + (i + ypos) * overlay->width * 2 + xpos * 2;
1697     for (j = 0; j < w; j += 2) {
1698       b0 = pimage[CAIRO_ARGB_B];
1699       g0 = pimage[CAIRO_ARGB_G];
1700       r0 = pimage[CAIRO_ARGB_R];
1701       a0 = pimage[CAIRO_ARGB_A];
1702       CAIRO_UNPREMULTIPLY (a0, r0, g0, b0);
1703       pimage += 4;
1704
1705       b1 = pimage[CAIRO_ARGB_B];
1706       g1 = pimage[CAIRO_ARGB_G];
1707       r1 = pimage[CAIRO_ARGB_R];
1708       a1 = pimage[CAIRO_ARGB_A];
1709       CAIRO_UNPREMULTIPLY (a1, r1, g1, b1);
1710       pimage += 4;
1711
1712       a0 += a1 + 2;
1713       a0 /= 2;
1714       if (a0 == 0) {
1715         dest += 4;
1716         continue;
1717       }
1718
1719       COMP_Y (y0, r0, g0, b0);
1720       COMP_Y (y1, r1, g1, b1);
1721
1722       b0 += b1 + 2;
1723       g0 += g1 + 2;
1724       r0 += r1 + 2;
1725
1726       b0 /= 2;
1727       g0 /= 2;
1728       r0 /= 2;
1729
1730       COMP_U (u, r0, g0, b0);
1731       COMP_V (v, r0, g0, b0);
1732
1733       BLEND (*dest, a0, u, *dest);
1734       dest++;
1735       BLEND (*dest, a0, y0, *dest);
1736       dest++;
1737       BLEND (*dest, a0, v, *dest);
1738       dest++;
1739       BLEND (*dest, a0, y1, *dest);
1740       dest++;
1741     }
1742   }
1743 }
1744
1745 static inline void
1746 gst_base_text_overlay_blit_AYUV (GstBaseTextOverlay * overlay,
1747     guint8 * rgb_pixels, gint xpos, gint ypos)
1748 {
1749   int a, r, g, b, a1;
1750   int y, u, v;
1751   int i, j;
1752   int h, w;
1753   guchar *pimage, *dest;
1754
1755   w = overlay->image_width;
1756   h = overlay->image_height;
1757
1758   if (xpos < 0) {
1759     xpos = 0;
1760   }
1761
1762   if (xpos + w > overlay->width) {
1763     w = overlay->width - xpos;
1764   }
1765
1766   if (ypos + h > overlay->height) {
1767     h = overlay->height - ypos;
1768   }
1769
1770   for (i = 0; i < h; i++) {
1771     pimage = overlay->text_image + i * overlay->image_width * 4;
1772     dest = rgb_pixels + (i + ypos) * 4 * overlay->width + xpos * 4;
1773     for (j = 0; j < w; j++) {
1774       a = pimage[CAIRO_ARGB_A];
1775       b = pimage[CAIRO_ARGB_B];
1776       g = pimage[CAIRO_ARGB_G];
1777       r = pimage[CAIRO_ARGB_R];
1778
1779       CAIRO_UNPREMULTIPLY (a, r, g, b);
1780
1781       // convert background to yuv
1782       COMP_Y (y, r, g, b);
1783       COMP_U (u, r, g, b);
1784       COMP_V (v, r, g, b);
1785
1786       // preform text "OVER" background alpha compositing
1787       a1 = a + (dest[0] * (255 - a)) / 255 + 1; // add 1 to prevent divide by 0
1788       OVER (dest[1], a, y, dest[0], dest[1], a1);
1789       OVER (dest[2], a, u, dest[0], dest[2], a1);
1790       OVER (dest[3], a, v, dest[0], dest[3], a1);
1791       dest[0] = a1 - 1;         // remove the temporary 1 we added
1792
1793       pimage += 4;
1794       dest += 4;
1795     }
1796   }
1797 }
1798
1799 #define xRGB_BLIT_FUNCTION(name, R, G, B) \
1800 static inline void \
1801 gst_base_text_overlay_blit_##name (GstBaseTextOverlay * overlay, \
1802     guint8 * rgb_pixels, gint xpos, gint ypos) \
1803 { \
1804   int a, r, g, b; \
1805   int i, j; \
1806   int h, w; \
1807   guchar *pimage, *dest; \
1808   \
1809   w = overlay->image_width; \
1810   h = overlay->image_height; \
1811   \
1812   if (xpos < 0) { \
1813     xpos = 0; \
1814   } \
1815   \
1816   if (xpos + w > overlay->width) { \
1817     w = overlay->width - xpos; \
1818   } \
1819   \
1820   if (ypos + h > overlay->height) { \
1821     h = overlay->height - ypos; \
1822   } \
1823   \
1824   for (i = 0; i < h; i++) { \
1825     pimage = overlay->text_image + i * overlay->image_width * 4; \
1826     dest = rgb_pixels + (i + ypos) * 4 * overlay->width + xpos * 4; \
1827     for (j = 0; j < w; j++) { \
1828       a = pimage[CAIRO_ARGB_A]; \
1829       b = pimage[CAIRO_ARGB_B]; \
1830       g = pimage[CAIRO_ARGB_G]; \
1831       r = pimage[CAIRO_ARGB_R]; \
1832       CAIRO_UNPREMULTIPLY (a, r, g, b); \
1833       b = (b*a + dest[B] * (255-a)) / 255; \
1834       g = (g*a + dest[G] * (255-a)) / 255; \
1835       r = (r*a + dest[R] * (255-a)) / 255; \
1836       \
1837       dest[B] = b; \
1838       dest[G] = g; \
1839       dest[R] = r; \
1840       pimage += 4; \
1841       dest += 4; \
1842     } \
1843   } \
1844 }
1845 xRGB_BLIT_FUNCTION (xRGB, 1, 2, 3);
1846 xRGB_BLIT_FUNCTION (BGRx, 2, 1, 0);
1847 xRGB_BLIT_FUNCTION (xBGR, 3, 2, 1);
1848 xRGB_BLIT_FUNCTION (RGBx, 0, 1, 2);
1849
1850 #define ARGB_BLIT_FUNCTION(name, A, R, G, B)    \
1851 static inline void \
1852 gst_base_text_overlay_blit_##name (GstBaseTextOverlay * overlay, \
1853     guint8 * rgb_pixels, gint xpos, gint ypos) \
1854 { \
1855   int a, r, g, b, a1;                           \
1856   int i, j; \
1857   int h, w; \
1858   guchar *pimage, *dest; \
1859   \
1860   w = overlay->image_width; \
1861   h = overlay->image_height; \
1862   \
1863   if (xpos < 0) { \
1864     xpos = 0; \
1865   } \
1866   \
1867   if (xpos + w > overlay->width) { \
1868     w = overlay->width - xpos; \
1869   } \
1870   \
1871   if (ypos + h > overlay->height) { \
1872     h = overlay->height - ypos; \
1873   } \
1874   \
1875   for (i = 0; i < h; i++) { \
1876     pimage = overlay->text_image + i * overlay->image_width * 4; \
1877     dest = rgb_pixels + (i + ypos) * 4 * overlay->width + xpos * 4; \
1878     for (j = 0; j < w; j++) { \
1879       a = pimage[CAIRO_ARGB_A]; \
1880       b = pimage[CAIRO_ARGB_B]; \
1881       g = pimage[CAIRO_ARGB_G]; \
1882       r = pimage[CAIRO_ARGB_R]; \
1883       CAIRO_UNPREMULTIPLY (a, r, g, b); \
1884       a1 = a + (dest[A] * (255 - a)) / 255 + 1; \
1885       OVER (dest[R], a, r, dest[0], dest[R], a1); \
1886       OVER (dest[G], a, g, dest[0], dest[G], a1); \
1887       OVER (dest[B], a, b, dest[0], dest[B], a1); \
1888       dest[A] = a1 - 1; \
1889       pimage += 4; \
1890       dest += 4; \
1891     } \
1892   } \
1893 }
1894 ARGB_BLIT_FUNCTION (RGBA, 3, 0, 1, 2);
1895 ARGB_BLIT_FUNCTION (BGRA, 3, 2, 1, 0);
1896 ARGB_BLIT_FUNCTION (ARGB, 0, 1, 2, 3);
1897 ARGB_BLIT_FUNCTION (ABGR, 0, 3, 2, 1);
1898
1899 static void
1900 gst_base_text_overlay_render_text (GstBaseTextOverlay * overlay,
1901     const gchar * text, gint textlen)
1902 {
1903   gchar *string;
1904
1905   if (!overlay->need_render) {
1906     GST_DEBUG ("Using previously rendered text.");
1907     return;
1908   }
1909
1910   /* -1 is the whole string */
1911   if (text != NULL && textlen < 0) {
1912     textlen = strlen (text);
1913   }
1914
1915   if (text != NULL) {
1916     string = g_strndup (text, textlen);
1917   } else {                      /* empty string */
1918     string = g_strdup (" ");
1919   }
1920   g_strdelimit (string, "\r\t", ' ');
1921   textlen = strlen (string);
1922
1923   /* FIXME: should we check for UTF-8 here? */
1924
1925   GST_DEBUG ("Rendering '%s'", string);
1926   gst_base_text_overlay_render_pangocairo (overlay, string, textlen);
1927
1928   g_free (string);
1929
1930   overlay->need_render = FALSE;
1931 }
1932
1933 static GstFlowReturn
1934 gst_base_text_overlay_push_frame (GstBaseTextOverlay * overlay,
1935     GstBuffer * video_frame)
1936 {
1937   gint xpos, ypos;
1938   gint width, height;
1939   GstBaseTextOverlayVAlign valign;
1940   GstBaseTextOverlayHAlign halign;
1941   guint8 *data;
1942   gsize size;
1943
1944   width = overlay->image_width;
1945   height = overlay->image_height;
1946
1947   video_frame = gst_buffer_make_writable (video_frame);
1948
1949   data = gst_buffer_map (video_frame, &size, NULL, GST_MAP_WRITE);
1950
1951   if (overlay->use_vertical_render)
1952     halign = GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT;
1953   else
1954     halign = overlay->halign;
1955
1956   switch (halign) {
1957     case GST_BASE_TEXT_OVERLAY_HALIGN_LEFT:
1958       xpos = overlay->xpad;
1959       break;
1960     case GST_BASE_TEXT_OVERLAY_HALIGN_CENTER:
1961       xpos = (overlay->width - width) / 2;
1962       break;
1963     case GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT:
1964       xpos = overlay->width - width - overlay->xpad;
1965       break;
1966     case GST_BASE_TEXT_OVERLAY_HALIGN_POS:
1967       xpos = (gint) (overlay->width * overlay->xpos) - width / 2;
1968       xpos = CLAMP (xpos, 0, overlay->width - width);
1969       if (xpos < 0)
1970         xpos = 0;
1971       break;
1972     default:
1973       xpos = 0;
1974   }
1975   xpos += overlay->deltax;
1976
1977   if (overlay->use_vertical_render)
1978     valign = GST_BASE_TEXT_OVERLAY_VALIGN_TOP;
1979   else
1980     valign = overlay->valign;
1981
1982   switch (valign) {
1983     case GST_BASE_TEXT_OVERLAY_VALIGN_BOTTOM:
1984       ypos = overlay->height - height - overlay->ypad;
1985       break;
1986     case GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE:
1987       ypos = overlay->height - (height + overlay->ypad);
1988       break;
1989     case GST_BASE_TEXT_OVERLAY_VALIGN_TOP:
1990       ypos = overlay->ypad;
1991       break;
1992     case GST_BASE_TEXT_OVERLAY_VALIGN_POS:
1993       ypos = (gint) (overlay->height * overlay->ypos) - height / 2;
1994       ypos = CLAMP (ypos, 0, overlay->height - height);
1995       break;
1996     case GST_BASE_TEXT_OVERLAY_VALIGN_CENTER:
1997       ypos = (overlay->height - height) / 2;
1998       break;
1999     default:
2000       ypos = overlay->ypad;
2001       break;
2002   }
2003   ypos += overlay->deltay;
2004
2005   /* shaded background box */
2006   if (overlay->want_shading) {
2007     switch (overlay->format) {
2008       case GST_VIDEO_FORMAT_I420:
2009       case GST_VIDEO_FORMAT_NV12:
2010       case GST_VIDEO_FORMAT_NV21:
2011         gst_base_text_overlay_shade_planar_Y (overlay, data,
2012             xpos, xpos + overlay->image_width,
2013             ypos, ypos + overlay->image_height);
2014         break;
2015       case GST_VIDEO_FORMAT_AYUV:
2016       case GST_VIDEO_FORMAT_UYVY:
2017         gst_base_text_overlay_shade_packed_Y (overlay, data,
2018             xpos, xpos + overlay->image_width,
2019             ypos, ypos + overlay->image_height);
2020         break;
2021       case GST_VIDEO_FORMAT_xRGB:
2022         gst_base_text_overlay_shade_xRGB (overlay, data,
2023             xpos, xpos + overlay->image_width,
2024             ypos, ypos + overlay->image_height);
2025         break;
2026       case GST_VIDEO_FORMAT_xBGR:
2027         gst_base_text_overlay_shade_xBGR (overlay, data,
2028             xpos, xpos + overlay->image_width,
2029             ypos, ypos + overlay->image_height);
2030         break;
2031       case GST_VIDEO_FORMAT_BGRx:
2032         gst_base_text_overlay_shade_BGRx (overlay, data,
2033             xpos, xpos + overlay->image_width,
2034             ypos, ypos + overlay->image_height);
2035         break;
2036       case GST_VIDEO_FORMAT_RGBx:
2037         gst_base_text_overlay_shade_RGBx (overlay, data,
2038             xpos, xpos + overlay->image_width,
2039             ypos, ypos + overlay->image_height);
2040         break;
2041       case GST_VIDEO_FORMAT_ARGB:
2042         gst_base_text_overlay_shade_ARGB (overlay, data,
2043             xpos, xpos + overlay->image_width,
2044             ypos, ypos + overlay->image_height);
2045         break;
2046       case GST_VIDEO_FORMAT_ABGR:
2047         gst_base_text_overlay_shade_ABGR (overlay, data,
2048             xpos, xpos + overlay->image_width,
2049             ypos, ypos + overlay->image_height);
2050         break;
2051       case GST_VIDEO_FORMAT_RGBA:
2052         gst_base_text_overlay_shade_RGBA (overlay, data,
2053             xpos, xpos + overlay->image_width,
2054             ypos, ypos + overlay->image_height);
2055         break;
2056       case GST_VIDEO_FORMAT_BGRA:
2057         gst_base_text_overlay_shade_BGRA (overlay, data,
2058             xpos, xpos + overlay->image_width,
2059             ypos, ypos + overlay->image_height);
2060         break;
2061       default:
2062         g_assert_not_reached ();
2063     }
2064   }
2065
2066   if (ypos < 0)
2067     ypos = 0;
2068
2069   if (overlay->text_image) {
2070     switch (overlay->format) {
2071       case GST_VIDEO_FORMAT_I420:
2072         gst_base_text_overlay_blit_I420 (overlay, data, xpos, ypos);
2073         break;
2074       case GST_VIDEO_FORMAT_NV12:
2075       case GST_VIDEO_FORMAT_NV21:
2076         gst_base_text_overlay_blit_NV12_NV21 (overlay, data, xpos, ypos);
2077         break;
2078       case GST_VIDEO_FORMAT_UYVY:
2079         gst_base_text_overlay_blit_UYVY (overlay, data, xpos, ypos);
2080         break;
2081       case GST_VIDEO_FORMAT_AYUV:
2082         gst_base_text_overlay_blit_AYUV (overlay, data, xpos, ypos);
2083         break;
2084       case GST_VIDEO_FORMAT_BGRx:
2085         gst_base_text_overlay_blit_BGRx (overlay, data, xpos, ypos);
2086         break;
2087       case GST_VIDEO_FORMAT_xRGB:
2088         gst_base_text_overlay_blit_xRGB (overlay, data, xpos, ypos);
2089         break;
2090       case GST_VIDEO_FORMAT_RGBx:
2091         gst_base_text_overlay_blit_RGBx (overlay, data, xpos, ypos);
2092         break;
2093       case GST_VIDEO_FORMAT_xBGR:
2094         gst_base_text_overlay_blit_xBGR (overlay, data, xpos, ypos);
2095         break;
2096       case GST_VIDEO_FORMAT_ARGB:
2097         gst_base_text_overlay_blit_ARGB (overlay, data, xpos, ypos);
2098         break;
2099       case GST_VIDEO_FORMAT_ABGR:
2100         gst_base_text_overlay_blit_ABGR (overlay, data, xpos, ypos);
2101         break;
2102       case GST_VIDEO_FORMAT_RGBA:
2103         gst_base_text_overlay_blit_RGBA (overlay, data, xpos, ypos);
2104         break;
2105       case GST_VIDEO_FORMAT_BGRA:
2106         gst_base_text_overlay_blit_BGRA (overlay, data, xpos, ypos);
2107         break;
2108       default:
2109         g_assert_not_reached ();
2110     }
2111   }
2112   gst_buffer_unmap (video_frame, data, size);
2113
2114   return gst_pad_push (overlay->srcpad, video_frame);
2115 }
2116
2117 static GstPadLinkReturn
2118 gst_base_text_overlay_text_pad_link (GstPad * pad, GstPad * peer)
2119 {
2120   GstBaseTextOverlay *overlay;
2121
2122   overlay = GST_BASE_TEXT_OVERLAY (gst_pad_get_parent (pad));
2123
2124   GST_DEBUG_OBJECT (overlay, "Text pad linked");
2125
2126   overlay->text_linked = TRUE;
2127
2128   gst_object_unref (overlay);
2129
2130   return GST_PAD_LINK_OK;
2131 }
2132
2133 static void
2134 gst_base_text_overlay_text_pad_unlink (GstPad * pad)
2135 {
2136   GstBaseTextOverlay *overlay;
2137
2138   /* don't use gst_pad_get_parent() here, will deadlock */
2139   overlay = GST_BASE_TEXT_OVERLAY (GST_PAD_PARENT (pad));
2140
2141   GST_DEBUG_OBJECT (overlay, "Text pad unlinked");
2142
2143   overlay->text_linked = FALSE;
2144
2145   gst_segment_init (&overlay->text_segment, GST_FORMAT_UNDEFINED);
2146 }
2147
2148 static gboolean
2149 gst_base_text_overlay_text_event (GstPad * pad, GstEvent * event)
2150 {
2151   gboolean ret = FALSE;
2152   GstBaseTextOverlay *overlay = NULL;
2153
2154   overlay = GST_BASE_TEXT_OVERLAY (gst_pad_get_parent (pad));
2155
2156   GST_LOG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
2157
2158   switch (GST_EVENT_TYPE (event)) {
2159     case GST_EVENT_SEGMENT:
2160     {
2161       const GstSegment *segment;
2162
2163       overlay->text_eos = FALSE;
2164
2165       gst_event_parse_segment (event, &segment);
2166
2167       if (segment->format == GST_FORMAT_TIME) {
2168         GST_OBJECT_LOCK (overlay);
2169         gst_segment_copy_into (segment, &overlay->text_segment);
2170         GST_DEBUG_OBJECT (overlay, "TEXT SEGMENT now: %" GST_SEGMENT_FORMAT,
2171             &overlay->text_segment);
2172         GST_OBJECT_UNLOCK (overlay);
2173       } else {
2174         GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL),
2175             ("received non-TIME newsegment event on text input"));
2176       }
2177
2178       gst_event_unref (event);
2179       ret = TRUE;
2180
2181       /* wake up the video chain, it might be waiting for a text buffer or
2182        * a text segment update */
2183       GST_OBJECT_LOCK (overlay);
2184       GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2185       GST_OBJECT_UNLOCK (overlay);
2186       break;
2187     }
2188     case GST_EVENT_FLUSH_STOP:
2189       GST_OBJECT_LOCK (overlay);
2190       GST_INFO_OBJECT (overlay, "text flush stop");
2191       overlay->text_flushing = FALSE;
2192       overlay->text_eos = FALSE;
2193       gst_base_text_overlay_pop_text (overlay);
2194       gst_segment_init (&overlay->text_segment, GST_FORMAT_TIME);
2195       GST_OBJECT_UNLOCK (overlay);
2196       gst_event_unref (event);
2197       ret = TRUE;
2198       break;
2199     case GST_EVENT_FLUSH_START:
2200       GST_OBJECT_LOCK (overlay);
2201       GST_INFO_OBJECT (overlay, "text flush start");
2202       overlay->text_flushing = TRUE;
2203       GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2204       GST_OBJECT_UNLOCK (overlay);
2205       gst_event_unref (event);
2206       ret = TRUE;
2207       break;
2208     case GST_EVENT_EOS:
2209       GST_OBJECT_LOCK (overlay);
2210       overlay->text_eos = TRUE;
2211       GST_INFO_OBJECT (overlay, "text EOS");
2212       /* wake up the video chain, it might be waiting for a text buffer or
2213        * a text segment update */
2214       GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2215       GST_OBJECT_UNLOCK (overlay);
2216       gst_event_unref (event);
2217       ret = TRUE;
2218       break;
2219     default:
2220       ret = gst_pad_event_default (pad, event);
2221       break;
2222   }
2223
2224   gst_object_unref (overlay);
2225
2226   return ret;
2227 }
2228
2229 static gboolean
2230 gst_base_text_overlay_video_event (GstPad * pad, GstEvent * event)
2231 {
2232   gboolean ret = FALSE;
2233   GstBaseTextOverlay *overlay = NULL;
2234
2235   overlay = GST_BASE_TEXT_OVERLAY (gst_pad_get_parent (pad));
2236
2237   GST_DEBUG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
2238
2239   switch (GST_EVENT_TYPE (event)) {
2240     case GST_EVENT_SEGMENT:
2241     {
2242       const GstSegment *segment;
2243
2244       GST_DEBUG_OBJECT (overlay, "received new segment");
2245
2246       gst_event_parse_segment (event, &segment);
2247
2248       if (segment->format == GST_FORMAT_TIME) {
2249         GST_DEBUG_OBJECT (overlay, "VIDEO SEGMENT now: %" GST_SEGMENT_FORMAT,
2250             &overlay->segment);
2251
2252         gst_segment_copy_into (segment, &overlay->segment);
2253       } else {
2254         GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL),
2255             ("received non-TIME newsegment event on video input"));
2256       }
2257
2258       ret = gst_pad_event_default (pad, event);
2259       break;
2260     }
2261     case GST_EVENT_EOS:
2262       GST_OBJECT_LOCK (overlay);
2263       GST_INFO_OBJECT (overlay, "video EOS");
2264       overlay->video_eos = TRUE;
2265       GST_OBJECT_UNLOCK (overlay);
2266       ret = gst_pad_event_default (pad, event);
2267       break;
2268     case GST_EVENT_FLUSH_START:
2269       GST_OBJECT_LOCK (overlay);
2270       GST_INFO_OBJECT (overlay, "video flush start");
2271       overlay->video_flushing = TRUE;
2272       GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2273       GST_OBJECT_UNLOCK (overlay);
2274       ret = gst_pad_event_default (pad, event);
2275       break;
2276     case GST_EVENT_FLUSH_STOP:
2277       GST_OBJECT_LOCK (overlay);
2278       GST_INFO_OBJECT (overlay, "video flush stop");
2279       overlay->video_flushing = FALSE;
2280       overlay->video_eos = FALSE;
2281       gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
2282       GST_OBJECT_UNLOCK (overlay);
2283       ret = gst_pad_event_default (pad, event);
2284       break;
2285     default:
2286       ret = gst_pad_event_default (pad, event);
2287       break;
2288   }
2289
2290   gst_object_unref (overlay);
2291
2292   return ret;
2293 }
2294
2295 /* Called with lock held */
2296 static void
2297 gst_base_text_overlay_pop_text (GstBaseTextOverlay * overlay)
2298 {
2299   g_return_if_fail (GST_IS_BASE_TEXT_OVERLAY (overlay));
2300
2301   if (overlay->text_buffer) {
2302     GST_DEBUG_OBJECT (overlay, "releasing text buffer %p",
2303         overlay->text_buffer);
2304     gst_buffer_unref (overlay->text_buffer);
2305     overlay->text_buffer = NULL;
2306   }
2307
2308   /* Let the text task know we used that buffer */
2309   GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2310 }
2311
2312 /* We receive text buffers here. If they are out of segment we just ignore them.
2313    If the buffer is in our segment we keep it internally except if another one
2314    is already waiting here, in that case we wait that it gets kicked out */
2315 static GstFlowReturn
2316 gst_base_text_overlay_text_chain (GstPad * pad, GstBuffer * buffer)
2317 {
2318   GstFlowReturn ret = GST_FLOW_OK;
2319   GstBaseTextOverlay *overlay = NULL;
2320   gboolean in_seg = FALSE;
2321   guint64 clip_start = 0, clip_stop = 0;
2322
2323   overlay = GST_BASE_TEXT_OVERLAY (GST_PAD_PARENT (pad));
2324
2325   GST_OBJECT_LOCK (overlay);
2326
2327   if (overlay->text_flushing) {
2328     GST_OBJECT_UNLOCK (overlay);
2329     ret = GST_FLOW_WRONG_STATE;
2330     GST_LOG_OBJECT (overlay, "text flushing");
2331     goto beach;
2332   }
2333
2334   if (overlay->text_eos) {
2335     GST_OBJECT_UNLOCK (overlay);
2336     ret = GST_FLOW_UNEXPECTED;
2337     GST_LOG_OBJECT (overlay, "text EOS");
2338     goto beach;
2339   }
2340
2341   GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT "  BUFFER: ts=%"
2342       GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment,
2343       GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)),
2344       GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer) +
2345           GST_BUFFER_DURATION (buffer)));
2346
2347   if (G_LIKELY (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))) {
2348     GstClockTime stop;
2349
2350     if (G_LIKELY (GST_BUFFER_DURATION_IS_VALID (buffer)))
2351       stop = GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer);
2352     else
2353       stop = GST_CLOCK_TIME_NONE;
2354
2355     in_seg = gst_segment_clip (&overlay->text_segment, GST_FORMAT_TIME,
2356         GST_BUFFER_TIMESTAMP (buffer), stop, &clip_start, &clip_stop);
2357   } else {
2358     in_seg = TRUE;
2359   }
2360
2361   if (in_seg) {
2362     if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2363       GST_BUFFER_TIMESTAMP (buffer) = clip_start;
2364     else if (GST_BUFFER_DURATION_IS_VALID (buffer))
2365       GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
2366
2367     /* Wait for the previous buffer to go away */
2368     while (overlay->text_buffer != NULL) {
2369       GST_DEBUG ("Pad %s:%s has a buffer queued, waiting",
2370           GST_DEBUG_PAD_NAME (pad));
2371       GST_BASE_TEXT_OVERLAY_WAIT (overlay);
2372       GST_DEBUG ("Pad %s:%s resuming", GST_DEBUG_PAD_NAME (pad));
2373       if (overlay->text_flushing) {
2374         GST_OBJECT_UNLOCK (overlay);
2375         ret = GST_FLOW_WRONG_STATE;
2376         goto beach;
2377       }
2378     }
2379
2380     if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2381       overlay->text_segment.position = clip_start;
2382
2383     overlay->text_buffer = buffer;
2384     /* That's a new text buffer we need to render */
2385     overlay->need_render = TRUE;
2386
2387     /* in case the video chain is waiting for a text buffer, wake it up */
2388     GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2389   }
2390
2391   GST_OBJECT_UNLOCK (overlay);
2392
2393 beach:
2394
2395   return ret;
2396 }
2397
2398 static GstFlowReturn
2399 gst_base_text_overlay_video_chain (GstPad * pad, GstBuffer * buffer)
2400 {
2401   GstBaseTextOverlayClass *klass;
2402   GstBaseTextOverlay *overlay;
2403   GstFlowReturn ret = GST_FLOW_OK;
2404   gboolean in_seg = FALSE;
2405   guint64 start, stop, clip_start = 0, clip_stop = 0;
2406   gchar *text = NULL;
2407
2408   overlay = GST_BASE_TEXT_OVERLAY (GST_PAD_PARENT (pad));
2409   klass = GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay);
2410
2411   if (!GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2412     goto missing_timestamp;
2413
2414   /* ignore buffers that are outside of the current segment */
2415   start = GST_BUFFER_TIMESTAMP (buffer);
2416
2417   if (!GST_BUFFER_DURATION_IS_VALID (buffer)) {
2418     stop = GST_CLOCK_TIME_NONE;
2419   } else {
2420     stop = start + GST_BUFFER_DURATION (buffer);
2421   }
2422
2423   GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT "  BUFFER: ts=%"
2424       GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment,
2425       GST_TIME_ARGS (start), GST_TIME_ARGS (stop));
2426
2427   /* segment_clip() will adjust start unconditionally to segment_start if
2428    * no stop time is provided, so handle this ourselves */
2429   if (stop == GST_CLOCK_TIME_NONE && start < overlay->segment.start)
2430     goto out_of_segment;
2431
2432   in_seg = gst_segment_clip (&overlay->segment, GST_FORMAT_TIME, start, stop,
2433       &clip_start, &clip_stop);
2434
2435   if (!in_seg)
2436     goto out_of_segment;
2437
2438   /* if the buffer is only partially in the segment, fix up stamps */
2439   if (clip_start != start || (stop != -1 && clip_stop != stop)) {
2440     GST_DEBUG_OBJECT (overlay, "clipping buffer timestamp/duration to segment");
2441     buffer = gst_buffer_make_writable (buffer);
2442     GST_BUFFER_TIMESTAMP (buffer) = clip_start;
2443     if (stop != -1)
2444       GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
2445   }
2446
2447   /* now, after we've done the clipping, fix up end time if there's no
2448    * duration (we only use those estimated values internally though, we
2449    * don't want to set bogus values on the buffer itself) */
2450   if (stop == -1) {
2451     GstCaps *caps;
2452     GstStructure *s;
2453     gint fps_num, fps_denom;
2454
2455     /* FIXME, store this in setcaps */
2456     caps = gst_pad_get_current_caps (pad);
2457     s = gst_caps_get_structure (caps, 0);
2458     if (gst_structure_get_fraction (s, "framerate", &fps_num, &fps_denom) &&
2459         fps_num && fps_denom) {
2460       GST_DEBUG_OBJECT (overlay, "estimating duration based on framerate");
2461       stop = start + gst_util_uint64_scale_int (GST_SECOND, fps_denom, fps_num);
2462     } else {
2463       GST_WARNING_OBJECT (overlay, "no duration, assuming minimal duration");
2464       stop = start + 1;         /* we need to assume some interval */
2465     }
2466     gst_caps_unref (caps);
2467   }
2468
2469   gst_object_sync_values (G_OBJECT (overlay), GST_BUFFER_TIMESTAMP (buffer));
2470
2471 wait_for_text_buf:
2472
2473   GST_OBJECT_LOCK (overlay);
2474
2475   if (overlay->video_flushing)
2476     goto flushing;
2477
2478   if (overlay->video_eos)
2479     goto have_eos;
2480
2481   if (overlay->silent) {
2482     GST_OBJECT_UNLOCK (overlay);
2483     ret = gst_pad_push (overlay->srcpad, buffer);
2484
2485     /* Update position */
2486     overlay->segment.position = clip_start;
2487
2488     return ret;
2489   }
2490
2491   /* Text pad not linked, rendering internal text */
2492   if (!overlay->text_linked) {
2493     if (klass->get_text) {
2494       text = klass->get_text (overlay, buffer);
2495     } else {
2496       text = g_strdup (overlay->default_text);
2497     }
2498
2499     GST_LOG_OBJECT (overlay, "Text pad not linked, rendering default "
2500         "text: '%s'", GST_STR_NULL (text));
2501
2502     GST_OBJECT_UNLOCK (overlay);
2503
2504     if (text != NULL && *text != '\0') {
2505       /* Render and push */
2506       gst_base_text_overlay_render_text (overlay, text, -1);
2507       ret = gst_base_text_overlay_push_frame (overlay, buffer);
2508     } else {
2509       /* Invalid or empty string */
2510       ret = gst_pad_push (overlay->srcpad, buffer);
2511     }
2512   } else {
2513     /* Text pad linked, check if we have a text buffer queued */
2514     if (overlay->text_buffer) {
2515       gboolean pop_text = FALSE, valid_text_time = TRUE;
2516       GstClockTime text_start = GST_CLOCK_TIME_NONE;
2517       GstClockTime text_end = GST_CLOCK_TIME_NONE;
2518       GstClockTime text_running_time = GST_CLOCK_TIME_NONE;
2519       GstClockTime text_running_time_end = GST_CLOCK_TIME_NONE;
2520       GstClockTime vid_running_time, vid_running_time_end;
2521
2522       /* if the text buffer isn't stamped right, pop it off the
2523        * queue and display it for the current video frame only */
2524       if (!GST_BUFFER_TIMESTAMP_IS_VALID (overlay->text_buffer) ||
2525           !GST_BUFFER_DURATION_IS_VALID (overlay->text_buffer)) {
2526         GST_WARNING_OBJECT (overlay,
2527             "Got text buffer with invalid timestamp or duration");
2528         pop_text = TRUE;
2529         valid_text_time = FALSE;
2530       } else {
2531         text_start = GST_BUFFER_TIMESTAMP (overlay->text_buffer);
2532         text_end = text_start + GST_BUFFER_DURATION (overlay->text_buffer);
2533       }
2534
2535       vid_running_time =
2536           gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2537           start);
2538       vid_running_time_end =
2539           gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2540           stop);
2541
2542       /* If timestamp and duration are valid */
2543       if (valid_text_time) {
2544         text_running_time =
2545             gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2546             text_start);
2547         text_running_time_end =
2548             gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2549             text_end);
2550       }
2551
2552       GST_LOG_OBJECT (overlay, "T: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
2553           GST_TIME_ARGS (text_running_time),
2554           GST_TIME_ARGS (text_running_time_end));
2555       GST_LOG_OBJECT (overlay, "V: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
2556           GST_TIME_ARGS (vid_running_time),
2557           GST_TIME_ARGS (vid_running_time_end));
2558
2559       /* Text too old or in the future */
2560       if (valid_text_time && text_running_time_end <= vid_running_time) {
2561         /* text buffer too old, get rid of it and do nothing  */
2562         GST_LOG_OBJECT (overlay, "text buffer too old, popping");
2563         pop_text = FALSE;
2564         gst_base_text_overlay_pop_text (overlay);
2565         GST_OBJECT_UNLOCK (overlay);
2566         goto wait_for_text_buf;
2567       } else if (valid_text_time && vid_running_time_end <= text_running_time) {
2568         GST_LOG_OBJECT (overlay, "text in future, pushing video buf");
2569         GST_OBJECT_UNLOCK (overlay);
2570         /* Push the video frame */
2571         ret = gst_pad_push (overlay->srcpad, buffer);
2572       } else {
2573         gchar *in_text, *otext;
2574         gsize in_size, osize;
2575
2576         otext =
2577             gst_buffer_map (overlay->text_buffer, &osize, NULL, GST_MAP_READ);
2578         in_text = otext;
2579         in_size = osize;
2580
2581         /* g_markup_escape_text() absolutely requires valid UTF8 input, it
2582          * might crash otherwise. We don't fall back on GST_SUBTITLE_ENCODING
2583          * here on purpose, this is something that needs fixing upstream */
2584         if (!g_utf8_validate (in_text, in_size, NULL)) {
2585           const gchar *end = NULL;
2586
2587           GST_WARNING_OBJECT (overlay, "received invalid UTF-8");
2588           in_text = g_strndup (in_text, in_size);
2589           while (!g_utf8_validate (in_text, in_size, &end) && end)
2590             *((gchar *) end) = '*';
2591         }
2592
2593         /* Get the string */
2594         if (overlay->have_pango_markup) {
2595           text = g_strndup (in_text, in_size);
2596         } else {
2597           text = g_markup_escape_text (in_text, in_size);
2598         }
2599
2600         if (text != NULL && *text != '\0') {
2601           gint text_len = strlen (text);
2602
2603           while (text_len > 0 && (text[text_len - 1] == '\n' ||
2604                   text[text_len - 1] == '\r')) {
2605             --text_len;
2606           }
2607           GST_DEBUG_OBJECT (overlay, "Rendering text '%*s'", text_len, text);
2608           gst_base_text_overlay_render_text (overlay, text, text_len);
2609         } else {
2610           GST_DEBUG_OBJECT (overlay, "No text to render (empty buffer)");
2611           gst_base_text_overlay_render_text (overlay, " ", 1);
2612         }
2613         gst_buffer_unmap (overlay->text_buffer, otext, osize);
2614
2615         if (in_text != otext)
2616           g_free (in_text);
2617
2618         GST_OBJECT_UNLOCK (overlay);
2619         ret = gst_base_text_overlay_push_frame (overlay, buffer);
2620
2621         if (valid_text_time && text_running_time_end <= vid_running_time_end) {
2622           GST_LOG_OBJECT (overlay, "text buffer not needed any longer");
2623           pop_text = TRUE;
2624         }
2625       }
2626       if (pop_text) {
2627         GST_OBJECT_LOCK (overlay);
2628         gst_base_text_overlay_pop_text (overlay);
2629         GST_OBJECT_UNLOCK (overlay);
2630       }
2631     } else {
2632       gboolean wait_for_text_buf = TRUE;
2633
2634       if (overlay->text_eos)
2635         wait_for_text_buf = FALSE;
2636
2637       if (!overlay->wait_text)
2638         wait_for_text_buf = FALSE;
2639
2640       /* Text pad linked, but no text buffer available - what now? */
2641       if (overlay->text_segment.format == GST_FORMAT_TIME) {
2642         GstClockTime text_start_running_time, text_position_running_time;
2643         GstClockTime vid_running_time;
2644
2645         vid_running_time =
2646             gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2647             GST_BUFFER_TIMESTAMP (buffer));
2648         text_start_running_time =
2649             gst_segment_to_running_time (&overlay->text_segment,
2650             GST_FORMAT_TIME, overlay->text_segment.start);
2651         text_position_running_time =
2652             gst_segment_to_running_time (&overlay->text_segment,
2653             GST_FORMAT_TIME, overlay->text_segment.position);
2654
2655         if ((GST_CLOCK_TIME_IS_VALID (text_start_running_time) &&
2656                 vid_running_time < text_start_running_time) ||
2657             (GST_CLOCK_TIME_IS_VALID (text_position_running_time) &&
2658                 vid_running_time < text_position_running_time)) {
2659           wait_for_text_buf = FALSE;
2660         }
2661       }
2662
2663       if (wait_for_text_buf) {
2664         GST_DEBUG_OBJECT (overlay, "no text buffer, need to wait for one");
2665         GST_BASE_TEXT_OVERLAY_WAIT (overlay);
2666         GST_DEBUG_OBJECT (overlay, "resuming");
2667         GST_OBJECT_UNLOCK (overlay);
2668         goto wait_for_text_buf;
2669       } else {
2670         GST_OBJECT_UNLOCK (overlay);
2671         GST_LOG_OBJECT (overlay, "no need to wait for a text buffer");
2672         ret = gst_pad_push (overlay->srcpad, buffer);
2673       }
2674     }
2675   }
2676
2677   g_free (text);
2678
2679   /* Update position */
2680   overlay->segment.position = clip_start;
2681
2682   return ret;
2683
2684 missing_timestamp:
2685   {
2686     GST_WARNING_OBJECT (overlay, "buffer without timestamp, discarding");
2687     gst_buffer_unref (buffer);
2688     return GST_FLOW_OK;
2689   }
2690
2691 flushing:
2692   {
2693     GST_OBJECT_UNLOCK (overlay);
2694     GST_DEBUG_OBJECT (overlay, "flushing, discarding buffer");
2695     gst_buffer_unref (buffer);
2696     return GST_FLOW_WRONG_STATE;
2697   }
2698 have_eos:
2699   {
2700     GST_OBJECT_UNLOCK (overlay);
2701     GST_DEBUG_OBJECT (overlay, "eos, discarding buffer");
2702     gst_buffer_unref (buffer);
2703     return GST_FLOW_UNEXPECTED;
2704   }
2705 out_of_segment:
2706   {
2707     GST_DEBUG_OBJECT (overlay, "buffer out of segment, discarding");
2708     gst_buffer_unref (buffer);
2709     return GST_FLOW_OK;
2710   }
2711 }
2712
2713 static GstStateChangeReturn
2714 gst_base_text_overlay_change_state (GstElement * element,
2715     GstStateChange transition)
2716 {
2717   GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
2718   GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (element);
2719
2720   switch (transition) {
2721     case GST_STATE_CHANGE_PAUSED_TO_READY:
2722       GST_OBJECT_LOCK (overlay);
2723       overlay->text_flushing = TRUE;
2724       overlay->video_flushing = TRUE;
2725       /* pop_text will broadcast on the GCond and thus also make the video
2726        * chain exit if it's waiting for a text buffer */
2727       gst_base_text_overlay_pop_text (overlay);
2728       GST_OBJECT_UNLOCK (overlay);
2729       break;
2730     default:
2731       break;
2732   }
2733
2734   ret = parent_class->change_state (element, transition);
2735   if (ret == GST_STATE_CHANGE_FAILURE)
2736     return ret;
2737
2738   switch (transition) {
2739     case GST_STATE_CHANGE_READY_TO_PAUSED:
2740       GST_OBJECT_LOCK (overlay);
2741       overlay->text_flushing = FALSE;
2742       overlay->video_flushing = FALSE;
2743       overlay->video_eos = FALSE;
2744       overlay->text_eos = FALSE;
2745       gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
2746       gst_segment_init (&overlay->text_segment, GST_FORMAT_TIME);
2747       GST_OBJECT_UNLOCK (overlay);
2748       break;
2749     default:
2750       break;
2751   }
2752
2753   return ret;
2754 }
2755
2756 static gboolean
2757 plugin_init (GstPlugin * plugin)
2758 {
2759   gst_controller_init (NULL, NULL);
2760
2761   if (!gst_element_register (plugin, "textoverlay", GST_RANK_NONE,
2762           GST_TYPE_TEXT_OVERLAY) ||
2763       !gst_element_register (plugin, "timeoverlay", GST_RANK_NONE,
2764           GST_TYPE_TIME_OVERLAY) ||
2765       !gst_element_register (plugin, "clockoverlay", GST_RANK_NONE,
2766           GST_TYPE_CLOCK_OVERLAY) ||
2767       !gst_element_register (plugin, "textrender", GST_RANK_NONE,
2768           GST_TYPE_TEXT_RENDER)) {
2769     return FALSE;
2770   }
2771
2772   /*texttestsrc_plugin_init(module, plugin); */
2773
2774   GST_DEBUG_CATEGORY_INIT (pango_debug, "pango", 0, "Pango elements");
2775
2776   return TRUE;
2777 }
2778
2779 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR,
2780     "pango", "Pango-based text rendering and overlay", plugin_init,
2781     VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)