Files
gimp/app/core/gimpchannel.c
Michael Natterer a65042aebf Issue #3512 - feather selection doesn't work at edges of images
Add a "gboolean edge_lock" parameter to GimpChannel::feather() and a
"Selected areas continue outside the image" toggle to the "Feather
Selection" dialog, just like they exist for shrink selection and
border selection. At the end, convert the boolean to the right abyss
policy for gegl:gaussian-blur.

(cherry picked from commit aace6b179b)
2019-06-16 16:55:03 +02:00

2014 lines
69 KiB
C

/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <string.h>
#include <cairo.h>
#include <gegl.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include "libgimpbase/gimpbase.h"
#include "libgimpmath/gimpmath.h"
#include "libgimpcolor/gimpcolor.h"
#include "core-types.h"
#include "paint/gimppaintcore-stroke.h"
#include "paint/gimppaintoptions.h"
#include "gegl/gimp-gegl-apply-operation.h"
#include "gegl/gimp-gegl-loops.h"
#include "gegl/gimp-gegl-mask.h"
#include "gegl/gimp-gegl-nodes.h"
#include "gegl/gimp-gegl-utils.h"
#include "gimp.h"
#include "gimp-utils.h"
#include "gimpboundary.h"
#include "gimpcontainer.h"
#include "gimperror.h"
#include "gimpimage.h"
#include "gimpimage-quick-mask.h"
#include "gimpimage-undo.h"
#include "gimpimage-undo-push.h"
#include "gimpchannel.h"
#include "gimpchannel-select.h"
#include "gimpcontext.h"
#include "gimpdrawable-fill.h"
#include "gimpdrawable-stroke.h"
#include "gimpmarshal.h"
#include "gimppaintinfo.h"
#include "gimppickable.h"
#include "gimpstrokeoptions.h"
#include "gimp-intl.h"
#define RGBA_EPSILON 1e-6
enum
{
COLOR_CHANGED,
LAST_SIGNAL
};
static void gimp_channel_pickable_iface_init (GimpPickableInterface *iface);
static void gimp_channel_finalize (GObject *object);
static gint64 gimp_channel_get_memsize (GimpObject *object,
gint64 *gui_size);
static gchar * gimp_channel_get_description (GimpViewable *viewable,
gchar **tooltip);
static GeglNode * gimp_channel_get_node (GimpFilter *filter);
static gboolean gimp_channel_is_attached (GimpItem *item);
static GimpItemTree * gimp_channel_get_tree (GimpItem *item);
static gboolean gimp_channel_bounds (GimpItem *item,
gdouble *x,
gdouble *y,
gdouble *width,
gdouble *height);
static GimpItem * gimp_channel_duplicate (GimpItem *item,
GType new_type);
static void gimp_channel_convert (GimpItem *item,
GimpImage *dest_image,
GType old_type);
static void gimp_channel_translate (GimpItem *item,
gdouble off_x,
gdouble off_y,
gboolean push_undo);
static void gimp_channel_scale (GimpItem *item,
gint new_width,
gint new_height,
gint new_offset_x,
gint new_offset_y,
GimpInterpolationType interp_type,
GimpProgress *progress);
static void gimp_channel_resize (GimpItem *item,
GimpContext *context,
GimpFillType fill_type,
gint new_width,
gint new_height,
gint offset_x,
gint offset_y);
static void gimp_channel_flip (GimpItem *item,
GimpContext *context,
GimpOrientationType flip_type,
gdouble axis,
gboolean flip_result);
static void gimp_channel_rotate (GimpItem *item,
GimpContext *context,
GimpRotationType flip_type,
gdouble center_x,
gdouble center_y,
gboolean flip_result);
static void gimp_channel_transform (GimpItem *item,
GimpContext *context,
const GimpMatrix3 *matrix,
GimpTransformDirection direction,
GimpInterpolationType interpolation_type,
GimpTransformResize clip_result,
GimpProgress *progress);
static gboolean gimp_channel_fill (GimpItem *item,
GimpDrawable *drawable,
GimpFillOptions *fill_options,
gboolean push_undo,
GimpProgress *progress,
GError **error);
static gboolean gimp_channel_stroke (GimpItem *item,
GimpDrawable *drawable,
GimpStrokeOptions *stroke_options,
gboolean push_undo,
GimpProgress *progress,
GError **error);
static void gimp_channel_to_selection (GimpItem *item,
GimpChannelOps op,
gboolean antialias,
gboolean feather,
gdouble feather_radius_x,
gdouble feather_radius_y);
static void gimp_channel_convert_type (GimpDrawable *drawable,
GimpImage *dest_image,
const Babl *new_format,
GimpColorProfile *dest_profile,
GeglDitherMethod layer_dither_type,
GeglDitherMethod mask_dither_type,
gboolean push_undo,
GimpProgress *progress);
static void gimp_channel_invalidate_boundary (GimpDrawable *drawable);
static void gimp_channel_get_active_components (GimpDrawable *drawable,
gboolean *active);
static void gimp_channel_set_buffer (GimpDrawable *drawable,
gboolean push_undo,
const gchar *undo_desc,
GeglBuffer *buffer,
gint offset_x,
gint offset_y);
static gdouble gimp_channel_get_opacity_at (GimpPickable *pickable,
gint x,
gint y);
static gboolean gimp_channel_real_boundary (GimpChannel *channel,
const GimpBoundSeg **segs_in,
const GimpBoundSeg **segs_out,
gint *num_segs_in,
gint *num_segs_out,
gint x1,
gint y1,
gint x2,
gint y2);
static gboolean gimp_channel_real_is_empty (GimpChannel *channel);
static void gimp_channel_real_feather (GimpChannel *channel,
gdouble radius_x,
gdouble radius_y,
gboolean edge_lock,
gboolean push_undo);
static void gimp_channel_real_sharpen (GimpChannel *channel,
gboolean push_undo);
static void gimp_channel_real_clear (GimpChannel *channel,
const gchar *undo_desc,
gboolean push_undo);
static void gimp_channel_real_all (GimpChannel *channel,
gboolean push_undo);
static void gimp_channel_real_invert (GimpChannel *channel,
gboolean push_undo);
static void gimp_channel_real_border (GimpChannel *channel,
gint radius_x,
gint radius_y,
GimpChannelBorderStyle style,
gboolean edge_lock,
gboolean push_undo);
static void gimp_channel_real_grow (GimpChannel *channel,
gint radius_x,
gint radius_y,
gboolean push_undo);
static void gimp_channel_real_shrink (GimpChannel *channel,
gint radius_x,
gint radius_y,
gboolean edge_lock,
gboolean push_undo);
static void gimp_channel_real_flood (GimpChannel *channel,
gboolean push_undo);
static void gimp_channel_buffer_changed (GeglBuffer *buffer,
const GeglRectangle *rect,
GimpChannel *channel);
G_DEFINE_TYPE_WITH_CODE (GimpChannel, gimp_channel, GIMP_TYPE_DRAWABLE,
G_IMPLEMENT_INTERFACE (GIMP_TYPE_PICKABLE,
gimp_channel_pickable_iface_init))
#define parent_class gimp_channel_parent_class
static guint channel_signals[LAST_SIGNAL] = { 0 };
static void
gimp_channel_class_init (GimpChannelClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
GimpViewableClass *viewable_class = GIMP_VIEWABLE_CLASS (klass);
GimpFilterClass *filter_class = GIMP_FILTER_CLASS (klass);
GimpItemClass *item_class = GIMP_ITEM_CLASS (klass);
GimpDrawableClass *drawable_class = GIMP_DRAWABLE_CLASS (klass);
channel_signals[COLOR_CHANGED] =
g_signal_new ("color-changed",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (GimpChannelClass, color_changed),
NULL, NULL,
gimp_marshal_VOID__VOID,
G_TYPE_NONE, 0);
object_class->finalize = gimp_channel_finalize;
gimp_object_class->get_memsize = gimp_channel_get_memsize;
viewable_class->get_description = gimp_channel_get_description;
viewable_class->default_icon_name = "gimp-channel";
filter_class->get_node = gimp_channel_get_node;
item_class->is_attached = gimp_channel_is_attached;
item_class->get_tree = gimp_channel_get_tree;
item_class->bounds = gimp_channel_bounds;
item_class->duplicate = gimp_channel_duplicate;
item_class->convert = gimp_channel_convert;
item_class->translate = gimp_channel_translate;
item_class->scale = gimp_channel_scale;
item_class->resize = gimp_channel_resize;
item_class->flip = gimp_channel_flip;
item_class->rotate = gimp_channel_rotate;
item_class->transform = gimp_channel_transform;
item_class->fill = gimp_channel_fill;
item_class->stroke = gimp_channel_stroke;
item_class->to_selection = gimp_channel_to_selection;
item_class->default_name = _("Channel");
item_class->rename_desc = C_("undo-type", "Rename Channel");
item_class->translate_desc = C_("undo-type", "Move Channel");
item_class->scale_desc = C_("undo-type", "Scale Channel");
item_class->resize_desc = C_("undo-type", "Resize Channel");
item_class->flip_desc = C_("undo-type", "Flip Channel");
item_class->rotate_desc = C_("undo-type", "Rotate Channel");
item_class->transform_desc = C_("undo-type", "Transform Channel");
item_class->fill_desc = C_("undo-type", "Fill Channel");
item_class->stroke_desc = C_("undo-type", "Stroke Channel");
item_class->to_selection_desc = C_("undo-type", "Channel to Selection");
item_class->reorder_desc = C_("undo-type", "Reorder Channel");
item_class->raise_desc = C_("undo-type", "Raise Channel");
item_class->raise_to_top_desc = C_("undo-type", "Raise Channel to Top");
item_class->lower_desc = C_("undo-type", "Lower Channel");
item_class->lower_to_bottom_desc = C_("undo-type", "Lower Channel to Bottom");
item_class->raise_failed = _("Channel cannot be raised higher.");
item_class->lower_failed = _("Channel cannot be lowered more.");
drawable_class->convert_type = gimp_channel_convert_type;
drawable_class->invalidate_boundary = gimp_channel_invalidate_boundary;
drawable_class->get_active_components = gimp_channel_get_active_components;
drawable_class->set_buffer = gimp_channel_set_buffer;
klass->boundary = gimp_channel_real_boundary;
klass->is_empty = gimp_channel_real_is_empty;
klass->feather = gimp_channel_real_feather;
klass->sharpen = gimp_channel_real_sharpen;
klass->clear = gimp_channel_real_clear;
klass->all = gimp_channel_real_all;
klass->invert = gimp_channel_real_invert;
klass->border = gimp_channel_real_border;
klass->grow = gimp_channel_real_grow;
klass->shrink = gimp_channel_real_shrink;
klass->flood = gimp_channel_real_flood;
klass->feather_desc = C_("undo-type", "Feather Channel");
klass->sharpen_desc = C_("undo-type", "Sharpen Channel");
klass->clear_desc = C_("undo-type", "Clear Channel");
klass->all_desc = C_("undo-type", "Fill Channel");
klass->invert_desc = C_("undo-type", "Invert Channel");
klass->border_desc = C_("undo-type", "Border Channel");
klass->grow_desc = C_("undo-type", "Grow Channel");
klass->shrink_desc = C_("undo-type", "Shrink Channel");
klass->flood_desc = C_("undo-type", "Flood Channel");
}
static void
gimp_channel_init (GimpChannel *channel)
{
gimp_rgba_set (&channel->color, 0.0, 0.0, 0.0, GIMP_OPACITY_OPAQUE);
channel->show_masked = FALSE;
/* Selection mask variables */
channel->boundary_known = FALSE;
channel->segs_in = NULL;
channel->segs_out = NULL;
channel->num_segs_in = 0;
channel->num_segs_out = 0;
channel->empty = FALSE;
channel->bounds_known = FALSE;
channel->x1 = 0;
channel->y1 = 0;
channel->x2 = 0;
channel->y2 = 0;
}
static void
gimp_channel_pickable_iface_init (GimpPickableInterface *iface)
{
iface->get_opacity_at = gimp_channel_get_opacity_at;
}
static void
gimp_channel_finalize (GObject *object)
{
GimpChannel *channel = GIMP_CHANNEL (object);
g_clear_pointer (&channel->segs_in, g_free);
g_clear_pointer (&channel->segs_out, g_free);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static gint64
gimp_channel_get_memsize (GimpObject *object,
gint64 *gui_size)
{
GimpChannel *channel = GIMP_CHANNEL (object);
*gui_size += channel->num_segs_in * sizeof (GimpBoundSeg);
*gui_size += channel->num_segs_out * sizeof (GimpBoundSeg);
return GIMP_OBJECT_CLASS (parent_class)->get_memsize (object, gui_size);
}
static gchar *
gimp_channel_get_description (GimpViewable *viewable,
gchar **tooltip)
{
if (! strcmp (GIMP_IMAGE_QUICK_MASK_NAME,
gimp_object_get_name (viewable)))
{
return g_strdup (_("Quick Mask"));
}
return GIMP_VIEWABLE_CLASS (parent_class)->get_description (viewable,
tooltip);
}
static GeglNode *
gimp_channel_get_node (GimpFilter *filter)
{
GimpDrawable *drawable = GIMP_DRAWABLE (filter);
GimpChannel *channel = GIMP_CHANNEL (filter);
GeglNode *node;
GeglNode *source;
GeglNode *mode_node;
const Babl *color_format;
node = GIMP_FILTER_CLASS (parent_class)->get_node (filter);
source = gimp_drawable_get_source_node (drawable);
gegl_node_add_child (node, source);
g_warn_if_fail (channel->color_node == NULL);
if (gimp_drawable_get_linear (drawable))
color_format = babl_format ("RGBA float");
else
color_format = babl_format ("R'G'B'A float");
channel->color_node = gegl_node_new_child (node,
"operation", "gegl:color",
"format", color_format,
NULL);
gimp_gegl_node_set_color (channel->color_node,
&channel->color);
g_warn_if_fail (channel->mask_node == NULL);
channel->mask_node = gegl_node_new_child (node,
"operation", "gegl:opacity",
NULL);
gegl_node_connect_to (channel->color_node, "output",
channel->mask_node, "input");
g_warn_if_fail (channel->invert_node == NULL);
channel->invert_node = gegl_node_new_child (node,
"operation", "gegl:invert-linear",
NULL);
if (channel->show_masked)
{
gegl_node_connect_to (source, "output",
channel->invert_node, "input");
gegl_node_connect_to (channel->invert_node, "output",
channel->mask_node, "aux");
}
else
{
gegl_node_connect_to (source, "output",
channel->mask_node, "aux");
}
mode_node = gimp_drawable_get_mode_node (drawable);
gegl_node_connect_to (channel->mask_node, "output",
mode_node, "aux");
return node;
}
static gboolean
gimp_channel_is_attached (GimpItem *item)
{
GimpImage *image = gimp_item_get_image (item);
return (GIMP_IS_IMAGE (image) &&
gimp_container_have (gimp_image_get_channels (image),
GIMP_OBJECT (item)));
}
static GimpItemTree *
gimp_channel_get_tree (GimpItem *item)
{
if (gimp_item_is_attached (item))
{
GimpImage *image = gimp_item_get_image (item);
return gimp_image_get_channel_tree (image);
}
return NULL;
}
static gboolean
gimp_channel_bounds (GimpItem *item,
gdouble *x,
gdouble *y,
gdouble *width,
gdouble *height)
{
GimpChannel *channel = GIMP_CHANNEL (item);
if (! channel->bounds_known)
{
GeglBuffer *buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (channel));
channel->empty = ! gimp_gegl_mask_bounds (buffer,
&channel->x1,
&channel->y1,
&channel->x2,
&channel->y2);
channel->bounds_known = TRUE;
}
*x = channel->x1;
*y = channel->y1;
*width = channel->x2 - channel->x1;
*height = channel->y2 - channel->y1;
return ! channel->empty;
}
static GimpItem *
gimp_channel_duplicate (GimpItem *item,
GType new_type)
{
GimpItem *new_item;
g_return_val_if_fail (g_type_is_a (new_type, GIMP_TYPE_DRAWABLE), NULL);
new_item = GIMP_ITEM_CLASS (parent_class)->duplicate (item, new_type);
if (GIMP_IS_CHANNEL (new_item))
{
GimpChannel *channel = GIMP_CHANNEL (item);
GimpChannel *new_channel = GIMP_CHANNEL (new_item);
new_channel->color = channel->color;
new_channel->show_masked = channel->show_masked;
/* selection mask variables */
new_channel->bounds_known = channel->bounds_known;
new_channel->empty = channel->empty;
new_channel->x1 = channel->x1;
new_channel->y1 = channel->y1;
new_channel->x2 = channel->x2;
new_channel->y2 = channel->y2;
if (new_type == GIMP_TYPE_CHANNEL)
{
/* 8-bit channel hack: make sure pixels between all sorts
* of channels of an image is always copied without any
* gamma conversion
*/
GimpDrawable *new_drawable = GIMP_DRAWABLE (new_item);
GimpImage *image = gimp_item_get_image (item);
const Babl *format = gimp_image_get_channel_format (image);
if (format != gimp_drawable_get_format (new_drawable))
{
GeglBuffer *new_buffer;
new_buffer =
gegl_buffer_new (GEGL_RECTANGLE (0, 0,
gimp_item_get_width (new_item),
gimp_item_get_height (new_item)),
format);
gegl_buffer_set_format (new_buffer,
gimp_drawable_get_format (new_drawable));
gimp_gegl_buffer_copy (gimp_drawable_get_buffer (new_drawable),
NULL, GEGL_ABYSS_NONE,
new_buffer, NULL);
gegl_buffer_set_format (new_buffer, NULL);
gimp_drawable_set_buffer (new_drawable, FALSE, NULL, new_buffer);
g_object_unref (new_buffer);
}
}
}
return new_item;
}
static void
gimp_channel_convert (GimpItem *item,
GimpImage *dest_image,
GType old_type)
{
GimpChannel *channel = GIMP_CHANNEL (item);
GimpDrawable *drawable = GIMP_DRAWABLE (item);
if (! gimp_drawable_is_gray (drawable))
{
gimp_drawable_convert_type (drawable, dest_image,
GIMP_GRAY,
gimp_image_get_precision (dest_image),
gimp_drawable_has_alpha (drawable),
NULL,
GEGL_DITHER_NONE, GEGL_DITHER_NONE,
FALSE, NULL);
}
if (gimp_drawable_has_alpha (drawable))
{
GeglBuffer *new_buffer;
const Babl *format;
GimpRGB background;
format = gimp_drawable_get_format_without_alpha (drawable);
new_buffer =
gegl_buffer_new (GEGL_RECTANGLE (0, 0,
gimp_item_get_width (item),
gimp_item_get_height (item)),
format);
gimp_rgba_set (&background, 0.0, 0.0, 0.0, 0.0);
gimp_gegl_apply_flatten (gimp_drawable_get_buffer (drawable),
NULL, NULL,
new_buffer, &background,
GIMP_LAYER_COLOR_SPACE_RGB_LINEAR);
gimp_drawable_set_buffer_full (drawable, FALSE, NULL,
new_buffer,
gimp_item_get_offset_x (item),
gimp_item_get_offset_y (item),
TRUE);
g_object_unref (new_buffer);
}
if (G_TYPE_FROM_INSTANCE (channel) == GIMP_TYPE_CHANNEL)
{
gint width = gimp_image_get_width (dest_image);
gint height = gimp_image_get_height (dest_image);
gimp_item_set_offset (item, 0, 0);
if (gimp_item_get_width (item) != width ||
gimp_item_get_height (item) != height)
{
gimp_item_resize (item, gimp_get_user_context (dest_image->gimp),
GIMP_FILL_TRANSPARENT,
width, height, 0, 0);
}
}
GIMP_ITEM_CLASS (parent_class)->convert (item, dest_image, old_type);
}
static void
gimp_channel_translate (GimpItem *item,
gdouble off_x,
gdouble off_y,
gboolean push_undo)
{
GimpChannel *channel = GIMP_CHANNEL (item);
gint x, y, width, height;
gimp_item_bounds (GIMP_ITEM (channel), &x, &y, &width, &height);
/* update the old area */
gimp_drawable_update (GIMP_DRAWABLE (item), x, y, width, height);
if (push_undo)
gimp_channel_push_undo (channel, NULL);
if (gimp_rectangle_intersect (x + SIGNED_ROUND (off_x),
y + SIGNED_ROUND (off_y),
width, height,
0, 0,
gimp_item_get_width (GIMP_ITEM (channel)),
gimp_item_get_height (GIMP_ITEM (channel)),
&x, &y, &width, &height))
{
/* copy the portion of the mask we will keep to a temporary
* buffer
*/
GeglBuffer *tmp_buffer =
gegl_buffer_new (GEGL_RECTANGLE (0, 0, width, height),
gimp_drawable_get_format (GIMP_DRAWABLE (channel)));
gimp_gegl_buffer_copy (gimp_drawable_get_buffer (GIMP_DRAWABLE (channel)),
GEGL_RECTANGLE (x - SIGNED_ROUND (off_x),
y - SIGNED_ROUND (off_y),
width, height),
GEGL_ABYSS_NONE,
tmp_buffer,
GEGL_RECTANGLE (0, 0, 0, 0));
/* clear the mask */
gegl_buffer_clear (gimp_drawable_get_buffer (GIMP_DRAWABLE (channel)),
NULL);
/* copy the temp mask back to the mask */
gimp_gegl_buffer_copy (tmp_buffer, NULL, GEGL_ABYSS_NONE,
gimp_drawable_get_buffer (GIMP_DRAWABLE (channel)),
GEGL_RECTANGLE (x, y, 0, 0));
/* free the temporary mask */
g_object_unref (tmp_buffer);
channel->x1 = x;
channel->y1 = y;
channel->x2 = x + width;
channel->y2 = y + height;
}
else
{
/* clear the mask */
gegl_buffer_clear (gimp_drawable_get_buffer (GIMP_DRAWABLE (channel)),
NULL);
channel->empty = TRUE;
channel->x1 = 0;
channel->y1 = 0;
channel->x2 = gimp_item_get_width (GIMP_ITEM (channel));
channel->y2 = gimp_item_get_height (GIMP_ITEM (channel));
}
/* update the new area */
gimp_drawable_update (GIMP_DRAWABLE (item),
channel->x1, channel->y1,
channel->x2 - channel->x1,
channel->y2 - channel->y1);
}
static void
gimp_channel_scale (GimpItem *item,
gint new_width,
gint new_height,
gint new_offset_x,
gint new_offset_y,
GimpInterpolationType interpolation_type,
GimpProgress *progress)
{
GimpChannel *channel = GIMP_CHANNEL (item);
if (G_TYPE_FROM_INSTANCE (item) == GIMP_TYPE_CHANNEL)
{
new_offset_x = 0;
new_offset_y = 0;
}
/* don't waste CPU cycles scaling an empty channel */
if (channel->bounds_known && channel->empty)
{
GimpDrawable *drawable = GIMP_DRAWABLE (item);
GeglBuffer *new_buffer;
new_buffer =
gegl_buffer_new (GEGL_RECTANGLE (0, 0, new_width, new_height),
gimp_drawable_get_format (drawable));
gimp_drawable_set_buffer_full (drawable,
gimp_item_is_attached (item), NULL,
new_buffer,
new_offset_x, new_offset_y,
TRUE);
g_object_unref (new_buffer);
gimp_channel_clear (GIMP_CHANNEL (item), NULL, FALSE);
}
else
{
GIMP_ITEM_CLASS (parent_class)->scale (item, new_width, new_height,
new_offset_x, new_offset_y,
interpolation_type, progress);
}
}
static void
gimp_channel_resize (GimpItem *item,
GimpContext *context,
GimpFillType fill_type,
gint new_width,
gint new_height,
gint offset_x,
gint offset_y)
{
GIMP_ITEM_CLASS (parent_class)->resize (item, context, GIMP_FILL_TRANSPARENT,
new_width, new_height,
offset_x, offset_y);
if (G_TYPE_FROM_INSTANCE (item) == GIMP_TYPE_CHANNEL)
{
gimp_item_set_offset (item, 0, 0);
}
}
static void
gimp_channel_flip (GimpItem *item,
GimpContext *context,
GimpOrientationType flip_type,
gdouble axis,
gboolean clip_result)
{
if (G_TYPE_FROM_INSTANCE (item) == GIMP_TYPE_CHANNEL)
clip_result = TRUE;
GIMP_ITEM_CLASS (parent_class)->flip (item, context, flip_type, axis,
clip_result);
}
static void
gimp_channel_rotate (GimpItem *item,
GimpContext *context,
GimpRotationType rotate_type,
gdouble center_x,
gdouble center_y,
gboolean clip_result)
{
/* don't default to clip_result == TRUE here */
GIMP_ITEM_CLASS (parent_class)->rotate (item, context,
rotate_type, center_x, center_y,
clip_result);
}
static void
gimp_channel_transform (GimpItem *item,
GimpContext *context,
const GimpMatrix3 *matrix,
GimpTransformDirection direction,
GimpInterpolationType interpolation_type,
GimpTransformResize clip_result,
GimpProgress *progress)
{
if (G_TYPE_FROM_INSTANCE (item) == GIMP_TYPE_CHANNEL)
clip_result = TRUE;
GIMP_ITEM_CLASS (parent_class)->transform (item, context, matrix, direction,
interpolation_type,
clip_result,
progress);
}
static gboolean
gimp_channel_fill (GimpItem *item,
GimpDrawable *drawable,
GimpFillOptions *fill_options,
gboolean push_undo,
GimpProgress *progress,
GError **error)
{
GimpChannel *channel = GIMP_CHANNEL (item);
const GimpBoundSeg *segs_in;
const GimpBoundSeg *segs_out;
gint n_segs_in;
gint n_segs_out;
gint offset_x, offset_y;
if (! gimp_channel_boundary (channel, &segs_in, &segs_out,
&n_segs_in, &n_segs_out,
0, 0, 0, 0))
{
g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
_("Cannot fill empty channel."));
return FALSE;
}
gimp_item_get_offset (item, &offset_x, &offset_y);
gimp_drawable_fill_boundary (drawable,
fill_options,
segs_in, n_segs_in,
offset_x, offset_y,
push_undo);
return TRUE;
}
static gboolean
gimp_channel_stroke (GimpItem *item,
GimpDrawable *drawable,
GimpStrokeOptions *stroke_options,
gboolean push_undo,
GimpProgress *progress,
GError **error)
{
GimpChannel *channel = GIMP_CHANNEL (item);
const GimpBoundSeg *segs_in;
const GimpBoundSeg *segs_out;
gint n_segs_in;
gint n_segs_out;
gboolean retval = FALSE;
gint offset_x, offset_y;
if (! gimp_channel_boundary (channel, &segs_in, &segs_out,
&n_segs_in, &n_segs_out,
0, 0, 0, 0))
{
g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
_("Cannot stroke empty channel."));
return FALSE;
}
gimp_item_get_offset (item, &offset_x, &offset_y);
switch (gimp_stroke_options_get_method (stroke_options))
{
case GIMP_STROKE_LINE:
gimp_drawable_stroke_boundary (drawable,
stroke_options,
segs_in, n_segs_in,
offset_x, offset_y,
push_undo);
retval = TRUE;
break;
case GIMP_STROKE_PAINT_METHOD:
{
GimpPaintInfo *paint_info;
GimpPaintCore *core;
GimpPaintOptions *paint_options;
gboolean emulate_dynamics;
paint_info = gimp_context_get_paint_info (GIMP_CONTEXT (stroke_options));
core = g_object_new (paint_info->paint_type, NULL);
paint_options = gimp_stroke_options_get_paint_options (stroke_options);
emulate_dynamics = gimp_stroke_options_get_emulate_dynamics (stroke_options);
retval = gimp_paint_core_stroke_boundary (core, drawable,
paint_options,
emulate_dynamics,
segs_in, n_segs_in,
offset_x, offset_y,
push_undo, error);
g_object_unref (core);
}
break;
default:
g_return_val_if_reached (FALSE);
}
return retval;
}
static void
gimp_channel_to_selection (GimpItem *item,
GimpChannelOps op,
gboolean antialias,
gboolean feather,
gdouble feather_radius_x,
gdouble feather_radius_y)
{
GimpChannel *channel = GIMP_CHANNEL (item);
GimpImage *image = gimp_item_get_image (item);
gint off_x, off_y;
gimp_item_get_offset (item, &off_x, &off_y);
gimp_channel_select_channel (gimp_image_get_mask (image),
GIMP_ITEM_GET_CLASS (item)->to_selection_desc,
channel, off_x, off_y,
op,
feather, feather_radius_x, feather_radius_x);
}
static void
gimp_channel_convert_type (GimpDrawable *drawable,
GimpImage *dest_image,
const Babl *new_format,
GimpColorProfile *dest_profile,
GeglDitherMethod layer_dither_type,
GeglDitherMethod mask_dither_type,
gboolean push_undo,
GimpProgress *progress)
{
GeglBuffer *dest_buffer;
dest_buffer =
gegl_buffer_new (GEGL_RECTANGLE (0, 0,
gimp_item_get_width (GIMP_ITEM (drawable)),
gimp_item_get_height (GIMP_ITEM (drawable))),
new_format);
if (mask_dither_type == GEGL_DITHER_NONE)
{
gimp_gegl_buffer_copy (gimp_drawable_get_buffer (drawable), NULL,
GEGL_ABYSS_NONE,
dest_buffer, NULL);
}
else
{
gint bits;
bits = (babl_format_get_bytes_per_pixel (new_format) * 8 /
babl_format_get_n_components (new_format));
gimp_gegl_apply_dither (gimp_drawable_get_buffer (drawable),
NULL, NULL,
dest_buffer, 1 << bits, mask_dither_type);
}
gimp_drawable_set_buffer (drawable, push_undo, NULL, dest_buffer);
g_object_unref (dest_buffer);
}
static void
gimp_channel_invalidate_boundary (GimpDrawable *drawable)
{
GimpChannel *channel = GIMP_CHANNEL (drawable);
channel->boundary_known = FALSE;
channel->bounds_known = FALSE;
}
static void
gimp_channel_get_active_components (GimpDrawable *drawable,
gboolean *active)
{
/* Make sure that the alpha channel is not valid. */
active[GRAY] = TRUE;
active[ALPHA_G] = FALSE;
}
static void
gimp_channel_set_buffer (GimpDrawable *drawable,
gboolean push_undo,
const gchar *undo_desc,
GeglBuffer *buffer,
gint offset_x,
gint offset_y)
{
GimpChannel *channel = GIMP_CHANNEL (drawable);
GeglBuffer *old_buffer = gimp_drawable_get_buffer (drawable);
if (old_buffer)
{
g_signal_handlers_disconnect_by_func (old_buffer,
gimp_channel_buffer_changed,
channel);
}
GIMP_DRAWABLE_CLASS (parent_class)->set_buffer (drawable,
push_undo, undo_desc,
buffer,
offset_x, offset_y);
gegl_buffer_signal_connect (buffer, "changed",
G_CALLBACK (gimp_channel_buffer_changed),
channel);
if (gimp_filter_peek_node (GIMP_FILTER (channel)))
{
const Babl *color_format;
if (gimp_drawable_get_linear (drawable))
color_format = babl_format ("RGBA float");
else
color_format = babl_format ("R'G'B'A float");
gegl_node_set (channel->color_node,
"format", color_format,
NULL);
}
}
static gdouble
gimp_channel_get_opacity_at (GimpPickable *pickable,
gint x,
gint y)
{
GimpChannel *channel = GIMP_CHANNEL (pickable);
gdouble value = GIMP_OPACITY_TRANSPARENT;
if (x >= 0 && x < gimp_item_get_width (GIMP_ITEM (channel)) &&
y >= 0 && y < gimp_item_get_height (GIMP_ITEM (channel)))
{
if (! channel->bounds_known ||
(! channel->empty &&
x >= channel->x1 &&
x < channel->x2 &&
y >= channel->y1 &&
y < channel->y2))
{
gegl_buffer_sample (gimp_drawable_get_buffer (GIMP_DRAWABLE (channel)),
x, y, NULL, &value, babl_format ("Y double"),
GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
}
}
return value;
}
static gboolean
gimp_channel_real_boundary (GimpChannel *channel,
const GimpBoundSeg **segs_in,
const GimpBoundSeg **segs_out,
gint *num_segs_in,
gint *num_segs_out,
gint x1,
gint y1,
gint x2,
gint y2)
{
if (! channel->boundary_known)
{
gint x3, y3, x4, y4;
/* free the out of date boundary segments */
g_free (channel->segs_in);
g_free (channel->segs_out);
if (gimp_item_bounds (GIMP_ITEM (channel), &x3, &y3, &x4, &y4))
{
GeglBuffer *buffer;
GeglRectangle rect = { x3, y3, x4, y4 };
x4 += x3;
y4 += y3;
buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (channel));
channel->segs_out = gimp_boundary_find (buffer, &rect,
babl_format ("Y float"),
GIMP_BOUNDARY_IGNORE_BOUNDS,
x1, y1, x2, y2,
GIMP_BOUNDARY_HALF_WAY,
&channel->num_segs_out);
x1 = MAX (x1, x3);
y1 = MAX (y1, y3);
x2 = MIN (x2, x4);
y2 = MIN (y2, y4);
if (x2 > x1 && y2 > y1)
{
channel->segs_in = gimp_boundary_find (buffer, NULL,
babl_format ("Y float"),
GIMP_BOUNDARY_WITHIN_BOUNDS,
x1, y1, x2, y2,
GIMP_BOUNDARY_HALF_WAY,
&channel->num_segs_in);
}
else
{
channel->segs_in = NULL;
channel->num_segs_in = 0;
}
}
else
{
channel->segs_in = NULL;
channel->segs_out = NULL;
channel->num_segs_in = 0;
channel->num_segs_out = 0;
}
channel->boundary_known = TRUE;
}
*segs_in = channel->segs_in;
*segs_out = channel->segs_out;
*num_segs_in = channel->num_segs_in;
*num_segs_out = channel->num_segs_out;
return (! channel->empty);
}
static gboolean
gimp_channel_real_is_empty (GimpChannel *channel)
{
GeglBuffer *buffer;
if (channel->bounds_known)
return channel->empty;
buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (channel));
if (! gimp_gegl_mask_is_empty (buffer))
return FALSE;
/* The mask is empty, meaning we can set the bounds as known */
g_clear_pointer (&channel->segs_in, g_free);
g_clear_pointer (&channel->segs_out, g_free);
channel->empty = TRUE;
channel->num_segs_in = 0;
channel->num_segs_out = 0;
channel->bounds_known = TRUE;
channel->boundary_known = TRUE;
channel->x1 = 0;
channel->y1 = 0;
channel->x2 = gimp_item_get_width (GIMP_ITEM (channel));
channel->y2 = gimp_item_get_height (GIMP_ITEM (channel));
return TRUE;
}
static void
gimp_channel_real_feather (GimpChannel *channel,
gdouble radius_x,
gdouble radius_y,
gboolean edge_lock,
gboolean push_undo)
{
gint x1, y1, x2, y2;
if (radius_x <= 0.0 && radius_y <= 0.0)
return;
if (! gimp_item_bounds (GIMP_ITEM (channel), &x1, &y1, &x2, &y2))
return;
x2 += x1;
y2 += y1;
if (gimp_channel_is_empty (channel))
return;
x1 = MAX (0, x1 - ceil (radius_x));
y1 = MAX (0, y1 - ceil (radius_y));
x2 = MIN (gimp_item_get_width (GIMP_ITEM (channel)), x2 + ceil (radius_x));
y2 = MIN (gimp_item_get_height (GIMP_ITEM (channel)), y2 + ceil (radius_y));
if (push_undo)
gimp_channel_push_undo (channel,
GIMP_CHANNEL_GET_CLASS (channel)->feather_desc);
gimp_gegl_apply_feather (gimp_drawable_get_buffer (GIMP_DRAWABLE (channel)),
NULL, NULL,
gimp_drawable_get_buffer (GIMP_DRAWABLE (channel)),
GEGL_RECTANGLE (x1, y1, x2 - x1, y2 - y1),
radius_x,
radius_y,
edge_lock);
gimp_drawable_update (GIMP_DRAWABLE (channel), 0, 0, -1, -1);
}
static void
gimp_channel_real_sharpen (GimpChannel *channel,
gboolean push_undo)
{
GimpDrawable *drawable = GIMP_DRAWABLE (channel);
if (push_undo)
gimp_channel_push_undo (channel,
GIMP_CHANNEL_GET_CLASS (channel)->sharpen_desc);
gimp_gegl_apply_threshold (gimp_drawable_get_buffer (drawable),
NULL, NULL,
gimp_drawable_get_buffer (drawable),
0.5);
gimp_drawable_update (GIMP_DRAWABLE (channel), 0, 0, -1, -1);
}
static void
gimp_channel_real_clear (GimpChannel *channel,
const gchar *undo_desc,
gboolean push_undo)
{
GeglBuffer *buffer;
GeglRectangle rect;
GeglRectangle aligned_rect;
if (channel->bounds_known && channel->empty)
return;
if (push_undo)
{
if (! undo_desc)
undo_desc = GIMP_CHANNEL_GET_CLASS (channel)->clear_desc;
gimp_channel_push_undo (channel, undo_desc);
}
buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (channel));
if (channel->bounds_known)
{
rect.x = channel->x1;
rect.y = channel->y1;
rect.width = channel->x2 - channel->x1;
rect.height = channel->y2 - channel->y1;
}
else
{
rect.x = 0;
rect.y = 0;
rect.width = gimp_item_get_width (GIMP_ITEM (channel));
rect.height = gimp_item_get_height (GIMP_ITEM (channel));
}
gimp_gegl_rectangle_align_to_tile_grid (&aligned_rect, &rect, buffer);
gegl_buffer_clear (buffer, &aligned_rect);
/* we know the bounds */
channel->bounds_known = TRUE;
channel->empty = TRUE;
channel->x1 = 0;
channel->y1 = 0;
channel->x2 = gimp_item_get_width (GIMP_ITEM (channel));
channel->y2 = gimp_item_get_height (GIMP_ITEM (channel));
gimp_drawable_update (GIMP_DRAWABLE (channel),
rect.x, rect.y, rect.width, rect.height);
}
static void
gimp_channel_real_all (GimpChannel *channel,
gboolean push_undo)
{
GeglColor *color;
if (push_undo)
gimp_channel_push_undo (channel,
GIMP_CHANNEL_GET_CLASS (channel)->all_desc);
/* clear the channel */
color = gegl_color_new ("#fff");
gegl_buffer_set_color (gimp_drawable_get_buffer (GIMP_DRAWABLE (channel)),
NULL, color);
g_object_unref (color);
/* we know the bounds */
channel->bounds_known = TRUE;
channel->empty = FALSE;
channel->x1 = 0;
channel->y1 = 0;
channel->x2 = gimp_item_get_width (GIMP_ITEM (channel));
channel->y2 = gimp_item_get_height (GIMP_ITEM (channel));
gimp_drawable_update (GIMP_DRAWABLE (channel), 0, 0, -1, -1);
}
static void
gimp_channel_real_invert (GimpChannel *channel,
gboolean push_undo)
{
GimpDrawable *drawable = GIMP_DRAWABLE (channel);
if (push_undo)
gimp_channel_push_undo (channel,
GIMP_CHANNEL_GET_CLASS (channel)->invert_desc);
if (channel->bounds_known && channel->empty)
{
gimp_channel_all (channel, FALSE);
}
else
{
gimp_gegl_apply_invert_linear (gimp_drawable_get_buffer (drawable),
NULL, NULL,
gimp_drawable_get_buffer (drawable));
gimp_drawable_update (GIMP_DRAWABLE (channel), 0, 0, -1, -1);
}
}
static void
gimp_channel_real_border (GimpChannel *channel,
gint radius_x,
gint radius_y,
GimpChannelBorderStyle style,
gboolean edge_lock,
gboolean push_undo)
{
gint x1, y1, x2, y2;
if (radius_x == 0 && radius_y == 0)
{
/* The relevant GEGL operations require radius_x and radius_y to be > 0.
* When both are 0 (currently can only be achieved by the user through
* PDB), the effect should be to clear the channel.
*/
gimp_channel_clear (channel,
GIMP_CHANNEL_GET_CLASS (channel)->border_desc,
push_undo);
return;
}
else if (radius_x <= 0 || radius_y <= 0)
{
/* FIXME: Implement the case where only one of radius_x and radius_y is 0.
* Currently, should never happen.
*/
g_return_if_reached();
}
if (! gimp_item_bounds (GIMP_ITEM (channel), &x1, &y1, &x2, &y2))
return;
x2 += x1;
y2 += y1;
if (gimp_channel_is_empty (channel))
return;
if (x1 - radius_x < 0)
x1 = 0;
else
x1 -= radius_x;
if (x2 + radius_x > gimp_item_get_width (GIMP_ITEM (channel)))
x2 = gimp_item_get_width (GIMP_ITEM (channel));
else
x2 += radius_x;
if (y1 - radius_y < 0)
y1 = 0;
else
y1 -= radius_y;
if (y2 + radius_y > gimp_item_get_height (GIMP_ITEM (channel)))
y2 = gimp_item_get_height (GIMP_ITEM (channel));
else
y2 += radius_y;
if (push_undo)
gimp_channel_push_undo (channel,
GIMP_CHANNEL_GET_CLASS (channel)->border_desc);
gimp_gegl_apply_border (gimp_drawable_get_buffer (GIMP_DRAWABLE (channel)),
NULL, NULL,
gimp_drawable_get_buffer (GIMP_DRAWABLE (channel)),
GEGL_RECTANGLE (x1, y1, x2 - x1, y2 - y1),
radius_x, radius_y, style, edge_lock);
gimp_drawable_update (GIMP_DRAWABLE (channel), 0, 0, -1, -1);
}
static void
gimp_channel_real_grow (GimpChannel *channel,
gint radius_x,
gint radius_y,
gboolean push_undo)
{
gint x1, y1, x2, y2;
if (radius_x == 0 && radius_y == 0)
return;
if (radius_x <= 0 && radius_y <= 0)
{
gimp_channel_shrink (channel, -radius_x, -radius_y, FALSE, push_undo);
return;
}
if (radius_x < 0 || radius_y < 0)
return;
if (! gimp_item_bounds (GIMP_ITEM (channel), &x1, &y1, &x2, &y2))
return;
x2 += x1;
y2 += y1;
if (gimp_channel_is_empty (channel))
return;
if (x1 - radius_x > 0)
x1 = x1 - radius_x;
else
x1 = 0;
if (y1 - radius_y > 0)
y1 = y1 - radius_y;
else
y1 = 0;
if (x2 + radius_x < gimp_item_get_width (GIMP_ITEM (channel)))
x2 = x2 + radius_x;
else
x2 = gimp_item_get_width (GIMP_ITEM (channel));
if (y2 + radius_y < gimp_item_get_height (GIMP_ITEM (channel)))
y2 = y2 + radius_y;
else
y2 = gimp_item_get_height (GIMP_ITEM (channel));
if (push_undo)
gimp_channel_push_undo (channel,
GIMP_CHANNEL_GET_CLASS (channel)->grow_desc);
gimp_gegl_apply_grow (gimp_drawable_get_buffer (GIMP_DRAWABLE (channel)),
NULL, NULL,
gimp_drawable_get_buffer (GIMP_DRAWABLE (channel)),
GEGL_RECTANGLE (x1, y1, x2 - x1, y2 - y1),
radius_x, radius_y);
gimp_drawable_update (GIMP_DRAWABLE (channel), 0, 0, -1, -1);
}
static void
gimp_channel_real_shrink (GimpChannel *channel,
gint radius_x,
gint radius_y,
gboolean edge_lock,
gboolean push_undo)
{
gint x1, y1, x2, y2;
if (radius_x == 0 && radius_y == 0)
return;
if (radius_x <= 0 && radius_y <= 0)
{
gimp_channel_grow (channel, -radius_x, -radius_y, push_undo);
return;
}
if (radius_x < 0 || radius_y < 0)
return;
if (! gimp_item_bounds (GIMP_ITEM (channel), &x1, &y1, &x2, &y2))
return;
x2 += x1;
y2 += y1;
if (gimp_channel_is_empty (channel))
return;
if (x1 > 0)
x1--;
if (y1 > 0)
y1--;
if (x2 < gimp_item_get_width (GIMP_ITEM (channel)))
x2++;
if (y2 < gimp_item_get_height (GIMP_ITEM (channel)))
y2++;
if (push_undo)
gimp_channel_push_undo (channel,
GIMP_CHANNEL_GET_CLASS (channel)->shrink_desc);
gimp_gegl_apply_shrink (gimp_drawable_get_buffer (GIMP_DRAWABLE (channel)),
NULL, NULL,
gimp_drawable_get_buffer (GIMP_DRAWABLE (channel)),
GEGL_RECTANGLE (x1, y1, x2 - x1, y2 - y1),
radius_x, radius_y, edge_lock);
gimp_drawable_update (GIMP_DRAWABLE (channel), 0, 0, -1, -1);
}
static void
gimp_channel_real_flood (GimpChannel *channel,
gboolean push_undo)
{
gint x, y, width, height;
if (! gimp_item_bounds (GIMP_ITEM (channel), &x, &y, &width, &height))
return;
if (gimp_channel_is_empty (channel))
return;
if (push_undo)
gimp_channel_push_undo (channel,
GIMP_CHANNEL_GET_CLASS (channel)->flood_desc);
gimp_gegl_apply_flood (gimp_drawable_get_buffer (GIMP_DRAWABLE (channel)),
NULL, NULL,
gimp_drawable_get_buffer (GIMP_DRAWABLE (channel)),
GEGL_RECTANGLE (x, y, width, height));
gimp_drawable_update (GIMP_DRAWABLE (channel), x, y, width, height);
}
static void
gimp_channel_buffer_changed (GeglBuffer *buffer,
const GeglRectangle *rect,
GimpChannel *channel)
{
gimp_drawable_invalidate_boundary (GIMP_DRAWABLE (channel));
}
/* public functions */
GimpChannel *
gimp_channel_new (GimpImage *image,
gint width,
gint height,
const gchar *name,
const GimpRGB *color)
{
GimpChannel *channel;
g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
channel =
GIMP_CHANNEL (gimp_drawable_new (GIMP_TYPE_CHANNEL,
image, name,
0, 0, width, height,
gimp_image_get_channel_format (image)));
if (color)
channel->color = *color;
channel->show_masked = TRUE;
/* selection mask variables */
channel->x2 = width;
channel->y2 = height;
return channel;
}
GimpChannel *
gimp_channel_new_from_buffer (GimpImage *image,
GeglBuffer *buffer,
const gchar *name,
const GimpRGB *color)
{
GimpChannel *channel;
GeglBuffer *dest;
g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
g_return_val_if_fail (GEGL_IS_BUFFER (buffer), NULL);
channel = gimp_channel_new (image,
gegl_buffer_get_width (buffer),
gegl_buffer_get_height (buffer),
name, color);
dest = gimp_drawable_get_buffer (GIMP_DRAWABLE (channel));
gimp_gegl_buffer_copy (buffer, NULL, GEGL_ABYSS_NONE, dest, NULL);
return channel;
}
GimpChannel *
gimp_channel_new_from_alpha (GimpImage *image,
GimpDrawable *drawable,
const gchar *name,
const GimpRGB *color)
{
GimpChannel *channel;
GeglBuffer *dest_buffer;
gint width;
gint height;
g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
g_return_val_if_fail (gimp_drawable_has_alpha (drawable), NULL);
width = gimp_item_get_width (GIMP_ITEM (drawable));
height = gimp_item_get_height (GIMP_ITEM (drawable));
channel = gimp_channel_new (image, width, height, name, color);
gimp_channel_clear (channel, NULL, FALSE);
dest_buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (channel));
gegl_buffer_set_format (dest_buffer,
gimp_drawable_get_component_format (drawable,
GIMP_CHANNEL_ALPHA));
gimp_gegl_buffer_copy (gimp_drawable_get_buffer (drawable), NULL,
GEGL_ABYSS_NONE,
dest_buffer, NULL);
gegl_buffer_set_format (dest_buffer, NULL);
return channel;
}
GimpChannel *
gimp_channel_new_from_component (GimpImage *image,
GimpChannelType type,
const gchar *name,
const GimpRGB *color)
{
GimpChannel *channel;
GeglBuffer *src_buffer;
GeglBuffer *dest_buffer;
gint width;
gint height;
const Babl *format;
g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
format = gimp_image_get_component_format (image, type);
g_return_val_if_fail (format != NULL, NULL);
gimp_pickable_flush (GIMP_PICKABLE (image));
src_buffer = gimp_pickable_get_buffer (GIMP_PICKABLE (image));
width = gegl_buffer_get_width (src_buffer);
height = gegl_buffer_get_height (src_buffer);
channel = gimp_channel_new (image, width, height, name, color);
dest_buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (channel));
gegl_buffer_set_format (dest_buffer, format);
gimp_gegl_buffer_copy (src_buffer, NULL, GEGL_ABYSS_NONE, dest_buffer, NULL);
gegl_buffer_set_format (dest_buffer, NULL);
return channel;
}
GimpChannel *
gimp_channel_get_parent (GimpChannel *channel)
{
g_return_val_if_fail (GIMP_IS_CHANNEL (channel), NULL);
return GIMP_CHANNEL (gimp_viewable_get_parent (GIMP_VIEWABLE (channel)));
}
void
gimp_channel_set_color (GimpChannel *channel,
const GimpRGB *color,
gboolean push_undo)
{
g_return_if_fail (GIMP_IS_CHANNEL (channel));
g_return_if_fail (color != NULL);
if (gimp_rgba_distance (&channel->color, color) > RGBA_EPSILON)
{
if (push_undo && gimp_item_is_attached (GIMP_ITEM (channel)))
{
GimpImage *image = gimp_item_get_image (GIMP_ITEM (channel));
gimp_image_undo_push_channel_color (image, C_("undo-type", "Set Channel Color"),
channel);
}
channel->color = *color;
if (gimp_filter_peek_node (GIMP_FILTER (channel)))
{
gimp_gegl_node_set_color (channel->color_node,
&channel->color);
}
gimp_drawable_update (GIMP_DRAWABLE (channel), 0, 0, -1, -1);
g_signal_emit (channel, channel_signals[COLOR_CHANGED], 0);
}
}
void
gimp_channel_get_color (GimpChannel *channel,
GimpRGB *color)
{
g_return_if_fail (GIMP_IS_CHANNEL (channel));
g_return_if_fail (color != NULL);
*color = channel->color;
}
gdouble
gimp_channel_get_opacity (GimpChannel *channel)
{
g_return_val_if_fail (GIMP_IS_CHANNEL (channel), GIMP_OPACITY_TRANSPARENT);
return channel->color.a;
}
void
gimp_channel_set_opacity (GimpChannel *channel,
gdouble opacity,
gboolean push_undo)
{
g_return_if_fail (GIMP_IS_CHANNEL (channel));
opacity = CLAMP (opacity, GIMP_OPACITY_TRANSPARENT, GIMP_OPACITY_OPAQUE);
if (channel->color.a != opacity)
{
if (push_undo && gimp_item_is_attached (GIMP_ITEM (channel)))
{
GimpImage *image = gimp_item_get_image (GIMP_ITEM (channel));
gimp_image_undo_push_channel_color (image, C_("undo-type", "Set Channel Opacity"),
channel);
}
channel->color.a = opacity;
if (gimp_filter_peek_node (GIMP_FILTER (channel)))
{
gimp_gegl_node_set_color (channel->color_node,
&channel->color);
}
gimp_drawable_update (GIMP_DRAWABLE (channel), 0, 0, -1, -1);
g_signal_emit (channel, channel_signals[COLOR_CHANGED], 0);
}
}
gboolean
gimp_channel_get_show_masked (GimpChannel *channel)
{
g_return_val_if_fail (GIMP_IS_CHANNEL (channel), FALSE);
return channel->show_masked;
}
void
gimp_channel_set_show_masked (GimpChannel *channel,
gboolean show_masked)
{
g_return_if_fail (GIMP_IS_CHANNEL (channel));
if (show_masked != channel->show_masked)
{
channel->show_masked = show_masked ? TRUE : FALSE;
if (channel->invert_node)
{
GeglNode *source;
source = gimp_drawable_get_source_node (GIMP_DRAWABLE (channel));
if (channel->show_masked)
{
gegl_node_connect_to (source, "output",
channel->invert_node, "input");
gegl_node_connect_to (channel->invert_node, "output",
channel->mask_node, "aux");
}
else
{
gegl_node_disconnect (channel->invert_node, "input");
gegl_node_connect_to (source, "output",
channel->mask_node, "aux");
}
}
gimp_drawable_update (GIMP_DRAWABLE (channel), 0, 0, -1, -1);
}
}
void
gimp_channel_push_undo (GimpChannel *channel,
const gchar *undo_desc)
{
g_return_if_fail (GIMP_IS_CHANNEL (channel));
g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (channel)));
gimp_image_undo_push_mask (gimp_item_get_image (GIMP_ITEM (channel)),
undo_desc, channel);
}
/******************************/
/* selection mask functions */
/******************************/
GimpChannel *
gimp_channel_new_mask (GimpImage *image,
gint width,
gint height)
{
GimpChannel *channel;
g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
channel =
GIMP_CHANNEL (gimp_drawable_new (GIMP_TYPE_CHANNEL,
image, _("Selection Mask"),
0, 0, width, height,
gimp_image_get_mask_format (image)));
channel->show_masked = TRUE;
channel->x2 = width;
channel->y2 = height;
gegl_buffer_clear (gimp_drawable_get_buffer (GIMP_DRAWABLE (channel)),
NULL);
return channel;
}
gboolean
gimp_channel_boundary (GimpChannel *channel,
const GimpBoundSeg **segs_in,
const GimpBoundSeg **segs_out,
gint *num_segs_in,
gint *num_segs_out,
gint x1,
gint y1,
gint x2,
gint y2)
{
g_return_val_if_fail (GIMP_IS_CHANNEL (channel), FALSE);
g_return_val_if_fail (segs_in != NULL, FALSE);
g_return_val_if_fail (segs_out != NULL, FALSE);
g_return_val_if_fail (num_segs_in != NULL, FALSE);
g_return_val_if_fail (num_segs_out != NULL, FALSE);
return GIMP_CHANNEL_GET_CLASS (channel)->boundary (channel,
segs_in, segs_out,
num_segs_in, num_segs_out,
x1, y1,
x2, y2);
}
gboolean
gimp_channel_is_empty (GimpChannel *channel)
{
g_return_val_if_fail (GIMP_IS_CHANNEL (channel), FALSE);
return GIMP_CHANNEL_GET_CLASS (channel)->is_empty (channel);
}
void
gimp_channel_feather (GimpChannel *channel,
gdouble radius_x,
gdouble radius_y,
gboolean edge_lock,
gboolean push_undo)
{
g_return_if_fail (GIMP_IS_CHANNEL (channel));
if (! gimp_item_is_attached (GIMP_ITEM (channel)))
push_undo = FALSE;
GIMP_CHANNEL_GET_CLASS (channel)->feather (channel, radius_x, radius_y,
edge_lock, push_undo);
}
void
gimp_channel_sharpen (GimpChannel *channel,
gboolean push_undo)
{
g_return_if_fail (GIMP_IS_CHANNEL (channel));
if (! gimp_item_is_attached (GIMP_ITEM (channel)))
push_undo = FALSE;
GIMP_CHANNEL_GET_CLASS (channel)->sharpen (channel, push_undo);
}
void
gimp_channel_clear (GimpChannel *channel,
const gchar *undo_desc,
gboolean push_undo)
{
g_return_if_fail (GIMP_IS_CHANNEL (channel));
if (! gimp_item_is_attached (GIMP_ITEM (channel)))
push_undo = FALSE;
GIMP_CHANNEL_GET_CLASS (channel)->clear (channel, undo_desc, push_undo);
}
void
gimp_channel_all (GimpChannel *channel,
gboolean push_undo)
{
g_return_if_fail (GIMP_IS_CHANNEL (channel));
if (! gimp_item_is_attached (GIMP_ITEM (channel)))
push_undo = FALSE;
GIMP_CHANNEL_GET_CLASS (channel)->all (channel, push_undo);
}
void
gimp_channel_invert (GimpChannel *channel,
gboolean push_undo)
{
g_return_if_fail (GIMP_IS_CHANNEL (channel));
if (! gimp_item_is_attached (GIMP_ITEM (channel)))
push_undo = FALSE;
GIMP_CHANNEL_GET_CLASS (channel)->invert (channel, push_undo);
}
void
gimp_channel_border (GimpChannel *channel,
gint radius_x,
gint radius_y,
GimpChannelBorderStyle style,
gboolean edge_lock,
gboolean push_undo)
{
g_return_if_fail (GIMP_IS_CHANNEL (channel));
if (! gimp_item_is_attached (GIMP_ITEM (channel)))
push_undo = FALSE;
GIMP_CHANNEL_GET_CLASS (channel)->border (channel,
radius_x, radius_y, style, edge_lock,
push_undo);
}
void
gimp_channel_grow (GimpChannel *channel,
gint radius_x,
gint radius_y,
gboolean push_undo)
{
g_return_if_fail (GIMP_IS_CHANNEL (channel));
if (! gimp_item_is_attached (GIMP_ITEM (channel)))
push_undo = FALSE;
GIMP_CHANNEL_GET_CLASS (channel)->grow (channel, radius_x, radius_y,
push_undo);
}
void
gimp_channel_shrink (GimpChannel *channel,
gint radius_x,
gint radius_y,
gboolean edge_lock,
gboolean push_undo)
{
g_return_if_fail (GIMP_IS_CHANNEL (channel));
if (! gimp_item_is_attached (GIMP_ITEM (channel)))
push_undo = FALSE;
GIMP_CHANNEL_GET_CLASS (channel)->shrink (channel, radius_x, radius_y,
edge_lock, push_undo);
}
void
gimp_channel_flood (GimpChannel *channel,
gboolean push_undo)
{
g_return_if_fail (GIMP_IS_CHANNEL (channel));
if (! gimp_item_is_attached (GIMP_ITEM (channel)))
push_undo = FALSE;
GIMP_CHANNEL_GET_CLASS (channel)->flood (channel, push_undo);
}