testoverlay: fix object and caps leak
[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_DEBUG_OBJECT (overlay, "received invalid buffer");
2108     return GST_FLOW_OK;
2109   }
2110 }
2111
2112 static GstPadLinkReturn
2113 gst_base_text_overlay_text_pad_link (GstPad * pad, GstPad * peer)
2114 {
2115   GstBaseTextOverlay *overlay;
2116
2117   overlay = GST_BASE_TEXT_OVERLAY (gst_pad_get_parent (pad));
2118   if (G_UNLIKELY (!overlay))
2119     return GST_PAD_LINK_REFUSED;
2120
2121   GST_DEBUG_OBJECT (overlay, "Text pad linked");
2122
2123   overlay->text_linked = TRUE;
2124
2125   gst_object_unref (overlay);
2126
2127   return GST_PAD_LINK_OK;
2128 }
2129
2130 static void
2131 gst_base_text_overlay_text_pad_unlink (GstPad * pad)
2132 {
2133   GstBaseTextOverlay *overlay;
2134
2135   /* don't use gst_pad_get_parent() here, will deadlock */
2136   overlay = GST_BASE_TEXT_OVERLAY (GST_PAD_PARENT (pad));
2137
2138   GST_DEBUG_OBJECT (overlay, "Text pad unlinked");
2139
2140   overlay->text_linked = FALSE;
2141
2142   gst_segment_init (&overlay->text_segment, GST_FORMAT_UNDEFINED);
2143 }
2144
2145 static gboolean
2146 gst_base_text_overlay_text_event (GstPad * pad, GstObject * parent,
2147     GstEvent * event)
2148 {
2149   gboolean ret = FALSE;
2150   GstBaseTextOverlay *overlay = NULL;
2151
2152   overlay = GST_BASE_TEXT_OVERLAY (parent);
2153
2154   GST_LOG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
2155
2156   switch (GST_EVENT_TYPE (event)) {
2157     case GST_EVENT_CAPS:
2158     {
2159       GstCaps *caps;
2160
2161       gst_event_parse_caps (event, &caps);
2162       ret = gst_base_text_overlay_setcaps_txt (overlay, caps);
2163       gst_event_unref (event);
2164       break;
2165     }
2166     case GST_EVENT_SEGMENT:
2167     {
2168       const GstSegment *segment;
2169
2170       overlay->text_eos = FALSE;
2171
2172       gst_event_parse_segment (event, &segment);
2173
2174       if (segment->format == GST_FORMAT_TIME) {
2175         GST_OBJECT_LOCK (overlay);
2176         gst_segment_copy_into (segment, &overlay->text_segment);
2177         GST_DEBUG_OBJECT (overlay, "TEXT SEGMENT now: %" GST_SEGMENT_FORMAT,
2178             &overlay->text_segment);
2179         GST_OBJECT_UNLOCK (overlay);
2180       } else {
2181         GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL),
2182             ("received non-TIME newsegment event on text input"));
2183       }
2184
2185       gst_event_unref (event);
2186       ret = TRUE;
2187
2188       /* wake up the video chain, it might be waiting for a text buffer or
2189        * a text segment update */
2190       GST_OBJECT_LOCK (overlay);
2191       GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2192       GST_OBJECT_UNLOCK (overlay);
2193       break;
2194     }
2195     case GST_EVENT_FLUSH_STOP:
2196       GST_OBJECT_LOCK (overlay);
2197       GST_INFO_OBJECT (overlay, "text flush stop");
2198       overlay->text_flushing = FALSE;
2199       overlay->text_eos = FALSE;
2200       gst_base_text_overlay_pop_text (overlay);
2201       gst_segment_init (&overlay->text_segment, GST_FORMAT_TIME);
2202       GST_OBJECT_UNLOCK (overlay);
2203       gst_event_unref (event);
2204       ret = TRUE;
2205       break;
2206     case GST_EVENT_FLUSH_START:
2207       GST_OBJECT_LOCK (overlay);
2208       GST_INFO_OBJECT (overlay, "text flush start");
2209       overlay->text_flushing = TRUE;
2210       GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2211       GST_OBJECT_UNLOCK (overlay);
2212       gst_event_unref (event);
2213       ret = TRUE;
2214       break;
2215     case GST_EVENT_EOS:
2216       GST_OBJECT_LOCK (overlay);
2217       overlay->text_eos = TRUE;
2218       GST_INFO_OBJECT (overlay, "text EOS");
2219       /* wake up the video chain, it might be waiting for a text buffer or
2220        * a text segment update */
2221       GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2222       GST_OBJECT_UNLOCK (overlay);
2223       gst_event_unref (event);
2224       ret = TRUE;
2225       break;
2226     default:
2227       ret = gst_pad_event_default (pad, parent, event);
2228       break;
2229   }
2230
2231   return ret;
2232 }
2233
2234 static gboolean
2235 gst_base_text_overlay_video_event (GstPad * pad, GstObject * parent,
2236     GstEvent * event)
2237 {
2238   gboolean ret = FALSE;
2239   GstBaseTextOverlay *overlay = NULL;
2240
2241   overlay = GST_BASE_TEXT_OVERLAY (parent);
2242
2243   GST_DEBUG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
2244
2245   switch (GST_EVENT_TYPE (event)) {
2246     case GST_EVENT_CAPS:
2247     {
2248       GstCaps *caps;
2249
2250       gst_event_parse_caps (event, &caps);
2251       ret = gst_base_text_overlay_setcaps (overlay, caps);
2252       gst_event_unref (event);
2253       break;
2254     }
2255     case GST_EVENT_SEGMENT:
2256     {
2257       const GstSegment *segment;
2258
2259       GST_DEBUG_OBJECT (overlay, "received new segment");
2260
2261       gst_event_parse_segment (event, &segment);
2262
2263       if (segment->format == GST_FORMAT_TIME) {
2264         GST_DEBUG_OBJECT (overlay, "VIDEO SEGMENT now: %" GST_SEGMENT_FORMAT,
2265             &overlay->segment);
2266
2267         gst_segment_copy_into (segment, &overlay->segment);
2268       } else {
2269         GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL),
2270             ("received non-TIME newsegment event on video input"));
2271       }
2272
2273       ret = gst_pad_event_default (pad, parent, event);
2274       break;
2275     }
2276     case GST_EVENT_EOS:
2277       GST_OBJECT_LOCK (overlay);
2278       GST_INFO_OBJECT (overlay, "video EOS");
2279       overlay->video_eos = TRUE;
2280       GST_OBJECT_UNLOCK (overlay);
2281       ret = gst_pad_event_default (pad, parent, event);
2282       break;
2283     case GST_EVENT_FLUSH_START:
2284       GST_OBJECT_LOCK (overlay);
2285       GST_INFO_OBJECT (overlay, "video flush start");
2286       overlay->video_flushing = TRUE;
2287       GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2288       GST_OBJECT_UNLOCK (overlay);
2289       ret = gst_pad_event_default (pad, parent, event);
2290       break;
2291     case GST_EVENT_FLUSH_STOP:
2292       GST_OBJECT_LOCK (overlay);
2293       GST_INFO_OBJECT (overlay, "video flush stop");
2294       overlay->video_flushing = FALSE;
2295       overlay->video_eos = FALSE;
2296       gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
2297       GST_OBJECT_UNLOCK (overlay);
2298       ret = gst_pad_event_default (pad, parent, event);
2299       break;
2300     default:
2301       ret = gst_pad_event_default (pad, parent, event);
2302       break;
2303   }
2304
2305   return ret;
2306 }
2307
2308 static gboolean
2309 gst_base_text_overlay_video_query (GstPad * pad, GstObject * parent,
2310     GstQuery * query)
2311 {
2312   gboolean ret = FALSE;
2313   GstBaseTextOverlay *overlay;
2314
2315   overlay = GST_BASE_TEXT_OVERLAY (parent);
2316
2317   switch (GST_QUERY_TYPE (query)) {
2318     case GST_QUERY_CAPS:
2319     {
2320       GstCaps *filter, *caps;
2321
2322       gst_query_parse_caps (query, &filter);
2323       caps = gst_base_text_overlay_getcaps (pad, overlay, filter);
2324       gst_query_set_caps_result (query, caps);
2325       gst_caps_unref (caps);
2326       ret = TRUE;
2327       break;
2328     }
2329     default:
2330       ret = gst_pad_query_default (pad, parent, query);
2331       break;
2332   }
2333
2334   return ret;
2335 }
2336
2337 /* Called with lock held */
2338 static void
2339 gst_base_text_overlay_pop_text (GstBaseTextOverlay * overlay)
2340 {
2341   g_return_if_fail (GST_IS_BASE_TEXT_OVERLAY (overlay));
2342
2343   if (overlay->text_buffer) {
2344     GST_DEBUG_OBJECT (overlay, "releasing text buffer %p",
2345         overlay->text_buffer);
2346     gst_buffer_unref (overlay->text_buffer);
2347     overlay->text_buffer = NULL;
2348   }
2349
2350   /* Let the text task know we used that buffer */
2351   GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2352 }
2353
2354 /* We receive text buffers here. If they are out of segment we just ignore them.
2355    If the buffer is in our segment we keep it internally except if another one
2356    is already waiting here, in that case we wait that it gets kicked out */
2357 static GstFlowReturn
2358 gst_base_text_overlay_text_chain (GstPad * pad, GstObject * parent,
2359     GstBuffer * buffer)
2360 {
2361   GstFlowReturn ret = GST_FLOW_OK;
2362   GstBaseTextOverlay *overlay = NULL;
2363   gboolean in_seg = FALSE;
2364   guint64 clip_start = 0, clip_stop = 0;
2365
2366   overlay = GST_BASE_TEXT_OVERLAY (parent);
2367
2368   GST_OBJECT_LOCK (overlay);
2369
2370   if (overlay->text_flushing) {
2371     GST_OBJECT_UNLOCK (overlay);
2372     ret = GST_FLOW_FLUSHING;
2373     GST_LOG_OBJECT (overlay, "text flushing");
2374     goto beach;
2375   }
2376
2377   if (overlay->text_eos) {
2378     GST_OBJECT_UNLOCK (overlay);
2379     ret = GST_FLOW_EOS;
2380     GST_LOG_OBJECT (overlay, "text EOS");
2381     goto beach;
2382   }
2383
2384   GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT "  BUFFER: ts=%"
2385       GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment,
2386       GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)),
2387       GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer) +
2388           GST_BUFFER_DURATION (buffer)));
2389
2390   if (G_LIKELY (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))) {
2391     GstClockTime stop;
2392
2393     if (G_LIKELY (GST_BUFFER_DURATION_IS_VALID (buffer)))
2394       stop = GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer);
2395     else
2396       stop = GST_CLOCK_TIME_NONE;
2397
2398     in_seg = gst_segment_clip (&overlay->text_segment, GST_FORMAT_TIME,
2399         GST_BUFFER_TIMESTAMP (buffer), stop, &clip_start, &clip_stop);
2400   } else {
2401     in_seg = TRUE;
2402   }
2403
2404   if (in_seg) {
2405     if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2406       GST_BUFFER_TIMESTAMP (buffer) = clip_start;
2407     else if (GST_BUFFER_DURATION_IS_VALID (buffer))
2408       GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
2409
2410     /* Wait for the previous buffer to go away */
2411     while (overlay->text_buffer != NULL) {
2412       GST_DEBUG ("Pad %s:%s has a buffer queued, waiting",
2413           GST_DEBUG_PAD_NAME (pad));
2414       GST_BASE_TEXT_OVERLAY_WAIT (overlay);
2415       GST_DEBUG ("Pad %s:%s resuming", GST_DEBUG_PAD_NAME (pad));
2416       if (overlay->text_flushing) {
2417         GST_OBJECT_UNLOCK (overlay);
2418         ret = GST_FLOW_FLUSHING;
2419         goto beach;
2420       }
2421     }
2422
2423     if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2424       overlay->text_segment.position = clip_start;
2425
2426     overlay->text_buffer = buffer;
2427     /* That's a new text buffer we need to render */
2428     overlay->need_render = TRUE;
2429
2430     /* in case the video chain is waiting for a text buffer, wake it up */
2431     GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2432   }
2433
2434   GST_OBJECT_UNLOCK (overlay);
2435
2436 beach:
2437
2438   return ret;
2439 }
2440
2441 static GstFlowReturn
2442 gst_base_text_overlay_video_chain (GstPad * pad, GstObject * parent,
2443     GstBuffer * buffer)
2444 {
2445   GstBaseTextOverlayClass *klass;
2446   GstBaseTextOverlay *overlay;
2447   GstFlowReturn ret = GST_FLOW_OK;
2448   gboolean in_seg = FALSE;
2449   guint64 start, stop, clip_start = 0, clip_stop = 0;
2450   gchar *text = NULL;
2451
2452   overlay = GST_BASE_TEXT_OVERLAY (parent);
2453   klass = GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay);
2454
2455   if (!GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2456     goto missing_timestamp;
2457
2458   /* ignore buffers that are outside of the current segment */
2459   start = GST_BUFFER_TIMESTAMP (buffer);
2460
2461   if (!GST_BUFFER_DURATION_IS_VALID (buffer)) {
2462     stop = GST_CLOCK_TIME_NONE;
2463   } else {
2464     stop = start + GST_BUFFER_DURATION (buffer);
2465   }
2466
2467   GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT "  BUFFER: ts=%"
2468       GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment,
2469       GST_TIME_ARGS (start), GST_TIME_ARGS (stop));
2470
2471   /* segment_clip() will adjust start unconditionally to segment_start if
2472    * no stop time is provided, so handle this ourselves */
2473   if (stop == GST_CLOCK_TIME_NONE && start < overlay->segment.start)
2474     goto out_of_segment;
2475
2476   in_seg = gst_segment_clip (&overlay->segment, GST_FORMAT_TIME, start, stop,
2477       &clip_start, &clip_stop);
2478
2479   if (!in_seg)
2480     goto out_of_segment;
2481
2482   /* if the buffer is only partially in the segment, fix up stamps */
2483   if (clip_start != start || (stop != -1 && clip_stop != stop)) {
2484     GST_DEBUG_OBJECT (overlay, "clipping buffer timestamp/duration to segment");
2485     buffer = gst_buffer_make_writable (buffer);
2486     GST_BUFFER_TIMESTAMP (buffer) = clip_start;
2487     if (stop != -1)
2488       GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
2489   }
2490
2491   /* now, after we've done the clipping, fix up end time if there's no
2492    * duration (we only use those estimated values internally though, we
2493    * don't want to set bogus values on the buffer itself) */
2494   if (stop == -1) {
2495     GstCaps *caps;
2496     GstStructure *s;
2497     gint fps_num, fps_denom;
2498
2499     /* FIXME, store this in setcaps */
2500     caps = gst_pad_get_current_caps (pad);
2501     s = gst_caps_get_structure (caps, 0);
2502     if (gst_structure_get_fraction (s, "framerate", &fps_num, &fps_denom) &&
2503         fps_num && fps_denom) {
2504       GST_DEBUG_OBJECT (overlay, "estimating duration based on framerate");
2505       stop = start + gst_util_uint64_scale_int (GST_SECOND, fps_denom, fps_num);
2506     } else {
2507       GST_WARNING_OBJECT (overlay, "no duration, assuming minimal duration");
2508       stop = start + 1;         /* we need to assume some interval */
2509     }
2510     gst_caps_unref (caps);
2511   }
2512
2513   gst_object_sync_values (GST_OBJECT (overlay), GST_BUFFER_TIMESTAMP (buffer));
2514
2515 wait_for_text_buf:
2516
2517   GST_OBJECT_LOCK (overlay);
2518
2519   if (overlay->video_flushing)
2520     goto flushing;
2521
2522   if (overlay->video_eos)
2523     goto have_eos;
2524
2525   if (overlay->silent) {
2526     GST_OBJECT_UNLOCK (overlay);
2527     ret = gst_pad_push (overlay->srcpad, buffer);
2528
2529     /* Update position */
2530     overlay->segment.position = clip_start;
2531
2532     return ret;
2533   }
2534
2535   /* Text pad not linked, rendering internal text */
2536   if (!overlay->text_linked) {
2537     if (klass->get_text) {
2538       text = klass->get_text (overlay, buffer);
2539     } else {
2540       text = g_strdup (overlay->default_text);
2541     }
2542
2543     GST_LOG_OBJECT (overlay, "Text pad not linked, rendering default "
2544         "text: '%s'", GST_STR_NULL (text));
2545
2546     GST_OBJECT_UNLOCK (overlay);
2547
2548     if (text != NULL && *text != '\0') {
2549       /* Render and push */
2550       gst_base_text_overlay_render_text (overlay, text, -1);
2551       ret = gst_base_text_overlay_push_frame (overlay, buffer);
2552     } else {
2553       /* Invalid or empty string */
2554       ret = gst_pad_push (overlay->srcpad, buffer);
2555     }
2556   } else {
2557     /* Text pad linked, check if we have a text buffer queued */
2558     if (overlay->text_buffer) {
2559       gboolean pop_text = FALSE, valid_text_time = TRUE;
2560       GstClockTime text_start = GST_CLOCK_TIME_NONE;
2561       GstClockTime text_end = GST_CLOCK_TIME_NONE;
2562       GstClockTime text_running_time = GST_CLOCK_TIME_NONE;
2563       GstClockTime text_running_time_end = GST_CLOCK_TIME_NONE;
2564       GstClockTime vid_running_time, vid_running_time_end;
2565
2566       /* if the text buffer isn't stamped right, pop it off the
2567        * queue and display it for the current video frame only */
2568       if (!GST_BUFFER_TIMESTAMP_IS_VALID (overlay->text_buffer) ||
2569           !GST_BUFFER_DURATION_IS_VALID (overlay->text_buffer)) {
2570         GST_WARNING_OBJECT (overlay,
2571             "Got text buffer with invalid timestamp or duration");
2572         pop_text = TRUE;
2573         valid_text_time = FALSE;
2574       } else {
2575         text_start = GST_BUFFER_TIMESTAMP (overlay->text_buffer);
2576         text_end = text_start + GST_BUFFER_DURATION (overlay->text_buffer);
2577       }
2578
2579       vid_running_time =
2580           gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2581           start);
2582       vid_running_time_end =
2583           gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2584           stop);
2585
2586       /* If timestamp and duration are valid */
2587       if (valid_text_time) {
2588         text_running_time =
2589             gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2590             text_start);
2591         text_running_time_end =
2592             gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2593             text_end);
2594       }
2595
2596       GST_LOG_OBJECT (overlay, "T: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
2597           GST_TIME_ARGS (text_running_time),
2598           GST_TIME_ARGS (text_running_time_end));
2599       GST_LOG_OBJECT (overlay, "V: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
2600           GST_TIME_ARGS (vid_running_time),
2601           GST_TIME_ARGS (vid_running_time_end));
2602
2603       /* Text too old or in the future */
2604       if (valid_text_time && text_running_time_end <= vid_running_time) {
2605         /* text buffer too old, get rid of it and do nothing  */
2606         GST_LOG_OBJECT (overlay, "text buffer too old, popping");
2607         pop_text = FALSE;
2608         gst_base_text_overlay_pop_text (overlay);
2609         GST_OBJECT_UNLOCK (overlay);
2610         goto wait_for_text_buf;
2611       } else if (valid_text_time && vid_running_time_end <= text_running_time) {
2612         GST_LOG_OBJECT (overlay, "text in future, pushing video buf");
2613         GST_OBJECT_UNLOCK (overlay);
2614         /* Push the video frame */
2615         ret = gst_pad_push (overlay->srcpad, buffer);
2616       } else {
2617         GstMapInfo map;
2618         gchar *in_text;
2619         gsize in_size;
2620
2621         gst_buffer_map (overlay->text_buffer, &map, GST_MAP_READ);
2622         in_text = (gchar *) map.data;
2623         in_size = map.size;
2624
2625         /* g_markup_escape_text() absolutely requires valid UTF8 input, it
2626          * might crash otherwise. We don't fall back on GST_SUBTITLE_ENCODING
2627          * here on purpose, this is something that needs fixing upstream */
2628         if (!g_utf8_validate (in_text, in_size, NULL)) {
2629           const gchar *end = NULL;
2630
2631           GST_WARNING_OBJECT (overlay, "received invalid UTF-8");
2632           in_text = g_strndup (in_text, in_size);
2633           while (!g_utf8_validate (in_text, in_size, &end) && end)
2634             *((gchar *) end) = '*';
2635         }
2636
2637         /* Get the string */
2638         if (overlay->have_pango_markup) {
2639           text = g_strndup (in_text, in_size);
2640         } else {
2641           text = g_markup_escape_text (in_text, in_size);
2642         }
2643
2644         if (text != NULL && *text != '\0') {
2645           gint text_len = strlen (text);
2646
2647           while (text_len > 0 && (text[text_len - 1] == '\n' ||
2648                   text[text_len - 1] == '\r')) {
2649             --text_len;
2650           }
2651           GST_DEBUG_OBJECT (overlay, "Rendering text '%*s'", text_len, text);
2652           gst_base_text_overlay_render_text (overlay, text, text_len);
2653         } else {
2654           GST_DEBUG_OBJECT (overlay, "No text to render (empty buffer)");
2655           gst_base_text_overlay_render_text (overlay, " ", 1);
2656         }
2657         if (in_text != (gchar *) map.data)
2658           g_free (in_text);
2659
2660         gst_buffer_unmap (overlay->text_buffer, &map);
2661
2662         GST_OBJECT_UNLOCK (overlay);
2663         ret = gst_base_text_overlay_push_frame (overlay, buffer);
2664
2665         if (valid_text_time && text_running_time_end <= vid_running_time_end) {
2666           GST_LOG_OBJECT (overlay, "text buffer not needed any longer");
2667           pop_text = TRUE;
2668         }
2669       }
2670       if (pop_text) {
2671         GST_OBJECT_LOCK (overlay);
2672         gst_base_text_overlay_pop_text (overlay);
2673         GST_OBJECT_UNLOCK (overlay);
2674       }
2675     } else {
2676       gboolean wait_for_text_buf = TRUE;
2677
2678       if (overlay->text_eos)
2679         wait_for_text_buf = FALSE;
2680
2681       if (!overlay->wait_text)
2682         wait_for_text_buf = FALSE;
2683
2684       /* Text pad linked, but no text buffer available - what now? */
2685       if (overlay->text_segment.format == GST_FORMAT_TIME) {
2686         GstClockTime text_start_running_time, text_position_running_time;
2687         GstClockTime vid_running_time;
2688
2689         vid_running_time =
2690             gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2691             GST_BUFFER_TIMESTAMP (buffer));
2692         text_start_running_time =
2693             gst_segment_to_running_time (&overlay->text_segment,
2694             GST_FORMAT_TIME, overlay->text_segment.start);
2695         text_position_running_time =
2696             gst_segment_to_running_time (&overlay->text_segment,
2697             GST_FORMAT_TIME, overlay->text_segment.position);
2698
2699         if ((GST_CLOCK_TIME_IS_VALID (text_start_running_time) &&
2700                 vid_running_time < text_start_running_time) ||
2701             (GST_CLOCK_TIME_IS_VALID (text_position_running_time) &&
2702                 vid_running_time < text_position_running_time)) {
2703           wait_for_text_buf = FALSE;
2704         }
2705       }
2706
2707       if (wait_for_text_buf) {
2708         GST_DEBUG_OBJECT (overlay, "no text buffer, need to wait for one");
2709         GST_BASE_TEXT_OVERLAY_WAIT (overlay);
2710         GST_DEBUG_OBJECT (overlay, "resuming");
2711         GST_OBJECT_UNLOCK (overlay);
2712         goto wait_for_text_buf;
2713       } else {
2714         GST_OBJECT_UNLOCK (overlay);
2715         GST_LOG_OBJECT (overlay, "no need to wait for a text buffer");
2716         ret = gst_pad_push (overlay->srcpad, buffer);
2717       }
2718     }
2719   }
2720
2721   g_free (text);
2722
2723   /* Update position */
2724   overlay->segment.position = clip_start;
2725
2726   return ret;
2727
2728 missing_timestamp:
2729   {
2730     GST_WARNING_OBJECT (overlay, "buffer without timestamp, discarding");
2731     gst_buffer_unref (buffer);
2732     return GST_FLOW_OK;
2733   }
2734
2735 flushing:
2736   {
2737     GST_OBJECT_UNLOCK (overlay);
2738     GST_DEBUG_OBJECT (overlay, "flushing, discarding buffer");
2739     gst_buffer_unref (buffer);
2740     return GST_FLOW_FLUSHING;
2741   }
2742 have_eos:
2743   {
2744     GST_OBJECT_UNLOCK (overlay);
2745     GST_DEBUG_OBJECT (overlay, "eos, discarding buffer");
2746     gst_buffer_unref (buffer);
2747     return GST_FLOW_EOS;
2748   }
2749 out_of_segment:
2750   {
2751     GST_DEBUG_OBJECT (overlay, "buffer out of segment, discarding");
2752     gst_buffer_unref (buffer);
2753     return GST_FLOW_OK;
2754   }
2755 }
2756
2757 static GstStateChangeReturn
2758 gst_base_text_overlay_change_state (GstElement * element,
2759     GstStateChange transition)
2760 {
2761   GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
2762   GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (element);
2763
2764   switch (transition) {
2765     case GST_STATE_CHANGE_PAUSED_TO_READY:
2766       GST_OBJECT_LOCK (overlay);
2767       overlay->text_flushing = TRUE;
2768       overlay->video_flushing = TRUE;
2769       /* pop_text will broadcast on the GCond and thus also make the video
2770        * chain exit if it's waiting for a text buffer */
2771       gst_base_text_overlay_pop_text (overlay);
2772       GST_OBJECT_UNLOCK (overlay);
2773       break;
2774     default:
2775       break;
2776   }
2777
2778   ret = parent_class->change_state (element, transition);
2779   if (ret == GST_STATE_CHANGE_FAILURE)
2780     return ret;
2781
2782   switch (transition) {
2783     case GST_STATE_CHANGE_READY_TO_PAUSED:
2784       GST_OBJECT_LOCK (overlay);
2785       overlay->text_flushing = FALSE;
2786       overlay->video_flushing = FALSE;
2787       overlay->video_eos = FALSE;
2788       overlay->text_eos = FALSE;
2789       gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
2790       gst_segment_init (&overlay->text_segment, GST_FORMAT_TIME);
2791       GST_OBJECT_UNLOCK (overlay);
2792       break;
2793     default:
2794       break;
2795   }
2796
2797   return ret;
2798 }
2799
2800 static gboolean
2801 plugin_init (GstPlugin * plugin)
2802 {
2803   if (!gst_element_register (plugin, "textoverlay", GST_RANK_NONE,
2804           GST_TYPE_TEXT_OVERLAY) ||
2805       !gst_element_register (plugin, "timeoverlay", GST_RANK_NONE,
2806           GST_TYPE_TIME_OVERLAY) ||
2807       !gst_element_register (plugin, "clockoverlay", GST_RANK_NONE,
2808           GST_TYPE_CLOCK_OVERLAY) ||
2809       !gst_element_register (plugin, "textrender", GST_RANK_NONE,
2810           GST_TYPE_TEXT_RENDER)) {
2811     return FALSE;
2812   }
2813
2814   /*texttestsrc_plugin_init(module, plugin); */
2815
2816   GST_DEBUG_CATEGORY_INIT (pango_debug, "pango", 0, "Pango elements");
2817
2818   return TRUE;
2819 }
2820
2821 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR,
2822     "pango", "Pango-based text rendering and overlay", plugin_init,
2823     VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)