515 lines
18 KiB
C
515 lines
18 KiB
C
/* 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 <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <cairo.h>
|
|
#include <gegl.h>
|
|
#include <gdk-pixbuf/gdk-pixbuf.h>
|
|
|
|
#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);
|
|
}
|
|
}
|