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