/* PIKA - Photo and Image Kooker Application * a rebranding of The GNU Image Manipulation Program (created with heckimp) * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio * * Original copyright, applying to most contents (license remains unchanged): * 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 #include "libpikabase/pikabase.h" #include "core-types.h" #include "gegl/pikaapplicator.h" #include "pikachannel.h" #include "pikadrawable-floating-selection.h" #include "pikadrawable-filters.h" #include "pikadrawable-private.h" #include "pikaimage.h" #include "pikalayer.h" #include "pika-log.h" #include "pika-intl.h" /* local function prototypes */ static void pika_drawable_remove_fs_filter (PikaDrawable *drawable); static void pika_drawable_sync_fs_filter (PikaDrawable *drawable); static void pika_drawable_fs_notify (GObject *object, const GParamSpec *pspec, PikaDrawable *drawable); static void pika_drawable_fs_lock_position_changed (PikaDrawable *signal_drawable, PikaDrawable *drawable); static void pika_drawable_fs_format_changed (PikaDrawable *signal_drawable, PikaDrawable *drawable); static void pika_drawable_fs_affect_changed (PikaImage *image, PikaChannelType channel, PikaDrawable *drawable); static void pika_drawable_fs_mask_changed (PikaImage *image, PikaDrawable *drawable); static void pika_drawable_fs_visibility_changed (PikaLayer *fs, PikaDrawable *drawable); static void pika_drawable_fs_excludes_backdrop_changed (PikaLayer *fs, PikaDrawable *drawable); static void pika_drawable_fs_bounding_box_changed (PikaLayer *fs, PikaDrawable *drawable); static void pika_drawable_fs_update (PikaLayer *fs, gint x, gint y, gint width, gint height, PikaDrawable *drawable); /* public functions */ PikaLayer * pika_drawable_get_floating_sel (PikaDrawable *drawable) { g_return_val_if_fail (PIKA_IS_DRAWABLE (drawable), NULL); return drawable->private->floating_selection; } void pika_drawable_attach_floating_sel (PikaDrawable *drawable, PikaLayer *fs) { PikaImage *image; g_return_if_fail (PIKA_IS_DRAWABLE (drawable)); g_return_if_fail (pika_item_is_attached (PIKA_ITEM (drawable))); g_return_if_fail (pika_drawable_get_floating_sel (drawable) == NULL); g_return_if_fail (PIKA_IS_LAYER (fs)); PIKA_LOG (FLOATING_SELECTION, "%s", G_STRFUNC); image = pika_item_get_image (PIKA_ITEM (drawable)); drawable->private->floating_selection = fs; pika_image_set_floating_selection (image, fs); /* clear the selection */ pika_drawable_invalidate_boundary (PIKA_DRAWABLE (fs)); pika_item_bind_visible_to_active (PIKA_ITEM (fs), FALSE); pika_filter_set_active (PIKA_FILTER (fs), FALSE); _pika_drawable_add_floating_sel_filter (drawable); g_signal_connect (fs, "visibility-changed", G_CALLBACK (pika_drawable_fs_visibility_changed), drawable); g_signal_connect (fs, "excludes-backdrop-changed", G_CALLBACK (pika_drawable_fs_excludes_backdrop_changed), drawable); g_signal_connect (fs, "bounding-box-changed", G_CALLBACK (pika_drawable_fs_bounding_box_changed), drawable); g_signal_connect (fs, "update", G_CALLBACK (pika_drawable_fs_update), drawable); pika_drawable_fs_update (fs, 0, 0, pika_item_get_width (PIKA_ITEM (fs)), pika_item_get_height (PIKA_ITEM (fs)), drawable); } void pika_drawable_detach_floating_sel (PikaDrawable *drawable) { PikaImage *image; PikaLayer *fs; g_return_if_fail (PIKA_IS_DRAWABLE (drawable)); g_return_if_fail (pika_drawable_get_floating_sel (drawable) != NULL); PIKA_LOG (FLOATING_SELECTION, "%s", G_STRFUNC); image = pika_item_get_image (PIKA_ITEM (drawable)); fs = drawable->private->floating_selection; pika_drawable_remove_fs_filter (drawable); g_signal_handlers_disconnect_by_func (fs, pika_drawable_fs_visibility_changed, drawable); g_signal_handlers_disconnect_by_func (fs, pika_drawable_fs_excludes_backdrop_changed, drawable); g_signal_handlers_disconnect_by_func (fs, pika_drawable_fs_bounding_box_changed, drawable); g_signal_handlers_disconnect_by_func (fs, pika_drawable_fs_update, drawable); pika_drawable_fs_update (fs, 0, 0, pika_item_get_width (PIKA_ITEM (fs)), pika_item_get_height (PIKA_ITEM (fs)), drawable); pika_item_bind_visible_to_active (PIKA_ITEM (fs), TRUE); /* clear the selection */ pika_drawable_invalidate_boundary (PIKA_DRAWABLE (fs)); pika_image_set_floating_selection (image, NULL); drawable->private->floating_selection = NULL; } PikaFilter * pika_drawable_get_floating_sel_filter (PikaDrawable *drawable) { g_return_val_if_fail (PIKA_IS_DRAWABLE (drawable), NULL); g_return_val_if_fail (pika_drawable_get_floating_sel (drawable) != NULL, NULL); /* Ensure that the graph is constructed 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. */ pika_filter_get_node (PIKA_FILTER (drawable)); return drawable->private->fs_filter; } /* private functions */ void _pika_drawable_add_floating_sel_filter (PikaDrawable *drawable) { PikaDrawablePrivate *private = drawable->private; PikaImage *image = pika_item_get_image (PIKA_ITEM (drawable)); PikaLayer *fs = pika_drawable_get_floating_sel (drawable); GeglNode *node; GeglNode *fs_source; if (! private->source_node) return; private->fs_filter = pika_filter_new (_("Floating Selection")); pika_viewable_set_icon_name (PIKA_VIEWABLE (private->fs_filter), "pika-floating-selection"); node = pika_filter_get_node (private->fs_filter); fs_source = pika_drawable_get_source_node (PIKA_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 (pika_filter_get_node (PIKA_FILTER (fs)), fs_source); } gegl_node_add_child (node, fs_source); private->fs_applicator = pika_applicator_new (node); pika_filter_set_applicator (private->fs_filter, private->fs_applicator); pika_applicator_set_cache (private->fs_applicator, TRUE); private->fs_crop_node = gegl_node_new_child (node, "operation", "gegl:nop", NULL); gegl_node_link (fs_source, private->fs_crop_node); gegl_node_connect (private->fs_crop_node, "output", node, "aux"); pika_drawable_add_filter (drawable, private->fs_filter); g_signal_connect (fs, "notify", G_CALLBACK (pika_drawable_fs_notify), drawable); g_signal_connect (drawable, "notify::offset-x", G_CALLBACK (pika_drawable_fs_notify), drawable); g_signal_connect (drawable, "notify::offset-y", G_CALLBACK (pika_drawable_fs_notify), drawable); g_signal_connect (drawable, "lock-position-changed", G_CALLBACK (pika_drawable_fs_lock_position_changed), drawable); g_signal_connect (drawable, "format-changed", G_CALLBACK (pika_drawable_fs_format_changed), drawable); g_signal_connect (image, "component-active-changed", G_CALLBACK (pika_drawable_fs_affect_changed), drawable); g_signal_connect (image, "mask-changed", G_CALLBACK (pika_drawable_fs_mask_changed), drawable); pika_drawable_sync_fs_filter (drawable); } /* private functions */ static void pika_drawable_remove_fs_filter (PikaDrawable *drawable) { PikaDrawablePrivate *private = drawable->private; PikaImage *image = pika_item_get_image (PIKA_ITEM (drawable)); PikaLayer *fs = pika_drawable_get_floating_sel (drawable); if (private->fs_filter) { GeglNode *node; GeglNode *fs_source; g_signal_handlers_disconnect_by_func (fs, pika_drawable_fs_notify, drawable); g_signal_handlers_disconnect_by_func (drawable, pika_drawable_fs_notify, drawable); g_signal_handlers_disconnect_by_func (drawable, pika_drawable_fs_lock_position_changed, drawable); g_signal_handlers_disconnect_by_func (drawable, pika_drawable_fs_format_changed, drawable); g_signal_handlers_disconnect_by_func (image, pika_drawable_fs_affect_changed, drawable); g_signal_handlers_disconnect_by_func (image, pika_drawable_fs_mask_changed, drawable); pika_drawable_remove_filter (drawable, private->fs_filter); node = pika_filter_get_node (private->fs_filter); fs_source = pika_drawable_get_source_node (PIKA_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 (pika_filter_get_node (PIKA_FILTER (fs)), fs_source); gegl_node_link (fs_source, fs->layer_offset_node); } g_clear_object (&private->fs_filter); g_clear_object (&private->fs_applicator); private->fs_crop_node = NULL; pika_drawable_update_bounding_box (drawable); } } static void pika_drawable_sync_fs_filter (PikaDrawable *drawable) { PikaDrawablePrivate *private = drawable->private; PikaImage *image = pika_item_get_image (PIKA_ITEM (drawable)); PikaChannel *mask = pika_image_get_mask (image); PikaLayer *fs = pika_drawable_get_floating_sel (drawable); gint off_x, off_y; gint fs_off_x, fs_off_y; pika_filter_set_active (private->fs_filter, pika_item_get_visible (PIKA_ITEM (fs))); pika_item_get_offset (PIKA_ITEM (drawable), &off_x, &off_y); pika_item_get_offset (PIKA_ITEM (fs), &fs_off_x, &fs_off_y); if (pika_item_get_clip (PIKA_ITEM (drawable), PIKA_TRANSFORM_RESIZE_ADJUST) == PIKA_TRANSFORM_RESIZE_CLIP || ! pika_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) pika_item_get_width (PIKA_ITEM (drawable)), "height", (gdouble) pika_item_get_height (PIKA_ITEM (drawable)), NULL); } else { gegl_node_set ( private->fs_crop_node, "operation", "gegl:nop", NULL); } pika_applicator_set_apply_offset (private->fs_applicator, fs_off_x - off_x, fs_off_y - off_y); if (pika_channel_is_empty (mask)) { pika_applicator_set_mask_buffer (private->fs_applicator, NULL); } else { GeglBuffer *buffer = pika_drawable_get_buffer (PIKA_DRAWABLE (mask)); pika_applicator_set_mask_buffer (private->fs_applicator, buffer); pika_applicator_set_mask_offset (private->fs_applicator, -off_x, -off_y); } pika_applicator_set_opacity (private->fs_applicator, pika_layer_get_opacity (fs)); pika_applicator_set_mode (private->fs_applicator, pika_layer_get_mode (fs), pika_layer_get_blend_space (fs), pika_layer_get_composite_space (fs), pika_layer_get_composite_mode (fs)); pika_applicator_set_affect (private->fs_applicator, pika_drawable_get_active_mask (drawable)); pika_applicator_set_output_format (private->fs_applicator, pika_drawable_get_format (drawable)); pika_drawable_update_bounding_box (drawable); } static void pika_drawable_fs_notify (GObject *object, const GParamSpec *pspec, PikaDrawable *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")) { pika_drawable_sync_fs_filter (drawable); } } static void pika_drawable_fs_lock_position_changed (PikaDrawable *signal_drawable, PikaDrawable *drawable) { PikaLayer *fs = pika_drawable_get_floating_sel (drawable); pika_drawable_sync_fs_filter (drawable); pika_drawable_update (PIKA_DRAWABLE (fs), 0, 0, -1, -1); } static void pika_drawable_fs_format_changed (PikaDrawable *signal_drawable, PikaDrawable *drawable) { PikaLayer *fs = pika_drawable_get_floating_sel (drawable); pika_drawable_sync_fs_filter (drawable); pika_drawable_update (PIKA_DRAWABLE (fs), 0, 0, -1, -1); } static void pika_drawable_fs_affect_changed (PikaImage *image, PikaChannelType channel, PikaDrawable *drawable) { PikaLayer *fs = pika_drawable_get_floating_sel (drawable); pika_drawable_sync_fs_filter (drawable); pika_drawable_update (PIKA_DRAWABLE (fs), 0, 0, -1, -1); } static void pika_drawable_fs_mask_changed (PikaImage *image, PikaDrawable *drawable) { PikaLayer *fs = pika_drawable_get_floating_sel (drawable); pika_drawable_sync_fs_filter (drawable); pika_drawable_update (PIKA_DRAWABLE (fs), 0, 0, -1, -1); } static void pika_drawable_fs_visibility_changed (PikaLayer *fs, PikaDrawable *drawable) { if (pika_layer_get_excludes_backdrop (fs)) pika_drawable_update (drawable, 0, 0, -1, -1); else pika_drawable_update (PIKA_DRAWABLE (fs), 0, 0, -1, -1); } static void pika_drawable_fs_excludes_backdrop_changed (PikaLayer *fs, PikaDrawable *drawable) { if (pika_item_get_visible (PIKA_ITEM (fs))) pika_drawable_update (drawable, 0, 0, -1, -1); } static void pika_drawable_fs_bounding_box_changed (PikaLayer *fs, PikaDrawable *drawable) { pika_drawable_update_bounding_box (drawable); } static void pika_drawable_fs_update (PikaLayer *fs, gint x, gint y, gint width, gint height, PikaDrawable *drawable) { GeglRectangle bounding_box; GeglRectangle rect; gint fs_off_x, fs_off_y; gint off_x, off_y; pika_item_get_offset (PIKA_ITEM (fs), &fs_off_x, &fs_off_y); pika_item_get_offset (PIKA_ITEM (drawable), &off_x, &off_y); bounding_box = pika_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)) { pika_drawable_update (drawable, rect.x - off_x, rect.y - off_y, rect.width, rect.height); } }