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