[gegl/bug-697876: 1/4] Add edge-neon GEGL Port



commit 2844f005f2205c5817d279f02ba58049216b0c10
Author: Peter O'Regan <peteroregan gmail com>
Date:   Sat Jan 21 20:40:08 2017 -0500

    Add edge-neon GEGL Port
    
        Ports edge-neon to GEGL. Current implementation is not the most
        efficient, but it matches the output from the GIMP Filter menu, provided
        that the Gamma-Hack is enabled. Optimizations to follow.
    
        Current methodology is slow and will need to be adjusted. Excess calls are
        made and the Gaussian separability is not yet enabled.
    
        https://bugzilla.gnome.org/show_bug.cgi?id=697876

 operations/common/Makefile.am |    6 +-
 operations/common/edge-neon.c |  580 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 583 insertions(+), 3 deletions(-)
---
diff --git a/operations/common/Makefile.am b/operations/common/Makefile.am
index bb9fd9e..6e88e2e 100644
--- a/operations/common/Makefile.am
+++ b/operations/common/Makefile.am
@@ -8,7 +8,6 @@ BUILT_SOURCES = $(subst .c,.c.h,$(wildcard $(srcdir)/*.c))
 
 AM_CPPFLAGS += -I$(srcdir)
 AM_CFLAGS +=  -DGEGL_OP_BUNDLE
-
 LIBS = $(op_libs)
 
 
@@ -38,7 +37,7 @@ gegl_common_la_SOURCES =\
        color-rotate.c \
        color-temperature.c \
        color-to-alpha.c \
-    color-warp.c \
+        color-warp.c \
        color.c \
        contrast-curve.c \
        convolution-matrix.c \
@@ -53,6 +52,7 @@ gegl_common_la_SOURCES =\
        dropshadow.c \
        edge.c \
        edge-laplace.c \
+       edge-neon.c \
        edge-sobel.c \
        emboss.c \
        engrave.c \
@@ -121,7 +121,7 @@ gegl_common_la_SOURCES =\
        red-eye-removal.c \
        reinhard05.c \
        remap.c \
-    rgb-clip.c \
+        rgb-clip.c \
        ripple.c \
        saturation.c \
        save.c \
diff --git a/operations/common/edge-neon.c b/operations/common/edge-neon.c
new file mode 100644
index 0000000..37abbaf
--- /dev/null
+++ b/operations/common/edge-neon.c
@@ -0,0 +1,580 @@
+/* This file is an image processing operation for GEGL
+ * GEGL is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * GEGL 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GEGL; if not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Neon filter for GIMP for BIPS
+ * Original by Spencer Kimball
+ * Port to GEGL Copyright 2017 Peter O'Regan <peteroregan gmail com>
+ *
+ * This filter works in a manner similar to the "edge"
+ *  plug-in, but uses the first derivative of the gaussian
+ *  operator to achieve resolution independence. The IIR
+ *  method of calculating the effect is utilized to keep
+ *  the processing time constant between large and small
+ *  standard deviations.
+ *
+ *
+ *  A description of the algorithm may be found in Deriche (1993).
+ *  In short, it is a fourth order recursive filter with causal and
+ *  anti-causal parts which are computed separately and summed. The
+ *  Gaussian is approximated using a closely-matched exponential
+ *  whose coefficients in IIR form have been optimized to minimize
+ *  the error against the Gaussian over a large range of std devs.
+ *
+ *  Deriche, R. "Recursively Implementing the Gaussian and its Derivatives," in
+ *    Proc. Rapports de Recherche, No. 1893. April 1993.
+ */
+
+
+#include "config.h"
+#include <glib/gi18n-lib.h>
+#include <math.h>
+#include <stdio.h>
+
+#ifdef GEGL_PROPERTIES
+
+property_double (radius, _("Radius"), 5.0)
+   description (_("Radius of effect (in pixels)"))
+   value_range (1, 1500.0)
+   ui_gamma    (2.0)
+   ui_range    (1, 50.0)
+   ui_meta     ("unit", "pixel-distance")
+
+
+property_double (amount, _("Intensity"), 0.0)
+   description (_("Strength of Effect"))
+   value_range (0.0, 100.0)
+   ui_range    (0.0, 3.0)
+
+#else
+
+#define GEGL_OP_FILTER
+#define GEGL_OP_C_SOURCE edge-neon.c
+
+#include "gegl-op.h"
+
+#define GEGL_SCALE_NONE 1.0
+#define NEON_MAXRGB 1.0
+#define NEON_MINRGB 0.0
+
+
+// Forward declarations
+static void      find_constants      (gdouble           n_p[],
+                                      gdouble           n_m[],
+                                      gdouble           d_p[],
+                                      gdouble           d_m[],
+                                      gdouble           bd_p[],
+                                      gdouble           bd_m[],
+                                      gdouble           std_dev);
+
+static void      transfer_pixels     (gdouble          *src1,
+                                      gdouble          *src2,
+                                      gdouble           *dest,
+                                      gint              bytes,
+                                      gint              width);
+
+static void      combine_to_gradient (gdouble           *dest,
+                                      gdouble           *src2,
+                                      gint              bytes,
+                                      gint              width,
+                                      gdouble           amount);
+
+
+
+static void neon_prepare (GeglOperation *operation)
+{
+  const Babl *src_format = gegl_operation_get_source_format (operation, "input");
+  gegl_operation_set_format (operation, "input", babl_format ("RGBA double"));
+  gegl_operation_set_format (operation, "output", babl_format("RGBA double"));
+}
+
+
+static gboolean
+neon_process (GeglOperation     *op,
+         GeglBuffer             *in_buf,
+         GeglBuffer             *out_buf,
+         const GeglRectangle    *roi,
+         gint                   level)
+{
+    // Used later for traversal
+    GeglRectangle cur_roi;
+    gint          x0, y0;
+    gint          width, height;
+    gint          chpp, bpp, aidx;     //Number of channels & bytes per pixel
+
+    //Pointers to buffer locations & iterators
+    gdouble      *val_p, *val_m, *vp, *vm;
+    gint          i, j, b;
+    gint          row, col, bufsz;
+    gint          terms; // # active terms during filter (active along edges)
+    gdouble       vanish_pt;
+
+    /* Retrieve a pointer to GeglProperties via Chant */
+    const Babl *rgba64 = babl_format("RGBA double");
+    GeglProperties *o = GEGL_PROPERTIES (op);
+    gdouble      radius, amount;
+    gdouble      std_dev;
+
+    //Linear buffers
+    gdouble      *dest;
+    gdouble      *src, *vert, *sp_p, *sp_m;
+
+    //Coefficient holders
+    gdouble       n_p[5], n_m[5];
+    gdouble       d_p[5], d_m[5];
+    gdouble       bd_p[5], bd_m[5];
+    gdouble       initial_p[4];
+    gdouble       initial_m[4];
+
+
+    radius = o->radius;
+    amount = o->amount;
+
+    width = roi->width;
+    height = roi->height;
+    x0 = roi->x;
+    y0 = roi->y;
+    chpp = 4; //RGBA
+    aidx = 3; //Alpha index
+    bpp = babl_format_get_bytes_per_pixel(rgba64);
+
+    radius = radius + 1.0;
+    vanish_pt = 1.0/255.0; //8-bit holdover, for now. (TODO)
+    std_dev = sqrt(-(radius * radius) / (2 * log(vanish_pt)));
+
+    find_constants (n_p, n_m, d_p, d_m, bd_p, bd_m, std_dev);
+
+    bufsz = bpp* MAX (width,height); //RGBA
+
+    //Buffers for causal and anti-causal parts
+    val_p = g_new (gdouble, bufsz);
+    val_m = g_new (gdouble, bufsz);
+
+    src  = g_new (gdouble, bufsz);
+    vert = g_new (gdouble, bufsz);
+    dest = g_new (gdouble, bufsz);
+
+    //Vertical pass of the 1D IIR
+    for (col=0; col<width; col++)
+    {
+      memset (val_p, 0.0, height * bpp);
+      memset (val_m, 0.0, height * bpp);
+
+      //Acquire an entire column
+      gegl_rectangle_set (&cur_roi,x0+col,y0,1,height);
+      gegl_buffer_get (in_buf, &cur_roi, GEGL_SCALE_NONE,rgba64,src,GEGL_AUTO_ROWSTRIDE,GEGL_ABYSS_NONE);
+
+      sp_p = src;
+      sp_m = src + (height - 1) * chpp;
+      vp = val_p;
+      vm = val_m + (height - 1) * chpp;
+
+      /*  Set up the first vals  */
+      for (i = 0; i < chpp; i++)
+      {
+          initial_p[i] = sp_p[i];
+          initial_m[i] = sp_m[i];
+      }
+      for (row = 0; row < height; row++)
+      {
+        gdouble *vpptr, *vmptr;
+        terms = (row < 4) ? row : 4; //Magic constant 4 due to 4th order filter
+        for (b = 0; b < 3; b++)      //RGB channels. Skip Alpha.
+        {
+          vpptr = vp + b; vmptr = vm + b;
+
+          for (i = 0; i <= terms; i++)
+          {
+            *vpptr += n_p[i] * sp_p[(-i * chpp) + b] -
+                      d_p[i] * vp[(-i * chpp) + b];
+            *vmptr += n_m[i] * sp_m[(i * chpp) + b] -
+                      d_m[i] * vm[(i * chpp) + b];
+          }
+          for (j = i; j <= 4; j++)
+              {
+                *vpptr += (n_p[j] - bd_p[j]) * initial_p[b];
+                *vmptr += (n_m[j] - bd_m[j]) * initial_m[b];
+              }
+        }
+        //Copy Alpha Straight
+        vp[aidx] = sp_p[aidx];
+        vm[aidx] = sp_m[aidx];
+
+
+        sp_p += chpp;
+        sp_m -= chpp;
+        vp   += chpp;
+        vm   -= chpp;
+        }
+
+      transfer_pixels (val_p, val_m, dest, chpp, height);
+      gegl_buffer_set (out_buf, &cur_roi, 0,rgba64,dest,GEGL_AUTO_ROWSTRIDE);
+
+    }
+
+    for (row=0; row<height; row++)
+    {
+        memset (val_p, 0.0, width * bpp);
+        memset (val_m, 0.0, width * bpp);
+        //Acquire an entire row
+        gegl_rectangle_set (&cur_roi, x0, y0+row, width, 1);
+        gegl_buffer_get (in_buf, &cur_roi, GEGL_SCALE_NONE, rgba64, src, GEGL_AUTO_ROWSTRIDE, 
GEGL_ABYSS_NONE);
+        gegl_buffer_get (out_buf, &cur_roi, GEGL_SCALE_NONE, rgba64, vert, GEGL_AUTO_ROWSTRIDE, 
GEGL_ABYSS_NONE);
+
+        sp_p = src;
+        sp_m = src + (width - 1) * chpp;
+        vp = val_p;
+        vm = val_m + (width - 1) * chpp;
+
+        /*  Set up the first vals  */
+        for (i = 0; i < chpp; i++)
+        {
+          initial_p[i] = sp_p[i];
+          initial_m[i] = sp_m[i];
+        }
+
+        for (col = 0; col < width; col++)
+        {
+          gdouble *vpptr, *vmptr;
+
+          terms = (col < 4) ? col : 4;
+
+          for (b = 0; b < 3; b++) //RGB|A
+          {
+            vpptr = vp + b; vmptr = vm + b;
+
+            for (i = 0; i <= terms; i++)
+            {
+              *vpptr += n_p[i] * sp_p[(-i * chpp) + b] -
+                        d_p[i] * vp[(-i * chpp) + b];
+              *vmptr += n_m[i] * sp_m[(i * chpp) + b] -
+                        d_m[i] * vm[(i * chpp) + b];
+            }
+
+            for (j = i; j <= 4; j++)
+            {
+              *vpptr += (n_p[j] - bd_p[j]) * initial_p[b];
+              *vmptr += (n_m[j] - bd_m[j]) * initial_m[b];
+            }
+          }
+          vp[aidx] = sp_p[aidx];
+          vm[aidx] = sp_m[aidx];
+
+          sp_p += chpp;
+          sp_m -= chpp;
+          vp   += chpp;
+          vm   -= chpp;
+        }
+
+        transfer_pixels (val_p, val_m, dest, chpp, width);
+        combine_to_gradient (dest, vert, chpp, width, amount);
+        gegl_buffer_set (out_buf, &cur_roi, 0, rgba64, dest, GEGL_AUTO_ROWSTRIDE);
+    }
+
+  /*  free up buffers  */
+  g_free (val_p);
+  g_free (val_m);
+  g_free (src);
+  g_free (vert);
+  g_free (dest);
+  return TRUE;
+}
+
+
+/*  This function combines the causal and anticausal segments
+    It also prevents each pixel from clippping.
+    TODO: Originally, the Alpha channel is included in all scaling operatings, and doubled on the transfer 
operation.
+    It appears that transparency is never preserved.
+    I am unsure if this is intended operation, and it should likely be fixed in the speed update.
+    */
+static void
+transfer_pixels (gdouble *src1,
+                 gdouble *src2,
+                 gdouble *dest,
+                 gint     chpp,
+                 gint     width)
+{
+  gint    b;
+  gint    bend = chpp * width;
+  gdouble sum;
+
+  for (b = 0; b < bend; b++)
+    {
+      sum = *src1++ + *src2++;
+
+      if (sum > NEON_MAXRGB)
+        sum = NEON_MAXRGB;
+      else if (sum < NEON_MINRGB)
+        sum = NEON_MINRGB;
+
+      *dest++ = (gdouble) sum;
+    }
+}
+
+
+static void
+combine_to_gradient (gdouble *dest,
+                     gdouble *src2,
+                     gint    bytes,
+                     gint    width,
+                     gdouble amount)
+{
+  gint    b;
+  gint    bend = bytes * width;
+  gdouble h, v;
+  gdouble sum;
+  //9.0 is a magic constant. I am not sure why it is 9.0.
+  gdouble scale = (1.0 + 9.0 * amount);
+
+  for (b = 0; b < bend; b++)
+    {
+
+      /* scale result */
+      h = *src2++;
+      v = *dest;
+
+      sum = sqrt (h*h + v*v) * scale;
+
+      if (sum > NEON_MAXRGB)
+        sum = NEON_MAXRGB;
+      else if (sum < NEON_MINRGB)
+        sum = NEON_MINRGB;
+
+      *dest++ = (gdouble) sum;
+    }
+}
+
+
+static void
+find_constants (gdouble n_p[],
+                gdouble n_m[],
+                gdouble d_p[],
+                gdouble d_m[],
+                gdouble bd_p[],
+                gdouble bd_m[],
+                gdouble std_dev)
+{
+  gdouble a0, a1, b0, b1, c0, c1, w0, w1;
+  gdouble w0n, w1n, cos0, cos1, sin0, sin1, b0n, b1n;
+
+  /* Coefficients which minimize difference between the
+     approximating function and the Gaussian derivative.
+     Calculated via an offline optimization process.
+     See Deriche (1993).
+  */
+
+  a0 =  0.6472;
+  a1 =  4.531;
+  b0 =  1.527;
+  b1 =  1.516;
+  c0 = -0.6494;
+  c1 = -0.9557;
+  w0 =  0.6719;
+  w1 =  2.072;
+
+  w0n  = w0 / std_dev;
+  w1n  = w1 / std_dev;
+  cos0 = cos (w0n);
+  cos1 = cos (w1n);
+  sin0 = sin (w0n);
+  sin1 = sin (w1n);
+  b0n  = b0 / std_dev;
+  b1n  = b1 / std_dev;
+
+  n_p[4] = 0.0;
+  n_p[3] = exp (-b1n - 2 * b0n) * (c1 * sin1 - cos1 * c0) + exp (-b0n - 2 * b1n) * (a1 * sin0 - cos0 * a0);
+  n_p[2] = 2 * exp (-b0n - b1n) * ((a0 + c0) * cos1 * cos0 - cos1 * a1 * sin0 - cos0 * c1 * sin1) + c0 * exp 
(-2 * b0n) + a0 * exp (-2 * b1n);
+  n_p[1] = exp (-b1n) * (c1 * sin1 - (c0 + 2 * a0) * cos1) + exp (-b0n) * (a1 * sin0 - (2 * c0 + a0) * cos0);
+  n_p[0] = a0 + c0;
+
+  d_p[4] = exp (-2 * b0n - 2 * b1n);
+  d_p[3] = -2 * cos0 * exp (-b0n - 2 * b1n) - 2 * cos1 * exp (-b1n - 2 * b0n);
+  d_p[2] = 4 * cos1 * cos0 * exp (-b0n - b1n) + exp (-2 * b1n) + exp (-2 * b0n);
+  d_p[1] = -2 * exp (-b1n) * cos1 - 2 * exp (-b0n) * cos0;
+  d_p[0] = 0.0;
+
+  n_m[4] = d_p[4] * n_p[0] - n_p[4];
+  n_m[3] = d_p[3] * n_p[0] - n_p[3];
+  n_m[2] = d_p[2] * n_p[0] - n_p[2];
+  n_m[1] = d_p[1] * n_p[0] - n_p[1];
+  n_m[0] = 0.0;
+
+  d_m[4] = d_p[4];
+  d_m[3] = d_p[3];
+  d_m[2] = d_p[2];
+  d_m[1] = d_p[1];
+  d_m[0] = d_p[0];
+
+  {
+  // Back-calculate values for edges
+    gint    i;
+    gdouble sum_n_p, sum_n_m, sum_d;
+    gdouble a, b;
+
+    sum_n_p = 0.0;
+    sum_n_m = 0.0;
+    sum_d   = 0.0;
+
+
+    for (i = 0; i <= 4; i++)
+      {
+        sum_n_p += n_p[i];
+        sum_n_m += n_m[i];
+        sum_d   += d_p[i];
+      }
+
+    a = sum_n_p / (1 + sum_d);
+    b = sum_n_m / (1 + sum_d);
+
+    for (i = 0; i <= 4; i++)
+      {
+        bd_p[i] = d_p[i] * a;
+        bd_m[i] = d_m[i] * b;
+      }
+  }
+}
+
+static GeglRectangle
+neon_get_bounding_box (GeglOperation *self)
+{
+  GeglRectangle  result = { 0, 0, 0, 0 };
+  GeglRectangle *in_rect;
+
+  in_rect = gegl_operation_source_get_bounding_box (self, "input");
+  if (in_rect)
+    {
+      result = *in_rect;
+    }
+  return result;
+}
+
+
+static GeglRectangle
+neon_get_required_for_output (GeglOperation        *operation,
+                         const gchar         *input_pad,
+                         const GeglRectangle *roi)
+{
+
+  GeglRectangle result = *gegl_operation_source_get_bounding_box (operation, "input");
+
+  /* Don't request an infinite plane */
+  if (gegl_rectangle_is_infinite_plane (&result))
+    return *roi;
+
+  return result;
+
+}
+
+
+GeglRectangle
+neon_get_cached_region (GeglOperation        *operation,
+                                  const GeglRectangle *roi)
+{
+  GeglRectangle result = *gegl_operation_source_get_bounding_box (operation, "input");
+
+  if (gegl_rectangle_is_infinite_plane (&result))
+    return *roi;
+
+  return result;
+
+
+
+}
+
+
+/* Pass-through when trying to perform on an infinite plane
+ */
+static gboolean
+operation_process (GeglOperation        *operation,
+                   GeglOperationContext *context,
+                   const gchar          *output_prop,
+                   const GeglRectangle  *result,
+                   gint                  level)
+{
+  GeglOperationClass  *operation_class;
+
+  const GeglRectangle *in_rect =
+    gegl_operation_source_get_bounding_box (operation, "input");
+
+  operation_class = GEGL_OPERATION_CLASS (gegl_op_parent_class);
+
+  if (in_rect && gegl_rectangle_is_infinite_plane (in_rect))
+    {
+      gpointer in = gegl_operation_context_get_object (context, "input");
+      gegl_operation_context_take_object (context, "output",
+                                          g_object_ref (G_OBJECT (in)));
+      return TRUE;
+    }
+
+  /* chain up, which will create the needed buffers for our actual
+   * process function
+   */
+  return operation_class->process (operation, context, output_prop, result,
+                                   gegl_operation_context_get_level (context));
+}
+
+static void
+gegl_op_class_init (GeglOpClass *klass)
+{
+
+  GeglOperationClass       *operation_class;
+  GeglOperationFilterClass *filter_class;
+
+  operation_class = GEGL_OPERATION_CLASS (klass);
+  filter_class    = GEGL_OPERATION_FILTER_CLASS (klass);
+
+  /* Override methods of inherited class */
+
+  filter_class->process                    = neon_process;
+
+
+  operation_class->prepare                 = neon_prepare;
+  operation_class->process                 = operation_process;
+  operation_class->get_bounding_box        = neon_get_bounding_box;
+  operation_class->get_required_for_output = neon_get_required_for_output;
+  operation_class->get_cached_region       = neon_get_cached_region;
+  operation_class->opencl_support          = 0;
+  operation_class->threaded                = 0; //Due to IIR Implementation (smear-carry), we require 
single-process, linear operation.
+  operation_class->want_in_place           = 0; //IIR Causes buffer build-up.
+  operation_class->no_cache                = 1; //IIR Causes buffer build-up.
+
+  gegl_operation_class_set_keys (operation_class,
+    "name",       "gegl:edge-neon",
+    "categories", "Edge Detection",
+    "description",
+        _("Performs edge detection using a Gaussian derivative method."),
+        NULL);
+
+
+
+  /*
+  gchar                    *composition = "<?xml version='1.0' encoding='UTF-8'?>"
+    "<gegl>"
+    "<node operation='gegl:edge-neon'>"
+    "  <params>"
+    "    <param name='radius'>2</param>"
+    "    <param name='amount'>1</param>"
+    "  </params>"
+    "</node>"
+    "<node operation='gegl:load'>"
+    "  <params>"
+    "    <param name='path'>standard-input.png</param>"
+    "  </params>"
+    "</node>"
+    "</gegl>";
+
+  */
+}
+
+#endif


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]