[gegl] voronoi-diagram: new op in workshop



commit cbc1f66468ca8304160ef30ff7ebe891ee526ae5
Author: Ell <ell_se yahoo com>
Date:   Wed Nov 21 16:59:58 2018 -0500

    voronoi-diagram: new op in workshop
    
    Given a subset of the input referred to as the "seed", the
    gegl:voronoi-diagram op paints each non-seed pixel with the color
    of the nearest seed pixel, using one of several metrics.  The non-
    seeded region is identified either through a special color in the
    input, or using an AUX input.  The resulting output is, essentialy,
    the Voronoi diagram of the input.

 operations/workshop/Makefile.am        |  15 +-
 operations/workshop/voronoi-diagram.cc | 487 +++++++++++++++++++++++++++++++++
 po/POTFILES.in                         |   1 +
 3 files changed, 501 insertions(+), 2 deletions(-)
---
diff --git a/operations/workshop/Makefile.am b/operations/workshop/Makefile.am
index edc6a5ff5..75c7de4e8 100644
--- a/operations/workshop/Makefile.am
+++ b/operations/workshop/Makefile.am
@@ -8,8 +8,7 @@ AM_CPPFLAGS += -I$(srcdir)
 
 LIBS = $(op_libs)
 
-opdir = $(ext_dir)
-op_LTLIBRARIES = \
+ops = \
        bayer-matrix.la \
        bilateral-filter-fast.la \
        connected-components.la \
@@ -27,3 +26,15 @@ op_LTLIBRARIES = \
        rawbayer-load.la  \
        segment-kmeans.la \
        selective-hue-saturation.la
+
+if HAVE_CXX11
+
+voronoi_diagram_la_SOURCES = voronoi-diagram.cc
+
+ops += \
+       voronoi-diagram.la
+
+endif
+
+opdir = $(ext_dir)
+op_LTLIBRARIES = $(ops)
diff --git a/operations/workshop/voronoi-diagram.cc b/operations/workshop/voronoi-diagram.cc
new file mode 100644
index 000000000..f6d26daf0
--- /dev/null
+++ b/operations/workshop/voronoi-diagram.cc
@@ -0,0 +1,487 @@
+/* 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 <https://www.gnu.org/licenses/>.
+ *
+ * Copyright (C) 2018 Ell
+ */
+
+#include "config.h"
+#include <glib/gi18n-lib.h>
+
+#ifdef GEGL_PROPERTIES
+
+property_enum (metric, _("Metric"),
+               GeglDistanceMetric, gegl_distance_metric,
+               GEGL_DISTANCE_METRIC_EUCLIDEAN)
+  description (_("Metric to use for the distance calculation"))
+
+property_color (mask, _("Mask"), "transparent")
+  description (_("Unseeded region color"))
+
+property_boolean (invert, _("Invert"), FALSE)
+  description (_("Invert mask"))
+
+property_boolean (seed_edges, _("Seed edges"), FALSE)
+  description (_("Whether the image edges are also seeded"))
+
+property_enum (abyss_policy, _("Abyss policy"),
+               GeglAbyssPolicy, gegl_abyss_policy,
+               GEGL_ABYSS_NONE)
+  description (_("How image edges are handled"))
+  ui_meta     ("sensitive", "seed-edges")
+
+#else
+
+#define GEGL_OP_COMPOSER
+#define GEGL_OP_NAME     voronoi_diagram
+#define GEGL_OP_C_SOURCE voronoi-diagram.cc
+
+#include "gegl-op.h"
+
+
+static GeglRectangle
+get_required_for_output (GeglOperation       *operation,
+                         const gchar         *input_pad,
+                         const GeglRectangle *roi)
+{
+  return gegl_operation_get_bounding_box (operation);
+}
+
+static GeglRectangle
+get_invalidated_by_change (GeglOperation       *operation,
+                           const gchar         *input_pad,
+                           const GeglRectangle *roi)
+{
+  return gegl_operation_get_bounding_box (operation);
+}
+
+static GeglRectangle
+get_cached_region (GeglOperation       *operation,
+                   const GeglRectangle *roi)
+{
+  return gegl_operation_get_bounding_box (operation);
+}
+
+static void
+prepare (GeglOperation *operation)
+{
+  const Babl *format = gegl_operation_get_source_format (operation, "input");
+
+  if (! format)
+    format = babl_format ("RGBA float");
+
+  gegl_operation_set_format (operation, "output", format);
+}
+
+struct BasicMetric
+{
+  static gint
+  prepare_y (gint y)
+  {
+    return y;
+  }
+
+  static gint
+  no_intersection ()
+  {
+    return G_MAXINT / 2;
+  }
+};
+
+struct EuclideanMetric : BasicMetric
+{
+  static gint
+  prepare_y (gint y)
+  {
+    return y * y;
+  }
+
+  static gint
+  distance (gint x,
+            gint y2)
+  {
+    return x * x + y2;
+  }
+
+  static gint
+  intersection (gint x_1,
+                gint y2_1,
+                gint x_2,
+                gint y2_2)
+  {
+    return (distance (x_2, y2_2) - distance (x_1, y2_1) + (x_1 - x_2)) /
+           (2 * (x_1 - x_2));
+  }
+};
+
+struct ManhattanMetric : BasicMetric
+{
+  static gint
+  distance (gint x,
+            gint y)
+  {
+    return x + y;
+  }
+
+  static gint
+  intersection (gint x_1,
+                gint y_1,
+                gint x_2,
+                gint y_2)
+  {
+    if (y_2 - y_1 <= x_1 - x_2)
+      return 0;
+    else
+      return no_intersection ();
+  }
+};
+
+struct ChebyshevMetric : BasicMetric
+{
+  static gint
+  distance (gint x,
+            gint y)
+  {
+    return MAX (x, y);
+  }
+
+  static gint
+  intersection (gint x_1,
+                gint y_1,
+                gint x_2,
+                gint y_2)
+  {
+    if (y_2 <= y_1)
+      return 0;
+    else
+      return y_2 - x_1;
+  }
+};
+
+template <class Metric>
+static gboolean
+process (GeglOperation       *operation,
+         GeglBuffer          *input,
+         GeglBuffer          *aux,
+         GeglBuffer          *output,
+         const GeglRectangle *roi,
+         gint                 level,
+         Metric               metric)
+{
+  GeglProperties *o           = GEGL_PROPERTIES (operation);
+  gboolean        invert      = o->invert;
+  const Babl     *format      = gegl_buffer_get_format (output);
+  const Babl     *dist_format = babl_format ("Y u32");
+  const Babl     *aux_format  = aux ? gegl_buffer_get_format (aux) : format;
+  gint            bpp         = babl_format_get_bytes_per_pixel (format);
+  gint            aux_bpp     = babl_format_get_bytes_per_pixel (aux_format);
+  GeglBuffer     *dist;
+  guint8          mask[64];
+
+  if (aux_bpp > sizeof (mask))
+    return FALSE;
+
+  gegl_color_get_pixel (o->mask, aux_format, mask);
+
+  dist = gegl_buffer_new (roi, dist_format);
+
+  gegl_parallel_distribute_range (
+    roi->width, gegl_operation_get_pixels_per_thread (operation) / roi->height,
+    [=] (gint x0, gint width)
+    {
+      guint8  *in_row;
+      guint8  *out_row;
+      guint32 *dist_row;
+      guint8  *aux_row;
+      gint     x;
+
+      in_row   = (guint8 *) g_malloc (bpp * (roi->height + 2));
+      out_row  = (guint8 *) g_malloc (bpp * roi->height);
+      dist_row = g_new (guint32, roi->height);
+
+      in_row += bpp;
+
+      if (aux)
+        aux_row = (guint8 *) g_malloc (bpp * roi->height);
+      else
+        aux_row = in_row;
+
+      for (x = x0; x < x0 + width; x++)
+        {
+          gegl_buffer_get (input,
+                           GEGL_RECTANGLE (roi->x + x, roi->y - 1,
+                                           1,          roi->height + 2),
+                           1.0, format, in_row - bpp,
+                           GEGL_AUTO_ROWSTRIDE, o->abyss_policy);
+
+          if (aux)
+            {
+              gegl_buffer_get (aux,
+                               GEGL_RECTANGLE (roi->x + x, roi->y,
+                                               1,          roi->height),
+                               1.0, aux_format, aux_row,
+                               GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+            }
+
+          auto pass = [&] (gint first, gint last, gint step)
+          {
+            gint          d = o->seed_edges ? 0 : roi->width + roi->height + 1;
+            const guint8 *p = in_row + (first - step) * bpp;
+            gint          y;
+
+            for (y = first; y != last; y += step)
+              {
+                gint dy;
+
+                if ((! memcmp (aux_row + y * bpp, mask, aux_bpp)) == invert)
+                  {
+                    d = 0;
+                    p = in_row + y * bpp;
+                  }
+                else
+                  {
+                    d++;
+                  }
+
+                dy = metric.prepare_y (d);
+
+                if (step > 0 || dy < dist_row[y])
+                  {
+                    memcpy (out_row + y * bpp, p, bpp);
+
+                    dist_row[y] = dy;
+                  }
+              }
+          };
+
+          pass (0,               roi->height, +1);
+          pass (roi->height - 1, -1,          -1);
+
+          gegl_buffer_set (output,
+                           GEGL_RECTANGLE (roi->x + x, roi->y, 1, roi->height),
+                           0, format, out_row, GEGL_AUTO_ROWSTRIDE);
+          gegl_buffer_set (dist,
+                           GEGL_RECTANGLE (roi->x + x, roi->y, 1, roi->height),
+                           0, dist_format, dist_row, GEGL_AUTO_ROWSTRIDE);
+        }
+
+      in_row -= bpp;
+
+      g_free (in_row);
+      g_free (out_row);
+      g_free (dist_row);
+
+      if (aux)
+        g_free (aux_row);
+    });
+
+  gegl_parallel_distribute_range (
+    roi->height, gegl_operation_get_pixels_per_thread (operation) / roi->width,
+    [=] (gint y0, gint height)
+    {
+      guint8  *in_row;
+      guint8  *out_row;
+      guint32 *dist_row;
+      gint    *queue;
+      gint    *hdist;
+      gint     y;
+
+      in_row   = (guint8 *) g_malloc (bpp * (roi->width + 2));
+      out_row  = (guint8 *) g_malloc (bpp * roi->width);
+      dist_row = g_new (guint32, roi->width);
+
+      in_row += bpp;
+
+      queue = g_new (gint, roi->width);
+      hdist = g_new (gint, roi->width);
+
+      for (y = y0; y < y0 + height; y++)
+        {
+          gegl_buffer_get (output,
+                           GEGL_RECTANGLE (roi->x - 1,     roi->y + y,
+                                           roi->width + 2, 1),
+                           1.0, format, in_row - bpp,
+                           GEGL_AUTO_ROWSTRIDE, o->abyss_policy);
+          gegl_buffer_get (dist,
+                           GEGL_RECTANGLE (roi->x,     roi->y + y,
+                                           roi->width, 1),
+                           1.0, dist_format, dist_row,
+                           GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+
+          auto pass = [&] (gint first, gint last, gint step)
+          {
+            gint          dx;
+            gint          dy;
+            const guint8 *p;
+            gint          x;
+
+            dx = o->seed_edges ? 0 : roi->width + 1;
+            dy = metric.prepare_y (o->seed_edges ? 0 : roi->height + 1);
+            p  = in_row + (first - step) * bpp;
+
+            memset (queue, 0, roi->width * sizeof (gint));
+
+            for (x = first; x != last; x += step)
+              {
+                gint d;
+
+                if (dist_row[x] == 0)
+                  {
+                    dx = 0;
+                    dy = metric.prepare_y (0);
+                    p  = in_row + x * bpp;
+                  }
+                else
+                  {
+                    gint qx = queue[x] - 1;
+                    gint dv;
+                    gint n;
+
+                    dx++;
+
+                    if (qx >= 0)
+                      {
+                        gint dh = (x - qx) * step;
+
+                        if (dh < dx)
+                          {
+                            dv = dist_row[qx];
+
+                            n = metric.intersection (dx, dy, dh, dv);
+
+                            if (n <= 0)
+                              {
+                                dx = dh;
+                                dy = dv;
+                                p  = in_row + qx * bpp;
+                              }
+                            else if (n < (last - x) * step)
+                              {
+                                queue[x + n * step] = qx + 1;
+                              }
+                          }
+                      }
+
+                    dv = dist_row[x];
+
+                    n = metric.intersection (dx, dy, 0, dv);
+
+                    if (n <= 0)
+                      {
+                        dx = 0;
+                        dy = dv;
+                        p  = in_row + x * bpp;
+                      }
+                    else if (n < (last - x) * step)
+                      {
+                        queue[x + n * step] = x + 1;
+                      }
+                  }
+
+                d = metric.distance (dx, dy);
+
+                if (step > 0 || d < hdist[x])
+                  {
+                    memcpy (out_row + x * bpp, p, bpp);
+
+                    hdist[x] = d;
+                  }
+              }
+          };
+
+          pass (0,              roi->width, +1);
+          pass (roi->width - 1, -1,         -1);
+
+          gegl_buffer_set (output,
+                           GEGL_RECTANGLE (roi->x, roi->y + y, roi->width, 1),
+                           0, format, out_row, GEGL_AUTO_ROWSTRIDE);
+        }
+
+      in_row -= bpp;
+
+      g_free (in_row);
+      g_free (out_row);
+      g_free (dist_row);
+
+      g_free (queue);
+      g_free (hdist);
+    });
+
+  g_object_unref (dist);
+
+  return TRUE;
+}
+
+static gboolean
+process (GeglOperation       *operation,
+         GeglBuffer          *input,
+         GeglBuffer          *aux,
+         GeglBuffer          *output,
+         const GeglRectangle *roi,
+         gint                 level)
+{
+  GeglProperties *o = GEGL_PROPERTIES (operation);
+
+  switch (o->metric)
+    {
+    case GEGL_DISTANCE_METRIC_EUCLIDEAN:
+      return process (operation, input, aux, output, roi, level,
+                      EuclideanMetric ());
+
+    case GEGL_DISTANCE_METRIC_MANHATTAN:
+      return process (operation, input, aux, output, roi, level,
+                      ManhattanMetric ());
+
+    case GEGL_DISTANCE_METRIC_CHEBYSHEV:
+      return process (operation, input, aux, output, roi, level,
+                      ChebyshevMetric ());
+    }
+
+  g_return_val_if_reached (FALSE);
+}
+
+static void
+gegl_op_class_init (GeglOpClass *klass)
+{
+  GeglOperationClass         *operation_class;
+  GeglOperationComposerClass *composer_class;
+
+  operation_class = GEGL_OPERATION_CLASS (klass);
+  composer_class  = GEGL_OPERATION_COMPOSER_CLASS (klass);
+
+  operation_class->get_required_for_output   = get_required_for_output;
+  operation_class->get_invalidated_by_change = get_invalidated_by_change;
+  operation_class->get_cached_region         = get_cached_region;
+  operation_class->prepare                   = prepare;
+
+  operation_class->threaded                  = FALSE;
+  operation_class->want_in_place             = TRUE;
+
+  composer_class->process                    = process;
+
+  gegl_operation_class_set_keys (operation_class,
+    "name",            "gegl:voronoi-diagram",
+    "title",           _("Voronoi Diagram"),
+    "categories",      "map",
+    "reference-chain", "color                     "
+                       "noise-hurl pct-random=0.1 "
+                       "crop width=256 height=256 "
+                       "voronoi-diagram mask=black",
+    "reference-hash",  "0731590098ed020b5a9e7a71b71735dc",
+    "description",     _("Paints each non-seed pixel with the color of "
+                         "the nearest seed pixel."),
+    NULL);
+}
+
+#endif
diff --git a/po/POTFILES.in b/po/POTFILES.in
index b1f4b71e6..bc5ca40ca 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -272,3 +272,4 @@ operations/workshop/linear-sinusoid.c
 operations/workshop/rawbayer-load.c
 operations/workshop/segment-kmeans.c
 operations/workshop/selective-hue-saturation.c
+operations/workshop/voronoi-diagram.cc


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