/* 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 . */ /* This file contains the code necessary for generating on canvas * previews, by connecting a specified GEGL operation to do the * processing. It uses drawable filters that allow for non-destructive * manipulation of drawable data, with live preview on screen. * * To create a tool that uses this, see app/tools/pikafiltertool.c for * the interface and e.g. app/tools/pikacolorbalancetool.c for an * example of using that interface. */ #include "config.h" #include #include #include #include "libpikabase/pikabase.h" #include "libpikacolor/pikacolor.h" #include "core-types.h" #include "gegl/pika-babl.h" #include "gegl/pikaapplicator.h" #include "gegl/pika-gegl-utils.h" #include "pikachannel.h" #include "pikadrawable-filters.h" #include "pikadrawablefilter.h" #include "pikaimage.h" #include "pikalayer.h" #include "pikaprogress.h" enum { FLUSH, LAST_SIGNAL }; struct _PikaDrawableFilter { PikaFilter parent_instance; PikaDrawable *drawable; GeglNode *operation; gboolean has_input; gboolean clip; PikaFilterRegion region; gboolean crop_enabled; GeglRectangle crop_rect; gboolean preview_enabled; gboolean preview_split_enabled; PikaAlignmentType preview_split_alignment; gint preview_split_position; gdouble opacity; PikaLayerMode paint_mode; PikaLayerColorSpace blend_space; PikaLayerColorSpace composite_space; PikaLayerCompositeMode composite_mode; gboolean add_alpha; gboolean color_managed; gboolean gamma_hack; gboolean override_constraints; GeglRectangle filter_area; gboolean filter_clip; GeglNode *translate; GeglNode *crop_before; GeglNode *cast_before; GeglNode *cast_after; GeglNode *crop_after; PikaApplicator *applicator; }; static void pika_drawable_filter_dispose (GObject *object); static void pika_drawable_filter_finalize (GObject *object); static void pika_drawable_filter_sync_active (PikaDrawableFilter *filter); static void pika_drawable_filter_sync_clip (PikaDrawableFilter *filter, gboolean sync_region); static void pika_drawable_filter_sync_region (PikaDrawableFilter *filter); static void pika_drawable_filter_sync_crop (PikaDrawableFilter *filter, gboolean old_crop_enabled, const GeglRectangle *old_crop_rect, gboolean old_preview_split_enabled, PikaAlignmentType old_preview_split_alignment, gint old_preview_split_position, gboolean update); static void pika_drawable_filter_sync_opacity (PikaDrawableFilter *filter); static void pika_drawable_filter_sync_mode (PikaDrawableFilter *filter); static void pika_drawable_filter_sync_affect (PikaDrawableFilter *filter); static void pika_drawable_filter_sync_format (PikaDrawableFilter *filter); static void pika_drawable_filter_sync_mask (PikaDrawableFilter *filter); static void pika_drawable_filter_sync_gamma_hack (PikaDrawableFilter *filter); static gboolean pika_drawable_filter_is_added (PikaDrawableFilter *filter); static gboolean pika_drawable_filter_is_active (PikaDrawableFilter *filter); static gboolean pika_drawable_filter_add_filter (PikaDrawableFilter *filter); static gboolean pika_drawable_filter_remove_filter (PikaDrawableFilter *filter); static void pika_drawable_filter_update_drawable (PikaDrawableFilter *filter, const GeglRectangle *area); static void pika_drawable_filter_affect_changed (PikaImage *image, PikaChannelType channel, PikaDrawableFilter *filter); static void pika_drawable_filter_mask_changed (PikaImage *image, PikaDrawableFilter *filter); static void pika_drawable_filter_lock_position_changed (PikaDrawable *drawable, PikaDrawableFilter *filter); static void pika_drawable_filter_format_changed (PikaDrawable *drawable, PikaDrawableFilter *filter); static void pika_drawable_filter_drawable_removed (PikaDrawable *drawable, PikaDrawableFilter *filter); static void pika_drawable_filter_lock_alpha_changed (PikaLayer *layer, PikaDrawableFilter *filter); G_DEFINE_TYPE (PikaDrawableFilter, pika_drawable_filter, PIKA_TYPE_FILTER) #define parent_class pika_drawable_filter_parent_class static guint drawable_filter_signals[LAST_SIGNAL] = { 0, }; static void pika_drawable_filter_class_init (PikaDrawableFilterClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); drawable_filter_signals[FLUSH] = g_signal_new ("flush", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaDrawableFilterClass, flush), NULL, NULL, NULL, G_TYPE_NONE, 0); object_class->dispose = pika_drawable_filter_dispose; object_class->finalize = pika_drawable_filter_finalize; } static void pika_drawable_filter_init (PikaDrawableFilter *drawable_filter) { drawable_filter->clip = TRUE; drawable_filter->region = PIKA_FILTER_REGION_SELECTION; drawable_filter->preview_enabled = TRUE; drawable_filter->preview_split_enabled = FALSE; drawable_filter->preview_split_alignment = PIKA_ALIGN_LEFT; drawable_filter->preview_split_position = 0; drawable_filter->opacity = PIKA_OPACITY_OPAQUE; drawable_filter->paint_mode = PIKA_LAYER_MODE_REPLACE; drawable_filter->blend_space = PIKA_LAYER_COLOR_SPACE_AUTO; drawable_filter->composite_space = PIKA_LAYER_COLOR_SPACE_AUTO; drawable_filter->composite_mode = PIKA_LAYER_COMPOSITE_AUTO; } static void pika_drawable_filter_dispose (GObject *object) { PikaDrawableFilter *drawable_filter = PIKA_DRAWABLE_FILTER (object); if (drawable_filter->drawable) pika_drawable_filter_remove_filter (drawable_filter); G_OBJECT_CLASS (parent_class)->dispose (object); } static void pika_drawable_filter_finalize (GObject *object) { PikaDrawableFilter *drawable_filter = PIKA_DRAWABLE_FILTER (object); g_clear_object (&drawable_filter->operation); g_clear_object (&drawable_filter->applicator); g_clear_object (&drawable_filter->drawable); G_OBJECT_CLASS (parent_class)->finalize (object); } PikaDrawableFilter * pika_drawable_filter_new (PikaDrawable *drawable, const gchar *undo_desc, GeglNode *operation, const gchar *icon_name) { PikaDrawableFilter *filter; GeglNode *node; g_return_val_if_fail (PIKA_IS_DRAWABLE (drawable), NULL); g_return_val_if_fail (pika_item_is_attached (PIKA_ITEM (drawable)), NULL); g_return_val_if_fail (GEGL_IS_NODE (operation), NULL); g_return_val_if_fail (gegl_node_has_pad (operation, "output"), NULL); filter = g_object_new (PIKA_TYPE_DRAWABLE_FILTER, "name", undo_desc, "icon-name", icon_name, NULL); filter->drawable = g_object_ref (drawable); filter->operation = g_object_ref (operation); node = pika_filter_get_node (PIKA_FILTER (filter)); gegl_node_add_child (node, operation); pika_gegl_node_set_underlying_operation (node, operation); filter->applicator = pika_applicator_new (node); pika_filter_set_applicator (PIKA_FILTER (filter), filter->applicator); pika_applicator_set_cache (filter->applicator, TRUE); filter->has_input = gegl_node_has_pad (filter->operation, "input"); if (filter->has_input) { GeglNode *input; input = gegl_node_get_input_proxy (node, "input"); filter->translate = gegl_node_new_child (node, "operation", "gegl:translate", NULL); filter->crop_before = gegl_node_new_child (node, "operation", "gegl:crop", NULL); filter->cast_before = gegl_node_new_child (node, "operation", "gegl:nop", NULL); gegl_node_link_many (input, filter->translate, filter->crop_before, filter->cast_before, filter->operation, NULL); } filter->cast_after = gegl_node_new_child (node, "operation", "gegl:nop", NULL); filter->crop_after = gegl_node_new_child (node, "operation", "gegl:crop", NULL); gegl_node_link_many (filter->operation, filter->cast_after, filter->crop_after, NULL); gegl_node_connect (filter->crop_after, "output", node, "aux"); return filter; } PikaDrawable * pika_drawable_filter_get_drawable (PikaDrawableFilter *filter) { g_return_val_if_fail (PIKA_IS_DRAWABLE_FILTER (filter), NULL); return filter->drawable; } GeglNode * pika_drawable_filter_get_operation (PikaDrawableFilter *filter) { g_return_val_if_fail (PIKA_IS_DRAWABLE_FILTER (filter), NULL); return filter->operation; } void pika_drawable_filter_set_clip (PikaDrawableFilter *filter, gboolean clip) { g_return_if_fail (PIKA_IS_DRAWABLE_FILTER (filter)); if (clip != filter->clip) { filter->clip = clip; pika_drawable_filter_sync_clip (filter, TRUE); } } void pika_drawable_filter_set_region (PikaDrawableFilter *filter, PikaFilterRegion region) { g_return_if_fail (PIKA_IS_DRAWABLE_FILTER (filter)); if (region != filter->region) { filter->region = region; pika_drawable_filter_sync_region (filter); if (pika_drawable_filter_is_active (filter)) pika_drawable_filter_update_drawable (filter, NULL); } } void pika_drawable_filter_set_crop (PikaDrawableFilter *filter, const GeglRectangle *rect, gboolean update) { g_return_if_fail (PIKA_IS_DRAWABLE_FILTER (filter)); if ((rect != NULL) != filter->crop_enabled || (rect && ! gegl_rectangle_equal (rect, &filter->crop_rect))) { gboolean old_enabled = filter->crop_enabled; GeglRectangle old_rect = filter->crop_rect; if (rect) { filter->crop_enabled = TRUE; filter->crop_rect = *rect; } else { filter->crop_enabled = FALSE; } pika_drawable_filter_sync_crop (filter, old_enabled, &old_rect, filter->preview_split_enabled, filter->preview_split_alignment, filter->preview_split_position, update); } } void pika_drawable_filter_set_preview (PikaDrawableFilter *filter, gboolean enabled) { g_return_if_fail (PIKA_IS_DRAWABLE_FILTER (filter)); if (enabled != filter->preview_enabled) { filter->preview_enabled = enabled; pika_drawable_filter_sync_active (filter); if (pika_drawable_filter_is_added (filter)) { pika_drawable_update_bounding_box (filter->drawable); pika_drawable_filter_update_drawable (filter, NULL); } } } void pika_drawable_filter_set_preview_split (PikaDrawableFilter *filter, gboolean enabled, PikaAlignmentType alignment, gint position) { PikaItem *item; g_return_if_fail (PIKA_IS_DRAWABLE_FILTER (filter)); g_return_if_fail (alignment == PIKA_ALIGN_LEFT || alignment == PIKA_ALIGN_RIGHT || alignment == PIKA_ALIGN_TOP || alignment == PIKA_ALIGN_BOTTOM); item = PIKA_ITEM (filter->drawable); switch (alignment) { case PIKA_ALIGN_LEFT: case PIKA_ALIGN_RIGHT: position = CLAMP (position, 0, pika_item_get_width (item)); break; case PIKA_ALIGN_TOP: case PIKA_ALIGN_BOTTOM: position = CLAMP (position, 0, pika_item_get_height (item)); break; default: g_return_if_reached (); } if (enabled != filter->preview_split_enabled || alignment != filter->preview_split_alignment || position != filter->preview_split_position) { gboolean old_enabled = filter->preview_split_enabled; PikaAlignmentType old_alignment = filter->preview_split_alignment; gint old_position = filter->preview_split_position; filter->preview_split_enabled = enabled; filter->preview_split_alignment = alignment; filter->preview_split_position = position; pika_drawable_filter_sync_crop (filter, filter->crop_enabled, &filter->crop_rect, old_enabled, old_alignment, old_position, TRUE); } } void pika_drawable_filter_set_opacity (PikaDrawableFilter *filter, gdouble opacity) { g_return_if_fail (PIKA_IS_DRAWABLE_FILTER (filter)); if (opacity != filter->opacity) { filter->opacity = opacity; pika_drawable_filter_sync_opacity (filter); if (pika_drawable_filter_is_active (filter)) pika_drawable_filter_update_drawable (filter, NULL); } } void pika_drawable_filter_set_mode (PikaDrawableFilter *filter, PikaLayerMode paint_mode, PikaLayerColorSpace blend_space, PikaLayerColorSpace composite_space, PikaLayerCompositeMode composite_mode) { g_return_if_fail (PIKA_IS_DRAWABLE_FILTER (filter)); if (paint_mode != filter->paint_mode || blend_space != filter->blend_space || composite_space != filter->composite_space || composite_mode != filter->composite_mode) { filter->paint_mode = paint_mode; filter->blend_space = blend_space; filter->composite_space = composite_space; filter->composite_mode = composite_mode; pika_drawable_filter_sync_mode (filter); if (pika_drawable_filter_is_active (filter)) pika_drawable_filter_update_drawable (filter, NULL); } } void pika_drawable_filter_set_add_alpha (PikaDrawableFilter *filter, gboolean add_alpha) { g_return_if_fail (PIKA_IS_DRAWABLE_FILTER (filter)); if (add_alpha != filter->add_alpha) { filter->add_alpha = add_alpha; pika_drawable_filter_sync_format (filter); if (pika_drawable_filter_is_active (filter)) pika_drawable_filter_update_drawable (filter, NULL); } } void pika_drawable_filter_set_gamma_hack (PikaDrawableFilter *filter, gboolean gamma_hack) { g_return_if_fail (PIKA_IS_DRAWABLE_FILTER (filter)); if (gamma_hack != filter->gamma_hack) { filter->gamma_hack = gamma_hack; pika_drawable_filter_sync_gamma_hack (filter); if (pika_drawable_filter_is_active (filter)) pika_drawable_filter_update_drawable (filter, NULL); } } void pika_drawable_filter_set_override_constraints (PikaDrawableFilter *filter, gboolean override_constraints) { g_return_if_fail (PIKA_IS_DRAWABLE_FILTER (filter)); if (override_constraints != filter->override_constraints) { filter->override_constraints = override_constraints; pika_drawable_filter_sync_affect (filter); pika_drawable_filter_sync_format (filter); pika_drawable_filter_sync_clip (filter, TRUE); if (pika_drawable_filter_is_active (filter)) pika_drawable_filter_update_drawable (filter, NULL); } } const Babl * pika_drawable_filter_get_format (PikaDrawableFilter *filter) { const Babl *format; g_return_val_if_fail (PIKA_IS_DRAWABLE_FILTER (filter), NULL); format = pika_applicator_get_output_format (filter->applicator); if (! format) format = pika_drawable_get_format (filter->drawable); return format; } void pika_drawable_filter_apply (PikaDrawableFilter *filter, const GeglRectangle *area) { g_return_if_fail (PIKA_IS_DRAWABLE_FILTER (filter)); g_return_if_fail (pika_item_is_attached (PIKA_ITEM (filter->drawable))); pika_drawable_filter_add_filter (filter); pika_drawable_filter_sync_clip (filter, TRUE); if (pika_drawable_filter_is_active (filter)) { pika_drawable_update_bounding_box (filter->drawable); pika_drawable_filter_update_drawable (filter, area); } } gboolean pika_drawable_filter_commit (PikaDrawableFilter *filter, PikaProgress *progress, gboolean cancellable) { gboolean success = TRUE; g_return_val_if_fail (PIKA_IS_DRAWABLE_FILTER (filter), FALSE); g_return_val_if_fail (pika_item_is_attached (PIKA_ITEM (filter->drawable)), FALSE); g_return_val_if_fail (progress == NULL || PIKA_IS_PROGRESS (progress), FALSE); if (pika_drawable_filter_is_added (filter)) { const Babl *format; format = pika_drawable_filter_get_format (filter); pika_drawable_filter_set_preview_split (filter, FALSE, filter->preview_split_alignment, filter->preview_split_position); pika_drawable_filter_set_preview (filter, TRUE); success = pika_drawable_merge_filter (filter->drawable, PIKA_FILTER (filter), progress, pika_object_get_name (filter), format, filter->filter_clip, cancellable, FALSE); pika_drawable_filter_remove_filter (filter); if (! success) pika_drawable_filter_update_drawable (filter, NULL); g_signal_emit (filter, drawable_filter_signals[FLUSH], 0); } return success; } void pika_drawable_filter_abort (PikaDrawableFilter *filter) { g_return_if_fail (PIKA_IS_DRAWABLE_FILTER (filter)); if (pika_drawable_filter_remove_filter (filter)) { pika_drawable_filter_update_drawable (filter, NULL); } } /* private functions */ static void pika_drawable_filter_sync_active (PikaDrawableFilter *filter) { pika_applicator_set_active (filter->applicator, filter->preview_enabled); } static void pika_drawable_filter_sync_clip (PikaDrawableFilter *filter, gboolean sync_region) { gboolean clip; if (filter->override_constraints) clip = filter->clip; else clip = pika_item_get_clip (PIKA_ITEM (filter->drawable), filter->clip); if (! clip) { PikaImage *image = pika_item_get_image (PIKA_ITEM (filter->drawable)); PikaChannel *mask = pika_image_get_mask (image); if (! pika_channel_is_empty (mask)) clip = TRUE; } if (! clip) { GeglRectangle bounding_box; bounding_box = gegl_node_get_bounding_box (filter->operation); if (gegl_rectangle_is_infinite_plane (&bounding_box)) clip = TRUE; } if (clip != filter->filter_clip) { filter->filter_clip = clip; if (sync_region) pika_drawable_filter_sync_region (filter); } } static void pika_drawable_filter_sync_region (PikaDrawableFilter *filter) { if (filter->region == PIKA_FILTER_REGION_SELECTION) { if (filter->has_input) { gegl_node_set (filter->translate, "x", (gdouble) -filter->filter_area.x, "y", (gdouble) -filter->filter_area.y, NULL); gegl_node_set (filter->crop_before, "width", (gdouble) filter->filter_area.width, "height", (gdouble) filter->filter_area.height, NULL); } if (filter->filter_clip) { gegl_node_set (filter->crop_after, "operation", "gegl:crop", "x", 0.0, "y", 0.0, "width", (gdouble) filter->filter_area.width, "height", (gdouble) filter->filter_area.height, NULL); } else { gegl_node_set (filter->crop_after, "operation", "gegl:nop", NULL); } pika_applicator_set_apply_offset (filter->applicator, filter->filter_area.x, filter->filter_area.y); } else { PikaItem *item = PIKA_ITEM (filter->drawable); gdouble width = pika_item_get_width (item); gdouble height = pika_item_get_height (item); if (filter->has_input) { gegl_node_set (filter->translate, "x", (gdouble) 0.0, "y", (gdouble) 0.0, NULL); gegl_node_set (filter->crop_before, "width", width, "height", height, NULL); } if (filter->filter_clip) { gegl_node_set (filter->crop_after, "operation", "gegl:crop", "x", (gdouble) filter->filter_area.x, "y", (gdouble) filter->filter_area.y, "width", (gdouble) filter->filter_area.width, "height", (gdouble) filter->filter_area.height, NULL); } else { gegl_node_set (filter->crop_after, "operation", "gegl:nop", NULL); } pika_applicator_set_apply_offset (filter->applicator, 0, 0); } if (pika_drawable_filter_is_active (filter)) { if (pika_drawable_update_bounding_box (filter->drawable)) g_signal_emit (filter, drawable_filter_signals[FLUSH], 0); } } static gboolean pika_drawable_filter_get_crop_rect (PikaDrawableFilter *filter, gboolean crop_enabled, const GeglRectangle *crop_rect, gboolean preview_split_enabled, PikaAlignmentType preview_split_alignment, gint preview_split_position, GeglRectangle *rect) { GeglRectangle bounds; gint x1, x2; gint y1, y2; bounds = gegl_rectangle_infinite_plane (); x1 = bounds.x; x2 = bounds.x + bounds.width; y1 = bounds.y; y2 = bounds.y + bounds.height; if (preview_split_enabled) { switch (preview_split_alignment) { case PIKA_ALIGN_LEFT: x2 = preview_split_position; break; case PIKA_ALIGN_RIGHT: x1 = preview_split_position; break; case PIKA_ALIGN_TOP: y2 = preview_split_position; break; case PIKA_ALIGN_BOTTOM: y1 = preview_split_position; break; default: g_return_val_if_reached (FALSE); } } gegl_rectangle_set (rect, x1, y1, x2 - x1, y2 - y1); if (crop_enabled) gegl_rectangle_intersect (rect, rect, crop_rect); return ! gegl_rectangle_equal (rect, &bounds); } static void pika_drawable_filter_sync_crop (PikaDrawableFilter *filter, gboolean old_crop_enabled, const GeglRectangle *old_crop_rect, gboolean old_preview_split_enabled, PikaAlignmentType old_preview_split_alignment, gint old_preview_split_position, gboolean update) { GeglRectangle old_rect; GeglRectangle new_rect; gboolean enabled; pika_drawable_filter_get_crop_rect (filter, old_crop_enabled, old_crop_rect, old_preview_split_enabled, old_preview_split_alignment, old_preview_split_position, &old_rect); enabled = pika_drawable_filter_get_crop_rect (filter, filter->crop_enabled, &filter->crop_rect, filter->preview_split_enabled, filter->preview_split_alignment, filter->preview_split_position, &new_rect); pika_applicator_set_crop (filter->applicator, enabled ? &new_rect : NULL); if (update && pika_drawable_filter_is_active (filter) && ! gegl_rectangle_equal (&old_rect, &new_rect)) { GeglRectangle diff_rects[4]; gint n_diff_rects; gint i; pika_drawable_update_bounding_box (filter->drawable); n_diff_rects = gegl_rectangle_xor (diff_rects, &old_rect, &new_rect); for (i = 0; i < n_diff_rects; i++) pika_drawable_filter_update_drawable (filter, &diff_rects[i]); } } static void pika_drawable_filter_sync_opacity (PikaDrawableFilter *filter) { pika_applicator_set_opacity (filter->applicator, filter->opacity); } static void pika_drawable_filter_sync_mode (PikaDrawableFilter *filter) { PikaLayerMode paint_mode = filter->paint_mode; if (! filter->has_input && paint_mode == PIKA_LAYER_MODE_REPLACE) { /* if the filter's op has no input, use NORMAL instead of REPLACE, so * that we composite the op's output on top of the input, instead of * completely replacing it. */ paint_mode = PIKA_LAYER_MODE_NORMAL; } pika_applicator_set_mode (filter->applicator, paint_mode, filter->blend_space, filter->composite_space, filter->composite_mode); } static void pika_drawable_filter_sync_affect (PikaDrawableFilter *filter) { pika_applicator_set_affect ( filter->applicator, filter->override_constraints ? PIKA_COMPONENT_MASK_RED | PIKA_COMPONENT_MASK_GREEN | PIKA_COMPONENT_MASK_BLUE | PIKA_COMPONENT_MASK_ALPHA : pika_drawable_get_active_mask (filter->drawable)); } static void pika_drawable_filter_sync_format (PikaDrawableFilter *filter) { const Babl *format; if (filter->add_alpha && (pika_drawable_supports_alpha (filter->drawable) || filter->override_constraints)) { format = pika_drawable_get_format_with_alpha (filter->drawable); } else { format = pika_drawable_get_format (filter->drawable); } pika_applicator_set_output_format (filter->applicator, format); } static void pika_drawable_filter_sync_mask (PikaDrawableFilter *filter) { PikaImage *image = pika_item_get_image (PIKA_ITEM (filter->drawable)); PikaChannel *mask = pika_image_get_mask (image); if (pika_channel_is_empty (mask)) { pika_applicator_set_mask_buffer (filter->applicator, NULL); } else { GeglBuffer *mask_buffer; gint offset_x, offset_y; mask_buffer = pika_drawable_get_buffer (PIKA_DRAWABLE (mask)); pika_item_get_offset (PIKA_ITEM (filter->drawable), &offset_x, &offset_y); pika_applicator_set_mask_buffer (filter->applicator, mask_buffer); pika_applicator_set_mask_offset (filter->applicator, -offset_x, -offset_y); } pika_item_mask_intersect (PIKA_ITEM (filter->drawable), &filter->filter_area.x, &filter->filter_area.y, &filter->filter_area.width, &filter->filter_area.height); } static void pika_drawable_filter_sync_gamma_hack (PikaDrawableFilter *filter) { if (filter->gamma_hack) { const Babl *drawable_format; const Babl *cast_format; PikaTRCType trc = PIKA_TRC_LINEAR; switch (pika_drawable_get_trc (filter->drawable)) { case PIKA_TRC_LINEAR: trc = PIKA_TRC_NON_LINEAR; break; case PIKA_TRC_NON_LINEAR: trc = PIKA_TRC_LINEAR; break; case PIKA_TRC_PERCEPTUAL: trc = PIKA_TRC_LINEAR; break; } drawable_format = pika_drawable_get_format_with_alpha (filter->drawable); cast_format = pika_babl_format (pika_babl_format_get_base_type (drawable_format), pika_babl_precision (pika_babl_format_get_component_type (drawable_format), trc), TRUE, babl_format_get_space (drawable_format)); if (filter->has_input) { gegl_node_set (filter->cast_before, "operation", "gegl:cast-format", "input-format", drawable_format, "output-format", cast_format, NULL); } gegl_node_set (filter->cast_after, "operation", "gegl:cast-format", "input-format", cast_format, "output-format", drawable_format, NULL); } else { if (filter->has_input) { gegl_node_set (filter->cast_before, "operation", "gegl:nop", NULL); } gegl_node_set (filter->cast_after, "operation", "gegl:nop", NULL); } } static gboolean pika_drawable_filter_is_added (PikaDrawableFilter *filter) { return pika_drawable_has_filter (filter->drawable, PIKA_FILTER (filter)); } static gboolean pika_drawable_filter_is_active (PikaDrawableFilter *filter) { return pika_drawable_filter_is_added (filter) && filter->preview_enabled; } static gboolean pika_drawable_filter_add_filter (PikaDrawableFilter *filter) { if (! pika_drawable_filter_is_added (filter)) { PikaImage *image = pika_item_get_image (PIKA_ITEM (filter->drawable)); pika_viewable_preview_freeze (PIKA_VIEWABLE (filter->drawable)); pika_drawable_filter_sync_active (filter); pika_drawable_filter_sync_mask (filter); pika_drawable_filter_sync_clip (filter, FALSE); pika_drawable_filter_sync_region (filter); pika_drawable_filter_sync_crop (filter, filter->crop_enabled, &filter->crop_rect, filter->preview_split_enabled, filter->preview_split_alignment, filter->preview_split_position, TRUE); pika_drawable_filter_sync_opacity (filter); pika_drawable_filter_sync_mode (filter); pika_drawable_filter_sync_affect (filter); pika_drawable_filter_sync_format (filter); pika_drawable_filter_sync_gamma_hack (filter); pika_drawable_add_filter (filter->drawable, PIKA_FILTER (filter)); pika_drawable_update_bounding_box (filter->drawable); g_signal_connect (image, "component-active-changed", G_CALLBACK (pika_drawable_filter_affect_changed), filter); g_signal_connect (image, "mask-changed", G_CALLBACK (pika_drawable_filter_mask_changed), filter); g_signal_connect (filter->drawable, "lock-position-changed", G_CALLBACK (pika_drawable_filter_lock_position_changed), filter); g_signal_connect (filter->drawable, "format-changed", G_CALLBACK (pika_drawable_filter_format_changed), filter); g_signal_connect (filter->drawable, "removed", G_CALLBACK (pika_drawable_filter_drawable_removed), filter); if (PIKA_IS_LAYER (filter->drawable)) { g_signal_connect (filter->drawable, "lock-alpha-changed", G_CALLBACK (pika_drawable_filter_lock_alpha_changed), filter); } return TRUE; } return FALSE; } static gboolean pika_drawable_filter_remove_filter (PikaDrawableFilter *filter) { if (pika_drawable_filter_is_added (filter)) { PikaImage *image = pika_item_get_image (PIKA_ITEM (filter->drawable)); if (PIKA_IS_LAYER (filter->drawable)) { g_signal_handlers_disconnect_by_func (filter->drawable, pika_drawable_filter_lock_alpha_changed, filter); } g_signal_handlers_disconnect_by_func (filter->drawable, pika_drawable_filter_drawable_removed, filter); g_signal_handlers_disconnect_by_func (filter->drawable, pika_drawable_filter_format_changed, filter); g_signal_handlers_disconnect_by_func (filter->drawable, pika_drawable_filter_lock_position_changed, filter); g_signal_handlers_disconnect_by_func (image, pika_drawable_filter_mask_changed, filter); g_signal_handlers_disconnect_by_func (image, pika_drawable_filter_affect_changed, filter); pika_drawable_remove_filter (filter->drawable, PIKA_FILTER (filter)); pika_drawable_update_bounding_box (filter->drawable); pika_viewable_preview_thaw (PIKA_VIEWABLE (filter->drawable)); return TRUE; } return FALSE; } static void pika_drawable_filter_update_drawable (PikaDrawableFilter *filter, const GeglRectangle *area) { GeglRectangle bounding_box; GeglRectangle update_area; bounding_box = pika_drawable_get_bounding_box (filter->drawable); if (area) { if (! gegl_rectangle_intersect (&update_area, area, &bounding_box)) { return; } } else { pika_drawable_filter_get_crop_rect (filter, filter->crop_enabled, &filter->crop_rect, filter->preview_split_enabled, filter->preview_split_alignment, filter->preview_split_position, &update_area); if (! gegl_rectangle_intersect (&update_area, &update_area, &bounding_box)) { return; } } if (update_area.width > 0 && update_area.height > 0) { pika_drawable_update (filter->drawable, update_area.x, update_area.y, update_area.width, update_area.height); g_signal_emit (filter, drawable_filter_signals[FLUSH], 0); } } static void pika_drawable_filter_affect_changed (PikaImage *image, PikaChannelType channel, PikaDrawableFilter *filter) { pika_drawable_filter_sync_affect (filter); pika_drawable_filter_update_drawable (filter, NULL); } static void pika_drawable_filter_mask_changed (PikaImage *image, PikaDrawableFilter *filter) { pika_drawable_filter_update_drawable (filter, NULL); pika_drawable_filter_sync_mask (filter); pika_drawable_filter_sync_clip (filter, FALSE); pika_drawable_filter_sync_region (filter); pika_drawable_filter_update_drawable (filter, NULL); } static void pika_drawable_filter_lock_position_changed (PikaDrawable *drawable, PikaDrawableFilter *filter) { pika_drawable_filter_sync_clip (filter, TRUE); pika_drawable_filter_update_drawable (filter, NULL); } static void pika_drawable_filter_format_changed (PikaDrawable *drawable, PikaDrawableFilter *filter) { pika_drawable_filter_sync_format (filter); pika_drawable_filter_update_drawable (filter, NULL); } static void pika_drawable_filter_drawable_removed (PikaDrawable *drawable, PikaDrawableFilter *filter) { pika_drawable_filter_remove_filter (filter); } static void pika_drawable_filter_lock_alpha_changed (PikaLayer *layer, PikaDrawableFilter *filter) { pika_drawable_filter_sync_affect (filter); pika_drawable_filter_update_drawable (filter, NULL); }