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