gaudieffects: Add Gaussian Blur effect, and brief example.
authorJan Schmidt <thaytan@noraisin.net>
Mon, 2 Aug 2010 10:41:12 +0000 (20:41 +1000)
committerJan Schmidt <thaytan@noraisin.net>
Mon, 2 Aug 2010 10:46:48 +0000 (20:46 +1000)
docs/plugins/inspect/plugin-gaudieffects.xml
gst/gaudieffects/Makefile.am
gst/gaudieffects/blur-example.py [new file with mode: 0644]
gst/gaudieffects/gstgaussblur.c [new file with mode: 0644]
gst/gaudieffects/gstgaussblur.h [new file with mode: 0644]
gst/gaudieffects/gstplugin.c
gst/gaudieffects/gstplugin.h

index 9ec6ad3f4975162982a73814c09893fc41eea3ff..c22cf6b77da8cf7a3b90285b44d7c3e44c704e42 100644 (file)
         </caps>
       </pads>
     </element>
+    <element>
+      <name>gaussianblur</name>
+      <longname>GaussBlur</longname>
+      <class>Filter/Effect/Video</class>
+      <description>Perform Gaussian blur/sharpen on a video</description>
+      <author>Jan Schmidt &lt;thaytan@noraisin.net&gt;</author>
+      <pads>
+        <caps>
+          <name>sink</name>
+          <direction>sink</direction>
+          <presence>always</presence>
+          <details>video/x-raw-yuv, format=(fourcc)AYUV, width=(int)[ 1, 2147483647 ], height=(int)[ 1, 2147483647 ], framerate=(fraction)[ 0/1, 2147483647/1 ]</details>
+        </caps>
+        <caps>
+          <name>src</name>
+          <direction>source</direction>
+          <presence>always</presence>
+          <details>video/x-raw-yuv, format=(fourcc)AYUV, width=(int)[ 1, 2147483647 ], height=(int)[ 1, 2147483647 ], framerate=(fraction)[ 0/1, 2147483647/1 ]</details>
+        </caps>
+      </pads>
+    </element>
     <element>
       <name>solarize</name>
       <longname>Solarize</longname>
index 41b005ccdd4b52bd59d647d03b3e0e1473ff4584..a050fbdaf314fe73f728570ecb7ab21c0b2ef49e 100644 (file)
@@ -1,13 +1,13 @@
 plugin_LTLIBRARIES = libgstgaudieffects.la
 
 libgstgaudieffects_la_SOURCES = gstburn.c gstchromium.c gstdilate.c \
-        gstdodge.c gstexclusion.c gstsolarize.c gstplugin.c
-libgstgaudieffects_la_CFLAGS = $(GST_CFLAGS) $(GST_PLUGINS_BASE_CFLAGS)
-libgstgaudieffects_la_LIBADD = $(GST_LIBS) -lgstvideo-@GST_MAJORMINOR@
+        gstdodge.c gstexclusion.c gstgaussblur.c gstsolarize.c gstplugin.c
+libgstgaudieffects_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_CFLAGS)
+libgstgaudieffects_la_LIBADD = $(GST_PLUGINS_BASE_LIBS) -lgstvideo-@GST_MAJORMINOR@ $(GST_CONTROLLER_LIBS) $(GST_LIBS) $(LIBM)
 libgstgaudieffects_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
 libgstgaudieffects_la_LIBTOOLFLAGS = --tag=disable-static
 
 # headers we need but don't want installed
 noinst_HEADERS = \
         gstburn.h gstchromium.h gstdilate.h gstdodge.h \
-        gstexclusion.h gstplugin.h gstsolarize.h
+        gstexclusion.h gstgaussblur.h gstplugin.h gstsolarize.h
diff --git a/gst/gaudieffects/blur-example.py b/gst/gaudieffects/blur-example.py
new file mode 100644 (file)
index 0000000..63920ec
--- /dev/null
@@ -0,0 +1,23 @@
+#!/usr/bin/python
+import gobject; gobject.threads_init()
+import pygst; pygst.require("0.10")
+import gst
+
+p = gst.parse_launch ("""
+   v4l2src !
+   ffmpegcolorspace ! queue ! video/x-raw-yuv,width=320,height=240,framerate=30/1 ! gaussianblur qos=true name=vf ! ffmpegcolorspace !
+   timeoverlay ! xvimagesink
+   """)
+
+m = p.get_by_name ("vf")
+m.set_property ("sigma", 0.5)
+
+control = gst.Controller(m, "sigma")
+control.set_interpolation_mode("sigma", gst.INTERPOLATE_LINEAR)
+control.set("sigma", 0 * gst.SECOND, 0.5)
+control.set("sigma", 5 * gst.SECOND, 10.0)
+control.set("sigma", 25 * gst.SECOND, -5.0)
+
+p.set_state (gst.STATE_PLAYING)
+
+gobject.MainLoop().run()
diff --git a/gst/gaudieffects/gstgaussblur.c b/gst/gaudieffects/gstgaussblur.c
new file mode 100644 (file)
index 0000000..6691c4c
--- /dev/null
@@ -0,0 +1,411 @@
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif
+
+#include <math.h>
+#include <gst/gst.h>
+#include <gst/controller/gstcontroller.h>
+
+#include "gstplugin.h"
+#include "gstgaussblur.h"
+
+static gboolean gauss_blur_stop (GstBaseTransform * btrans);
+static gboolean gauss_blur_set_caps (GstBaseTransform * btrans,
+    GstCaps * incaps, GstCaps * outcaps);
+static GstFlowReturn gauss_blur_process_frame (GstBaseTransform * btrans,
+    GstBuffer * in_buf, GstBuffer * out_buf);
+
+static void gauss_blur_set_property (GObject * object,
+    guint prop_id, const GValue * value, GParamSpec * pspec);
+static void gauss_blur_get_property (GObject * object,
+    guint prop_id, GValue * value, GParamSpec * pspec);
+
+GST_DEBUG_CATEGORY_STATIC (gst_gauss_blur_debug);
+#define GST_CAT_DEFAULT gst_gauss_blur_debug
+
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+#define CAPS_STR_RGB GST_VIDEO_CAPS_BGRx ";" GST_VIDEO_CAPS_RGBx
+#else
+#define CAPS_STR_RGB GST_VIDEO_CAPS_xRGB ";" GST_VIDEO_CAPS_xBGR
+#endif
+
+#define CAPS_STR GST_VIDEO_CAPS_YUV("AYUV")
+
+/* The capabilities of the inputs and outputs. */
+static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink",
+    GST_PAD_SINK,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS (CAPS_STR)
+    );
+
+static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
+    GST_PAD_SRC,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS (CAPS_STR)
+    );
+
+enum
+{
+  PROP_0,
+  PROP_SIGMA,
+  PROP_LAST
+};
+
+static void cleanup (GaussBlur * gb);
+static gboolean make_gaussian_kernel (GaussBlur * gb, float sigma);
+static void gaussian_smooth (GaussBlur * gb, guint8 * image,
+    guint8 * out_image);
+
+GST_BOILERPLATE (GaussBlur, gauss_blur, GstVideoFilter, GST_TYPE_VIDEO_FILTER);
+
+#define DEFAULT_SIGMA 1.2
+
+static void
+gauss_blur_base_init (gpointer gclass)
+{
+  GstElementClass *element_class = GST_ELEMENT_CLASS (gclass);
+
+  gst_element_class_set_details_simple (element_class,
+      "GaussBlur",
+      "Filter/Effect/Video",
+      "Perform Gaussian blur/sharpen on a video",
+      "Jan Schmidt <thaytan@noraisin.net>");
+
+  gst_element_class_add_pad_template (element_class,
+      gst_static_pad_template_get (&src_factory));
+  gst_element_class_add_pad_template (element_class,
+      gst_static_pad_template_get (&sink_factory));
+}
+
+static void
+gauss_blur_class_init (GaussBlurClass * klass)
+{
+  GObjectClass *object_class = (GObjectClass *) klass;
+  GstBaseTransformClass *trans_class = (GstBaseTransformClass *) klass;
+
+  object_class->set_property = gauss_blur_set_property;
+  object_class->get_property = gauss_blur_get_property;
+
+  trans_class->stop = gauss_blur_stop;
+  trans_class->set_caps = gauss_blur_set_caps;
+  trans_class->transform = gauss_blur_process_frame;
+
+  g_object_class_install_property (object_class, PROP_SIGMA,
+      g_param_spec_double ("sigma", "Sigma",
+          "Sigma value for gaussian blur (negative for sharpen)",
+          -20.0, 20.0, DEFAULT_SIGMA,
+          G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
+}
+
+static void
+gauss_blur_init (GaussBlur * gb, GaussBlurClass * gclass)
+{
+  gb->sigma = DEFAULT_SIGMA;
+  gb->cur_sigma = -1.0;
+}
+
+static void
+cleanup (GaussBlur * gb)
+{
+  g_free (gb->tempim);
+  gb->tempim = NULL;
+
+  g_free (gb->smoothedim);
+  gb->smoothedim = NULL;
+
+  g_free (gb->kernel);
+  gb->kernel = NULL;
+  g_free (gb->kernel_sum);
+  gb->kernel_sum = NULL;
+}
+
+static gboolean
+gauss_blur_stop (GstBaseTransform * btrans)
+{
+  GaussBlur *gb = GAUSS_BLUR (btrans);
+
+  cleanup (gb);
+
+  return TRUE;
+}
+
+static gboolean
+gauss_blur_set_caps (GstBaseTransform * btrans,
+    GstCaps * incaps, GstCaps * outcaps)
+{
+  GaussBlur *gb = GAUSS_BLUR (btrans);
+  GstStructure *structure;
+  GstVideoFormat format;
+  guint32 n_elems;
+
+  structure = gst_caps_get_structure (incaps, 0);
+  g_return_val_if_fail (structure != NULL, FALSE);
+
+  if (!gst_video_format_parse_caps (incaps, &format, &gb->width, &gb->height))
+    return FALSE;
+
+  /* get stride */
+  gb->stride = gst_video_format_get_row_stride (format, 0, gb->width);
+
+  n_elems = gb->stride * gb->height;
+
+  gb->tempim = g_malloc (sizeof (gfloat) * n_elems);
+  //gb->smoothedim = g_malloc (sizeof (guint16) * n_elems);
+
+  return TRUE;
+}
+
+static GstFlowReturn
+gauss_blur_process_frame (GstBaseTransform * btrans,
+    GstBuffer * in_buf, GstBuffer * out_buf)
+{
+  GaussBlur *gb = GAUSS_BLUR (btrans);
+  GstClockTime timestamp;
+  gint64 stream_time;
+  gfloat sigma;
+
+  /* GstController: update the properties */
+  timestamp = GST_BUFFER_TIMESTAMP (in_buf);
+  stream_time =
+      gst_segment_to_stream_time (&btrans->segment, GST_FORMAT_TIME, timestamp);
+  if (GST_CLOCK_TIME_IS_VALID (stream_time))
+    gst_object_sync_values (G_OBJECT (gb), stream_time);
+
+  GST_OBJECT_LOCK (gb);
+  sigma = gb->sigma;
+  GST_OBJECT_UNLOCK (gb);
+
+  if (gb->cur_sigma != sigma) {
+    g_free (gb->kernel);
+    gb->kernel = NULL;
+    g_free (gb->kernel_sum);
+    gb->kernel_sum = NULL;
+    gb->cur_sigma = sigma;
+  }
+  if (gb->kernel == NULL && !make_gaussian_kernel (gb, gb->cur_sigma)) {
+    GST_ELEMENT_ERROR (btrans, RESOURCE, NO_SPACE_LEFT, ("Out of memory"),
+        ("Failed to allocation gaussian kernel"));
+    return GST_FLOW_ERROR;
+  }
+
+  /*
+   * Perform gaussian smoothing on the image using the input standard
+   * deviation.
+   */
+  memcpy (GST_BUFFER_DATA (out_buf), GST_BUFFER_DATA (in_buf),
+      gb->height * gb->stride);
+  gaussian_smooth (gb, GST_BUFFER_DATA (in_buf), GST_BUFFER_DATA (out_buf));
+
+  return GST_FLOW_OK;
+}
+
+static void
+blur_row_x (GaussBlur * gb, guint8 * in_row, gfloat * out_row)
+{
+  int c, cc, center;
+  float dot[4], sum;
+  int k, kmin, kmax;
+
+  center = gb->windowsize / 2;
+
+  for (c = 0; c < gb->width; c++) {
+    /* Calculate min */
+    cc = center - c;
+    kmin = MAX (0, cc);
+    cc = kmin - cc;
+    /* Calc max */
+    kmax = MIN (gb->windowsize, gb->width - cc);
+    cc *= 4;
+
+    dot[0] = dot[1] = dot[2] = dot[3] = 0.0;
+    /* Calculate sum for range */
+    sum = gb->kernel_sum[kmax - 1];
+    sum -= kmin ? gb->kernel_sum[kmin - 1] : 0.0;
+
+    for (k = kmin; k < kmax; k++) {
+      float coeff = gb->kernel[k];
+      dot[0] += (float) in_row[cc++] * coeff;
+      dot[1] += (float) in_row[cc++] * coeff;
+      dot[2] += (float) in_row[cc++] * coeff;
+      dot[3] += (float) in_row[cc++] * coeff;
+    }
+
+    out_row[c * 4] = dot[0] / sum;
+    out_row[c * 4 + 1] = dot[1] / sum;
+    out_row[c * 4 + 2] = dot[2] / sum;
+    out_row[c * 4 + 3] = dot[3] / sum;
+  }
+}
+
+static void
+gaussian_smooth (GaussBlur * gb, guint8 * image, guint8 * out_image)
+{
+  int r, c, rr, center;
+  float dot[4], sum;
+  int k, kmin, kmax;
+  guint8 *in_row = image;
+  float *tmp_out_row = gb->tempim;
+  float *tmp_in_pos;
+  gint y_avail = 0;
+  guint8 *out_row;
+
+  /* Apply the gaussian kernel */
+  center = gb->windowsize / 2;
+
+  /* Blur in the y - direction. */
+  for (r = 0; r < gb->height; r++) {
+    /* Calculate input row range */
+    rr = center - r;
+    kmin = MAX (0, rr);
+    rr = kmin - rr;
+    /* Calc max */
+    kmax = MIN (gb->windowsize, gb->height - rr);
+
+    /* Precalculate sum for range */
+    sum = gb->kernel_sum[kmax - 1];
+    sum -= kmin ? gb->kernel_sum[kmin - 1] : 0.0;
+
+    /* Blur more input rows (x direction blur) */
+    while (y_avail <= (r + center) && y_avail < gb->height) {
+      blur_row_x (gb, in_row, tmp_out_row);
+      in_row += gb->stride;
+      tmp_out_row += gb->stride;
+      y_avail++;
+    }
+
+    tmp_in_pos = gb->tempim + (rr * gb->stride);
+    out_row = out_image + r * gb->stride;
+
+    for (c = 0; c < gb->width; c++) {
+      float *tmp = tmp_in_pos;
+
+      dot[0] = dot[1] = dot[2] = dot[3] = 0.0;
+      for (k = kmin; k < kmax; k++, tmp += gb->stride) {
+        float kern = gb->kernel[k];
+        dot[0] += tmp[0] * kern;
+        dot[1] += tmp[1] * kern;
+        dot[2] += tmp[2] * kern;
+        dot[3] += tmp[3] * kern;
+      }
+
+      *out_row++ = (guint8) CLAMP ((dot[0] / sum + 0.5), 0, 255);
+      *out_row++ = (guint8) CLAMP ((dot[1] / sum + 0.5), 0, 255);
+      *out_row++ = (guint8) CLAMP ((dot[2] / sum + 0.5), 0, 255);
+      *out_row++ = (guint8) CLAMP ((dot[3] / sum + 0.5), 0, 255);
+
+      tmp_in_pos += 4;
+    }
+  }
+}
+
+/*
+ * Create a one dimensional gaussian kernel.
+ */
+static gboolean
+make_gaussian_kernel (GaussBlur * gb, float sigma)
+{
+  int i, center, left, right;
+  float sum, sum2;
+  const float fe = -0.5 / (sigma * sigma);
+  const float dx = 1.0 / (sigma * sqrt (2 * M_PI));
+
+  center = ceil (2.5 * fabs (sigma));
+  gb->windowsize = (int) (1 + 2 * center);
+
+  gb->kernel = g_new (float, gb->windowsize);
+  gb->kernel_sum = g_new (float, gb->windowsize);
+  if (gb->kernel == NULL || gb->kernel_sum == NULL)
+    return FALSE;
+
+  if (gb->windowsize == 1) {
+    gb->kernel[0] = 1.0;
+    gb->kernel_sum[0] = 1.0;
+    return TRUE;
+  }
+
+  /* Center co-efficient */
+  sum = gb->kernel[center] = dx;
+
+  /* Other coefficients */
+  left = center - 1;
+  right = center + 1;
+  for (i = 1; i <= center; i++, left--, right++) {
+    float fx = dx * pow (M_E, fe * i * i);
+    gb->kernel[right] = gb->kernel[left] = fx;
+    sum += 2 * fx;
+  }
+
+  if (sigma < 0) {
+    sum = -sum;
+    gb->kernel[center] += 2.0 * sum;
+  }
+
+  for (i = 0; i < gb->windowsize; i++)
+    gb->kernel[i] /= sum;
+
+  sum2 = 0.0;
+  for (i = 0; i < gb->windowsize; i++) {
+    sum2 += gb->kernel[i];
+    gb->kernel_sum[i] = sum2;
+  }
+
+#if 0
+  g_print ("Sigma %f: ", sigma);
+  for (i = 0; i < gb->windowsize; i++)
+    g_print ("%f ", gb->kernel[i]);
+  g_print ("\n");
+  g_print ("sums: ");
+  for (i = 0; i < gb->windowsize; i++)
+    g_print ("%f ", gb->kernel_sum[i]);
+  g_print ("\n");
+  g_print ("sum %f sum2 %f\n", sum, sum2);
+#endif
+
+  return TRUE;
+}
+
+static void
+gauss_blur_set_property (GObject * object,
+    guint prop_id, const GValue * value, GParamSpec * pspec)
+{
+  GaussBlur *gb = GAUSS_BLUR (object);
+  switch (prop_id) {
+    case PROP_SIGMA:
+      GST_OBJECT_LOCK (object);
+      gb->sigma = g_value_get_double (value);
+      GST_OBJECT_UNLOCK (object);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static void
+gauss_blur_get_property (GObject * object,
+    guint prop_id, GValue * value, GParamSpec * pspec)
+{
+  GaussBlur *gb = GAUSS_BLUR (object);
+  switch (prop_id) {
+    case PROP_SIGMA:
+      GST_OBJECT_LOCK (gb);
+      g_value_set_double (value, gb->sigma);
+      GST_OBJECT_UNLOCK (gb);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+/* Register the element factories and other features. */
+gboolean
+gst_gauss_blur_plugin_init (GstPlugin * plugin)
+{
+  /* debug category for fltering log messages */
+  GST_DEBUG_CATEGORY_INIT (gst_gauss_blur_debug, "gaussianblur",
+      0, "Gaussian Blur video effect");
+
+  return gst_element_register (plugin, "gaussianblur", GST_RANK_NONE,
+      GST_TYPE_GAUSS_BLUR);
+}
diff --git a/gst/gaudieffects/gstgaussblur.h b/gst/gaudieffects/gstgaussblur.h
new file mode 100644 (file)
index 0000000..e582d18
--- /dev/null
@@ -0,0 +1,40 @@
+#ifndef __GST_GAUSS_BLUR_H__
+#define __GST_GAUSS_BLUR_H__
+
+#include <gst/gst.h>
+#include <gst/video/video.h>
+#include <gst/video/gstvideofilter.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_GAUSS_BLUR (gauss_blur_get_type())
+#define GAUSS_BLUR(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_GAUSS_BLUR, GaussBlur))
+
+typedef struct GaussBlur GaussBlur;
+typedef struct GaussBlurClass GaussBlurClass;
+
+struct GaussBlur
+{
+  GstVideoFilter videofilter;
+  gint width, height, stride;
+
+  float cur_sigma, sigma;
+  int windowsize;
+
+  float *kernel;
+  float *kernel_sum;
+  float *tempim;
+  gint16 *smoothedim;
+};
+
+struct GaussBlurClass
+{
+  GstVideoFilterClass parent_class;
+};
+
+GType gauss_blur_get_type(void);
+
+G_END_DECLS
+
+#endif
index d8cb57137c429385bc879b58a8b0615ddfaa982d..536bcb7e0818fe34059faef59a1db411132c01a4 100644 (file)
@@ -71,6 +71,7 @@ plugin_init (GstPlugin * plugin)
   ret &= gst_dodge_plugin_init (plugin);
   ret &= gst_exclusion_plugin_init (plugin);
   ret &= gst_solarize_plugin_init (plugin);
+  ret &= gst_gauss_blur_plugin_init (plugin);
 
   return ret;
 }
index 4794a39757abf0b81718ed4726e6bd64364f569d..dcfacbd7d963509b9a4b9ffe1f1c60815210a585 100644 (file)
 
 G_BEGIN_DECLS
 
-gboolean gst_burn_plugin_init (GstPlugin * chromium);
-gboolean gst_chromium_plugin_init (GstPlugin * chromium);
-gboolean gst_dilate_plugin_init (GstPlugin * chromium);
-gboolean gst_dodge_plugin_init (GstPlugin * chromium);
-gboolean gst_exclusion_plugin_init (GstPlugin * chromium);
-gboolean gst_solarize_plugin_init (GstPlugin * chromium);
+gboolean gst_burn_plugin_init (GstPlugin *plugin);
+gboolean gst_chromium_plugin_init (GstPlugin *plugin);
+gboolean gst_dilate_plugin_init (GstPlugin *plugin);
+gboolean gst_dodge_plugin_init (GstPlugin *plugin);
+gboolean gst_exclusion_plugin_init (GstPlugin *plugin);
+gboolean gst_gauss_blur_plugin_init (GstPlugin *plugin);
+gboolean gst_solarize_plugin_init (GstPlugin *plugin);
 
 G_END_DECLS