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.
This commit is contained in:
Michael Natterer 2014-04-19 00:34:21 +02:00
parent df8a148db0
commit 6ec272fde7
2 changed files with 260 additions and 8 deletions

View File

@ -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);
}

View File

@ -54,6 +54,9 @@ struct _GimpForegroundSelectTool
GeglBuffer *trimap;
GeglBuffer *mask;
GList *undo_stack;
GList *redo_stack;
GimpToolGui *gui;
GtkWidget *preview_toggle;
};