PIKApp/app/core/pikadrawablefilter.c

1227 lines
40 KiB
C
Raw Normal View History

2023-09-26 00:35:21 +02:00
/* PIKA - Photo and Image Kooker Application
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
*
* Original copyright, applying to most contents (license remains unchanged):
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/* This file contains the code necessary for generating on canvas
* previews, by connecting a specified GEGL operation to do the
* processing. It uses drawable filters that allow for non-destructive
* manipulation of drawable data, with live preview on screen.
*
* To create a tool that uses this, see app/tools/pikafiltertool.c for
* the interface and e.g. app/tools/pikacolorbalancetool.c for an
* example of using that interface.
*/
#include "config.h"
#include <cairo.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include <gegl.h>
#include "libpikabase/pikabase.h"
#include "libpikacolor/pikacolor.h"
#include "core-types.h"
#include "gegl/pika-babl.h"
#include "gegl/pikaapplicator.h"
#include "gegl/pika-gegl-utils.h"
#include "pikachannel.h"
#include "pikadrawable-filters.h"
#include "pikadrawablefilter.h"
#include "pikaimage.h"
#include "pikalayer.h"
#include "pikaprogress.h"
enum
{
FLUSH,
LAST_SIGNAL
};
struct _PikaDrawableFilter
{
PikaFilter parent_instance;
PikaDrawable *drawable;
GeglNode *operation;
gboolean has_input;
gboolean clip;
PikaFilterRegion region;
gboolean crop_enabled;
GeglRectangle crop_rect;
gboolean preview_enabled;
gboolean preview_split_enabled;
PikaAlignmentType preview_split_alignment;
gint preview_split_position;
gdouble opacity;
PikaLayerMode paint_mode;
PikaLayerColorSpace blend_space;
PikaLayerColorSpace composite_space;
PikaLayerCompositeMode composite_mode;
gboolean add_alpha;
gboolean color_managed;
gboolean gamma_hack;
gboolean override_constraints;
GeglRectangle filter_area;
gboolean filter_clip;
GeglNode *translate;
GeglNode *crop_before;
GeglNode *cast_before;
GeglNode *cast_after;
GeglNode *crop_after;
PikaApplicator *applicator;
};
static void pika_drawable_filter_dispose (GObject *object);
static void pika_drawable_filter_finalize (GObject *object);
static void pika_drawable_filter_sync_active (PikaDrawableFilter *filter);
static void pika_drawable_filter_sync_clip (PikaDrawableFilter *filter,
gboolean sync_region);
static void pika_drawable_filter_sync_region (PikaDrawableFilter *filter);
static void pika_drawable_filter_sync_crop (PikaDrawableFilter *filter,
gboolean old_crop_enabled,
const GeglRectangle *old_crop_rect,
gboolean old_preview_split_enabled,
PikaAlignmentType old_preview_split_alignment,
gint old_preview_split_position,
gboolean update);
static void pika_drawable_filter_sync_opacity (PikaDrawableFilter *filter);
static void pika_drawable_filter_sync_mode (PikaDrawableFilter *filter);
static void pika_drawable_filter_sync_affect (PikaDrawableFilter *filter);
static void pika_drawable_filter_sync_format (PikaDrawableFilter *filter);
static void pika_drawable_filter_sync_mask (PikaDrawableFilter *filter);
static void pika_drawable_filter_sync_gamma_hack (PikaDrawableFilter *filter);
static gboolean pika_drawable_filter_is_added (PikaDrawableFilter *filter);
static gboolean pika_drawable_filter_is_active (PikaDrawableFilter *filter);
static gboolean pika_drawable_filter_add_filter (PikaDrawableFilter *filter);
static gboolean pika_drawable_filter_remove_filter (PikaDrawableFilter *filter);
static void pika_drawable_filter_update_drawable (PikaDrawableFilter *filter,
const GeglRectangle *area);
static void pika_drawable_filter_affect_changed (PikaImage *image,
PikaChannelType channel,
PikaDrawableFilter *filter);
static void pika_drawable_filter_mask_changed (PikaImage *image,
PikaDrawableFilter *filter);
static void pika_drawable_filter_lock_position_changed (PikaDrawable *drawable,
PikaDrawableFilter *filter);
static void pika_drawable_filter_format_changed (PikaDrawable *drawable,
PikaDrawableFilter *filter);
static void pika_drawable_filter_drawable_removed (PikaDrawable *drawable,
PikaDrawableFilter *filter);
static void pika_drawable_filter_lock_alpha_changed (PikaLayer *layer,
PikaDrawableFilter *filter);
G_DEFINE_TYPE (PikaDrawableFilter, pika_drawable_filter, PIKA_TYPE_FILTER)
#define parent_class pika_drawable_filter_parent_class
static guint drawable_filter_signals[LAST_SIGNAL] = { 0, };
static void
pika_drawable_filter_class_init (PikaDrawableFilterClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
drawable_filter_signals[FLUSH] =
g_signal_new ("flush",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (PikaDrawableFilterClass, flush),
NULL, NULL, NULL,
G_TYPE_NONE, 0);
object_class->dispose = pika_drawable_filter_dispose;
object_class->finalize = pika_drawable_filter_finalize;
}
static void
pika_drawable_filter_init (PikaDrawableFilter *drawable_filter)
{
drawable_filter->clip = TRUE;
drawable_filter->region = PIKA_FILTER_REGION_SELECTION;
drawable_filter->preview_enabled = TRUE;
drawable_filter->preview_split_enabled = FALSE;
drawable_filter->preview_split_alignment = PIKA_ALIGN_LEFT;
drawable_filter->preview_split_position = 0;
drawable_filter->opacity = PIKA_OPACITY_OPAQUE;
drawable_filter->paint_mode = PIKA_LAYER_MODE_REPLACE;
drawable_filter->blend_space = PIKA_LAYER_COLOR_SPACE_AUTO;
drawable_filter->composite_space = PIKA_LAYER_COLOR_SPACE_AUTO;
drawable_filter->composite_mode = PIKA_LAYER_COMPOSITE_AUTO;
}
static void
pika_drawable_filter_dispose (GObject *object)
{
PikaDrawableFilter *drawable_filter = PIKA_DRAWABLE_FILTER (object);
if (drawable_filter->drawable)
pika_drawable_filter_remove_filter (drawable_filter);
G_OBJECT_CLASS (parent_class)->dispose (object);
}
static void
pika_drawable_filter_finalize (GObject *object)
{
PikaDrawableFilter *drawable_filter = PIKA_DRAWABLE_FILTER (object);
g_clear_object (&drawable_filter->operation);
g_clear_object (&drawable_filter->applicator);
g_clear_object (&drawable_filter->drawable);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
PikaDrawableFilter *
pika_drawable_filter_new (PikaDrawable *drawable,
const gchar *undo_desc,
GeglNode *operation,
const gchar *icon_name)
{
PikaDrawableFilter *filter;
GeglNode *node;
g_return_val_if_fail (PIKA_IS_DRAWABLE (drawable), NULL);
g_return_val_if_fail (pika_item_is_attached (PIKA_ITEM (drawable)), NULL);
g_return_val_if_fail (GEGL_IS_NODE (operation), NULL);
g_return_val_if_fail (gegl_node_has_pad (operation, "output"), NULL);
filter = g_object_new (PIKA_TYPE_DRAWABLE_FILTER,
"name", undo_desc,
"icon-name", icon_name,
NULL);
filter->drawable = g_object_ref (drawable);
filter->operation = g_object_ref (operation);
node = pika_filter_get_node (PIKA_FILTER (filter));
gegl_node_add_child (node, operation);
pika_gegl_node_set_underlying_operation (node, operation);
filter->applicator = pika_applicator_new (node);
pika_filter_set_applicator (PIKA_FILTER (filter), filter->applicator);
pika_applicator_set_cache (filter->applicator, TRUE);
filter->has_input = gegl_node_has_pad (filter->operation, "input");
if (filter->has_input)
{
GeglNode *input;
input = gegl_node_get_input_proxy (node, "input");
filter->translate = gegl_node_new_child (node,
"operation", "gegl:translate",
NULL);
filter->crop_before = gegl_node_new_child (node,
"operation", "gegl:crop",
NULL);
filter->cast_before = gegl_node_new_child (node,
"operation", "gegl:nop",
NULL);
gegl_node_link_many (input,
filter->translate,
filter->crop_before,
filter->cast_before,
filter->operation,
NULL);
}
filter->cast_after = gegl_node_new_child (node,
"operation", "gegl:nop",
NULL);
filter->crop_after = gegl_node_new_child (node,
"operation", "gegl:crop",
NULL);
gegl_node_link_many (filter->operation,
filter->cast_after,
filter->crop_after,
NULL);
gegl_node_connect (filter->crop_after, "output", node, "aux");
return filter;
}
PikaDrawable *
pika_drawable_filter_get_drawable (PikaDrawableFilter *filter)
{
g_return_val_if_fail (PIKA_IS_DRAWABLE_FILTER (filter), NULL);
return filter->drawable;
}
GeglNode *
pika_drawable_filter_get_operation (PikaDrawableFilter *filter)
{
g_return_val_if_fail (PIKA_IS_DRAWABLE_FILTER (filter), NULL);
return filter->operation;
}
void
pika_drawable_filter_set_clip (PikaDrawableFilter *filter,
gboolean clip)
{
g_return_if_fail (PIKA_IS_DRAWABLE_FILTER (filter));
if (clip != filter->clip)
{
filter->clip = clip;
pika_drawable_filter_sync_clip (filter, TRUE);
}
}
void
pika_drawable_filter_set_region (PikaDrawableFilter *filter,
PikaFilterRegion region)
{
g_return_if_fail (PIKA_IS_DRAWABLE_FILTER (filter));
if (region != filter->region)
{
filter->region = region;
pika_drawable_filter_sync_region (filter);
if (pika_drawable_filter_is_active (filter))
pika_drawable_filter_update_drawable (filter, NULL);
}
}
void
pika_drawable_filter_set_crop (PikaDrawableFilter *filter,
const GeglRectangle *rect,
gboolean update)
{
g_return_if_fail (PIKA_IS_DRAWABLE_FILTER (filter));
if ((rect != NULL) != filter->crop_enabled ||
(rect && ! gegl_rectangle_equal (rect, &filter->crop_rect)))
{
gboolean old_enabled = filter->crop_enabled;
GeglRectangle old_rect = filter->crop_rect;
if (rect)
{
filter->crop_enabled = TRUE;
filter->crop_rect = *rect;
}
else
{
filter->crop_enabled = FALSE;
}
pika_drawable_filter_sync_crop (filter,
old_enabled,
&old_rect,
filter->preview_split_enabled,
filter->preview_split_alignment,
filter->preview_split_position,
update);
}
}
void
pika_drawable_filter_set_preview (PikaDrawableFilter *filter,
gboolean enabled)
{
g_return_if_fail (PIKA_IS_DRAWABLE_FILTER (filter));
if (enabled != filter->preview_enabled)
{
filter->preview_enabled = enabled;
pika_drawable_filter_sync_active (filter);
if (pika_drawable_filter_is_added (filter))
{
pika_drawable_update_bounding_box (filter->drawable);
pika_drawable_filter_update_drawable (filter, NULL);
}
}
}
void
pika_drawable_filter_set_preview_split (PikaDrawableFilter *filter,
gboolean enabled,
PikaAlignmentType alignment,
gint position)
{
PikaItem *item;
g_return_if_fail (PIKA_IS_DRAWABLE_FILTER (filter));
g_return_if_fail (alignment == PIKA_ALIGN_LEFT ||
alignment == PIKA_ALIGN_RIGHT ||
alignment == PIKA_ALIGN_TOP ||
alignment == PIKA_ALIGN_BOTTOM);
item = PIKA_ITEM (filter->drawable);
switch (alignment)
{
case PIKA_ALIGN_LEFT:
case PIKA_ALIGN_RIGHT:
position = CLAMP (position, 0, pika_item_get_width (item));
break;
case PIKA_ALIGN_TOP:
case PIKA_ALIGN_BOTTOM:
position = CLAMP (position, 0, pika_item_get_height (item));
break;
default:
g_return_if_reached ();
}
if (enabled != filter->preview_split_enabled ||
alignment != filter->preview_split_alignment ||
position != filter->preview_split_position)
{
gboolean old_enabled = filter->preview_split_enabled;
PikaAlignmentType old_alignment = filter->preview_split_alignment;
gint old_position = filter->preview_split_position;
filter->preview_split_enabled = enabled;
filter->preview_split_alignment = alignment;
filter->preview_split_position = position;
pika_drawable_filter_sync_crop (filter,
filter->crop_enabled,
&filter->crop_rect,
old_enabled,
old_alignment,
old_position,
TRUE);
}
}
void
pika_drawable_filter_set_opacity (PikaDrawableFilter *filter,
gdouble opacity)
{
g_return_if_fail (PIKA_IS_DRAWABLE_FILTER (filter));
if (opacity != filter->opacity)
{
filter->opacity = opacity;
pika_drawable_filter_sync_opacity (filter);
if (pika_drawable_filter_is_active (filter))
pika_drawable_filter_update_drawable (filter, NULL);
}
}
void
pika_drawable_filter_set_mode (PikaDrawableFilter *filter,
PikaLayerMode paint_mode,
PikaLayerColorSpace blend_space,
PikaLayerColorSpace composite_space,
PikaLayerCompositeMode composite_mode)
{
g_return_if_fail (PIKA_IS_DRAWABLE_FILTER (filter));
if (paint_mode != filter->paint_mode ||
blend_space != filter->blend_space ||
composite_space != filter->composite_space ||
composite_mode != filter->composite_mode)
{
filter->paint_mode = paint_mode;
filter->blend_space = blend_space;
filter->composite_space = composite_space;
filter->composite_mode = composite_mode;
pika_drawable_filter_sync_mode (filter);
if (pika_drawable_filter_is_active (filter))
pika_drawable_filter_update_drawable (filter, NULL);
}
}
void
pika_drawable_filter_set_add_alpha (PikaDrawableFilter *filter,
gboolean add_alpha)
{
g_return_if_fail (PIKA_IS_DRAWABLE_FILTER (filter));
if (add_alpha != filter->add_alpha)
{
filter->add_alpha = add_alpha;
pika_drawable_filter_sync_format (filter);
if (pika_drawable_filter_is_active (filter))
pika_drawable_filter_update_drawable (filter, NULL);
}
}
void
pika_drawable_filter_set_gamma_hack (PikaDrawableFilter *filter,
gboolean gamma_hack)
{
g_return_if_fail (PIKA_IS_DRAWABLE_FILTER (filter));
if (gamma_hack != filter->gamma_hack)
{
filter->gamma_hack = gamma_hack;
pika_drawable_filter_sync_gamma_hack (filter);
if (pika_drawable_filter_is_active (filter))
pika_drawable_filter_update_drawable (filter, NULL);
}
}
void
pika_drawable_filter_set_override_constraints (PikaDrawableFilter *filter,
gboolean override_constraints)
{
g_return_if_fail (PIKA_IS_DRAWABLE_FILTER (filter));
if (override_constraints != filter->override_constraints)
{
filter->override_constraints = override_constraints;
pika_drawable_filter_sync_affect (filter);
pika_drawable_filter_sync_format (filter);
pika_drawable_filter_sync_clip (filter, TRUE);
if (pika_drawable_filter_is_active (filter))
pika_drawable_filter_update_drawable (filter, NULL);
}
}
const Babl *
pika_drawable_filter_get_format (PikaDrawableFilter *filter)
{
const Babl *format;
g_return_val_if_fail (PIKA_IS_DRAWABLE_FILTER (filter), NULL);
format = pika_applicator_get_output_format (filter->applicator);
if (! format)
format = pika_drawable_get_format (filter->drawable);
return format;
}
void
pika_drawable_filter_apply (PikaDrawableFilter *filter,
const GeglRectangle *area)
{
g_return_if_fail (PIKA_IS_DRAWABLE_FILTER (filter));
g_return_if_fail (pika_item_is_attached (PIKA_ITEM (filter->drawable)));
pika_drawable_filter_add_filter (filter);
pika_drawable_filter_sync_clip (filter, TRUE);
if (pika_drawable_filter_is_active (filter))
{
pika_drawable_update_bounding_box (filter->drawable);
pika_drawable_filter_update_drawable (filter, area);
}
}
gboolean
pika_drawable_filter_commit (PikaDrawableFilter *filter,
PikaProgress *progress,
gboolean cancellable)
{
gboolean success = TRUE;
g_return_val_if_fail (PIKA_IS_DRAWABLE_FILTER (filter), FALSE);
g_return_val_if_fail (pika_item_is_attached (PIKA_ITEM (filter->drawable)),
FALSE);
g_return_val_if_fail (progress == NULL || PIKA_IS_PROGRESS (progress), FALSE);
if (pika_drawable_filter_is_added (filter))
{
const Babl *format;
format = pika_drawable_filter_get_format (filter);
pika_drawable_filter_set_preview_split (filter, FALSE,
filter->preview_split_alignment,
filter->preview_split_position);
pika_drawable_filter_set_preview (filter, TRUE);
success = pika_drawable_merge_filter (filter->drawable,
PIKA_FILTER (filter),
progress,
pika_object_get_name (filter),
format,
filter->filter_clip,
cancellable,
FALSE);
pika_drawable_filter_remove_filter (filter);
if (! success)
pika_drawable_filter_update_drawable (filter, NULL);
g_signal_emit (filter, drawable_filter_signals[FLUSH], 0);
}
return success;
}
void
pika_drawable_filter_abort (PikaDrawableFilter *filter)
{
g_return_if_fail (PIKA_IS_DRAWABLE_FILTER (filter));
if (pika_drawable_filter_remove_filter (filter))
{
pika_drawable_filter_update_drawable (filter, NULL);
}
}
/* private functions */
static void
pika_drawable_filter_sync_active (PikaDrawableFilter *filter)
{
pika_applicator_set_active (filter->applicator, filter->preview_enabled);
}
static void
pika_drawable_filter_sync_clip (PikaDrawableFilter *filter,
gboolean sync_region)
{
gboolean clip;
if (filter->override_constraints)
clip = filter->clip;
else
clip = pika_item_get_clip (PIKA_ITEM (filter->drawable), filter->clip);
if (! clip)
{
PikaImage *image = pika_item_get_image (PIKA_ITEM (filter->drawable));
PikaChannel *mask = pika_image_get_mask (image);
if (! pika_channel_is_empty (mask))
clip = TRUE;
}
if (! clip)
{
GeglRectangle bounding_box;
bounding_box = gegl_node_get_bounding_box (filter->operation);
if (gegl_rectangle_is_infinite_plane (&bounding_box))
clip = TRUE;
}
if (clip != filter->filter_clip)
{
filter->filter_clip = clip;
if (sync_region)
pika_drawable_filter_sync_region (filter);
}
}
static void
pika_drawable_filter_sync_region (PikaDrawableFilter *filter)
{
if (filter->region == PIKA_FILTER_REGION_SELECTION)
{
if (filter->has_input)
{
gegl_node_set (filter->translate,
"x", (gdouble) -filter->filter_area.x,
"y", (gdouble) -filter->filter_area.y,
NULL);
gegl_node_set (filter->crop_before,
"width", (gdouble) filter->filter_area.width,
"height", (gdouble) filter->filter_area.height,
NULL);
}
if (filter->filter_clip)
{
gegl_node_set (filter->crop_after,
"operation", "gegl:crop",
"x", 0.0,
"y", 0.0,
"width", (gdouble) filter->filter_area.width,
"height", (gdouble) filter->filter_area.height,
NULL);
}
else
{
gegl_node_set (filter->crop_after,
"operation", "gegl:nop",
NULL);
}
pika_applicator_set_apply_offset (filter->applicator,
filter->filter_area.x,
filter->filter_area.y);
}
else
{
PikaItem *item = PIKA_ITEM (filter->drawable);
gdouble width = pika_item_get_width (item);
gdouble height = pika_item_get_height (item);
if (filter->has_input)
{
gegl_node_set (filter->translate,
"x", (gdouble) 0.0,
"y", (gdouble) 0.0,
NULL);
gegl_node_set (filter->crop_before,
"width", width,
"height", height,
NULL);
}
if (filter->filter_clip)
{
gegl_node_set (filter->crop_after,
"operation", "gegl:crop",
"x", (gdouble) filter->filter_area.x,
"y", (gdouble) filter->filter_area.y,
"width", (gdouble) filter->filter_area.width,
"height", (gdouble) filter->filter_area.height,
NULL);
}
else
{
gegl_node_set (filter->crop_after,
"operation", "gegl:nop",
NULL);
}
pika_applicator_set_apply_offset (filter->applicator, 0, 0);
}
if (pika_drawable_filter_is_active (filter))
{
if (pika_drawable_update_bounding_box (filter->drawable))
g_signal_emit (filter, drawable_filter_signals[FLUSH], 0);
}
}
static gboolean
pika_drawable_filter_get_crop_rect (PikaDrawableFilter *filter,
gboolean crop_enabled,
const GeglRectangle *crop_rect,
gboolean preview_split_enabled,
PikaAlignmentType preview_split_alignment,
gint preview_split_position,
GeglRectangle *rect)
{
GeglRectangle bounds;
gint x1, x2;
gint y1, y2;
bounds = gegl_rectangle_infinite_plane ();
x1 = bounds.x;
x2 = bounds.x + bounds.width;
y1 = bounds.y;
y2 = bounds.y + bounds.height;
if (preview_split_enabled)
{
switch (preview_split_alignment)
{
case PIKA_ALIGN_LEFT:
x2 = preview_split_position;
break;
case PIKA_ALIGN_RIGHT:
x1 = preview_split_position;
break;
case PIKA_ALIGN_TOP:
y2 = preview_split_position;
break;
case PIKA_ALIGN_BOTTOM:
y1 = preview_split_position;
break;
default:
g_return_val_if_reached (FALSE);
}
}
gegl_rectangle_set (rect, x1, y1, x2 - x1, y2 - y1);
if (crop_enabled)
gegl_rectangle_intersect (rect, rect, crop_rect);
return ! gegl_rectangle_equal (rect, &bounds);
}
static void
pika_drawable_filter_sync_crop (PikaDrawableFilter *filter,
gboolean old_crop_enabled,
const GeglRectangle *old_crop_rect,
gboolean old_preview_split_enabled,
PikaAlignmentType old_preview_split_alignment,
gint old_preview_split_position,
gboolean update)
{
GeglRectangle old_rect;
GeglRectangle new_rect;
gboolean enabled;
pika_drawable_filter_get_crop_rect (filter,
old_crop_enabled,
old_crop_rect,
old_preview_split_enabled,
old_preview_split_alignment,
old_preview_split_position,
&old_rect);
enabled = pika_drawable_filter_get_crop_rect (filter,
filter->crop_enabled,
&filter->crop_rect,
filter->preview_split_enabled,
filter->preview_split_alignment,
filter->preview_split_position,
&new_rect);
pika_applicator_set_crop (filter->applicator, enabled ? &new_rect : NULL);
if (update &&
pika_drawable_filter_is_active (filter) &&
! gegl_rectangle_equal (&old_rect, &new_rect))
{
GeglRectangle diff_rects[4];
gint n_diff_rects;
gint i;
pika_drawable_update_bounding_box (filter->drawable);
n_diff_rects = gegl_rectangle_xor (diff_rects, &old_rect, &new_rect);
for (i = 0; i < n_diff_rects; i++)
pika_drawable_filter_update_drawable (filter, &diff_rects[i]);
}
}
static void
pika_drawable_filter_sync_opacity (PikaDrawableFilter *filter)
{
pika_applicator_set_opacity (filter->applicator,
filter->opacity);
}
static void
pika_drawable_filter_sync_mode (PikaDrawableFilter *filter)
{
PikaLayerMode paint_mode = filter->paint_mode;
if (! filter->has_input && paint_mode == PIKA_LAYER_MODE_REPLACE)
{
/* if the filter's op has no input, use NORMAL instead of REPLACE, so
* that we composite the op's output on top of the input, instead of
* completely replacing it.
*/
paint_mode = PIKA_LAYER_MODE_NORMAL;
}
pika_applicator_set_mode (filter->applicator,
paint_mode,
filter->blend_space,
filter->composite_space,
filter->composite_mode);
}
static void
pika_drawable_filter_sync_affect (PikaDrawableFilter *filter)
{
pika_applicator_set_affect (
filter->applicator,
filter->override_constraints ?
PIKA_COMPONENT_MASK_RED |
PIKA_COMPONENT_MASK_GREEN |
PIKA_COMPONENT_MASK_BLUE |
PIKA_COMPONENT_MASK_ALPHA :
pika_drawable_get_active_mask (filter->drawable));
}
static void
pika_drawable_filter_sync_format (PikaDrawableFilter *filter)
{
const Babl *format;
if (filter->add_alpha &&
(pika_drawable_supports_alpha (filter->drawable) ||
filter->override_constraints))
{
format = pika_drawable_get_format_with_alpha (filter->drawable);
}
else
{
format = pika_drawable_get_format (filter->drawable);
}
pika_applicator_set_output_format (filter->applicator, format);
}
static void
pika_drawable_filter_sync_mask (PikaDrawableFilter *filter)
{
PikaImage *image = pika_item_get_image (PIKA_ITEM (filter->drawable));
PikaChannel *mask = pika_image_get_mask (image);
if (pika_channel_is_empty (mask))
{
pika_applicator_set_mask_buffer (filter->applicator, NULL);
}
else
{
GeglBuffer *mask_buffer;
gint offset_x, offset_y;
mask_buffer = pika_drawable_get_buffer (PIKA_DRAWABLE (mask));
pika_item_get_offset (PIKA_ITEM (filter->drawable),
&offset_x, &offset_y);
pika_applicator_set_mask_buffer (filter->applicator, mask_buffer);
pika_applicator_set_mask_offset (filter->applicator,
-offset_x, -offset_y);
}
pika_item_mask_intersect (PIKA_ITEM (filter->drawable),
&filter->filter_area.x,
&filter->filter_area.y,
&filter->filter_area.width,
&filter->filter_area.height);
}
static void
pika_drawable_filter_sync_gamma_hack (PikaDrawableFilter *filter)
{
if (filter->gamma_hack)
{
const Babl *drawable_format;
const Babl *cast_format;
PikaTRCType trc = PIKA_TRC_LINEAR;
switch (pika_drawable_get_trc (filter->drawable))
{
case PIKA_TRC_LINEAR: trc = PIKA_TRC_NON_LINEAR; break;
case PIKA_TRC_NON_LINEAR: trc = PIKA_TRC_LINEAR; break;
case PIKA_TRC_PERCEPTUAL: trc = PIKA_TRC_LINEAR; break;
}
drawable_format =
pika_drawable_get_format_with_alpha (filter->drawable);
cast_format =
pika_babl_format (pika_babl_format_get_base_type (drawable_format),
pika_babl_precision (pika_babl_format_get_component_type (drawable_format),
trc),
TRUE,
babl_format_get_space (drawable_format));
if (filter->has_input)
{
gegl_node_set (filter->cast_before,
"operation", "gegl:cast-format",
"input-format", drawable_format,
"output-format", cast_format,
NULL);
}
gegl_node_set (filter->cast_after,
"operation", "gegl:cast-format",
"input-format", cast_format,
"output-format", drawable_format,
NULL);
}
else
{
if (filter->has_input)
{
gegl_node_set (filter->cast_before,
"operation", "gegl:nop",
NULL);
}
gegl_node_set (filter->cast_after,
"operation", "gegl:nop",
NULL);
}
}
static gboolean
pika_drawable_filter_is_added (PikaDrawableFilter *filter)
{
return pika_drawable_has_filter (filter->drawable,
PIKA_FILTER (filter));
}
static gboolean
pika_drawable_filter_is_active (PikaDrawableFilter *filter)
{
return pika_drawable_filter_is_added (filter) &&
filter->preview_enabled;
}
static gboolean
pika_drawable_filter_add_filter (PikaDrawableFilter *filter)
{
if (! pika_drawable_filter_is_added (filter))
{
PikaImage *image = pika_item_get_image (PIKA_ITEM (filter->drawable));
pika_viewable_preview_freeze (PIKA_VIEWABLE (filter->drawable));
pika_drawable_filter_sync_active (filter);
pika_drawable_filter_sync_mask (filter);
pika_drawable_filter_sync_clip (filter, FALSE);
pika_drawable_filter_sync_region (filter);
pika_drawable_filter_sync_crop (filter,
filter->crop_enabled,
&filter->crop_rect,
filter->preview_split_enabled,
filter->preview_split_alignment,
filter->preview_split_position,
TRUE);
pika_drawable_filter_sync_opacity (filter);
pika_drawable_filter_sync_mode (filter);
pika_drawable_filter_sync_affect (filter);
pika_drawable_filter_sync_format (filter);
pika_drawable_filter_sync_gamma_hack (filter);
pika_drawable_add_filter (filter->drawable,
PIKA_FILTER (filter));
pika_drawable_update_bounding_box (filter->drawable);
g_signal_connect (image, "component-active-changed",
G_CALLBACK (pika_drawable_filter_affect_changed),
filter);
g_signal_connect (image, "mask-changed",
G_CALLBACK (pika_drawable_filter_mask_changed),
filter);
g_signal_connect (filter->drawable, "lock-position-changed",
G_CALLBACK (pika_drawable_filter_lock_position_changed),
filter);
g_signal_connect (filter->drawable, "format-changed",
G_CALLBACK (pika_drawable_filter_format_changed),
filter);
g_signal_connect (filter->drawable, "removed",
G_CALLBACK (pika_drawable_filter_drawable_removed),
filter);
if (PIKA_IS_LAYER (filter->drawable))
{
g_signal_connect (filter->drawable, "lock-alpha-changed",
G_CALLBACK (pika_drawable_filter_lock_alpha_changed),
filter);
}
return TRUE;
}
return FALSE;
}
static gboolean
pika_drawable_filter_remove_filter (PikaDrawableFilter *filter)
{
if (pika_drawable_filter_is_added (filter))
{
PikaImage *image = pika_item_get_image (PIKA_ITEM (filter->drawable));
if (PIKA_IS_LAYER (filter->drawable))
{
g_signal_handlers_disconnect_by_func (filter->drawable,
pika_drawable_filter_lock_alpha_changed,
filter);
}
g_signal_handlers_disconnect_by_func (filter->drawable,
pika_drawable_filter_drawable_removed,
filter);
g_signal_handlers_disconnect_by_func (filter->drawable,
pika_drawable_filter_format_changed,
filter);
g_signal_handlers_disconnect_by_func (filter->drawable,
pika_drawable_filter_lock_position_changed,
filter);
g_signal_handlers_disconnect_by_func (image,
pika_drawable_filter_mask_changed,
filter);
g_signal_handlers_disconnect_by_func (image,
pika_drawable_filter_affect_changed,
filter);
pika_drawable_remove_filter (filter->drawable,
PIKA_FILTER (filter));
pika_drawable_update_bounding_box (filter->drawable);
pika_viewable_preview_thaw (PIKA_VIEWABLE (filter->drawable));
return TRUE;
}
return FALSE;
}
static void
pika_drawable_filter_update_drawable (PikaDrawableFilter *filter,
const GeglRectangle *area)
{
GeglRectangle bounding_box;
GeglRectangle update_area;
bounding_box = pika_drawable_get_bounding_box (filter->drawable);
if (area)
{
if (! gegl_rectangle_intersect (&update_area,
area, &bounding_box))
{
return;
}
}
else
{
pika_drawable_filter_get_crop_rect (filter,
filter->crop_enabled,
&filter->crop_rect,
filter->preview_split_enabled,
filter->preview_split_alignment,
filter->preview_split_position,
&update_area);
if (! gegl_rectangle_intersect (&update_area,
&update_area, &bounding_box))
{
return;
}
}
if (update_area.width > 0 &&
update_area.height > 0)
{
pika_drawable_update (filter->drawable,
update_area.x,
update_area.y,
update_area.width,
update_area.height);
g_signal_emit (filter, drawable_filter_signals[FLUSH], 0);
}
}
static void
pika_drawable_filter_affect_changed (PikaImage *image,
PikaChannelType channel,
PikaDrawableFilter *filter)
{
pika_drawable_filter_sync_affect (filter);
pika_drawable_filter_update_drawable (filter, NULL);
}
static void
pika_drawable_filter_mask_changed (PikaImage *image,
PikaDrawableFilter *filter)
{
pika_drawable_filter_update_drawable (filter, NULL);
pika_drawable_filter_sync_mask (filter);
pika_drawable_filter_sync_clip (filter, FALSE);
pika_drawable_filter_sync_region (filter);
pika_drawable_filter_update_drawable (filter, NULL);
}
static void
pika_drawable_filter_lock_position_changed (PikaDrawable *drawable,
PikaDrawableFilter *filter)
{
pika_drawable_filter_sync_clip (filter, TRUE);
pika_drawable_filter_update_drawable (filter, NULL);
}
static void
pika_drawable_filter_format_changed (PikaDrawable *drawable,
PikaDrawableFilter *filter)
{
pika_drawable_filter_sync_format (filter);
pika_drawable_filter_update_drawable (filter, NULL);
}
static void
pika_drawable_filter_drawable_removed (PikaDrawable *drawable,
PikaDrawableFilter *filter)
{
pika_drawable_filter_remove_filter (filter);
}
static void
pika_drawable_filter_lock_alpha_changed (PikaLayer *layer,
PikaDrawableFilter *filter)
{
pika_drawable_filter_sync_affect (filter);
pika_drawable_filter_update_drawable (filter, NULL);
}