2 * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
3 * Copyright (C) <2003> David Schleef <ds@schleef.org>
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.
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.
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.
27 #include "gsttextoverlay.h"
29 static GstElementDetails textoverlay_details = {
31 "Filter/Editor/Video",
32 "Adds text strings on top of a video buffer",
33 "David Schleef <ds@schleef.org>"
48 static GstStaticPadTemplate textoverlay_src_template_factory =
49 GST_STATIC_PAD_TEMPLATE ("src",
52 GST_STATIC_CAPS ("video/x-raw-yuv, "
53 "format = (fourcc) I420, "
54 "width = (int) [ 1, MAX ], " "height = (int) [ 1, MAX ]")
57 static GstStaticPadTemplate video_sink_template_factory =
58 GST_STATIC_PAD_TEMPLATE ("video_sink",
61 GST_STATIC_CAPS ("video/x-raw-yuv, "
62 "format = (fourcc) I420, "
63 "width = (int) [ 1, MAX ], " "height = (int) [ 1, MAX ]")
66 static GstStaticPadTemplate text_sink_template_factory =
67 GST_STATIC_PAD_TEMPLATE ("text_sink",
70 GST_STATIC_CAPS ("text/plain")
73 static void gst_textoverlay_base_init (gpointer g_class);
74 static void gst_textoverlay_class_init (GstTextOverlayClass * klass);
75 static void gst_textoverlay_init (GstTextOverlay * overlay);
76 static void gst_textoverlay_set_property (GObject * object,
77 guint prop_id, const GValue * value, GParamSpec * pspec);
78 static void gst_textoverlay_get_property (GObject * object,
79 guint prop_id, GValue * value, GParamSpec * pspec);
80 static GstStateChangeReturn gst_textoverlay_change_state (GstElement * element,
81 GstStateChange transition);
82 static void gst_textoverlay_finalize (GObject * object);
85 static GstElementClass *parent_class = NULL;
87 /*static guint gst_textoverlay_signals[LAST_SIGNAL] = { 0 }; */
91 gst_textoverlay_get_type (void)
93 static GType textoverlay_type = 0;
95 if (!textoverlay_type) {
96 static const GTypeInfo textoverlay_info = {
97 sizeof (GstTextOverlayClass),
98 gst_textoverlay_base_init,
100 (GClassInitFunc) gst_textoverlay_class_init,
103 sizeof (GstTextOverlay),
105 (GInstanceInitFunc) gst_textoverlay_init,
109 g_type_register_static (GST_TYPE_ELEMENT, "GstTextOverlay",
110 &textoverlay_info, 0);
112 return textoverlay_type;
116 gst_textoverlay_base_init (gpointer g_class)
118 GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
120 gst_element_class_add_pad_template (element_class,
121 gst_static_pad_template_get (&textoverlay_src_template_factory));
122 gst_element_class_add_pad_template (element_class,
123 gst_static_pad_template_get (&video_sink_template_factory));
124 gst_element_class_add_pad_template (element_class,
125 gst_static_pad_template_get (&text_sink_template_factory));
127 gst_element_class_set_details (element_class, &textoverlay_details);
131 gst_textoverlay_class_init (GstTextOverlayClass * klass)
133 GObjectClass *gobject_class;
134 GstElementClass *gstelement_class;
136 gobject_class = (GObjectClass *) klass;
137 gstelement_class = (GstElementClass *) klass;
139 parent_class = g_type_class_peek_parent (klass);
141 gobject_class->finalize = gst_textoverlay_finalize;
142 gobject_class->set_property = gst_textoverlay_set_property;
143 gobject_class->get_property = gst_textoverlay_get_property;
145 gstelement_class->change_state = gst_textoverlay_change_state;
146 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_TEXT,
147 g_param_spec_string ("text", "text",
148 "Text to be display.", "", G_PARAM_WRITABLE));
149 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_VALIGN,
150 g_param_spec_string ("valign", "vertical alignment",
151 "Vertical alignment of the text. "
152 "Can be either 'baseline', 'bottom', or 'top'",
153 "baseline", G_PARAM_WRITABLE));
154 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_HALIGN,
155 g_param_spec_string ("halign", "horizontal alignment",
156 "Horizontal alignment of the text. "
157 "Can be either 'left', 'right', or 'center'",
158 "center", G_PARAM_WRITABLE));
159 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_X0,
160 g_param_spec_int ("x0", "X position",
161 "Initial X position."
162 " Horizontal aligment takes this point"
163 " as reference.", G_MININT, G_MAXINT, 0, G_PARAM_WRITABLE));
164 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_Y0,
165 g_param_spec_int ("y0", "Y position",
166 "Initial Y position."
167 " Vertical aligment takes this point"
168 " as reference.", G_MININT, G_MAXINT, 0, G_PARAM_WRITABLE));
169 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_FONT_DESC,
170 g_param_spec_string ("font-desc", "font description",
171 "Pango font description of font "
172 "to be used for rendering. "
173 "See documentation of "
174 "pango_font_description_from_string"
175 " for syntax.", "", G_PARAM_WRITABLE));
181 resize_bitmap (GstTextOverlay * overlay, int width, int height)
183 FT_Bitmap *bitmap = &overlay->bitmap;
184 int pitch = (width | 3) + 1;
185 int size = pitch * height;
187 /* no need to keep reallocating; just keep the maximum size so far */
188 if (size <= overlay->bitmap_buffer_size) {
189 bitmap->rows = height;
190 bitmap->width = width;
191 bitmap->pitch = pitch;
192 memset (bitmap->buffer, 0, overlay->bitmap_buffer_size);
195 if (!bitmap->buffer) {
197 bitmap->pixel_mode = ft_pixel_mode_grays;
198 bitmap->num_grays = 256;
201 bitmap->buffer = g_realloc (bitmap->buffer, size);
203 bitmap->buffer = g_malloc (size);
204 bitmap->rows = height;
205 bitmap->width = width;
206 bitmap->pitch = pitch;
207 memset (bitmap->buffer, 0, size);
208 overlay->bitmap_buffer_size = size;
213 gst_textoverlay_render_text (GstTextOverlay * overlay, gchar * text,
216 cairo_text_extents_t extents;
220 string = g_strndup (text, textlen);
222 if (overlay->text_fill_image)
223 g_free (overlay->text_fill_image);
224 overlay->text_fill_image =
225 g_malloc (4 * overlay->width * overlay->text_height);
226 cairo_set_target_image (overlay->cr, overlay->text_fill_image,
227 CAIRO_FORMAT_ARGB32, overlay->width, overlay->text_height,
230 cairo_save (overlay->cr);
231 cairo_rectangle (overlay->cr, 0, 0, overlay->width, overlay->text_height);
232 cairo_set_rgb_color (overlay->cr, 0, 0, 0);
233 cairo_set_alpha (overlay->cr, 1.0);
234 cairo_set_operator (overlay->cr, CAIRO_OPERATOR_SRC);
235 cairo_fill (overlay->cr);
236 cairo_restore (overlay->cr);
238 cairo_save (overlay->cr);
239 cairo_text_extents (overlay->cr, string, &extents);
240 cairo_set_rgb_color (overlay->cr, 1, 1, 1);
241 cairo_set_alpha (overlay->cr, 1.0);
242 switch (overlay->halign) {
243 case GST_TEXT_OVERLAY_HALIGN_LEFT:
246 case GST_TEXT_OVERLAY_HALIGN_CENTER:
247 x = overlay->x0 - extents.width / 2;
249 case GST_TEXT_OVERLAY_HALIGN_RIGHT:
250 x = overlay->x0 - extents.width;
255 y = overlay->text_height - 2;
256 cairo_move_to (overlay->cr, x, y);
257 cairo_show_text (overlay->cr, string);
258 cairo_restore (overlay->cr);
260 if (overlay->text_outline_image)
261 g_free (overlay->text_outline_image);
262 overlay->text_outline_image =
263 g_malloc (4 * overlay->width * overlay->text_height);
264 cairo_set_target_image (overlay->cr, overlay->text_outline_image,
265 CAIRO_FORMAT_ARGB32, overlay->width, overlay->text_height,
268 cairo_save (overlay->cr);
269 cairo_rectangle (overlay->cr, 0, 0, overlay->width, overlay->text_height);
270 cairo_set_rgb_color (overlay->cr, 0, 0, 0);
271 cairo_set_alpha (overlay->cr, 1.0);
272 cairo_set_operator (overlay->cr, CAIRO_OPERATOR_SRC);
273 cairo_fill (overlay->cr);
274 cairo_restore (overlay->cr);
276 cairo_save (overlay->cr);
277 cairo_move_to (overlay->cr, x, y);
278 cairo_set_rgb_color (overlay->cr, 1, 1, 1);
279 cairo_set_alpha (overlay->cr, 1.0);
280 cairo_set_line_width (overlay->cr, 1.0);
281 cairo_text_path (overlay->cr, string);
282 cairo_stroke (overlay->cr);
283 cairo_restore (overlay->cr);
288 /* static GstPadLinkReturn */
289 /* gst_textoverlay_text_sinkconnect (GstPad *pad, GstCaps *caps) */
291 /* return GST_PAD_LINK_DONE; */
295 static GstPadLinkReturn
296 gst_textoverlay_video_sinkconnect (GstPad * pad, const GstCaps * caps)
298 GstTextOverlay *overlay;
299 GstStructure *structure;
301 overlay = GST_TEXTOVERLAY (gst_pad_get_parent (pad));
303 structure = gst_caps_get_structure (caps, 0);
304 overlay->width = overlay->height = 0;
305 gst_structure_get_int (structure, "width", &overlay->width);
306 gst_structure_get_int (structure, "height", &overlay->height);
308 return gst_pad_try_set_caps (overlay->srcpad, caps);
313 gst_text_overlay_blit_1 (GstTextOverlay * overlay, guchar * dest,
314 guchar * text_image, int val)
323 for (i = 0; i < overlay->text_height; i++) {
324 for (j = 0; j < overlay->width; j++) {
325 x = dest[(i + y0) * overlay->width + j];
326 a = text_image[4 * (i * overlay->width + j) + 1];
327 dest[(i + y0) * overlay->width + j] = (y * a + x * (255 - a)) / 255;
333 gst_text_overlay_blit_sub2x2 (GstTextOverlay * overlay, guchar * dest,
334 guchar * text_image, int val)
343 for (i = 0; i < overlay->text_height; i += 2) {
344 for (j = 0; j < overlay->width; j += 2) {
345 x = dest[(i / 2 + y0) * (overlay->width / 2) + j / 2];
346 a = (text_image[4 * (i * overlay->width + j) + 1] +
347 text_image[4 * (i * overlay->width + j + 1) + 1] +
348 text_image[4 * ((i + 1) * overlay->width + j) + 1] +
349 text_image[4 * ((i + 1) * overlay->width + j + 1) + 1] + 2) / 4;
350 dest[(i / 2 + y0) * (overlay->width / 2) + j / 2] =
351 (y * a + x * (255 - a)) / 255;
358 gst_textoverlay_video_chain (GstPad * pad, GstData * _data)
360 GstBuffer *buf = GST_BUFFER (_data);
361 GstTextOverlay *overlay;
365 g_return_if_fail (pad != NULL);
366 g_return_if_fail (GST_IS_PAD (pad));
367 g_return_if_fail (buf != NULL);
368 overlay = GST_TEXTOVERLAY (gst_pad_get_parent (pad));
369 g_return_if_fail (overlay != NULL);
370 g_return_if_fail (GST_IS_TEXTOVERLAY (overlay));
372 if (!GST_IS_BUFFER (_data))
375 pixbuf = GST_BUFFER_DATA (buf);
378 switch (overlay->valign) {
379 case GST_TEXT_OVERLAY_VALIGN_BOTTOM:
380 y -= overlay->text_height;
382 case GST_TEXT_OVERLAY_VALIGN_BASELINE:
384 y -= (overlay->text_height - BASELINE);
386 case GST_TEXT_OVERLAY_VALIGN_TOP:
390 gst_text_overlay_blit_1 (overlay,
391 pixbuf + y * overlay->width, overlay->text_outline_image, 0);
392 gst_text_overlay_blit_sub2x2 (overlay,
393 pixbuf + (overlay->height * overlay->width) +
394 (y / 2) * overlay->width / 2, overlay->text_outline_image, 128);
395 gst_text_overlay_blit_sub2x2 (overlay, pixbuf +
396 (overlay->height * overlay->width) +
397 (overlay->height * overlay->width) / 4 + (y / 2) * overlay->width / 2,
398 overlay->text_outline_image, 128);
400 gst_text_overlay_blit_1 (overlay, pixbuf + y * overlay->width,
401 overlay->text_fill_image, 255);
402 gst_text_overlay_blit_sub2x2 (overlay,
403 pixbuf + (overlay->height * overlay->width) +
404 (y / 2) * overlay->width / 2, overlay->text_fill_image, 128);
405 gst_text_overlay_blit_sub2x2 (overlay,
406 pixbuf + (overlay->height * overlay->width) +
407 (overlay->height * overlay->width) / 4 + (y / 2) * overlay->width / 2,
408 overlay->text_fill_image, 128);
410 gst_pad_push (overlay->srcpad, GST_DATA (buf));
413 #define PAST_END(buffer, time) \
414 (GST_BUFFER_TIMESTAMP (buffer) != GST_CLOCK_TIME_NONE && \
415 GST_BUFFER_DURATION (buffer) != GST_CLOCK_TIME_NONE && \
416 GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer) \
420 gst_textoverlay_loop (GstElement * element)
422 GstTextOverlay *overlay;
423 GstBuffer *video_frame;
426 g_return_if_fail (element != NULL);
427 g_return_if_fail (GST_IS_TEXTOVERLAY (element));
428 overlay = GST_TEXTOVERLAY (element);
431 GST_DEBUG ("Attempting to pull next video frame");
432 video_frame = GST_BUFFER (gst_pad_pull (overlay->video_sinkpad));
433 if (GST_IS_EVENT (video_frame)) {
434 GstEvent *event = GST_EVENT (video_frame);
435 GstEventType type = GST_EVENT_TYPE (event);
437 gst_pad_event_default (overlay->video_sinkpad, event);
438 GST_DEBUG ("Received event of type %d", type);
439 if (type == GST_EVENT_EOS || type == GST_EVENT_INTERRUPT)
443 } while (!video_frame);
444 now = GST_BUFFER_TIMESTAMP (video_frame);
445 GST_DEBUG ("Got video frame, time=%" GST_TIME_FORMAT, GST_TIME_ARGS (now));
448 * This state machine has a bug that can't be resolved easily.
449 * (Needs a more complicated state machine.) Basically, if the
450 * text that came from a buffer from the sink pad is being
451 * displayed, and the default text is changed by set_parameter,
452 * we'll incorrectly display the default text.
454 * Otherwise, this is a pretty decent state machine that handles
455 * buffer timestamps and durations correctly. (I think)
458 while ((!overlay->current_buffer ||
459 PAST_END (overlay->current_buffer, now)) &&
460 overlay->next_buffer == NULL) {
461 GST_DEBUG ("attempting to pull a buffer");
463 /* read all text buffers until we get one "in the future" */
464 if (!GST_PAD_IS_USABLE (overlay->text_sinkpad)) {
468 overlay->next_buffer = GST_BUFFER (gst_pad_pull (overlay->text_sinkpad));
469 if (GST_IS_EVENT (overlay->next_buffer)) {
470 GstEvent *event = GST_EVENT (overlay->next_buffer);
471 GstEventType type = GST_EVENT_TYPE (event);
473 gst_pad_event_default (overlay->text_sinkpad, event);
474 if (type == GST_EVENT_EOS || type == GST_EVENT_INTERRUPT)
476 overlay->next_buffer = NULL;
478 } while (!overlay->next_buffer);
480 if (PAST_END (overlay->next_buffer, now)) {
481 GST_DEBUG ("Received buffer is past end (%" GST_TIME_FORMAT " + %"
482 GST_TIME_FORMAT " < %" GST_TIME_FORMAT ")",
483 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (overlay->next_buffer)),
484 GST_TIME_ARGS (GST_BUFFER_DURATION (overlay->next_buffer)),
485 GST_TIME_ARGS (now));
486 gst_buffer_unref (overlay->next_buffer);
487 overlay->next_buffer = NULL;
489 GST_DEBUG ("Received new text buffer of time %" GST_TIME_FORMAT
490 "and duration %" GST_TIME_FORMAT,
491 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (overlay->next_buffer)),
492 GST_TIME_ARGS (GST_BUFFER_DURATION (overlay->next_buffer)));
496 if (overlay->next_buffer &&
497 (GST_BUFFER_TIMESTAMP (overlay->next_buffer) <= now ||
498 GST_BUFFER_TIMESTAMP (overlay->next_buffer) == GST_CLOCK_TIME_NONE)) {
499 GST_DEBUG ("using new buffer");
501 if (overlay->current_buffer) {
502 gst_buffer_unref (overlay->current_buffer);
504 overlay->current_buffer = overlay->next_buffer;
505 overlay->next_buffer = NULL;
507 GST_DEBUG ("rendering '%*s'",
508 GST_BUFFER_SIZE (overlay->current_buffer),
509 GST_BUFFER_DATA (overlay->current_buffer));
510 gst_textoverlay_render_text (overlay,
511 GST_BUFFER_DATA (overlay->current_buffer),
512 GST_BUFFER_SIZE (overlay->current_buffer));
513 overlay->need_render = FALSE;
516 if (overlay->current_buffer && PAST_END (overlay->current_buffer, now)) {
517 GST_DEBUG ("dropping old buffer");
519 gst_buffer_unref (overlay->current_buffer);
520 overlay->current_buffer = NULL;
522 overlay->need_render = TRUE;
525 if (overlay->need_render) {
526 GST_DEBUG ("rendering '%s'", overlay->default_text);
527 gst_textoverlay_render_text (overlay,
528 overlay->default_text, strlen (overlay->default_text));
530 overlay->need_render = FALSE;
533 gst_textoverlay_video_chain (overlay->srcpad, GST_DATA (video_frame));
537 gst_textoverlay_font_init (GstTextOverlay * overlay)
539 cairo_font_extents_t font_extents;
541 cairo_select_font (overlay->cr, overlay->font, overlay->slant,
543 cairo_scale_font (overlay->cr, overlay->scale);
545 cairo_current_font_extents (overlay->cr, &font_extents);
546 overlay->text_height = font_extents.height;
547 if (overlay->text_height & 1)
548 overlay->text_height++;
550 overlay->need_render = TRUE;
553 static GstStateChangeReturn
554 gst_textoverlay_change_state (GstElement * element, GstStateChange transition)
556 GstTextOverlay *overlay;
558 overlay = GST_TEXTOVERLAY (element);
560 switch (transition) {
561 case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
563 case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
565 case GST_STATE_CHANGE_PAUSED_TO_READY:
571 parent_class->change_state (element, transition);
573 return GST_STATE_CHANGE_SUCCESS;
577 gst_textoverlay_finalize (GObject * object)
579 GstTextOverlay *overlay = GST_TEXTOVERLAY (object);
582 cairo_destroy (overlay->cr);
585 G_OBJECT_CLASS (parent_class)->finalize (object);
589 gst_textoverlay_init (GstTextOverlay * overlay)
592 overlay->video_sinkpad =
593 gst_pad_new_from_template (gst_static_pad_template_get
594 (&video_sink_template_factory), "video_sink");
595 gst_pad_set_link_function (overlay->video_sinkpad,
596 gst_textoverlay_video_sinkconnect);
597 gst_element_add_pad (GST_ELEMENT (overlay), overlay->video_sinkpad);
600 overlay->text_sinkpad =
601 gst_pad_new_from_template (gst_static_pad_template_get
602 (&text_sink_template_factory), "text_sink");
603 gst_element_add_pad (GST_ELEMENT (overlay), overlay->text_sinkpad);
607 gst_pad_new_from_template (gst_static_pad_template_get
608 (&textoverlay_src_template_factory), "src");
609 gst_element_add_pad (GST_ELEMENT (overlay), overlay->srcpad);
611 overlay->cr = cairo_create ();
613 overlay->halign = GST_TEXT_OVERLAY_HALIGN_CENTER;
614 overlay->valign = GST_TEXT_OVERLAY_VALIGN_BASELINE;
615 overlay->x0 = overlay->y0 = 25;
617 overlay->default_text = g_strdup ("");
618 overlay->need_render = TRUE;
620 overlay->font = g_strdup ("sans");
621 overlay->slant = CAIRO_FONT_SLANT_NORMAL;
622 overlay->weight = CAIRO_FONT_WEIGHT_NORMAL;
624 gst_textoverlay_font_init (overlay);
626 gst_element_set_loop_function (GST_ELEMENT (overlay), gst_textoverlay_loop);
630 gst_textoverlay_set_property (GObject * object, guint prop_id,
631 const GValue * value, GParamSpec * pspec)
633 GstTextOverlay *overlay;
635 g_return_if_fail (GST_IS_TEXTOVERLAY (object));
636 overlay = GST_TEXTOVERLAY (object);
641 if (overlay->default_text) {
642 g_free (overlay->default_text);
644 overlay->default_text = g_strdup (g_value_get_string (value));
645 overlay->need_render = TRUE;
649 if (strcasecmp (g_value_get_string (value), "baseline") == 0)
650 overlay->valign = GST_TEXT_OVERLAY_VALIGN_BASELINE;
651 else if (strcasecmp (g_value_get_string (value), "bottom") == 0)
652 overlay->valign = GST_TEXT_OVERLAY_VALIGN_BOTTOM;
653 else if (strcasecmp (g_value_get_string (value), "top") == 0)
654 overlay->valign = GST_TEXT_OVERLAY_VALIGN_TOP;
656 g_warning ("Invalid 'valign' property value: %s",
657 g_value_get_string (value));
658 overlay->need_render = TRUE;
662 if (strcasecmp (g_value_get_string (value), "left") == 0)
663 overlay->halign = GST_TEXT_OVERLAY_HALIGN_LEFT;
664 else if (strcasecmp (g_value_get_string (value), "right") == 0)
665 overlay->halign = GST_TEXT_OVERLAY_HALIGN_RIGHT;
666 else if (strcasecmp (g_value_get_string (value), "center") == 0)
667 overlay->halign = GST_TEXT_OVERLAY_HALIGN_CENTER;
669 g_warning ("Invalid 'halign' property value: %s",
670 g_value_get_string (value));
671 overlay->need_render = TRUE;
675 overlay->x0 = g_value_get_int (value);
679 overlay->y0 = g_value_get_int (value);
684 g_free (overlay->font);
685 overlay->font = g_strdup (g_value_get_string (value));
686 gst_textoverlay_font_init (overlay);
695 gst_textoverlay_get_property (GObject * object, guint prop_id, GValue * value,
698 GstTextOverlay *overlay;
700 g_return_if_fail (GST_IS_TEXTOVERLAY (object));
701 overlay = GST_TEXTOVERLAY (object);
705 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);