[gegl] workshop: add experimental alpha-inpainting op



commit 44f82840a37c16b3bf072949e448c8ef30631276
Author: Øyvind Kolås <pippin gimp org>
Date:   Wed Jan 31 01:21:23 2018 +0100

    workshop: add experimental alpha-inpainting op
    
    This is a protoype of an algorithm/operation behaving a bit like resynthesizer,
    for now - too many parameters exposed and prototype quality code, but it does
    the job well for filling in small pixel holes in images.

 operations/workshop/Makefile.am |    1 +
 operations/workshop/inpaint.c   |  616 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 617 insertions(+), 0 deletions(-)
---
diff --git a/operations/workshop/Makefile.am b/operations/workshop/Makefile.am
index 1bb8c3d..e7f97df 100644
--- a/operations/workshop/Makefile.am
+++ b/operations/workshop/Makefile.am
@@ -19,6 +19,7 @@ op_LTLIBRARIES =    \
        domain-transform.la \
        gradient-map.la \
        hstack.la \
+       inpaint.la \
        integral-image.la \
        linear-sinusoid.la \
        rawbayer-load.la \
diff --git a/operations/workshop/inpaint.c b/operations/workshop/inpaint.c
new file mode 100644
index 0000000..11af44d
--- /dev/null
+++ b/operations/workshop/inpaint.c
@@ -0,0 +1,616 @@
+/* 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/>.
+ *
+ * Copyright 2018 Øyvind Kolås <pippin gimp org>
+ *
+ */
+
+#include <stdio.h>
+#include "config.h"
+#include <glib/gi18n-lib.h>
+
+#ifdef GEGL_PROPERTIES
+
+property_int (seek_distance, "seek radius", 16)
+  value_range (4, 512)
+
+property_int (min_neigh, "min neigh", 7)
+  value_range (1, 10)
+
+property_int (min_iter, "min iter", 96)
+  value_range (1, 512)
+
+property_double (chance_try, "try chance", 0.66)
+  value_range (0.0, 1.0)
+
+property_double (chance_retry, "retry chance", 0.33)
+  value_range (0.0, 1.0)
+
+#else
+
+#define GEGL_OP_FILTER
+#define GEGL_OP_NAME      inpaint
+#define GEGL_OP_C_SOURCE  inpaint.c
+
+#include "gegl-op.h"
+#include <math.h>
+#include <stdio.h>
+
+#define POW2(x) ((x)*(x))
+
+#define INITIAL_SCORE 1200000000
+#define NEIGHBORHOOD 49
+#if 1
+int order_2d[][15]={
+                 {  0,  0,  0,  0,  0,142,111,112,126,128,  0,  0,  0,  0,  0},
+                 {  0,  0,  0,  0,124,110, 86, 87, 88,113,129,143,  0,  0,  0},
+                 {  0,  0,  0,125,109, 85, 58, 59, 60, 89, 90,114,  0,  0,  0},
+                 {  0,  0,  0,108, 78, 57, 38, 39, 40, 61, 79,115,130,  0,  0},
+                 {  0,  0,107, 84, 76, 37, 26, 21, 27, 41, 62, 91,116,  0,  0},
+                 {  0,153,106, 77, 56, 25, 17,  9, 13, 28, 42, 62, 92,152,  0},
+                 {  0,149,105, 55, 36, 16,  8,  1,  5, 18, 29, 43, 63,150,  0},
+                 {  0,145, 75, 54, 24, 12,  4,  0,  2, 10, 22, 44, 64,147,  0},
+                 {  0,146,104, 53, 35, 20,  7,  3,  6, 14, 30, 45, 65,148,  0},
+                 {  0,154,103, 74, 52, 34, 15, 11, 19, 31, 46, 66, 93,152,  0},
+                 {  0,156,102, 83, 73, 51, 33, 23, 32, 47, 67, 80,117,158,  0},
+                 {  0,  0,123,101, 82, 72, 50, 49, 48, 68, 81, 94,131,159,  0},
+                 {  0,  0,  0,122,100, 99, 71, 70, 69, 95,118,132,140,  0,  0},
+                 {  0,  0,  0,  0,121,120, 98, 97, 96,119,133,139,  0,  0,  0},
+                 {  0,  0,  0,  0,144,138,136,134,135,137,141,  0,  0,  0,  0},
+               };
+#else
+int order_2d[][15]={
+                 {  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+                 {  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+                 {  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+                 {  0,  0,  0,  0,  0, 21,  0,  0, 22,  0,  0,  0,  0,  0,  0},
+                 {  0,  0,  0,  0,  0,  0, 17,  0,  0,  0, 18,  0,  0,  0,  0},
+                 {  0,  0,  0,  0,  0,  0,  0,  9,  0, 13,  0,  0,  0,  0,  0},
+                 {  0,  0,  0,  0,  0, 16,  8,  1,  5,  0,  0,  0,  0,  0,  0},
+                 {  0,  0,  0,  0,  0,  0,  4,  0,  2, 10,  0,  0,  0,  0,  0},
+                 {  0,  0,  0,  0,  0, 12,  7,  3,  6,  0,  0,  0,  0,  0,  0},
+                 {  0,  0,  0,  0,  0,  0,  0, 11,  0,  0, 23,  0,  0,  0,  0},
+                 {  0,  0,  0,  0,  0,  0, 15,  0,  0, 14,  0,  0,  0,  0,  0},
+                 {  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+                 {  0,  0,  0,  0,  0, 20,  0,  0, 19,  0,  0,  0,  0,  0,  0},
+                 {  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+                 {  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+               };
+#endif
+
+static int order[512][3];
+static GHashTable *ht = NULL;
+static GList *probes = NULL;
+
+static void init_order(void)
+{
+  static int inited = 0;
+  int i, x, y;
+  if (inited)
+    return;
+
+  order[0][0] = 0;
+  order[0][1] = 0;
+  order[0][2] = 1;
+
+  for (i = 1; i < 159; i++)
+  for (y = -7; y <= 7; y ++)
+    for (x = -7; x <= 7; x ++)
+      {
+        if (order_2d[x+7][y+7] == i)
+        {
+          order[i][0] = x;
+          order[i][1] = y;
+          order[i][2] = POW2(x)+POW2(y);
+        }
+      }
+  inited = 1;
+}
+
+
+static void
+prepare (GeglOperation *operation)
+{
+  const Babl *format = babl_format ("RGBA float");
+
+  gegl_operation_set_format (operation, "input",  format);
+  gegl_operation_set_format (operation, "output", format);
+}
+
+static GeglRectangle
+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;
+}
+
+
+static void extract_site (GeglBuffer *input, int x, int y, guchar *dst)
+{
+  static const Babl *format = NULL;
+  if (!format) format = babl_format ("R'G'B'A u8");
+  for (int i = 0; i <= NEIGHBORHOOD; i++)
+  {
+    gegl_buffer_sample (input,  x + order[i][0], y + order[i][1], NULL,
+      &dst[i*4], format, GEGL_SAMPLER_NEAREST, 0);
+  }
+}
+
+static int u8_rgb_diff (guchar *a, guchar *b)
+{
+  return POW2(a[0]-b[0]) * 2 + POW2(a[1]-b[1]) * 4 + POW2(a[2]-b[2]) * 1;
+}
+
+
+static int inline score_site (guchar *needle, guchar *hay, int bail)
+{
+  int i;
+  int score = 0;
+  /* bail early with really bad score - the target site doesnt have opacity */
+
+  if (hay[3] < 2)
+  {
+    return INITIAL_SCORE;
+  }
+
+  for (i = 1; i < NEIGHBORHOOD && score < bail; i++)
+  {
+    if (needle[i*4 + 3] && hay[i*4 + 3])
+    {
+      score += u8_rgb_diff (&needle[i*4 + 0], &hay[i*4 + 0]) * 10 / order[i][2];
+    }
+    else
+    {
+      /* we score missing cells as if it is a big diff */
+      score += ((POW2(36))*3) * 70 / order[i][2];
+    }
+  }
+  return score;
+}
+
+typedef struct Probe {
+  int target_x;
+  int target_y;
+  int age;
+  int score;
+  int source_x;
+  int source_y;
+} Probe;
+
+
+static void add_probe (GeglOperation *operation, int target_x, int target_y)
+{
+  Probe *probe    = g_malloc0 (sizeof (Probe));
+  probe->target_x = target_x;
+  probe->target_y = target_y;
+  probe->source_x = target_x;
+  probe->source_y = target_y;
+  probe->score    = INITIAL_SCORE;
+  probes          = g_list_prepend (probes, probe);
+}
+
+static int probe_rel_is_set (GeglBuffer *output, Probe *probe, int rel_x, int rel_y)
+{
+  static const Babl *format = NULL;
+  guchar pix[4];
+  if (!format) format = babl_format ("R'G'B'A u8");
+  gegl_buffer_sample (output, probe->target_x + rel_x, probe->target_y + rel_y, NULL, &pix[0], format, 
GEGL_SAMPLER_NEAREST, 0);
+  return pix[3] > 5;
+}
+
+
+static int probe_neighbors (GeglBuffer *output, Probe *probe)
+{
+  return probe_rel_is_set (output, probe, -1, 0) +
+         probe_rel_is_set (output, probe,  1, 0) +
+         probe_rel_is_set (output, probe,  0, 1) +
+         probe_rel_is_set (output, probe,  0, -1)
+#if 1
+         + probe_rel_is_set (output, probe,  1, 1)
+         + probe_rel_is_set (output, probe,  2, 0)
+         + probe_rel_is_set (output, probe,  0, 2)
+         + probe_rel_is_set (output, probe, -2, 0)
+         + probe_rel_is_set (output, probe,  0, -2)
+         + probe_rel_is_set (output, probe, -1,-1)
+         + probe_rel_is_set (output, probe,  1,-1)
+         + probe_rel_is_set (output, probe, -1, 1)
+         + probe_rel_is_set (output, probe, -3, 0)
+         + probe_rel_is_set (output, probe,  3, 0)
+         + probe_rel_is_set (output, probe,  0, 3)
+         + probe_rel_is_set (output, probe,  0, -3)
+#endif
+;
+}
+
+static int probe_improve (GeglOperation       *operation,
+                          GeglBuffer          *input,
+                          GeglBuffer          *output,
+                          const GeglRectangle *result,
+                          Probe               *probe,
+                          int                  min_neighbours)
+{
+  GeglProperties *o = GEGL_PROPERTIES (operation);
+  guchar needle[4 * NEIGHBORHOOD];
+  gint  dst_x  = probe->target_x;
+  gint  dst_y  = probe->target_y;
+  gint *best_x = &probe->source_x;
+  gint *best_y = &probe->source_y;
+  gint start_x = probe->source_x;
+  gint start_y = probe->source_y;
+  int old_score = probe->score;
+  static const Babl *format = NULL;
+  if (!format)
+    format = babl_format ("RGBA float");
+  extract_site (output, dst_x, dst_y, &needle[0]);
+
+#if 0
+  if (probe->score < 10000)
+    return 0;
+#endif
+
+#if 0
+  for (int dy = -o->seek_distance; dy < o->seek_distance; dy += (dy*dy < 64 ? 1 : 3))
+    for (int dx = -o->seek_distance ; dx < o->seek_distance; dx += (dx*dx < 64 ? 1 : 3))
+#else
+  for (int dy = -o->seek_distance; dy < o->seek_distance; dy ++)
+    for (int dx = -o->seek_distance ; dx < o->seek_distance; dx ++)
+#endif
+      {
+#define xy2offset(x,y)   ((y) * result->width + (x))
+        int offset = xy2offset(start_x + dx, start_y + dy);
+        int score;
+        guchar *hay = NULL;
+
+        if (start_x + dx == dst_x &&
+            start_y + dy == dst_y)
+          continue;
+
+        if (offset < 0 || offset >= result->width * result->height)
+          continue;
+        if (start_x + dx < 5 || start_y + dy < 5 ||
+            start_x + dx > result->width - 5 ||
+            start_y + dy > result->height - 5)
+          continue;
+
+        hay = g_hash_table_lookup (ht, GINT_TO_POINTER(offset));
+        if (!hay)
+        {
+          hay = g_malloc (4 * NEIGHBORHOOD);
+          extract_site (input, start_x + dx, start_y + dy, hay);
+          g_hash_table_insert (ht, GINT_TO_POINTER(offset), hay);
+        }
+
+        score = score_site (&needle[0], hay, probe->score);
+
+        if (score < probe->score)
+          {
+            *best_x = start_x + dx;
+            *best_y = start_y + dy;
+            probe->score = score;
+          }
+      }
+
+  if (old_score != probe->score && old_score != INITIAL_SCORE) 
+    {
+      /* spread our resulting neighborhodo to unset neighbors */
+      if (!probe_rel_is_set (output, probe, -1, 0))
+        {
+          for (GList *l = probes; l; l = l->next)
+          {
+            Probe *neighbor_probe = l->data;
+            if ( (probe->target_y      == neighbor_probe->target_y) &&
+                ((probe->target_x - 1) == neighbor_probe->target_x))
+            {
+              gfloat rgba[4];
+              gegl_buffer_sample (input, probe->source_x - 1, probe->source_y,  NULL, &rgba[0], format, 
GEGL_SAMPLER_NEAREST, 0);
+              if (rgba[3] > 0.001)
+               {
+                 neighbor_probe->source_x = probe->source_x - 1;
+                 neighbor_probe->source_y = probe->source_y;
+                 neighbor_probe->score --;
+                 gegl_buffer_set (output, GEGL_RECTANGLE(neighbor_probe->target_x, neighbor_probe->target_y, 
1, 1), 0, format, &rgba[0], 0);
+                 neighbor_probe->age++;
+               }
+            }
+          }
+        }
+
+      if (!probe_rel_is_set (output, probe, 1, 0))
+        {
+          for (GList *l = probes; l; l = l->next)
+          {
+            Probe *neighbor_probe = l->data;
+            if ( (probe->target_y      == neighbor_probe->target_y) &&
+                ((probe->target_x + 1) == neighbor_probe->target_x))
+            {
+              gfloat rgba[4];
+              gegl_buffer_sample (input, probe->source_x + 1, probe->source_y,  NULL, &rgba[0], format, 
GEGL_SAMPLER_NEAREST, 0);
+              if (rgba[3] > 0.001)
+               {
+                 neighbor_probe->source_x = probe->source_x + 1;
+                 neighbor_probe->source_y = probe->source_y;
+                 neighbor_probe->score --;
+                 gegl_buffer_set (output, GEGL_RECTANGLE(neighbor_probe->target_x, neighbor_probe->target_y, 
1, 1), 0, format, &rgba[0], 0);
+                 neighbor_probe->age++;
+               }
+            }
+          }
+        }
+
+      if (!probe_rel_is_set (output, probe, 0, -1))
+        {
+          for (GList *l = probes; l; l = l->next)
+          {
+            Probe *neighbor_probe = l->data;
+            if ( (probe->target_y - 1  == neighbor_probe->target_y) &&
+                ((probe->target_x    ) == neighbor_probe->target_x))
+            {
+              gfloat rgba[4];
+              gegl_buffer_sample (input, probe->source_x, probe->source_y - 1,  NULL, &rgba[0], format, 
GEGL_SAMPLER_NEAREST, 0);
+              if (rgba[3] > 0.001)
+               {
+                 neighbor_probe->source_x = probe->source_x;
+                 neighbor_probe->source_y = probe->source_y - 1;
+                 neighbor_probe->score --;
+                 gegl_buffer_set (output, GEGL_RECTANGLE(neighbor_probe->target_x, neighbor_probe->target_y, 
1, 1), 0, format, &rgba[0], 0);
+                 neighbor_probe->age++;
+               }
+            }
+          }
+        }
+
+      if (!probe_rel_is_set (output, probe, 0, 1))
+        {
+          for (GList *l = probes; l; l = l->next)
+          {
+            Probe *neighbor_probe = l->data;
+            if ( (probe->target_y + 1  == neighbor_probe->target_y) &&
+                ((probe->target_x    ) == neighbor_probe->target_x))
+            {
+              gfloat rgba[4];
+              gegl_buffer_sample (input, probe->source_x, probe->source_y + 1,  NULL, &rgba[0], format, 
GEGL_SAMPLER_NEAREST, 0);
+              if (rgba[3] > 0.001)
+               {
+                 neighbor_probe->source_x = probe->source_x;
+                 neighbor_probe->source_y = probe->source_y + 1;
+                 neighbor_probe->score --;
+                 gegl_buffer_set (output, GEGL_RECTANGLE(neighbor_probe->target_x, neighbor_probe->target_y, 
1, 1), 0, format, &rgba[0], 0);
+                 neighbor_probe->age++;
+               }
+            }
+          }
+        }
+
+
+    }
+  if (probe->score == old_score)
+    return -1;
+  return 0;
+}
+
+static void copy_buf (GeglOperation *operation,
+                      GeglBuffer    *input,
+                      GeglBuffer    *output,
+                      const GeglRectangle *result)
+{
+  const Babl *format = babl_format ("RGBA float");
+  GeglBufferIterator *i = gegl_buffer_iterator_new (output,
+                                                    result,
+                                                    0,
+                                                    format,
+                                                    GEGL_ACCESS_WRITE,
+                                                    GEGL_ABYSS_NONE);
+  gegl_buffer_iterator_add (i, input, result, 0, format, GEGL_ACCESS_READ, GEGL_ABYSS_NONE);
+  while (gegl_buffer_iterator_next (i))
+  {
+    gint x = i->roi[0].x;
+    gint y = i->roi[0].y;
+    gint n_pixels  = i->roi[0].width * i->roi[0].height;
+    float *in_pix  = i->data[1];
+    float *out_pix = i->data[0];
+
+    while (n_pixels--)
+    {
+      if (in_pix[3] <= 0.001)
+      {
+        out_pix[0] = 0;
+        out_pix[1] = 0;
+        out_pix[2] = 0;
+        out_pix[3] = 0;
+        add_probe (operation, x, y);
+      }
+      else
+      {
+        out_pix[0] = in_pix[0];
+        out_pix[1] = in_pix[1];
+        out_pix[2] = in_pix[2];
+        out_pix[3] = in_pix[3];
+      }
+
+      in_pix += 4;
+      out_pix += 4;
+
+      x++;
+      if (x >= i->roi[0].x + i->roi[0].width)
+      {
+        x = i->roi[0].x;
+        y++;
+      }
+    }
+  }
+}
+
+static void do_inpaint (GeglOperation *operation,
+                        GeglBuffer *input,
+                        GeglBuffer *output,
+                        const GeglRectangle *result)
+{
+  GeglProperties *o = GEGL_PROPERTIES (operation);
+  const Babl *format = babl_format ("RGBA float");
+  gint missing = 1;
+  gint old_missing = 3;
+  gint total = 0;
+  gint runs = 0;
+
+  while (missing != old_missing || runs < o->min_iter)
+  { runs++;
+    total = 0;
+    old_missing = missing;
+    missing = 0;
+  for (GList *p= probes; p; p= p->next)
+  {
+    Probe *probe = p->data;
+    gint try_replace;
+
+    if (probe->score == INITIAL_SCORE)
+    {
+      missing ++;
+      try_replace =  0;
+    }
+    else
+    {
+      try_replace = (probe->age < 8 &&
+                     ((rand()%100)/100.0) < o->chance_retry);
+    }
+    total ++;
+
+    if ((probe->source_x == probe->target_x &&
+        probe->source_y == probe->target_y) || try_replace)
+    {
+       if ((probe->source_x == probe->target_x &&
+            probe->source_y == probe->target_y))
+         try_replace = 0;
+
+      if ((rand()%100)/100.0 < o->chance_try && probe_neighbors (output, probe) >= o->min_neigh)
+      {
+        if(try_replace)
+         probe->score = INITIAL_SCORE;
+        if (probe_improve (operation, input, output, result, probe, 1) == 0)
+        {
+          gfloat rgba[4];
+          gegl_buffer_sample (input,  probe->source_x, probe->source_y,  NULL, &rgba[0], format, 
GEGL_SAMPLER_NEAREST, 0);
+          if (rgba[3] <= 0.01)
+            fprintf (stderr, "eek %i,%i %f %f %f %f\n", probe->source_x, probe->source_y, rgba[0], rgba[1], 
rgba[2], rgba[3]);
+          gegl_buffer_set (output, GEGL_RECTANGLE(probe->target_x, probe->target_y, 1, 1), 0, format, 
&rgba[0], 0);
+          probe->age++;
+         }
+      }
+    }
+  }
+  gegl_operation_progress (operation, (total-missing) * 1.0 / total,
+                           "finding suitable pixels");
+#if 0
+  fprintf (stderr, "\r%i/%i %2.2f run#:%i  ", total-missing, total, (total-missing) * 100.0 / total, runs);
+#endif
+  }
+  fprintf (stderr, "\n");
+}
+
+
+static gboolean
+process (GeglOperation       *operation,
+         GeglBuffer          *input,
+         GeglBuffer          *output,
+         const GeglRectangle *result,
+         gint                 level)
+{
+  init_order ();
+  ht = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free);
+
+  copy_buf (operation, input, output, result);
+  do_inpaint (operation, input, output, result);
+
+  while (probes)
+  {
+    g_free (probes->data);
+    probes = g_list_remove (probes, probes->data);
+  }
+
+  g_hash_table_destroy (ht);
+
+  return TRUE;
+}
+
+static GeglRectangle
+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;
+}
+
+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;
+    }
+
+  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);
+
+  filter_class->process                    = process;
+  operation_class->prepare                 = prepare;
+  operation_class->process                 = operation_process;
+  operation_class->get_required_for_output = get_required_for_output;
+  operation_class->get_cached_region = get_cached_region;
+  operation_class->opencl_support          = FALSE;
+  operation_class->threaded                = FALSE;
+
+  gegl_operation_class_set_keys (operation_class,
+      "name",        "gegl:alpha-inpaint",
+      "title",       "Heal transparent",
+      "categories",  "heal",
+      "description", "Replaces fully transparent pixels with good candidate pixels found in the 
neighbourhood of the hole",
+      NULL);
+}
+
+#endif


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