close #333784 unref the result of gst_pad_get_parent() by: Christophe Fergeau.
[platform/upstream/gstreamer.git] / ext / cairo / gsttextoverlay.c
1 /* GStreamer
2  * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
3  * Copyright (C) <2003> David Schleef <ds@schleef.org>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, USA.
19  */
20
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24 #include <string.h>
25 #include <strings.h>
26 #include <gst/video/video.h>
27 #include "gsttextoverlay.h"
28
29 #include <cairo.h>
30
31 /* FIXME:
32  *   - calculating the position of the shading rectangle is 
33  *     not really right (try with text "L"), to say the least.
34  *     Seems to work at least with latin script though.
35  *   - check final x/y position and text width/height so that
36  *     we don't do out-of-memory access when blitting the text.
37  *     Also, we do not want to blit over the right or left margin.
38  *   - what about text with newline characters? Cairo doesn't deal
39  *     with that (we'd need to fix text_height usage for that as well)
40  *   - upstream caps renegotiation, ie. when video window gets resized
41  */
42
43 GST_DEBUG_CATEGORY_EXTERN (cairo_debug);
44 #define GST_CAT_DEFAULT cairo_debug
45
46 static GstElementDetails cairo_text_overlay_details = {
47   "Text Overlay",
48   "Filter/Editor/Video",
49   "Adds text strings on top of a video buffer",
50   "David Schleef <ds@schleef.org>"
51 };
52
53 enum
54 {
55   ARG_0,
56   ARG_TEXT,
57   ARG_SHADING,
58   ARG_VALIGN,
59   ARG_HALIGN,
60   ARG_XPAD,
61   ARG_YPAD,
62   ARG_DELTAX,
63   ARG_DELTAY,
64   ARG_FONT_DESC
65 };
66
67 #define DEFAULT_YPAD 25
68 #define DEFAULT_XPAD 25
69 #define DEFAULT_FONT "sans"
70
71 static GstStaticPadTemplate cairo_text_overlay_src_template_factory =
72 GST_STATIC_PAD_TEMPLATE ("src",
73     GST_PAD_SRC,
74     GST_PAD_ALWAYS,
75     GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("I420"))
76     );
77
78 static GstStaticPadTemplate video_sink_template_factory =
79 GST_STATIC_PAD_TEMPLATE ("video_sink",
80     GST_PAD_SINK,
81     GST_PAD_ALWAYS,
82     GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("I420"))
83     );
84
85 static GstStaticPadTemplate text_sink_template_factory =
86 GST_STATIC_PAD_TEMPLATE ("text_sink",
87     GST_PAD_SINK,
88     GST_PAD_ALWAYS,
89     GST_STATIC_CAPS ("text/plain")
90     );
91
92 static void gst_text_overlay_set_property (GObject * object,
93     guint prop_id, const GValue * value, GParamSpec * pspec);
94 static GstStateChangeReturn gst_text_overlay_change_state (GstElement * element,
95     GstStateChange transition);
96 static GstCaps *gst_text_overlay_getcaps (GstPad * pad);
97 static gboolean gst_text_overlay_setcaps (GstPad * pad, GstCaps * caps);
98 static GstPadLinkReturn gst_text_overlay_text_pad_linked (GstPad * pad,
99     GstPad * peer);
100 static void gst_text_overlay_text_pad_unlinked (GstPad * pad);
101 static GstFlowReturn gst_text_overlay_collected (GstCollectPads * pads,
102     gpointer data);
103 static void gst_text_overlay_finalize (GObject * object);
104 static void gst_text_overlay_font_init (GstCairoTextOverlay * overlay);
105
106 /* These macros are adapted from videotestsrc.c */
107 #define I420_Y_ROWSTRIDE(width) (GST_ROUND_UP_4(width))
108 #define I420_U_ROWSTRIDE(width) (GST_ROUND_UP_8(width)/2)
109 #define I420_V_ROWSTRIDE(width) ((GST_ROUND_UP_8(I420_Y_ROWSTRIDE(width)))/2)
110
111 #define I420_Y_OFFSET(w,h) (0)
112 #define I420_U_OFFSET(w,h) (I420_Y_OFFSET(w,h)+(I420_Y_ROWSTRIDE(w)*GST_ROUND_UP_2(h)))
113 #define I420_V_OFFSET(w,h) (I420_U_OFFSET(w,h)+(I420_U_ROWSTRIDE(w)*GST_ROUND_UP_2(h)/2))
114
115 #define I420_SIZE(w,h)     (I420_V_OFFSET(w,h)+(I420_V_ROWSTRIDE(w)*GST_ROUND_UP_2(h)/2))
116
117 GST_BOILERPLATE (GstCairoTextOverlay, gst_text_overlay, GstElement,
118     GST_TYPE_ELEMENT)
119
120      static void gst_text_overlay_base_init (gpointer g_class)
121 {
122   GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
123
124   gst_element_class_add_pad_template (element_class,
125       gst_static_pad_template_get (&cairo_text_overlay_src_template_factory));
126   gst_element_class_add_pad_template (element_class,
127       gst_static_pad_template_get (&video_sink_template_factory));
128   gst_element_class_add_pad_template (element_class,
129       gst_static_pad_template_get (&text_sink_template_factory));
130
131   gst_element_class_set_details (element_class, &cairo_text_overlay_details);
132 }
133
134 static void
135 gst_text_overlay_class_init (GstCairoTextOverlayClass * klass)
136 {
137   GObjectClass *gobject_class;
138   GstElementClass *gstelement_class;
139
140   gobject_class = (GObjectClass *) klass;
141   gstelement_class = (GstElementClass *) klass;
142
143   gobject_class->finalize = gst_text_overlay_finalize;
144   gobject_class->set_property = gst_text_overlay_set_property;
145
146   gstelement_class->change_state =
147       GST_DEBUG_FUNCPTR (gst_text_overlay_change_state);
148
149   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_TEXT,
150       g_param_spec_string ("text", "text",
151           "Text to be display.", "", G_PARAM_WRITABLE));
152   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_SHADING,
153       g_param_spec_boolean ("shaded-background", "shaded background",
154           "Whether to shade the background under the text area", FALSE,
155           G_PARAM_WRITABLE));
156   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_VALIGN,
157       g_param_spec_string ("valign", "vertical alignment",
158           "Vertical alignment of the text. "
159           "Can be either 'baseline', 'bottom', or 'top'",
160           "baseline", G_PARAM_WRITABLE));
161   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_HALIGN,
162       g_param_spec_string ("halign", "horizontal alignment",
163           "Horizontal alignment of the text. "
164           "Can be either 'left', 'right', or 'center'",
165           "center", G_PARAM_WRITABLE));
166   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_XPAD,
167       g_param_spec_int ("xpad", "horizontal paddding",
168           "Horizontal paddding when using left/right alignment",
169           G_MININT, G_MAXINT, DEFAULT_XPAD, G_PARAM_WRITABLE));
170   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_YPAD,
171       g_param_spec_int ("ypad", "vertical padding",
172           "Vertical padding when using top/bottom alignment",
173           G_MININT, G_MAXINT, DEFAULT_YPAD, G_PARAM_WRITABLE));
174   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_DELTAX,
175       g_param_spec_int ("deltax", "X position modifier",
176           "Shift X position to the left or to the right. Unit is pixels.",
177           G_MININT, G_MAXINT, 0, G_PARAM_WRITABLE));
178   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_DELTAY,
179       g_param_spec_int ("deltay", "Y position modifier",
180           "Shift Y position up or down. Unit is pixels.",
181           G_MININT, G_MAXINT, 0, G_PARAM_WRITABLE));
182   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_FONT_DESC,
183       g_param_spec_string ("font-desc", "font description",
184           "Pango font description of font "
185           "to be used for rendering. "
186           "See documentation of "
187           "pango_font_description_from_string"
188           " for syntax.", "", G_PARAM_WRITABLE));
189 }
190
191 static void
192 gst_text_overlay_finalize (GObject * object)
193 {
194   GstCairoTextOverlay *overlay = GST_CAIRO_TEXT_OVERLAY (object);
195
196   gst_collect_pads_stop (overlay->collect);
197   gst_object_unref (overlay->collect);
198
199   g_free (overlay->text_fill_image);
200   g_free (overlay->text_outline_image);
201
202   g_free (overlay->default_text);
203   g_free (overlay->font);
204
205   G_OBJECT_CLASS (parent_class)->finalize (object);
206 }
207
208 static void
209 gst_text_overlay_init (GstCairoTextOverlay * overlay,
210     GstCairoTextOverlayClass * klass)
211 {
212   /* video sink */
213   overlay->video_sinkpad =
214       gst_pad_new_from_template (gst_static_pad_template_get
215       (&video_sink_template_factory), "video_sink");
216   gst_pad_set_getcaps_function (overlay->video_sinkpad,
217       GST_DEBUG_FUNCPTR (gst_text_overlay_getcaps));
218   gst_pad_set_setcaps_function (overlay->video_sinkpad,
219       GST_DEBUG_FUNCPTR (gst_text_overlay_setcaps));
220   gst_element_add_pad (GST_ELEMENT (overlay), overlay->video_sinkpad);
221
222   /* text sink */
223   overlay->text_sinkpad =
224       gst_pad_new_from_template (gst_static_pad_template_get
225       (&text_sink_template_factory), "text_sink");
226   gst_pad_set_link_function (overlay->text_sinkpad,
227       GST_DEBUG_FUNCPTR (gst_text_overlay_text_pad_linked));
228   gst_pad_set_unlink_function (overlay->text_sinkpad,
229       GST_DEBUG_FUNCPTR (gst_text_overlay_text_pad_unlinked));
230   gst_element_add_pad (GST_ELEMENT (overlay), overlay->text_sinkpad);
231
232   /* (video) source */
233   overlay->srcpad =
234       gst_pad_new_from_template (gst_static_pad_template_get
235       (&cairo_text_overlay_src_template_factory), "src");
236   gst_pad_set_getcaps_function (overlay->srcpad,
237       GST_DEBUG_FUNCPTR (gst_text_overlay_getcaps));
238   gst_element_add_pad (GST_ELEMENT (overlay), overlay->srcpad);
239
240   overlay->halign = GST_CAIRO_TEXT_OVERLAY_HALIGN_CENTER;
241   overlay->valign = GST_CAIRO_TEXT_OVERLAY_VALIGN_BASELINE;
242   overlay->xpad = DEFAULT_XPAD;
243   overlay->ypad = DEFAULT_YPAD;
244   overlay->deltax = 0;
245   overlay->deltay = 0;
246
247   overlay->default_text = g_strdup ("");
248   overlay->need_render = TRUE;
249
250   overlay->font = g_strdup (DEFAULT_FONT);
251   overlay->slant = CAIRO_FONT_SLANT_NORMAL;
252   overlay->weight = CAIRO_FONT_WEIGHT_NORMAL;
253   overlay->scale = 20;
254   gst_text_overlay_font_init (overlay);
255
256   overlay->fps_n = 0;
257   overlay->fps_d = 1;
258
259   overlay->collect = gst_collect_pads_new ();
260
261   gst_collect_pads_set_function (overlay->collect,
262       GST_DEBUG_FUNCPTR (gst_text_overlay_collected), overlay);
263
264   overlay->video_collect_data = gst_collect_pads_add_pad (overlay->collect,
265       overlay->video_sinkpad, sizeof (GstCollectData));
266
267   /* text pad will be added when it is linked */
268   overlay->text_collect_data = NULL;
269 }
270
271 static void
272 gst_text_overlay_font_init (GstCairoTextOverlay * overlay)
273 {
274   cairo_font_extents_t font_extents;
275   cairo_surface_t *surface;
276   cairo_t *cr;
277
278   surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 256, 256);
279   cr = cairo_create (surface);
280
281   cairo_select_font_face (cr, overlay->font, overlay->slant, overlay->weight);
282   cairo_set_font_size (cr, overlay->scale);
283
284   cairo_font_extents (cr, &font_extents);
285   overlay->font_height = GST_ROUND_UP_2 ((guint) font_extents.height);
286   overlay->need_render = TRUE;
287
288   cairo_destroy (cr);
289   cairo_surface_destroy (surface);
290 }
291
292 static void
293 gst_text_overlay_set_property (GObject * object, guint prop_id,
294     const GValue * value, GParamSpec * pspec)
295 {
296   GstCairoTextOverlay *overlay = GST_CAIRO_TEXT_OVERLAY (object);
297
298   GST_OBJECT_LOCK (overlay);
299
300   switch (prop_id) {
301     case ARG_TEXT:{
302       g_free (overlay->default_text);
303       overlay->default_text = g_value_dup_string (value);
304       break;
305     }
306     case ARG_SHADING:{
307       overlay->want_shading = g_value_get_boolean (value);
308       break;
309     }
310     case ARG_VALIGN:{
311       const gchar *s = g_value_get_string (value);
312
313       if (strcasecmp (s, "baseline") == 0)
314         overlay->valign = GST_CAIRO_TEXT_OVERLAY_VALIGN_BASELINE;
315       else if (strcasecmp (s, "bottom") == 0)
316         overlay->valign = GST_CAIRO_TEXT_OVERLAY_VALIGN_BOTTOM;
317       else if (strcasecmp (s, "top") == 0)
318         overlay->valign = GST_CAIRO_TEXT_OVERLAY_VALIGN_TOP;
319       else
320         g_warning ("Invalid 'valign' property value: %s", s);
321       break;
322     }
323     case ARG_HALIGN:{
324       const gchar *s = g_value_get_string (value);
325
326       if (strcasecmp (s, "left") == 0)
327         overlay->halign = GST_CAIRO_TEXT_OVERLAY_HALIGN_LEFT;
328       else if (strcasecmp (s, "right") == 0)
329         overlay->halign = GST_CAIRO_TEXT_OVERLAY_HALIGN_RIGHT;
330       else if (strcasecmp (s, "center") == 0)
331         overlay->halign = GST_CAIRO_TEXT_OVERLAY_HALIGN_CENTER;
332       else
333         g_warning ("Invalid 'halign' property value: %s", s);
334       break;
335     }
336     case ARG_XPAD:{
337       overlay->xpad = g_value_get_int (value);
338       break;
339     }
340     case ARG_YPAD:{
341       overlay->ypad = g_value_get_int (value);
342       break;
343     }
344     case ARG_DELTAX:{
345       overlay->deltax = g_value_get_int (value);
346       break;
347     }
348     case ARG_DELTAY:{
349       overlay->deltay = g_value_get_int (value);
350       break;
351     }
352     case ARG_FONT_DESC:{
353       g_free (overlay->font);
354       overlay->font = g_value_dup_string (value);
355       if (overlay->font == NULL)
356         overlay->font = g_strdup (DEFAULT_FONT);
357       gst_text_overlay_font_init (overlay);
358       break;
359     }
360     default:{
361       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
362       break;
363     }
364   }
365
366   overlay->need_render = TRUE;
367
368   GST_OBJECT_UNLOCK (overlay);
369 }
370
371 static void
372 gst_text_overlay_render_text (GstCairoTextOverlay * overlay,
373     const gchar * text, gint textlen)
374 {
375   cairo_text_extents_t extents;
376   cairo_surface_t *surface;
377   cairo_t *cr;
378   gchar *string;
379   double x, y;
380
381   if (textlen < 0)
382     textlen = strlen (text);
383
384   string = g_strndup (text, textlen);
385
386   if (overlay->need_render) {
387     GST_DEBUG ("Rendering text '%s' on cairo RGBA surface", string);
388   } else {
389     GST_DEBUG ("Using previously rendered text.");
390     g_return_if_fail (overlay->text_fill_image != NULL);
391     g_return_if_fail (overlay->text_outline_image != NULL);
392     return;
393   }
394
395   overlay->text_fill_image =
396       g_realloc (overlay->text_fill_image,
397       4 * overlay->width * overlay->font_height);
398
399   surface = cairo_image_surface_create_for_data (overlay->text_fill_image,
400       CAIRO_FORMAT_ARGB32, overlay->width, overlay->font_height,
401       overlay->width * 4);
402
403   cr = cairo_create (surface);
404
405   cairo_select_font_face (cr, overlay->font, overlay->slant, overlay->weight);
406   cairo_set_font_size (cr, overlay->scale);
407
408   cairo_save (cr);
409   cairo_rectangle (cr, 0, 0, overlay->width, overlay->font_height);
410   cairo_set_source_rgba (cr, 0, 0, 0, 1.0);
411
412   cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
413   cairo_fill (cr);
414   cairo_restore (cr);
415
416   cairo_save (cr);
417   cairo_text_extents (cr, string, &extents);
418   cairo_set_source_rgba (cr, 1, 1, 1, 1.0);
419
420   switch (overlay->halign) {
421     case GST_CAIRO_TEXT_OVERLAY_HALIGN_LEFT:
422       x = overlay->xpad;
423       break;
424     case GST_CAIRO_TEXT_OVERLAY_HALIGN_CENTER:
425       x = (overlay->width - extents.width) / 2;
426       break;
427     case GST_CAIRO_TEXT_OVERLAY_HALIGN_RIGHT:
428       x = overlay->width - extents.width - overlay->xpad;
429       break;
430     default:
431       x = 0;
432   }
433   x += overlay->deltax;
434
435   overlay->text_x0 = x;
436   overlay->text_x1 = x + extents.x_advance;
437
438   overlay->text_dy = (extents.height + extents.y_bearing);
439   y = overlay->font_height - overlay->text_dy;
440
441   cairo_move_to (cr, x, y);
442   cairo_show_text (cr, string);
443   cairo_restore (cr);
444
445   cairo_destroy (cr);
446   cairo_surface_destroy (surface);
447
448   /* ----------- */
449
450   overlay->text_outline_image =
451       g_realloc (overlay->text_outline_image,
452       4 * overlay->width * overlay->font_height);
453
454   surface = cairo_image_surface_create_for_data (overlay->text_outline_image,
455       CAIRO_FORMAT_ARGB32, overlay->width, overlay->font_height,
456       overlay->width * 4);
457
458   cr = cairo_create (surface);
459
460   cairo_select_font_face (cr, overlay->font, overlay->slant, overlay->weight);
461   cairo_set_font_size (cr, overlay->scale);
462
463   cairo_save (cr);
464   cairo_rectangle (cr, 0, 0, overlay->width, overlay->font_height);
465   cairo_set_source_rgba (cr, 0, 0, 0, 1.0);
466   cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
467   cairo_fill (cr);
468   cairo_restore (cr);
469
470   cairo_save (cr);
471   cairo_move_to (cr, x, y);
472   cairo_set_source_rgba (cr, 1, 1, 1, 1.0);
473   cairo_set_line_width (cr, 1.0);
474   cairo_text_path (cr, string);
475   cairo_stroke (cr);
476   cairo_restore (cr);
477
478   g_free (string);
479
480   cairo_destroy (cr);
481   cairo_surface_destroy (surface);
482
483   overlay->need_render = FALSE;
484 }
485
486 static GstCaps *
487 gst_text_overlay_getcaps (GstPad * pad)
488 {
489   GstCairoTextOverlay *overlay;
490   GstPad *otherpad;
491   GstCaps *caps;
492
493   overlay = GST_CAIRO_TEXT_OVERLAY (gst_pad_get_parent (pad));
494
495   if (pad == overlay->srcpad)
496     otherpad = overlay->video_sinkpad;
497   else
498     otherpad = overlay->srcpad;
499
500   /* we can do what the peer can */
501   caps = gst_pad_peer_get_caps (otherpad);
502   if (caps) {
503     GstCaps *temp;
504     const GstCaps *templ;
505
506     GST_DEBUG_OBJECT (pad, "peer caps  %" GST_PTR_FORMAT, caps);
507
508     /* filtered against our padtemplate */
509     templ = gst_pad_get_pad_template_caps (otherpad);
510     GST_DEBUG_OBJECT (pad, "our template  %" GST_PTR_FORMAT, templ);
511     temp = gst_caps_intersect (caps, templ);
512     GST_DEBUG_OBJECT (pad, "intersected %" GST_PTR_FORMAT, temp);
513     gst_caps_unref (caps);
514     /* this is what we can do */
515     caps = temp;
516   } else {
517     /* no peer, our padtemplate is enough then */
518     caps = gst_caps_copy (gst_pad_get_pad_template_caps (pad));
519   }
520
521   GST_DEBUG_OBJECT (overlay, "returning  %" GST_PTR_FORMAT, caps);
522
523   gst_object_unref (overlay);
524
525   return caps;
526 }
527
528 /* FIXME: upstream nego (e.g. when the video window is resized) */
529
530 static gboolean
531 gst_text_overlay_setcaps (GstPad * pad, GstCaps * caps)
532 {
533   GstCairoTextOverlay *overlay;
534   GstStructure *structure;
535   gboolean ret = FALSE;
536   const GValue *fps;
537
538   if (!GST_PAD_IS_SINK (pad))
539     return TRUE;
540
541   g_return_val_if_fail (gst_caps_is_fixed (caps), FALSE);
542
543   overlay = GST_CAIRO_TEXT_OVERLAY (gst_pad_get_parent (pad));
544
545   overlay->width = 0;
546   overlay->height = 0;
547   structure = gst_caps_get_structure (caps, 0);
548   fps = gst_structure_get_value (structure, "framerate");
549
550   if (gst_structure_get_int (structure, "width", &overlay->width) &&
551       gst_structure_get_int (structure, "height", &overlay->height) &&
552       fps != NULL) {
553     ret = gst_pad_set_caps (overlay->srcpad, caps);
554   }
555
556   overlay->fps_n = gst_value_get_fraction_numerator (fps);
557   overlay->fps_d = gst_value_get_fraction_denominator (fps);
558
559   gst_object_unref (overlay);
560
561   return ret;
562 }
563
564 static GstPadLinkReturn
565 gst_text_overlay_text_pad_linked (GstPad * pad, GstPad * peer)
566 {
567   GstCairoTextOverlay *overlay;
568
569   overlay = GST_CAIRO_TEXT_OVERLAY (GST_PAD_PARENT (pad));
570
571   GST_DEBUG_OBJECT (overlay, "Text pad linked");
572
573   if (overlay->text_collect_data == NULL) {
574     overlay->text_collect_data = gst_collect_pads_add_pad (overlay->collect,
575         overlay->text_sinkpad, sizeof (GstCollectData));
576   }
577
578   overlay->need_render = TRUE;
579
580   return GST_PAD_LINK_OK;
581 }
582
583 static void
584 gst_text_overlay_text_pad_unlinked (GstPad * pad)
585 {
586   GstCairoTextOverlay *overlay;
587
588   /* don't use gst_pad_get_parent() here, will deadlock */
589   overlay = GST_CAIRO_TEXT_OVERLAY (GST_PAD_PARENT (pad));
590
591   GST_DEBUG_OBJECT (overlay, "Text pad unlinked");
592
593   if (overlay->text_collect_data) {
594     gst_collect_pads_remove_pad (overlay->collect, overlay->text_sinkpad);
595     overlay->text_collect_data = NULL;
596   }
597
598   overlay->need_render = TRUE;
599 }
600
601 #define BOX_SHADING_VAL -80
602 #define BOX_XPAD         6
603 #define BOX_YPAD         6
604
605 static inline void
606 gst_text_overlay_shade_y (GstCairoTextOverlay * overlay, guchar * dest,
607     guint dest_stride, gint y0, gint y1)
608 {
609   gint i, j, x0, x1;
610
611   x0 = CLAMP (overlay->text_x0 - BOX_XPAD, 0, overlay->width);
612   x1 = CLAMP (overlay->text_x1 + BOX_XPAD, 0, overlay->width);
613
614   y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
615   y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
616
617   for (i = y0; i < y1; ++i) {
618     for (j = x0; j < x1; ++j) {
619       gint y = dest[(i * dest_stride) + j] + BOX_SHADING_VAL;
620
621       dest[(i * dest_stride) + j] = CLAMP (y, 0, 255);
622     }
623   }
624 }
625
626 static inline void
627 gst_text_overlay_blit_1 (GstCairoTextOverlay * overlay, guchar * dest,
628     guchar * text_image, gint val, guint dest_stride)
629 {
630   gint i, j;
631   gint x, a, y;
632   gint y0 = 0;
633
634   y = val;
635
636   for (i = 0; i < overlay->font_height; i++) {
637     for (j = 0; j < overlay->width; j++) {
638       x = dest[(i + y0) * dest_stride + j];
639       a = text_image[4 * (i * overlay->width + j) + 1];
640       dest[(i + y0) * dest_stride + j] = (y * a + x * (255 - a)) / 255;
641     }
642   }
643 }
644
645 static inline void
646 gst_text_overlay_blit_sub2x2 (GstCairoTextOverlay * overlay, guchar * dest,
647     guchar * text_image, gint val, guint dest_stride)
648 {
649   gint i, j;
650   gint x, a, y;
651   gint y0 = 0;
652
653   y = val;
654
655   for (i = 0; i < overlay->font_height; i += 2) {
656     for (j = 0; j < overlay->width; j += 2) {
657       x = dest[(i / 2 + y0) * dest_stride + j / 2];
658       a = (text_image[4 * (i * overlay->width + j) + 1] +
659           text_image[4 * (i * overlay->width + j + 1) + 1] +
660           text_image[4 * ((i + 1) * overlay->width + j) + 1] +
661           text_image[4 * ((i + 1) * overlay->width + j + 1) + 1] + 2) / 4;
662       dest[(i / 2 + y0) * dest_stride + j / 2] = (y * a + x * (255 - a)) / 255;
663     }
664   }
665 }
666
667
668 static GstFlowReturn
669 gst_text_overlay_push_frame (GstCairoTextOverlay * overlay,
670     GstBuffer * video_frame)
671 {
672   guchar *y, *u, *v;
673   gint ypos;
674
675   video_frame = gst_buffer_make_writable (video_frame);
676
677   switch (overlay->valign) {
678     case GST_CAIRO_TEXT_OVERLAY_VALIGN_BOTTOM:
679       ypos = overlay->height - overlay->font_height - overlay->ypad;
680       break;
681     case GST_CAIRO_TEXT_OVERLAY_VALIGN_BASELINE:
682       ypos = overlay->height - (overlay->font_height - overlay->text_dy)
683           - overlay->ypad;
684       break;
685     case GST_CAIRO_TEXT_OVERLAY_VALIGN_TOP:
686       ypos = overlay->ypad;
687       break;
688     default:
689       ypos = overlay->ypad;
690       break;
691   }
692
693   ypos += overlay->deltay;
694
695   y = GST_BUFFER_DATA (video_frame);
696   u = y + I420_U_OFFSET (overlay->width, overlay->height);
697   v = y + I420_V_OFFSET (overlay->width, overlay->height);
698
699   /* shaded background box */
700   if (overlay->want_shading) {
701     gst_text_overlay_shade_y (overlay,
702         y, I420_Y_ROWSTRIDE (overlay->width),
703         ypos + overlay->text_dy, ypos + overlay->font_height);
704   }
705
706   /* blit outline text on video image */
707   gst_text_overlay_blit_1 (overlay,
708       y + (ypos / 1) * I420_Y_ROWSTRIDE (overlay->width),
709       overlay->text_outline_image, 0, I420_Y_ROWSTRIDE (overlay->width));
710   gst_text_overlay_blit_sub2x2 (overlay,
711       u + (ypos / 2) * I420_U_ROWSTRIDE (overlay->width),
712       overlay->text_outline_image, 128, I420_U_ROWSTRIDE (overlay->width));
713   gst_text_overlay_blit_sub2x2 (overlay,
714       v + (ypos / 2) * I420_V_ROWSTRIDE (overlay->width),
715       overlay->text_outline_image, 128, I420_V_ROWSTRIDE (overlay->width));
716
717   /* blit text on video image */
718   gst_text_overlay_blit_1 (overlay,
719       y + (ypos / 1) * I420_Y_ROWSTRIDE (overlay->width),
720       overlay->text_fill_image, 255, I420_Y_ROWSTRIDE (overlay->width));
721   gst_text_overlay_blit_sub2x2 (overlay,
722       u + (ypos / 2) * I420_U_ROWSTRIDE (overlay->width),
723       overlay->text_fill_image, 128, I420_U_ROWSTRIDE (overlay->width));
724   gst_text_overlay_blit_sub2x2 (overlay,
725       v + (ypos / 2) * I420_V_ROWSTRIDE (overlay->width),
726       overlay->text_fill_image, 128, I420_V_ROWSTRIDE (overlay->width));
727
728   return gst_pad_push (overlay->srcpad, video_frame);
729 }
730
731 static void
732 gst_text_overlay_pop_video (GstCairoTextOverlay * overlay)
733 {
734   GstBuffer *buf;
735
736   buf = gst_collect_pads_pop (overlay->collect, overlay->video_collect_data);
737   g_return_if_fail (buf != NULL);
738   gst_buffer_unref (buf);
739 }
740
741 static void
742 gst_text_overlay_pop_text (GstCairoTextOverlay * overlay)
743 {
744   GstBuffer *buf;
745
746   if (overlay->text_collect_data) {
747     buf = gst_collect_pads_pop (overlay->collect, overlay->text_collect_data);
748     g_return_if_fail (buf != NULL);
749     gst_buffer_unref (buf);
750   }
751
752   overlay->need_render = TRUE;
753 }
754
755 /* This function is called when there is data on all pads */
756 static GstFlowReturn
757 gst_text_overlay_collected (GstCollectPads * pads, gpointer data)
758 {
759   GstCairoTextOverlay *overlay;
760   GstFlowReturn ret = GST_FLOW_OK;
761   GstClockTime now, txt_end, frame_end;
762   GstBuffer *video_frame = NULL;
763   GstBuffer *text_buf = NULL;
764   gchar *text;
765   gint text_len;
766
767   overlay = GST_CAIRO_TEXT_OVERLAY (data);
768
769   GST_DEBUG ("Collecting");
770
771   video_frame = gst_collect_pads_peek (overlay->collect,
772       overlay->video_collect_data);
773
774   /* send EOS if video stream EOSed regardless of text stream */
775   if (video_frame == NULL) {
776     GST_DEBUG ("Video stream at EOS");
777     if (overlay->text_collect_data) {
778       text_buf = gst_collect_pads_pop (overlay->collect,
779           overlay->text_collect_data);
780     }
781     gst_pad_push_event (overlay->srcpad, gst_event_new_eos ());
782     ret = GST_FLOW_UNEXPECTED;
783     goto done;
784   }
785
786   if (GST_BUFFER_TIMESTAMP (video_frame) == GST_CLOCK_TIME_NONE) {
787     g_warning ("%s: video frame has invalid timestamp", G_STRLOC);
788   }
789
790   now = GST_BUFFER_TIMESTAMP (video_frame);
791
792   if (GST_BUFFER_DURATION (video_frame) != GST_CLOCK_TIME_NONE) {
793     frame_end = now + GST_BUFFER_DURATION (video_frame);
794   } else if (overlay->fps_n > 0) {
795     frame_end = now + gst_util_uint64_scale_int (GST_SECOND,
796         overlay->fps_d, overlay->fps_n);
797   } else {
798     /* magic value, does not really matter since texts
799      * tend to span quite a few frames in practice anyway */
800     frame_end = now + GST_SECOND / 25;
801   }
802
803   GST_DEBUG ("Got video frame: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
804       GST_TIME_ARGS (now), GST_TIME_ARGS (frame_end));
805
806   /* text pad not linked? */
807   if (overlay->text_collect_data == NULL) {
808     GST_DEBUG ("Text pad not linked, rendering default text: '%s'",
809         GST_STR_NULL (overlay->default_text));
810     if (overlay->default_text && *overlay->default_text != '\0') {
811       gst_text_overlay_render_text (overlay, overlay->default_text, -1);
812       ret = gst_text_overlay_push_frame (overlay, video_frame);
813     } else {
814       ret = gst_pad_push (overlay->srcpad, video_frame);
815     }
816     gst_text_overlay_pop_video (overlay);
817     video_frame = NULL;
818     goto done;
819   }
820
821   text_buf = gst_collect_pads_peek (overlay->collect,
822       overlay->text_collect_data);
823
824   /* just push the video frame if the text stream has EOSed */
825   if (text_buf == NULL) {
826     GST_DEBUG ("Text pad EOSed, just pushing video frame as is");
827     ret = gst_pad_push (overlay->srcpad, video_frame);
828     gst_text_overlay_pop_video (overlay);
829     video_frame = NULL;
830     goto done;
831   }
832
833   /* if the text buffer isn't stamped right, pop it off the
834    *  queue and display it for the current video frame only */
835   if (GST_BUFFER_TIMESTAMP (text_buf) == GST_CLOCK_TIME_NONE ||
836       GST_BUFFER_DURATION (text_buf) == GST_CLOCK_TIME_NONE) {
837     GST_WARNING ("Got text buffer with invalid time stamp or duration");
838     gst_text_overlay_pop_text (overlay);
839     GST_BUFFER_TIMESTAMP (text_buf) = now;
840     GST_BUFFER_DURATION (text_buf) = frame_end - now;
841   }
842
843   txt_end = GST_BUFFER_TIMESTAMP (text_buf) + GST_BUFFER_DURATION (text_buf);
844
845   GST_DEBUG ("Got text buffer: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
846       GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (text_buf)), GST_TIME_ARGS (txt_end));
847
848   /* if the text buffer is too old, pop it off the
849    * queue and return so we get a new one next time */
850   if (txt_end < now) {
851     GST_DEBUG ("Text buffer too old, popping off the queue");
852     gst_text_overlay_pop_text (overlay);
853     ret = GST_FLOW_OK;
854     goto done;
855   }
856
857   /* if the video frame ends before the text even starts,
858    * just push it out as is and pop it off the queue */
859   if (frame_end < GST_BUFFER_TIMESTAMP (text_buf)) {
860     GST_DEBUG ("Video buffer before text, pushing out and popping off queue");
861     ret = gst_pad_push (overlay->srcpad, video_frame);
862     gst_text_overlay_pop_video (overlay);
863     video_frame = NULL;
864     goto done;
865   }
866
867   /* text duration overlaps video frame duration */
868   text = g_strndup ((gchar *) GST_BUFFER_DATA (text_buf),
869       GST_BUFFER_SIZE (text_buf));
870   g_strdelimit (text, "\n\r\t", ' ');
871   text_len = strlen (text);
872
873   if (text_len > 0) {
874     GST_DEBUG ("Rendering text '%*s'", text_len, text);;
875     gst_text_overlay_render_text (overlay, text, text_len);
876   } else {
877     GST_DEBUG ("No text to render (empty buffer)");
878     gst_text_overlay_render_text (overlay, " ", 1);
879   }
880
881   g_free (text);
882
883   gst_text_overlay_pop_video (overlay);
884   ret = gst_text_overlay_push_frame (overlay, video_frame);
885   video_frame = NULL;
886   goto done;
887
888 done:
889   {
890     if (text_buf)
891       gst_buffer_unref (text_buf);
892
893     if (video_frame)
894       gst_buffer_unref (video_frame);
895
896     return ret;
897   }
898 }
899
900 static GstStateChangeReturn
901 gst_text_overlay_change_state (GstElement * element, GstStateChange transition)
902 {
903   GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
904   GstCairoTextOverlay *overlay = GST_CAIRO_TEXT_OVERLAY (element);
905
906   switch (transition) {
907     case GST_STATE_CHANGE_READY_TO_PAUSED:
908       gst_collect_pads_start (overlay->collect);
909       break;
910     case GST_STATE_CHANGE_PAUSED_TO_READY:
911       /* need to unblock the collectpads before calling the
912        * parent change_state so that streaming can finish */
913       gst_collect_pads_stop (overlay->collect);
914       break;
915     default:
916       break;
917   }
918
919   ret = parent_class->change_state (element, transition);
920   if (ret == GST_STATE_CHANGE_FAILURE)
921     return ret;
922
923   switch (transition) {
924     default:
925       break;
926   }
927
928   return ret;
929 }