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