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