diff --git a/app/actions/filters-commands.c b/app/actions/filters-commands.c
index 389048e3a1..7cdbe79e0a 100644
--- a/app/actions/filters-commands.c
+++ b/app/actions/filters-commands.c
@@ -51,12 +51,6 @@ static gchar * filters_parse_operation (Gimp *gimp,
const gchar *icon_name,
GimpObject **settings);
-static void filters_run_procedure (Gimp *gimp,
- GimpDisplay *display,
- GimpProcedure *procedure,
- GimpRunMode run_mode);
-
-
/* public functions */
void
@@ -86,6 +80,7 @@ filters_apply_cmd_callback (GimpAction *action,
&settings);
procedure = gimp_gegl_procedure_new (image->gimp,
+ NULL,
GIMP_RUN_NONINTERACTIVE, settings,
operation,
gimp_action_get_name (action),
@@ -130,6 +125,7 @@ filters_apply_interactive_cmd_callback (GimpAction *action,
}
procedure = gimp_gegl_procedure_new (image->gimp,
+ NULL,
GIMP_RUN_INTERACTIVE, NULL,
g_variant_get_string (value, NULL),
gimp_action_get_name (action),
@@ -240,7 +236,7 @@ filters_parse_operation (Gimp *gimp,
return g_strdup (operation_str);
}
-static void
+void
filters_run_procedure (Gimp *gimp,
GimpDisplay *display,
GimpProcedure *procedure,
diff --git a/app/actions/filters-commands.h b/app/actions/filters-commands.h
index 68cb2ea7f7..d4db5ec25e 100644
--- a/app/actions/filters-commands.h
+++ b/app/actions/filters-commands.h
@@ -33,5 +33,10 @@ void filters_history_cmd_callback (GimpAction *action,
GVariant *value,
gpointer data);
+void filters_run_procedure (Gimp *gimp,
+ GimpDisplay *display,
+ GimpProcedure *procedure,
+ GimpRunMode run_mode);
+
#endif /* __FILTERS_COMMANDS_H__ */
diff --git a/app/actions/gimpgeglprocedure.c b/app/actions/gimpgeglprocedure.c
index 5f2ef4b09d..8c27377627 100644
--- a/app/actions/gimpgeglprocedure.c
+++ b/app/actions/gimpgeglprocedure.c
@@ -41,6 +41,7 @@
#include "core/gimpcontext.h"
#include "core/gimpdisplay.h"
#include "core/gimpdrawable-operation.h"
+#include "core/gimpdrawablefilter.h"
#include "core/gimpimage.h"
#include "core/gimplayermask.h"
#include "core/gimpparamspecs.h"
@@ -366,6 +367,7 @@ gimp_gegl_procedure_execute_async (GimpProcedure *procedure,
if (! strcmp (tool_name, "gimp-operation-tool"))
{
gimp_operation_tool_set_operation (GIMP_OPERATION_TOOL (active_tool),
+ gegl_procedure->filter,
gegl_procedure->operation,
gimp_procedure_get_label (procedure),
gimp_procedure_get_label (procedure),
@@ -376,6 +378,25 @@ gimp_gegl_procedure_execute_async (GimpProcedure *procedure,
tool_manager_initialize_active (gimp, display);
+ /* For GIMP-specific GEGL operations, we need to copy over the
+ * config object stored in the GeglNode */
+ if (gegl_procedure->filter &&
+ (! strcmp (gegl_procedure->operation, "gimp:brightness-contrast") ||
+ ! strcmp (gegl_procedure->operation, "gimp:curves") ||
+ ! strcmp (gegl_procedure->operation, "gimp:levels") ||
+ ! strcmp (gegl_procedure->operation, "gimp:threshold")))
+ {
+ GeglNode *node;
+
+ GIMP_FILTER_TOOL (active_tool)->existing_filter = gegl_procedure->filter;
+ gimp_filter_set_active (GIMP_FILTER (gegl_procedure->filter), FALSE);
+
+ node = gimp_drawable_filter_get_operation (gegl_procedure->filter);
+ gegl_node_get (node,
+ "config", &settings,
+ NULL);
+ }
+
if (settings)
gimp_filter_tool_set_config (GIMP_FILTER_TOOL (active_tool),
GIMP_CONFIG (settings));
@@ -386,15 +407,16 @@ gimp_gegl_procedure_execute_async (GimpProcedure *procedure,
/* public functions */
GimpProcedure *
-gimp_gegl_procedure_new (Gimp *gimp,
- GimpRunMode default_run_mode,
- GimpObject *default_settings,
- const gchar *operation,
- const gchar *name,
- const gchar *menu_label,
- const gchar *tooltip,
- const gchar *icon_name,
- const gchar *help_id)
+gimp_gegl_procedure_new (Gimp *gimp,
+ GimpDrawableFilter *filter,
+ GimpRunMode default_run_mode,
+ GimpObject *default_settings,
+ const gchar *operation,
+ const gchar *name,
+ const gchar *menu_label,
+ const gchar *tooltip,
+ const gchar *icon_name,
+ const gchar *help_id)
{
GimpProcedure *procedure;
GimpGeglProcedure *gegl_procedure;
@@ -412,6 +434,7 @@ gimp_gegl_procedure_new (Gimp *gimp,
gegl_procedure = GIMP_GEGL_PROCEDURE (procedure);
+ gegl_procedure->filter = filter;
gegl_procedure->operation = g_strdup (operation);
gegl_procedure->default_run_mode = default_run_mode;
gegl_procedure->menu_label = g_strdup (menu_label);
diff --git a/app/actions/gimpgeglprocedure.h b/app/actions/gimpgeglprocedure.h
index c95927e5e3..3d3e91c962 100644
--- a/app/actions/gimpgeglprocedure.h
+++ b/app/actions/gimpgeglprocedure.h
@@ -40,12 +40,13 @@ struct _GimpGeglProcedure
{
GimpProcedure parent_instance;
- gchar *operation;
+ GimpDrawableFilter *filter;
+ gchar *operation;
- GimpRunMode default_run_mode;
- GimpObject *default_settings;
+ GimpRunMode default_run_mode;
+ GimpObject *default_settings;
- gchar *menu_label;
+ gchar *menu_label;
};
struct _GimpGeglProcedureClass
@@ -56,15 +57,15 @@ struct _GimpGeglProcedureClass
GType gimp_gegl_procedure_get_type (void) G_GNUC_CONST;
-GimpProcedure * gimp_gegl_procedure_new (Gimp *gimp,
- GimpRunMode default_run_mode,
- GimpObject *default_settings,
- const gchar *operation,
- const gchar *name,
- const gchar *menu_label,
- const gchar *tooltip,
- const gchar *icon_name,
- const gchar *help_id);
-
+GimpProcedure * gimp_gegl_procedure_new (Gimp *gimp,
+ GimpDrawableFilter *filter,
+ GimpRunMode default_run_mode,
+ GimpObject *default_settings,
+ const gchar *operation,
+ const gchar *name,
+ const gchar *menu_label,
+ const gchar *tooltip,
+ const gchar *icon_name,
+ const gchar *help_id);
#endif /* __GIMP_GEGL_PROCEDURE_H__ */
diff --git a/app/actions/layers-commands.c b/app/actions/layers-commands.c
index 8651c657eb..98f426d384 100644
--- a/app/actions/layers-commands.c
+++ b/app/actions/layers-commands.c
@@ -39,6 +39,8 @@
#include "core/gimpcontainer.h"
#include "core/gimpcontext.h"
#include "core/gimpdrawable-fill.h"
+#include "core/gimpdrawable-filters.h"
+#include "core/gimpdrawablefilter.h"
#include "core/gimpgrouplayer.h"
#include "core/gimpimage.h"
#include "core/gimpimage-merge.h"
@@ -48,6 +50,7 @@
#include "core/gimplayerpropundo.h"
#include "core/gimplayer-floating-selection.h"
#include "core/gimplayer-new.h"
+#include "core/gimplist.h"
#include "core/gimppickable.h"
#include "core/gimppickable-auto-shrink.h"
#include "core/gimptoolinfo.h"
@@ -811,6 +814,35 @@ layers_duplicate_cmd_callback (GimpAction *action,
gimp_item_get_index (iter->data),
TRUE);
new_layers = g_list_prepend (new_layers, new_layer);
+
+ /* Import any attached layer effects */
+ if (gimp_drawable_has_filters (GIMP_DRAWABLE (iter->data)))
+ {
+ GList *filter_list;
+ GimpContainer *filters;
+
+ filters = gimp_drawable_get_filters (GIMP_DRAWABLE (iter->data));
+
+ for (filter_list = GIMP_LIST (filters)->queue->tail; filter_list;
+ filter_list = g_list_previous (filter_list))
+ {
+ if (GIMP_IS_DRAWABLE_FILTER (filter_list->data))
+ {
+ GimpDrawableFilter *old_filter = filter_list->data;
+ GimpDrawableFilter *filter;
+
+ filter =
+ gimp_drawable_filter_duplicate (GIMP_DRAWABLE (new_layer),
+ old_filter);
+
+ gimp_drawable_filter_apply (filter, NULL);
+ gimp_drawable_filter_commit (filter, TRUE, NULL, FALSE);
+
+ gimp_drawable_filter_layer_mask_freeze (filter);
+ g_object_unref (filter);
+ }
+ }
+ }
}
gimp_image_set_selected_layers (image, new_layers);
diff --git a/app/core/core-enums.c b/app/core/core-enums.c
index 8bce2f42ec..adc8b005fa 100644
--- a/app/core/core-enums.c
+++ b/app/core/core-enums.c
@@ -1284,6 +1284,9 @@ gimp_undo_type_get_type (void)
{ GIMP_UNDO_FOREGROUND_SELECT, "GIMP_UNDO_FOREGROUND_SELECT", "foreground-select" },
{ GIMP_UNDO_PARASITE_ATTACH, "GIMP_UNDO_PARASITE_ATTACH", "parasite-attach" },
{ GIMP_UNDO_PARASITE_REMOVE, "GIMP_UNDO_PARASITE_REMOVE", "parasite-remove" },
+ { GIMP_UNDO_FILTER_ADD, "GIMP_UNDO_FILTER_ADD", "filter-add" },
+ { GIMP_UNDO_FILTER_REMOVE, "GIMP_UNDO_FILTER_REMOVE", "filter-remove" },
+ { GIMP_UNDO_FILTER_REORDER, "GIMP_UNDO_FILTER_REORDER", "filter-reorder" },
{ GIMP_UNDO_CANT, "GIMP_UNDO_CANT", "cant" },
{ 0, NULL, NULL }
};
@@ -1393,6 +1396,9 @@ gimp_undo_type_get_type (void)
{ GIMP_UNDO_FOREGROUND_SELECT, NC_("undo-type", "Select foreground"), NULL },
{ GIMP_UNDO_PARASITE_ATTACH, NC_("undo-type", "Attach parasite"), NULL },
{ GIMP_UNDO_PARASITE_REMOVE, NC_("undo-type", "Remove parasite"), NULL },
+ { GIMP_UNDO_FILTER_ADD, NC_("undo-type", "Add effect"), NULL },
+ { GIMP_UNDO_FILTER_REMOVE, NC_("undo-type", "Remove effect"), NULL },
+ { GIMP_UNDO_FILTER_REORDER, NC_("undo-type", "Reorder effect"), NULL },
{ GIMP_UNDO_CANT, NC_("undo-type", "Not undoable"), NULL },
{ 0, NULL, NULL }
};
diff --git a/app/core/core-enums.h b/app/core/core-enums.h
index d53a4aa18e..66503987a0 100644
--- a/app/core/core-enums.h
+++ b/app/core/core-enums.h
@@ -605,7 +605,7 @@ typedef enum /*< pdb-skip >*/
GIMP_UNDO_ITEM_COLOR_TAG, /*< desc="Item color tag" >*/
GIMP_UNDO_ITEM_LOCK_CONTENT, /*< desc="Lock/Unlock content" >*/
GIMP_UNDO_ITEM_LOCK_POSITION, /*< desc="Lock/Unlock position" >*/
- GIMP_UNDO_ITEM_LOCK_VISIBILITY, /*< desc="Lock/Unlock visibility" >*/
+ GIMP_UNDO_ITEM_LOCK_VISIBILITY, /*< desc="Lock/Unlock visibility" >*/
GIMP_UNDO_LAYER_ADD, /*< desc="New layer" >*/
GIMP_UNDO_LAYER_REMOVE, /*< desc="Delete layer" >*/
GIMP_UNDO_LAYER_MODE, /*< desc="Set layer mode" >*/
@@ -638,6 +638,9 @@ typedef enum /*< pdb-skip >*/
GIMP_UNDO_FOREGROUND_SELECT, /*< desc="Select foreground" >*/
GIMP_UNDO_PARASITE_ATTACH, /*< desc="Attach parasite" >*/
GIMP_UNDO_PARASITE_REMOVE, /*< desc="Remove parasite" >*/
+ GIMP_UNDO_FILTER_ADD, /*< desc="Add effect" >*/
+ GIMP_UNDO_FILTER_REMOVE, /*< desc="Remove effect" >*/
+ GIMP_UNDO_FILTER_REORDER, /*< desc="Reorder effect" >*/
GIMP_UNDO_CANT /*< desc="Not undoable" >*/
} GimpUndoType;
diff --git a/app/core/gimpdrawable-edit.c b/app/core/gimpdrawable-edit.c
index e826b530e2..848d69bcde 100644
--- a/app/core/gimpdrawable-edit.c
+++ b/app/core/gimpdrawable-edit.c
@@ -224,7 +224,7 @@ gimp_drawable_edit_fill (GimpDrawable *drawable,
composite_mode);
gimp_drawable_filter_apply (filter, NULL);
- gimp_drawable_filter_commit (filter, NULL, FALSE);
+ gimp_drawable_filter_commit (filter, FALSE, NULL, FALSE);
g_object_unref (filter);
g_object_unref (operation);
diff --git a/app/core/gimpdrawable-filters.c b/app/core/gimpdrawable-filters.c
index 762a870e2c..ff0a0fa041 100644
--- a/app/core/gimpdrawable-filters.c
+++ b/app/core/gimpdrawable-filters.c
@@ -34,14 +34,18 @@
#include "gimpdrawable.h"
#include "gimpdrawable-filters.h"
#include "gimpdrawable-private.h"
+#include "gimpdrawablefilter.h"
+#include "gimpdrawablefilterundo.h"
#include "gimpfilter.h"
#include "gimpfilterstack.h"
#include "gimpimage.h"
#include "gimpimage-undo.h"
+#include "core/gimpimage-undo-push.h"
#include "gimplayer.h"
#include "gimpprogress.h"
#include "gimpprojection.h"
+#include "gimp-intl.h"
GimpContainer *
gimp_drawable_get_filters (GimpDrawable *drawable)
@@ -81,6 +85,8 @@ gimp_drawable_add_filter (GimpDrawable *drawable,
gimp_container_add (drawable->private->filter_stack,
GIMP_OBJECT (filter));
+
+ gimp_drawable_filters_changed (drawable);
}
void
@@ -93,17 +99,120 @@ gimp_drawable_remove_filter (GimpDrawable *drawable,
gimp_container_remove (drawable->private->filter_stack,
GIMP_OBJECT (filter));
+
+ gimp_drawable_filters_changed (drawable);
+}
+
+void
+gimp_drawable_clear_filters (GimpDrawable *drawable)
+{
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+
+ gimp_container_clear (drawable->private->filter_stack);
+
+ gimp_drawable_filters_changed (drawable);
+}
+
+void
+gimp_drawable_remove_last_filter (GimpDrawable *drawable)
+{
+ GimpDrawableFilter *filter = NULL;
+
+ if (! GIMP_IS_DRAWABLE (drawable))
+ return;
+
+ if (gimp_drawable_has_filters (drawable))
+ {
+ filter = GIMP_LIST (drawable->private->filter_stack)->queue->head->data;
+
+ if (GIMP_IS_DRAWABLE_FILTER (filter))
+ {
+ gimp_drawable_remove_filter (drawable,
+ GIMP_FILTER (filter));
+
+ gimp_drawable_filters_changed (drawable);
+ }
+ }
+}
+
+void
+gimp_drawable_merge_filters (GimpDrawable *drawable)
+{
+ GList *list;
+ GeglBuffer *buffer = NULL;
+ GeglBuffer *new_buffer = NULL;
+
+ if (! GIMP_IS_DRAWABLE (drawable))
+ return;
+
+ gimp_image_undo_group_start (gimp_item_get_image (GIMP_ITEM (drawable)),
+ GIMP_UNDO_GROUP_DRAWABLE,
+ _("Rasterize filters"));
+
+ /* Save buffer with effects for later use */
+ buffer = gimp_drawable_get_buffer_with_effects (drawable);
+ if (buffer)
+ {
+ gint64 width = (gint64) gegl_buffer_get_width (buffer);
+ gint64 height = (gint64) gegl_buffer_get_height (buffer);
+
+ new_buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0, width, height),
+ gegl_buffer_get_format (buffer));
+
+ gimp_gegl_buffer_copy (buffer, NULL, GEGL_ABYSS_NONE,
+ new_buffer, NULL);
+ g_clear_object (&buffer);
+ }
+
+ for (list = GIMP_LIST (drawable->private->filter_stack)->queue->tail;
+ list;
+ list = g_list_previous (list))
+ {
+ if (GIMP_IS_DRAWABLE_FILTER (list->data))
+ {
+ GimpDrawableFilter *filter = list->data;
+ const Babl *format;
+
+ format = gimp_drawable_get_format (drawable);
+
+ gimp_image_undo_push_filter_remove (gimp_item_get_image (GIMP_ITEM (drawable)),
+ _("Merge filter"),
+ drawable, filter);
+
+ gimp_drawable_merge_filter (drawable,
+ GIMP_FILTER (filter),
+ NULL,
+ _("Rasterize filters"),
+ format,
+ TRUE, TRUE, FALSE);
+ }
+ }
+
+ /* Update with correct buffer */
+ if (new_buffer)
+ {
+ gimp_drawable_set_buffer (drawable, TRUE, NULL, new_buffer);
+ g_clear_object (&new_buffer);
+ }
+
+ gimp_image_undo_group_end (gimp_item_get_image (GIMP_ITEM (drawable)));
+
+ gimp_drawable_filters_changed (drawable);
}
gboolean
gimp_drawable_has_filter (GimpDrawable *drawable,
GimpFilter *filter)
{
+ gboolean filter_exists = FALSE;
+
g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), FALSE);
g_return_val_if_fail (GIMP_IS_FILTER (filter), FALSE);
- return gimp_container_have (drawable->private->filter_stack,
- GIMP_OBJECT (filter));
+ filter_exists = gimp_container_have (drawable->private->filter_stack,
+ GIMP_OBJECT (filter));
+
+ return filter_exists;
}
gboolean
@@ -339,5 +448,8 @@ gimp_drawable_merge_filter (GimpDrawable *drawable,
rect.width, rect.height);
}
+ if (success)
+ gimp_drawable_filters_changed (drawable);
+
return success;
}
diff --git a/app/core/gimpdrawable-filters.h b/app/core/gimpdrawable-filters.h
index 504d402199..dd4b0eed79 100644
--- a/app/core/gimpdrawable-filters.h
+++ b/app/core/gimpdrawable-filters.h
@@ -29,6 +29,10 @@ void gimp_drawable_add_filter (GimpDrawable *drawable,
GimpFilter *filter);
void gimp_drawable_remove_filter (GimpDrawable *drawable,
GimpFilter *filter);
+void gimp_drawable_clear_filters (GimpDrawable *drawable);
+void gimp_drawable_remove_last_filter
+ (GimpDrawable *drawable);
+void gimp_drawable_merge_filters (GimpDrawable *drawable);
gboolean gimp_drawable_has_filter (GimpDrawable *drawable,
GimpFilter *filter);
diff --git a/app/core/gimpdrawable-operation.c b/app/core/gimpdrawable-operation.c
index fdda28698f..70e8226241 100644
--- a/app/core/gimpdrawable-operation.c
+++ b/app/core/gimpdrawable-operation.c
@@ -89,7 +89,7 @@ gimp_drawable_apply_operation_with_config (GimpDrawable *drawable,
}
gimp_drawable_filter_apply (filter, NULL);
- gimp_drawable_filter_commit (filter, progress, TRUE);
+ gimp_drawable_filter_commit (filter, FALSE, progress, TRUE);
g_object_unref (filter);
diff --git a/app/core/gimpdrawable.c b/app/core/gimpdrawable.c
index f5f4f2b225..9e7498708f 100644
--- a/app/core/gimpdrawable.c
+++ b/app/core/gimpdrawable.c
@@ -31,6 +31,7 @@
#include "gegl/gimp-gegl-apply-operation.h"
#include "gegl/gimp-gegl-loops.h"
#include "gegl/gimp-gegl-utils.h"
+#include "gegl/gimptilehandlervalidate.h"
#include "gimp-memsize.h"
#include "gimp-utils.h"
@@ -38,15 +39,18 @@
#include "gimpcontext.h"
#include "gimpdrawable-combine.h"
#include "gimpdrawable-fill.h"
+#include "gimpdrawable-filters.h"
#include "gimpdrawable-floating-selection.h"
#include "gimpdrawable-preview.h"
#include "gimpdrawable-private.h"
#include "gimpdrawable-shadow.h"
#include "gimpdrawable-transform.h"
+#include "gimpdrawablefilter.h"
#include "gimpfilterstack.h"
#include "gimpimage.h"
#include "gimpimage-colormap.h"
#include "gimpimage-undo-push.h"
+#include "gimplayer.h"
#include "gimpmarshal.h"
#include "gimppickable.h"
#include "gimpprogress.h"
@@ -66,6 +70,7 @@ enum
FORMAT_CHANGED,
ALPHA_CHANGED,
BOUNDING_BOX_CHANGED,
+ FILTERS_CHANGED,
LAST_SIGNAL
};
@@ -270,6 +275,14 @@ gimp_drawable_class_init (GimpDrawableClass *klass)
NULL, NULL, NULL,
G_TYPE_NONE, 0);
+ gimp_drawable_signals[FILTERS_CHANGED] =
+ g_signal_new ("filters-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpDrawableClass, filters_changed),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+
object_class->dispose = gimp_drawable_dispose;
object_class->finalize = gimp_drawable_finalize;
object_class->set_property = gimp_drawable_set_property;
@@ -297,6 +310,7 @@ gimp_drawable_class_init (GimpDrawableClass *klass)
klass->format_changed = NULL;
klass->alpha_changed = NULL;
klass->bounding_box_changed = NULL;
+ klass->filters_changed = NULL;
klass->estimate_memsize = gimp_drawable_real_estimate_memsize;
klass->update_all = gimp_drawable_real_update_all;
klass->invalidate_boundary = NULL;
@@ -336,12 +350,13 @@ gimp_color_managed_iface_init (GimpColorManagedInterface *iface)
static void
gimp_pickable_iface_init (GimpPickableInterface *iface)
{
- iface->get_image = (GimpImage * (*) (GimpPickable *pickable)) gimp_item_get_image;
- iface->get_format = (const Babl * (*) (GimpPickable *pickable)) gimp_drawable_get_format;
- iface->get_format_with_alpha = (const Babl * (*) (GimpPickable *pickable)) gimp_drawable_get_format_with_alpha;
- iface->get_buffer = (GeglBuffer * (*) (GimpPickable *pickable)) gimp_drawable_get_buffer;
- iface->get_pixel_at = gimp_drawable_get_pixel_at;
- iface->get_pixel_average = gimp_drawable_get_pixel_average;
+ iface->get_image = (GimpImage * (*) (GimpPickable *pickable)) gimp_item_get_image;
+ iface->get_format = (const Babl * (*) (GimpPickable *pickable)) gimp_drawable_get_format;
+ iface->get_format_with_alpha = (const Babl * (*) (GimpPickable *pickable)) gimp_drawable_get_format_with_alpha;
+ iface->get_buffer = (GeglBuffer * (*) (GimpPickable *pickable)) gimp_drawable_get_buffer;
+ iface->get_buffer_with_effects = (GeglBuffer * (*) (GimpPickable *pickable)) gimp_drawable_get_buffer_with_effects;
+ iface->get_pixel_at = gimp_drawable_get_pixel_at;
+ iface->get_pixel_average = gimp_drawable_get_pixel_average;
}
static void
@@ -554,6 +569,30 @@ gimp_drawable_scale (GimpItem *item,
0, 0),
TRUE);
g_object_unref (new_buffer);
+
+ if (GIMP_IS_LAYER (drawable))
+ {
+ GList *list;
+
+ for (list = GIMP_LIST (drawable->private->filter_stack)->queue->tail;
+ list; list = g_list_previous (list))
+ {
+ if (GIMP_IS_DRAWABLE_FILTER (list->data))
+ {
+ GimpDrawableFilter *filter = list->data;
+ GimpChannel *mask = gimp_drawable_filter_get_mask (filter);
+ GeglRectangle *rect = GEGL_RECTANGLE (0, 0,
+ new_width,
+ new_height);
+
+ /* Don't resize partial layer effects */
+ if (gimp_channel_is_empty (mask))
+ gimp_drawable_filter_refresh_crop (filter, rect);
+ }
+ }
+ if (list)
+ g_list_free (list);
+ }
}
static void
@@ -637,6 +676,28 @@ gimp_drawable_resize (GimpItem *item,
0, 0),
TRUE);
g_object_unref (new_buffer);
+
+ if (GIMP_IS_LAYER (drawable))
+ {
+ GList *list;
+
+ for (list = GIMP_LIST (drawable->private->filter_stack)->queue->tail;
+ list; list = g_list_previous (list))
+ {
+ if (GIMP_IS_DRAWABLE_FILTER (list->data))
+ {
+ GimpDrawableFilter *filter = list->data;
+ GimpChannel *mask = gimp_drawable_filter_get_mask (filter);
+ GeglRectangle rect = {0, 0, new_width, new_height};
+
+ /* Don't resize partial layer effects */
+ if (gimp_channel_is_empty (mask))
+ gimp_drawable_filter_refresh_crop (filter, &rect);
+ }
+ }
+ if (list)
+ g_list_free (list);
+ }
}
static void
@@ -1439,6 +1500,55 @@ gimp_drawable_set_buffer_full (GimpDrawable *drawable,
gimp_drawable_update (drawable, 0, 0, -1, -1);
}
+GeglBuffer *
+gimp_drawable_get_buffer_with_effects (GimpDrawable *drawable)
+{
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+
+ if (drawable->private->paint_count == 0)
+ {
+ if (gimp_drawable_has_filters (drawable))
+ {
+ GeglNode *source = NULL;
+ GeglBuffer *buffer;
+ GimpTileHandlerValidate *validate;
+
+ source = gimp_drawable_get_source_node (drawable);
+ buffer = GIMP_DRAWABLE_GET_CLASS (drawable)->get_buffer (drawable);
+
+ if (source)
+ {
+ buffer = gegl_buffer_new (gegl_buffer_get_extent (buffer),
+ gegl_buffer_get_format (buffer));
+
+ validate =
+ GIMP_TILE_HANDLER_VALIDATE (gimp_tile_handler_validate_new (source));
+
+ gimp_tile_handler_validate_assign (validate, buffer);
+
+ g_object_unref (validate);
+
+ gimp_tile_handler_validate_invalidate (validate,
+ gegl_buffer_get_extent (buffer));
+
+ return buffer;
+ }
+ else
+ {
+ return buffer;
+ }
+ }
+ else
+ {
+ return GIMP_DRAWABLE_GET_CLASS (drawable)->get_buffer (drawable);
+ }
+ }
+ else
+ {
+ return drawable->private->paint_buffer;
+ }
+}
+
void
gimp_drawable_steal_buffer (GimpDrawable *drawable,
GimpDrawable *src_drawable)
@@ -1936,6 +2046,16 @@ gimp_drawable_end_paint (GimpDrawable *drawable)
drawable->private->paint_count--;
+ /* Refresh filters after painting */
+ if (gimp_drawable_has_filters (drawable) &&
+ drawable->private->paint_count == 0)
+ {
+ gimp_item_set_visible (GIMP_ITEM (drawable), FALSE, FALSE);
+ gimp_image_flush (gimp_item_get_image (GIMP_ITEM (drawable)));
+ gimp_item_set_visible (GIMP_ITEM (drawable),TRUE, FALSE);
+ gimp_image_flush (gimp_item_get_image (GIMP_ITEM (drawable)));
+ }
+
return result;
}
@@ -2004,3 +2124,9 @@ gimp_drawable_is_painting (GimpDrawable *drawable)
return drawable->private->paint_count > 0;
}
+
+void
+gimp_drawable_filters_changed (GimpDrawable *drawable)
+{
+ g_signal_emit (drawable, gimp_drawable_signals[FILTERS_CHANGED], 0);
+}
diff --git a/app/core/gimpdrawable.h b/app/core/gimpdrawable.h
index 5f48850bf6..7a8b5351ad 100644
--- a/app/core/gimpdrawable.h
+++ b/app/core/gimpdrawable.h
@@ -53,160 +53,163 @@ struct _GimpDrawableClass
void (* format_changed) (GimpDrawable *drawable);
void (* alpha_changed) (GimpDrawable *drawable);
void (* bounding_box_changed) (GimpDrawable *drawable);
+ void (* filters_changed) (GimpDrawable *drawable);
/* virtual functions */
- gint64 (* estimate_memsize) (GimpDrawable *drawable,
- GimpComponentType component_type,
- gint width,
- gint height);
- void (* update_all) (GimpDrawable *drawable);
- void (* invalidate_boundary) (GimpDrawable *drawable);
- void (* get_active_components) (GimpDrawable *drawable,
- gboolean *active);
- GimpComponentMask (* get_active_mask) (GimpDrawable *drawable);
- gboolean (* supports_alpha) (GimpDrawable *drawable);
- void (* convert_type) (GimpDrawable *drawable,
- GimpImage *dest_image,
- const Babl *new_format,
- GimpColorProfile *src_profile,
- GimpColorProfile *dest_profile,
- GeglDitherMethod layer_dither_type,
- GeglDitherMethod mask_dither_type,
- gboolean push_undo,
- GimpProgress *progress);
- void (* apply_buffer) (GimpDrawable *drawable,
- GeglBuffer *buffer,
- const GeglRectangle *buffer_region,
- gboolean push_undo,
- const gchar *undo_desc,
- gdouble opacity,
- GimpLayerMode mode,
- GimpLayerColorSpace blend_space,
- GimpLayerColorSpace composite_space,
- GimpLayerCompositeMode composite_mode,
- GeglBuffer *base_buffer,
- gint base_x,
- gint base_y);
- GeglBuffer * (* get_buffer) (GimpDrawable *drawable);
- void (* set_buffer) (GimpDrawable *drawable,
- gboolean push_undo,
- const gchar *undo_desc,
- GeglBuffer *buffer,
- const GeglRectangle *bounds);
- GeglRectangle (* get_bounding_box) (GimpDrawable *drawable);
- void (* push_undo) (GimpDrawable *drawable,
- const gchar *undo_desc,
- GeglBuffer *buffer,
- gint x,
- gint y,
- gint width,
- gint height);
- void (* swap_pixels) (GimpDrawable *drawable,
- GeglBuffer *buffer,
- gint x,
- gint y);
- GeglNode * (* get_source_node) (GimpDrawable *drawable);
+ gint64 (* estimate_memsize) (GimpDrawable *drawable,
+ GimpComponentType component_type,
+ gint width,
+ gint height);
+ void (* update_all) (GimpDrawable *drawable);
+ void (* invalidate_boundary) (GimpDrawable *drawable);
+ void (* get_active_components) (GimpDrawable *drawable,
+ gboolean *active);
+ GimpComponentMask (* get_active_mask) (GimpDrawable *drawable);
+ gboolean (* supports_alpha) (GimpDrawable *drawable);
+ void (* convert_type) (GimpDrawable *drawable,
+ GimpImage *dest_image,
+ const Babl *new_format,
+ GimpColorProfile *src_profile,
+ GimpColorProfile *dest_profile,
+ GeglDitherMethod layer_dither_type,
+ GeglDitherMethod mask_dither_type,
+ gboolean push_undo,
+ GimpProgress *progress);
+ void (* apply_buffer) (GimpDrawable *drawable,
+ GeglBuffer *buffer,
+ const GeglRectangle *buffer_region,
+ gboolean push_undo,
+ const gchar *undo_desc,
+ gdouble opacity,
+ GimpLayerMode mode,
+ GimpLayerColorSpace blend_space,
+ GimpLayerColorSpace composite_space,
+ GimpLayerCompositeMode composite_mode,
+ GeglBuffer *base_buffer,
+ gint base_x,
+ gint base_y);
+ GeglBuffer * (* get_buffer) (GimpDrawable *drawable);
+ void (* set_buffer) (GimpDrawable *drawable,
+ gboolean push_undo,
+ const gchar *undo_desc,
+ GeglBuffer *buffer,
+ const GeglRectangle *bounds);
+ GeglBuffer * (* get_buffer_with_effects) (GimpDrawable *drawable);
+ GeglRectangle (* get_bounding_box) (GimpDrawable *drawable);
+ void (* push_undo) (GimpDrawable *drawable,
+ const gchar *undo_desc,
+ GeglBuffer *buffer,
+ gint x,
+ gint y,
+ gint width,
+ gint height);
+ void (* swap_pixels) (GimpDrawable *drawable,
+ GeglBuffer *buffer,
+ gint x,
+ gint y);
+ GeglNode * (* get_source_node) (GimpDrawable *drawable);
};
-GType gimp_drawable_get_type (void) G_GNUC_CONST;
+GType gimp_drawable_get_type (void) G_GNUC_CONST;
-GimpDrawable * gimp_drawable_new (GType type,
- GimpImage *image,
- const gchar *name,
- gint offset_x,
- gint offset_y,
- gint width,
- gint height,
- const Babl *format);
+GimpDrawable * gimp_drawable_new (GType type,
+ GimpImage *image,
+ const gchar *name,
+ gint offset_x,
+ gint offset_y,
+ gint width,
+ gint height,
+ const Babl *format);
-gint64 gimp_drawable_estimate_memsize (GimpDrawable *drawable,
- GimpComponentType component_type,
- gint width,
- gint height);
+gint64 gimp_drawable_estimate_memsize (GimpDrawable *drawable,
+ GimpComponentType component_type,
+ gint width,
+ gint height);
-void gimp_drawable_update (GimpDrawable *drawable,
- gint x,
- gint y,
- gint width,
- gint height);
-void gimp_drawable_update_all (GimpDrawable *drawable);
+void gimp_drawable_update (GimpDrawable *drawable,
+ gint x,
+ gint y,
+ gint width,
+ gint height);
+void gimp_drawable_update_all (GimpDrawable *drawable);
-void gimp_drawable_invalidate_boundary (GimpDrawable *drawable);
-void gimp_drawable_get_active_components (GimpDrawable *drawable,
- gboolean *active);
-GimpComponentMask gimp_drawable_get_active_mask (GimpDrawable *drawable);
+void gimp_drawable_invalidate_boundary (GimpDrawable *drawable);
+void gimp_drawable_get_active_components (GimpDrawable *drawable,
+ gboolean *active);
+GimpComponentMask gimp_drawable_get_active_mask (GimpDrawable *drawable);
-gboolean gimp_drawable_supports_alpha (GimpDrawable *drawable);
+gboolean gimp_drawable_supports_alpha (GimpDrawable *drawable);
-void gimp_drawable_convert_type (GimpDrawable *drawable,
- GimpImage *dest_image,
- GimpImageBaseType new_base_type,
- GimpPrecision new_precision,
- gboolean new_has_alpha,
- GimpColorProfile *src_profile,
- GimpColorProfile *dest_profile,
- GeglDitherMethod layer_dither_type,
- GeglDitherMethod mask_dither_type,
- gboolean push_undo,
- GimpProgress *progress);
+void gimp_drawable_convert_type (GimpDrawable *drawable,
+ GimpImage *dest_image,
+ GimpImageBaseType new_base_type,
+ GimpPrecision new_precision,
+ gboolean new_has_alpha,
+ GimpColorProfile *src_profile,
+ GimpColorProfile *dest_profile,
+ GeglDitherMethod layer_dither_type,
+ GeglDitherMethod mask_dither_type,
+ gboolean push_undo,
+ GimpProgress *progress);
-void gimp_drawable_apply_buffer (GimpDrawable *drawable,
- GeglBuffer *buffer,
- const GeglRectangle *buffer_rect,
- gboolean push_undo,
- const gchar *undo_desc,
- gdouble opacity,
- GimpLayerMode mode,
- GimpLayerColorSpace blend_space,
- GimpLayerColorSpace composite_space,
- GimpLayerCompositeMode composite_mode,
- GeglBuffer *base_buffer,
- gint base_x,
- gint base_y);
+void gimp_drawable_apply_buffer (GimpDrawable *drawable,
+ GeglBuffer *buffer,
+ const GeglRectangle *buffer_rect,
+ gboolean push_undo,
+ const gchar *undo_desc,
+ gdouble opacity,
+ GimpLayerMode mode,
+ GimpLayerColorSpace blend_space,
+ GimpLayerColorSpace composite_space,
+ GimpLayerCompositeMode composite_mode,
+ GeglBuffer *base_buffer,
+ gint base_x,
+ gint base_y);
-GeglBuffer * gimp_drawable_get_buffer (GimpDrawable *drawable);
-void gimp_drawable_set_buffer (GimpDrawable *drawable,
- gboolean push_undo,
- const gchar *undo_desc,
- GeglBuffer *buffer);
-void gimp_drawable_set_buffer_full (GimpDrawable *drawable,
- gboolean push_undo,
- const gchar *undo_desc,
- GeglBuffer *buffer,
- const GeglRectangle *bounds,
- gboolean update);
+GeglBuffer * gimp_drawable_get_buffer (GimpDrawable *drawable);
+void gimp_drawable_set_buffer (GimpDrawable *drawable,
+ gboolean push_undo,
+ const gchar *undo_desc,
+ GeglBuffer *buffer);
+void gimp_drawable_set_buffer_full (GimpDrawable *drawable,
+ gboolean push_undo,
+ const gchar *undo_desc,
+ GeglBuffer *buffer,
+ const GeglRectangle *bounds,
+ gboolean update);
+GeglBuffer * gimp_drawable_get_buffer_with_effects (GimpDrawable *drawable);
-void gimp_drawable_steal_buffer (GimpDrawable *drawable,
- GimpDrawable *src_drawable);
+void gimp_drawable_steal_buffer (GimpDrawable *drawable,
+ GimpDrawable *src_drawable);
-void gimp_drawable_set_format (GimpDrawable *drawable,
- const Babl *format,
- gboolean copy_buffer,
- gboolean push_undo);
+void gimp_drawable_set_format (GimpDrawable *drawable,
+ const Babl *format,
+ gboolean copy_buffer,
+ gboolean push_undo);
-GeglNode * gimp_drawable_get_source_node (GimpDrawable *drawable);
-GeglNode * gimp_drawable_get_mode_node (GimpDrawable *drawable);
+GeglNode * gimp_drawable_get_source_node (GimpDrawable *drawable);
+GeglNode * gimp_drawable_get_mode_node (GimpDrawable *drawable);
-GeglRectangle gimp_drawable_get_bounding_box (GimpDrawable *drawable);
+GeglRectangle gimp_drawable_get_bounding_box (GimpDrawable *drawable);
gboolean gimp_drawable_update_bounding_box
- (GimpDrawable *drawable);
+ (GimpDrawable *drawable);
-void gimp_drawable_swap_pixels (GimpDrawable *drawable,
- GeglBuffer *buffer,
- gint x,
- gint y);
+void gimp_drawable_swap_pixels (GimpDrawable *drawable,
+ GeglBuffer *buffer,
+ gint x,
+ gint y);
-void gimp_drawable_push_undo (GimpDrawable *drawable,
- const gchar *undo_desc,
- GeglBuffer *buffer,
- gint x,
- gint y,
- gint width,
- gint height);
+void gimp_drawable_push_undo (GimpDrawable *drawable,
+ const gchar *undo_desc,
+ GeglBuffer *buffer,
+ gint x,
+ gint y,
+ gint width,
+ gint height);
-void gimp_drawable_disable_resize_undo (GimpDrawable *drawable);
-void gimp_drawable_enable_resize_undo (GimpDrawable *drawable);
+void gimp_drawable_disable_resize_undo (GimpDrawable *drawable);
+void gimp_drawable_enable_resize_undo (GimpDrawable *drawable);
const Babl * gimp_drawable_get_space (GimpDrawable *drawable);
const Babl * gimp_drawable_get_format (GimpDrawable *drawable);
@@ -234,5 +237,7 @@ gboolean gimp_drawable_end_paint (GimpDrawable *drawable)
gboolean gimp_drawable_flush_paint (GimpDrawable *drawable);
gboolean gimp_drawable_is_painting (GimpDrawable *drawable);
+void gimp_drawable_filters_changed (GimpDrawable *drawable);
+
#endif /* __GIMP_DRAWABLE_H__ */
diff --git a/app/core/gimpdrawablefilter.c b/app/core/gimpdrawablefilter.c
index 3e58b054fd..10667f8776 100644
--- a/app/core/gimpdrawablefilter.c
+++ b/app/core/gimpdrawablefilter.c
@@ -54,12 +54,19 @@ enum
LAST_SIGNAL
};
+enum
+{
+ PROP_0,
+ PROP_MASK
+};
+
struct _GimpDrawableFilter
{
GimpFilter parent_instance;
GimpDrawable *drawable;
+ GimpChannel *mask;
GeglNode *operation;
gboolean has_input;
@@ -94,7 +101,15 @@ struct _GimpDrawableFilter
GimpApplicator *applicator;
};
+static void gimp_drawable_filter_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_drawable_filter_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
static void gimp_drawable_filter_dispose (GObject *object);
static void gimp_drawable_filter_finalize (GObject *object);
@@ -159,8 +174,16 @@ gimp_drawable_filter_class_init (GimpDrawableFilterClass *klass)
NULL, NULL, NULL,
G_TYPE_NONE, 0);
- object_class->dispose = gimp_drawable_filter_dispose;
- object_class->finalize = gimp_drawable_filter_finalize;
+ object_class->set_property = gimp_drawable_filter_set_property;
+ object_class->get_property = gimp_drawable_filter_get_property;
+ object_class->dispose = gimp_drawable_filter_dispose;
+ object_class->finalize = gimp_drawable_filter_finalize;
+
+ g_object_class_install_property (object_class, PROP_MASK,
+ g_param_spec_object ("mask",
+ NULL, NULL,
+ GIMP_TYPE_CHANNEL,
+ GIMP_PARAM_READWRITE));
}
static void
@@ -179,6 +202,49 @@ gimp_drawable_filter_init (GimpDrawableFilter *drawable_filter)
drawable_filter->composite_mode = GIMP_LAYER_COMPOSITE_AUTO;
}
+static void
+gimp_drawable_filter_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpDrawableFilter *filter = GIMP_DRAWABLE_FILTER (object);
+
+ switch (property_id)
+ {
+ case PROP_MASK:
+ g_set_object (&filter->mask, g_value_get_object (value));
+
+ if (filter->mask)
+ gimp_drawable_filter_sync_mask (filter);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_drawable_filter_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpDrawableFilter *filter = GIMP_DRAWABLE_FILTER (object);
+
+ switch (property_id)
+ {
+ case PROP_MASK:
+ g_value_set_object (value, gimp_drawable_filter_get_mask (filter));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
static void
gimp_drawable_filter_dispose (GObject *object)
{
@@ -195,9 +261,10 @@ gimp_drawable_filter_finalize (GObject *object)
{
GimpDrawableFilter *drawable_filter = GIMP_DRAWABLE_FILTER (object);
- g_clear_object (&drawable_filter->operation);
g_clear_object (&drawable_filter->applicator);
g_clear_object (&drawable_filter->drawable);
+ g_clear_object (&drawable_filter->operation);
+ g_clear_object (&drawable_filter->mask);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
@@ -219,6 +286,7 @@ gimp_drawable_filter_new (GimpDrawable *drawable,
filter = g_object_new (GIMP_TYPE_DRAWABLE_FILTER,
"name", undo_desc,
"icon-name", icon_name,
+ "mask", NULL,
NULL);
filter->drawable = g_object_ref (drawable);
@@ -226,8 +294,11 @@ gimp_drawable_filter_new (GimpDrawable *drawable,
node = gimp_filter_get_node (GIMP_FILTER (filter));
- gegl_node_add_child (node, operation);
- gimp_gegl_node_set_underlying_operation (node, operation);
+ if (! gegl_node_get_parent (operation))
+ {
+ gegl_node_add_child (node, operation);
+ gimp_gegl_node_set_underlying_operation (node, operation);
+ }
filter->applicator = gimp_applicator_new (node);
@@ -281,6 +352,78 @@ gimp_drawable_filter_new (GimpDrawable *drawable,
return filter;
}
+GimpDrawableFilter *
+gimp_drawable_filter_duplicate (GimpDrawable *drawable,
+ GimpDrawableFilter *prior_filter)
+{
+ GimpDrawableFilter *filter;
+ GimpChannel *mask;
+ GeglNode *prior_node;
+ GeglNode *node = gegl_node_new ();
+ const gchar *operation;
+ const gchar *undo_desc;
+ const gchar *icon_name;
+ GParamSpec **pspecs;
+ guint n_pspecs;
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+ g_return_val_if_fail (GIMP_IS_DRAWABLE_FILTER (prior_filter), NULL);
+
+ prior_node = gimp_drawable_filter_get_operation (prior_filter);
+
+ g_object_get (prior_filter,
+ "name", &undo_desc,
+ "icon-name", &icon_name,
+ NULL);
+
+ gegl_node_get (prior_node,
+ "operation", &operation,
+ NULL);
+
+ gegl_node_set (node,
+ "operation", operation,
+ NULL);
+
+ pspecs = gegl_operation_list_properties (operation, &n_pspecs);
+
+ for (gint i = 0; i < n_pspecs; i++)
+ {
+ GParamSpec *pspec = pspecs[i];
+ GValue value = G_VALUE_INIT;
+
+ g_value_init (&value, pspec->value_type);
+ gegl_node_get_property (prior_node, pspec->name,
+ &value);
+
+ gegl_node_set_property (node, pspec->name,
+ &value);
+ g_value_unset (&value);
+ }
+ g_free (pspecs);
+
+ filter = gimp_drawable_filter_new (drawable, undo_desc, node, icon_name);
+ g_object_unref (node);
+
+ gimp_drawable_filter_set_opacity (filter, prior_filter->opacity);
+ gimp_drawable_filter_set_mode (filter,
+ prior_filter->paint_mode,
+ prior_filter->blend_space,
+ prior_filter->composite_space,
+ prior_filter->composite_mode);
+ gimp_drawable_filter_set_region (filter,
+ prior_filter->region);
+
+ mask = GIMP_CHANNEL (gimp_item_duplicate (GIMP_ITEM (prior_filter->mask),
+ GIMP_TYPE_CHANNEL));
+
+ g_object_set (filter,
+ "mask", mask,
+ NULL);
+ g_object_unref (mask);
+
+ return filter;
+}
+
GimpDrawable *
gimp_drawable_filter_get_drawable (GimpDrawableFilter *filter)
{
@@ -297,6 +440,62 @@ gimp_drawable_filter_get_operation (GimpDrawableFilter *filter)
return filter->operation;
}
+GimpChannel *
+gimp_drawable_filter_get_mask (GimpDrawableFilter *filter)
+{
+ g_return_val_if_fail (GIMP_IS_DRAWABLE_FILTER (filter), NULL);
+
+ return filter->mask;
+}
+
+gdouble
+gimp_drawable_filter_get_opacity (GimpDrawableFilter *filter)
+{
+ g_return_val_if_fail (GIMP_IS_DRAWABLE_FILTER (filter), 0.0f);
+
+ return filter->opacity;
+}
+
+GimpLayerMode
+gimp_drawable_filter_get_paint_mode (GimpDrawableFilter *filter)
+{
+ g_return_val_if_fail (GIMP_IS_DRAWABLE_FILTER (filter), 0);
+
+ return filter->paint_mode;
+}
+
+GimpLayerColorSpace
+gimp_drawable_filter_get_blend_space (GimpDrawableFilter *filter)
+{
+ g_return_val_if_fail (GIMP_IS_DRAWABLE_FILTER (filter), 0);
+
+ return filter->blend_space;
+}
+
+GimpLayerColorSpace
+gimp_drawable_filter_get_composite_space (GimpDrawableFilter *filter)
+{
+ g_return_val_if_fail (GIMP_IS_DRAWABLE_FILTER (filter), 0);
+
+ return filter->composite_space;
+}
+
+GimpLayerCompositeMode
+gimp_drawable_filter_get_composite_mode (GimpDrawableFilter *filter)
+{
+ g_return_val_if_fail (GIMP_IS_DRAWABLE_FILTER (filter), 0);
+
+ return filter->composite_mode;
+}
+
+GimpFilterRegion
+gimp_drawable_filter_get_region (GimpDrawableFilter *filter)
+{
+ g_return_val_if_fail (GIMP_IS_DRAWABLE_FILTER (filter), 0);
+
+ return filter->region;
+}
+
void
gimp_drawable_filter_set_clip (GimpDrawableFilter *filter,
gboolean clip)
@@ -307,6 +506,7 @@ gimp_drawable_filter_set_clip (GimpDrawableFilter *filter,
{
filter->clip = clip;
+ gimp_drawable_filter_sync_region (filter);
gimp_drawable_filter_sync_clip (filter, TRUE);
}
}
@@ -568,6 +768,7 @@ gimp_drawable_filter_apply (GimpDrawableFilter *filter,
gboolean
gimp_drawable_filter_commit (GimpDrawableFilter *filter,
+ gboolean non_destructive,
GimpProgress *progress,
gboolean cancellable)
{
@@ -589,16 +790,25 @@ gimp_drawable_filter_commit (GimpDrawableFilter *filter,
filter->preview_split_position);
gimp_drawable_filter_set_preview (filter, TRUE);
- success = gimp_drawable_merge_filter (filter->drawable,
- GIMP_FILTER (filter),
- progress,
- gimp_object_get_name (filter),
- format,
- filter->filter_clip,
- cancellable,
- FALSE);
+ /* Only commit if filter is applied destructively */
+ if (! non_destructive)
+ {
+ success = gimp_drawable_merge_filter (filter->drawable,
+ GIMP_FILTER (filter),
+ progress,
+ gimp_object_get_name (filter),
+ format,
+ filter->filter_clip,
+ cancellable,
+ FALSE);
- gimp_drawable_filter_remove_filter (filter);
+ gimp_drawable_filter_remove_filter (filter);
+ }
+ else
+ {
+ if (gimp_viewable_preview_is_frozen (GIMP_VIEWABLE (filter->drawable)))
+ gimp_viewable_preview_thaw (GIMP_VIEWABLE (filter->drawable));
+ }
if (! success)
gimp_drawable_filter_update_drawable (filter, NULL);
@@ -620,6 +830,41 @@ gimp_drawable_filter_abort (GimpDrawableFilter *filter)
}
}
+void
+gimp_drawable_filter_layer_mask_freeze (GimpDrawableFilter *filter)
+{
+ GimpImage *image = gimp_item_get_image (GIMP_ITEM (filter->drawable));
+ GimpChannel *mask;
+
+ if (! filter->mask)
+ {
+ mask = GIMP_CHANNEL (gimp_item_duplicate (GIMP_ITEM (gimp_image_get_mask (image)),
+ GIMP_TYPE_CHANNEL));
+
+ g_set_object (&filter->mask, mask);
+ g_object_unref (mask);
+ }
+
+ g_signal_handlers_disconnect_by_func (image,
+ gimp_drawable_filter_mask_changed,
+ filter);
+}
+
+void gimp_drawable_filter_refresh_crop (GimpDrawableFilter *filter,
+ GeglRectangle *rect)
+{
+ g_return_if_fail (GIMP_IS_DRAWABLE_FILTER (filter));
+
+ if (rect)
+ {
+ gimp_drawable_filter_set_clip (filter, TRUE);
+ gimp_drawable_filter_set_clip (filter, FALSE);
+ gimp_drawable_filter_set_region (filter, GIMP_FILTER_REGION_SELECTION);
+ gimp_drawable_filter_set_region (filter, GIMP_FILTER_REGION_DRAWABLE);
+ gimp_drawable_filter_set_crop (filter, NULL, FALSE);
+ gimp_drawable_filter_set_crop (filter, rect, FALSE);
+ }
+}
/* private functions */
@@ -642,10 +887,9 @@ gimp_drawable_filter_sync_clip (GimpDrawableFilter *filter,
if (! clip)
{
- GimpImage *image = gimp_item_get_image (GIMP_ITEM (filter->drawable));
- GimpChannel *mask = gimp_image_get_mask (image);
+ GimpChannel *mask = GIMP_CHANNEL (filter->mask);
- if (! gimp_channel_is_empty (mask))
+ if (mask && ! gimp_channel_is_empty (mask))
clip = TRUE;
}
@@ -838,7 +1082,7 @@ gimp_drawable_filter_sync_crop (GimpDrawableFilter *filter,
gimp_applicator_set_crop (filter->applicator, enabled ? &new_rect : NULL);
- if (update &&
+ if (update &&
gimp_drawable_filter_is_active (filter) &&
! gegl_rectangle_equal (&old_rect, &new_rect))
{
@@ -921,11 +1165,22 @@ static void
gimp_drawable_filter_sync_mask (GimpDrawableFilter *filter)
{
GimpImage *image = gimp_item_get_image (GIMP_ITEM (filter->drawable));
- GimpChannel *mask = gimp_image_get_mask (image);
+ GimpChannel *mask = NULL;
- if (gimp_channel_is_empty (mask))
+ if (! filter->mask)
+ mask = gimp_image_get_mask (image);
+ else
+ mask = GIMP_CHANNEL (filter->mask);
+
+ if (! mask || gimp_channel_is_empty (mask))
{
gimp_applicator_set_mask_buffer (filter->applicator, NULL);
+
+ gimp_item_mask_intersect (GIMP_ITEM (filter->drawable),
+ &filter->filter_area.x,
+ &filter->filter_area.y,
+ &filter->filter_area.width,
+ &filter->filter_area.height);
}
else
{
@@ -939,13 +1194,15 @@ gimp_drawable_filter_sync_mask (GimpDrawableFilter *filter)
gimp_applicator_set_mask_buffer (filter->applicator, mask_buffer);
gimp_applicator_set_mask_offset (filter->applicator,
-offset_x, -offset_y);
- }
- gimp_item_mask_intersect (GIMP_ITEM (filter->drawable),
- &filter->filter_area.x,
- &filter->filter_area.y,
- &filter->filter_area.width,
- &filter->filter_area.height);
+ /* Update filter crop */
+ filter->filter_area.x = mask->x1;
+ filter->filter_area.y = mask->y1;
+ filter->filter_area.width = mask->x2 - mask->x1;
+ filter->filter_area.height = mask->y2 - mask->y1;
+
+ gimp_drawable_filter_sync_region (filter);
+ }
}
static void
@@ -1052,24 +1309,25 @@ gimp_drawable_filter_add_filter (GimpDrawableFilter *filter)
g_signal_connect (image, "component-active-changed",
G_CALLBACK (gimp_drawable_filter_affect_changed),
filter);
- g_signal_connect (image, "mask-changed",
- G_CALLBACK (gimp_drawable_filter_mask_changed),
- filter);
- g_signal_connect (filter->drawable, "lock-position-changed",
- G_CALLBACK (gimp_drawable_filter_lock_position_changed),
- filter);
- g_signal_connect (filter->drawable, "format-changed",
- G_CALLBACK (gimp_drawable_filter_format_changed),
- filter);
- g_signal_connect (filter->drawable, "removed",
- G_CALLBACK (gimp_drawable_filter_drawable_removed),
- filter);
+ if (! filter->mask)
+ g_signal_connect_object (image, "mask-changed",
+ G_CALLBACK (gimp_drawable_filter_mask_changed),
+ filter, 0);
+ g_signal_connect_object (filter->drawable, "lock-position-changed",
+ G_CALLBACK (gimp_drawable_filter_lock_position_changed),
+ filter, 0);
+ g_signal_connect_object (filter->drawable, "format-changed",
+ G_CALLBACK (gimp_drawable_filter_format_changed),
+ filter, 0);
+ g_signal_connect_object (filter->drawable, "removed",
+ G_CALLBACK (gimp_drawable_filter_drawable_removed),
+ filter, 0);
if (GIMP_IS_LAYER (filter->drawable))
{
- g_signal_connect (filter->drawable, "lock-alpha-changed",
- G_CALLBACK (gimp_drawable_filter_lock_alpha_changed),
- filter);
+ g_signal_connect_object (filter->drawable, "lock-alpha-changed",
+ G_CALLBACK (gimp_drawable_filter_lock_alpha_changed),
+ filter, 0);
}
return TRUE;
@@ -1101,9 +1359,7 @@ gimp_drawable_filter_remove_filter (GimpDrawableFilter *filter)
g_signal_handlers_disconnect_by_func (filter->drawable,
gimp_drawable_filter_lock_position_changed,
filter);
- g_signal_handlers_disconnect_by_func (image,
- gimp_drawable_filter_mask_changed,
- filter);
+
g_signal_handlers_disconnect_by_func (image,
gimp_drawable_filter_affect_changed,
filter);
@@ -1111,9 +1367,12 @@ gimp_drawable_filter_remove_filter (GimpDrawableFilter *filter)
gimp_drawable_remove_filter (filter->drawable,
GIMP_FILTER (filter));
- gimp_drawable_update_bounding_box (filter->drawable);
+ if (filter->drawable &&
+ GIMP_IS_DRAWABLE (filter->drawable))
+ gimp_drawable_update_bounding_box (filter->drawable);
- gimp_viewable_preview_thaw (GIMP_VIEWABLE (filter->drawable));
+ if (gimp_viewable_preview_is_frozen (GIMP_VIEWABLE (filter->drawable)))
+ gimp_viewable_preview_thaw (GIMP_VIEWABLE (filter->drawable));
return TRUE;
}
@@ -1181,13 +1440,22 @@ static void
gimp_drawable_filter_mask_changed (GimpImage *image,
GimpDrawableFilter *filter)
{
- gimp_drawable_filter_update_drawable (filter, NULL);
+ if (! filter->mask)
+ {
+ gimp_drawable_filter_update_drawable (filter, NULL);
- gimp_drawable_filter_sync_mask (filter);
- gimp_drawable_filter_sync_clip (filter, FALSE);
- gimp_drawable_filter_sync_region (filter);
+ gimp_drawable_filter_sync_mask (filter);
+ gimp_drawable_filter_sync_clip (filter, FALSE);
+ gimp_drawable_filter_sync_region (filter);
- gimp_drawable_filter_update_drawable (filter, NULL);
+ gimp_drawable_filter_update_drawable (filter, NULL);
+ }
+ else
+ {
+ g_signal_handlers_disconnect_by_func (image,
+ gimp_drawable_filter_mask_changed,
+ filter);
+ }
}
static void
@@ -1210,7 +1478,8 @@ static void
gimp_drawable_filter_drawable_removed (GimpDrawable *drawable,
GimpDrawableFilter *filter)
{
- gimp_drawable_filter_remove_filter (filter);
+ if (filter)
+ gimp_drawable_filter_remove_filter (filter);
}
static void
diff --git a/app/core/gimpdrawablefilter.h b/app/core/gimpdrawablefilter.h
index d45b680bd1..367a9ab375 100644
--- a/app/core/gimpdrawablefilter.h
+++ b/app/core/gimpdrawablefilter.h
@@ -54,10 +54,29 @@ GimpDrawableFilter *
const gchar *undo_desc,
GeglNode *operation,
const gchar *icon_name);
+GimpDrawableFilter *
+ gimp_drawable_filter_duplicate (GimpDrawable *drawable,
+ GimpDrawableFilter *prior_filter);
GimpDrawable *
gimp_drawable_filter_get_drawable (GimpDrawableFilter *filter);
GeglNode * gimp_drawable_filter_get_operation (GimpDrawableFilter *filter);
+GimpChannel *
+ gimp_drawable_filter_get_mask (GimpDrawableFilter *filter);
+gdouble gimp_drawable_filter_get_opacity (GimpDrawableFilter *filter);
+GimpLayerMode
+ gimp_drawable_filter_get_paint_mode (GimpDrawableFilter *filter);
+GimpLayerColorSpace
+ gimp_drawable_filter_get_blend_space
+ (GimpDrawableFilter *filter);
+GimpLayerColorSpace
+ gimp_drawable_filter_get_composite_space
+ (GimpDrawableFilter *filter);
+GimpLayerCompositeMode
+ gimp_drawable_filter_get_composite_mode
+ (GimpDrawableFilter *filter);
+GimpFilterRegion
+ gimp_drawable_filter_get_region (GimpDrawableFilter *filter);
void gimp_drawable_filter_set_clip (GimpDrawableFilter *filter,
gboolean clip);
@@ -97,9 +116,15 @@ void gimp_drawable_filter_apply (GimpDrawableFilter *filter,
const GeglRectangle *area);
gboolean gimp_drawable_filter_commit (GimpDrawableFilter *filter,
+ gboolean non_destructive,
GimpProgress *progress,
gboolean cancellable);
void gimp_drawable_filter_abort (GimpDrawableFilter *filter);
+void gimp_drawable_filter_layer_mask_freeze
+ (GimpDrawableFilter *filter);
+void gimp_drawable_filter_refresh_crop (GimpDrawableFilter *filter,
+ GeglRectangle *rect);
+
#endif /* __GIMP_DRAWABLE_FILTER_H__ */
diff --git a/app/core/gimpdrawablefilterundo.c b/app/core/gimpdrawablefilterundo.c
new file mode 100644
index 0000000000..d3b89a43d5
--- /dev/null
+++ b/app/core/gimpdrawablefilterundo.c
@@ -0,0 +1,228 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * 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 .
+ */
+
+#include "config.h"
+
+#include
+#include
+
+#include "libgimpbase/gimpbase.h"
+
+#include "core-types.h"
+
+#include "gimpcontainer.h"
+#include "gimpdrawable-filters.h"
+#include "gimpdrawablefilter.h"
+#include "gimpdrawablefilterundo.h"
+#include "gimpimage.h"
+#include "gimpitem.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_FILTER
+};
+
+
+static void gimp_drawable_filter_undo_constructed (GObject *object);
+static void gimp_drawable_filter_undo_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_drawable_filter_undo_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static gint64 gimp_drawable_filter_undo_get_memsize (GimpObject *object,
+ gint64 *gui_size);
+static void gimp_drawable_filter_undo_pop (GimpUndo *undo,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum);
+static void gimp_drawable_filter_undo_free (GimpUndo *undo,
+ GimpUndoMode undo_mode);
+
+
+G_DEFINE_TYPE (GimpDrawableFilterUndo, gimp_drawable_filter_undo, GIMP_TYPE_UNDO)
+
+#define parent_class gimp_drawable_filter_undo_parent_class
+
+
+static void
+gimp_drawable_filter_undo_class_init (GimpDrawableFilterUndoClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
+ GimpUndoClass *undo_class = GIMP_UNDO_CLASS (klass);
+
+ object_class->constructed = gimp_drawable_filter_undo_constructed;
+ object_class->set_property = gimp_drawable_filter_undo_set_property;
+ object_class->get_property = gimp_drawable_filter_undo_get_property;
+
+ gimp_object_class->get_memsize = gimp_drawable_filter_undo_get_memsize;
+
+ undo_class->pop = gimp_drawable_filter_undo_pop;
+ undo_class->free = gimp_drawable_filter_undo_free;
+
+ g_object_class_install_property (object_class, PROP_FILTER,
+ g_param_spec_object ("filter", NULL, NULL,
+ GIMP_TYPE_DRAWABLE_FILTER,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+gimp_drawable_filter_undo_init (GimpDrawableFilterUndo *undo)
+{
+}
+
+static void
+gimp_drawable_filter_undo_constructed (GObject *object)
+{
+ GimpDrawableFilterUndo *drawable_filter_undo = GIMP_DRAWABLE_FILTER_UNDO (object);
+ GimpDrawable *drawable;
+ GimpContainer *filter_stack;
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ gimp_assert (GIMP_IS_DRAWABLE_FILTER (drawable_filter_undo->filter));
+
+ drawable = gimp_drawable_filter_get_drawable (drawable_filter_undo->filter);
+ if (drawable)
+ {
+ filter_stack = gimp_drawable_get_filters (drawable);
+
+ drawable_filter_undo->row_index =
+ gimp_container_get_child_index (filter_stack,
+ GIMP_OBJECT (drawable_filter_undo->filter));
+
+ }
+}
+
+static void
+gimp_drawable_filter_undo_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpDrawableFilterUndo *drawable_filter_undo = GIMP_DRAWABLE_FILTER_UNDO (object);
+
+ switch (property_id)
+ {
+ case PROP_FILTER:
+ drawable_filter_undo->filter = g_value_dup_object (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_drawable_filter_undo_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpDrawableFilterUndo *drawable_filter_undo = GIMP_DRAWABLE_FILTER_UNDO (object);
+
+ switch (property_id)
+ {
+ case PROP_FILTER:
+ g_value_set_object (value, drawable_filter_undo->filter);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static gint64
+gimp_drawable_filter_undo_get_memsize (GimpObject *object,
+ gint64 *gui_size)
+{
+ GimpDrawableFilterUndo *drawable_filter_undo = GIMP_DRAWABLE_FILTER_UNDO (object);
+ gint64 memsize = 0;
+
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (drawable_filter_undo->filter),
+ NULL);
+ memsize += sizeof (guint32);
+
+ return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
+ gui_size);
+}
+
+static void
+gimp_drawable_filter_undo_pop (GimpUndo *undo,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum)
+{
+ GimpDrawableFilterUndo *drawable_filter_undo = GIMP_DRAWABLE_FILTER_UNDO (undo);
+ GimpDrawableFilter *filter = GIMP_DRAWABLE_FILTER (drawable_filter_undo->filter);
+ GimpDrawable *drawable = gimp_drawable_filter_get_drawable (filter);
+ GimpContainer *filter_stack = gimp_drawable_get_filters (drawable);
+
+ GIMP_UNDO_CLASS (parent_class)->pop (undo, undo_mode, accum);
+
+ if ((undo_mode == GIMP_UNDO_MODE_UNDO &&
+ undo->undo_type == GIMP_UNDO_FILTER_ADD) ||
+ (undo_mode == GIMP_UNDO_MODE_REDO &&
+ undo->undo_type == GIMP_UNDO_FILTER_REMOVE) )
+ {
+ if (drawable)
+ {
+ gimp_drawable_remove_filter (drawable, GIMP_FILTER (filter));
+
+ gimp_item_set_visible (GIMP_ITEM (drawable), FALSE, FALSE);
+ gimp_image_flush (undo->image);
+ gimp_item_set_visible (GIMP_ITEM (drawable), TRUE, FALSE);
+ gimp_image_flush (undo->image);
+ }
+ }
+ if ((undo_mode == GIMP_UNDO_MODE_UNDO &&
+ undo->undo_type == GIMP_UNDO_FILTER_REMOVE) ||
+ (undo_mode == GIMP_UNDO_MODE_REDO &&
+ undo->undo_type == GIMP_UNDO_FILTER_ADD) )
+ {
+ if (drawable)
+ {
+ gimp_drawable_filter_apply (filter, NULL);
+ gimp_container_reorder (filter_stack, GIMP_OBJECT (filter),
+ drawable_filter_undo->row_index);
+ }
+ }
+ else if (undo->undo_type == GIMP_UNDO_FILTER_REORDER)
+ {
+ gimp_container_reorder (filter_stack, GIMP_OBJECT (filter),
+ drawable_filter_undo->row_index);
+ gimp_drawable_filter_apply (filter, NULL);
+ }
+}
+
+static void
+gimp_drawable_filter_undo_free (GimpUndo *undo,
+ GimpUndoMode undo_mode)
+{
+ GimpDrawableFilterUndo *drawable_filter_undo = GIMP_DRAWABLE_FILTER_UNDO (undo);
+
+ if (drawable_filter_undo->filter)
+ g_clear_object (&drawable_filter_undo->filter);
+
+ GIMP_UNDO_CLASS (parent_class)->free (undo, undo_mode);
+}
diff --git a/app/core/gimpdrawablefilterundo.h b/app/core/gimpdrawablefilterundo.h
new file mode 100644
index 0000000000..a3f3c50713
--- /dev/null
+++ b/app/core/gimpdrawablefilterundo.h
@@ -0,0 +1,53 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * 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 .
+ */
+
+#ifndef __GIMP_DRAWABLE_FILTER_UNDO_H__
+#define __GIMP_DRAWABLE_FILTER_UNDO_H__
+
+
+#include "gimpundo.h"
+
+
+#define GIMP_TYPE_DRAWABLE_FILTER_UNDO (gimp_drawable_filter_undo_get_type ())
+#define GIMP_DRAWABLE_FILTER_UNDO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_DRAWABLE_FILTER_UNDO, GimpDrawableFilterUndo))
+#define GIMP_DRAWABLE_FILTER_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_DRAWABLE_FILTER_UNDO, GimpDrawableFilterUndoClass))
+#define GIMP_IS_DRAWABLE_FILTER_UNDO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_DRAWABLE_FILTER_UNDO))
+#define GIMP_IS_DRAWABLE_FILTER_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_DRAWABLE_FILTER_UNDO))
+#define GIMP_DRAWABLE_FILTER_UNDO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_DRAWABLE_FILTER_UNDO, GimpDrawableFilterUndoClass))
+
+
+typedef struct _GimpDrawableFilterUndo GimpDrawableFilterUndo;
+typedef struct _GimpDrawableFilterUndoClass GimpDrawableFilterUndoClass;
+
+struct _GimpDrawableFilterUndo
+{
+ GimpUndo parent_instance;
+
+ GimpDrawableFilter *filter;
+ guint32 row_index;
+};
+
+struct _GimpDrawableFilterUndoClass
+{
+ GimpUndoClass parent_class;
+};
+
+
+GType gimp_drawable_filter_undo_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_DRAWABLE_FILTER_UNDO_H__ */
diff --git a/app/core/gimpimage-duplicate.c b/app/core/gimpimage-duplicate.c
index 6bacd6564c..9987f64194 100644
--- a/app/core/gimpimage-duplicate.c
+++ b/app/core/gimpimage-duplicate.c
@@ -30,6 +30,8 @@
#include "gimp.h"
#include "gimpchannel.h"
+#include "gimpdrawable-filters.h"
+#include "gimpdrawablefilter.h"
#include "gimpguide.h"
#include "gimpimage.h"
#include "gimpimage-color-profile.h"
@@ -259,6 +261,36 @@ gimp_image_duplicate_layers (GimpImage *image,
gimp_image_add_layer (new_image, new_layer,
NULL, count++, FALSE);
+
+ /* Import any attached layer effects */
+ if (gimp_drawable_has_filters (GIMP_DRAWABLE (layer)))
+ {
+ GList *filter_list;
+ GimpContainer *filters;
+
+ filters = gimp_drawable_get_filters (GIMP_DRAWABLE (layer));
+
+ for (filter_list = GIMP_LIST (filters)->queue->tail; filter_list;
+ filter_list = g_list_previous (filter_list))
+ {
+ if (GIMP_IS_DRAWABLE_FILTER (filter_list->data))
+ {
+ GimpDrawableFilter *old_filter = filter_list->data;
+ GimpDrawableFilter *filter;
+
+ filter =
+ gimp_drawable_filter_duplicate (GIMP_DRAWABLE (new_layer),
+ old_filter);
+
+ gimp_drawable_filter_apply (filter, NULL);
+ gimp_drawable_filter_commit (filter, TRUE, NULL, FALSE);
+
+ gimp_drawable_filter_layer_mask_freeze (filter);
+
+ g_object_unref (filter);
+ }
+ }
+ }
}
new_item_stack = GIMP_ITEM_STACK (gimp_image_get_layers (new_image));
diff --git a/app/core/gimpimage-undo-push.c b/app/core/gimpimage-undo-push.c
index aa4a05e42c..27992a8460 100644
--- a/app/core/gimpimage-undo-push.c
+++ b/app/core/gimpimage-undo-push.c
@@ -27,6 +27,8 @@
#include "gimp.h"
#include "gimpchannelpropundo.h"
#include "gimpchannelundo.h"
+#include "gimpdrawablefilter.h"
+#include "gimpdrawablefilterundo.h"
#include "gimpdrawablemodundo.h"
#include "gimpdrawablepropundo.h"
#include "gimpdrawableundo.h"
@@ -306,6 +308,58 @@ gimp_image_undo_push_drawable_format (GimpImage *image,
}
+/***************************/
+/* Drawable Filter Undos */
+/***************************/
+GimpUndo *
+gimp_image_undo_push_filter_add (GimpImage *image,
+ const gchar *undo_desc,
+ GimpDrawable *drawable,
+ GimpDrawableFilter *filter)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_DRAWABLE_FILTER (filter), NULL);
+
+ return gimp_image_undo_push (image, GIMP_TYPE_DRAWABLE_FILTER_UNDO,
+ GIMP_UNDO_FILTER_ADD, undo_desc,
+ GIMP_DIRTY_DRAWABLE,
+ "filter", filter,
+ NULL);
+}
+
+GimpUndo *
+gimp_image_undo_push_filter_remove (GimpImage *image,
+ const gchar *undo_desc,
+ GimpDrawable *drawable,
+ GimpDrawableFilter *filter)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_DRAWABLE_FILTER (filter), NULL);
+
+ return gimp_image_undo_push (image, GIMP_TYPE_DRAWABLE_FILTER_UNDO,
+ GIMP_UNDO_FILTER_REMOVE, undo_desc,
+ GIMP_DIRTY_DRAWABLE,
+ "filter", filter,
+ NULL);
+}
+
+GimpUndo *
+gimp_image_undo_push_filter_reorder (GimpImage *image,
+ const gchar *undo_desc,
+ GimpDrawable *drawable,
+ GimpDrawableFilter *filter)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_DRAWABLE_FILTER (filter), NULL);
+
+ return gimp_image_undo_push (image, GIMP_TYPE_DRAWABLE_FILTER_UNDO,
+ GIMP_UNDO_FILTER_REORDER, undo_desc,
+ GIMP_DIRTY_DRAWABLE,
+ "filter", filter,
+ NULL);
+}
+
+
/****************/
/* Mask Undos */
/****************/
diff --git a/app/core/gimpimage-undo-push.h b/app/core/gimpimage-undo-push.h
index e9081885db..ff39f011aa 100644
--- a/app/core/gimpimage-undo-push.h
+++ b/app/core/gimpimage-undo-push.h
@@ -77,6 +77,26 @@ GimpUndo * gimp_image_undo_push_drawable_format (GimpImage *image,
GimpDrawable *drawable);
+/* drawable filter undos */
+GimpUndo * gimp_image_undo_push_filter_add (GimpImage *image,
+ const gchar *undo_desc,
+ GimpDrawable *drawable,
+ GimpDrawableFilter
+ *filter);
+
+GimpUndo * gimp_image_undo_push_filter_remove (GimpImage *image,
+ const gchar *undo_desc,
+ GimpDrawable *drawable,
+ GimpDrawableFilter
+ *filter);
+
+GimpUndo * gimp_image_undo_push_filter_reorder (GimpImage *image,
+ const gchar *undo_desc,
+ GimpDrawable *drawable,
+ GimpDrawableFilter
+ *filter);
+
+
/* mask undos */
GimpUndo * gimp_image_undo_push_mask (GimpImage *image,
diff --git a/app/core/gimpimage.c b/app/core/gimpimage.c
index 6c994ab08e..81b01ee65f 100644
--- a/app/core/gimpimage.c
+++ b/app/core/gimpimage.c
@@ -44,6 +44,7 @@
#include "gimp-parasites.h"
#include "gimp-utils.h"
#include "gimpcontext.h"
+#include "gimpdrawable-filters.h"
#include "gimpdrawable-floating-selection.h"
#include "gimpdrawablestack.h"
#include "gimpgrid.h"
@@ -2974,6 +2975,13 @@ gimp_image_get_xcf_version (GimpImage *image,
"GIMP 3.0"));
version = MAX (19, version);
}
+
+ if (gimp_drawable_has_filters (GIMP_DRAWABLE (layer)))
+ {
+ ADD_REASON (g_strdup_printf (_("Layer effects were added in %s"),
+ "GIMP 3.0"));
+ version = MAX (20, version);
+ }
}
g_list_free (items);
@@ -3141,6 +3149,7 @@ gimp_image_get_xcf_version (GimpImage *image,
case 17:
case 18:
case 19:
+ case 20:
if (gimp_version) *gimp_version = 300;
if (version_string) *version_string = "GIMP 3.0";
break;
diff --git a/app/core/gimppickable.c b/app/core/gimppickable.c
index c623b7a03e..a9645c1410 100644
--- a/app/core/gimppickable.c
+++ b/app/core/gimppickable.c
@@ -188,6 +188,23 @@ gimp_pickable_get_buffer (GimpPickable *pickable)
return NULL;
}
+GeglBuffer *
+gimp_pickable_get_buffer_with_effects (GimpPickable *pickable)
+{
+ GimpPickableInterface *pickable_iface;
+
+ g_return_val_if_fail (GIMP_IS_PICKABLE (pickable), NULL);
+
+ pickable_iface = GIMP_PICKABLE_GET_IFACE (pickable);
+
+ if (pickable_iface->get_buffer_with_effects)
+ return pickable_iface->get_buffer_with_effects (pickable);
+ else if (pickable_iface->get_buffer)
+ return pickable_iface->get_buffer (pickable);
+
+ return NULL;
+}
+
gboolean
gimp_pickable_get_pixel_at (GimpPickable *pickable,
gint x,
diff --git a/app/core/gimppickable.h b/app/core/gimppickable.h
index 3014fd8cba..c1f7bc96c6 100644
--- a/app/core/gimppickable.h
+++ b/app/core/gimppickable.h
@@ -30,74 +30,76 @@ struct _GimpPickableInterface
GTypeInterface base_iface;
/* virtual functions */
- void (* flush) (GimpPickable *pickable);
- GimpImage * (* get_image) (GimpPickable *pickable);
- const Babl * (* get_format) (GimpPickable *pickable);
- const Babl * (* get_format_with_alpha) (GimpPickable *pickable);
- GeglBuffer * (* get_buffer) (GimpPickable *pickable);
- gboolean (* get_pixel_at) (GimpPickable *pickable,
- gint x,
- gint y,
- const Babl *format,
- gpointer pixel);
- gdouble (* get_opacity_at) (GimpPickable *pickable,
- gint x,
- gint y);
- void (* get_pixel_average) (GimpPickable *pickable,
- const GeglRectangle *rect,
- const Babl *format,
- gpointer pixel);
- void (* pixel_to_rgb) (GimpPickable *pickable,
- const Babl *format,
- gpointer pixel,
- GimpRGB *color);
- void (* rgb_to_pixel) (GimpPickable *pickable,
- const GimpRGB *color,
- const Babl *format,
- gpointer pixel);
+ void (* flush) (GimpPickable *pickable);
+ GimpImage * (* get_image) (GimpPickable *pickable);
+ const Babl * (* get_format) (GimpPickable *pickable);
+ const Babl * (* get_format_with_alpha) (GimpPickable *pickable);
+ GeglBuffer * (* get_buffer) (GimpPickable *pickable);
+ GeglBuffer * (* get_buffer_with_effects) (GimpPickable *pickable);
+ gboolean (* get_pixel_at) (GimpPickable *pickable,
+ gint x,
+ gint y,
+ const Babl *format,
+ gpointer pixel);
+ gdouble (* get_opacity_at) (GimpPickable *pickable,
+ gint x,
+ gint y);
+ void (* get_pixel_average) (GimpPickable *pickable,
+ const GeglRectangle *rect,
+ const Babl *format,
+ gpointer pixel);
+ void (* pixel_to_rgb) (GimpPickable *pickable,
+ const Babl *format,
+ gpointer pixel,
+ GimpRGB *color);
+ void (* rgb_to_pixel) (GimpPickable *pickable,
+ const GimpRGB *color,
+ const Babl *format,
+ gpointer pixel);
};
-void gimp_pickable_flush (GimpPickable *pickable);
-GimpImage * gimp_pickable_get_image (GimpPickable *pickable);
-const Babl * gimp_pickable_get_format (GimpPickable *pickable);
-const Babl * gimp_pickable_get_format_with_alpha (GimpPickable *pickable);
-GeglBuffer * gimp_pickable_get_buffer (GimpPickable *pickable);
-gboolean gimp_pickable_get_pixel_at (GimpPickable *pickable,
- gint x,
- gint y,
- const Babl *format,
- gpointer pixel);
-gboolean gimp_pickable_get_color_at (GimpPickable *pickable,
- gint x,
- gint y,
- GimpRGB *color);
-gdouble gimp_pickable_get_opacity_at (GimpPickable *pickable,
- gint x,
- gint y);
-void gimp_pickable_get_pixel_average (GimpPickable *pickable,
- const GeglRectangle *rect,
- const Babl *format,
- gpointer pixel);
-void gimp_pickable_pixel_to_rgb (GimpPickable *pickable,
- const Babl *format,
- gpointer pixel,
- GimpRGB *color);
-void gimp_pickable_rgb_to_pixel (GimpPickable *pickable,
- const GimpRGB *color,
- const Babl *format,
- gpointer pixel);
-void gimp_pickable_srgb_to_image_color (GimpPickable *pickable,
- const GimpRGB *color,
- GimpRGB *image_color);
+void gimp_pickable_flush (GimpPickable *pickable);
+GimpImage * gimp_pickable_get_image (GimpPickable *pickable);
+const Babl * gimp_pickable_get_format (GimpPickable *pickable);
+const Babl * gimp_pickable_get_format_with_alpha (GimpPickable *pickable);
+GeglBuffer * gimp_pickable_get_buffer (GimpPickable *pickable);
+GeglBuffer * gimp_pickable_get_buffer_with_effects (GimpPickable *pickable);
+gboolean gimp_pickable_get_pixel_at (GimpPickable *pickable,
+ gint x,
+ gint y,
+ const Babl *format,
+ gpointer pixel);
+gboolean gimp_pickable_get_color_at (GimpPickable *pickable,
+ gint x,
+ gint y,
+ GimpRGB *color);
+gdouble gimp_pickable_get_opacity_at (GimpPickable *pickable,
+ gint x,
+ gint y);
+void gimp_pickable_get_pixel_average (GimpPickable *pickable,
+ const GeglRectangle *rect,
+ const Babl *format,
+ gpointer pixel);
+void gimp_pickable_pixel_to_rgb (GimpPickable *pickable,
+ const Babl *format,
+ gpointer pixel,
+ GimpRGB *color);
+void gimp_pickable_rgb_to_pixel (GimpPickable *pickable,
+ const GimpRGB *color,
+ const Babl *format,
+ gpointer pixel);
+void gimp_pickable_srgb_to_image_color (GimpPickable *pickable,
+ const GimpRGB *color,
+ GimpRGB *image_color);
-gboolean gimp_pickable_pick_color (GimpPickable *pickable,
- gint x,
- gint y,
- gboolean sample_average,
- gdouble average_radius,
- gpointer pixel,
- GimpRGB *color);
+gboolean gimp_pickable_pick_color (GimpPickable *pickable,
+ gint x,
+ gint y,
+ gboolean sample_average,
+ gdouble average_radius,
+ gpointer pixel,
+ GimpRGB *color);
#endif /* __GIMP_PICKABLE_H__ */
diff --git a/app/core/meson.build b/app/core/meson.build
index b2206d5a4e..f5e7f647cd 100644
--- a/app/core/meson.build
+++ b/app/core/meson.build
@@ -114,6 +114,7 @@ libappcore_sources = [
'gimpdrawable-transform.c',
'gimpdrawable.c',
'gimpdrawablefilter.c',
+ 'gimpdrawablefilterundo.c',
'gimpdrawablemodundo.c',
'gimpdrawablepropundo.c',
'gimpdrawablestack.c',
diff --git a/app/gegl/gimp-gegl-utils.c b/app/gegl/gimp-gegl-utils.c
index 8cb45dde7c..f4c936a7e4 100644
--- a/app/gegl/gimp-gegl-utils.c
+++ b/app/gegl/gimp-gegl-utils.c
@@ -141,15 +141,27 @@ gimp_gegl_progress_connect (GeglNode *node,
g_return_if_fail (GIMP_IS_PROGRESS (progress));
g_return_if_fail (text != NULL);
- g_signal_connect (node, "progress",
- G_CALLBACK (gimp_gegl_progress_callback),
- progress);
+ g_signal_connect_object (node, "progress",
+ G_CALLBACK (gimp_gegl_progress_callback),
+ progress, 0);
g_object_set_data_full (G_OBJECT (node),
"gimp-progress-text", g_strdup (text),
(GDestroyNotify) g_free);
}
+void
+gimp_gegl_progress_disconnect (GeglNode *node,
+ GimpProgress *progress)
+{
+ g_return_if_fail (GEGL_IS_NODE (node));
+ g_return_if_fail (GIMP_IS_PROGRESS (progress));
+
+ g_signal_handlers_disconnect_by_func (node,
+ gimp_gegl_progress_callback,
+ progress);
+}
+
gboolean
gimp_gegl_node_is_source_operation (GeglNode *node)
{
diff --git a/app/gegl/gimp-gegl-utils.h b/app/gegl/gimp-gegl-utils.h
index 1bfd8da881..0239b0e816 100644
--- a/app/gegl/gimp-gegl-utils.h
+++ b/app/gegl/gimp-gegl-utils.h
@@ -33,6 +33,8 @@ GeglColor * gimp_gegl_color_new (const GimpRGB *rgb,
void gimp_gegl_progress_connect (GeglNode *node,
GimpProgress *progress,
const gchar *text);
+void gimp_gegl_progress_disconnect (GeglNode *node,
+ GimpProgress *progress);
gboolean gimp_gegl_node_is_source_operation (GeglNode *node);
gboolean gimp_gegl_node_is_point_operation (GeglNode *node);
diff --git a/app/pdb/drawable-cmds.c b/app/pdb/drawable-cmds.c
index dae80b7c33..782ca78928 100644
--- a/app/pdb/drawable-cmds.c
+++ b/app/pdb/drawable-cmds.c
@@ -33,6 +33,7 @@
#include "core/gimp.h"
#include "core/gimpchannel-select.h"
#include "core/gimpdrawable-fill.h"
+#include "core/gimpdrawable-filters.h"
#include "core/gimpdrawable-foreground-extract.h"
#include "core/gimpdrawable-offset.h"
#include "core/gimpdrawable-preview.h"
@@ -502,6 +503,35 @@ drawable_mask_intersect_invoker (GimpProcedure *procedure,
return return_vals;
}
+static GimpValueArray *
+drawable_merge_filters_invoker (GimpProcedure *procedure,
+ Gimp *gimp,
+ GimpContext *context,
+ GimpProgress *progress,
+ const GimpValueArray *args,
+ GError **error)
+{
+ gboolean success = TRUE;
+ GimpDrawable *drawable;
+
+ drawable = g_value_get_object (gimp_value_array_index (args, 0));
+
+ if (success)
+ {
+ if (gimp_pdb_item_is_attached (GIMP_ITEM (drawable), NULL,
+ GIMP_PDB_ITEM_CONTENT, error) &&
+ gimp_pdb_item_is_not_group (GIMP_ITEM (drawable), error))
+ {
+ gimp_drawable_merge_filters (drawable);
+ }
+ else
+ success = FALSE;
+ }
+
+ return gimp_procedure_get_return_values (procedure, success,
+ error ? *error : NULL);
+}
+
static GimpValueArray *
drawable_merge_shadow_invoker (GimpProcedure *procedure,
Gimp *gimp,
@@ -1348,6 +1378,29 @@ register_drawable_procs (GimpPDB *pdb)
gimp_pdb_register_procedure (pdb, procedure);
g_object_unref (procedure);
+ /*
+ * gimp-drawable-merge-filters
+ */
+ procedure = gimp_procedure_new (drawable_merge_filters_invoker);
+ gimp_object_set_static_name (GIMP_OBJECT (procedure),
+ "gimp-drawable-merge-filters");
+ gimp_procedure_set_static_help (procedure,
+ "Merge the layer effect filters to the specified drawable.",
+ "This procedure combines the contents of the drawable's filter stack (for export) with the specified drawable.",
+ NULL);
+ gimp_procedure_set_static_attribution (procedure,
+ "Spencer Kimball & Peter Mattis",
+ "Spencer Kimball & Peter Mattis",
+ "1995-1996");
+ gimp_procedure_add_argument (procedure,
+ gimp_param_spec_drawable ("drawable",
+ "drawable",
+ "The drawable",
+ FALSE,
+ GIMP_PARAM_READWRITE));
+ gimp_pdb_register_procedure (pdb, procedure);
+ g_object_unref (procedure);
+
/*
* gimp-drawable-merge-shadow
*/
diff --git a/app/pdb/internal-procs.c b/app/pdb/internal-procs.c
index 5c350cd596..3cd3659a8a 100644
--- a/app/pdb/internal-procs.c
+++ b/app/pdb/internal-procs.c
@@ -30,7 +30,7 @@
#include "internal-procs.h"
-/* 781 procedures registered total */
+/* 782 procedures registered total */
void
internal_procs_init (GimpPDB *pdb)
diff --git a/app/tools/gimpbucketfilltool.c b/app/tools/gimpbucketfilltool.c
index 764978e3b3..dffbc0d056 100644
--- a/app/tools/gimpbucketfilltool.c
+++ b/app/tools/gimpbucketfilltool.c
@@ -497,7 +497,7 @@ gimp_bucket_fill_tool_commit (GimpBucketFillTool *tool)
{
if (tool->priv->filter)
{
- gimp_drawable_filter_commit (tool->priv->filter,
+ gimp_drawable_filter_commit (tool->priv->filter, FALSE,
GIMP_PROGRESS (tool), FALSE);
gimp_image_flush (gimp_display_get_image (GIMP_TOOL (tool)->display));
}
diff --git a/app/tools/gimpcagetool.c b/app/tools/gimpcagetool.c
index 053fccae75..a139361abd 100644
--- a/app/tools/gimpcagetool.c
+++ b/app/tools/gimpcagetool.c
@@ -1029,7 +1029,8 @@ gimp_cage_tool_commit (GimpCageTool *ct)
gimp_tool_control_push_preserve (tool->control, TRUE);
- gimp_drawable_filter_commit (ct->filter, GIMP_PROGRESS (tool), FALSE);
+ gimp_drawable_filter_commit (ct->filter, FALSE,
+ GIMP_PROGRESS (tool), FALSE);
g_clear_object (&ct->filter);
gimp_tool_control_pop_preserve (tool->control);
diff --git a/app/tools/gimpfiltertool.c b/app/tools/gimpfiltertool.c
index 3fc294338d..c73b2e5ddd 100644
--- a/app/tools/gimpfiltertool.c
+++ b/app/tools/gimpfiltertool.c
@@ -46,12 +46,14 @@
#include "core/gimp.h"
#include "core/gimpchannel.h"
#include "core/gimpdrawable.h"
+#include "core/gimpdrawable-filters.h"
#include "core/gimpdrawablefilter.h"
#include "core/gimperror.h"
#include "core/gimpguide.h"
#include "core/gimpimage.h"
#include "core/gimpimage-guides.h"
#include "core/gimpimage-pick-color.h"
+#include "core/gimpimage-undo-push.h"
#include "core/gimplayer.h"
#include "core/gimplist.h"
#include "core/gimppickable.h"
@@ -153,7 +155,8 @@ static void gimp_filter_tool_real_config_notify
const GParamSpec *pspec);
static void gimp_filter_tool_halt (GimpFilterTool *filter_tool);
-static void gimp_filter_tool_commit (GimpFilterTool *filter_tool);
+static void gimp_filter_tool_commit (GimpFilterTool *filter_tool,
+ gboolean non_destructive);
static void gimp_filter_tool_dialog (GimpFilterTool *filter_tool);
static void gimp_filter_tool_reset (GimpFilterTool *filter_tool);
@@ -331,7 +334,7 @@ gimp_filter_tool_initialize (GimpTool *tool,
return FALSE;
}
- gimp_filter_tool_get_operation (filter_tool);
+ gimp_filter_tool_get_operation (filter_tool, NULL);
gimp_filter_tool_disable_color_picking (filter_tool);
@@ -339,7 +342,7 @@ gimp_filter_tool_initialize (GimpTool *tool,
g_list_free (tool->drawables);
tool->drawables = drawables;
- if (filter_tool->config)
+ if (filter_tool->config && ! filter_tool->existing_filter)
gimp_config_reset (GIMP_CONFIG (filter_tool->config));
if (! filter_tool->gui)
@@ -465,7 +468,8 @@ gimp_filter_tool_control (GimpTool *tool,
GimpToolAction action,
GimpDisplay *display)
{
- GimpFilterTool *filter_tool = GIMP_FILTER_TOOL (tool);
+ GimpFilterTool *filter_tool = GIMP_FILTER_TOOL (tool);
+ gboolean non_destructive = TRUE;
switch (action)
{
@@ -478,7 +482,12 @@ gimp_filter_tool_control (GimpTool *tool,
break;
case GIMP_TOOL_ACTION_COMMIT:
- gimp_filter_tool_commit (filter_tool);
+ /* TODO: Expand non-destructive editing to other drawables
+ * besides layers */
+ if (! GIMP_IS_LAYER (tool->drawables->data))
+ non_destructive = FALSE;
+
+ gimp_filter_tool_commit (filter_tool, non_destructive);
break;
}
@@ -994,10 +1003,17 @@ gimp_filter_tool_halt (GimpFilterTool *filter_tool)
if (filter_tool->filter)
{
gimp_drawable_filter_abort (filter_tool->filter);
+ g_signal_handlers_disconnect_by_func (filter_tool->filter,
+ gimp_filter_tool_flush,
+ filter_tool);
g_clear_object (&filter_tool->filter);
gimp_filter_tool_remove_guide (filter_tool);
}
+ if (filter_tool->operation)
+ gimp_gegl_progress_disconnect (filter_tool->operation,
+ GIMP_PROGRESS (filter_tool));
+
g_clear_object (&filter_tool->operation);
if (filter_tool->config)
@@ -1022,16 +1038,80 @@ gimp_filter_tool_halt (GimpFilterTool *filter_tool)
tool->display = NULL;
g_list_free (tool->drawables);
tool->drawables = NULL;
+
+ if (filter_tool->existing_filter)
+ {
+ gimp_filter_set_active (GIMP_FILTER (filter_tool->existing_filter), TRUE);
+
+ /* Restore buttons in layer tree view */
+ gimp_drawable_filters_changed (gimp_drawable_filter_get_drawable (filter_tool->existing_filter));
+ }
+
+ filter_tool->existing_filter = NULL;
}
+/* Add code to prevent creating new filter when editing */
static void
-gimp_filter_tool_commit (GimpFilterTool *filter_tool)
+gimp_filter_tool_commit (GimpFilterTool *filter_tool,
+ gboolean non_destructive)
{
GimpTool *tool = GIMP_TOOL (filter_tool);
if (filter_tool->gui)
gimp_tool_gui_hide (filter_tool->gui);
+ /* Copy over filter info back to existing filter */
+ if (filter_tool->existing_filter)
+ {
+ GeglNode *existing_node;
+ gdouble opacity;
+ GimpLayerMode paint_mode;
+ GimpLayerColorSpace blend_space;
+ GimpLayerColorSpace composite_space;
+ GimpLayerCompositeMode composite_mode;
+ GimpFilterRegion region;
+ GParamSpec **pspecs;
+ guint n_pspecs;
+ gchar *name = NULL;
+
+ opacity = gimp_drawable_filter_get_opacity (filter_tool->filter);
+ paint_mode = gimp_drawable_filter_get_paint_mode (filter_tool->filter);
+ blend_space = gimp_drawable_filter_get_blend_space (filter_tool->filter);
+ composite_space = gimp_drawable_filter_get_composite_space (filter_tool->filter);
+ composite_mode = gimp_drawable_filter_get_composite_mode (filter_tool->filter);
+ region = gimp_drawable_filter_get_region (filter_tool->filter);
+
+ existing_node = gimp_drawable_filter_get_operation (filter_tool->existing_filter);
+ gegl_node_get (existing_node,
+ "operation", &name,
+ NULL);
+
+ pspecs = gegl_operation_list_properties (name, &n_pspecs);
+ g_free (name);
+
+ for (gint i = 0; i < n_pspecs; i++)
+ {
+ GParamSpec *pspec = pspecs[i];
+ GValue value = G_VALUE_INIT;
+
+ g_value_init (&value, pspec->value_type);
+ gegl_node_get_property (filter_tool->operation, pspec->name,
+ &value);
+
+ gegl_node_set_property (existing_node, pspec->name,
+ &value);
+ }
+
+ gimp_drawable_filter_set_opacity (filter_tool->existing_filter, opacity);
+ gimp_drawable_filter_set_mode (filter_tool->existing_filter,
+ paint_mode, blend_space, composite_space,
+ composite_mode);
+ gimp_drawable_filter_set_region (filter_tool->existing_filter, region);
+
+ /* Restore buttons in layer tree view */
+ gimp_drawable_filters_changed (gimp_drawable_filter_get_drawable (filter_tool->existing_filter));
+ }
+
if (filter_tool->filter)
{
GimpFilterOptions *options = GIMP_FILTER_TOOL_GET_OPTIONS (tool);
@@ -1039,10 +1119,36 @@ gimp_filter_tool_commit (GimpFilterTool *filter_tool)
if (! options->preview)
gimp_drawable_filter_apply (filter_tool->filter, NULL);
+ gimp_drawable_filter_layer_mask_freeze (filter_tool->filter);
+
+ if (non_destructive)
+ {
+ gimp_drawable_filter_abort (filter_tool->filter);
+
+ if (! filter_tool->existing_filter)
+ gimp_drawable_filter_apply (filter_tool->filter, NULL);
+ }
+
gimp_tool_control_push_preserve (tool->control, TRUE);
- gimp_drawable_filter_commit (filter_tool->filter,
- GIMP_PROGRESS (tool), TRUE);
+ if (! filter_tool->existing_filter)
+ gimp_drawable_filter_commit (filter_tool->filter, non_destructive,
+ GIMP_PROGRESS (tool), non_destructive);
+
+ g_signal_handlers_disconnect_by_func (filter_tool->filter,
+ gimp_filter_tool_flush,
+ filter_tool);
+
+ if (non_destructive && ! filter_tool->existing_filter)
+ {
+ GimpDrawable *drawable =
+ gimp_drawable_filter_get_drawable (filter_tool->filter);
+
+ gimp_image_undo_push_filter_add (gimp_display_get_image (tool->display),
+ _("Add filter"),
+ drawable, filter_tool->filter);
+ }
+
g_clear_object (&filter_tool->filter);
gimp_tool_control_pop_preserve (tool->control);
@@ -1059,6 +1165,11 @@ gimp_filter_tool_commit (GimpFilterTool *filter_tool)
config->filter_tool_max_recent);
}
}
+
+ if (filter_tool->existing_filter)
+ gimp_filter_set_active (GIMP_FILTER (filter_tool->existing_filter), TRUE);
+
+ filter_tool->existing_filter = NULL;
}
static void
@@ -1227,6 +1338,33 @@ gimp_filter_tool_create_filter (GimpFilterTool *filter_tool)
if (options->preview)
gimp_drawable_filter_apply (filter_tool->filter, NULL);
+
+ /* If editing existing filter, shift into the right location
+ * in the filter stack. */
+ if (filter_tool->existing_filter)
+ {
+ GimpContainer *filters;
+ GimpChannel *mask;
+ gint index;
+ const gchar *name = _("Editing filter...");
+
+ filters = gimp_drawable_get_filters (GIMP_DRAWABLE (tool->drawables->data));
+
+ if (filters)
+ {
+ index = gimp_container_get_child_index (filters,
+ GIMP_OBJECT (filter_tool->existing_filter));
+
+ gimp_container_reorder (filters, GIMP_OBJECT (filter_tool->filter),
+ index);
+ }
+
+ mask = gimp_drawable_filter_get_mask (filter_tool->existing_filter);
+ g_object_set (filter_tool->filter,
+ "name", name,
+ "mask", mask,
+ NULL);
+ }
}
static void
@@ -1594,7 +1732,8 @@ gimp_filter_tool_set_has_settings (GimpFilterTool *filter_tool,
/* public functions */
void
-gimp_filter_tool_get_operation (GimpFilterTool *filter_tool)
+gimp_filter_tool_get_operation (GimpFilterTool *filter_tool,
+ GimpDrawableFilter *existing_filter)
{
GimpTool *tool;
GimpFilterToolClass *klass;
@@ -1643,6 +1782,11 @@ gimp_filter_tool_get_operation (GimpFilterTool *filter_tool)
filter_tool->operation = gegl_node_new_child (NULL,
"operation", operation_name,
NULL);
+ if (existing_filter)
+ {
+ filter_tool->existing_filter = existing_filter;
+ gimp_filter_set_active (GIMP_FILTER (filter_tool->existing_filter), FALSE);
+ }
filter_tool->config =
g_object_new (gimp_operation_config_get_type (tool->tool_info->gimp,
@@ -1651,6 +1795,55 @@ gimp_filter_tool_get_operation (GimpFilterTool *filter_tool)
GIMP_TYPE_OPERATION_SETTINGS),
NULL);
+ /* Update layer effect if we're editing it */
+ if (filter_tool->existing_filter)
+ {
+ GeglNode *existing_node;
+ gdouble opacity;
+ GimpLayerMode paint_mode;
+ GimpFilterRegion region;
+ GParamSpec **pspecs;
+ guint n_pspecs;
+ const gchar *name;
+
+ opacity = gimp_drawable_filter_get_opacity (filter_tool->existing_filter);
+ paint_mode = gimp_drawable_filter_get_paint_mode (filter_tool->existing_filter);
+ region = gimp_drawable_filter_get_region (filter_tool->existing_filter);
+
+ existing_node = gimp_drawable_filter_get_operation (filter_tool->existing_filter);
+ gegl_node_get (existing_node,
+ "operation", &name,
+ NULL);
+
+ if (! strcmp (gimp_object_get_name (tool->tool_info), "gimp-operation-tool"))
+ {
+ pspecs = gegl_operation_list_properties (operation_name, &n_pspecs);
+
+ for (gint i = 0; i < n_pspecs; i++)
+ {
+ GValue value = G_VALUE_INIT;
+ GParamSpec *pspec = pspecs[i];
+ GParamSpec *gimp_pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (filter_tool->config),
+ pspec->name);
+
+ g_value_init (&value, pspec->value_type);
+ gegl_node_get_property (existing_node, pspec->name,
+ &value);
+
+ g_object_set_property (G_OBJECT (filter_tool->config), gimp_pspec->name,
+ &value);
+ g_value_unset (&value);
+ }
+ g_free (pspecs);
+ }
+
+ g_object_set (filter_tool->config,
+ "gimp-opacity", opacity,
+ "gimp-mode", paint_mode,
+ "gimp-region", region,
+ NULL);
+ }
+
gimp_operation_config_sync_node (filter_tool->config,
filter_tool->operation);
gimp_operation_config_connect_node (filter_tool->config,
diff --git a/app/tools/gimpfiltertool.h b/app/tools/gimpfiltertool.h
index b1c91cbae0..5735dd74e3 100644
--- a/app/tools/gimpfiltertool.h
+++ b/app/tools/gimpfiltertool.h
@@ -39,6 +39,7 @@ struct _GimpFilterTool
GimpColorTool parent_instance;
GeglNode *operation;
+ GimpDrawableFilter *existing_filter;
GObject *config;
GObject *default_config;
GimpContainer *settings;
@@ -103,7 +104,8 @@ struct _GimpFilterToolClass
GType gimp_filter_tool_get_type (void) G_GNUC_CONST;
-void gimp_filter_tool_get_operation (GimpFilterTool *filter_tool);
+void gimp_filter_tool_get_operation (GimpFilterTool *filter_tool,
+ GimpDrawableFilter *existing_filter);
void gimp_filter_tool_set_config (GimpFilterTool *filter_tool,
GimpConfig *config);
diff --git a/app/tools/gimpgegltool.c b/app/tools/gimpgegltool.c
index 48867144c7..9f2cc358d1 100644
--- a/app/tools/gimpgegltool.c
+++ b/app/tools/gimpgegltool.c
@@ -249,7 +249,7 @@ gimp_gegl_tool_halt (GimpGeglTool *gegl_tool)
{
GimpOperationTool *op_tool = GIMP_OPERATION_TOOL (gegl_tool);
- gimp_operation_tool_set_operation (op_tool, NULL,
+ gimp_operation_tool_set_operation (op_tool, NULL, NULL,
NULL, NULL, NULL, NULL, NULL);
}
@@ -272,10 +272,15 @@ gimp_gegl_tool_operation_changed (GtkWidget *widget,
if (operation)
{
+ const gchar *title;
const gchar *description;
+ title = gegl_operation_get_key (operation, "title");
description = gegl_operation_get_key (operation, "description");
+ if (! title)
+ title = gegl_operation_get_key (operation, "name");
+
if (description)
{
gtk_label_set_text (GTK_LABEL (tool->description_label), description);
@@ -287,9 +292,11 @@ gimp_gegl_tool_operation_changed (GtkWidget *widget,
}
gimp_operation_tool_set_operation (GIMP_OPERATION_TOOL (tool),
+ NULL,
operation,
_("GEGL Operation"),
- _("GEGL Operation"),
+ title ?
+ title : _("GEGL Operation"),
NULL,
GIMP_ICON_GEGL,
GIMP_HELP_TOOL_GEGL);
diff --git a/app/tools/gimpgradienttool.c b/app/tools/gimpgradienttool.c
index 5f3a96bce0..2f98807c87 100644
--- a/app/tools/gimpgradienttool.c
+++ b/app/tools/gimpgradienttool.c
@@ -769,7 +769,7 @@ gimp_gradient_tool_commit (GimpGradientTool *gradient_tool)
gimp_tool_control_push_preserve (tool->control, TRUE);
- gimp_drawable_filter_commit (gradient_tool->filter,
+ gimp_drawable_filter_commit (gradient_tool->filter, FALSE,
GIMP_PROGRESS (tool), FALSE);
g_clear_object (&gradient_tool->filter);
diff --git a/app/tools/gimpoperationtool.c b/app/tools/gimpoperationtool.c
index fdd2b8eaea..144c61a475 100644
--- a/app/tools/gimpoperationtool.c
+++ b/app/tools/gimpoperationtool.c
@@ -810,13 +810,14 @@ gimp_operation_tool_relink_chains (GimpOperationTool *op_tool)
/* public functions */
void
-gimp_operation_tool_set_operation (GimpOperationTool *op_tool,
- const gchar *operation,
- const gchar *title,
- const gchar *description,
- const gchar *undo_desc,
- const gchar *icon_name,
- const gchar *help_id)
+gimp_operation_tool_set_operation (GimpOperationTool *op_tool,
+ GimpDrawableFilter *filter,
+ const gchar *operation,
+ const gchar *title,
+ const gchar *description,
+ const gchar *undo_desc,
+ const gchar *icon_name,
+ const gchar *help_id)
{
GimpTool *tool;
GimpFilterTool *filter_tool;
@@ -855,7 +856,13 @@ gimp_operation_tool_set_operation (GimpOperationTool *op_tool,
if (! operation)
return;
- gimp_filter_tool_get_operation (filter_tool);
+ gimp_filter_tool_get_operation (filter_tool, filter);
+
+ /* Update filter name for GeglOperation tool presets */
+ if (filter_tool->filter && description)
+ g_object_set (filter_tool->filter,
+ "name", description,
+ NULL);
if (tool->drawables)
gimp_operation_tool_sync_op (op_tool, TRUE);
diff --git a/app/tools/gimpoperationtool.h b/app/tools/gimpoperationtool.h
index c35c8aba6d..523fb25bd1 100644
--- a/app/tools/gimpoperationtool.h
+++ b/app/tools/gimpoperationtool.h
@@ -60,6 +60,7 @@ void gimp_operation_tool_register (GimpToolRegisterCallback callback,
GType gimp_operation_tool_get_type (void) G_GNUC_CONST;
void gimp_operation_tool_set_operation (GimpOperationTool *op_tool,
+ GimpDrawableFilter *filter,
const gchar *operation,
const gchar *title,
const gchar *description,
diff --git a/app/tools/gimpseamlessclonetool.c b/app/tools/gimpseamlessclonetool.c
index a3dc6099ad..f6d1cd7975 100644
--- a/app/tools/gimpseamlessclonetool.c
+++ b/app/tools/gimpseamlessclonetool.c
@@ -379,7 +379,8 @@ gimp_seamless_clone_tool_commit (GimpSeamlessCloneTool *sc)
{
gimp_tool_control_push_preserve (tool->control, TRUE);
- gimp_drawable_filter_commit (sc->filter, GIMP_PROGRESS (tool), FALSE);
+ gimp_drawable_filter_commit (sc->filter, FALSE,
+ GIMP_PROGRESS (tool), FALSE);
g_clear_object (&sc->filter);
gimp_tool_control_pop_preserve (tool->control);
@@ -528,7 +529,8 @@ gimp_seamless_clone_tool_key_press (GimpTool *tool,
* 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);
+ gimp_drawable_filter_commit (sct->filter, FALSE,
+ GIMP_PROGRESS (tool), FALSE);
g_clear_object (&sct->filter);
gimp_tool_control_set_preserve (tool->control, FALSE);
diff --git a/app/tools/gimptexttool.c b/app/tools/gimptexttool.c
index 6bbdc80dd0..2e49bfc6a9 100644
--- a/app/tools/gimptexttool.c
+++ b/app/tools/gimptexttool.c
@@ -35,6 +35,8 @@
#include "core/gimpasyncset.h"
#include "core/gimpcontext.h"
#include "core/gimpdatafactory.h"
+#include "core/gimpdrawable-filters.h"
+#include "core/gimpdrawablefilter.h"
#include "core/gimperror.h"
#include "core/gimpimage.h"
#include "core/gimp-palettes.h"
@@ -42,6 +44,7 @@
#include "core/gimpimage-undo.h"
#include "core/gimpimage-undo-push.h"
#include "core/gimplayer-floating-selection.h"
+#include "core/gimplist.h"
#include "core/gimptoolinfo.h"
#include "core/gimpundostack.h"
@@ -1089,6 +1092,26 @@ gimp_text_tool_frame_item (GimpTextTool *text_tool)
gimp_tool_rectangle_frame_item (GIMP_TOOL_RECTANGLE (text_tool->widget),
GIMP_ITEM (text_tool->layer));
+ /* Update crop of any filters applied to text */
+ if (text_tool->layer)
+ {
+ GList *list;
+ GimpDrawable *drawable = GIMP_DRAWABLE (text_tool->layer);
+ GimpContainer *filters = gimp_drawable_get_filters (drawable);
+
+ for (list = GIMP_LIST (filters)->queue->tail;
+ list; list = g_list_previous (list))
+ {
+ GimpDrawableFilter *filter = list->data;
+
+ /* TODO: Handle partial layer effect */
+ gimp_drawable_filter_set_region (filter, GIMP_FILTER_REGION_SELECTION);
+ gimp_drawable_filter_set_region (filter, GIMP_FILTER_REGION_DRAWABLE);
+ }
+ if (list)
+ g_list_free (list);
+ }
+
text_tool->handle_rectangle_change_complete = TRUE;
}
diff --git a/app/tools/gimpwarptool.c b/app/tools/gimpwarptool.c
index ffe0b8b24f..82012cff45 100644
--- a/app/tools/gimpwarptool.c
+++ b/app/tools/gimpwarptool.c
@@ -982,7 +982,8 @@ gimp_warp_tool_commit (GimpWarpTool *wt)
gimp_warp_tool_set_sampler (wt, /* commit = */ TRUE);
- gimp_drawable_filter_commit (wt->filter, GIMP_PROGRESS (tool), FALSE);
+ gimp_drawable_filter_commit (wt->filter, FALSE,
+ GIMP_PROGRESS (tool), FALSE);
g_clear_object (&wt->filter);
gimp_tool_control_pop_preserve (tool->control);
diff --git a/app/widgets/gimpitemtreeview.c b/app/widgets/gimpitemtreeview.c
index 9212018dfd..b4321b387e 100644
--- a/app/widgets/gimpitemtreeview.c
+++ b/app/widgets/gimpitemtreeview.c
@@ -30,15 +30,23 @@
#include "widgets-types.h"
+#include "actions/gimpgeglprocedure.h"
+#include "actions/filters-commands.h"
+
#include "core/gimp.h"
+#include "core/gimp-filter-history.h"
#include "core/gimpchannel.h"
#include "core/gimpcontainer.h"
#include "core/gimpcontext.h"
+#include "core/gimpdrawable-filters.h"
+#include "core/gimpdrawablefilter.h"
+#include "core/gimpdrawablefilterundo.h"
#include "core/gimpimage.h"
#include "core/gimpimage-undo.h"
#include "core/gimpimage-undo-push.h"
#include "core/gimpitem-exclusive.h"
#include "core/gimpitemundo.h"
+#include "core/gimplist.h"
#include "core/gimptreehandler.h"
#include "core/gimpundostack.h"
@@ -50,6 +58,7 @@
#include "gimpdnd.h"
#include "gimpdocked.h"
#include "gimpitemtreeview.h"
+#include "gimplayertreeview.h"
#include "gimpmenufactory.h"
#include "gimpviewrenderer.h"
#include "gimpuimanager.h"
@@ -80,6 +89,21 @@ struct _GimpItemTreeViewPrivate
GtkWidget *lock_popover;
GtkWidget *lock_box;
+ GtkWidget *effects_popover;
+ GtkWidget *effects_box;
+ GtkWidget *effects_filters;
+ GtkWidget *effects_options;
+
+ GtkWidget *effects_visible_button;
+ GtkWidget *effects_edit_button;
+ GtkWidget *effects_raise_button;
+ GtkWidget *effects_lower_button;
+ GtkWidget *effects_merge_button;
+ GtkWidget *effects_remove_button;
+ GimpDrawable *effects_drawable;
+ GimpDrawableFilter
+ *effects_filter;
+
GList *locks;
GtkWidget *new_button;
@@ -93,11 +117,14 @@ struct _GimpItemTreeViewPrivate
gint model_column_locked;
gint model_column_lock_icon;
gint model_column_color_tag;
+ gint model_column_effects;
GtkCellRenderer *eye_cell;
GtkCellRenderer *lock_cell;
+ GtkCellRenderer *effects_cell;
GimpTreeHandler *visible_changed_handler;
GimpTreeHandler *color_tag_changed_handler;
+ GimpTreeHandler *filters_changed_handler;
};
typedef struct
@@ -200,6 +227,8 @@ static void gimp_item_tree_view_color_tag_changed (GimpItem *item,
GimpItemTreeView *view);
static void gimp_item_tree_view_lock_changed (GimpItem *item,
GimpItemTreeView *view);
+static void gimp_item_tree_view_filters_changed (GimpItem *item,
+ GimpItemTreeView *view);
static void gimp_item_tree_view_eye_clicked (GtkCellRendererToggle *toggle,
gchar *path,
@@ -209,9 +238,43 @@ static void gimp_item_tree_view_lock_clicked (GtkCellRendererToggle *togg
gchar *path,
GdkModifierType state,
GimpItemTreeView *view);
+static void gimp_item_tree_view_effects_clicked (GtkCellRendererToggle *toggle,
+ gchar *path,
+ GdkModifierType state,
+ GimpItemTreeView *view);
+static gboolean
+ gimp_item_tree_view_effects_filters_selected
+ (GimpContainerView *view,
+ GList *filters,
+ GList *paths,
+ GimpItemTreeView *item_view);
+static void gimp_item_tree_view_effects_activate_filter
+ (GtkWidget *widget,
+ GimpViewable *viewable,
+ gpointer insert_data,
+ GimpItemTreeView *view);
+
+static void gimp_item_tree_view_effects_visible_toggled
+ (GtkWidget *widget,
+ GimpItemTreeView *view);
+static void gimp_item_tree_view_effects_edited_clicked
+ (GtkWidget *widget,
+ GimpItemTreeView *view);
+static void gimp_item_tree_view_effects_raised_clicked
+ (GtkWidget *widget,
+ GimpItemTreeView *view);
+static void gimp_item_tree_view_effects_lowered_clicked
+ (GtkWidget *widget,
+ GimpItemTreeView *view);
+static void gimp_item_tree_view_effects_merged_clicked
+ (GtkWidget *widget,
+ GimpItemTreeView *view);
+static void gimp_item_tree_view_effects_removed_clicked
+ (GtkWidget *widget,
+ GimpItemTreeView *view);
static gboolean gimp_item_tree_view_lock_button_release (GtkWidget *widget,
- GdkEvent *event,
- GimpItemTreeView *view);
+ GdkEvent *event,
+ GimpItemTreeView *view);
static void gimp_item_tree_view_lock_toggled (GtkWidget *widget,
GimpItemTreeView *view);
static void gimp_item_tree_view_update_lock_box (GimpItemTreeView *view,
@@ -363,9 +426,16 @@ gimp_item_tree_view_init (GimpItemTreeView *view)
&tree_view->n_model_columns,
GDK_TYPE_RGBA);
+ view->priv->model_column_effects =
+ gimp_container_tree_store_columns_add (tree_view->model_columns,
+ &tree_view->n_model_columns,
+ G_TYPE_BOOLEAN);
+
gimp_container_tree_view_set_dnd_drop_to_empty (tree_view, TRUE);
- view->priv->image = NULL;
+ view->priv->image = NULL;
+ view->priv->effects_drawable = NULL;
+ view->priv->effects_filter = NULL;
}
static void
@@ -377,6 +447,8 @@ gimp_item_tree_view_constructed (GObject *object)
GimpItemTreeView *item_view = GIMP_ITEM_TREE_VIEW (object);
GtkTreeViewColumn *column;
GtkWidget *image;
+ GtkWidget *label;
+ gchar *text;
GtkIconSize button_icon_size = GTK_ICON_SIZE_SMALL_TOOLBAR;
gint pixel_icon_size = 16;
gint button_spacing;
@@ -462,6 +534,32 @@ gimp_item_tree_view_constructed (GObject *object)
G_CALLBACK (gimp_item_tree_view_lock_clicked),
item_view);
+ /* TODO: Expand layer effects to other drawable types */
+ if (GIMP_IS_LAYER_TREE_VIEW (object))
+ {
+ column = gtk_tree_view_column_new ();
+ gtk_tree_view_insert_column (tree_view->view, column, 2);
+
+ item_view->priv->effects_cell = gimp_cell_renderer_toggle_new (GIMP_ICON_DISPLAY_FILTER);
+ g_object_set (item_view->priv->effects_cell,
+ "xpad", 0,
+ "ypad", 0,
+ "icon-size", pixel_icon_size,
+ NULL);
+ gtk_tree_view_column_pack_start (column, item_view->priv->effects_cell, FALSE);
+ gtk_tree_view_column_set_attributes (column, item_view->priv->effects_cell,
+ "active",
+ item_view->priv->model_column_effects,
+ NULL);
+
+ gimp_container_tree_view_add_toggle_cell (tree_view,
+ item_view->priv->effects_cell);
+
+ g_signal_connect (item_view->priv->effects_cell, "clicked",
+ G_CALLBACK (gimp_item_tree_view_effects_clicked),
+ item_view);
+ }
+
/* disable the default GimpContainerView drop handler */
gimp_container_view_set_dnd_widget (GIMP_CONTAINER_VIEW (item_view), NULL);
@@ -577,6 +675,120 @@ gimp_item_tree_view_constructed (GObject *object)
item_view);
gtk_container_add (GTK_CONTAINER (item_view->priv->lock_popover), item_view->priv->lock_box);
gtk_widget_show (item_view->priv->lock_box);
+
+ /* Effects box. */
+ if (GIMP_IS_LAYER_TREE_VIEW (object))
+ {
+ item_view->priv->effects_filters = gtk_box_new (GTK_ORIENTATION_VERTICAL, button_spacing);
+ item_view->priv->effects_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, button_spacing);
+ item_view->priv->effects_options = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, button_spacing);
+
+ /* Effects Buttons */
+ item_view->priv->effects_visible_button = gtk_toggle_button_new ();
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (item_view->priv->effects_visible_button),
+ TRUE);
+ image = gtk_image_new_from_icon_name (GIMP_ICON_VISIBLE,
+ GTK_ICON_SIZE_SMALL_TOOLBAR);
+ gtk_container_add (GTK_CONTAINER (item_view->priv->effects_visible_button), image);
+
+ gimp_help_set_help_data (item_view->priv->effects_visible_button,
+ _("Toggle the visibility of all filters."),
+ NULL);
+ g_signal_connect (item_view->priv->effects_visible_button, "toggled",
+ G_CALLBACK (gimp_item_tree_view_effects_visible_toggled),
+ item_view);
+ gtk_box_pack_start (GTK_BOX (item_view->priv->effects_options),
+ item_view->priv->effects_visible_button, TRUE, TRUE, 0);
+ gtk_widget_set_visible (image, TRUE);
+ gtk_widget_set_visible (item_view->priv->effects_visible_button, TRUE);
+
+ item_view->priv->effects_edit_button =
+ gtk_button_new_from_icon_name (GIMP_ICON_EDIT,
+ GTK_ICON_SIZE_SMALL_TOOLBAR);
+ gimp_help_set_help_data (item_view->priv->effects_edit_button,
+ _("Edit the selected filter."),
+ NULL);
+ g_signal_connect (item_view->priv->effects_edit_button, "clicked",
+ G_CALLBACK (gimp_item_tree_view_effects_edited_clicked),
+ item_view);
+ gtk_box_pack_start (GTK_BOX (item_view->priv->effects_options),
+ item_view->priv->effects_edit_button, TRUE, TRUE, 0);
+ gtk_widget_set_visible (item_view->priv->effects_edit_button, TRUE);
+
+ item_view->priv->effects_raise_button =
+ gtk_button_new_from_icon_name (GIMP_ICON_GO_UP,
+ GTK_ICON_SIZE_SMALL_TOOLBAR);
+ gimp_help_set_help_data (item_view->priv->effects_raise_button,
+ _("Raise filter one step up in the stack."),
+ NULL);
+ g_signal_connect (item_view->priv->effects_raise_button, "clicked",
+ G_CALLBACK (gimp_item_tree_view_effects_raised_clicked),
+ item_view);
+ gtk_box_pack_start (GTK_BOX (item_view->priv->effects_options),
+ item_view->priv->effects_raise_button, TRUE, TRUE, 0);
+ gtk_widget_set_visible (item_view->priv->effects_raise_button, TRUE);
+
+ item_view->priv->effects_lower_button =
+ gtk_button_new_from_icon_name (GIMP_ICON_GO_DOWN,
+ GTK_ICON_SIZE_SMALL_TOOLBAR);
+ gimp_help_set_help_data (item_view->priv->effects_lower_button,
+ _("Lower filter one step down in the stack."),
+ NULL);
+ g_signal_connect (item_view->priv->effects_lower_button, "clicked",
+ G_CALLBACK (gimp_item_tree_view_effects_lowered_clicked),
+ item_view);
+ gtk_box_pack_start (GTK_BOX (item_view->priv->effects_options),
+ item_view->priv->effects_lower_button, TRUE, TRUE, 0);
+ gtk_widget_set_visible (item_view->priv->effects_lower_button, TRUE);
+
+ item_view->priv->effects_merge_button =
+ gtk_button_new_from_icon_name (GIMP_ICON_LAYER_MERGE_DOWN,
+ GTK_ICON_SIZE_SMALL_TOOLBAR);
+ gimp_help_set_help_data (item_view->priv->effects_merge_button,
+ _("Merge all active filters down."),
+ NULL);
+ g_signal_connect (item_view->priv->effects_merge_button, "clicked",
+ G_CALLBACK (gimp_item_tree_view_effects_merged_clicked),
+ item_view);
+ gtk_box_pack_start (GTK_BOX (item_view->priv->effects_options),
+ item_view->priv->effects_merge_button, TRUE, TRUE, 0);
+ gtk_widget_set_visible (item_view->priv->effects_merge_button, TRUE);
+
+ item_view->priv->effects_remove_button =
+ gtk_button_new_from_icon_name (GIMP_ICON_EDIT_DELETE,
+ GTK_ICON_SIZE_SMALL_TOOLBAR);
+ gimp_help_set_help_data (item_view->priv->effects_remove_button,
+ _("Remove the selected filter."),
+ NULL);
+ g_signal_connect (item_view->priv->effects_remove_button, "clicked",
+ G_CALLBACK (gimp_item_tree_view_effects_removed_clicked),
+ item_view);
+ gtk_box_pack_start (GTK_BOX (item_view->priv->effects_options),
+ item_view->priv->effects_remove_button, TRUE, TRUE, 0);
+ gtk_widget_set_visible (item_view->priv->effects_remove_button, TRUE);
+
+ label = gtk_label_new (NULL);
+ text = g_strdup_printf ("%s",
+ _("Layer Effects"));
+ gtk_label_set_markup (GTK_LABEL (label), text);
+ gtk_widget_set_visible (label, TRUE);
+ g_free (text);
+
+ /* Effects popover. */
+ item_view->priv->effects_popover = gtk_popover_new (GTK_WIDGET (tree_view->view));
+ gtk_popover_set_modal (GTK_POPOVER (item_view->priv->effects_popover), TRUE);
+ gtk_container_add (GTK_CONTAINER (item_view->priv->effects_popover),
+ item_view->priv->effects_filters);
+ gtk_box_pack_start (GTK_BOX (item_view->priv->effects_filters), label,
+ FALSE, FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (item_view->priv->effects_filters),
+ item_view->priv->effects_box, FALSE, FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (item_view->priv->effects_filters),
+ item_view->priv->effects_options, FALSE, FALSE, 0);
+ gtk_widget_set_visible (item_view->priv->effects_box, TRUE);
+ gtk_widget_set_visible (item_view->priv->effects_options, TRUE);
+ gtk_widget_set_visible (item_view->priv->effects_filters, TRUE);
+ }
}
static void
@@ -606,6 +818,14 @@ gimp_item_tree_view_dispose (GObject *object)
view->priv->locks = NULL;
}
+ if (view->priv->effects_popover)
+ {
+ gtk_widget_destroy (view->priv->effects_popover);
+ view->priv->effects_popover = NULL;
+ }
+ view->priv->effects_drawable = NULL;
+ view->priv->effects_filter = NULL;
+
G_OBJECT_CLASS (parent_class)->dispose (object);
}
@@ -722,6 +942,12 @@ gimp_item_tree_view_style_updated (GtkWidget *widget)
"icon-size", pixel_icon_size,
NULL);
+ if (GIMP_IS_LAYER_TREE_VIEW (view))
+ g_object_set (view->priv->effects_cell,
+ "icon-name", GIMP_ICON_DISPLAY_FILTER,
+ "icon-size", pixel_icon_size,
+ NULL);
+
GTK_WIDGET_CLASS (parent_class)->style_updated (widget);
}
@@ -1033,7 +1259,9 @@ gimp_item_tree_view_real_set_image (GimpItemTreeView *view,
view);
}
- view->priv->image = image;
+ view->priv->image = image;
+ view->priv->effects_drawable = NULL;
+ view->priv->effects_filter = NULL;
if (view->priv->image)
{
@@ -1106,6 +1334,12 @@ gimp_item_tree_view_set_container (GimpContainerView *view,
gimp_tree_handler_disconnect (item_view->priv->color_tag_changed_handler);
item_view->priv->color_tag_changed_handler = NULL;
+ if (GIMP_IS_LAYER_TREE_VIEW (item_view))
+ {
+ gimp_tree_handler_disconnect (item_view->priv->filters_changed_handler);
+ item_view->priv->filters_changed_handler = NULL;
+ }
+
for (list = item_view->priv->locks; list; list = list->next)
{
LockToggle *data = list->data;
@@ -1138,6 +1372,12 @@ gimp_item_tree_view_set_container (GimpContainerView *view,
G_CALLBACK (gimp_item_tree_view_lock_changed),
view);
}
+
+ if (GIMP_IS_LAYER_TREE_VIEW (item_view))
+ item_view->priv->filters_changed_handler =
+ gimp_tree_handler_connect (container, "filters-changed",
+ G_CALLBACK (gimp_item_tree_view_filters_changed),
+ view);
}
}
@@ -1236,6 +1476,20 @@ gimp_item_tree_view_insert_item (GimpContainerView *view,
has_color ? (GdkRGBA *) &color : NULL,
-1);
+ if (GIMP_IS_LAYER_TREE_VIEW (item_view))
+ {
+ GimpContainer *filters;
+ gint n_filters;
+
+ filters = gimp_drawable_get_filters (GIMP_DRAWABLE (item));
+ n_filters = gimp_container_get_n_children (filters);
+
+ gtk_tree_store_set (GTK_TREE_STORE (tree_view->model), iter,
+ item_view->priv->model_column_effects,
+ n_filters > 0,
+ -1);
+ }
+
return iter;
}
@@ -1764,6 +2018,407 @@ gimp_item_tree_view_lock_clicked (GtkCellRendererToggle *toggle,
}
}
+/* "Effects" callbacks */
+static void
+gimp_item_tree_view_effects_clicked (GtkCellRendererToggle *toggle,
+ gchar *path_str,
+ GdkModifierType state,
+ GimpItemTreeView *view)
+{
+ GtkTreePath *path;
+ GtkTreeIter iter;
+
+ path = gtk_tree_path_new_from_string (path_str);
+
+ if (gtk_tree_model_get_iter (GIMP_CONTAINER_TREE_VIEW (view)->model,
+ &iter, path))
+ {
+ GimpViewRenderer *renderer;
+ GimpContainerTreeStore *store;
+ GimpItem *item;
+ GimpContainer *filters;
+ GdkRectangle rect;
+ GtkWidget *filter_view;
+ GList *filter_list;
+ GList *children;
+ gint n_children = 0;
+ gboolean visible = TRUE;
+
+ children = gtk_container_get_children (GTK_CONTAINER (view->priv->effects_box));
+
+ /* Update the filter state. */
+ store = GIMP_CONTAINER_TREE_STORE (GIMP_CONTAINER_TREE_VIEW (view)->model);
+ renderer = gimp_container_tree_store_get_renderer (store, &iter);
+ item = GIMP_ITEM (renderer->viewable);
+ g_object_unref (renderer);
+
+ /* Get filters */
+ if (children)
+ {
+ g_signal_handlers_disconnect_by_func (children->data,
+ gimp_item_tree_view_effects_filters_selected,
+ view);
+ g_signal_handlers_disconnect_by_func (children->data,
+ gimp_item_tree_view_effects_activate_filter,
+ view);
+ gtk_widget_destroy (children->data);
+ g_list_free (children);
+ }
+
+ view->priv->effects_drawable = GIMP_DRAWABLE (item);
+ filters = gimp_drawable_get_filters (GIMP_DRAWABLE (item));
+ for (filter_list = GIMP_LIST (filters)->queue->tail; filter_list;
+ filter_list = g_list_previous (filter_list))
+ {
+ if (GIMP_IS_DRAWABLE_FILTER (filter_list->data))
+ {
+ if (! gimp_filter_get_active (GIMP_FILTER (filter_list->data)))
+ visible = FALSE;
+
+ n_children++;
+ }
+ }
+
+ /* TODO: Revisit when we can set individual filter visiblity */
+ g_signal_handlers_block_by_func (view->priv->effects_visible_button,
+ gimp_item_tree_view_effects_visible_toggled,
+ view);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (view->priv->effects_visible_button),
+ visible);
+ g_signal_handlers_unblock_by_func (view->priv->effects_visible_button,
+ gimp_item_tree_view_effects_visible_toggled,
+ view);
+
+ /* Only show if we have at least one active filter */
+ if (n_children > 0)
+ {
+ filter_view = gimp_container_tree_view_new (filters,
+ gimp_container_view_get_context (GIMP_CONTAINER_VIEW (view)),
+ GIMP_VIEW_SIZE_SMALL, 0);
+ g_signal_connect (GIMP_CONTAINER_TREE_VIEW (filter_view),
+ "select-items",
+ G_CALLBACK (gimp_item_tree_view_effects_filters_selected),
+ view);
+ g_signal_connect_object (GIMP_CONTAINER_TREE_VIEW (filter_view),
+ "activate-item",
+ G_CALLBACK (gimp_item_tree_view_effects_activate_filter),
+ view, 0);
+
+ gtk_box_pack_start (GTK_BOX (view->priv->effects_box), filter_view, TRUE, TRUE, 0);
+ gtk_widget_set_visible (filter_view, TRUE);
+
+ gtk_widget_set_size_request (view->priv->effects_box, 200, 24 * (n_children + 1));
+
+ /* Change popover position. */
+ gtk_tree_view_get_cell_area (GIMP_CONTAINER_TREE_VIEW (view)->view, path,
+ gtk_tree_view_get_column (GIMP_CONTAINER_TREE_VIEW (view)->view, 2),
+ &rect);
+ gtk_tree_view_convert_bin_window_to_widget_coords (GIMP_CONTAINER_TREE_VIEW (view)->view,
+ rect.x, rect.y, &rect.x, &rect.y);
+ gtk_popover_set_pointing_to (GTK_POPOVER (view->priv->effects_popover), &rect);
+
+ gimp_item_tree_view_filters_changed (item, view);
+ gtk_widget_show (view->priv->effects_popover);
+ }
+ }
+}
+
+static gboolean
+gimp_item_tree_view_effects_filters_selected (GimpContainerView *view,
+ GList *filters,
+ GList *paths,
+ GimpItemTreeView *item_view)
+{
+ g_return_val_if_fail (g_list_length (filters) <= 1, FALSE);
+
+ if (filters &&
+ item_view->priv->effects_drawable &&
+ GIMP_IS_DRAWABLE (item_view->priv->effects_drawable))
+ {
+ GimpDrawableFilter *filter = filters->data;
+ GimpContainer *container;
+ gint index;
+ gint n_children;
+
+ item_view->priv->effects_filter = filter;
+
+ container =
+ gimp_drawable_get_filters (GIMP_DRAWABLE (item_view->priv->effects_drawable));
+
+ index = gimp_container_get_child_index (container,
+ GIMP_OBJECT (filter));
+
+ n_children = gimp_container_get_n_children (container);
+
+ gtk_widget_set_sensitive (item_view->priv->effects_raise_button,
+ (index != 0));
+ gtk_widget_set_sensitive (item_view->priv->effects_lower_button,
+ (index != n_children - 1));
+ }
+
+ return TRUE;
+}
+
+static void
+gimp_item_tree_view_effects_activate_filter (GtkWidget *widget,
+ GimpViewable *viewable,
+ gpointer insert_data,
+ GimpItemTreeView *view)
+{
+ if (gtk_widget_get_sensitive (view->priv->effects_edit_button))
+ gimp_item_tree_view_effects_edited_clicked (widget, view);
+}
+
+static void
+gimp_item_tree_view_effects_visible_toggled (GtkWidget *widget,
+ GimpItemTreeView *view)
+{
+ if (view->priv->effects_drawable)
+ {
+ gboolean visible;
+ GimpContainer *filter_stack;
+ GList *list;
+ GimpImage *image = view->priv->image;
+
+ visible = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget));
+ filter_stack = gimp_drawable_get_filters (GIMP_DRAWABLE (view->priv->effects_drawable));
+
+ for (list = GIMP_LIST (filter_stack)->queue->head; list;
+ list = g_list_next (list))
+ {
+ if (GIMP_IS_DRAWABLE_FILTER (list->data))
+ {
+ GimpFilter *filter = list->data;
+
+ gimp_filter_set_active (filter, visible);
+ }
+ }
+
+ /* Hack to make the effects visibly change */
+ gimp_item_set_visible (GIMP_ITEM (view->priv->effects_drawable), FALSE, FALSE);
+ gimp_image_flush (image);
+ gimp_item_set_visible (GIMP_ITEM (view->priv->effects_drawable), TRUE, FALSE);
+ gimp_image_flush (image);
+ }
+}
+
+static void
+gimp_item_tree_view_effects_edited_clicked (GtkWidget *widget,
+ GimpItemTreeView *view)
+{
+ if (! view->priv->effects_filter ||
+ ! GIMP_IS_DRAWABLE_FILTER (view->priv->effects_filter))
+ return;
+
+ if (view->priv->effects_drawable)
+ {
+ GimpImage *image = view->priv->image;
+ GeglNode *op = gimp_drawable_filter_get_operation (view->priv->effects_filter);
+
+ if (op)
+ {
+ GimpProcedure *procedure;
+ GVariant *variant;
+ gchar *operation;
+ gchar *name;
+
+ g_object_get (view->priv->effects_filter,
+ "name", &name,
+ NULL);
+ g_object_get (op,
+ "operation", &operation,
+ NULL);
+
+ if (operation)
+ {
+ procedure = gimp_gegl_procedure_new (image->gimp,
+ view->priv->effects_filter,
+ GIMP_RUN_INTERACTIVE, NULL,
+ operation,
+ name,
+ name,
+ NULL, NULL, NULL);
+
+ gimp_filter_history_add (image->gimp, procedure);
+
+ variant = g_variant_new_uint64 (GPOINTER_TO_SIZE (procedure));
+ g_variant_take_ref (variant);
+ filters_run_procedure (image->gimp,
+ gimp_context_get_display (gimp_get_user_context (image->gimp)),
+ procedure, GIMP_RUN_INTERACTIVE);
+
+ g_variant_unref (variant);
+ g_object_unref (procedure);
+
+ /* Disable buttons until we're done editing */
+ gtk_widget_set_sensitive (view->priv->effects_box, FALSE);
+ gtk_widget_set_sensitive (view->priv->effects_visible_button, FALSE);
+ gtk_widget_set_sensitive (view->priv->effects_edit_button, FALSE);
+ gtk_widget_set_sensitive (view->priv->effects_raise_button, FALSE);
+ gtk_widget_set_sensitive (view->priv->effects_lower_button, FALSE);
+ gtk_widget_set_sensitive (view->priv->effects_merge_button, FALSE);
+ gtk_widget_set_sensitive (view->priv->effects_remove_button, FALSE);
+ }
+
+ g_free (name);
+ g_free (operation);
+ }
+ }
+}
+
+static void
+gimp_item_tree_view_effects_raised_clicked (GtkWidget *widget,
+ GimpItemTreeView *view)
+{
+ if (! view->priv->effects_filter ||
+ ! GIMP_IS_DRAWABLE_FILTER (view->priv->effects_filter))
+ return;
+
+ if (view->priv->effects_drawable)
+ {
+ GimpImage *image = view->priv->image;
+ GimpContainer *filters;
+ gint index;
+
+ filters = gimp_drawable_get_filters (GIMP_DRAWABLE (view->priv->effects_drawable));
+
+ index = gimp_container_get_child_index (filters,
+ GIMP_OBJECT (view->priv->effects_filter));
+ index--;
+
+ if (index >= 0)
+ {
+ gimp_image_undo_push_filter_reorder (image, _("Reorder filter"),
+ view->priv->effects_drawable,
+ view->priv->effects_filter);
+
+ gimp_container_reorder (filters, GIMP_OBJECT (view->priv->effects_filter),
+ index);
+
+ if (gtk_widget_get_sensitive (view->priv->effects_edit_button))
+ {
+ gtk_widget_set_sensitive (view->priv->effects_lower_button, TRUE);
+ if (index == 0)
+ gtk_widget_set_sensitive (view->priv->effects_raise_button, FALSE);
+ }
+
+ /* Hack to make the effects visibly change */
+ gimp_item_set_visible (GIMP_ITEM (view->priv->effects_drawable), FALSE, FALSE);
+ gimp_image_flush (image);
+ gimp_item_set_visible (GIMP_ITEM (view->priv->effects_drawable), TRUE, FALSE);
+ gimp_image_flush (image);
+ }
+ }
+}
+
+static void
+gimp_item_tree_view_effects_lowered_clicked (GtkWidget *widget,
+ GimpItemTreeView *view)
+{
+ if (! view->priv->effects_filter ||
+ ! GIMP_IS_DRAWABLE_FILTER (view->priv->effects_filter))
+ return;
+
+ if (view->priv->effects_drawable)
+ {
+ GimpImage *image = view->priv->image;
+ GimpContainer *filters;
+ gint index;
+
+ filters = gimp_drawable_get_filters (GIMP_DRAWABLE (view->priv->effects_drawable));
+
+ index = gimp_container_get_child_index (filters,
+ GIMP_OBJECT (view->priv->effects_filter));
+ index++;
+
+ if (index < gimp_container_get_n_children (filters))
+ {
+ gimp_image_undo_push_filter_reorder (image, _("Reorder filter"),
+ view->priv->effects_drawable,
+ view->priv->effects_filter);
+
+ gimp_container_reorder (filters, GIMP_OBJECT (view->priv->effects_filter),
+ index);
+
+ if (gtk_widget_get_sensitive (view->priv->effects_edit_button))
+ {
+ gtk_widget_set_sensitive (view->priv->effects_raise_button, TRUE);
+ if (index == gimp_container_get_n_children (filters) - 1)
+ gtk_widget_set_sensitive (view->priv->effects_lower_button, FALSE);
+ }
+
+ /* Hack to make the effects visibly change */
+ gimp_item_set_visible (GIMP_ITEM (view->priv->effects_drawable), FALSE, FALSE);
+ gimp_image_flush (image);
+ gimp_item_set_visible (GIMP_ITEM (view->priv->effects_drawable), TRUE, FALSE);
+ gimp_image_flush (image);
+ }
+ }
+}
+
+static void
+gimp_item_tree_view_effects_merged_clicked (GtkWidget *widget,
+ GimpItemTreeView *view)
+{
+ if (! view->priv->effects_filter ||
+ ! GIMP_IS_DRAWABLE_FILTER (view->priv->effects_filter))
+ return;
+
+ if (view->priv->effects_drawable)
+ {
+ GimpImage *image = view->priv->image;
+ GeglNode *op = gimp_drawable_filter_get_operation (view->priv->effects_filter);
+
+ if (op)
+ {
+ gimp_drawable_merge_filters (GIMP_DRAWABLE (view->priv->effects_drawable));
+ gimp_drawable_clear_filters (GIMP_DRAWABLE (view->priv->effects_drawable));
+
+ /* Hack to make the effects visibly change */
+ gimp_item_set_visible (GIMP_ITEM (view->priv->effects_drawable), FALSE, FALSE);
+ gimp_image_flush (image);
+ gimp_item_set_visible (GIMP_ITEM (view->priv->effects_drawable), TRUE, FALSE);
+ gimp_image_flush (image);
+ }
+ }
+}
+
+static void
+gimp_item_tree_view_effects_removed_clicked (GtkWidget *widget,
+ GimpItemTreeView *view)
+{
+ if (view->priv->effects_drawable)
+ {
+ GimpImage *image = view->priv->image;
+ GeglNode *op = NULL;
+
+ if (view->priv->effects_filter &&
+ GIMP_IS_DRAWABLE_FILTER (view->priv->effects_filter))
+ op = gimp_drawable_filter_get_operation (view->priv->effects_filter);
+
+ if (op)
+ {
+ gimp_image_undo_push_filter_remove (image, _("Remove filter"),
+ view->priv->effects_drawable,
+ view->priv->effects_filter);
+
+ gimp_drawable_filter_abort (view->priv->effects_filter);
+
+ view->priv->effects_filter = NULL;
+ }
+ else
+ {
+ gimp_drawable_remove_last_filter (GIMP_DRAWABLE (view->priv->effects_drawable));
+ }
+
+ /* Hack to make the effects visibly change */
+ gimp_item_set_visible (GIMP_ITEM (view->priv->effects_drawable), FALSE, FALSE);
+ gimp_image_flush (image);
+ gimp_item_set_visible (GIMP_ITEM (view->priv->effects_drawable), TRUE, FALSE);
+ gimp_image_flush (image);
+ }
+}
+
/* "Color Tag" callbacks */
@@ -1836,6 +2491,68 @@ gimp_item_tree_view_lock_changed (GimpItem *item,
gimp_item_tree_view_update_lock_box (view, item, NULL);
}
+static void
+gimp_item_tree_view_filters_changed (GimpItem *item,
+ GimpItemTreeView *view)
+{
+ GimpContainerView *container_view = GIMP_CONTAINER_VIEW (view);
+ GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (view);
+ GtkTreeIter *iter;
+ GimpContainer *filters;
+ GList *filter_list = NULL;
+ gint n_filters = 0;
+ gboolean fs_disabled = FALSE;
+
+ iter = gimp_container_view_lookup (container_view,
+ (GimpViewable *) item);
+
+ filters = gimp_drawable_get_filters (GIMP_DRAWABLE (item));
+ /* Since floating selections are also stored in the filter stack,
+ * we need to verify what's in there to get the correct count */
+ for (filter_list = GIMP_LIST (filters)->queue->tail; filter_list;
+ filter_list = g_list_previous (filter_list))
+ {
+ if (GIMP_IS_DRAWABLE_FILTER (filter_list->data))
+ n_filters++;
+ else
+ fs_disabled = TRUE;
+ }
+
+ if (fs_disabled)
+ view->priv->effects_filter = NULL;
+
+ if (iter)
+ gtk_tree_store_set (GTK_TREE_STORE (tree_view->model), iter,
+ view->priv->model_column_effects,
+ n_filters > 0,
+ -1);
+
+ /* Re-enable buttons after editing */
+ gtk_widget_set_sensitive (view->priv->effects_box, ! fs_disabled);
+ gtk_widget_set_sensitive (view->priv->effects_visible_button, ! fs_disabled);
+ gtk_widget_set_sensitive (view->priv->effects_edit_button, ! fs_disabled);
+ gtk_widget_set_sensitive (view->priv->effects_raise_button, ! fs_disabled);
+ gtk_widget_set_sensitive (view->priv->effects_lower_button, ! fs_disabled);
+ gtk_widget_set_sensitive (view->priv->effects_merge_button, ! fs_disabled);
+ gtk_widget_set_sensitive (view->priv->effects_remove_button, ! fs_disabled);
+
+ if (view->priv->effects_popover &&
+ view->priv->effects_filter &&
+ ! fs_disabled)
+ {
+ if (GIMP_IS_DRAWABLE_FILTER (view->priv->effects_filter))
+ {
+ gint index = gimp_container_get_child_index (filters,
+ GIMP_OBJECT (view->priv->effects_filter));
+
+ if (index == 0)
+ gtk_widget_set_sensitive (view->priv->effects_raise_button, FALSE);
+ else if (index == (n_filters - 1))
+ gtk_widget_set_sensitive (view->priv->effects_lower_button, FALSE);
+ }
+ }
+}
+
static gboolean
gimp_item_tree_view_lock_button_release (GtkWidget *widget,
GdkEvent *event,
diff --git a/app/xcf/xcf-load.c b/app/xcf/xcf-load.c
index 29f08256c4..7e5015a505 100644
--- a/app/xcf/xcf-load.c
+++ b/app/xcf/xcf-load.c
@@ -36,7 +36,10 @@
#include "core/gimp.h"
#include "core/gimpcontainer.h"
+#include "core/gimpdrawable-filters.h"
#include "core/gimpdrawable-private.h" /* eek */
+#include "core/gimpdrawablefilter.h"
+#include "core/gimpfilterstack.h"
#include "core/gimpgrid.h"
#include "core/gimpgrouplayer.h"
#include "core/gimpimage.h"
@@ -85,8 +88,27 @@
/* #define GIMP_XCF_PATH_DEBUG */
+/* Filters can not be created until a layer is attached
+ * to an image, so we use this struct to store relevant
+ * information until then */
+typedef struct
+{
+ GeglNode *operation;
+ gchar *name;
+ gchar *icon_name;
+ GimpChannel *mask;
+ gboolean is_visible;
+ gdouble opacity;
+ GimpLayerMode paint_mode;
+ GimpLayerColorSpace blend_space;
+ GimpLayerColorSpace composite_space;
+ GimpLayerCompositeMode composite_mode;
+ GimpFilterRegion region;
+} FilterData;
static void xcf_load_add_masks (GimpImage *image);
+static void xcf_load_add_effects (XcfInfo *info,
+ GimpImage *image);
static gboolean xcf_load_image_props (XcfInfo *info,
GimpImage *image);
static gboolean xcf_load_layer_props (XcfInfo *info,
@@ -105,6 +127,8 @@ static gboolean xcf_check_layer_props (XcfInfo *info,
static gboolean xcf_load_channel_props (XcfInfo *info,
GimpImage *image,
GimpChannel **channel);
+static gboolean xcf_load_effect_props (XcfInfo *info,
+ FilterData *filter);
static gboolean xcf_load_vectors_props (XcfInfo *info,
GimpImage *image,
GimpVectors **vectors);
@@ -116,6 +140,10 @@ static GimpLayer * xcf_load_layer (XcfInfo *info,
GList **item_path);
static GimpChannel * xcf_load_channel (XcfInfo *info,
GimpImage *image);
+static FilterData * xcf_load_effect (XcfInfo *info,
+ GimpDrawable *drawable);
+static void xcf_load_free_effect (FilterData *data);
+static void xcf_load_free_effects (GList *effects);
static GimpVectors * xcf_load_vectors (XcfInfo *info,
GimpImage *image);
static GimpLayerMask * xcf_load_layer_mask (XcfInfo *info,
@@ -767,7 +795,10 @@ xcf_load_image (Gimp *gimp,
}
if (n_broken_layers == 0 && n_broken_channels == 0)
- xcf_load_add_masks (image);
+ {
+ xcf_load_add_masks (image);
+ xcf_load_add_effects (info, image);
+ }
if (info->floating_sel && info->floating_sel_drawable)
{
@@ -948,6 +979,7 @@ xcf_load_image (Gimp *gimp,
"of it as I can, but it is incomplete."));
xcf_load_add_masks (image);
+ xcf_load_add_effects (info, image);
gimp_image_undo_enable (image);
@@ -1015,6 +1047,86 @@ xcf_load_add_masks (GimpImage *image)
g_list_free (layers);
}
+static void
+xcf_load_add_effects (XcfInfo *info,
+ GimpImage *image)
+{
+ GList *layers;
+ GList *list;
+
+ layers = gimp_image_get_layer_list (image);
+
+ for (list = layers; list; list = g_list_next (list))
+ {
+ GimpLayer *layer = list->data;
+ GList *effects_nodes;
+
+ effects_nodes = g_object_get_data (G_OBJECT (layer), "gimp-layer-effects");
+
+ if (effects_nodes)
+ {
+ GList *iter;
+
+ for (iter = effects_nodes; iter; iter = iter->next)
+ {
+ FilterData *data = iter->data;
+ GSList *children;
+
+ if (! data->icon_name)
+ data->icon_name = g_strdup ("gimp-gegl");
+
+ children = gegl_node_get_children (data->operation);
+
+ if (g_slist_length (children) == 1)
+ {
+ GimpDrawableFilter *filter = NULL;
+ GeglNode *op;
+
+ op = g_object_ref (children->data);
+ gegl_node_remove_child (data->operation, op);
+ filter = gimp_drawable_filter_new (GIMP_DRAWABLE (layer),
+ data->name, op,
+ data->icon_name);
+
+ gimp_drawable_filter_set_opacity (filter, data->opacity);
+ gimp_drawable_filter_set_mode (filter, data->paint_mode,
+ data->blend_space,
+ data->composite_space,
+ data->composite_mode);
+ gimp_drawable_filter_set_region (filter, data->region);
+
+ gimp_drawable_filter_apply (filter, NULL);
+
+ g_object_set (filter,
+ "mask", data->mask,
+ NULL);
+
+ gimp_drawable_filter_commit (filter, TRUE, NULL, FALSE);
+
+ gimp_drawable_filter_layer_mask_freeze (filter);
+
+ g_object_unref (op);
+ g_object_unref (filter);
+ }
+ else
+ {
+ gimp_message (info->gimp, G_OBJECT (info->progress),
+ GIMP_MESSAGE_WARNING,
+ "XCF Warning: failed loading filter \"%s\": "
+ "node has %d children, 1 expected.",
+ data->name, g_slist_length (children));
+ }
+
+ g_slist_free (children);
+ }
+
+ g_object_set_data (G_OBJECT (layer), "gimp-layer-effects", NULL);
+ }
+ }
+
+ g_list_free (layers);
+}
+
static gboolean
xcf_load_image_props (XcfInfo *info,
GimpImage *image)
@@ -2271,6 +2383,145 @@ xcf_load_channel_props (XcfInfo *info,
return FALSE;
}
+static gboolean
+xcf_load_effect_props (XcfInfo *info,
+ FilterData *filter)
+{
+ PropType prop_type;
+ guint32 prop_size;
+
+ while (TRUE)
+ {
+ if (! xcf_load_prop (info, &prop_type, &prop_size))
+ return FALSE;
+
+ switch (prop_type)
+ {
+ case PROP_END:
+ return TRUE;
+
+ case PROP_FILTER_NAME:
+ {
+ gchar *filter_name;
+ goffset base = info->cp;
+
+ /* Go back so we can get the size of the string */
+ xcf_seek_pos (info, base - sizeof (guint32), NULL);
+
+ xcf_read_string (info, &filter_name, 1);
+ filter->name = filter_name;
+ }
+ break;
+
+ case PROP_FILTER_ICON:
+ {
+ gchar *filter_icon;
+ goffset base = info->cp;
+
+ /* Go back so we can get the size of the string */
+ xcf_seek_pos (info, base - sizeof (guint32), NULL);
+
+ xcf_read_string (info, &filter_icon, 1);
+ filter->icon_name = filter_icon;
+ }
+ break;
+
+ case PROP_VISIBLE:
+ {
+ gboolean visible;
+
+ xcf_read_int32 (info, (guint32 *) &visible, 1);
+ filter->is_visible = visible;
+ }
+ break;
+
+ case PROP_OPACITY:
+ {
+ guint32 opacity;
+
+ xcf_read_int32 (info, &opacity, 1);
+ filter->opacity = (gdouble) opacity / 255.0;
+ }
+ break;
+
+ case PROP_MODE:
+ {
+ GimpLayerMode mode;
+
+ xcf_read_int32 (info, (guint32 *) &mode, 1);
+
+ if (mode == GIMP_LAYER_MODE_OVERLAY_LEGACY)
+ mode = GIMP_LAYER_MODE_SOFTLIGHT_LEGACY;
+
+ filter->paint_mode = mode;
+ }
+ break;
+
+ case PROP_BLEND_SPACE:
+ {
+ gint32 blend_space;
+
+ xcf_read_int32 (info, (guint32 *) &blend_space, 1);
+
+ /* TODO: Revisit when blend space can be set
+ * on filter effects */
+ blend_space = GIMP_LAYER_COLOR_SPACE_AUTO;
+
+ filter->blend_space = blend_space;
+ }
+ break;
+
+ case PROP_COMPOSITE_SPACE:
+ {
+ gint32 composite_space;
+
+ xcf_read_int32 (info, (guint32 *) &composite_space, 1);
+
+ /* TODO: Revisit when composite space can be set
+ * on filter effects */
+ composite_space = GIMP_LAYER_COLOR_SPACE_AUTO;
+
+ filter->composite_space = composite_space;
+ }
+ break;
+
+ case PROP_COMPOSITE_MODE:
+ {
+ gint32 composite_mode;
+
+ xcf_read_int32 (info, (guint32 *) &composite_mode, 1);
+
+ /* TODO: Revisit when composite mode can be set
+ * on filter effects */
+ composite_mode = GIMP_LAYER_COMPOSITE_AUTO;
+
+ filter->composite_mode = composite_mode;
+ }
+ break;
+
+ case PROP_FILTER_REGION:
+ {
+ GimpFilterRegion region;
+
+ xcf_read_int32 (info, (guint32 *) ®ion, 1);
+
+ filter->region = region;
+ }
+ break;
+
+ default:
+#ifdef GIMP_UNSTABLE
+ g_printerr ("unexpected/unknown effect property: %d (skipping)\n",
+ prop_type);
+#endif
+ if (! xcf_skip_unknown_prop (info, prop_size))
+ return FALSE;
+ }
+ }
+
+ return FALSE;
+}
+
static gboolean
xcf_load_vectors_props (XcfInfo *info,
GimpImage *image,
@@ -2454,6 +2705,7 @@ xcf_load_layer (XcfInfo *info,
GimpLayer *layer;
GimpLayerMask *layer_mask;
goffset hierarchy_offset;
+ goffset effects_offset = 0;
goffset layer_mask_offset;
gboolean apply_mask = TRUE;
gboolean edit_mask = FALSE;
@@ -2618,6 +2870,8 @@ xcf_load_layer (XcfInfo *info,
cur_offset = info->cp;
xcf_read_offset (info, &hierarchy_offset, 1);
xcf_read_offset (info, &layer_mask_offset, 1);
+ if (info->file_version >= 20)
+ xcf_read_offset (info, &effects_offset, 1);
/* read in the hierarchy (ignore it for group layers, both as an
* optimization and because the hierarchy's extents don't match
@@ -2650,6 +2904,8 @@ xcf_load_layer (XcfInfo *info,
gimp_viewable_set_expanded (GIMP_VIEWABLE (layer), expanded);
}
+ cur_offset += info->bytes_per_offset;
+
/* read in the layer mask */
if (layer_mask_offset != 0)
{
@@ -2665,6 +2921,8 @@ xcf_load_layer (XcfInfo *info,
if (! layer_mask)
goto error;
+ cur_offset += info->bytes_per_offset;
+
xcf_progress_update (info);
/* don't add the layer mask yet, that won't work for group
@@ -2682,6 +2940,89 @@ xcf_load_layer (XcfInfo *info,
GINT_TO_POINTER (show_mask));
}
+ /* read in any layer effects and effect masks */
+ if (effects_offset != 0)
+ {
+ GList *filter_data_list = NULL;
+ gint filter_count = 0;
+
+ cur_offset += info->bytes_per_offset;
+ while (TRUE)
+ {
+ FilterData *filter_data;
+ GimpChannel *effect_mask;
+
+ /* if the offset is 0 then we are at the end
+ * of the effect list.
+ */
+ if (effects_offset == 0)
+ break;
+
+ if (effects_offset < cur_offset)
+ {
+ GIMP_LOG (XCF, "Invalid effect offset: %" G_GOFFSET_FORMAT
+ " at offset: %" G_GOFFSET_FORMAT, effects_offset, cur_offset);
+ goto error;
+ }
+
+ /* seek to the effect offset */
+ if (! xcf_seek_pos (info, effects_offset, NULL))
+ goto error;
+
+ filter_data = xcf_load_effect (info, GIMP_DRAWABLE (layer));
+ if (! filter_data)
+ goto error;
+
+ xcf_progress_update (info);
+
+ /* restore the saved position so we'll be ready to
+ * read the next offset.
+ */
+ cur_offset += info->bytes_per_offset;
+ if (! xcf_seek_pos (info, cur_offset, NULL))
+ goto error;
+
+ /* read in the offset of the effect mask */
+ xcf_read_offset (info, &effects_offset, 1);
+
+ if (effects_offset < cur_offset)
+ {
+ GIMP_LOG (XCF, "Invalid effect mask offset: %" G_GOFFSET_FORMAT
+ " at offset: %" G_GOFFSET_FORMAT, effects_offset, cur_offset);
+ goto error;
+ }
+
+ /* seek to the effect mask offset */
+ if (! xcf_seek_pos (info, effects_offset, NULL))
+ goto error;
+
+ effect_mask = xcf_load_channel (info, image);
+ if (! effect_mask)
+ goto error;
+
+ filter_data->mask = effect_mask;
+
+ filter_data_list = g_list_prepend (filter_data_list, filter_data);
+ filter_count++;
+
+ xcf_progress_update (info);
+
+ /* restore the saved position so we'll be ready to
+ * read the next offset.
+ */
+ cur_offset += info->bytes_per_offset;
+ if (! xcf_seek_pos (info, cur_offset, NULL))
+ goto error;
+
+ /* read in the offset of the next effect */
+ xcf_read_offset (info, &effects_offset, 1);
+ }
+
+ if (filter_count > 0)
+ g_object_set_data_full (G_OBJECT (layer), "gimp-layer-effects", filter_data_list,
+ (GDestroyNotify) xcf_load_free_effects);
+ }
+
/* attach the floating selection... */
if (is_fs_drawable)
info->floating_sel_drawable = GIMP_DRAWABLE (layer);
@@ -2793,6 +3134,57 @@ xcf_load_channel (XcfInfo *info,
return NULL;
}
+static FilterData *
+xcf_load_effect (XcfInfo *info,
+ GimpDrawable *drawable)
+{
+ FilterData *filter;
+ GeglNode *operation;
+ gchar *xml;
+
+ filter = g_new0 (FilterData, 1);
+
+ xcf_read_string (info, &xml, 1);
+
+ operation = gegl_node_new_from_xml (xml, NULL);
+ g_free (xml);
+
+ if (! operation)
+ goto error;
+
+ filter->operation = operation;
+
+ /* read in the effect properties */
+ if (! xcf_load_effect_props (info, &(*filter)))
+ goto error;
+
+ xcf_progress_update (info);
+
+ return filter;
+
+ error:
+
+ xcf_load_free_effect (filter);
+
+ return NULL;
+}
+
+static void
+xcf_load_free_effect (FilterData *data)
+{
+ g_free (data->name);
+ g_free (data->icon_name);
+ g_clear_object (&data->operation);
+ g_clear_object (&data->mask);
+ g_free (data);
+}
+
+static void
+xcf_load_free_effects (GList *effects)
+{
+ g_list_free_full (effects, (GDestroyNotify) xcf_load_free_effect);
+}
+
/* The new path structure since XCF 18. */
static GimpVectors *
xcf_load_vectors (XcfInfo *info,
diff --git a/app/xcf/xcf-private.h b/app/xcf/xcf-private.h
index 118390c8c1..eb07b32872 100644
--- a/app/xcf/xcf-private.h
+++ b/app/xcf/xcf-private.h
@@ -70,6 +70,9 @@ typedef enum
PROP_ITEM_SET_ITEM = 41,
PROP_LOCK_VISIBILITY = 42,
PROP_SELECTED_PATH = 43,
+ PROP_FILTER_NAME = 44,
+ PROP_FILTER_ICON = 45,
+ PROP_FILTER_REGION = 46,
} PropType;
typedef enum
diff --git a/app/xcf/xcf-save.c b/app/xcf/xcf-save.c
index 4d3fa7bd86..39f01b0aa6 100644
--- a/app/xcf/xcf-save.c
+++ b/app/xcf/xcf-save.c
@@ -38,6 +38,8 @@
#include "core/gimpcontainer.h"
#include "core/gimpchannel.h"
#include "core/gimpdrawable.h"
+#include "core/gimpdrawable-filters.h"
+#include "core/gimpdrawablefilter.h"
#include "core/gimpgrid.h"
#include "core/gimpguide.h"
#include "core/gimpimage.h"
@@ -51,6 +53,7 @@
#include "core/gimpitemlist.h"
#include "core/gimplayer.h"
#include "core/gimplayermask.h"
+#include "core/gimplist.h"
#include "core/gimpparasitelist.h"
#include "core/gimpprogress.h"
#include "core/gimpsamplepoint.h"
@@ -114,6 +117,10 @@ static gboolean xcf_save_channel_props (XcfInfo *info,
GimpImage *image,
GimpChannel *channel,
GError **error);
+static gboolean xcf_save_effect_props (XcfInfo *info,
+ GimpImage *image,
+ GimpFilter *filter,
+ GError **error);
static gboolean xcf_save_path_props (XcfInfo *info,
GimpImage *image,
GimpVectors *vectors,
@@ -131,6 +138,10 @@ static gboolean xcf_save_channel (XcfInfo *info,
GimpImage *image,
GimpChannel *channel,
GError **error);
+static gboolean xcf_save_effect (XcfInfo *info,
+ GimpImage *image,
+ GimpFilter *filter,
+ GError **error);
static gboolean xcf_save_path (XcfInfo *info,
GimpImage *image,
GimpVectors *vectors,
@@ -817,6 +828,44 @@ xcf_save_channel_props (XcfInfo *info,
return TRUE;
}
+static gboolean
+xcf_save_effect_props (XcfInfo *info,
+ GimpImage *image,
+ GimpFilter *filter,
+ GError **error)
+{
+ const gchar *name;
+ const gchar *icon;
+
+ g_object_get (filter,
+ "name", &name,
+ "icon-name", &icon,
+ NULL);
+
+ xcf_check_error (xcf_save_prop (info, image, PROP_FILTER_NAME, error,
+ name), ;);
+ xcf_check_error (xcf_save_prop (info, image, PROP_FILTER_ICON, error,
+ icon), ;);
+ xcf_check_error (xcf_save_prop (info, image, PROP_VISIBLE, error,
+ gimp_filter_get_active (filter)), ;);
+ xcf_check_error (xcf_save_prop (info, image, PROP_OPACITY, error,
+ gimp_drawable_filter_get_opacity (GIMP_DRAWABLE_FILTER (filter))), ;);
+ xcf_check_error (xcf_save_prop (info, image, PROP_MODE, error,
+ gimp_drawable_filter_get_paint_mode (GIMP_DRAWABLE_FILTER (filter))), ;);
+ xcf_check_error (xcf_save_prop (info, image, PROP_BLEND_SPACE, error,
+ gimp_drawable_filter_get_blend_space (GIMP_DRAWABLE_FILTER (filter))), ;);
+ xcf_check_error (xcf_save_prop (info, image, PROP_COMPOSITE_SPACE, error,
+ gimp_drawable_filter_get_composite_space (GIMP_DRAWABLE_FILTER (filter))), ;);
+ xcf_check_error (xcf_save_prop (info, image, PROP_COMPOSITE_MODE, error,
+ gimp_drawable_filter_get_composite_mode (GIMP_DRAWABLE_FILTER (filter))), ;);
+ xcf_check_error (xcf_save_prop (info, image, PROP_FILTER_REGION, error,
+ gimp_drawable_filter_get_region (GIMP_DRAWABLE_FILTER (filter))), ;);
+
+ xcf_check_error (xcf_save_prop (info, image, PROP_END, error), ;);
+
+ return TRUE;
+}
+
static gboolean
xcf_save_path_props (XcfInfo *info,
GimpImage *image,
@@ -1620,6 +1669,37 @@ xcf_save_prop (XcfInfo *info,
xcf_write_int32_check_error (info, &set_n, 1, va_end (args));
}
break;
+
+ case PROP_FILTER_NAME:
+ {
+ const gchar *filter_name = va_arg (args, gchar *);
+
+ xcf_write_prop_type_check_error (info, prop_type, va_end (args));
+ xcf_write_string_check_error (info, (gchar **) &filter_name, 1, va_end (args));
+ }
+ break;
+
+ case PROP_FILTER_ICON:
+ {
+ const gchar *filter_icon = va_arg (args, gchar *);
+
+ xcf_write_prop_type_check_error (info, prop_type, va_end (args));
+ xcf_write_string_check_error (info, (gchar **) &filter_icon, 1, va_end (args));
+ }
+ break;
+
+ case PROP_FILTER_REGION:
+ {
+ guint32 filter_region = va_arg (args, guint32);
+
+ size = 4;
+
+ xcf_write_prop_type_check_error (info, prop_type, va_end (args));
+ xcf_write_int32_check_error (info, &size, 1, va_end (args));
+
+ xcf_write_int32_check_error (info, &filter_region, 1, va_end (args));
+ }
+ break;
}
va_end (args);
@@ -1633,11 +1713,15 @@ xcf_save_layer (XcfInfo *info,
GimpLayer *layer,
GError **error)
{
- goffset saved_pos;
- goffset offset;
- guint32 value;
- const gchar *string;
- GError *tmp_error = NULL;
+ goffset saved_pos;
+ goffset offset;
+ guint32 value;
+ const gchar *string;
+ GimpContainer *filters;
+ GList *filter_list;
+ guint32 num_effects = 0;
+ GList *list;
+ GError *tmp_error = NULL;
/* check and see if this is the drawable that the floating
* selection is attached to.
@@ -1650,6 +1734,17 @@ xcf_save_layer (XcfInfo *info,
xcf_check_error (xcf_seek_pos (info, saved_pos, error), ;);
}
+ /* Get filter information */
+ filters = gimp_drawable_get_filters (GIMP_DRAWABLE (layer));
+ /* Since floating selections are also stored in the filter stack,
+ * we need to verify what's in there to get the correct count */
+ for (filter_list = GIMP_LIST (filters)->queue->tail; filter_list;
+ filter_list = g_list_previous (filter_list))
+ {
+ if (GIMP_IS_DRAWABLE_FILTER (filter_list->data))
+ num_effects++;
+ }
+
/* write out the width, height and image type information for the layer */
value = gimp_item_get_width (GIMP_ITEM (layer));
xcf_write_int32_check_error (info, &value, 1, ;);
@@ -1667,8 +1762,8 @@ xcf_save_layer (XcfInfo *info,
/* write out the layer properties */
xcf_save_layer_props (info, image, layer, error);
- /* write out the layer tile hierarchy */
- offset = info->cp + 2 * info->bytes_per_offset;
+ /* write out the layer tile hierarchy and effects */
+ offset = info->cp + (2 + (num_effects * 2) + 1) * info->bytes_per_offset;
xcf_write_offset_check_error (info, &offset, 1, ;);
saved_pos = info->cp;
@@ -1676,6 +1771,10 @@ xcf_save_layer (XcfInfo *info,
/* write a zero layer mask offset */
xcf_write_zero_offset_check_error (info, 1, ;);
+ /* write out zero effect and effect mask offset(s) */
+ for (gint i = 0; i <= (num_effects * 2); i++)
+ xcf_write_zero_offset_check_error (info, 1, ;);
+
xcf_check_error (xcf_save_buffer (info, image,
gimp_drawable_get_buffer (GIMP_DRAWABLE (layer)),
error), ;);
@@ -1690,11 +1789,55 @@ xcf_save_layer (XcfInfo *info,
xcf_check_error (xcf_seek_pos (info, saved_pos, error), ;);
xcf_write_offset_check_error (info, &offset, 1, ;);
+ saved_pos = info->cp;
+
xcf_check_error (xcf_seek_pos (info, offset, error), ;);
xcf_check_error (xcf_save_channel (info, image, GIMP_CHANNEL (mask),
error), ;);
}
+ /* write out any layer effects and effect masks */
+ if (num_effects > 0)
+ {
+ saved_pos += info->bytes_per_offset;
+
+ for (list = GIMP_LIST (filters)->queue->head; list;
+ list = g_list_next (list))
+ {
+ if (GIMP_IS_DRAWABLE_FILTER (list->data))
+ {
+ GimpDrawableFilter *filter = list->data;
+ GimpChannel *effect_mask;
+
+ offset = info->cp;
+
+ xcf_check_error (xcf_seek_pos (info, saved_pos, error), ;);
+ xcf_write_offset_check_error (info, &offset, 1, ;);
+
+ saved_pos = info->cp;
+
+ xcf_check_error (xcf_seek_pos (info, offset, error), ;);
+ xcf_check_error (xcf_save_effect (info, image, GIMP_FILTER (filter),
+ error), ;);
+
+ /* write out effect mask */
+ effect_mask = gimp_drawable_filter_get_mask (filter);
+
+ offset = info->cp;
+
+ xcf_check_error (xcf_seek_pos (info, saved_pos, error), ;);
+ xcf_write_offset_check_error (info, &offset, 1, ;);
+
+ saved_pos = info->cp;
+
+ xcf_check_error (xcf_seek_pos (info, offset, error), ;);
+ xcf_check_error (xcf_save_channel (info, image, effect_mask,
+ error), ;);
+ }
+ }
+ g_list_free (list);
+ }
+
return TRUE;
}
@@ -1746,6 +1889,60 @@ xcf_save_channel (XcfInfo *info,
return TRUE;
}
+static gboolean
+xcf_save_effect (XcfInfo *info,
+ GimpImage *image,
+ GimpFilter *filter,
+ GError **error)
+{
+ const gchar *string;
+ GimpDrawableFilter *filter_drawable;
+ GeglNode *node;
+ GeglNode *save_node = gegl_node_new ();
+ const gchar *operation;
+ GParamSpec **pspecs;
+ guint n_pspecs;
+ GError *tmp_error = NULL;
+
+ filter_drawable = GIMP_DRAWABLE_FILTER (filter);
+ node = gimp_drawable_filter_get_operation (filter_drawable);
+
+ /* Create operation-only node */
+ gegl_node_get (node,
+ "operation", &operation,
+ NULL);
+
+ gegl_node_set (save_node,
+ "operation", operation,
+ NULL);
+
+ pspecs = gegl_operation_list_properties (operation, &n_pspecs);
+
+ for (gint i = 0; i < n_pspecs; i++)
+ {
+ GParamSpec *pspec = pspecs[i];
+ GValue value = G_VALUE_INIT;
+
+ g_value_init (&value, pspec->value_type);
+ gegl_node_get_property (node, pspec->name,
+ &value);
+
+ gegl_node_set_property (save_node, pspec->name,
+ &value);
+ g_value_unset (&value);
+ }
+ g_free (pspecs);
+
+ /* Write out GEGL xml */
+ string = gegl_node_to_xml_full (save_node, save_node, "/");
+ xcf_write_string_check_error (info, (gchar **) &string, 1, ;);
+
+ /* write out the effect properties */
+ xcf_save_effect_props (info, image, filter, error);
+
+ return TRUE;
+}
+
static gint
xcf_calc_levels (gint size,
gint tile_size)
diff --git a/app/xcf/xcf.c b/app/xcf/xcf.c
index 62812d3259..d0a9f23547 100644
--- a/app/xcf/xcf.c
+++ b/app/xcf/xcf.c
@@ -88,6 +88,7 @@ static GimpXcfLoaderFunc * const xcf_loaders[] =
xcf_load_image, /* version 17 */
xcf_load_image, /* version 18 */
xcf_load_image, /* version 19 */
+ xcf_load_image, /* version 20 */
};
diff --git a/libgimp/gimp.def b/libgimp/gimp.def
index 1a484d757a..068a50d7c9 100644
--- a/libgimp/gimp.def
+++ b/libgimp/gimp.def
@@ -236,6 +236,7 @@ EXPORTS
gimp_drawable_levels_stretch
gimp_drawable_mask_bounds
gimp_drawable_mask_intersect
+ gimp_drawable_merge_filters
gimp_drawable_merge_shadow
gimp_drawable_offset
gimp_drawable_posterize
diff --git a/libgimp/gimpdrawable_pdb.c b/libgimp/gimpdrawable_pdb.c
index c942e7fdb6..f31bbdc2e5 100644
--- a/libgimp/gimpdrawable_pdb.c
+++ b/libgimp/gimpdrawable_pdb.c
@@ -592,6 +592,40 @@ gimp_drawable_mask_intersect (GimpDrawable *drawable,
return non_empty;
}
+/**
+ * gimp_drawable_merge_filters:
+ * @drawable: The drawable.
+ *
+ * Merge the layer effect filters to the specified drawable.
+ *
+ * This procedure combines the contents of the drawable's filter stack
+ * (for export) with the specified drawable.
+ *
+ * Returns: TRUE on success.
+ **/
+gboolean
+gimp_drawable_merge_filters (GimpDrawable *drawable)
+{
+ GimpValueArray *args;
+ GimpValueArray *return_vals;
+ gboolean success = TRUE;
+
+ args = gimp_value_array_new_from_types (NULL,
+ GIMP_TYPE_DRAWABLE, drawable,
+ G_TYPE_NONE);
+
+ return_vals = _gimp_pdb_run_procedure_array (gimp_get_pdb (),
+ "gimp-drawable-merge-filters",
+ args);
+ gimp_value_array_unref (args);
+
+ success = GIMP_VALUES_GET_ENUM (return_vals, 0) == GIMP_PDB_SUCCESS;
+
+ gimp_value_array_unref (return_vals);
+
+ return success;
+}
+
/**
* gimp_drawable_merge_shadow:
* @drawable: The drawable.
diff --git a/libgimp/gimpdrawable_pdb.h b/libgimp/gimpdrawable_pdb.h
index a94fa0cd41..b261827030 100644
--- a/libgimp/gimpdrawable_pdb.h
+++ b/libgimp/gimpdrawable_pdb.h
@@ -56,6 +56,7 @@ gboolean gimp_drawable_mask_intersect (GimpDrawable
gint *y,
gint *width,
gint *height);
+gboolean gimp_drawable_merge_filters (GimpDrawable *drawable);
gboolean gimp_drawable_merge_shadow (GimpDrawable *drawable,
gboolean undo);
gboolean gimp_drawable_free_shadow (GimpDrawable *drawable);
diff --git a/libgimp/gimpexport.c b/libgimp/gimpexport.c
index 37ae4eff76..056a7dca70 100644
--- a/libgimp/gimpexport.c
+++ b/libgimp/gimpexport.c
@@ -172,6 +172,21 @@ export_flatten (GimpImage *image,
*drawables = g_list_prepend (NULL, flattened);
}
+static void
+export_merge_layer_effects (GimpImage *image,
+ GList **drawables)
+{
+ GList *layers;
+ GList *iter;
+
+ layers = gimp_image_list_layers (image);
+
+ for (iter = layers; iter; iter = g_list_next (iter))
+ gimp_drawable_merge_filters (GIMP_DRAWABLE (iter->data));
+
+ g_list_free (layers);
+}
+
static void
export_remove_alpha (GimpImage *image,
GList **drawables)
@@ -379,6 +394,15 @@ static ExportAction export_action_flatten =
0
};
+static ExportAction export_action_merge_layer_effects =
+{
+ export_merge_layer_effects,
+ NULL,
+ N_("%s plug-in can't handle layer effects"),
+ { N_("Merge Layer Effects"), NULL },
+ 0
+};
+
static ExportAction export_action_remove_alpha =
{
export_remove_alpha,
@@ -870,6 +894,9 @@ gimp_export_image (GimpImage **image,
return GIMP_EXPORT_CANCEL;
}
+ /* Merge down layer effects for non-project file formats */
+ if (! (capabilities & GIMP_EXPORT_CAN_HANDLE_LAYER_EFFECTS))
+ actions = g_slist_prepend (actions, &export_action_merge_layer_effects);
/* check alpha and layer masks */
layers = gimp_image_list_layers (*image);
diff --git a/libgimp/gimpexport.h b/libgimp/gimpexport.h
index 3c0e6ff24a..44cb856d45 100644
--- a/libgimp/gimpexport.h
+++ b/libgimp/gimpexport.h
@@ -40,6 +40,7 @@ G_BEGIN_DECLS
* @GIMP_EXPORT_CAN_HANDLE_ALPHA: Handles alpha channels
* @GIMP_EXPORT_CAN_HANDLE_LAYERS: Handles layers
* @GIMP_EXPORT_CAN_HANDLE_LAYERS_AS_ANIMATION: Handles animation of layers
+ * @GIMP_EXPORT_CAN_HANDLE_LAYER_EFFECTS: Handles layer effects
* @GIMP_EXPORT_CAN_HANDLE_LAYER_MASKS: Handles layer masks
* @GIMP_EXPORT_NEEDS_ALPHA: Needs alpha channels
* @GIMP_EXPORT_NEEDS_CROP: Needs to crop content to image bounds
@@ -56,8 +57,9 @@ typedef enum
GIMP_EXPORT_CAN_HANDLE_LAYERS = 1 << 5,
GIMP_EXPORT_CAN_HANDLE_LAYERS_AS_ANIMATION = 1 << 6,
GIMP_EXPORT_CAN_HANDLE_LAYER_MASKS = 1 << 7,
- GIMP_EXPORT_NEEDS_ALPHA = 1 << 8,
- GIMP_EXPORT_NEEDS_CROP = 1 << 9
+ GIMP_EXPORT_CAN_HANDLE_LAYER_EFFECTS = 1 << 8,
+ GIMP_EXPORT_NEEDS_ALPHA = 1 << 9,
+ GIMP_EXPORT_NEEDS_CROP = 1 << 10
} GimpExportCapabilities;
diff --git a/pdb/groups/drawable.pdb b/pdb/groups/drawable.pdb
index 5f9cabfb26..89b743359d 100644
--- a/pdb/groups/drawable.pdb
+++ b/pdb/groups/drawable.pdb
@@ -16,6 +16,38 @@
# "Perlized" from C source by Manish Singh
+sub drawable_merge_filters {
+ $blurb = 'Merge the layer effect filters to the specified drawable.';
+
+ $help = <<'HELP';
+This procedure combines the contents of the drawable's filter stack
+(for export) with the specified drawable.
+HELP
+
+ &std_pdb_misc;
+
+ @inargs = (
+ { name => 'drawable', type => 'drawable',
+ desc => 'The drawable' }
+ );
+
+ %invoke = (
+ headers => [ qw("core/gimpdrawable-filters.h") ],
+ code => <<'CODE'
+{
+ if (gimp_pdb_item_is_attached (GIMP_ITEM (drawable), NULL,
+ GIMP_PDB_ITEM_CONTENT, error) &&
+ gimp_pdb_item_is_not_group (GIMP_ITEM (drawable), error))
+ {
+ gimp_drawable_merge_filters (drawable);
+ }
+ else
+ success = FALSE;
+}
+CODE
+ );
+}
+
sub drawable_merge_shadow {
$blurb = 'Merge the shadow buffer with the specified drawable.';
@@ -899,6 +931,7 @@ CODE
"gegl/gimp-babl-compat.h"
"core/gimp.h"
"core/gimpchannel-select.h"
+ "core/gimpdrawable-filters.h"
"core/gimpdrawable-offset.h"
"core/gimpimage.h"
"core/gimptempbuf.h"
@@ -920,10 +953,11 @@ CODE
drawable_get_offsets
drawable_mask_bounds
drawable_mask_intersect
+ drawable_merge_filters
drawable_merge_shadow
drawable_free_shadow
drawable_update
- drawable_fill
+ drawable_fill
drawable_offset
drawable_thumbnail
drawable_sub_thumbnail