[gimp] Bug 312780 - Add undo to foreground selection tool



commit 6ec272fde7f978369b11fbd8767b669f8dac716f
Author: Michael Natterer <mitch gimp org>
Date:   Sat Apr 19 00:34:21 2014 +0200

    Bug 312780 - Add undo to foreground selection tool
    
    Implement undoing strokes by keeping copies of the changed parts
    of the trimap around in internal undo/redo stacks.

 app/tools/gimpforegroundselecttool.c |  265 +++++++++++++++++++++++++++++++++-
 app/tools/gimpforegroundselecttool.h |    3 +
 2 files changed, 260 insertions(+), 8 deletions(-)
---
diff --git a/app/tools/gimpforegroundselecttool.c b/app/tools/gimpforegroundselecttool.c
index 2152ba1..1c6dd77 100644
--- a/app/tools/gimpforegroundselecttool.c
+++ b/app/tools/gimpforegroundselecttool.c
@@ -27,6 +27,7 @@
 #include <gdk/gdkkeysyms.h>
 
 #include "libgimpmath/gimpmath.h"
+#include "libgimpbase/gimpbase.h"
 #include "libgimpcolor/gimpcolor.h"
 #include "libgimpwidgets/gimpwidgets.h"
 
@@ -58,13 +59,16 @@
 #include "gimp-intl.h"
 
 
-typedef struct
+typedef struct _StrokeUndo StrokeUndo;
+
+struct _StrokeUndo
 {
-  gint         width;
-  gboolean     background;
-  gint         num_points;
-  GimpVector2 *points;
-} FgSelectStroke;
+  GeglBuffer          *saved_trimap;
+  gint                 trimap_x;
+  gint                 trimap_y;
+  GimpMattingDrawMode  draw_mode;
+  gint                 stroke_width;
+};
 
 
 static void   gimp_foreground_select_tool_finalize       (GObject          *object);
@@ -115,6 +119,16 @@ static void   gimp_foreground_select_tool_cursor_update  (GimpTool         *tool
                                                           const GimpCoords *coords,
                                                           GdkModifierType   state,
                                                           GimpDisplay      *display);
+static const gchar * gimp_foreground_select_tool_get_undo_desc
+                                                         (GimpTool         *tool,
+                                                          GimpDisplay      *display);
+static const gchar * gimp_foreground_select_tool_get_redo_desc
+                                                         (GimpTool         *tool,
+                                                          GimpDisplay      *display);
+static gboolean   gimp_foreground_select_tool_undo       (GimpTool         *tool,
+                                                          GimpDisplay      *display);
+static gboolean   gimp_foreground_select_tool_redo       (GimpTool         *tool,
+                                                          GimpDisplay      *display);
 static void   gimp_foreground_select_tool_options_notify (GimpTool         *tool,
                                                           GimpToolOptions  *options,
                                                           const GParamSpec *pspec);
@@ -145,6 +159,14 @@ static void   gimp_foreground_select_tool_preview_toggled(GtkToggleButton
 
 static void   gimp_foreground_select_tool_update_gui     (GimpForegroundSelectTool *fg_select);
 
+static StrokeUndo * gimp_foreground_select_undo_new      (GeglBuffer               *trimap,
+                                                          GArray                   *stroke,
+                                                          GimpMattingDrawMode       draw_mode,
+                                                          gint                      stroke_width);
+static void         gimp_foreground_select_undo_pop      (StrokeUndo               *undo,
+                                                          GeglBuffer               *trimap);
+static void         gimp_foreground_select_undo_free     (StrokeUndo               *undo);
+
 
 G_DEFINE_TYPE (GimpForegroundSelectTool, gimp_foreground_select_tool,
                GIMP_TYPE_FREE_SELECT_TOOL)
@@ -191,6 +213,10 @@ gimp_foreground_select_tool_class_init (GimpForegroundSelectToolClass *klass)
   tool_class->active_modifier_key = gimp_foreground_select_tool_active_modifier_key;
   tool_class->oper_update         = gimp_foreground_select_tool_oper_update;
   tool_class->cursor_update       = gimp_foreground_select_tool_cursor_update;
+  tool_class->get_undo_desc       = gimp_foreground_select_tool_get_undo_desc;
+  tool_class->get_redo_desc       = gimp_foreground_select_tool_get_redo_desc;
+  tool_class->undo                = gimp_foreground_select_tool_undo;
+  tool_class->redo                = gimp_foreground_select_tool_redo;
   tool_class->options_notify      = gimp_foreground_select_tool_options_notify;
 
   draw_tool_class->draw           = gimp_foreground_select_tool_draw;
@@ -622,6 +648,100 @@ gimp_foreground_select_tool_cursor_update (GimpTool         *tool,
   GIMP_TOOL_CLASS (parent_class)->cursor_update (tool, coords, state, display);
 }
 
+static const gchar *
+gimp_foreground_select_tool_get_undo_desc (GimpTool    *tool,
+                                           GimpDisplay *display)
+{
+  GimpForegroundSelectTool *fg_select = GIMP_FOREGROUND_SELECT_TOOL (tool);
+
+  if (fg_select->undo_stack)
+    {
+      StrokeUndo  *undo = fg_select->undo_stack->data;
+      const gchar *desc;
+
+      if (gimp_enum_get_value (GIMP_TYPE_MATTING_DRAW_MODE, undo->draw_mode,
+                               NULL, NULL, &desc, NULL))
+        {
+          return desc;
+        }
+    }
+
+  return NULL;
+}
+
+static const gchar *
+gimp_foreground_select_tool_get_redo_desc (GimpTool    *tool,
+                                           GimpDisplay *display)
+{
+  GimpForegroundSelectTool *fg_select = GIMP_FOREGROUND_SELECT_TOOL (tool);
+
+  if (fg_select->redo_stack)
+    {
+      StrokeUndo  *undo = fg_select->redo_stack->data;
+      const gchar *desc;
+
+      if (gimp_enum_get_value (GIMP_TYPE_MATTING_DRAW_MODE, undo->draw_mode,
+                               NULL, NULL, &desc, NULL))
+        {
+          return desc;
+        }
+    }
+
+  return NULL;
+}
+
+static gboolean
+gimp_foreground_select_tool_undo (GimpTool    *tool,
+                                  GimpDisplay *display)
+{
+  GimpForegroundSelectTool *fg_select = GIMP_FOREGROUND_SELECT_TOOL (tool);
+
+  if (fg_select->undo_stack)
+    {
+      StrokeUndo *undo = fg_select->undo_stack->data;
+
+      gimp_foreground_select_undo_pop (undo, fg_select->trimap);
+
+      fg_select->undo_stack = g_list_remove (fg_select->undo_stack, undo);
+      fg_select->redo_stack = g_list_prepend (fg_select->redo_stack, undo);
+
+      if (fg_select->state == MATTING_STATE_PREVIEW_MASK)
+        gimp_foreground_select_tool_preview (fg_select, display);
+      else
+        gimp_foreground_select_tool_set_trimap (fg_select, display);
+
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static gboolean
+gimp_foreground_select_tool_redo (GimpTool    *tool,
+                                  GimpDisplay *display)
+{
+  GimpForegroundSelectTool *fg_select = GIMP_FOREGROUND_SELECT_TOOL (tool);
+
+  if (fg_select->redo_stack)
+    {
+      StrokeUndo *undo = fg_select->redo_stack->data;
+
+      gimp_foreground_select_undo_pop (undo, fg_select->trimap);
+
+      fg_select->redo_stack = g_list_remove (fg_select->redo_stack, undo);
+      fg_select->undo_stack = g_list_prepend (fg_select->undo_stack, undo);
+
+      if (fg_select->state == MATTING_STATE_PREVIEW_MASK)
+        gimp_foreground_select_tool_preview (fg_select, display);
+      else
+        gimp_foreground_select_tool_set_trimap (fg_select, display);
+
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
 static void
 gimp_foreground_select_tool_options_notify (GimpTool         *tool,
                                             GimpToolOptions  *options,
@@ -780,6 +900,20 @@ gimp_foreground_select_tool_halt (GimpForegroundSelectTool *fg_select)
       fg_select->mask = NULL;
     }
 
+  if (fg_select->undo_stack)
+    {
+      g_list_free_full (fg_select->undo_stack,
+                        (GDestroyNotify) gimp_foreground_select_undo_free);
+      fg_select->undo_stack = NULL;
+    }
+
+  if (fg_select->redo_stack)
+    {
+      g_list_free_full (fg_select->redo_stack,
+                        (GDestroyNotify) gimp_foreground_select_undo_free);
+      fg_select->redo_stack = NULL;
+    }
+
   if (tool->display)
     gimp_display_shell_set_mask (gimp_display_get_shell (tool->display),
                                  NULL, NULL);
@@ -793,6 +927,10 @@ gimp_foreground_select_tool_halt (GimpForegroundSelectTool *fg_select)
 
   fg_select->state = MATTING_STATE_FREE_SELECT;
 
+  /*  update the undo actions / menu items  */
+  if (tool->display)
+    gimp_image_flush (gimp_display_get_image (tool->display));
+
   tool->display  = NULL;
   tool->drawable = NULL;
 
@@ -981,6 +1119,7 @@ gimp_foreground_select_tool_stroke_paint (GimpForegroundSelectTool *fg_select)
 {
   GimpForegroundSelectOptions *options;
   GimpScanConvert             *scan_convert;
+  StrokeUndo                  *undo;
   gint                         width;
   gdouble                      opacity;
 
@@ -988,6 +1127,21 @@ gimp_foreground_select_tool_stroke_paint (GimpForegroundSelectTool *fg_select)
 
   g_return_if_fail (fg_select->stroke != NULL);
 
+  width = ROUND ((gdouble) options->stroke_width);
+
+  if (fg_select->redo_stack)
+    {
+      g_list_free_full (fg_select->redo_stack,
+                        (GDestroyNotify) gimp_foreground_select_undo_free);
+      fg_select->redo_stack = NULL;
+    }
+
+  undo = gimp_foreground_select_undo_new (fg_select->trimap,
+                                          fg_select->stroke,
+                                          options->draw_mode, width);
+
+  fg_select->undo_stack = g_list_prepend (fg_select->undo_stack, undo);
+
   scan_convert = gimp_scan_convert_new ();
 
   if (fg_select->stroke->len == 1)
@@ -1009,8 +1163,6 @@ gimp_foreground_select_tool_stroke_paint (GimpForegroundSelectTool *fg_select)
                                       FALSE);
     }
 
-  width = ROUND ((gdouble) options->stroke_width);
-
   gimp_scan_convert_stroke (scan_convert,
                             width,
                             GIMP_JOIN_ROUND, GIMP_CAP_ROUND, 10.0,
@@ -1031,6 +1183,9 @@ gimp_foreground_select_tool_stroke_paint (GimpForegroundSelectTool *fg_select)
 
   g_array_free (fg_select->stroke, TRUE);
   fg_select->stroke = NULL;
+
+  /*  update the undo actions / menu items  */
+  gimp_image_flush (gimp_display_get_image (GIMP_TOOL (fg_select)->display));
 }
 
 static void
@@ -1098,3 +1253,97 @@ gimp_foreground_select_tool_update_gui (GimpForegroundSelectTool *fg_select)
                                         TRUE);
   gtk_widget_set_sensitive (fg_select->preview_toggle, TRUE);
 }
+
+static StrokeUndo *
+gimp_foreground_select_undo_new (GeglBuffer          *trimap,
+                                 GArray              *stroke,
+                                 GimpMattingDrawMode draw_mode,
+                                 gint                stroke_width)
+
+{
+  StrokeUndo *undo = g_slice_new0 (StrokeUndo);
+  gint        x1, y1, x2, y2;
+  gint        width, height;
+  gint        i;
+
+  x1 = G_MAXINT;
+  y1 = G_MAXINT;
+  x2 = G_MININT;
+  y2 = G_MININT;
+
+  for (i = 0; i < stroke->len; i++)
+    {
+      GimpVector2 *point = &g_array_index (stroke, GimpVector2, i);
+
+      x1 = MIN (x1, floor (point->x));
+      y1 = MIN (y1, floor (point->y));
+      x2 = MAX (x2, ceil (point->x));
+      y2 = MAX (y2, ceil (point->y));
+    }
+
+  x1 -= stroke_width;
+  y1 -= stroke_width;
+  x2 += stroke_width;
+  y2 += stroke_width;
+
+  x1 = MAX (x1, 0);
+  y1 = MAX (y1, 0);
+  x2 = MIN (x2, gegl_buffer_get_width  (trimap));
+  y2 = MIN (y2, gegl_buffer_get_height (trimap));
+
+  width  = x2 - x1;
+  height = y2 - y1;
+
+  undo->saved_trimap = gegl_buffer_new (GEGL_RECTANGLE (0, 0, width, height),
+                                        gegl_buffer_get_format (trimap));
+
+  gegl_buffer_copy (trimap,             GEGL_RECTANGLE (x1, y1, width, height),
+                    undo->saved_trimap, GEGL_RECTANGLE (0, 0, width, height));
+
+  undo->trimap_x = x1;
+  undo->trimap_y = y1;
+
+  undo->draw_mode    = draw_mode;
+  undo->stroke_width = stroke_width;
+
+  return undo;
+}
+
+static void
+gimp_foreground_select_undo_pop (StrokeUndo *undo,
+                                 GeglBuffer *trimap)
+{
+  GeglBuffer *buffer;
+  gint        width, height;
+
+  buffer = gegl_buffer_dup (undo->saved_trimap);
+
+  width  = gegl_buffer_get_width  (buffer);
+  height = gegl_buffer_get_height (buffer);
+
+  gegl_buffer_copy (trimap,
+                    GEGL_RECTANGLE (undo->trimap_x, undo->trimap_y,
+                                    width, height),
+                    undo->saved_trimap,
+                    GEGL_RECTANGLE (0, 0, width, height));
+
+  gegl_buffer_copy (buffer,
+                    GEGL_RECTANGLE (0, 0, width, height),
+                    trimap,
+                    GEGL_RECTANGLE (undo->trimap_x, undo->trimap_y,
+                                    width, height));
+
+  g_object_unref (buffer);
+}
+
+static void
+gimp_foreground_select_undo_free (StrokeUndo *undo)
+{
+  if (undo->saved_trimap)
+    {
+      g_object_unref (undo->saved_trimap);
+      undo->saved_trimap = NULL;
+    }
+
+  g_slice_free (StrokeUndo, undo);
+}
diff --git a/app/tools/gimpforegroundselecttool.h b/app/tools/gimpforegroundselecttool.h
index b840408..ebc61f0 100644
--- a/app/tools/gimpforegroundselecttool.h
+++ b/app/tools/gimpforegroundselecttool.h
@@ -54,6 +54,9 @@ struct _GimpForegroundSelectTool
   GeglBuffer         *trimap;
   GeglBuffer         *mask;
 
+  GList              *undo_stack;
+  GList              *redo_stack;
+
   GimpToolGui        *gui;
   GtkWidget          *preview_toggle;
 };


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