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