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