/* 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 . */ /* 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 #include #include #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); }