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