diff options
Diffstat (limited to '')
-rw-r--r-- | app/core/gimpdrawable-floating-selection.c | 512 |
1 files changed, 512 insertions, 0 deletions
diff --git a/app/core/gimpdrawable-floating-selection.c b/app/core/gimpdrawable-floating-selection.c new file mode 100644 index 0000000..71866b1 --- /dev/null +++ b/app/core/gimpdrawable-floating-selection.c @@ -0,0 +1,512 @@ +/* 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 <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <cairo.h> +#include <gegl.h> +#include <gdk-pixbuf/gdk-pixbuf.h> + +#include "libgimpbase/gimpbase.h" + +#include "core-types.h" + +#include "gegl/gimpapplicator.h" + +#include "gimpchannel.h" +#include "gimpdrawable-floating-selection.h" +#include "gimpdrawable-filters.h" +#include "gimpdrawable-private.h" +#include "gimpimage.h" +#include "gimplayer.h" + +#include "gimp-log.h" + +#include "gimp-intl.h" + + +/* local function prototypes */ + +static void gimp_drawable_remove_fs_filter (GimpDrawable *drawable); +static void gimp_drawable_sync_fs_filter (GimpDrawable *drawable); + +static void gimp_drawable_fs_notify (GObject *object, + const GParamSpec *pspec, + GimpDrawable *drawable); +static void gimp_drawable_fs_lock_position_changed (GimpDrawable *signal_drawable, + GimpDrawable *drawable); +static void gimp_drawable_fs_format_changed (GimpDrawable *signal_drawable, + GimpDrawable *drawable); +static void gimp_drawable_fs_affect_changed (GimpImage *image, + GimpChannelType channel, + GimpDrawable *drawable); +static void gimp_drawable_fs_mask_changed (GimpImage *image, + GimpDrawable *drawable); +static void gimp_drawable_fs_visibility_changed (GimpLayer *fs, + GimpDrawable *drawable); +static void gimp_drawable_fs_excludes_backdrop_changed (GimpLayer *fs, + GimpDrawable *drawable); +static void gimp_drawable_fs_bounding_box_changed (GimpLayer *fs, + GimpDrawable *drawable); +static void gimp_drawable_fs_update (GimpLayer *fs, + gint x, + gint y, + gint width, + gint height, + GimpDrawable *drawable); + + +/* public functions */ + +GimpLayer * +gimp_drawable_get_floating_sel (GimpDrawable *drawable) +{ + g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL); + + return drawable->private->floating_selection; +} + +void +gimp_drawable_attach_floating_sel (GimpDrawable *drawable, + GimpLayer *fs) +{ + GimpImage *image; + + g_return_if_fail (GIMP_IS_DRAWABLE (drawable)); + g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable))); + g_return_if_fail (gimp_drawable_get_floating_sel (drawable) == NULL); + g_return_if_fail (GIMP_IS_LAYER (fs)); + + GIMP_LOG (FLOATING_SELECTION, "%s", G_STRFUNC); + + image = gimp_item_get_image (GIMP_ITEM (drawable)); + + drawable->private->floating_selection = fs; + gimp_image_set_floating_selection (image, fs); + + /* clear the selection */ + gimp_drawable_invalidate_boundary (GIMP_DRAWABLE (fs)); + + gimp_item_bind_visible_to_active (GIMP_ITEM (fs), FALSE); + gimp_filter_set_active (GIMP_FILTER (fs), FALSE); + + _gimp_drawable_add_floating_sel_filter (drawable); + + g_signal_connect (fs, "visibility-changed", + G_CALLBACK (gimp_drawable_fs_visibility_changed), + drawable); + g_signal_connect (fs, "excludes-backdrop-changed", + G_CALLBACK (gimp_drawable_fs_excludes_backdrop_changed), + drawable); + g_signal_connect (fs, "bounding-box-changed", + G_CALLBACK (gimp_drawable_fs_bounding_box_changed), + drawable); + g_signal_connect (fs, "update", + G_CALLBACK (gimp_drawable_fs_update), + drawable); + + gimp_drawable_fs_update (fs, + 0, 0, + gimp_item_get_width (GIMP_ITEM (fs)), + gimp_item_get_height (GIMP_ITEM (fs)), + drawable); +} + +void +gimp_drawable_detach_floating_sel (GimpDrawable *drawable) +{ + GimpImage *image; + GimpLayer *fs; + + g_return_if_fail (GIMP_IS_DRAWABLE (drawable)); + g_return_if_fail (gimp_drawable_get_floating_sel (drawable) != NULL); + + GIMP_LOG (FLOATING_SELECTION, "%s", G_STRFUNC); + + image = gimp_item_get_image (GIMP_ITEM (drawable)); + fs = drawable->private->floating_selection; + + gimp_drawable_remove_fs_filter (drawable); + + g_signal_handlers_disconnect_by_func (fs, + gimp_drawable_fs_visibility_changed, + drawable); + g_signal_handlers_disconnect_by_func (fs, + gimp_drawable_fs_excludes_backdrop_changed, + drawable); + g_signal_handlers_disconnect_by_func (fs, + gimp_drawable_fs_bounding_box_changed, + drawable); + g_signal_handlers_disconnect_by_func (fs, + gimp_drawable_fs_update, + drawable); + + gimp_drawable_fs_update (fs, + 0, 0, + gimp_item_get_width (GIMP_ITEM (fs)), + gimp_item_get_height (GIMP_ITEM (fs)), + drawable); + + gimp_item_bind_visible_to_active (GIMP_ITEM (fs), TRUE); + + /* clear the selection */ + gimp_drawable_invalidate_boundary (GIMP_DRAWABLE (fs)); + + gimp_image_set_floating_selection (image, NULL); + drawable->private->floating_selection = NULL; +} + +GimpFilter * +gimp_drawable_get_floating_sel_filter (GimpDrawable *drawable) +{ + g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL); + g_return_val_if_fail (gimp_drawable_get_floating_sel (drawable) != NULL, NULL); + + /* Ensure that the graph is construced before the filter is used. + * Otherwise, we rely on the projection to cause the graph to be + * constructed, which fails for images that aren't displayed. + */ + gimp_filter_get_node (GIMP_FILTER (drawable)); + + return drawable->private->fs_filter; +} + + +/* private functions */ + +void +_gimp_drawable_add_floating_sel_filter (GimpDrawable *drawable) +{ + GimpDrawablePrivate *private = drawable->private; + GimpImage *image = gimp_item_get_image (GIMP_ITEM (drawable)); + GimpLayer *fs = gimp_drawable_get_floating_sel (drawable); + GeglNode *node; + GeglNode *fs_source; + + if (! private->source_node) + return; + + private->fs_filter = gimp_filter_new (_("Floating Selection")); + gimp_viewable_set_icon_name (GIMP_VIEWABLE (private->fs_filter), + "gimp-floating-selection"); + + node = gimp_filter_get_node (private->fs_filter); + + fs_source = gimp_drawable_get_source_node (GIMP_DRAWABLE (fs)); + + /* rip the fs' source node out of its graph */ + if (fs->layer_offset_node) + { + gegl_node_disconnect (fs->layer_offset_node, "input"); + gegl_node_remove_child (gimp_filter_get_node (GIMP_FILTER (fs)), + fs_source); + } + + gegl_node_add_child (node, fs_source); + + private->fs_applicator = gimp_applicator_new (node); + + gimp_filter_set_applicator (private->fs_filter, private->fs_applicator); + + gimp_applicator_set_cache (private->fs_applicator, TRUE); + + private->fs_crop_node = gegl_node_new_child (node, + "operation", "gegl:nop", + NULL); + + gegl_node_connect_to (fs_source, "output", + private->fs_crop_node, "input"); + gegl_node_connect_to (private->fs_crop_node, "output", + node, "aux"); + + gimp_drawable_add_filter (drawable, private->fs_filter); + + g_signal_connect (fs, "notify", + G_CALLBACK (gimp_drawable_fs_notify), + drawable); + g_signal_connect (drawable, "notify::offset-x", + G_CALLBACK (gimp_drawable_fs_notify), + drawable); + g_signal_connect (drawable, "notify::offset-y", + G_CALLBACK (gimp_drawable_fs_notify), + drawable); + g_signal_connect (drawable, "lock-position-changed", + G_CALLBACK (gimp_drawable_fs_lock_position_changed), + drawable); + g_signal_connect (drawable, "format-changed", + G_CALLBACK (gimp_drawable_fs_format_changed), + drawable); + g_signal_connect (image, "component-active-changed", + G_CALLBACK (gimp_drawable_fs_affect_changed), + drawable); + g_signal_connect (image, "mask-changed", + G_CALLBACK (gimp_drawable_fs_mask_changed), + drawable); + + gimp_drawable_sync_fs_filter (drawable); +} + + +/* private functions */ + +static void +gimp_drawable_remove_fs_filter (GimpDrawable *drawable) +{ + GimpDrawablePrivate *private = drawable->private; + GimpImage *image = gimp_item_get_image (GIMP_ITEM (drawable)); + GimpLayer *fs = gimp_drawable_get_floating_sel (drawable); + + if (private->fs_filter) + { + GeglNode *node; + GeglNode *fs_source; + + g_signal_handlers_disconnect_by_func (fs, + gimp_drawable_fs_notify, + drawable); + g_signal_handlers_disconnect_by_func (drawable, + gimp_drawable_fs_notify, + drawable); + g_signal_handlers_disconnect_by_func (drawable, + gimp_drawable_fs_lock_position_changed, + drawable); + g_signal_handlers_disconnect_by_func (drawable, + gimp_drawable_fs_format_changed, + drawable); + g_signal_handlers_disconnect_by_func (image, + gimp_drawable_fs_affect_changed, + drawable); + g_signal_handlers_disconnect_by_func (image, + gimp_drawable_fs_mask_changed, + drawable); + + gimp_drawable_remove_filter (drawable, private->fs_filter); + + node = gimp_filter_get_node (private->fs_filter); + + fs_source = gimp_drawable_get_source_node (GIMP_DRAWABLE (fs)); + + gegl_node_remove_child (node, fs_source); + + /* plug the fs' source node back into its graph */ + if (fs->layer_offset_node) + { + gegl_node_add_child (gimp_filter_get_node (GIMP_FILTER (fs)), + fs_source); + gegl_node_connect_to (fs_source, "output", + fs->layer_offset_node, "input"); + } + + g_clear_object (&private->fs_filter); + g_clear_object (&private->fs_applicator); + + private->fs_crop_node = NULL; + + gimp_drawable_update_bounding_box (drawable); + } +} + +static void +gimp_drawable_sync_fs_filter (GimpDrawable *drawable) +{ + GimpDrawablePrivate *private = drawable->private; + GimpImage *image = gimp_item_get_image (GIMP_ITEM (drawable)); + GimpChannel *mask = gimp_image_get_mask (image); + GimpLayer *fs = gimp_drawable_get_floating_sel (drawable); + gint off_x, off_y; + gint fs_off_x, fs_off_y; + + gimp_filter_set_active (private->fs_filter, + gimp_item_get_visible (GIMP_ITEM (fs))); + + gimp_item_get_offset (GIMP_ITEM (drawable), &off_x, &off_y); + gimp_item_get_offset (GIMP_ITEM (fs), &fs_off_x, &fs_off_y); + + if (gimp_item_get_clip (GIMP_ITEM (drawable), GIMP_TRANSFORM_RESIZE_ADJUST) == + GIMP_TRANSFORM_RESIZE_CLIP || + ! gimp_drawable_has_alpha (drawable)) + { + gegl_node_set ( + private->fs_crop_node, + "operation", "gegl:crop", + "x", (gdouble) (off_x - fs_off_x), + "y", (gdouble) (off_y - fs_off_y), + "width", (gdouble) gimp_item_get_width (GIMP_ITEM (drawable)), + "height", (gdouble) gimp_item_get_height (GIMP_ITEM (drawable)), + NULL); + } + else + { + gegl_node_set ( + private->fs_crop_node, + "operation", "gegl:nop", + NULL); + } + + gimp_applicator_set_apply_offset (private->fs_applicator, + fs_off_x - off_x, + fs_off_y - off_y); + + if (gimp_channel_is_empty (mask)) + { + gimp_applicator_set_mask_buffer (private->fs_applicator, NULL); + } + else + { + GeglBuffer *buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (mask)); + + gimp_applicator_set_mask_buffer (private->fs_applicator, buffer); + gimp_applicator_set_mask_offset (private->fs_applicator, + -off_x, -off_y); + } + + gimp_applicator_set_opacity (private->fs_applicator, + gimp_layer_get_opacity (fs)); + gimp_applicator_set_mode (private->fs_applicator, + gimp_layer_get_mode (fs), + gimp_layer_get_blend_space (fs), + gimp_layer_get_composite_space (fs), + gimp_layer_get_composite_mode (fs)); + gimp_applicator_set_affect (private->fs_applicator, + gimp_drawable_get_active_mask (drawable)); + gimp_applicator_set_output_format (private->fs_applicator, + gimp_drawable_get_format (drawable)); + + gimp_drawable_update_bounding_box (drawable); +} + +static void +gimp_drawable_fs_notify (GObject *object, + const GParamSpec *pspec, + GimpDrawable *drawable) +{ + if (! strcmp (pspec->name, "offset-x") || + ! strcmp (pspec->name, "offset-y") || + ! strcmp (pspec->name, "visible") || + ! strcmp (pspec->name, "mode") || + ! strcmp (pspec->name, "blend-space") || + ! strcmp (pspec->name, "composite-space") || + ! strcmp (pspec->name, "composite-mode") || + ! strcmp (pspec->name, "opacity")) + { + gimp_drawable_sync_fs_filter (drawable); + } +} + +static void +gimp_drawable_fs_lock_position_changed (GimpDrawable *signal_drawable, + GimpDrawable *drawable) +{ + GimpLayer *fs = gimp_drawable_get_floating_sel (drawable); + + gimp_drawable_sync_fs_filter (drawable); + + gimp_drawable_update (GIMP_DRAWABLE (fs), 0, 0, -1, -1); +} + +static void +gimp_drawable_fs_format_changed (GimpDrawable *signal_drawable, + GimpDrawable *drawable) +{ + GimpLayer *fs = gimp_drawable_get_floating_sel (drawable); + + gimp_drawable_sync_fs_filter (drawable); + + gimp_drawable_update (GIMP_DRAWABLE (fs), 0, 0, -1, -1); +} + +static void +gimp_drawable_fs_affect_changed (GimpImage *image, + GimpChannelType channel, + GimpDrawable *drawable) +{ + GimpLayer *fs = gimp_drawable_get_floating_sel (drawable); + + gimp_drawable_sync_fs_filter (drawable); + + gimp_drawable_update (GIMP_DRAWABLE (fs), 0, 0, -1, -1); +} + +static void +gimp_drawable_fs_mask_changed (GimpImage *image, + GimpDrawable *drawable) +{ + GimpLayer *fs = gimp_drawable_get_floating_sel (drawable); + + gimp_drawable_sync_fs_filter (drawable); + + gimp_drawable_update (GIMP_DRAWABLE (fs), 0, 0, -1, -1); +} + +static void +gimp_drawable_fs_visibility_changed (GimpLayer *fs, + GimpDrawable *drawable) +{ + if (gimp_layer_get_excludes_backdrop (fs)) + gimp_drawable_update (drawable, 0, 0, -1, -1); + else + gimp_drawable_update (GIMP_DRAWABLE (fs), 0, 0, -1, -1); +} + +static void +gimp_drawable_fs_excludes_backdrop_changed (GimpLayer *fs, + GimpDrawable *drawable) +{ + if (gimp_item_get_visible (GIMP_ITEM (fs))) + gimp_drawable_update (drawable, 0, 0, -1, -1); +} + +static void +gimp_drawable_fs_bounding_box_changed (GimpLayer *fs, + GimpDrawable *drawable) +{ + gimp_drawable_update_bounding_box (drawable); +} + +static void +gimp_drawable_fs_update (GimpLayer *fs, + gint x, + gint y, + gint width, + gint height, + GimpDrawable *drawable) +{ + GeglRectangle bounding_box; + GeglRectangle rect; + gint fs_off_x, fs_off_y; + gint off_x, off_y; + + gimp_item_get_offset (GIMP_ITEM (fs), &fs_off_x, &fs_off_y); + gimp_item_get_offset (GIMP_ITEM (drawable), &off_x, &off_y); + + bounding_box = gimp_drawable_get_bounding_box (drawable); + + bounding_box.x += off_x; + bounding_box.y += off_y; + + rect.x = x + fs_off_x; + rect.y = y + fs_off_y; + rect.width = width; + rect.height = height; + + if (gegl_rectangle_intersect (&rect, &rect, &bounding_box)) + { + gimp_drawable_update (drawable, + rect.x - off_x, rect.y - off_y, + rect.width, rect.height); + } +} |