gsttextoverlay.c 32.1 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
/* GStreamer
 * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
 * Copyright (C) <2003> David Schleef <ds@schleef.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */
20 21 22 23 24 25 26 27 28 29 30 31
/**
 * SECTION:element-cairotextoverlay
 *
 * cairotextoverlay renders the text on top of the video frames.
 *
 * <refsect2>
 * <title>Example launch line</title>
 * |[
 * gst-launch videotestsrc ! cairotextoverlay text="hello" ! autovideosink
 * ]|
 * </refsect2>
 */
32 33 34 35

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
36
#include <string.h>
37
#include <gst/video/video.h>
38 39
#include "gsttextoverlay.h"

40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
#include <cairo.h>

/* FIXME:
 *   - calculating the position of the shading rectangle is 
 *     not really right (try with text "L"), to say the least.
 *     Seems to work at least with latin script though.
 *   - check final x/y position and text width/height so that
 *     we don't do out-of-memory access when blitting the text.
 *     Also, we do not want to blit over the right or left margin.
 *   - what about text with newline characters? Cairo doesn't deal
 *     with that (we'd need to fix text_height usage for that as well)
 *   - upstream caps renegotiation, ie. when video window gets resized
 */

GST_DEBUG_CATEGORY_EXTERN (cairo_debug);
#define GST_CAT_DEFAULT cairo_debug

57 58 59 60
enum
{
  ARG_0,
  ARG_TEXT,
61
  ARG_SHADING,
62 63
  ARG_VALIGN,
  ARG_HALIGN,
64 65 66 67
  ARG_XPAD,
  ARG_YPAD,
  ARG_DELTAX,
  ARG_DELTAY,
68
  ARG_SILENT,
69 70 71
  ARG_FONT_DESC
};

72 73 74
#define DEFAULT_YPAD 25
#define DEFAULT_XPAD 25
#define DEFAULT_FONT "sans"
75
#define DEFAULT_SILENT FALSE
76

77 78
#define GST_CAIRO_TEXT_OVERLAY_DEFAULT_SCALE   20.0

79
static GstStaticPadTemplate cairo_text_overlay_src_template_factory =
80 81 82
GST_STATIC_PAD_TEMPLATE ("src",
    GST_PAD_SRC,
    GST_PAD_ALWAYS,
83
    GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("I420"))
84 85 86 87 88 89
    );

static GstStaticPadTemplate video_sink_template_factory =
GST_STATIC_PAD_TEMPLATE ("video_sink",
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
90
    GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("I420"))
91 92 93 94 95 96 97 98 99
    );

static GstStaticPadTemplate text_sink_template_factory =
GST_STATIC_PAD_TEMPLATE ("text_sink",
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("text/plain")
    );

100
static void gst_text_overlay_set_property (GObject * object,
101
    guint prop_id, const GValue * value, GParamSpec * pspec);
102
static GstStateChangeReturn gst_text_overlay_change_state (GstElement * element,
103
    GstStateChange transition);
104 105 106 107 108
static GstCaps *gst_text_overlay_getcaps (GstPad * pad);
static gboolean gst_text_overlay_setcaps (GstPad * pad, GstCaps * caps);
static GstPadLinkReturn gst_text_overlay_text_pad_linked (GstPad * pad,
    GstPad * peer);
static void gst_text_overlay_text_pad_unlinked (GstPad * pad);
109
static GstFlowReturn gst_text_overlay_collected (GstCollectPads2 * pads,
110 111
    gpointer data);
static void gst_text_overlay_finalize (GObject * object);
112
static void gst_text_overlay_font_init (GstCairoTextOverlay * overlay);
113
static gboolean gst_text_overlay_src_event (GstPad * pad, GstEvent * event);
114
static gboolean gst_text_overlay_video_event (GstPad * pad, GstEvent * event);
115 116 117 118 119 120 121 122 123 124 125 126

/* These macros are adapted from videotestsrc.c */
#define I420_Y_ROWSTRIDE(width) (GST_ROUND_UP_4(width))
#define I420_U_ROWSTRIDE(width) (GST_ROUND_UP_8(width)/2)
#define I420_V_ROWSTRIDE(width) ((GST_ROUND_UP_8(I420_Y_ROWSTRIDE(width)))/2)

#define I420_Y_OFFSET(w,h) (0)
#define I420_U_OFFSET(w,h) (I420_Y_OFFSET(w,h)+(I420_Y_ROWSTRIDE(w)*GST_ROUND_UP_2(h)))
#define I420_V_OFFSET(w,h) (I420_U_OFFSET(w,h)+(I420_U_ROWSTRIDE(w)*GST_ROUND_UP_2(h)/2))

#define I420_SIZE(w,h)     (I420_V_OFFSET(w,h)+(I420_V_ROWSTRIDE(w)*GST_ROUND_UP_2(h)/2))

127
GST_BOILERPLATE (GstCairoTextOverlay, gst_text_overlay, GstElement,
128
    GST_TYPE_ELEMENT);
129

130 131
static void
gst_text_overlay_base_init (gpointer g_class)
132 133 134 135
{
  GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);

  gst_element_class_add_pad_template (element_class,
136
      gst_static_pad_template_get (&cairo_text_overlay_src_template_factory));
137 138 139 140 141
  gst_element_class_add_pad_template (element_class,
      gst_static_pad_template_get (&video_sink_template_factory));
  gst_element_class_add_pad_template (element_class,
      gst_static_pad_template_get (&text_sink_template_factory));

142
  gst_element_class_set_static_metadata (element_class, "Text overlay",
143 144 145
      "Filter/Editor/Video",
      "Adds text strings on top of a video buffer",
      "David Schleef <ds@schleef.org>");
146 147 148
}

static void
149
gst_text_overlay_class_init (GstCairoTextOverlayClass * klass)
150 151 152 153 154 155 156
{
  GObjectClass *gobject_class;
  GstElementClass *gstelement_class;

  gobject_class = (GObjectClass *) klass;
  gstelement_class = (GstElementClass *) klass;

157 158
  gobject_class->finalize = gst_text_overlay_finalize;
  gobject_class->set_property = gst_text_overlay_set_property;
159

160 161
  gstelement_class->change_state =
      GST_DEBUG_FUNCPTR (gst_text_overlay_change_state);
162 163 164

  g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_TEXT,
      g_param_spec_string ("text", "text",
165 166
          "Text to be display.", "",
          G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
167 168 169
  g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_SHADING,
      g_param_spec_boolean ("shaded-background", "shaded background",
          "Whether to shade the background under the text area", FALSE,
170
          G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
171 172 173 174
  g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_VALIGN,
      g_param_spec_string ("valign", "vertical alignment",
          "Vertical alignment of the text. "
          "Can be either 'baseline', 'bottom', or 'top'",
175
          "baseline", G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
176 177 178 179
  g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_HALIGN,
      g_param_spec_string ("halign", "horizontal alignment",
          "Horizontal alignment of the text. "
          "Can be either 'left', 'right', or 'center'",
180
          "center", G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
181 182 183
  g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_XPAD,
      g_param_spec_int ("xpad", "horizontal paddding",
          "Horizontal paddding when using left/right alignment",
184 185
          G_MININT, G_MAXINT, DEFAULT_XPAD,
          G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
186 187 188
  g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_YPAD,
      g_param_spec_int ("ypad", "vertical padding",
          "Vertical padding when using top/bottom alignment",
189 190
          G_MININT, G_MAXINT, DEFAULT_YPAD,
          G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
191 192 193
  g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_DELTAX,
      g_param_spec_int ("deltax", "X position modifier",
          "Shift X position to the left or to the right. Unit is pixels.",
194
          G_MININT, G_MAXINT, 0, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
195 196 197
  g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_DELTAY,
      g_param_spec_int ("deltay", "Y position modifier",
          "Shift Y position up or down. Unit is pixels.",
198
          G_MININT, G_MAXINT, 0, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
199 200 201 202 203 204
  g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_FONT_DESC,
      g_param_spec_string ("font-desc", "font description",
          "Pango font description of font "
          "to be used for rendering. "
          "See documentation of "
          "pango_font_description_from_string"
205
          " for syntax.", "", G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
206 207 208 209 210
  /* FIXME 0.11: rename to "visible" or "text-visible" or "render-text" */
  g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_SILENT,
      g_param_spec_boolean ("silent", "silent",
          "Whether to render the text string",
          DEFAULT_SILENT, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
211 212
}

213 214 215
static void
gst_text_overlay_finalize (GObject * object)
{
216
  GstCairoTextOverlay *overlay = GST_CAIRO_TEXT_OVERLAY (object);
217

218
  gst_collect_pads2_stop (overlay->collect);
219 220 221 222 223 224 225 226 227 228
  gst_object_unref (overlay->collect);

  g_free (overlay->text_fill_image);
  g_free (overlay->text_outline_image);

  g_free (overlay->default_text);
  g_free (overlay->font);

  G_OBJECT_CLASS (parent_class)->finalize (object);
}
229 230

static void
231 232
gst_text_overlay_init (GstCairoTextOverlay * overlay,
    GstCairoTextOverlayClass * klass)
233
{
234 235
  /* video sink */
  overlay->video_sinkpad =
236 237
      gst_pad_new_from_static_template (&video_sink_template_factory,
      "video_sink");
238 239 240 241 242 243 244 245
  gst_pad_set_getcaps_function (overlay->video_sinkpad,
      GST_DEBUG_FUNCPTR (gst_text_overlay_getcaps));
  gst_pad_set_setcaps_function (overlay->video_sinkpad,
      GST_DEBUG_FUNCPTR (gst_text_overlay_setcaps));
  gst_element_add_pad (GST_ELEMENT (overlay), overlay->video_sinkpad);

  /* text sink */
  overlay->text_sinkpad =
246 247
      gst_pad_new_from_static_template (&text_sink_template_factory,
      "text_sink");
248 249 250 251 252 253 254 255
  gst_pad_set_link_function (overlay->text_sinkpad,
      GST_DEBUG_FUNCPTR (gst_text_overlay_text_pad_linked));
  gst_pad_set_unlink_function (overlay->text_sinkpad,
      GST_DEBUG_FUNCPTR (gst_text_overlay_text_pad_unlinked));
  gst_element_add_pad (GST_ELEMENT (overlay), overlay->text_sinkpad);

  /* (video) source */
  overlay->srcpad =
256 257
      gst_pad_new_from_static_template
      (&cairo_text_overlay_src_template_factory, "src");
258 259
  gst_pad_set_getcaps_function (overlay->srcpad,
      GST_DEBUG_FUNCPTR (gst_text_overlay_getcaps));
260 261
  gst_pad_set_event_function (overlay->srcpad,
      GST_DEBUG_FUNCPTR (gst_text_overlay_src_event));
262 263
  gst_element_add_pad (GST_ELEMENT (overlay), overlay->srcpad);

264 265
  overlay->halign = GST_CAIRO_TEXT_OVERLAY_HALIGN_CENTER;
  overlay->valign = GST_CAIRO_TEXT_OVERLAY_VALIGN_BASELINE;
266 267 268 269 270 271 272 273 274 275 276
  overlay->xpad = DEFAULT_XPAD;
  overlay->ypad = DEFAULT_YPAD;
  overlay->deltax = 0;
  overlay->deltay = 0;

  overlay->default_text = g_strdup ("");
  overlay->need_render = TRUE;

  overlay->font = g_strdup (DEFAULT_FONT);
  gst_text_overlay_font_init (overlay);

277 278
  overlay->silent = DEFAULT_SILENT;

279 280
  overlay->fps_n = 0;
  overlay->fps_d = 1;
281

282
  overlay->collect = gst_collect_pads2_new ();
283

284
  gst_collect_pads2_set_function (overlay->collect,
285 286
      GST_DEBUG_FUNCPTR (gst_text_overlay_collected), overlay);

287 288
  overlay->video_collect_data = gst_collect_pads2_add_pad (overlay->collect,
      overlay->video_sinkpad, sizeof (GstCollectData2));
289

290
  /* FIXME: hacked way to override/extend the event function of
291
   * GstCollectPads2; because it sets its own event function giving the
292 293 294 295 296 297
   * element no access to events. Nicked from avimux. */
  overlay->collect_event =
      (GstPadEventFunction) GST_PAD_EVENTFUNC (overlay->video_sinkpad);
  gst_pad_set_event_function (overlay->video_sinkpad,
      GST_DEBUG_FUNCPTR (gst_text_overlay_video_event));

298 299 300 301 302
  /* text pad will be added when it is linked */
  overlay->text_collect_data = NULL;
}

static void
303
gst_text_overlay_font_init (GstCairoTextOverlay * overlay)
304 305 306 307
{
  cairo_font_extents_t font_extents;
  cairo_surface_t *surface;
  cairo_t *cr;
308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334
  gchar *font_desc, *sep;

  font_desc = g_ascii_strdown (overlay->font, -1);

  /* cairo_select_font_face() does not parse the size at the end, so we have
   * to do that ourselves; same for slate and weight */
  sep = MAX (strrchr (font_desc, ' '), strrchr (font_desc, ','));
  if (sep != NULL && g_strtod (sep, NULL) > 0.0) {
    /* there may be a suffix such as 'px', but we just ignore that for now */
    overlay->scale = g_strtod (sep, NULL);
  } else {
    overlay->scale = GST_CAIRO_TEXT_OVERLAY_DEFAULT_SCALE;
  }
  if (strstr (font_desc, "bold"))
    overlay->weight = CAIRO_FONT_WEIGHT_BOLD;
  else
    overlay->weight = CAIRO_FONT_WEIGHT_NORMAL;

  if (strstr (font_desc, "italic"))
    overlay->slant = CAIRO_FONT_SLANT_ITALIC;
  else if (strstr (font_desc, "oblique"))
    overlay->slant = CAIRO_FONT_SLANT_OBLIQUE;
  else
    overlay->slant = CAIRO_FONT_SLANT_NORMAL;

  GST_LOG_OBJECT (overlay, "Font desc: '%s', scale=%f, weight=%d, slant=%d",
      overlay->font, overlay->scale, overlay->weight, overlay->slant);
335 336 337 338 339 340 341

  surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 256, 256);
  cr = cairo_create (surface);

  cairo_select_font_face (cr, overlay->font, overlay->slant, overlay->weight);
  cairo_set_font_size (cr, overlay->scale);

342 343 344
  /* this has a static leak:
   * http://lists.freedesktop.org/archives/cairo/2007-May/010623.html
   */
345 346 347 348 349 350
  cairo_font_extents (cr, &font_extents);
  overlay->font_height = GST_ROUND_UP_2 ((guint) font_extents.height);
  overlay->need_render = TRUE;

  cairo_destroy (cr);
  cairo_surface_destroy (surface);
351
  g_free (font_desc);
352 353 354 355 356 357
}

static void
gst_text_overlay_set_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * pspec)
{
358
  GstCairoTextOverlay *overlay = GST_CAIRO_TEXT_OVERLAY (object);
359

360
  GST_OBJECT_LOCK (overlay);
361 362 363 364 365 366 367 368 369 370 371 372 373 374

  switch (prop_id) {
    case ARG_TEXT:{
      g_free (overlay->default_text);
      overlay->default_text = g_value_dup_string (value);
      break;
    }
    case ARG_SHADING:{
      overlay->want_shading = g_value_get_boolean (value);
      break;
    }
    case ARG_VALIGN:{
      const gchar *s = g_value_get_string (value);

375
      if (g_ascii_strcasecmp (s, "baseline") == 0)
376
        overlay->valign = GST_CAIRO_TEXT_OVERLAY_VALIGN_BASELINE;
377
      else if (g_ascii_strcasecmp (s, "bottom") == 0)
378
        overlay->valign = GST_CAIRO_TEXT_OVERLAY_VALIGN_BOTTOM;
379
      else if (g_ascii_strcasecmp (s, "top") == 0)
380
        overlay->valign = GST_CAIRO_TEXT_OVERLAY_VALIGN_TOP;
381 382 383 384 385 386 387
      else
        g_warning ("Invalid 'valign' property value: %s", s);
      break;
    }
    case ARG_HALIGN:{
      const gchar *s = g_value_get_string (value);

388
      if (g_ascii_strcasecmp (s, "left") == 0)
389
        overlay->halign = GST_CAIRO_TEXT_OVERLAY_HALIGN_LEFT;
390
      else if (g_ascii_strcasecmp (s, "right") == 0)
391
        overlay->halign = GST_CAIRO_TEXT_OVERLAY_HALIGN_RIGHT;
392
      else if (g_ascii_strcasecmp (s, "center") == 0)
393
        overlay->halign = GST_CAIRO_TEXT_OVERLAY_HALIGN_CENTER;
394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421
      else
        g_warning ("Invalid 'halign' property value: %s", s);
      break;
    }
    case ARG_XPAD:{
      overlay->xpad = g_value_get_int (value);
      break;
    }
    case ARG_YPAD:{
      overlay->ypad = g_value_get_int (value);
      break;
    }
    case ARG_DELTAX:{
      overlay->deltax = g_value_get_int (value);
      break;
    }
    case ARG_DELTAY:{
      overlay->deltay = g_value_get_int (value);
      break;
    }
    case ARG_FONT_DESC:{
      g_free (overlay->font);
      overlay->font = g_value_dup_string (value);
      if (overlay->font == NULL)
        overlay->font = g_strdup (DEFAULT_FONT);
      gst_text_overlay_font_init (overlay);
      break;
    }
422 423 424
    case ARG_SILENT:
      overlay->silent = g_value_get_boolean (value);
      break;
425 426 427 428
    default:{
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
429
  }
430 431 432

  overlay->need_render = TRUE;

433
  GST_OBJECT_UNLOCK (overlay);
434 435 436
}

static void
437 438
gst_text_overlay_render_text (GstCairoTextOverlay * overlay,
    const gchar * text, gint textlen)
439 440
{
  cairo_text_extents_t extents;
441 442 443
  cairo_surface_t *surface;
  cairo_t *cr;
  gchar *string;
444
  double x, y;
445

446 447 448 449 450
  if (overlay->silent) {
    GST_DEBUG_OBJECT (overlay, "Silent mode, not rendering");
    return;
  }

451 452 453
  if (textlen < 0)
    textlen = strlen (text);

454
  if (!overlay->need_render) {
455 456 457 458 459 460
    GST_DEBUG ("Using previously rendered text.");
    g_return_if_fail (overlay->text_fill_image != NULL);
    g_return_if_fail (overlay->text_outline_image != NULL);
    return;
  }

461
  string = g_strndup (text, textlen);
462
  GST_DEBUG ("Rendering text '%s' on cairo RGBA surface", string);
463

464
  overlay->text_fill_image =
465 466 467 468 469
      g_realloc (overlay->text_fill_image,
      4 * overlay->width * overlay->font_height);

  surface = cairo_image_surface_create_for_data (overlay->text_fill_image,
      CAIRO_FORMAT_ARGB32, overlay->width, overlay->font_height,
470
      overlay->width * 4);
471

472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488
  cr = cairo_create (surface);

  cairo_select_font_face (cr, overlay->font, overlay->slant, overlay->weight);
  cairo_set_font_size (cr, overlay->scale);

  cairo_save (cr);
  cairo_rectangle (cr, 0, 0, overlay->width, overlay->font_height);
  cairo_set_source_rgba (cr, 0, 0, 0, 1.0);

  cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
  cairo_fill (cr);
  cairo_restore (cr);

  cairo_save (cr);
  cairo_text_extents (cr, string, &extents);
  cairo_set_source_rgba (cr, 1, 1, 1, 1.0);

489
  switch (overlay->halign) {
490
    case GST_CAIRO_TEXT_OVERLAY_HALIGN_LEFT:
491
      x = overlay->xpad;
492
      break;
493
    case GST_CAIRO_TEXT_OVERLAY_HALIGN_CENTER:
494
      x = (overlay->width - extents.width) / 2;
495
      break;
496
    case GST_CAIRO_TEXT_OVERLAY_HALIGN_RIGHT:
497
      x = overlay->width - extents.width - overlay->xpad;
498 499 500 501
      break;
    default:
      x = 0;
  }
502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517
  x += overlay->deltax;

  overlay->text_x0 = x;
  overlay->text_x1 = x + extents.x_advance;

  overlay->text_dy = (extents.height + extents.y_bearing);
  y = overlay->font_height - overlay->text_dy;

  cairo_move_to (cr, x, y);
  cairo_show_text (cr, string);
  cairo_restore (cr);

  cairo_destroy (cr);
  cairo_surface_destroy (surface);

  /* ----------- */
518

519
  overlay->text_outline_image =
520 521 522 523 524
      g_realloc (overlay->text_outline_image,
      4 * overlay->width * overlay->font_height);

  surface = cairo_image_surface_create_for_data (overlay->text_outline_image,
      CAIRO_FORMAT_ARGB32, overlay->width, overlay->font_height,
525 526
      overlay->width * 4);

527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545
  cr = cairo_create (surface);

  cairo_select_font_face (cr, overlay->font, overlay->slant, overlay->weight);
  cairo_set_font_size (cr, overlay->scale);

  cairo_save (cr);
  cairo_rectangle (cr, 0, 0, overlay->width, overlay->font_height);
  cairo_set_source_rgba (cr, 0, 0, 0, 1.0);
  cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
  cairo_fill (cr);
  cairo_restore (cr);

  cairo_save (cr);
  cairo_move_to (cr, x, y);
  cairo_set_source_rgba (cr, 1, 1, 1, 1.0);
  cairo_set_line_width (cr, 1.0);
  cairo_text_path (cr, string);
  cairo_stroke (cr);
  cairo_restore (cr);
546

547
  g_free (string);
548 549 550 551 552

  cairo_destroy (cr);
  cairo_surface_destroy (surface);

  overlay->need_render = FALSE;
553 554
}

555 556 557
static GstCaps *
gst_text_overlay_getcaps (GstPad * pad)
{
558
  GstCairoTextOverlay *overlay;
559 560
  GstPad *otherpad;
  GstCaps *caps;
561

562
  overlay = GST_CAIRO_TEXT_OVERLAY (gst_pad_get_parent (pad));
563

564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600
  if (pad == overlay->srcpad)
    otherpad = overlay->video_sinkpad;
  else
    otherpad = overlay->srcpad;

  /* we can do what the peer can */
  caps = gst_pad_peer_get_caps (otherpad);
  if (caps) {
    GstCaps *temp;
    const GstCaps *templ;

    GST_DEBUG_OBJECT (pad, "peer caps  %" GST_PTR_FORMAT, caps);

    /* filtered against our padtemplate */
    templ = gst_pad_get_pad_template_caps (otherpad);
    GST_DEBUG_OBJECT (pad, "our template  %" GST_PTR_FORMAT, templ);
    temp = gst_caps_intersect (caps, templ);
    GST_DEBUG_OBJECT (pad, "intersected %" GST_PTR_FORMAT, temp);
    gst_caps_unref (caps);
    /* this is what we can do */
    caps = temp;
  } else {
    /* no peer, our padtemplate is enough then */
    caps = gst_caps_copy (gst_pad_get_pad_template_caps (pad));
  }

  GST_DEBUG_OBJECT (overlay, "returning  %" GST_PTR_FORMAT, caps);

  gst_object_unref (overlay);

  return caps;
}

/* FIXME: upstream nego (e.g. when the video window is resized) */

static gboolean
gst_text_overlay_setcaps (GstPad * pad, GstCaps * caps)
601
{
602
  GstCairoTextOverlay *overlay;
603
  GstStructure *structure;
604
  gboolean ret = FALSE;
605
  const GValue *fps;
606 607 608 609 610

  if (!GST_PAD_IS_SINK (pad))
    return TRUE;

  g_return_val_if_fail (gst_caps_is_fixed (caps), FALSE);
611

612
  overlay = GST_CAIRO_TEXT_OVERLAY (gst_pad_get_parent (pad));
613

614 615
  overlay->width = 0;
  overlay->height = 0;
616
  structure = gst_caps_get_structure (caps, 0);
617 618
  fps = gst_structure_get_value (structure, "framerate");

619
  if (gst_structure_get_int (structure, "width", &overlay->width) &&
620 621
      gst_structure_get_int (structure, "height", &overlay->height) &&
      fps != NULL) {
622 623 624
    ret = gst_pad_set_caps (overlay->srcpad, caps);
  }

625 626
  overlay->fps_n = gst_value_get_fraction_numerator (fps);
  overlay->fps_d = gst_value_get_fraction_denominator (fps);
627

628 629
  gst_object_unref (overlay);

630
  return ret;
631 632
}

633 634 635
static GstPadLinkReturn
gst_text_overlay_text_pad_linked (GstPad * pad, GstPad * peer)
{
636
  GstCairoTextOverlay *overlay;
637

638
  overlay = GST_CAIRO_TEXT_OVERLAY (GST_PAD_PARENT (pad));
639 640 641 642

  GST_DEBUG_OBJECT (overlay, "Text pad linked");

  if (overlay->text_collect_data == NULL) {
643 644
    overlay->text_collect_data = gst_collect_pads2_add_pad (overlay->collect,
        overlay->text_sinkpad, sizeof (GstCollectData2));
645 646 647 648 649 650
  }

  overlay->need_render = TRUE;

  return GST_PAD_LINK_OK;
}
651 652

static void
653 654
gst_text_overlay_text_pad_unlinked (GstPad * pad)
{
655
  GstCairoTextOverlay *overlay;
656 657

  /* don't use gst_pad_get_parent() here, will deadlock */
658
  overlay = GST_CAIRO_TEXT_OVERLAY (GST_PAD_PARENT (pad));
659 660 661 662

  GST_DEBUG_OBJECT (overlay, "Text pad unlinked");

  if (overlay->text_collect_data) {
663
    gst_collect_pads2_remove_pad (overlay->collect, overlay->text_sinkpad);
664 665 666 667 668 669 670 671 672 673 674
    overlay->text_collect_data = NULL;
  }

  overlay->need_render = TRUE;
}

#define BOX_SHADING_VAL -80
#define BOX_XPAD         6
#define BOX_YPAD         6

static inline void
675
gst_text_overlay_shade_y (GstCairoTextOverlay * overlay, guchar * dest,
676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695
    guint dest_stride, gint y0, gint y1)
{
  gint i, j, x0, x1;

  x0 = CLAMP (overlay->text_x0 - BOX_XPAD, 0, overlay->width);
  x1 = CLAMP (overlay->text_x1 + BOX_XPAD, 0, overlay->width);

  y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
  y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);

  for (i = y0; i < y1; ++i) {
    for (j = x0; j < x1; ++j) {
      gint y = dest[(i * dest_stride) + j] + BOX_SHADING_VAL;

      dest[(i * dest_stride) + j] = CLAMP (y, 0, 255);
    }
  }
}

static inline void
696
gst_text_overlay_blit_1 (GstCairoTextOverlay * overlay, guchar * dest,
697
    guchar * text_image, gint val, guint dest_stride, gint y0)
698
{
699 700
  gint i, j;
  gint x, a, y;
701
  gint y1;
702

703
  y = val;
704 705
  y0 = MIN (y0, overlay->height);
  y1 = MIN (y0 + overlay->font_height, overlay->height);
706

707
  for (i = y0; i < y1; i++) {
708
    for (j = 0; j < overlay->width; j++) {
709 710 711
      x = dest[i * dest_stride + j];
      a = text_image[4 * ((i - y0) * overlay->width + j) + 1];
      dest[i * dest_stride + j] = (y * a + x * (255 - a)) / 255;
712 713
    }
  }
714
}
715

716
static inline void
717
gst_text_overlay_blit_sub2x2 (GstCairoTextOverlay * overlay, guchar * dest,
718
    guchar * text_image, gint val, guint dest_stride, gint y0)
719
{
720 721
  gint i, j;
  gint x, a, y;
722 723 724 725
  gint y1;

  y0 = MIN (y0, overlay->height);
  y1 = MIN (y0 + overlay->font_height, overlay->height);
726 727 728

  y = val;

729
  for (i = y0; i < y1; i += 2) {
730
    for (j = 0; j < overlay->width; j += 2) {
731 732 733 734 735 736
      x = dest[(i / 2) * dest_stride + j / 2];
      a = (text_image[4 * ((i - y0) * overlay->width + j) + 1] +
          text_image[4 * ((i - y0) * overlay->width + j + 1) + 1] +
          text_image[4 * ((i - y0 + 1) * overlay->width + j) + 1] +
          text_image[4 * ((i - y0 + 1) * overlay->width + j + 1) + 1] + 2) / 4;
      dest[(i / 2) * dest_stride + j / 2] = (y * a + x * (255 - a)) / 255;
737 738
    }
  }
739 740 741
}


742
static GstFlowReturn
743 744
gst_text_overlay_push_frame (GstCairoTextOverlay * overlay,
    GstBuffer * video_frame)
745
{
746 747
  guchar *y, *u, *v;
  gint ypos;
748

749
  video_frame = gst_buffer_make_writable (video_frame);
750 751

  switch (overlay->valign) {
752
    case GST_CAIRO_TEXT_OVERLAY_VALIGN_BOTTOM:
753
      ypos = overlay->height - overlay->font_height - overlay->ypad;
754
      break;
755
    case GST_CAIRO_TEXT_OVERLAY_VALIGN_BASELINE:
756 757
      ypos = overlay->height - (overlay->font_height - overlay->text_dy)
          - overlay->ypad;
758
      break;
759
    case GST_CAIRO_TEXT_OVERLAY_VALIGN_TOP:
760 761 762 763
      ypos = overlay->ypad;
      break;
    default:
      ypos = overlay->ypad;
764 765 766
      break;
  }

767 768 769 770 771 772 773 774 775 776 777 778 779 780
  ypos += overlay->deltay;

  y = GST_BUFFER_DATA (video_frame);
  u = y + I420_U_OFFSET (overlay->width, overlay->height);
  v = y + I420_V_OFFSET (overlay->width, overlay->height);

  /* shaded background box */
  if (overlay->want_shading) {
    gst_text_overlay_shade_y (overlay,
        y, I420_Y_ROWSTRIDE (overlay->width),
        ypos + overlay->text_dy, ypos + overlay->font_height);
  }

  /* blit outline text on video image */
781
  gst_text_overlay_blit_1 (overlay,
782 783
      y,
      overlay->text_outline_image, 0, I420_Y_ROWSTRIDE (overlay->width), ypos);
784
  gst_text_overlay_blit_sub2x2 (overlay,
785 786 787 788 789
      u,
      overlay->text_outline_image, 128, I420_U_ROWSTRIDE (overlay->width),
      ypos);
  gst_text_overlay_blit_sub2x2 (overlay, v, overlay->text_outline_image, 128,
      I420_V_ROWSTRIDE (overlay->width), ypos);
790 791 792

  /* blit text on video image */
  gst_text_overlay_blit_1 (overlay,
793 794
      y,
      overlay->text_fill_image, 255, I420_Y_ROWSTRIDE (overlay->width), ypos);
795
  gst_text_overlay_blit_sub2x2 (overlay,
796 797
      u,
      overlay->text_fill_image, 128, I420_U_ROWSTRIDE (overlay->width), ypos);
798
  gst_text_overlay_blit_sub2x2 (overlay,
799 800
      v,
      overlay->text_fill_image, 128, I420_V_ROWSTRIDE (overlay->width), ypos);
801

802
  return gst_pad_push (overlay->srcpad, video_frame);
803 804 805
}

static void
806
gst_text_overlay_pop_video (GstCairoTextOverlay * overlay)
807
{
808
  GstBuffer *buf;
809

810
  buf = gst_collect_pads2_pop (overlay->collect, overlay->video_collect_data);
811 812
  g_return_if_fail (buf != NULL);
  gst_buffer_unref (buf);
813 814
}

815
static void
816
gst_text_overlay_pop_text (GstCairoTextOverlay * overlay)
817
{
818
  GstBuffer *buf;
819

820
  if (overlay->text_collect_data) {
821
    buf = gst_collect_pads2_pop (overlay->collect, overlay->text_collect_data);
822 823 824
    g_return_if_fail (buf != NULL);
    gst_buffer_unref (buf);
  }
825 826 827

  overlay->need_render = TRUE;
}
828

829 830
/* This function is called when there is data on all pads */
static GstFlowReturn
831
gst_text_overlay_collected (GstCollectPads2 * pads, gpointer data)
832
{
833
  GstCairoTextOverlay *overlay;
834 835 836 837
  GstFlowReturn ret = GST_FLOW_OK;
  GstClockTime now, txt_end, frame_end;
  GstBuffer *video_frame = NULL;
  GstBuffer *text_buf = NULL;
838 839
  gchar *text;
  gint text_len;
840

841
  overlay = GST_CAIRO_TEXT_OVERLAY (data);
842

843
  GST_DEBUG ("Collecting");
844

845
  video_frame = gst_collect_pads2_peek (overlay->collect,
846
      overlay->video_collect_data);
847

848 849 850 851
  /* send EOS if video stream EOSed regardless of text stream */
  if (video_frame == NULL) {
    GST_DEBUG ("Video stream at EOS");
    if (overlay->text_collect_data) {
852
      text_buf = gst_collect_pads2_pop (overlay->collect,
853 854 855
          overlay->text_collect_data);
    }
    gst_pad_push_event (overlay->srcpad, gst_event_new_eos ());
856
    ret = GST_FLOW_EOS;
857
    goto done;
858 859
  }

860 861 862
  if (GST_BUFFER_TIMESTAMP (video_frame) == GST_CLOCK_TIME_NONE) {
    g_warning ("%s: video frame has invalid timestamp", G_STRLOC);
  }
863

864
  now = GST_BUFFER_TIMESTAMP (video_frame);
865

866 867
  if (GST_BUFFER_DURATION (video_frame) != GST_CLOCK_TIME_NONE) {
    frame_end = now + GST_BUFFER_DURATION (video_frame);
868
  } else if (overlay->fps_n > 0) {
Jan Schmidt's avatar
Jan Schmidt committed
869
    frame_end = now + gst_util_uint64_scale_int (GST_SECOND,
870
        overlay->fps_d, overlay->fps_n);
871 872 873 874 875
  } else {
    /* magic value, does not really matter since texts
     * tend to span quite a few frames in practice anyway */
    frame_end = now + GST_SECOND / 25;
  }
876

877 878
  GST_DEBUG ("Got video frame: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
      GST_TIME_ARGS (now), GST_TIME_ARGS (frame_end));
879

880 881 882 883 884 885 886 887 888 889 890 891 892 893
  /* text pad not linked? */
  if (overlay->text_collect_data == NULL) {
    GST_DEBUG ("Text pad not linked, rendering default text: '%s'",
        GST_STR_NULL (overlay->default_text));
    if (overlay->default_text && *overlay->default_text != '\0') {
      gst_text_overlay_render_text (overlay, overlay->default_text, -1);
      ret = gst_text_overlay_push_frame (overlay, video_frame);
    } else {
      ret = gst_pad_push (overlay->srcpad, video_frame);
    }
    gst_text_overlay_pop_video (overlay);
    video_frame = NULL;
    goto done;
  }
894

895
  text_buf = gst_collect_pads2_peek (overlay->collect,
896
      overlay->text_collect_data);
897

898 899 900 901 902 903 904 905
  /* just push the video frame if the text stream has EOSed */
  if (text_buf == NULL) {
    GST_DEBUG ("Text pad EOSed, just pushing video frame as is");
    ret = gst_pad_push (overlay->srcpad, video_frame);
    gst_text_overlay_pop_video (overlay);
    video_frame = NULL;
    goto done;
  }
906

907 908 909 910 911 912 913 914 915
  /* if the text buffer isn't stamped right, pop it off the
   *  queue and display it for the current video frame only */
  if (GST_BUFFER_TIMESTAMP (text_buf) == GST_CLOCK_TIME_NONE ||
      GST_BUFFER_DURATION (text_buf) == GST_CLOCK_TIME_NONE) {
    GST_WARNING ("Got text buffer with invalid time stamp or duration");
    gst_text_overlay_pop_text (overlay);
    GST_BUFFER_TIMESTAMP (text_buf) = now;
    GST_BUFFER_DURATION (text_buf) = frame_end - now;
  }
916

917
  txt_end = GST_BUFFER_TIMESTAMP (text_buf) + GST_BUFFER_DURATION (text_buf);
918

919 920
  GST_DEBUG ("Got text buffer: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
      GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (text_buf)), GST_TIME_ARGS (txt_end));
921

922 923 924 925 926 927 928 929
  /* if the text buffer is too old, pop it off the
   * queue and return so we get a new one next time */
  if (txt_end < now) {
    GST_DEBUG ("Text buffer too old, popping off the queue");
    gst_text_overlay_pop_text (overlay);
    ret = GST_FLOW_OK;
    goto done;
  }
930

931 932 933 934 935 936 937 938 939
  /* if the video frame ends before the text even starts,
   * just push it out as is and pop it off the queue */
  if (frame_end < GST_BUFFER_TIMESTAMP (text_buf)) {
    GST_DEBUG ("Video buffer before text, pushing out and popping off queue");
    ret = gst_pad_push (overlay->srcpad, video_frame);
    gst_text_overlay_pop_video (overlay);
    video_frame = NULL;
    goto done;
  }
940

941
  /* text duration overlaps video frame duration */
942 943 944 945 946 947 948 949 950 951 952 953 954 955 956
  text = g_strndup ((gchar *) GST_BUFFER_DATA (text_buf),
      GST_BUFFER_SIZE (text_buf));
  g_strdelimit (text, "\n\r\t", ' ');
  text_len = strlen (text);

  if (text_len > 0) {
    GST_DEBUG ("Rendering text '%*s'", text_len, text);;
    gst_text_overlay_render_text (overlay, text, text_len);
  } else {
    GST_DEBUG ("No text to render (empty buffer)");
    gst_text_overlay_render_text (overlay, " ", 1);
  }

  g_free (text);

957
  gst_text_overlay_pop_video (overlay);
958
  ret = gst_text_overlay_push_frame (overlay, video_frame);
959 960
  video_frame = NULL;
  goto done;
961

962 963 964 965
done:
  {
    if (text_buf)
      gst_buffer_unref (text_buf);
966

967 968
    if (video_frame)
      gst_buffer_unref (video_frame);
969

970 971 972
    return ret;
  }
}
973

974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991
static gboolean
gst_text_overlay_src_event (GstPad * pad, GstEvent * event)
{
  GstCairoTextOverlay *overlay =
      GST_CAIRO_TEXT_OVERLAY (gst_pad_get_parent (pad));
  gboolean ret = TRUE;

  /* forward events to the video sink, and, if it is linked, the text sink */
  if (overlay->text_collect_data) {
    gst_event_ref (event);
    ret &= gst_pad_push_event (overlay->text_sinkpad, event);
  }
  ret &= gst_pad_push_event (overlay->video_sinkpad, event);

  gst_object_unref (overlay);
  return ret;
}

992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006
static gboolean
gst_text_overlay_video_event (GstPad * pad, GstEvent * event)
{
  gboolean ret = FALSE;
  GstCairoTextOverlay *overlay = NULL;

  overlay = GST_CAIRO_TEXT_OVERLAY (gst_pad_get_parent (pad));

  if (GST_EVENT_TYPE (event) == GST_EVENT_NEWSEGMENT) {
    GST_DEBUG_OBJECT (overlay,
        "received new segment on video sink pad, forwarding");
    gst_event_ref (event);
    gst_pad_push_event (overlay->srcpad, event);
  }

1007
  /* now GstCollectPads2 can take care of the rest, e.g. EOS */
1008 1009 1010 1011 1012
  ret = overlay->collect_event (pad, event);
  gst_object_unref (overlay);
  return ret;
}

1013 1014 1015 1016
static GstStateChangeReturn
gst_text_overlay_change_state (GstElement * element, GstStateChange transition)
{
  GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
1017
  GstCairoTextOverlay *overlay = GST_CAIRO_TEXT_OVERLAY (element);
1018

1019 1020
  switch (transition) {
    case GST_STATE_CHANGE_READY_TO_PAUSED:
1021
      gst_collect_pads2_start (overlay->collect);
1022 1023 1024 1025
      break;
    case GST_STATE_CHANGE_PAUSED_TO_READY:
      /* need to unblock the collectpads before calling the
       * parent change_state so that streaming can finish */
1026
      gst_collect_pads2_stop (overlay->collect);
1027 1028 1029 1030 1031
      break;
    default:
      break;
  }

1032 1033 1034
  ret = parent_class->change_state (element, transition);
  if (ret == GST_STATE_CHANGE_FAILURE)
    return ret;
1035

1036
  switch (transition) {
1037 1038 1039
    default:
      break;
  }
1040 1041

  return ret;
1042
}