[gegl] workshop/external: new paint-select operation
- From: Thomas Manni <tmanni src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gegl] workshop/external: new paint-select operation
- Date: Tue, 24 Nov 2020 10:04:39 +0000 (UTC)
commit 9505fd2f6985b3f578d45e23d1bb7c29e346f94c
Author: Thomas Manni <thomas manni free fr>
Date: Mon Nov 23 19:31:47 2020 +0100
workshop/external: new paint-select operation
A preliminary work towards the implementation of a smart selection tool.
operations/workshop/external/meson.build | 20 +
operations/workshop/external/paint-select.cc | 739 +++++++++++++++++++++++++++
po/POTFILES.in | 1 +
3 files changed, 760 insertions(+)
---
diff --git a/operations/workshop/external/meson.build b/operations/workshop/external/meson.build
index c349ed95e..1da329f34 100644
--- a/operations/workshop/external/meson.build
+++ b/operations/workshop/external/meson.build
@@ -97,3 +97,23 @@ if libv4l2.found()
install_dir: get_option('libdir') / api_name,
)
endif
+
+if maxflow.found()
+ gegl_operations += shared_library('paint-select',
+ 'paint-select.cc',
+ include_directories: [ rootInclude, geglInclude, seamlessInclude, ],
+ dependencies: [
+ babl,
+ glib,
+ gobject,
+ math,
+ maxflow,
+ ],
+ link_with: [
+ gegl_lib,
+ ],
+ name_prefix: '',
+ install: true,
+ install_dir: get_option('libdir') / api_name,
+ )
+endif
diff --git a/operations/workshop/external/paint-select.cc b/operations/workshop/external/paint-select.cc
new file mode 100644
index 000000000..d400278f9
--- /dev/null
+++ b/operations/workshop/external/paint-select.cc
@@ -0,0 +1,739 @@
+/* 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 2020 Thomas Manni <thomas manni free fr>
+ *
+ * expected input buffers:
+ * - input : current selection
+ * - aux : color image
+ * - aux2 : scribbles
+ */
+
+#include "config.h"
+#include <glib/gi18n-lib.h>
+
+#ifdef GEGL_PROPERTIES
+
+enum_start (gegl_paint_select_mode_type)
+ enum_value (GEGL_PAINT_SELECT_MODE_ADD, "add", N_("Add"))
+ enum_value (GEGL_PAINT_SELECT_MODE_SUBTRACT, "subtract", N_("Subtract"))
+enum_end (GeglPaintSelectModeType)
+
+property_enum (mode, _("Mode"),
+ GeglPaintSelectModeType, gegl_paint_select_mode_type,
+ GEGL_PAINT_SELECT_MODE_ADD)
+ description (_("Either to add to or subtract from the mask"))
+
+#else
+
+#define GEGL_OP_COMPOSER3
+#define GEGL_OP_NAME paint_select
+#define GEGL_OP_C_SOURCE paint-select.cc
+
+#include <maxflow/graph.h>
+#include <math.h>
+#include "gegl-op.h"
+
+#define SELECTION_FORMAT "Y float"
+#define SCRIBBLES_FORMAT "Y float"
+#define COLORS_FORMAT "R'G'B' float"
+
+#define POW2(x) ((x)*(x))
+#define BIG_CAPACITY 100.f
+#define EPSILON 0.05f
+#define N_GLOBAL_SAMPLES 1200
+#define N_BINS 64
+#define LOCAL_REGION_DILATE 40
+
+#define FG_MASK 1.f
+#define BG_MASK 0.f
+#define FG_SCRIBBLE 1.f
+#define BG_SCRIBBLE 0.f
+
+typedef maxflow::Graph<gfloat,gfloat,gfloat> GraphType;
+typedef int node_id;
+
+typedef struct
+{
+ gfloat bins[N_BINS][N_BINS][N_BINS];
+} Histogram;
+
+typedef enum
+{
+ HARD_SOURCE,
+ HARD_SINK,
+ SOFT
+} ConstraintType;
+
+typedef struct
+{
+ GeglPaintSelectModeType mode;
+ gint width;
+ gint height;
+ gint n_pixels;
+
+ gfloat *mask; /* selection mask */
+ gfloat *colors; /* input image */
+ gfloat *scribbles; /* user scribbles */
+ gfloat *h_costs; /* horizontal edges costs */
+ gfloat *v_costs; /* vertical edges costs */
+ gfloat mean_costs;
+
+ node_id *nodes; /* nodes map */
+ GraphType *graph; /* maxflow::graph */
+
+ gfloat *fg_samples;
+ gint n_fg_samples;
+ Histogram fg_hist;
+
+ gfloat *bg_samples;
+ gint n_bg_samples;
+ Histogram bg_hist;
+} PaintSelect;
+
+
+static inline gfloat
+pixels_distance (gfloat *p1,
+ gfloat *p2)
+{
+ return sqrtf (POW2(p1[0] - p2[0]) +
+ POW2(p1[1] - p2[1]) +
+ POW2(p1[2] - p2[2]));
+}
+
+static void
+paint_select_init_buffers (PaintSelect *ps,
+ GeglBuffer *mask,
+ GeglBuffer *colors,
+ GeglBuffer *scribbles,
+ GeglPaintSelectModeType mode)
+{
+ ps->mode = mode;
+ ps->width = gegl_buffer_get_width (mask);
+ ps->height = gegl_buffer_get_height (mask);
+ ps->n_pixels = ps->width * ps->height;
+
+ ps->graph = new GraphType (ps->n_pixels,
+ (ps->width - 1) * ps->height + ps->width * (ps->height - 1));
+
+ ps->mask = (gfloat *) gegl_malloc (sizeof (gfloat) * ps->n_pixels);
+ ps->colors = (gfloat *) gegl_malloc (sizeof (gfloat) * ps->n_pixels * 3);
+ ps->scribbles = (gfloat *) gegl_malloc (sizeof (gfloat) * ps->n_pixels);
+ ps->h_costs = (gfloat *) gegl_malloc (sizeof (gfloat) * (ps->width - 1) * ps->height);
+ ps->v_costs = (gfloat *) gegl_malloc (sizeof (gfloat) * ps->width * (ps->height - 1));
+ ps->nodes = (node_id *) gegl_malloc (sizeof (node_id) * ps->n_pixels);
+
+ ps->fg_samples = NULL;
+ ps->bg_samples = NULL;
+
+ gegl_buffer_get (mask, NULL, 1.0, babl_format (SELECTION_FORMAT),
+ ps->mask, GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+
+ gegl_buffer_get (colors, NULL, 1.0, babl_format (COLORS_FORMAT),
+ ps->colors, GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+
+ gegl_buffer_get (scribbles, NULL, 1.0, babl_format (SCRIBBLES_FORMAT),
+ ps->scribbles, GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+}
+
+static void
+paint_select_free_buffers (PaintSelect *ps)
+{
+ delete ps->graph;
+ gegl_free (ps->nodes);
+ gegl_free (ps->mask);
+ gegl_free (ps->scribbles);
+ gegl_free (ps->colors);
+ gegl_free (ps->h_costs);
+ gegl_free (ps->v_costs);
+ g_free (ps->fg_samples);
+ g_free (ps->bg_samples);
+}
+
+static void
+paint_select_compute_adjacent_costs (PaintSelect *ps)
+{
+ gint x, y, n = 0;
+ gfloat sum = 0.f;
+
+ /* horizontal */
+
+ for (y = 0; y < ps->height; y++)
+ {
+ for (x = 0; x < ps->width - 1; x++)
+ {
+ gint w_off = x + y * (ps->width - 1);
+ gint c1_off = (x + y * ps->width) * 3;
+ gint c2_off = c1_off + 3;
+
+ gfloat d = pixels_distance (ps->colors + c1_off, ps->colors + c2_off);
+ ps->h_costs[w_off] = d;
+ sum += d;
+ n++;
+ }
+ }
+
+ /* vertical */
+
+ for (x = 0; x < ps->width; x++)
+ {
+ for (y = 0; y < ps->height - 1; y++)
+ {
+ gint w_off = x + y * ps->width;
+ gint c1_off = (x + y * ps->width) * 3;
+ gint c2_off = c1_off + ps->width * 3;
+
+ gfloat d = pixels_distance (ps->colors + c1_off, ps->colors + c2_off);
+ ps->v_costs[w_off] = d;
+ sum += d;
+ n++;
+ }
+ }
+
+ /* compute mean costs */
+
+ ps->mean_costs = sum / (gfloat) n;
+}
+
+static inline gboolean
+paint_select_mask_is_interior_boundary (PaintSelect *ps,
+ gint x,
+ gint y)
+{
+ gboolean is_boundary = FALSE;
+ gint offset = x + y * ps->width;
+ gint x2, y2, n;
+ gint neighbors[4] = {0, };
+
+ x2 = x - 1;
+ if (x2 >= 0)
+ {
+ neighbors[0] = x2 + y * ps->width;
+ }
+
+ x2 = x + 1;
+ if (x2 < ps->width)
+ {
+ neighbors[1] = x2 + y * ps->width;
+ }
+
+ y2 = y - 1;
+ if (y2 >= 0)
+ {
+ neighbors[2] = x + y2 * ps->width;
+ }
+
+ y2 = y + 1;
+ if (y2 < ps->height)
+ {
+ neighbors[3] = x + y2 * ps->width;
+ }
+
+ for (n = 0; n < 4; n++)
+ {
+ gint neighbor_offset = neighbors[n];
+
+ if (neighbor_offset && ps->mask[neighbor_offset] != ps->mask[offset])
+ {
+ is_boundary = TRUE;
+ break;
+ }
+ }
+
+ return is_boundary;
+}
+
+static GeglRectangle
+paint_select_get_local_region (PaintSelect *ps)
+{
+ GeglRectangle extent = {0, 0, ps->width, ps->height};
+ GeglRectangle region;
+ gfloat scribble_val;
+ gfloat mask_val;
+ gint minx, miny;
+ gint maxx, maxy;
+ gint x, y;
+
+ minx = ps->width;
+ miny = ps->height;
+ maxx = maxy = 0;
+
+ if (ps->mode == GEGL_PAINT_SELECT_MODE_ADD)
+ {
+ scribble_val = FG_SCRIBBLE;
+ mask_val = BG_MASK;
+ }
+ else
+ {
+ scribble_val = BG_SCRIBBLE;
+ mask_val = FG_MASK;
+ }
+
+ for (y = 0; y < ps->height; y++)
+ {
+ for (x = 0; x < ps->width; x++)
+ {
+ gint off = x + y * ps->width;
+ if (ps->scribbles[off] == scribble_val &&
+ ps->mask[off] == mask_val)
+ {
+ if (x < minx)
+ minx = x;
+ else if (x > maxx)
+ maxx = x;
+
+ if (y < miny)
+ miny = y;
+ else if (y > maxy)
+ maxy = y;
+ }
+ }
+ }
+
+ region.x = minx;
+ region.y = miny;
+ region.width = maxx - minx + 1;
+ region.height = maxy - miny + 1;
+
+ if (gegl_rectangle_is_empty (®ion) ||
+ gegl_rectangle_is_infinite_plane (®ion))
+ return region;
+
+ region.x -= LOCAL_REGION_DILATE;
+ region.y -= LOCAL_REGION_DILATE;
+ region.width += 2 * LOCAL_REGION_DILATE;
+ region.height += 2 * LOCAL_REGION_DILATE;
+
+ gegl_rectangle_intersect (®ion, ®ion, &extent);
+ return region;
+}
+
+static void
+paint_select_init_local_samples (PaintSelect *ps,
+ GeglRectangle *region)
+{
+ gint *n_samples;
+ gfloat **samples;
+ gfloat scribble_val;
+ gfloat mask_val;
+ gint x, y, i;
+
+ if (ps->mode == GEGL_PAINT_SELECT_MODE_ADD)
+ {
+ n_samples = &ps->n_fg_samples;
+ samples = &ps->fg_samples;
+ scribble_val = FG_SCRIBBLE;
+ mask_val = FG_MASK;
+ g_printerr ("local samples are foreground\n");
+ }
+ else
+ {
+ n_samples = &ps->n_bg_samples;
+ samples = &ps->bg_samples;
+ scribble_val = BG_SCRIBBLE;
+ mask_val = BG_MASK;
+ g_printerr ("local samples are background\n");
+ }
+
+ *n_samples = 0;
+
+ for (y = region->y; y < region->y + region->height; y++)
+ {
+ for (x = region->x; x < region->x + region->width; x++)
+ {
+ gint off = x + y * ps->width;
+
+ if (ps->scribbles[off] == scribble_val ||
+ ps->mask[off] == mask_val)
+ {
+ *n_samples += 1;
+ }
+ }
+ }
+
+ *samples = g_new (gfloat, *n_samples * 3);
+ i = 0;
+
+ for (y = region->y; y < region->y + region->height; y++)
+ {
+ for (x = region->x; x < region->x + region->width; x++)
+ {
+ gint off = x + y * ps->width;
+
+ if (ps->scribbles[off] == scribble_val ||
+ ps->mask[off] == mask_val)
+ {
+ gint coff = off * 3;
+ (*samples)[i*3] = ps->colors[coff];
+ (*samples)[i*3+1] = ps->colors[coff+1];
+ (*samples)[i*3+2] = ps->colors[coff+2];
+ i++;
+ }
+ }
+ }
+}
+
+static void
+paint_select_init_global_samples (PaintSelect *ps)
+{
+ gint *n_samples;
+ gfloat **samples;
+ gfloat mask_val;
+ GRand *gr;
+ gint i = 0;
+
+ gr = g_rand_new_with_seed (0);
+
+ if (ps->mode == GEGL_PAINT_SELECT_MODE_SUBTRACT)
+ {
+ n_samples = &ps->n_fg_samples;
+ samples = &ps->fg_samples;
+ mask_val = FG_MASK;
+ g_printerr ("global samples are foreground\n");
+ }
+ else
+ {
+ n_samples = &ps->n_bg_samples;
+ samples = &ps->bg_samples;
+ mask_val = BG_MASK;
+ g_printerr ("global samples are background\n");
+ }
+
+ *n_samples = N_GLOBAL_SAMPLES;
+ *samples = g_new (gfloat, N_GLOBAL_SAMPLES * 3);
+
+ while (i < N_GLOBAL_SAMPLES)
+ {
+ gint x = g_rand_int_range (gr, 0, ps->width);
+ gint y = g_rand_int_range (gr, 0, ps->height);
+ gint off = x + y * ps->width;
+
+ if (ps->mask[off] == mask_val)
+ {
+ gint coff = off * 3;
+ (*samples)[i*3] = ps->colors[coff];
+ (*samples)[i*3+1] = ps->colors[coff+1];
+ (*samples)[i*3+2] = ps->colors[coff+2];
+ i++;
+ }
+ }
+}
+
+static void
+paint_select_init_fg_bg_models (PaintSelect *ps)
+{
+ gint n;
+ gfloat increment = 1.f / (gfloat) ps->n_fg_samples;
+
+ memset (&ps->fg_hist, 0.f, sizeof (Histogram));
+
+ for (n = 0; n < ps->n_fg_samples; n++)
+ {
+ gfloat *sample = ps->fg_samples + n * 3;
+ gint b1 = (gint) (sample[0] * (N_BINS - 1));
+ gint b2 = (gint) (sample[1] * (N_BINS - 1));
+ gint b3 = (gint) (sample[2] * (N_BINS - 1));
+ ps->fg_hist.bins[b1][b2][b3] += increment;
+ }
+
+ memset (&ps->bg_hist, 0.f, sizeof (Histogram));
+
+ increment = 1.f / (gfloat) ps->n_bg_samples;
+
+ for (n = 0; n < ps->n_bg_samples; n++)
+ {
+ gfloat *sample = ps->bg_samples + n * 3;
+ gint b1 = (gint) (sample[0] * (N_BINS - 1));
+ gint b2 = (gint) (sample[1] * (N_BINS - 1));
+ gint b3 = (gint) (sample[2] * (N_BINS - 1));
+ ps->bg_hist.bins[b1][b2][b3] += increment;
+ }
+}
+
+/* SOURCE terminal is foreground (selected pixels)
+ * SINK terminal is background (unselected pixels)
+ */
+
+static void
+paint_select_init_graph_nodes_and_tlinks (PaintSelect *ps)
+{
+ gfloat node_mask;
+ ConstraintType boundary;
+ gint x, y;
+
+ if (ps->mode == GEGL_PAINT_SELECT_MODE_ADD)
+ {
+ node_mask = BG_MASK;
+ boundary = HARD_SOURCE;
+ }
+ else
+ {
+ node_mask = FG_MASK;
+ boundary = HARD_SINK;
+ }
+
+ for (y = 0; y < ps->height; y++)
+ {
+ for (x = 0; x < ps->width; x++)
+ {
+ gboolean is_boundary = FALSE;
+ node_id id = -1;
+ gint off = x + y * ps->width;
+
+ /* determine if a node is needed for this pixel */
+
+ if (ps->mask[off] == node_mask)
+ {
+ id = ps->graph->add_node();
+ }
+ else
+ {
+ is_boundary = paint_select_mask_is_interior_boundary (ps, x, y);
+ if (is_boundary)
+ id = ps->graph->add_node();
+ }
+
+ ps->nodes[off] = id;
+
+ /* determine the constraint type and set weights accordingly */
+
+ if (id != -1)
+ {
+ gfloat source_weight;
+ gfloat sink_weight;
+
+ if (is_boundary)
+ {
+ if (boundary == HARD_SOURCE)
+ {
+ source_weight = BIG_CAPACITY;
+ sink_weight = 0.f;
+ }
+ else
+ {
+ source_weight = 0.f;
+ sink_weight = BIG_CAPACITY;
+ }
+ }
+ else if (ps->scribbles[off] == FG_SCRIBBLE)
+ {
+ source_weight = BIG_CAPACITY;
+ sink_weight = 0.f;
+ }
+ else if (ps->scribbles[off] == BG_SCRIBBLE)
+ {
+ source_weight = 0.f;
+ sink_weight = BIG_CAPACITY;
+ }
+ else
+ {
+ gint coff = off * 3;
+ gint b1 = (gint) (ps->colors[coff] * (N_BINS - 1));
+ gint b2 = (gint) (ps->colors[coff+1] * (N_BINS - 1));
+ gint b3 = (gint) (ps->colors[coff+2] * (N_BINS - 1));
+
+ sink_weight = - logf (ps->fg_hist.bins[b1][b2][b3] + 0.0001f);
+ source_weight = - logf (ps->bg_hist.bins[b1][b2][b3] + 0.0001f);
+ }
+
+ if (source_weight < 0)
+ source_weight = 0.f;
+
+ if (sink_weight < 0)
+ sink_weight = 0.f;
+
+ ps->graph->add_tweights (id, source_weight, sink_weight);
+ }
+ }
+ }
+}
+
+static void
+paint_select_init_graph_nlinks (PaintSelect *ps)
+{
+ gint x, y;
+
+ /* horizontal */
+
+ for (y = 0; y < ps->height; y++)
+ {
+ for (x = 0; x < ps->width - 1; x++)
+ {
+ node_id id1 = ps->nodes[x + y * ps->width];
+ node_id id2 = ps->nodes[x + 1 + y * ps->width];
+
+ if (id1 != -1 && id2 != -1)
+ {
+ gint costs_off = x + y * (ps->width - 1);
+ gfloat weight = 60.f * ps->mean_costs / (ps->h_costs[costs_off] + EPSILON);
+ g_assert (weight >= 0.f);
+ ps->graph->add_edge (id1, id2, weight, weight);
+ }
+ }
+ }
+
+ /* vertical */
+
+ for (x = 0; x < ps->width; x++)
+ {
+ for (y = 0; y < ps->height - 1; y++)
+ {
+ node_id id1 = ps->nodes[x + y * ps->width];
+ node_id id2 = ps->nodes[x + (y+1) * ps->width];
+
+ if (id1 != -1 && id2 != -1)
+ {
+ gint costs_off = x + y * ps->width;
+ gfloat weight = 60.f * ps->mean_costs / (ps->v_costs[costs_off] + EPSILON);
+ g_assert (weight >= 0.f);
+ ps->graph->add_edge (id1, id2, weight, weight);
+ }
+ }
+ }
+}
+
+static void
+paint_select_update_mask (PaintSelect *ps)
+{
+ gint i;
+
+ for (i = 0; i < ps->width * ps->height; i++)
+ {
+ node_id id = ps->nodes[i];
+
+ if (id != -1)
+ {
+ if (ps->graph->what_segment(id) == GraphType::SOURCE)
+ ps->mask[i] = 1.f;
+ else
+ ps->mask[i] = 0.f;
+ }
+ }
+}
+
+static void
+prepare (GeglOperation *operation)
+{
+ const Babl *space = gegl_operation_get_source_space (operation, "aux");
+ const Babl *selection = babl_format (SELECTION_FORMAT);
+ const Babl *scribbles = babl_format (SCRIBBLES_FORMAT);
+ const Babl *colors = babl_format_with_space (COLORS_FORMAT, space);
+
+ gegl_operation_set_format (operation, "input", selection);
+ gegl_operation_set_format (operation, "aux", colors);
+ gegl_operation_set_format (operation, "aux2", scribbles);
+ gegl_operation_set_format (operation, "output", selection);
+}
+
+static GeglRectangle
+get_cached_region (GeglOperation *operation,
+ const GeglRectangle *roi)
+{
+ GeglRectangle empty_r = {0, };
+ GeglRectangle *in_r = gegl_operation_source_get_bounding_box (operation,
+ "input");
+ if (in_r)
+ return *in_r;
+
+ return empty_r;
+}
+
+static gboolean
+process (GeglOperation *operation,
+ GeglBuffer *input,
+ GeglBuffer *aux,
+ GeglBuffer *aux2,
+ GeglBuffer *output,
+ const GeglRectangle *result,
+ gint level)
+{
+ GeglProperties *o = GEGL_PROPERTIES (operation);
+ PaintSelect ps;
+ gfloat flow;
+ GeglRectangle region;
+
+ if (! aux || ! aux2)
+ {
+ gegl_buffer_copy (input, NULL, GEGL_ABYSS_NONE, output, NULL);
+ return TRUE;
+ }
+
+ /* memory allocations, pixels fetch */
+
+ paint_select_init_buffers (&ps, input, aux, aux2, o->mode);
+
+ /* find the overlap region where scribble value doesn't match mask value */
+
+ region = paint_select_get_local_region (&ps);
+ g_printerr ("local region: (%d,%d) %d x %d\n",
+ region.x, region.y, region.width, region.height);
+
+ if (! gegl_rectangle_is_empty (®ion) &&
+ ! gegl_rectangle_is_infinite_plane (®ion))
+ {
+ /* retrieve colors samples and init histograms */
+
+ paint_select_init_local_samples (&ps, ®ion);
+ paint_select_init_global_samples (&ps);
+
+ g_printerr ("n fg samples: %d\n", ps.n_fg_samples);
+ g_printerr ("n bg samples: %d\n", ps.n_bg_samples);
+
+ paint_select_init_fg_bg_models (&ps);
+
+ /* init graph */
+
+ paint_select_compute_adjacent_costs (&ps);
+ g_printerr ("mean adjacent costs: %f\n", ps.mean_costs);
+ paint_select_init_graph_nodes_and_tlinks (&ps);
+ paint_select_init_graph_nlinks (&ps);
+
+ /* compute flow and set result */
+
+ flow = ps.graph->maxflow();
+ g_printerr ("flow: %f\n", flow);
+
+ paint_select_update_mask (&ps);
+
+ gegl_buffer_set (output, NULL, 0, babl_format (SELECTION_FORMAT),
+ ps.mask, GEGL_AUTO_ROWSTRIDE);
+ }
+
+ paint_select_free_buffers (&ps);
+
+ return TRUE;
+}
+
+static void
+gegl_op_class_init (GeglOpClass *klass)
+{
+ GeglOperationClass *operation_class;
+ GeglOperationComposer3Class *composer_class;
+
+ operation_class = GEGL_OPERATION_CLASS (klass);
+ composer_class = GEGL_OPERATION_COMPOSER3_CLASS (klass);
+
+ operation_class->get_cached_region = get_cached_region;
+ operation_class->prepare = prepare;
+ operation_class->threaded = FALSE;
+ composer_class->process = process;
+
+ gegl_operation_class_set_keys (operation_class,
+ "name", "gegl:paint-select",
+ "title", _("Paint Select"),
+ NULL);
+}
+
+#endif
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 7024d9c55..986916ff4 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -277,6 +277,7 @@ operations/workshop/demosaic-bimedian.c
operations/workshop/demosaic-simple.c
operations/workshop/ditto.c
operations/workshop/external/gluas.c
+operations/workshop/external/paint-select.cc
operations/workshop/external/lens-correct.c
operations/workshop/external/line-profile.c
operations/workshop/external/spyrograph.c
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]