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