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