summaryrefslogtreecommitdiffstats
path: root/app/tools/gimpseamlessclonetool.c
diff options
context:
space:
mode:
Diffstat (limited to 'app/tools/gimpseamlessclonetool.c')
-rw-r--r--app/tools/gimpseamlessclonetool.c845
1 files changed, 845 insertions, 0 deletions
diff --git a/app/tools/gimpseamlessclonetool.c b/app/tools/gimpseamlessclonetool.c
new file mode 100644
index 0000000..79a9780
--- /dev/null
+++ b/app/tools/gimpseamlessclonetool.c
@@ -0,0 +1,845 @@
+/* GIMP - The GNU Image Manipulation Program
+ *
+ * gimpseamlessclonetool.c
+ * Copyright (C) 2011 Barak Itkin <lightningismyname@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gegl-plugin.h> /* gegl_operation_invalidate() */
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "config/gimpguiconfig.h" /* playground */
+
+#include "gegl/gimp-gegl-utils.h"
+
+#include "core/gimp.h"
+#include "core/gimpbuffer.h"
+#include "core/gimpdrawablefilter.h"
+#include "core/gimpimage.h"
+#include "core/gimpitem.h"
+#include "core/gimpprogress.h"
+#include "core/gimpprojection.h"
+#include "core/gimptoolinfo.h"
+
+#include "widgets/gimphelp-ids.h"
+#include "widgets/gimpclipboard.h"
+
+#include "display/gimpdisplay.h"
+#include "display/gimpdisplayshell.h"
+#include "display/gimpdisplayshell-transform.h"
+
+#include "gimpseamlessclonetool.h"
+#include "gimpseamlesscloneoptions.h"
+#include "gimptoolcontrol.h"
+
+#include "gimp-intl.h"
+
+
+#define SC_DEBUG TRUE
+
+#ifdef SC_DEBUG
+#define sc_debug_fstart() g_debug ("%s::start", __FUNCTION__);
+#define sc_debug_fend() g_debug ("%s::end", __FUNCTION__);
+#else
+#define sc_debug_fstart()
+#define sc_debug_fend()
+#endif
+
+#define gimp_seamless_clone_tool_is_in_paste(sc,x0,y0) \
+ ( ((sc)->xoff <= (x0) && (x0) < (sc)->xoff + (sc)->width) \
+ && ((sc)->yoff <= (y0) && (y0) < (sc)->yoff + (sc)->height)) \
+
+#define gimp_seamless_clone_tool_is_in_paste_c(sc,coords) \
+ gimp_seamless_clone_tool_is_in_paste((sc),(coords)->x,(coords)->y)
+
+
+/* init ----------> preprocess
+ * | |
+ * | |
+ * | |
+ * | v
+ * | render(wait, motion)
+ * | / |
+ * | _____/ |
+ * | _____/ |
+ * v v v
+ * quit <---------- commit
+ *
+ * Begin at INIT state
+ *
+ * INIT: Wait for click on canvas
+ * have a paste ? -> PREPROCESS : -> QUIT
+ *
+ * PREPROCESS: Do the preprocessing
+ * -> RENDER
+ *
+ * RENDER: Interact and wait for quit signal
+ * commit quit ? -> COMMIT : -> QUIT
+ *
+ * COMMIT: Commit the changes
+ * -> QUIT
+ *
+ * QUIT: Invoked by sending a ACTION_HALT to the tool_control
+ * Free resources
+ */
+enum
+{
+ SC_STATE_INIT,
+ SC_STATE_PREPROCESS,
+ SC_STATE_RENDER_WAIT,
+ SC_STATE_RENDER_MOTION,
+ SC_STATE_COMMIT,
+ SC_STATE_QUIT
+};
+
+
+static void gimp_seamless_clone_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display);
+static void gimp_seamless_clone_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display);
+
+static void gimp_seamless_clone_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display);
+static void gimp_seamless_clone_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display);
+static gboolean gimp_seamless_clone_tool_key_press (GimpTool *tool,
+ GdkEventKey *kevent,
+ GimpDisplay *display);
+static void gimp_seamless_clone_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display);
+static void gimp_seamless_clone_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_seamless_clone_tool_options_notify (GimpTool *tool,
+ GimpToolOptions *options,
+ const GParamSpec *pspec);
+
+static void gimp_seamless_clone_tool_draw (GimpDrawTool *draw_tool);
+
+static void gimp_seamless_clone_tool_start (GimpSeamlessCloneTool *sc,
+ GimpDisplay *display);
+
+static void gimp_seamless_clone_tool_stop (GimpSeamlessCloneTool *sc,
+ gboolean display_change_only);
+
+static void gimp_seamless_clone_tool_commit (GimpSeamlessCloneTool *sc);
+
+static void gimp_seamless_clone_tool_create_render_node (GimpSeamlessCloneTool *sc);
+static gboolean gimp_seamless_clone_tool_render_node_update (GimpSeamlessCloneTool *sc);
+static void gimp_seamless_clone_tool_create_filter (GimpSeamlessCloneTool *sc,
+ GimpDrawable *drawable);
+static void gimp_seamless_clone_tool_filter_flush (GimpDrawableFilter *filter,
+ GimpTool *tool);
+static void gimp_seamless_clone_tool_filter_update (GimpSeamlessCloneTool *sc);
+
+
+G_DEFINE_TYPE (GimpSeamlessCloneTool, gimp_seamless_clone_tool,
+ GIMP_TYPE_DRAW_TOOL)
+
+#define parent_class gimp_seamless_clone_tool_parent_class
+
+
+void
+gimp_seamless_clone_tool_register (GimpToolRegisterCallback callback,
+ gpointer data)
+{
+ /* we should not know that "data" is a Gimp*, but what the heck this
+ * is experimental playground stuff
+ */
+ if (GIMP_GUI_CONFIG (GIMP (data)->config)->playground_seamless_clone_tool)
+ (* callback) (GIMP_TYPE_SEAMLESS_CLONE_TOOL,
+ GIMP_TYPE_SEAMLESS_CLONE_OPTIONS,
+ gimp_seamless_clone_options_gui,
+ 0,
+ "gimp-seamless-clone-tool",
+ _("Seamless Clone"),
+ _("Seamless Clone: Seamlessly paste one image into another"),
+ N_("_Seamless Clone"), NULL,
+ NULL, GIMP_HELP_TOOL_SEAMLESS_CLONE,
+ GIMP_ICON_TOOL_SEAMLESS_CLONE,
+ data);
+}
+
+static void
+gimp_seamless_clone_tool_class_init (GimpSeamlessCloneToolClass *klass)
+{
+ GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
+ GimpDrawToolClass *draw_tool_class = GIMP_DRAW_TOOL_CLASS (klass);
+
+ tool_class->control = gimp_seamless_clone_tool_control;
+ tool_class->button_press = gimp_seamless_clone_tool_button_press;
+ tool_class->button_release = gimp_seamless_clone_tool_button_release;
+ tool_class->motion = gimp_seamless_clone_tool_motion;
+ tool_class->key_press = gimp_seamless_clone_tool_key_press;
+ tool_class->oper_update = gimp_seamless_clone_tool_oper_update;
+ tool_class->cursor_update = gimp_seamless_clone_tool_cursor_update;
+ tool_class->options_notify = gimp_seamless_clone_tool_options_notify;
+
+ draw_tool_class->draw = gimp_seamless_clone_tool_draw;
+}
+
+static void
+gimp_seamless_clone_tool_init (GimpSeamlessCloneTool *self)
+{
+ GimpTool *tool = GIMP_TOOL (self);
+
+ gimp_tool_control_set_dirty_mask (tool->control,
+ GIMP_DIRTY_IMAGE |
+ GIMP_DIRTY_IMAGE_STRUCTURE |
+ GIMP_DIRTY_DRAWABLE |
+ GIMP_DIRTY_SELECTION);
+
+ gimp_tool_control_set_tool_cursor (tool->control,
+ GIMP_TOOL_CURSOR_MOVE);
+
+ self->tool_state = SC_STATE_INIT;
+}
+
+static void
+gimp_seamless_clone_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display)
+{
+ GimpSeamlessCloneTool *sc = GIMP_SEAMLESS_CLONE_TOOL (tool);
+
+ switch (action)
+ {
+ case GIMP_TOOL_ACTION_PAUSE:
+ case GIMP_TOOL_ACTION_RESUME:
+ break;
+
+ case GIMP_TOOL_ACTION_HALT:
+ if (tool->display)
+ gimp_seamless_clone_tool_stop (sc, FALSE);
+
+ /* TODO: If we have any tool options that should be reset, here is
+ * a good place to do so.
+ */
+ break;
+
+ case GIMP_TOOL_ACTION_COMMIT:
+ gimp_seamless_clone_tool_commit (sc);
+ break;
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->control (tool, action, display);
+}
+
+/**
+ * gimp_seamless_clone_tool_start:
+ * @sc: The GimpSeamlessCloneTool to initialize for usage on the given
+ * display
+ * @display: The display to initialize the tool for
+ *
+ * A utility function to initialize a tool for working on a given
+ * display. At the beginning of each function, we can check if the event's
+ * display is the same as the tool's one, and if not call this. This is
+ * not required by the gimptool interface or anything like that, but
+ * this is a convenient way to do all the initialization work in one
+ * place, and this is how the base class (GimpDrawTool) does that
+ */
+static void
+gimp_seamless_clone_tool_start (GimpSeamlessCloneTool *sc,
+ GimpDisplay *display)
+{
+ GimpTool *tool = GIMP_TOOL (sc);
+ GimpImage *image = gimp_display_get_image (display);
+ GimpDrawable *drawable = gimp_image_get_active_drawable (image);
+
+ /* First handle the paste - we need to make sure we have one in order
+ * to do anything else.
+ */
+ if (sc->paste == NULL)
+ {
+ GimpBuffer *buffer = gimp_clipboard_get_buffer (tool->tool_info->gimp);
+
+ if (! buffer)
+ {
+ gimp_tool_push_status (tool, display,
+ "%s",
+ _("There is no image data in the clipboard to paste."));
+ return;
+ }
+
+ sc->paste = gimp_gegl_buffer_dup (gimp_buffer_get_buffer (buffer));
+ g_object_unref (buffer);
+
+ sc->width = gegl_buffer_get_width (sc->paste);
+ sc->height = gegl_buffer_get_height (sc->paste);
+ }
+
+ /* Free resources which are relevant only for the previous display */
+ gimp_seamless_clone_tool_stop (sc, TRUE);
+
+ tool->display = display;
+
+ gimp_seamless_clone_tool_create_filter (sc, drawable);
+
+ gimp_draw_tool_start (GIMP_DRAW_TOOL (sc), display);
+
+ sc->tool_state = SC_STATE_RENDER_WAIT;
+}
+
+
+/**
+ * gimp_seamless_clone_tool_stop:
+ * @sc: The seamless clone tool whose resources should be freed
+ * @display_change_only: Mark that the only reason for this call was a
+ * switch of the working display.
+ *
+ * This function frees any resources associated with the seamless clone
+ * tool, including caches, gegl graphs, and anything the tool created.
+ * Afterwards, it initializes all the relevant pointers to some initial
+ * value (usually NULL) like the init function does.
+ *
+ * Note that for seamless cloning, no change needs to be done when
+ * switching to a different display, except for clearing the image map.
+ * So for that, we provide a boolean parameter to specify that the only
+ * change was one of the display
+ */
+static void
+gimp_seamless_clone_tool_stop (GimpSeamlessCloneTool *sc,
+ gboolean display_change_only)
+{
+ /* See if we actually have any reason to stop */
+ if (sc->tool_state == SC_STATE_INIT)
+ return;
+
+ if (! display_change_only)
+ {
+ sc->tool_state = SC_STATE_INIT;
+
+ g_clear_object (&sc->paste);
+ g_clear_object (&sc->render_node);
+ sc->sc_node = NULL;
+ }
+
+ /* This should always happen, even when we just switch a display */
+ if (sc->filter)
+ {
+ gimp_drawable_filter_abort (sc->filter);
+ g_clear_object (&sc->filter);
+
+ if (GIMP_TOOL (sc)->display)
+ gimp_image_flush (gimp_display_get_image (GIMP_TOOL (sc)->display));
+ }
+
+ gimp_draw_tool_stop (GIMP_DRAW_TOOL (sc));
+}
+
+static void
+gimp_seamless_clone_tool_commit (GimpSeamlessCloneTool *sc)
+{
+ GimpTool *tool = GIMP_TOOL (sc);
+
+ if (sc->filter)
+ {
+ gimp_tool_control_push_preserve (tool->control, TRUE);
+
+ gimp_drawable_filter_commit (sc->filter, GIMP_PROGRESS (tool), FALSE);
+ g_clear_object (&sc->filter);
+
+ gimp_tool_control_pop_preserve (tool->control);
+
+ gimp_image_flush (gimp_display_get_image (tool->display));
+ }
+}
+
+static void
+gimp_seamless_clone_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display)
+{
+ GimpSeamlessCloneTool *sc = GIMP_SEAMLESS_CLONE_TOOL (tool);
+
+ if (display != tool->display)
+ {
+ gimp_seamless_clone_tool_start (sc, display);
+
+ /* Center the paste on the mouse */
+ sc->xoff = (gint) coords->x - sc->width / 2;
+ sc->yoff = (gint) coords->y - sc->height / 2;
+ }
+
+ if (sc->tool_state == SC_STATE_RENDER_WAIT &&
+ gimp_seamless_clone_tool_is_in_paste_c (sc, coords))
+ {
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (sc));
+
+ /* Record previous location, in case the user cancels the
+ * movement
+ */
+ sc->xoff_p = sc->xoff;
+ sc->yoff_p = sc->yoff;
+
+ /* Record the mouse location, so that the dragging offset can be
+ * calculated
+ */
+ sc->xclick = coords->x;
+ sc->yclick = coords->y;
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
+
+ if (gimp_seamless_clone_tool_render_node_update (sc))
+ {
+ gimp_seamless_clone_tool_filter_update (sc);
+ }
+
+ sc->tool_state = SC_STATE_RENDER_MOTION;
+
+ /* In order to receive motion events from the current click, we must
+ * activate the tool control
+ */
+ gimp_tool_control_activate (tool->control);
+ }
+}
+
+void
+gimp_seamless_clone_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display)
+{
+ GimpSeamlessCloneTool *sc = GIMP_SEAMLESS_CLONE_TOOL (tool);
+
+ gimp_tool_control_halt (tool->control);
+
+ /* There is nothing to do, unless we were actually moving a paste */
+ if (sc->tool_state == SC_STATE_RENDER_MOTION)
+ {
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (sc));
+
+ if (release_type == GIMP_BUTTON_RELEASE_CANCEL)
+ {
+ sc->xoff = sc->xoff_p;
+ sc->yoff = sc->yoff_p;
+ }
+ else
+ {
+ sc->xoff = sc->xoff_p + (gint) (coords->x - sc->xclick);
+ sc->yoff = sc->yoff_p + (gint) (coords->y - sc->yclick);
+ }
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
+
+ if (gimp_seamless_clone_tool_render_node_update (sc))
+ {
+ gimp_seamless_clone_tool_filter_update (sc);
+ }
+
+ sc->tool_state = SC_STATE_RENDER_WAIT;
+ }
+}
+
+static void
+gimp_seamless_clone_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpSeamlessCloneTool *sc = GIMP_SEAMLESS_CLONE_TOOL (tool);
+
+ if (sc->tool_state == SC_STATE_RENDER_MOTION)
+ {
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (sc));
+
+ sc->xoff = sc->xoff_p + (gint) (coords->x - sc->xclick);
+ sc->yoff = sc->yoff_p + (gint) (coords->y - sc->yclick);
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
+
+ if (gimp_seamless_clone_tool_render_node_update (sc))
+ {
+ gimp_seamless_clone_tool_filter_update (sc);
+ }
+ }
+}
+
+static gboolean
+gimp_seamless_clone_tool_key_press (GimpTool *tool,
+ GdkEventKey *kevent,
+ GimpDisplay *display)
+{
+ GimpSeamlessCloneTool *sct = GIMP_SEAMLESS_CLONE_TOOL (tool);
+
+ if (sct->tool_state == SC_STATE_RENDER_MOTION ||
+ sct->tool_state == SC_STATE_RENDER_WAIT)
+ {
+ switch (kevent->keyval)
+ {
+ case GDK_KEY_Return:
+ case GDK_KEY_KP_Enter:
+ case GDK_KEY_ISO_Enter:
+ gimp_tool_control_set_preserve (tool->control, TRUE);
+
+ /* TODO: there may be issues with committing the image map
+ * result after some changes were made and the preview
+ * was scrolled. We can fix these by either invalidating
+ * the area which is a union of the previous paste
+ * rectangle each time (in the update function) or by
+ * invalidating and re-rendering all now (expensive and
+ * perhaps useless */
+ gimp_drawable_filter_commit (sct->filter, GIMP_PROGRESS (tool), FALSE);
+ g_clear_object (&sct->filter);
+
+ gimp_tool_control_set_preserve (tool->control, FALSE);
+
+ gimp_image_flush (gimp_display_get_image (display));
+
+ gimp_seamless_clone_tool_control (tool, GIMP_TOOL_ACTION_HALT,
+ display);
+ return TRUE;
+
+ case GDK_KEY_Escape:
+ gimp_seamless_clone_tool_control (tool, GIMP_TOOL_ACTION_HALT,
+ display);
+ return TRUE;
+
+ default:
+ break;
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+gimp_seamless_clone_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display)
+{
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
+
+ /* TODO: Modify data here */
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
+}
+
+/* Mouse cursor policy:
+ * - Always use the move cursor
+ * - While dragging the paste, use a move modified
+ * - Else, While hovering above it, display no modifier
+ * - Else, display a "bad" modifier
+ */
+static void
+gimp_seamless_clone_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpSeamlessCloneTool *sc = GIMP_SEAMLESS_CLONE_TOOL (tool);
+ GimpCursorModifier modifier = GIMP_CURSOR_MODIFIER_BAD;
+
+ /* Only update if the tool is actually active on some display */
+ if (tool->display)
+ {
+ if (sc->tool_state == SC_STATE_RENDER_MOTION)
+ {
+ modifier = GIMP_CURSOR_MODIFIER_MOVE;
+ }
+ else if (sc->tool_state == SC_STATE_RENDER_WAIT &&
+ gimp_seamless_clone_tool_is_in_paste_c (sc, coords))
+ {
+ modifier = GIMP_CURSOR_MODIFIER_NONE;
+ }
+
+ gimp_tool_control_set_cursor_modifier (tool->control, modifier);
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->cursor_update (tool, coords, state, display);
+}
+
+static void
+gimp_seamless_clone_tool_options_notify (GimpTool *tool,
+ GimpToolOptions *options,
+ const GParamSpec *pspec)
+{
+ GimpSeamlessCloneTool *sc = GIMP_SEAMLESS_CLONE_TOOL (tool);
+
+ GIMP_TOOL_CLASS (parent_class)->options_notify (tool, options, pspec);
+
+ if (! tool->display)
+ return;
+
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
+
+ if (! strcmp (pspec->name, "max-refine-scale"))
+ {
+ if (gimp_seamless_clone_tool_render_node_update (sc))
+ {
+ gimp_seamless_clone_tool_filter_update (sc);
+ }
+ }
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
+}
+
+static void
+gimp_seamless_clone_tool_draw (GimpDrawTool *draw_tool)
+{
+ GimpSeamlessCloneTool *sc = GIMP_SEAMLESS_CLONE_TOOL (draw_tool);
+
+ if (sc->tool_state == SC_STATE_RENDER_WAIT ||
+ sc->tool_state == SC_STATE_RENDER_MOTION)
+ {
+ gimp_draw_tool_add_rectangle (draw_tool, FALSE,
+ sc->xoff, sc->yoff, sc->width, sc->height);
+ }
+}
+
+/**
+ * gimp_seamless_clone_tool_create_render_node:
+ * @sc: The GimpSeamlessCloneTool to initialize
+ *
+ * This function creates a Gegl node graph of the composition which is
+ * needed to render the drawable. The graph should have an "input" pad
+ * which will receive the drawable on which the preview is applied, and
+ * it should also have an "output" pad to which the final result will be
+ * rendered
+ */
+static void
+gimp_seamless_clone_tool_create_render_node (GimpSeamlessCloneTool *sc)
+{
+ /* Here is a textual description of the graph we are going to create:
+ *
+ * <input> <- drawable
+ * +--+--------------------------+
+ * | |output |
+ * | | |
+ * | | <buffer-source> <- paste |
+ * | | |output |
+ * | | | |
+ * | |input |aux |
+ * |<seamless-paste-render> |
+ * | |output |
+ * | | |
+ * | |input |
+ * +----+------------------------+
+ * <output>
+ */
+ GimpSeamlessCloneOptions *options = GIMP_SEAMLESS_CLONE_TOOL_GET_OPTIONS (sc);
+ GeglNode *node;
+ GeglNode *op, *paste, *overlay;
+ GeglNode *input, *output;
+
+ node = gegl_node_new ();
+
+ input = gegl_node_get_input_proxy (node, "input");
+ output = gegl_node_get_output_proxy (node, "output");
+
+ paste = gegl_node_new_child (node,
+ "operation", "gegl:buffer-source",
+ "buffer", sc->paste,
+ NULL);
+
+ op = gegl_node_new_child (node,
+ "operation", "gegl:seamless-clone",
+ "max-refine-scale", options->max_refine_scale,
+ NULL);
+
+ overlay = gegl_node_new_child (node,
+ "operation", "svg:dst-over",
+ NULL);
+
+ gegl_node_connect_to (input, "output",
+ op, "input");
+
+ gegl_node_connect_to (paste, "output",
+ op, "aux");
+
+ gegl_node_connect_to (op, "output",
+ overlay, "input");
+
+ gegl_node_connect_to (input, "output",
+ overlay, "aux");
+
+ gegl_node_connect_to (overlay, "output",
+ output, "input");
+
+ sc->render_node = node;
+ sc->sc_node = op;
+}
+
+/* gimp_seamless_clone_tool_render_node_update:
+ * sc: the Seamless Clone tool whose render has to be updated.
+ *
+ * Returns: TRUE if any property changed.
+ */
+static gboolean
+gimp_seamless_clone_tool_render_node_update (GimpSeamlessCloneTool *sc)
+{
+ static gint rendered__max_refine_scale = -1;
+ static gint rendered_xoff = G_MAXINT;
+ static gint rendered_yoff = G_MAXINT;
+
+ GimpSeamlessCloneOptions *options = GIMP_SEAMLESS_CLONE_TOOL_GET_OPTIONS (sc);
+ GimpDrawable *bg = GIMP_TOOL (sc)->drawable;
+ gint off_x, off_y;
+
+ /* All properties stay the same. No need to update. */
+ if (rendered__max_refine_scale == options->max_refine_scale &&
+ rendered_xoff == sc->xoff &&
+ rendered_yoff == sc->yoff)
+ return FALSE;
+
+ gimp_item_get_offset (GIMP_ITEM (bg), &off_x, &off_y);
+
+ gegl_node_set (sc->sc_node,
+ "xoff", (gint) sc->xoff - off_x,
+ "yoff", (gint) sc->yoff - off_y,
+ "max-refine-scale", (gint) options->max_refine_scale,
+ NULL);
+
+ rendered__max_refine_scale = options->max_refine_scale;
+ rendered_xoff = sc->xoff;
+ rendered_yoff = sc->yoff;
+
+ return TRUE;
+}
+
+static void
+gimp_seamless_clone_tool_create_filter (GimpSeamlessCloneTool *sc,
+ GimpDrawable *drawable)
+{
+ if (! sc->render_node)
+ gimp_seamless_clone_tool_create_render_node (sc);
+
+ sc->filter = gimp_drawable_filter_new (drawable,
+ _("Seamless Clone"),
+ sc->render_node,
+ GIMP_ICON_TOOL_SEAMLESS_CLONE);
+
+ gimp_drawable_filter_set_region (sc->filter, GIMP_FILTER_REGION_DRAWABLE);
+
+ g_signal_connect (sc->filter, "flush",
+ G_CALLBACK (gimp_seamless_clone_tool_filter_flush),
+ sc);
+}
+
+static void
+gimp_seamless_clone_tool_filter_flush (GimpDrawableFilter *filter,
+ GimpTool *tool)
+{
+ GimpImage *image = gimp_display_get_image (tool->display);
+
+ gimp_projection_flush (gimp_image_get_projection (image));
+}
+
+static void
+gimp_seamless_clone_tool_filter_update (GimpSeamlessCloneTool *sc)
+{
+ GimpTool *tool = GIMP_TOOL (sc);
+ GimpDisplayShell *shell = gimp_display_get_shell (tool->display);
+ GimpItem *item = GIMP_ITEM (tool->drawable);
+ gint x, y;
+ gint w, h;
+ gint off_x, off_y;
+ GeglRectangle visible;
+ GeglOperation *op = NULL;
+
+ GimpProgress *progress;
+ GeglNode *output;
+ GeglProcessor *processor;
+ gdouble value;
+
+ progress = gimp_progress_start (GIMP_PROGRESS (sc), FALSE,
+ _("Cloning the foreground object"));
+
+ /* Find out at which x,y is the top left corner of the currently
+ * displayed part */
+ gimp_display_shell_untransform_viewport (shell, ! shell->show_all,
+ &x, &y, &w, &h);
+
+ /* Find out where is our drawable positioned */
+ gimp_item_get_offset (item, &off_x, &off_y);
+
+ /* Create a rectangle from the intersection of the currently displayed
+ * part with the drawable */
+ gimp_rectangle_intersect (x, y, w, h,
+ off_x,
+ off_y,
+ gimp_item_get_width (item),
+ gimp_item_get_height (item),
+ &visible.x,
+ &visible.y,
+ &visible.width,
+ &visible.height);
+
+ /* Since the filter_apply function receives a rectangle describing
+ * where it should update the preview, and since that rectangle should
+ * be relative to the drawable's location, we now offset back by the
+ * drawable's offsetts. */
+ visible.x -= off_x;
+ visible.y -= off_y;
+
+ g_object_get (sc->sc_node, "gegl-operation", &op, NULL);
+ /* If any cache of the visible area was present, clear it!
+ * We need to clear the cache in the sc_node, since that is
+ * where the previous paste was located
+ */
+ gegl_operation_invalidate (op, &visible, TRUE);
+ g_object_unref (op);
+
+ /* Now update the image map and show this area */
+ gimp_drawable_filter_apply (sc->filter, NULL);
+
+ /* Show update progress. */
+ output = gegl_node_get_output_proxy (sc->render_node, "output");
+ processor = gegl_node_new_processor (output, NULL);
+
+ while (gegl_processor_work (processor, &value))
+ {
+ if (progress)
+ gimp_progress_set_value (progress, value);
+ }
+
+ if (progress)
+ gimp_progress_end (progress);
+
+ g_object_unref (processor);
+}