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