gst_plugins_base/ext/pango/gsttextrender.c
changeset 0 0e761a78d257
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gst_plugins_base/ext/pango/gsttextrender.c	Thu Dec 17 08:53:32 2009 +0200
@@ -0,0 +1,382 @@
+/* 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.
+ */
+
+
+/**
+ * SECTION:element-textrender
+ * @see_also: #GstTextOverlay
+ *
+ * <refsect2>
+ * <para>
+ * This plugin renders text received on the text sink pad to a video
+ * buffer (retaining the alpha channel), so it can later be overlayed
+ * on top of video streams using other elements.
+ * </para>
+ * <para>
+ * The text can contain newline characters. (FIXME: What about text 
+ * wrapping? It does not make sense in this context)
+ * </para>
+ * <para>
+ * Example pipeline:
+ * <programlisting>
+ * gst-launch -v filesrc location=subtitles.srt ! subparse ! textrender ! xvimagesink
+ * </programlisting>
+ * </para>
+ * </refsect2>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gst/gst.h>
+#include <gst/video/video.h>
+
+#include "gsttextrender.h"
+
+GST_DEBUG_CATEGORY_EXTERN (pango_debug);
+#define GST_CAT_DEFAULT pango_debug
+
+static const GstElementDetails text_render_details =
+GST_ELEMENT_DETAILS ("Text renderer",
+    "Filter/Editor/Video",
+    "Renders a text string to an image bitmap",
+    "David Schleef <ds@schleef.org>, "
+    "Ronald S. Bultje <rbultje@ronald.bitfreak.net>");
+
+enum
+{
+  ARG_0,
+  ARG_FONT_DESC
+};
+
+
+static GstStaticPadTemplate src_template_factory =
+GST_STATIC_PAD_TEMPLATE ("src",
+    GST_PAD_SRC,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("AYUV"))
+    );
+
+static GstStaticPadTemplate sink_template_factory =
+    GST_STATIC_PAD_TEMPLATE ("sink",
+    GST_PAD_SINK,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS ("text/x-pango-markup; text/plain")
+    );
+
+GST_BOILERPLATE (GstTextRender, gst_text_render, GstElement, GST_TYPE_ELEMENT)
+
+     static void gst_text_render_finalize (GObject * object);
+     static void gst_text_render_set_property (GObject * object,
+    guint prop_id, const GValue * value, GParamSpec * pspec);
+
+     static void gst_text_render_base_init (gpointer g_class)
+{
+  GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
+
+  gst_element_class_add_pad_template (element_class,
+      gst_static_pad_template_get (&src_template_factory));
+  gst_element_class_add_pad_template (element_class,
+      gst_static_pad_template_get (&sink_template_factory));
+
+  gst_element_class_set_details (element_class, &text_render_details);
+}
+
+static void
+gst_text_render_class_init (GstTextRenderClass * klass)
+{
+  GObjectClass *gobject_class;
+  GstElementClass *gstelement_class;
+
+  gobject_class = (GObjectClass *) klass;
+  gstelement_class = (GstElementClass *) klass;
+
+  parent_class = g_type_class_peek_parent (klass);
+
+  gobject_class->finalize = gst_text_render_finalize;
+  gobject_class->set_property = gst_text_render_set_property;
+
+  klass->pango_context = pango_ft2_get_context (72, 72);
+  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"
+          " for syntax.", "", G_PARAM_WRITABLE));
+}
+
+
+static void
+resize_bitmap (GstTextRender * render, gint width, gint height)
+{
+  FT_Bitmap *bitmap = &render->bitmap;
+  gint pitch = (width | 3) + 1;
+  gint size = pitch * height;
+
+  /* no need to keep reallocating; just keep the maximum size so far */
+  if (size <= render->bitmap_buffer_size) {
+    bitmap->rows = height;
+    bitmap->width = width;
+    bitmap->pitch = pitch;
+    memset (bitmap->buffer, 0, render->bitmap_buffer_size);
+    return;
+  }
+  if (!bitmap->buffer) {
+    /* initialize */
+    bitmap->pixel_mode = ft_pixel_mode_grays;
+    bitmap->num_grays = 256;
+  }
+  if (bitmap->buffer)
+    bitmap->buffer = g_realloc (bitmap->buffer, size);
+  else
+    bitmap->buffer = g_malloc (size);
+  bitmap->rows = height;
+  bitmap->width = width;
+  bitmap->pitch = pitch;
+  memset (bitmap->buffer, 0, size);
+  render->bitmap_buffer_size = size;
+}
+
+static void
+gst_text_render_render_text (GstTextRender * render)
+{
+  PangoRectangle ink_rect, logical_rect;
+
+  pango_layout_get_pixel_extents (render->layout, &ink_rect, &logical_rect);
+  resize_bitmap (render, ink_rect.width, ink_rect.height + ink_rect.y);
+  pango_ft2_render_layout (&render->bitmap, render->layout, -ink_rect.x, 0);
+  render->baseline_y = ink_rect.y;
+}
+
+static gboolean
+gst_text_render_setcaps (GstPad * pad, GstCaps * caps)
+{
+  GstTextRender *render = GST_TEXT_RENDER (gst_pad_get_parent (pad));
+  GstStructure *structure;
+  gboolean ret = FALSE;
+  gint width = 0, height = 0;
+
+  structure = gst_caps_get_structure (caps, 0);
+  gst_structure_get_int (structure, "width", &width);
+  gst_structure_get_int (structure, "height", &height);
+
+  GST_DEBUG ("Got caps %" GST_PTR_FORMAT, caps);
+
+  if (width >= render->bitmap.width && height >= render->bitmap.rows) {
+    render->width = width;
+    render->height = height;
+    ret = TRUE;
+  }
+
+  gst_object_unref (render);
+  return ret;
+}
+
+static void
+gst_text_render_fixate_caps (GstPad * pad, GstCaps * caps)
+{
+  GstTextRender *render = GST_TEXT_RENDER (gst_pad_get_parent (pad));
+  GstStructure *s = gst_caps_get_structure (caps, 0);
+
+  GST_DEBUG ("Fixating caps %" GST_PTR_FORMAT, caps);
+  gst_structure_fixate_field_nearest_int (s, "width", render->bitmap.width);
+  gst_structure_fixate_field_nearest_int (s, "height", render->bitmap.rows);
+  GST_DEBUG ("Fixated to    %" GST_PTR_FORMAT, caps);
+
+  gst_object_unref (render);
+}
+
+static void
+gst_text_renderer_bitmap_to_ayuv (GstTextRender * render, FT_Bitmap * bitmap,
+    guchar * pixbuf)
+{
+  int y;                        /* text bitmap coordinates */
+  int rowinc, bit_rowinc;
+  guchar *p, *bitp;
+  guchar v;
+
+  rowinc = render->width - bitmap->width;
+  bit_rowinc = bitmap->pitch - bitmap->width;
+
+  bitp = bitmap->buffer;
+  p = pixbuf;
+
+  for (y = 0; y < bitmap->rows; y++) {
+    int n;
+
+    for (n = bitmap->width; n > 0; --n) {
+      v = *bitp;
+      if (v) {
+        p[0] = v;
+        p[1] = 255;
+        p[2] = 0x80;
+        p[3] = 0x80;
+      }
+      p += 4;
+      bitp++;
+    }
+    p += rowinc * 4;
+    bitp += bit_rowinc;
+  }
+}
+
+
+static GstFlowReturn
+gst_text_render_chain (GstPad * pad, GstBuffer * inbuf)
+{
+  GstTextRender *render;
+  GstFlowReturn ret;
+  GstBuffer *outbuf;
+  GstCaps *caps = NULL;
+  guint8 *data = GST_BUFFER_DATA (inbuf);
+  guint size = GST_BUFFER_SIZE (inbuf);
+  gint n;
+
+  render = GST_TEXT_RENDER (gst_pad_get_parent (pad));
+
+  /* somehow pango barfs over "\0" buffers... */
+  while (size > 0 &&
+      (data[size - 1] == '\r' ||
+          data[size - 1] == '\n' || data[size - 1] == '\0')) {
+    size--;
+  }
+
+  /* render text */
+  GST_DEBUG ("rendering '%*s'", size, data);
+  pango_layout_set_markup (render->layout, (gchar *) data, size);
+  gst_text_render_render_text (render);
+
+  caps = gst_caps_new_simple ("video/x-raw-yuv", "format", GST_TYPE_FOURCC,
+      GST_MAKE_FOURCC ('A', 'Y', 'U', 'V'), "width", G_TYPE_INT,
+      render->bitmap.width, "height", G_TYPE_INT, render->bitmap.rows,
+      "framerate", GST_TYPE_FRACTION, 1, 1, NULL);
+
+  if (!gst_pad_set_caps (render->srcpad, caps)) {
+    gst_caps_unref (caps);
+    GST_ELEMENT_ERROR (render, CORE, NEGOTIATION, (NULL), (NULL));
+    ret = GST_FLOW_ERROR;
+    goto done;
+  }
+
+  GST_DEBUG ("Allocating AYUV buffer WxH = %dx%d", render->width,
+      render->height);
+  ret =
+      gst_pad_alloc_buffer_and_set_caps (render->srcpad, GST_BUFFER_OFFSET_NONE,
+      render->width * render->height * 4, caps, &outbuf);
+
+  if (ret != GST_FLOW_OK)
+    goto done;
+
+  gst_buffer_copy_metadata (outbuf, inbuf, GST_BUFFER_COPY_TIMESTAMPS);
+  data = GST_BUFFER_DATA (outbuf);
+
+  for (n = 0; n < render->width * render->height; n++) {
+    data[n * 4] = 0;
+    data[n * 4 + 1] = 0;
+    data[n * 4 + 2] = data[n * 4 + 3] = 128;
+  }
+
+  if (render->bitmap.buffer) {
+    gst_text_renderer_bitmap_to_ayuv (render, &render->bitmap, data);
+  }
+
+  ret = gst_pad_push (render->srcpad, outbuf);
+
+done:
+  if (caps)
+    gst_caps_unref (caps);
+  gst_buffer_unref (inbuf);
+  gst_object_unref (render);
+  return ret;
+}
+
+static void
+gst_text_render_finalize (GObject * object)
+{
+  GstTextRender *render = GST_TEXT_RENDER (object);
+
+  g_free (render->bitmap.buffer);
+
+  if (render->layout)
+    g_object_unref (render->layout);
+
+  G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gst_text_render_init (GstTextRender * render, GstTextRenderClass * klass)
+{
+  GstPadTemplate *template;
+
+  /* sink */
+  template = gst_static_pad_template_get (&sink_template_factory);
+  render->sinkpad = gst_pad_new_from_template (template, "sink");
+  gst_object_unref (template);
+  gst_pad_set_chain_function (render->sinkpad,
+      GST_DEBUG_FUNCPTR (gst_text_render_chain));
+  gst_element_add_pad (GST_ELEMENT (render), render->sinkpad);
+
+  /* source */
+  template = gst_static_pad_template_get (&src_template_factory);
+  render->srcpad = gst_pad_new_from_template (template, "src");
+  gst_object_unref (template);
+  gst_pad_set_fixatecaps_function (render->srcpad,
+      GST_DEBUG_FUNCPTR (gst_text_render_fixate_caps));
+  gst_pad_set_setcaps_function (render->srcpad,
+      GST_DEBUG_FUNCPTR (gst_text_render_setcaps));
+  gst_element_add_pad (GST_ELEMENT (render), render->srcpad);
+
+  render->layout =
+      pango_layout_new (GST_TEXT_RENDER_GET_CLASS (render)->pango_context);
+  memset (&render->bitmap, 0, sizeof (render->bitmap));
+}
+
+static void
+gst_text_render_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec)
+{
+  GstTextRender *render = GST_TEXT_RENDER (object);
+
+  switch (prop_id) {
+    case ARG_FONT_DESC:
+    {
+      PangoFontDescription *desc;
+
+      desc = pango_font_description_from_string (g_value_get_string (value));
+      if (desc) {
+        GST_LOG ("font description set: %s", g_value_get_string (value));
+        GST_OBJECT_LOCK (render);
+        pango_layout_set_font_description (render->layout, desc);
+        pango_font_description_free (desc);
+        gst_text_render_render_text (render);
+        GST_OBJECT_UNLOCK (render);
+      } else {
+        GST_WARNING ("font description parse failed: %s",
+            g_value_get_string (value));
+      }
+      break;
+    }
+
+    default:
+      break;
+  }
+}