summaryrefslogtreecommitdiffstats
path: root/app/core/gimpdrawablefilter.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:30:19 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:30:19 +0000
commit5c1676dfe6d2f3c837a5e074117b45613fd29a72 (patch)
treecbffb45144febf451e54061db2b21395faf94bfe /app/core/gimpdrawablefilter.c
parentInitial commit. (diff)
downloadgimp-5c1676dfe6d2f3c837a5e074117b45613fd29a72.tar.xz
gimp-5c1676dfe6d2f3c837a5e074117b45613fd29a72.zip
Adding upstream version 2.10.34.upstream/2.10.34upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'app/core/gimpdrawablefilter.c')
-rw-r--r--app/core/gimpdrawablefilter.c1364
1 files changed, 1364 insertions, 0 deletions
diff --git a/app/core/gimpdrawablefilter.c b/app/core/gimpdrawablefilter.c
new file mode 100644
index 0000000..6cda4f1
--- /dev/null
+++ b/app/core/gimpdrawablefilter.c
@@ -0,0 +1,1364 @@
+/* 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/>.
+ */
+
+/* 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/gimpfiltertool.c for
+ * the interface and e.g. app/tools/gimpcolorbalancetool.c for an
+ * example of using that interface.
+ */
+
+#include "config.h"
+
+#include <cairo.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpcolor/gimpcolor.h"
+
+#include "core-types.h"
+
+#include "gegl/gimp-babl.h"
+#include "gegl/gimpapplicator.h"
+#include "gegl/gimp-gegl-utils.h"
+
+#include "gimpchannel.h"
+#include "gimpdrawable-filters.h"
+#include "gimpdrawablefilter.h"
+#include "gimpimage.h"
+#include "gimplayer.h"
+#include "gimpmarshal.h"
+#include "gimpprogress.h"
+
+
+enum
+{
+ FLUSH,
+ LAST_SIGNAL
+};
+
+
+struct _GimpDrawableFilter
+{
+ GimpFilter parent_instance;
+
+ GimpDrawable *drawable;
+ GeglNode *operation;
+
+ gboolean has_input;
+
+ gboolean clip;
+ GimpFilterRegion region;
+ gboolean crop_enabled;
+ GeglRectangle crop_rect;
+ gboolean preview_enabled;
+ gboolean preview_split_enabled;
+ GimpAlignmentType preview_split_alignment;
+ gint preview_split_position;
+ gdouble opacity;
+ GimpLayerMode paint_mode;
+ GimpLayerColorSpace blend_space;
+ GimpLayerColorSpace composite_space;
+ GimpLayerCompositeMode 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 *transform_before;
+ GeglNode *transform_after;
+ GeglNode *cast_after;
+ GeglNode *crop_after;
+ GimpApplicator *applicator;
+};
+
+
+static void gimp_drawable_filter_dispose (GObject *object);
+static void gimp_drawable_filter_finalize (GObject *object);
+
+static void gimp_drawable_filter_sync_active (GimpDrawableFilter *filter);
+static void gimp_drawable_filter_sync_clip (GimpDrawableFilter *filter,
+ gboolean sync_region);
+static void gimp_drawable_filter_sync_region (GimpDrawableFilter *filter);
+static void gimp_drawable_filter_sync_crop (GimpDrawableFilter *filter,
+ gboolean old_crop_enabled,
+ const GeglRectangle *old_crop_rect,
+ gboolean old_preview_split_enabled,
+ GimpAlignmentType old_preview_split_alignment,
+ gint old_preview_split_position,
+ gboolean update);
+static void gimp_drawable_filter_sync_opacity (GimpDrawableFilter *filter);
+static void gimp_drawable_filter_sync_mode (GimpDrawableFilter *filter);
+static void gimp_drawable_filter_sync_affect (GimpDrawableFilter *filter);
+static void gimp_drawable_filter_sync_format (GimpDrawableFilter *filter);
+static void gimp_drawable_filter_sync_mask (GimpDrawableFilter *filter);
+static void gimp_drawable_filter_sync_transform (GimpDrawableFilter *filter);
+static void gimp_drawable_filter_sync_gamma_hack (GimpDrawableFilter *filter);
+
+static gboolean gimp_drawable_filter_is_added (GimpDrawableFilter *filter);
+static gboolean gimp_drawable_filter_is_active (GimpDrawableFilter *filter);
+static gboolean gimp_drawable_filter_add_filter (GimpDrawableFilter *filter);
+static gboolean gimp_drawable_filter_remove_filter (GimpDrawableFilter *filter);
+
+static void gimp_drawable_filter_update_drawable (GimpDrawableFilter *filter,
+ const GeglRectangle *area);
+
+static void gimp_drawable_filter_affect_changed (GimpImage *image,
+ GimpChannelType channel,
+ GimpDrawableFilter *filter);
+static void gimp_drawable_filter_mask_changed (GimpImage *image,
+ GimpDrawableFilter *filter);
+static void gimp_drawable_filter_profile_changed (GimpColorManaged *managed,
+ GimpDrawableFilter *filter);
+static void gimp_drawable_filter_lock_position_changed (GimpDrawable *drawable,
+ GimpDrawableFilter *filter);
+static void gimp_drawable_filter_format_changed (GimpDrawable *drawable,
+ GimpDrawableFilter *filter);
+static void gimp_drawable_filter_drawable_removed (GimpDrawable *drawable,
+ GimpDrawableFilter *filter);
+static void gimp_drawable_filter_lock_alpha_changed (GimpLayer *layer,
+ GimpDrawableFilter *filter);
+
+
+G_DEFINE_TYPE (GimpDrawableFilter, gimp_drawable_filter, GIMP_TYPE_FILTER)
+
+#define parent_class gimp_drawable_filter_parent_class
+
+static guint drawable_filter_signals[LAST_SIGNAL] = { 0, };
+
+
+static void
+gimp_drawable_filter_class_init (GimpDrawableFilterClass *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 (GimpDrawableFilterClass, flush),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ object_class->dispose = gimp_drawable_filter_dispose;
+ object_class->finalize = gimp_drawable_filter_finalize;
+}
+
+static void
+gimp_drawable_filter_init (GimpDrawableFilter *drawable_filter)
+{
+ drawable_filter->clip = TRUE;
+ drawable_filter->region = GIMP_FILTER_REGION_SELECTION;
+ drawable_filter->preview_enabled = TRUE;
+ drawable_filter->preview_split_enabled = FALSE;
+ drawable_filter->preview_split_alignment = GIMP_ALIGN_LEFT;
+ drawable_filter->preview_split_position = 0;
+ drawable_filter->opacity = GIMP_OPACITY_OPAQUE;
+ drawable_filter->paint_mode = GIMP_LAYER_MODE_REPLACE;
+ drawable_filter->blend_space = GIMP_LAYER_COLOR_SPACE_AUTO;
+ drawable_filter->composite_space = GIMP_LAYER_COLOR_SPACE_AUTO;
+ drawable_filter->composite_mode = GIMP_LAYER_COMPOSITE_AUTO;
+}
+
+static void
+gimp_drawable_filter_dispose (GObject *object)
+{
+ GimpDrawableFilter *drawable_filter = GIMP_DRAWABLE_FILTER (object);
+
+ if (drawable_filter->drawable)
+ gimp_drawable_filter_remove_filter (drawable_filter);
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+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_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+GimpDrawableFilter *
+gimp_drawable_filter_new (GimpDrawable *drawable,
+ const gchar *undo_desc,
+ GeglNode *operation,
+ const gchar *icon_name)
+{
+ GimpDrawableFilter *filter;
+ GeglNode *node;
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_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 (GIMP_TYPE_DRAWABLE_FILTER,
+ "name", undo_desc,
+ "icon-name", icon_name,
+ NULL);
+
+ filter->drawable = g_object_ref (drawable);
+ filter->operation = g_object_ref (operation);
+
+ node = gimp_filter_get_node (GIMP_FILTER (filter));
+
+ gegl_node_add_child (node, operation);
+ gimp_gegl_node_set_underlying_operation (node, operation);
+
+ filter->applicator = gimp_applicator_new (node);
+
+ gimp_filter_set_applicator (GIMP_FILTER (filter), filter->applicator);
+
+ gimp_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);
+
+ filter->transform_before = gegl_node_new_child (node,
+ "operation", "gegl:nop",
+ NULL);
+
+ gegl_node_link_many (input,
+ filter->translate,
+ filter->crop_before,
+ filter->cast_before,
+ filter->transform_before,
+ filter->operation,
+ NULL);
+ }
+
+ filter->transform_after = gegl_node_new_child (node,
+ "operation", "gegl:nop",
+ 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->transform_after,
+ filter->cast_after,
+ filter->crop_after,
+ NULL);
+
+ gegl_node_connect_to (filter->crop_after, "output",
+ node, "aux");
+
+ return filter;
+}
+
+GimpDrawable *
+gimp_drawable_filter_get_drawable (GimpDrawableFilter *filter)
+{
+ g_return_val_if_fail (GIMP_IS_DRAWABLE_FILTER (filter), NULL);
+
+ return filter->drawable;
+}
+
+GeglNode *
+gimp_drawable_filter_get_operation (GimpDrawableFilter *filter)
+{
+ g_return_val_if_fail (GIMP_IS_DRAWABLE_FILTER (filter), NULL);
+
+ return filter->operation;
+}
+
+void
+gimp_drawable_filter_set_clip (GimpDrawableFilter *filter,
+ gboolean clip)
+{
+ g_return_if_fail (GIMP_IS_DRAWABLE_FILTER (filter));
+
+ if (clip != filter->clip)
+ {
+ filter->clip = clip;
+
+ gimp_drawable_filter_sync_clip (filter, TRUE);
+ }
+}
+
+void
+gimp_drawable_filter_set_region (GimpDrawableFilter *filter,
+ GimpFilterRegion region)
+{
+ g_return_if_fail (GIMP_IS_DRAWABLE_FILTER (filter));
+
+ if (region != filter->region)
+ {
+ filter->region = region;
+
+ gimp_drawable_filter_sync_region (filter);
+
+ if (gimp_drawable_filter_is_active (filter))
+ gimp_drawable_filter_update_drawable (filter, NULL);
+ }
+}
+
+void
+gimp_drawable_filter_set_crop (GimpDrawableFilter *filter,
+ const GeglRectangle *rect,
+ gboolean update)
+{
+ g_return_if_fail (GIMP_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;
+ }
+
+ gimp_drawable_filter_sync_crop (filter,
+ old_enabled,
+ &old_rect,
+ filter->preview_split_enabled,
+ filter->preview_split_alignment,
+ filter->preview_split_position,
+ update);
+ }
+}
+
+void
+gimp_drawable_filter_set_preview (GimpDrawableFilter *filter,
+ gboolean enabled)
+{
+ g_return_if_fail (GIMP_IS_DRAWABLE_FILTER (filter));
+
+ if (enabled != filter->preview_enabled)
+ {
+ filter->preview_enabled = enabled;
+
+ gimp_drawable_filter_sync_active (filter);
+
+ if (gimp_drawable_filter_is_added (filter))
+ {
+ gimp_drawable_update_bounding_box (filter->drawable);
+
+ gimp_drawable_filter_update_drawable (filter, NULL);
+ }
+ }
+}
+
+void
+gimp_drawable_filter_set_preview_split (GimpDrawableFilter *filter,
+ gboolean enabled,
+ GimpAlignmentType alignment,
+ gint position)
+{
+ GimpItem *item;
+
+ g_return_if_fail (GIMP_IS_DRAWABLE_FILTER (filter));
+ g_return_if_fail (alignment == GIMP_ALIGN_LEFT ||
+ alignment == GIMP_ALIGN_RIGHT ||
+ alignment == GIMP_ALIGN_TOP ||
+ alignment == GIMP_ALIGN_BOTTOM);
+
+ item = GIMP_ITEM (filter->drawable);
+
+ switch (alignment)
+ {
+ case GIMP_ALIGN_LEFT:
+ case GIMP_ALIGN_RIGHT:
+ position = CLAMP (position, 0, gimp_item_get_width (item));
+ break;
+
+ case GIMP_ALIGN_TOP:
+ case GIMP_ALIGN_BOTTOM:
+ position = CLAMP (position, 0, gimp_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;
+ GimpAlignmentType 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;
+
+ gimp_drawable_filter_sync_crop (filter,
+ filter->crop_enabled,
+ &filter->crop_rect,
+ old_enabled,
+ old_alignment,
+ old_position,
+ TRUE);
+ }
+}
+
+void
+gimp_drawable_filter_set_opacity (GimpDrawableFilter *filter,
+ gdouble opacity)
+{
+ g_return_if_fail (GIMP_IS_DRAWABLE_FILTER (filter));
+
+ if (opacity != filter->opacity)
+ {
+ filter->opacity = opacity;
+
+ gimp_drawable_filter_sync_opacity (filter);
+
+ if (gimp_drawable_filter_is_active (filter))
+ gimp_drawable_filter_update_drawable (filter, NULL);
+ }
+}
+
+void
+gimp_drawable_filter_set_mode (GimpDrawableFilter *filter,
+ GimpLayerMode paint_mode,
+ GimpLayerColorSpace blend_space,
+ GimpLayerColorSpace composite_space,
+ GimpLayerCompositeMode composite_mode)
+{
+ g_return_if_fail (GIMP_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;
+
+ gimp_drawable_filter_sync_mode (filter);
+
+ if (gimp_drawable_filter_is_active (filter))
+ gimp_drawable_filter_update_drawable (filter, NULL);
+ }
+}
+
+void
+gimp_drawable_filter_set_add_alpha (GimpDrawableFilter *filter,
+ gboolean add_alpha)
+{
+ g_return_if_fail (GIMP_IS_DRAWABLE_FILTER (filter));
+
+ if (add_alpha != filter->add_alpha)
+ {
+ filter->add_alpha = add_alpha;
+
+ gimp_drawable_filter_sync_format (filter);
+
+ if (gimp_drawable_filter_is_active (filter))
+ gimp_drawable_filter_update_drawable (filter, NULL);
+ }
+}
+
+void
+gimp_drawable_filter_set_color_managed (GimpDrawableFilter *filter,
+ gboolean color_managed)
+{
+ g_return_if_fail (GIMP_IS_DRAWABLE_FILTER (filter));
+
+ if (color_managed != filter->color_managed)
+ {
+ filter->color_managed = color_managed;
+
+ gimp_drawable_filter_sync_transform (filter);
+
+ if (gimp_drawable_filter_is_active (filter))
+ gimp_drawable_filter_update_drawable (filter, NULL);
+ }
+}
+
+void
+gimp_drawable_filter_set_gamma_hack (GimpDrawableFilter *filter,
+ gboolean gamma_hack)
+{
+ g_return_if_fail (GIMP_IS_DRAWABLE_FILTER (filter));
+
+ if (gamma_hack != filter->gamma_hack)
+ {
+ filter->gamma_hack = gamma_hack;
+
+ gimp_drawable_filter_sync_gamma_hack (filter);
+ gimp_drawable_filter_sync_transform (filter);
+
+ if (gimp_drawable_filter_is_active (filter))
+ gimp_drawable_filter_update_drawable (filter, NULL);
+ }
+}
+
+void
+gimp_drawable_filter_set_override_constraints (GimpDrawableFilter *filter,
+ gboolean override_constraints)
+{
+ g_return_if_fail (GIMP_IS_DRAWABLE_FILTER (filter));
+
+ if (override_constraints != filter->override_constraints)
+ {
+ filter->override_constraints = override_constraints;
+
+ gimp_drawable_filter_sync_affect (filter);
+ gimp_drawable_filter_sync_format (filter);
+ gimp_drawable_filter_sync_clip (filter, TRUE);
+
+ if (gimp_drawable_filter_is_active (filter))
+ gimp_drawable_filter_update_drawable (filter, NULL);
+ }
+}
+
+const Babl *
+gimp_drawable_filter_get_format (GimpDrawableFilter *filter)
+{
+ const Babl *format;
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE_FILTER (filter), NULL);
+
+ format = gimp_applicator_get_output_format (filter->applicator);
+
+ if (! format)
+ format = gimp_drawable_get_format (filter->drawable);
+
+ return format;
+}
+
+void
+gimp_drawable_filter_apply (GimpDrawableFilter *filter,
+ const GeglRectangle *area)
+{
+ g_return_if_fail (GIMP_IS_DRAWABLE_FILTER (filter));
+ g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (filter->drawable)));
+
+ gimp_drawable_filter_add_filter (filter);
+
+ gimp_drawable_filter_sync_clip (filter, TRUE);
+
+ if (gimp_drawable_filter_is_active (filter))
+ {
+ gimp_drawable_update_bounding_box (filter->drawable);
+
+ gimp_drawable_filter_update_drawable (filter, area);
+ }
+}
+
+gboolean
+gimp_drawable_filter_commit (GimpDrawableFilter *filter,
+ GimpProgress *progress,
+ gboolean cancellable)
+{
+ gboolean success = TRUE;
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE_FILTER (filter), FALSE);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (filter->drawable)),
+ FALSE);
+ g_return_val_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress), FALSE);
+
+ if (gimp_drawable_filter_is_added (filter))
+ {
+ const Babl *format;
+
+ format = gimp_drawable_filter_get_format (filter);
+
+ gimp_drawable_filter_set_preview_split (filter, FALSE,
+ filter->preview_split_alignment,
+ 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);
+
+ gimp_drawable_filter_remove_filter (filter);
+
+ if (! success)
+ gimp_drawable_filter_update_drawable (filter, NULL);
+
+ g_signal_emit (filter, drawable_filter_signals[FLUSH], 0);
+ }
+
+ return success;
+}
+
+void
+gimp_drawable_filter_abort (GimpDrawableFilter *filter)
+{
+ g_return_if_fail (GIMP_IS_DRAWABLE_FILTER (filter));
+
+ if (gimp_drawable_filter_remove_filter (filter))
+ {
+ gimp_drawable_filter_update_drawable (filter, NULL);
+ }
+}
+
+
+/* private functions */
+
+static void
+gimp_drawable_filter_sync_active (GimpDrawableFilter *filter)
+{
+ gimp_applicator_set_active (filter->applicator, filter->preview_enabled);
+}
+
+static void
+gimp_drawable_filter_sync_clip (GimpDrawableFilter *filter,
+ gboolean sync_region)
+{
+ gboolean clip;
+
+ if (filter->override_constraints)
+ clip = filter->clip;
+ else
+ clip = gimp_item_get_clip (GIMP_ITEM (filter->drawable), filter->clip);
+
+ if (! clip)
+ {
+ GimpImage *image = gimp_item_get_image (GIMP_ITEM (filter->drawable));
+ GimpChannel *mask = gimp_image_get_mask (image);
+
+ if (! gimp_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)
+ gimp_drawable_filter_sync_region (filter);
+ }
+}
+
+static void
+gimp_drawable_filter_sync_region (GimpDrawableFilter *filter)
+{
+ if (filter->region == GIMP_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);
+ }
+
+ gimp_applicator_set_apply_offset (filter->applicator,
+ filter->filter_area.x,
+ filter->filter_area.y);
+ }
+ else
+ {
+ GimpItem *item = GIMP_ITEM (filter->drawable);
+ gdouble width = gimp_item_get_width (item);
+ gdouble height = gimp_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);
+ }
+
+ gimp_applicator_set_apply_offset (filter->applicator, 0, 0);
+ }
+
+ if (gimp_drawable_filter_is_active (filter))
+ {
+ if (gimp_drawable_update_bounding_box (filter->drawable))
+ g_signal_emit (filter, drawable_filter_signals[FLUSH], 0);
+ }
+}
+
+static gboolean
+gimp_drawable_filter_get_crop_rect (GimpDrawableFilter *filter,
+ gboolean crop_enabled,
+ const GeglRectangle *crop_rect,
+ gboolean preview_split_enabled,
+ GimpAlignmentType 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 GIMP_ALIGN_LEFT:
+ x2 = preview_split_position;
+ break;
+
+ case GIMP_ALIGN_RIGHT:
+ x1 = preview_split_position;
+ break;
+
+ case GIMP_ALIGN_TOP:
+ y2 = preview_split_position;
+ break;
+
+ case GIMP_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
+gimp_drawable_filter_sync_crop (GimpDrawableFilter *filter,
+ gboolean old_crop_enabled,
+ const GeglRectangle *old_crop_rect,
+ gboolean old_preview_split_enabled,
+ GimpAlignmentType old_preview_split_alignment,
+ gint old_preview_split_position,
+ gboolean update)
+{
+ GeglRectangle old_rect;
+ GeglRectangle new_rect;
+ gboolean enabled;
+
+ gimp_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 = gimp_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);
+
+ gimp_applicator_set_crop (filter->applicator, enabled ? &new_rect : NULL);
+
+ if (update &&
+ gimp_drawable_filter_is_active (filter) &&
+ ! gegl_rectangle_equal (&old_rect, &new_rect))
+ {
+ GeglRectangle diff_rects[4];
+ gint n_diff_rects;
+ gint i;
+
+ gimp_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++)
+ gimp_drawable_filter_update_drawable (filter, &diff_rects[i]);
+ }
+}
+
+static void
+gimp_drawable_filter_sync_opacity (GimpDrawableFilter *filter)
+{
+ gimp_applicator_set_opacity (filter->applicator,
+ filter->opacity);
+}
+
+static void
+gimp_drawable_filter_sync_mode (GimpDrawableFilter *filter)
+{
+ GimpLayerMode paint_mode = filter->paint_mode;
+
+ if (! filter->has_input && paint_mode == GIMP_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 = GIMP_LAYER_MODE_NORMAL;
+ }
+
+ gimp_applicator_set_mode (filter->applicator,
+ paint_mode,
+ filter->blend_space,
+ filter->composite_space,
+ filter->composite_mode);
+}
+
+static void
+gimp_drawable_filter_sync_affect (GimpDrawableFilter *filter)
+{
+ gimp_applicator_set_affect (
+ filter->applicator,
+ filter->override_constraints ?
+
+ GIMP_COMPONENT_MASK_RED |
+ GIMP_COMPONENT_MASK_GREEN |
+ GIMP_COMPONENT_MASK_BLUE |
+ GIMP_COMPONENT_MASK_ALPHA :
+
+ gimp_drawable_get_active_mask (filter->drawable));
+}
+
+static void
+gimp_drawable_filter_sync_format (GimpDrawableFilter *filter)
+{
+ const Babl *format;
+
+ if (filter->add_alpha &&
+ (gimp_drawable_supports_alpha (filter->drawable) ||
+ filter->override_constraints))
+ {
+ format = gimp_drawable_get_format_with_alpha (filter->drawable);
+ }
+ else
+ {
+ format = gimp_drawable_get_format (filter->drawable);
+ }
+
+ gimp_applicator_set_output_format (filter->applicator, format);
+}
+
+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);
+
+ if (gimp_channel_is_empty (mask))
+ {
+ gimp_applicator_set_mask_buffer (filter->applicator, NULL);
+ }
+ else
+ {
+ GeglBuffer *mask_buffer;
+ gint offset_x, offset_y;
+
+ mask_buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (mask));
+ gimp_item_get_offset (GIMP_ITEM (filter->drawable),
+ &offset_x, &offset_y);
+
+ 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);
+}
+
+static void
+gimp_drawable_filter_sync_transform (GimpDrawableFilter *filter)
+{
+ GimpColorManaged *managed = GIMP_COLOR_MANAGED (filter->drawable);
+
+ if (filter->color_managed)
+ {
+ const Babl *drawable_format = NULL;
+ const Babl *input_format = NULL;
+ const Babl *output_format = NULL;
+ GimpColorProfile *drawable_profile = NULL;
+ GimpColorProfile *input_profile = NULL;
+ GimpColorProfile *output_profile = NULL;
+ guint32 dummy;
+
+ drawable_format = gimp_drawable_get_format (filter->drawable);
+ if (filter->has_input)
+ input_format = gimp_gegl_node_get_format (filter->operation, "input");
+ output_format = gimp_gegl_node_get_format (filter->operation, "output");
+
+ g_printerr ("drawable format: %s\n", babl_get_name (drawable_format));
+ if (filter->has_input)
+ g_printerr ("filter input format: %s\n", babl_get_name (input_format));
+ g_printerr ("filter output format: %s\n", babl_get_name (output_format));
+
+ /* convert the drawable format to float, so we get a precise
+ * color transform
+ */
+ drawable_format =
+ gimp_babl_format (gimp_babl_format_get_base_type (drawable_format),
+ gimp_babl_precision (GIMP_COMPONENT_TYPE_FLOAT,
+ gimp_babl_format_get_linear (drawable_format)),
+ babl_format_has_alpha (drawable_format));
+
+ /* convert the filter input/output formats to something we have
+ * built-in color profiles for (see the get_color_profile()
+ * calls below)
+ */
+ if (filter->has_input)
+ input_format = gimp_color_profile_get_lcms_format (input_format, &dummy);
+ output_format = gimp_color_profile_get_lcms_format (output_format, &dummy);
+
+ g_printerr ("profile transform drawable format: %s\n",
+ babl_get_name (drawable_format));
+ if (filter->has_input)
+ g_printerr ("profile transform input format: %s\n",
+ babl_get_name (input_format));
+ g_printerr ("profile transform output format: %s\n",
+ babl_get_name (output_format));
+
+ drawable_profile = gimp_color_managed_get_color_profile (managed);
+ if (filter->has_input)
+ input_profile = gimp_babl_format_get_color_profile (input_format);
+ output_profile = gimp_babl_format_get_color_profile (output_format);
+
+ if ((filter->has_input &&
+ ! gimp_color_transform_can_gegl_copy (drawable_profile,
+ input_profile)) ||
+ ! gimp_color_transform_can_gegl_copy (output_profile,
+ drawable_profile))
+ {
+ g_printerr ("using gimp:profile-transform\n");
+
+ if (filter->has_input)
+ {
+ gegl_node_set (filter->transform_before,
+ "operation", "gimp:profile-transform",
+ "src-profile", drawable_profile,
+ "src-format", drawable_format,
+ "dest-profile", input_profile,
+ "dest-format", input_format,
+ NULL);
+ }
+
+ gegl_node_set (filter->transform_after,
+ "operation", "gimp:profile-transform",
+ "src-profile", output_profile,
+ "src-format", output_format,
+ "dest-profile", drawable_profile,
+ "dest-format", drawable_format,
+ NULL);
+
+ return;
+ }
+ }
+
+ g_printerr ("using gegl copy\n");
+
+ if (filter->has_input)
+ {
+ gegl_node_set (filter->transform_before,
+ "operation", "gegl:nop",
+ NULL);
+ }
+
+ gegl_node_set (filter->transform_after,
+ "operation", "gegl:nop",
+ NULL);
+}
+
+static void
+gimp_drawable_filter_sync_gamma_hack (GimpDrawableFilter *filter)
+{
+ if (filter->gamma_hack)
+ {
+ const Babl *drawable_format;
+ const Babl *cast_format;
+
+ drawable_format =
+ gimp_drawable_get_format_with_alpha (filter->drawable);
+
+ cast_format =
+ gimp_babl_format (gimp_babl_format_get_base_type (drawable_format),
+ gimp_babl_precision (gimp_babl_format_get_component_type (drawable_format),
+ ! gimp_babl_format_get_linear (drawable_format)),
+ TRUE);
+
+ 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
+gimp_drawable_filter_is_added (GimpDrawableFilter *filter)
+{
+ return gimp_drawable_has_filter (filter->drawable,
+ GIMP_FILTER (filter));
+}
+
+static gboolean
+gimp_drawable_filter_is_active (GimpDrawableFilter *filter)
+{
+ return gimp_drawable_filter_is_added (filter) &&
+ filter->preview_enabled;
+}
+
+static gboolean
+gimp_drawable_filter_add_filter (GimpDrawableFilter *filter)
+{
+ if (! gimp_drawable_filter_is_added (filter))
+ {
+ GimpImage *image = gimp_item_get_image (GIMP_ITEM (filter->drawable));
+
+ gimp_viewable_preview_freeze (GIMP_VIEWABLE (filter->drawable));
+
+ gimp_drawable_filter_sync_active (filter);
+ gimp_drawable_filter_sync_mask (filter);
+ gimp_drawable_filter_sync_clip (filter, FALSE);
+ gimp_drawable_filter_sync_region (filter);
+ gimp_drawable_filter_sync_crop (filter,
+ filter->crop_enabled,
+ &filter->crop_rect,
+ filter->preview_split_enabled,
+ filter->preview_split_alignment,
+ filter->preview_split_position,
+ TRUE);
+ gimp_drawable_filter_sync_opacity (filter);
+ gimp_drawable_filter_sync_mode (filter);
+ gimp_drawable_filter_sync_affect (filter);
+ gimp_drawable_filter_sync_format (filter);
+ gimp_drawable_filter_sync_transform (filter);
+ gimp_drawable_filter_sync_gamma_hack (filter);
+
+ gimp_drawable_add_filter (filter->drawable,
+ GIMP_FILTER (filter));
+
+ gimp_drawable_update_bounding_box (filter->drawable);
+
+ 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 (image, "profile-changed",
+ G_CALLBACK (gimp_drawable_filter_profile_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 (GIMP_IS_LAYER (filter->drawable))
+ {
+ g_signal_connect (filter->drawable, "lock-alpha-changed",
+ G_CALLBACK (gimp_drawable_filter_lock_alpha_changed),
+ filter);
+ }
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+gimp_drawable_filter_remove_filter (GimpDrawableFilter *filter)
+{
+ if (gimp_drawable_filter_is_added (filter))
+ {
+ GimpImage *image = gimp_item_get_image (GIMP_ITEM (filter->drawable));
+
+ if (GIMP_IS_LAYER (filter->drawable))
+ {
+ g_signal_handlers_disconnect_by_func (filter->drawable,
+ gimp_drawable_filter_lock_alpha_changed,
+ filter);
+ }
+
+ g_signal_handlers_disconnect_by_func (filter->drawable,
+ gimp_drawable_filter_drawable_removed,
+ filter);
+ g_signal_handlers_disconnect_by_func (filter->drawable,
+ gimp_drawable_filter_format_changed,
+ 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_profile_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);
+
+ gimp_drawable_remove_filter (filter->drawable,
+ GIMP_FILTER (filter));
+
+ gimp_drawable_update_bounding_box (filter->drawable);
+
+ gimp_viewable_preview_thaw (GIMP_VIEWABLE (filter->drawable));
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+gimp_drawable_filter_update_drawable (GimpDrawableFilter *filter,
+ const GeglRectangle *area)
+{
+ GeglRectangle bounding_box;
+ GeglRectangle update_area;
+
+ bounding_box = gimp_drawable_get_bounding_box (filter->drawable);
+
+ if (area)
+ {
+ if (! gegl_rectangle_intersect (&update_area,
+ area, &bounding_box))
+ {
+ return;
+ }
+ }
+ else
+ {
+ gimp_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)
+ {
+ gimp_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
+gimp_drawable_filter_affect_changed (GimpImage *image,
+ GimpChannelType channel,
+ GimpDrawableFilter *filter)
+{
+ gimp_drawable_filter_sync_affect (filter);
+ gimp_drawable_filter_update_drawable (filter, NULL);
+}
+
+static void
+gimp_drawable_filter_mask_changed (GimpImage *image,
+ GimpDrawableFilter *filter)
+{
+ 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_update_drawable (filter, NULL);
+}
+
+static void
+gimp_drawable_filter_profile_changed (GimpColorManaged *managed,
+ GimpDrawableFilter *filter)
+{
+ gimp_drawable_filter_sync_transform (filter);
+ gimp_drawable_filter_update_drawable (filter, NULL);
+}
+
+static void
+gimp_drawable_filter_lock_position_changed (GimpDrawable *drawable,
+ GimpDrawableFilter *filter)
+{
+ gimp_drawable_filter_sync_clip (filter, TRUE);
+ gimp_drawable_filter_update_drawable (filter, NULL);
+}
+
+static void
+gimp_drawable_filter_format_changed (GimpDrawable *drawable,
+ GimpDrawableFilter *filter)
+{
+ gimp_drawable_filter_sync_format (filter);
+ gimp_drawable_filter_update_drawable (filter, NULL);
+}
+
+static void
+gimp_drawable_filter_drawable_removed (GimpDrawable *drawable,
+ GimpDrawableFilter *filter)
+{
+ gimp_drawable_filter_remove_filter (filter);
+}
+
+static void
+gimp_drawable_filter_lock_alpha_changed (GimpLayer *layer,
+ GimpDrawableFilter *filter)
+{
+ gimp_drawable_filter_sync_affect (filter);
+ gimp_drawable_filter_update_drawable (filter, NULL);
+}