/* 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 . */ #include "config.h" #include #include #include #include #include "libpikabase/pikabase.h" #include "libpikamath/pikamath.h" #include "libpikacolor/pikacolor.h" #include "core-types.h" #include "paint/pikapaintcore-stroke.h" #include "paint/pikapaintoptions.h" #include "gegl/pika-babl.h" #include "gegl/pika-gegl-apply-operation.h" #include "gegl/pika-gegl-loops.h" #include "gegl/pika-gegl-mask.h" #include "gegl/pika-gegl-nodes.h" #include "pika.h" #include "pika-utils.h" #include "pikaboundary.h" #include "pikacontainer.h" #include "pikaerror.h" #include "pikaimage.h" #include "pikaimage-quick-mask.h" #include "pikaimage-undo.h" #include "pikaimage-undo-push.h" #include "pikachannel.h" #include "pikachannel-select.h" #include "pikacontext.h" #include "pikadrawable-fill.h" #include "pikadrawable-stroke.h" #include "pikapaintinfo.h" #include "pikapickable.h" #include "pikastrokeoptions.h" #include "pika-intl.h" #define RGBA_EPSILON 1e-6 enum { COLOR_CHANGED, LAST_SIGNAL }; static void pika_channel_pickable_iface_init (PikaPickableInterface *iface); static void pika_channel_finalize (GObject *object); static gint64 pika_channel_get_memsize (PikaObject *object, gint64 *gui_size); static gchar * pika_channel_get_description (PikaViewable *viewable, gchar **tooltip); static GeglNode * pika_channel_get_node (PikaFilter *filter); static gboolean pika_channel_is_attached (PikaItem *item); static PikaItemTree * pika_channel_get_tree (PikaItem *item); static gboolean pika_channel_bounds (PikaItem *item, gdouble *x, gdouble *y, gdouble *width, gdouble *height); static PikaItem * pika_channel_duplicate (PikaItem *item, GType new_type); static void pika_channel_convert (PikaItem *item, PikaImage *dest_image, GType old_type); static void pika_channel_translate (PikaItem *item, gdouble off_x, gdouble off_y, gboolean push_undo); static void pika_channel_scale (PikaItem *item, gint new_width, gint new_height, gint new_offset_x, gint new_offset_y, PikaInterpolationType interp_type, PikaProgress *progress); static void pika_channel_resize (PikaItem *item, PikaContext *context, PikaFillType fill_type, gint new_width, gint new_height, gint offset_x, gint offset_y); static PikaTransformResize pika_channel_get_clip (PikaItem *item, PikaTransformResize clip_result); static gboolean pika_channel_fill (PikaItem *item, PikaDrawable *drawable, PikaFillOptions *fill_options, gboolean push_undo, PikaProgress *progress, GError **error); static gboolean pika_channel_stroke (PikaItem *item, PikaDrawable *drawable, PikaStrokeOptions *stroke_options, gboolean push_undo, PikaProgress *progress, GError **error); static void pika_channel_to_selection (PikaItem *item, PikaChannelOps op, gboolean antialias, gboolean feather, gdouble feather_radius_x, gdouble feather_radius_y); static void pika_channel_convert_type (PikaDrawable *drawable, PikaImage *dest_image, const Babl *new_format, PikaColorProfile *src_profile, PikaColorProfile *dest_profile, GeglDitherMethod layer_dither_type, GeglDitherMethod mask_dither_type, gboolean push_undo, PikaProgress *progress); static void pika_channel_invalidate_boundary (PikaDrawable *drawable); static void pika_channel_get_active_components (PikaDrawable *drawable, gboolean *active); static void pika_channel_set_buffer (PikaDrawable *drawable, gboolean push_undo, const gchar *undo_desc, GeglBuffer *buffer, const GeglRectangle *bounds); static gdouble pika_channel_get_opacity_at (PikaPickable *pickable, gint x, gint y); static gboolean pika_channel_real_boundary (PikaChannel *channel, const PikaBoundSeg **segs_in, const PikaBoundSeg **segs_out, gint *num_segs_in, gint *num_segs_out, gint x1, gint y1, gint x2, gint y2); static gboolean pika_channel_real_is_empty (PikaChannel *channel); static gboolean pika_channel_real_is_full (PikaChannel *channel); static void pika_channel_real_feather (PikaChannel *channel, gdouble radius_x, gdouble radius_y, gboolean edge_lock, gboolean push_undo); static void pika_channel_real_sharpen (PikaChannel *channel, gboolean push_undo); static void pika_channel_real_clear (PikaChannel *channel, const gchar *undo_desc, gboolean push_undo); static void pika_channel_real_all (PikaChannel *channel, gboolean push_undo); static void pika_channel_real_invert (PikaChannel *channel, gboolean push_undo); static void pika_channel_real_border (PikaChannel *channel, gint radius_x, gint radius_y, PikaChannelBorderStyle style, gboolean edge_lock, gboolean push_undo); static void pika_channel_real_grow (PikaChannel *channel, gint radius_x, gint radius_y, gboolean push_undo); static void pika_channel_real_shrink (PikaChannel *channel, gint radius_x, gint radius_y, gboolean edge_lock, gboolean push_undo); static void pika_channel_real_flood (PikaChannel *channel, gboolean push_undo); static void pika_channel_buffer_changed (GeglBuffer *buffer, const GeglRectangle *rect, PikaChannel *channel); G_DEFINE_TYPE_WITH_CODE (PikaChannel, pika_channel, PIKA_TYPE_DRAWABLE, G_IMPLEMENT_INTERFACE (PIKA_TYPE_PICKABLE, pika_channel_pickable_iface_init)) #define parent_class pika_channel_parent_class static guint channel_signals[LAST_SIGNAL] = { 0 }; static void pika_channel_class_init (PikaChannelClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); PikaObjectClass *pika_object_class = PIKA_OBJECT_CLASS (klass); PikaViewableClass *viewable_class = PIKA_VIEWABLE_CLASS (klass); PikaFilterClass *filter_class = PIKA_FILTER_CLASS (klass); PikaItemClass *item_class = PIKA_ITEM_CLASS (klass); PikaDrawableClass *drawable_class = PIKA_DRAWABLE_CLASS (klass); channel_signals[COLOR_CHANGED] = g_signal_new ("color-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaChannelClass, color_changed), NULL, NULL, NULL, G_TYPE_NONE, 0); object_class->finalize = pika_channel_finalize; pika_object_class->get_memsize = pika_channel_get_memsize; viewable_class->get_description = pika_channel_get_description; viewable_class->default_icon_name = "pika-channel"; filter_class->get_node = pika_channel_get_node; item_class->is_attached = pika_channel_is_attached; item_class->get_tree = pika_channel_get_tree; item_class->bounds = pika_channel_bounds; item_class->duplicate = pika_channel_duplicate; item_class->convert = pika_channel_convert; item_class->translate = pika_channel_translate; item_class->scale = pika_channel_scale; item_class->resize = pika_channel_resize; item_class->get_clip = pika_channel_get_clip; item_class->fill = pika_channel_fill; item_class->stroke = pika_channel_stroke; item_class->to_selection = pika_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 = pika_channel_convert_type; drawable_class->invalidate_boundary = pika_channel_invalidate_boundary; drawable_class->get_active_components = pika_channel_get_active_components; drawable_class->set_buffer = pika_channel_set_buffer; klass->boundary = pika_channel_real_boundary; klass->is_empty = pika_channel_real_is_empty; klass->is_full = pika_channel_real_is_full; klass->feather = pika_channel_real_feather; klass->sharpen = pika_channel_real_sharpen; klass->clear = pika_channel_real_clear; klass->all = pika_channel_real_all; klass->invert = pika_channel_real_invert; klass->border = pika_channel_real_border; klass->grow = pika_channel_real_grow; klass->shrink = pika_channel_real_shrink; klass->flood = pika_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 pika_channel_init (PikaChannel *channel) { pika_rgba_set (&channel->color, 0.0, 0.0, 0.0, PIKA_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 pika_channel_pickable_iface_init (PikaPickableInterface *iface) { iface->get_opacity_at = pika_channel_get_opacity_at; } static void pika_channel_finalize (GObject *object) { PikaChannel *channel = PIKA_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 pika_channel_get_memsize (PikaObject *object, gint64 *gui_size) { PikaChannel *channel = PIKA_CHANNEL (object); *gui_size += channel->num_segs_in * sizeof (PikaBoundSeg); *gui_size += channel->num_segs_out * sizeof (PikaBoundSeg); return PIKA_OBJECT_CLASS (parent_class)->get_memsize (object, gui_size); } static gchar * pika_channel_get_description (PikaViewable *viewable, gchar **tooltip) { if (! strcmp (PIKA_IMAGE_QUICK_MASK_NAME, pika_object_get_name (viewable))) { return g_strdup (_("Quick Mask")); } return PIKA_VIEWABLE_CLASS (parent_class)->get_description (viewable, tooltip); } static GeglNode * pika_channel_get_node (PikaFilter *filter) { PikaDrawable *drawable = PIKA_DRAWABLE (filter); PikaChannel *channel = PIKA_CHANNEL (filter); GeglNode *node; GeglNode *source; GeglNode *mode_node; const Babl *color_format; node = PIKA_FILTER_CLASS (parent_class)->get_node (filter); source = pika_drawable_get_source_node (drawable); gegl_node_add_child (node, source); g_warn_if_fail (channel->color_node == NULL); color_format = pika_babl_format (PIKA_RGB, pika_babl_precision (PIKA_COMPONENT_TYPE_FLOAT, pika_drawable_get_trc (drawable)), TRUE, NULL); channel->color_node = gegl_node_new_child (node, "operation", "gegl:color", "format", color_format, NULL); pika_gegl_node_set_color (channel->color_node, &channel->color, NULL); g_warn_if_fail (channel->mask_node == NULL); channel->mask_node = gegl_node_new_child (node, "operation", "gegl:opacity", NULL); gegl_node_link (channel->color_node, channel->mask_node); 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_link (source, channel->invert_node); gegl_node_connect (channel->invert_node, "output", channel->mask_node, "aux"); } else { gegl_node_connect (source, "output", channel->mask_node, "aux"); } mode_node = pika_drawable_get_mode_node (drawable); gegl_node_connect (channel->mask_node, "output", mode_node, "aux"); return node; } static gboolean pika_channel_is_attached (PikaItem *item) { PikaImage *image = pika_item_get_image (item); return (PIKA_IS_IMAGE (image) && pika_container_have (pika_image_get_channels (image), PIKA_OBJECT (item))); } static PikaItemTree * pika_channel_get_tree (PikaItem *item) { if (pika_item_is_attached (item)) { PikaImage *image = pika_item_get_image (item); return pika_image_get_channel_tree (image); } return NULL; } static gboolean pika_channel_bounds (PikaItem *item, gdouble *x, gdouble *y, gdouble *width, gdouble *height) { PikaChannel *channel = PIKA_CHANNEL (item); if (! channel->bounds_known) { GeglBuffer *buffer = pika_drawable_get_buffer (PIKA_DRAWABLE (channel)); channel->empty = ! pika_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 PikaItem * pika_channel_duplicate (PikaItem *item, GType new_type) { PikaItem *new_item; g_return_val_if_fail (g_type_is_a (new_type, PIKA_TYPE_DRAWABLE), NULL); new_item = PIKA_ITEM_CLASS (parent_class)->duplicate (item, new_type); if (PIKA_IS_CHANNEL (new_item)) { PikaChannel *channel = PIKA_CHANNEL (item); PikaChannel *new_channel = PIKA_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 == PIKA_TYPE_CHANNEL) { /* 8-bit channel hack: make sure pixels between all sorts * of channels of an image is always copied without any * gamma conversion */ PikaDrawable *new_drawable = PIKA_DRAWABLE (new_item); PikaImage *image = pika_item_get_image (item); const Babl *format = pika_image_get_channel_format (image); if (format != pika_drawable_get_format (new_drawable)) { GeglBuffer *new_buffer; new_buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0, pika_item_get_width (new_item), pika_item_get_height (new_item)), format); gegl_buffer_set_format (new_buffer, pika_drawable_get_format (new_drawable)); pika_gegl_buffer_copy (pika_drawable_get_buffer (new_drawable), NULL, GEGL_ABYSS_NONE, new_buffer, NULL); gegl_buffer_set_format (new_buffer, NULL); pika_drawable_set_buffer (new_drawable, FALSE, NULL, new_buffer); g_object_unref (new_buffer); } } } return new_item; } static void pika_channel_convert (PikaItem *item, PikaImage *dest_image, GType old_type) { PikaChannel *channel = PIKA_CHANNEL (item); PikaDrawable *drawable = PIKA_DRAWABLE (item); if (! pika_drawable_is_gray (drawable)) { pika_drawable_convert_type (drawable, dest_image, PIKA_GRAY, pika_image_get_precision (dest_image), pika_drawable_has_alpha (drawable), NULL, NULL, GEGL_DITHER_NONE, GEGL_DITHER_NONE, FALSE, NULL); } if (pika_drawable_has_alpha (drawable)) { GeglBuffer *new_buffer; const Babl *format; PikaRGB background; format = pika_drawable_get_format_without_alpha (drawable); new_buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0, pika_item_get_width (item), pika_item_get_height (item)), format); pika_rgba_set (&background, 0.0, 0.0, 0.0, 0.0); pika_gegl_apply_flatten (pika_drawable_get_buffer (drawable), NULL, NULL, new_buffer, &background, NULL, PIKA_LAYER_COLOR_SPACE_RGB_LINEAR); pika_drawable_set_buffer_full (drawable, FALSE, NULL, new_buffer, GEGL_RECTANGLE ( pika_item_get_offset_x (item), pika_item_get_offset_y (item), 0, 0), TRUE); g_object_unref (new_buffer); } if (G_TYPE_FROM_INSTANCE (channel) == PIKA_TYPE_CHANNEL) { gint width = pika_image_get_width (dest_image); gint height = pika_image_get_height (dest_image); pika_item_set_offset (item, 0, 0); if (pika_item_get_width (item) != width || pika_item_get_height (item) != height) { pika_item_resize (item, pika_get_user_context (dest_image->pika), PIKA_FILL_TRANSPARENT, width, height, 0, 0); } } PIKA_ITEM_CLASS (parent_class)->convert (item, dest_image, old_type); } static void pika_channel_translate (PikaItem *item, gdouble off_x, gdouble off_y, gboolean push_undo) { PikaChannel *channel = PIKA_CHANNEL (item); gint x, y, width, height; pika_item_bounds (PIKA_ITEM (channel), &x, &y, &width, &height); /* update the old area */ pika_drawable_update (PIKA_DRAWABLE (item), x, y, width, height); if (push_undo) pika_channel_push_undo (channel, NULL); if (pika_rectangle_intersect (x + SIGNED_ROUND (off_x), y + SIGNED_ROUND (off_y), width, height, 0, 0, pika_item_get_width (PIKA_ITEM (channel)), pika_item_get_height (PIKA_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), pika_drawable_get_format (PIKA_DRAWABLE (channel))); pika_gegl_buffer_copy (pika_drawable_get_buffer (PIKA_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 (pika_drawable_get_buffer (PIKA_DRAWABLE (channel)), NULL); /* copy the temp mask back to the mask */ pika_gegl_buffer_copy (tmp_buffer, NULL, GEGL_ABYSS_NONE, pika_drawable_get_buffer (PIKA_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 (pika_drawable_get_buffer (PIKA_DRAWABLE (channel)), NULL); channel->empty = TRUE; channel->x1 = 0; channel->y1 = 0; channel->x2 = pika_item_get_width (PIKA_ITEM (channel)); channel->y2 = pika_item_get_height (PIKA_ITEM (channel)); } /* update the new area */ pika_drawable_update (PIKA_DRAWABLE (item), channel->x1, channel->y1, channel->x2 - channel->x1, channel->y2 - channel->y1); } static void pika_channel_scale (PikaItem *item, gint new_width, gint new_height, gint new_offset_x, gint new_offset_y, PikaInterpolationType interpolation_type, PikaProgress *progress) { PikaChannel *channel = PIKA_CHANNEL (item); if (G_TYPE_FROM_INSTANCE (item) == PIKA_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) { PikaDrawable *drawable = PIKA_DRAWABLE (item); GeglBuffer *new_buffer; new_buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0, new_width, new_height), pika_drawable_get_format (drawable)); pika_drawable_set_buffer_full (drawable, pika_item_is_attached (item), NULL, new_buffer, GEGL_RECTANGLE (new_offset_x, new_offset_y, 0, 0), TRUE); g_object_unref (new_buffer); pika_channel_clear (PIKA_CHANNEL (item), NULL, FALSE); } else { PIKA_ITEM_CLASS (parent_class)->scale (item, new_width, new_height, new_offset_x, new_offset_y, interpolation_type, progress); } } static void pika_channel_resize (PikaItem *item, PikaContext *context, PikaFillType fill_type, gint new_width, gint new_height, gint offset_x, gint offset_y) { PIKA_ITEM_CLASS (parent_class)->resize (item, context, fill_type, new_width, new_height, offset_x, offset_y); if (G_TYPE_FROM_INSTANCE (item) == PIKA_TYPE_CHANNEL) { pika_item_set_offset (item, 0, 0); } } static PikaTransformResize pika_channel_get_clip (PikaItem *item, PikaTransformResize clip_result) { return PIKA_TRANSFORM_RESIZE_CLIP; } static gboolean pika_channel_fill (PikaItem *item, PikaDrawable *drawable, PikaFillOptions *fill_options, gboolean push_undo, PikaProgress *progress, GError **error) { PikaChannel *channel = PIKA_CHANNEL (item); const PikaBoundSeg *segs_in; const PikaBoundSeg *segs_out; gint n_segs_in; gint n_segs_out; gint offset_x, offset_y; if (! pika_channel_boundary (channel, &segs_in, &segs_out, &n_segs_in, &n_segs_out, 0, 0, 0, 0)) { g_set_error_literal (error, PIKA_ERROR, PIKA_FAILED, _("Cannot fill empty channel.")); return FALSE; } pika_item_get_offset (item, &offset_x, &offset_y); pika_drawable_fill_boundary (drawable, fill_options, segs_in, n_segs_in, offset_x, offset_y, push_undo); return TRUE; } static gboolean pika_channel_stroke (PikaItem *item, PikaDrawable *drawable, PikaStrokeOptions *stroke_options, gboolean push_undo, PikaProgress *progress, GError **error) { PikaChannel *channel = PIKA_CHANNEL (item); const PikaBoundSeg *segs_in; const PikaBoundSeg *segs_out; gint n_segs_in; gint n_segs_out; gboolean retval = FALSE; gint offset_x, offset_y; if (! pika_channel_boundary (channel, &segs_in, &segs_out, &n_segs_in, &n_segs_out, 0, 0, 0, 0)) { g_set_error_literal (error, PIKA_ERROR, PIKA_FAILED, _("Cannot stroke empty channel.")); return FALSE; } pika_item_get_offset (item, &offset_x, &offset_y); switch (pika_stroke_options_get_method (stroke_options)) { case PIKA_STROKE_LINE: pika_drawable_stroke_boundary (drawable, stroke_options, n_segs_in > 0 ? segs_in : segs_out, n_segs_in > 0 ? n_segs_in : n_segs_out, offset_x, offset_y, push_undo); retval = TRUE; break; case PIKA_STROKE_PAINT_METHOD: { PikaPaintInfo *paint_info; PikaPaintCore *core; PikaPaintOptions *paint_options; gboolean emulate_dynamics; paint_info = pika_context_get_paint_info (PIKA_CONTEXT (stroke_options)); core = g_object_new (paint_info->paint_type, NULL); paint_options = pika_stroke_options_get_paint_options (stroke_options); emulate_dynamics = pika_stroke_options_get_emulate_dynamics (stroke_options); retval = pika_paint_core_stroke_boundary (core, drawable, paint_options, emulate_dynamics, n_segs_in > 0 ? segs_in : segs_out, n_segs_in > 0 ? n_segs_in : n_segs_out, offset_x, offset_y, push_undo, error); g_object_unref (core); } break; default: g_return_val_if_reached (FALSE); } return retval; } static void pika_channel_to_selection (PikaItem *item, PikaChannelOps op, gboolean antialias, gboolean feather, gdouble feather_radius_x, gdouble feather_radius_y) { PikaChannel *channel = PIKA_CHANNEL (item); PikaImage *image = pika_item_get_image (item); gint off_x, off_y; pika_item_get_offset (item, &off_x, &off_y); pika_channel_select_channel (pika_image_get_mask (image), PIKA_ITEM_GET_CLASS (item)->to_selection_desc, channel, off_x, off_y, op, feather, feather_radius_x, feather_radius_x); } static void pika_channel_convert_type (PikaDrawable *drawable, PikaImage *dest_image, const Babl *new_format, PikaColorProfile *src_profile, PikaColorProfile *dest_profile, GeglDitherMethod layer_dither_type, GeglDitherMethod mask_dither_type, gboolean push_undo, PikaProgress *progress) { GeglBuffer *dest_buffer; dest_buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0, pika_item_get_width (PIKA_ITEM (drawable)), pika_item_get_height (PIKA_ITEM (drawable))), new_format); if (mask_dither_type == GEGL_DITHER_NONE) { pika_gegl_buffer_copy (pika_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)); pika_gegl_apply_dither (pika_drawable_get_buffer (drawable), NULL, NULL, dest_buffer, 1 << bits, mask_dither_type); } pika_drawable_set_buffer (drawable, push_undo, NULL, dest_buffer); g_object_unref (dest_buffer); } static void pika_channel_invalidate_boundary (PikaDrawable *drawable) { PikaChannel *channel = PIKA_CHANNEL (drawable); channel->boundary_known = FALSE; channel->bounds_known = FALSE; } static void pika_channel_get_active_components (PikaDrawable *drawable, gboolean *active) { /* Make sure that the alpha channel is not valid. */ active[GRAY] = TRUE; active[ALPHA_G] = FALSE; } static void pika_channel_set_buffer (PikaDrawable *drawable, gboolean push_undo, const gchar *undo_desc, GeglBuffer *buffer, const GeglRectangle *bounds) { PikaChannel *channel = PIKA_CHANNEL (drawable); GeglBuffer *old_buffer = pika_drawable_get_buffer (drawable); if (old_buffer) { g_signal_handlers_disconnect_by_func (old_buffer, pika_channel_buffer_changed, channel); } PIKA_DRAWABLE_CLASS (parent_class)->set_buffer (drawable, push_undo, undo_desc, buffer, bounds); gegl_buffer_signal_connect (buffer, "changed", G_CALLBACK (pika_channel_buffer_changed), channel); if (pika_filter_peek_node (PIKA_FILTER (channel))) { const Babl *color_format = pika_babl_format (PIKA_RGB, pika_babl_precision (PIKA_COMPONENT_TYPE_FLOAT, pika_drawable_get_trc (drawable)), TRUE, NULL); gegl_node_set (channel->color_node, "format", color_format, NULL); } } static gdouble pika_channel_get_opacity_at (PikaPickable *pickable, gint x, gint y) { PikaChannel *channel = PIKA_CHANNEL (pickable); gdouble value = PIKA_OPACITY_TRANSPARENT; if (x >= 0 && x < pika_item_get_width (PIKA_ITEM (channel)) && y >= 0 && y < pika_item_get_height (PIKA_ITEM (channel))) { if (! channel->bounds_known || (! channel->empty && x >= channel->x1 && x < channel->x2 && y >= channel->y1 && y < channel->y2)) { gegl_buffer_sample (pika_drawable_get_buffer (PIKA_DRAWABLE (channel)), x, y, NULL, &value, babl_format ("Y double"), GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE); } } return value; } static gboolean pika_channel_real_boundary (PikaChannel *channel, const PikaBoundSeg **segs_in, const PikaBoundSeg **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 (pika_item_bounds (PIKA_ITEM (channel), &x3, &y3, &x4, &y4)) { GeglBuffer *buffer; GeglRectangle rect = { x3, y3, x4, y4 }; x4 += x3; y4 += y3; buffer = pika_drawable_get_buffer (PIKA_DRAWABLE (channel)); channel->segs_out = pika_boundary_find (buffer, &rect, babl_format ("Y float"), PIKA_BOUNDARY_IGNORE_BOUNDS, x1, y1, x2, y2, PIKA_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 = pika_boundary_find (buffer, NULL, babl_format ("Y float"), PIKA_BOUNDARY_WITHIN_BOUNDS, x1, y1, x2, y2, PIKA_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 pika_channel_real_is_empty (PikaChannel *channel) { GeglBuffer *buffer; if (channel->bounds_known) return channel->empty; buffer = pika_drawable_get_buffer (PIKA_DRAWABLE (channel)); if (! pika_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 = pika_item_get_width (PIKA_ITEM (channel)); channel->y2 = pika_item_get_height (PIKA_ITEM (channel)); return TRUE; } static gboolean pika_channel_real_is_full (PikaChannel *channel) { return ! pika_channel_is_empty (channel) && channel->x1 == 0 && channel->y1 == 0 && channel->x2 == pika_item_get_width (PIKA_ITEM (channel)) && channel->y2 == pika_item_get_height (PIKA_ITEM (channel)); } static void pika_channel_real_feather (PikaChannel *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 (! pika_item_bounds (PIKA_ITEM (channel), &x1, &y1, &x2, &y2)) return; x2 += x1; y2 += y1; if (pika_channel_is_empty (channel)) return; x1 = MAX (0, x1 - ceil (radius_x)); y1 = MAX (0, y1 - ceil (radius_y)); x2 = MIN (pika_item_get_width (PIKA_ITEM (channel)), x2 + ceil (radius_x)); y2 = MIN (pika_item_get_height (PIKA_ITEM (channel)), y2 + ceil (radius_y)); if (push_undo) pika_channel_push_undo (channel, PIKA_CHANNEL_GET_CLASS (channel)->feather_desc); pika_gegl_apply_feather (pika_drawable_get_buffer (PIKA_DRAWABLE (channel)), NULL, NULL, pika_drawable_get_buffer (PIKA_DRAWABLE (channel)), GEGL_RECTANGLE (x1, y1, x2 - x1, y2 - y1), radius_x, radius_y, edge_lock); pika_drawable_update (PIKA_DRAWABLE (channel), 0, 0, -1, -1); } static void pika_channel_real_sharpen (PikaChannel *channel, gboolean push_undo) { PikaDrawable *drawable = PIKA_DRAWABLE (channel); if (push_undo) pika_channel_push_undo (channel, PIKA_CHANNEL_GET_CLASS (channel)->sharpen_desc); pika_gegl_apply_threshold (pika_drawable_get_buffer (drawable), NULL, NULL, pika_drawable_get_buffer (drawable), 0.5); pika_drawable_update (PIKA_DRAWABLE (channel), 0, 0, -1, -1); } static void pika_channel_real_clear (PikaChannel *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 = PIKA_CHANNEL_GET_CLASS (channel)->clear_desc; pika_channel_push_undo (channel, undo_desc); } buffer = pika_drawable_get_buffer (PIKA_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 = pika_item_get_width (PIKA_ITEM (channel)); rect.height = pika_item_get_height (PIKA_ITEM (channel)); } gegl_rectangle_align_to_buffer (&aligned_rect, &rect, buffer, GEGL_RECTANGLE_ALIGNMENT_SUPERSET); 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 = pika_item_get_width (PIKA_ITEM (channel)); channel->y2 = pika_item_get_height (PIKA_ITEM (channel)); pika_drawable_update (PIKA_DRAWABLE (channel), rect.x, rect.y, rect.width, rect.height); } static void pika_channel_real_all (PikaChannel *channel, gboolean push_undo) { GeglColor *color; if (push_undo) pika_channel_push_undo (channel, PIKA_CHANNEL_GET_CLASS (channel)->all_desc); /* clear the channel */ color = gegl_color_new ("#fff"); gegl_buffer_set_color (pika_drawable_get_buffer (PIKA_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 = pika_item_get_width (PIKA_ITEM (channel)); channel->y2 = pika_item_get_height (PIKA_ITEM (channel)); pika_drawable_update (PIKA_DRAWABLE (channel), 0, 0, -1, -1); } static void pika_channel_real_invert (PikaChannel *channel, gboolean push_undo) { PikaDrawable *drawable = PIKA_DRAWABLE (channel); if (push_undo) pika_channel_push_undo (channel, PIKA_CHANNEL_GET_CLASS (channel)->invert_desc); if (channel->bounds_known && channel->empty) { pika_channel_all (channel, FALSE); } else { pika_gegl_apply_invert_linear (pika_drawable_get_buffer (drawable), NULL, NULL, pika_drawable_get_buffer (drawable)); pika_drawable_update (PIKA_DRAWABLE (channel), 0, 0, -1, -1); } } static void pika_channel_real_border (PikaChannel *channel, gint radius_x, gint radius_y, PikaChannelBorderStyle 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. */ pika_channel_clear (channel, PIKA_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 (! pika_item_bounds (PIKA_ITEM (channel), &x1, &y1, &x2, &y2)) return; x2 += x1; y2 += y1; if (pika_channel_is_empty (channel)) return; if (x1 - radius_x < 0) x1 = 0; else x1 -= radius_x; if (x2 + radius_x > pika_item_get_width (PIKA_ITEM (channel))) x2 = pika_item_get_width (PIKA_ITEM (channel)); else x2 += radius_x; if (y1 - radius_y < 0) y1 = 0; else y1 -= radius_y; if (y2 + radius_y > pika_item_get_height (PIKA_ITEM (channel))) y2 = pika_item_get_height (PIKA_ITEM (channel)); else y2 += radius_y; if (push_undo) pika_channel_push_undo (channel, PIKA_CHANNEL_GET_CLASS (channel)->border_desc); pika_gegl_apply_border (pika_drawable_get_buffer (PIKA_DRAWABLE (channel)), NULL, NULL, pika_drawable_get_buffer (PIKA_DRAWABLE (channel)), GEGL_RECTANGLE (x1, y1, x2 - x1, y2 - y1), radius_x, radius_y, style, edge_lock); pika_drawable_update (PIKA_DRAWABLE (channel), 0, 0, -1, -1); } static void pika_channel_real_grow (PikaChannel *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) { pika_channel_shrink (channel, -radius_x, -radius_y, FALSE, push_undo); return; } if (radius_x < 0 || radius_y < 0) return; if (! pika_item_bounds (PIKA_ITEM (channel), &x1, &y1, &x2, &y2)) return; x2 += x1; y2 += y1; if (pika_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 < pika_item_get_width (PIKA_ITEM (channel))) x2 = x2 + radius_x; else x2 = pika_item_get_width (PIKA_ITEM (channel)); if (y2 + radius_y < pika_item_get_height (PIKA_ITEM (channel))) y2 = y2 + radius_y; else y2 = pika_item_get_height (PIKA_ITEM (channel)); if (push_undo) pika_channel_push_undo (channel, PIKA_CHANNEL_GET_CLASS (channel)->grow_desc); pika_gegl_apply_grow (pika_drawable_get_buffer (PIKA_DRAWABLE (channel)), NULL, NULL, pika_drawable_get_buffer (PIKA_DRAWABLE (channel)), GEGL_RECTANGLE (x1, y1, x2 - x1, y2 - y1), radius_x, radius_y); pika_drawable_update (PIKA_DRAWABLE (channel), 0, 0, -1, -1); } static void pika_channel_real_shrink (PikaChannel *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) { pika_channel_grow (channel, -radius_x, -radius_y, push_undo); return; } if (radius_x < 0 || radius_y < 0) return; if (! pika_item_bounds (PIKA_ITEM (channel), &x1, &y1, &x2, &y2)) return; x2 += x1; y2 += y1; if (pika_channel_is_empty (channel)) return; if (x1 > 0) x1--; if (y1 > 0) y1--; if (x2 < pika_item_get_width (PIKA_ITEM (channel))) x2++; if (y2 < pika_item_get_height (PIKA_ITEM (channel))) y2++; if (push_undo) pika_channel_push_undo (channel, PIKA_CHANNEL_GET_CLASS (channel)->shrink_desc); pika_gegl_apply_shrink (pika_drawable_get_buffer (PIKA_DRAWABLE (channel)), NULL, NULL, pika_drawable_get_buffer (PIKA_DRAWABLE (channel)), GEGL_RECTANGLE (x1, y1, x2 - x1, y2 - y1), radius_x, radius_y, edge_lock); pika_drawable_update (PIKA_DRAWABLE (channel), 0, 0, -1, -1); } static void pika_channel_real_flood (PikaChannel *channel, gboolean push_undo) { gint x, y, width, height; if (! pika_item_bounds (PIKA_ITEM (channel), &x, &y, &width, &height)) return; if (pika_channel_is_empty (channel)) return; if (push_undo) pika_channel_push_undo (channel, PIKA_CHANNEL_GET_CLASS (channel)->flood_desc); pika_gegl_apply_flood (pika_drawable_get_buffer (PIKA_DRAWABLE (channel)), NULL, NULL, pika_drawable_get_buffer (PIKA_DRAWABLE (channel)), GEGL_RECTANGLE (x, y, width, height)); pika_drawable_update (PIKA_DRAWABLE (channel), x, y, width, height); } static void pika_channel_buffer_changed (GeglBuffer *buffer, const GeglRectangle *rect, PikaChannel *channel) { pika_drawable_invalidate_boundary (PIKA_DRAWABLE (channel)); } /* public functions */ PikaChannel * pika_channel_new (PikaImage *image, gint width, gint height, const gchar *name, const PikaRGB *color) { PikaChannel *channel; g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); channel = PIKA_CHANNEL (pika_drawable_new (PIKA_TYPE_CHANNEL, image, name, 0, 0, width, height, pika_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; } PikaChannel * pika_channel_new_from_buffer (PikaImage *image, GeglBuffer *buffer, const gchar *name, const PikaRGB *color) { PikaChannel *channel; GeglBuffer *dest; g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); g_return_val_if_fail (GEGL_IS_BUFFER (buffer), NULL); channel = pika_channel_new (image, gegl_buffer_get_width (buffer), gegl_buffer_get_height (buffer), name, color); dest = pika_drawable_get_buffer (PIKA_DRAWABLE (channel)); pika_gegl_buffer_copy (buffer, NULL, GEGL_ABYSS_NONE, dest, NULL); return channel; } PikaChannel * pika_channel_new_from_alpha (PikaImage *image, PikaDrawable *drawable, const gchar *name, const PikaRGB *color) { PikaChannel *channel; GeglBuffer *dest_buffer; gint width; gint height; g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); g_return_val_if_fail (PIKA_IS_DRAWABLE (drawable), NULL); g_return_val_if_fail (pika_drawable_has_alpha (drawable), NULL); width = pika_item_get_width (PIKA_ITEM (drawable)); height = pika_item_get_height (PIKA_ITEM (drawable)); channel = pika_channel_new (image, width, height, name, color); pika_channel_clear (channel, NULL, FALSE); dest_buffer = pika_drawable_get_buffer (PIKA_DRAWABLE (channel)); gegl_buffer_set_format (dest_buffer, pika_drawable_get_component_format (drawable, PIKA_CHANNEL_ALPHA)); pika_gegl_buffer_copy (pika_drawable_get_buffer (drawable), NULL, GEGL_ABYSS_NONE, dest_buffer, NULL); gegl_buffer_set_format (dest_buffer, NULL); return channel; } PikaChannel * pika_channel_new_from_component (PikaImage *image, PikaChannelType type, const gchar *name, const PikaRGB *color) { PikaChannel *channel; GeglBuffer *src_buffer; GeglBuffer *dest_buffer; gint width; gint height; const Babl *format; g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); format = pika_image_get_component_format (image, type); g_return_val_if_fail (format != NULL, NULL); pika_pickable_flush (PIKA_PICKABLE (image)); src_buffer = pika_pickable_get_buffer (PIKA_PICKABLE (image)); width = gegl_buffer_get_width (src_buffer); height = gegl_buffer_get_height (src_buffer); channel = pika_channel_new (image, width, height, name, color); dest_buffer = pika_drawable_get_buffer (PIKA_DRAWABLE (channel)); gegl_buffer_set_format (dest_buffer, format); pika_gegl_buffer_copy (src_buffer, NULL, GEGL_ABYSS_NONE, dest_buffer, NULL); gegl_buffer_set_format (dest_buffer, NULL); return channel; } PikaChannel * pika_channel_get_parent (PikaChannel *channel) { g_return_val_if_fail (PIKA_IS_CHANNEL (channel), NULL); return PIKA_CHANNEL (pika_viewable_get_parent (PIKA_VIEWABLE (channel))); } void pika_channel_set_color (PikaChannel *channel, const PikaRGB *color, gboolean push_undo) { g_return_if_fail (PIKA_IS_CHANNEL (channel)); g_return_if_fail (color != NULL); if (pika_rgba_distance (&channel->color, color) > RGBA_EPSILON) { if (push_undo && pika_item_is_attached (PIKA_ITEM (channel))) { PikaImage *image = pika_item_get_image (PIKA_ITEM (channel)); pika_image_undo_push_channel_color (image, C_("undo-type", "Set Channel Color"), channel); } channel->color = *color; if (pika_filter_peek_node (PIKA_FILTER (channel))) { pika_gegl_node_set_color (channel->color_node, &channel->color, NULL); } pika_drawable_update (PIKA_DRAWABLE (channel), 0, 0, -1, -1); g_signal_emit (channel, channel_signals[COLOR_CHANGED], 0); } } void pika_channel_get_color (PikaChannel *channel, PikaRGB *color) { g_return_if_fail (PIKA_IS_CHANNEL (channel)); g_return_if_fail (color != NULL); *color = channel->color; } gdouble pika_channel_get_opacity (PikaChannel *channel) { g_return_val_if_fail (PIKA_IS_CHANNEL (channel), PIKA_OPACITY_TRANSPARENT); return channel->color.a; } void pika_channel_set_opacity (PikaChannel *channel, gdouble opacity, gboolean push_undo) { g_return_if_fail (PIKA_IS_CHANNEL (channel)); opacity = CLAMP (opacity, PIKA_OPACITY_TRANSPARENT, PIKA_OPACITY_OPAQUE); if (channel->color.a != opacity) { if (push_undo && pika_item_is_attached (PIKA_ITEM (channel))) { PikaImage *image = pika_item_get_image (PIKA_ITEM (channel)); pika_image_undo_push_channel_color (image, C_("undo-type", "Set Channel Opacity"), channel); } channel->color.a = opacity; if (pika_filter_peek_node (PIKA_FILTER (channel))) { pika_gegl_node_set_color (channel->color_node, &channel->color, NULL); } pika_drawable_update (PIKA_DRAWABLE (channel), 0, 0, -1, -1); g_signal_emit (channel, channel_signals[COLOR_CHANGED], 0); } } gboolean pika_channel_get_show_masked (PikaChannel *channel) { g_return_val_if_fail (PIKA_IS_CHANNEL (channel), FALSE); return channel->show_masked; } void pika_channel_set_show_masked (PikaChannel *channel, gboolean show_masked) { g_return_if_fail (PIKA_IS_CHANNEL (channel)); if (show_masked != channel->show_masked) { channel->show_masked = show_masked ? TRUE : FALSE; if (channel->invert_node) { GeglNode *source; source = pika_drawable_get_source_node (PIKA_DRAWABLE (channel)); if (channel->show_masked) { gegl_node_link (source, channel->invert_node); gegl_node_connect (channel->invert_node, "output", channel->mask_node, "aux"); } else { gegl_node_disconnect (channel->invert_node, "input"); gegl_node_connect (source, "output", channel->mask_node, "aux"); } } pika_drawable_update (PIKA_DRAWABLE (channel), 0, 0, -1, -1); } } void pika_channel_push_undo (PikaChannel *channel, const gchar *undo_desc) { g_return_if_fail (PIKA_IS_CHANNEL (channel)); g_return_if_fail (pika_item_is_attached (PIKA_ITEM (channel))); pika_image_undo_push_mask (pika_item_get_image (PIKA_ITEM (channel)), undo_desc, channel); } /******************************/ /* selection mask functions */ /******************************/ PikaChannel * pika_channel_new_mask (PikaImage *image, gint width, gint height) { PikaChannel *channel; g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); channel = PIKA_CHANNEL (pika_drawable_new (PIKA_TYPE_CHANNEL, image, _("Selection Mask"), 0, 0, width, height, pika_image_get_mask_format (image))); channel->show_masked = TRUE; channel->x2 = width; channel->y2 = height; gegl_buffer_clear (pika_drawable_get_buffer (PIKA_DRAWABLE (channel)), NULL); return channel; } gboolean pika_channel_boundary (PikaChannel *channel, const PikaBoundSeg **segs_in, const PikaBoundSeg **segs_out, gint *num_segs_in, gint *num_segs_out, gint x1, gint y1, gint x2, gint y2) { g_return_val_if_fail (PIKA_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 PIKA_CHANNEL_GET_CLASS (channel)->boundary (channel, segs_in, segs_out, num_segs_in, num_segs_out, x1, y1, x2, y2); } gboolean pika_channel_is_empty (PikaChannel *channel) { g_return_val_if_fail (PIKA_IS_CHANNEL (channel), TRUE); return PIKA_CHANNEL_GET_CLASS (channel)->is_empty (channel); } gboolean pika_channel_is_full (PikaChannel *channel) { g_return_val_if_fail (PIKA_IS_CHANNEL (channel), FALSE); return PIKA_CHANNEL_GET_CLASS (channel)->is_full (channel); } void pika_channel_feather (PikaChannel *channel, gdouble radius_x, gdouble radius_y, gboolean edge_lock, gboolean push_undo) { g_return_if_fail (PIKA_IS_CHANNEL (channel)); if (! pika_item_is_attached (PIKA_ITEM (channel))) push_undo = FALSE; PIKA_CHANNEL_GET_CLASS (channel)->feather (channel, radius_x, radius_y, edge_lock, push_undo); } void pika_channel_sharpen (PikaChannel *channel, gboolean push_undo) { g_return_if_fail (PIKA_IS_CHANNEL (channel)); if (! pika_item_is_attached (PIKA_ITEM (channel))) push_undo = FALSE; PIKA_CHANNEL_GET_CLASS (channel)->sharpen (channel, push_undo); } void pika_channel_clear (PikaChannel *channel, const gchar *undo_desc, gboolean push_undo) { g_return_if_fail (PIKA_IS_CHANNEL (channel)); if (! pika_item_is_attached (PIKA_ITEM (channel))) push_undo = FALSE; PIKA_CHANNEL_GET_CLASS (channel)->clear (channel, undo_desc, push_undo); } void pika_channel_all (PikaChannel *channel, gboolean push_undo) { g_return_if_fail (PIKA_IS_CHANNEL (channel)); if (! pika_item_is_attached (PIKA_ITEM (channel))) push_undo = FALSE; PIKA_CHANNEL_GET_CLASS (channel)->all (channel, push_undo); } void pika_channel_invert (PikaChannel *channel, gboolean push_undo) { g_return_if_fail (PIKA_IS_CHANNEL (channel)); if (! pika_item_is_attached (PIKA_ITEM (channel))) push_undo = FALSE; PIKA_CHANNEL_GET_CLASS (channel)->invert (channel, push_undo); } void pika_channel_border (PikaChannel *channel, gint radius_x, gint radius_y, PikaChannelBorderStyle style, gboolean edge_lock, gboolean push_undo) { g_return_if_fail (PIKA_IS_CHANNEL (channel)); if (! pika_item_is_attached (PIKA_ITEM (channel))) push_undo = FALSE; PIKA_CHANNEL_GET_CLASS (channel)->border (channel, radius_x, radius_y, style, edge_lock, push_undo); } void pika_channel_grow (PikaChannel *channel, gint radius_x, gint radius_y, gboolean push_undo) { g_return_if_fail (PIKA_IS_CHANNEL (channel)); if (! pika_item_is_attached (PIKA_ITEM (channel))) push_undo = FALSE; PIKA_CHANNEL_GET_CLASS (channel)->grow (channel, radius_x, radius_y, push_undo); } void pika_channel_shrink (PikaChannel *channel, gint radius_x, gint radius_y, gboolean edge_lock, gboolean push_undo) { g_return_if_fail (PIKA_IS_CHANNEL (channel)); if (! pika_item_is_attached (PIKA_ITEM (channel))) push_undo = FALSE; PIKA_CHANNEL_GET_CLASS (channel)->shrink (channel, radius_x, radius_y, edge_lock, push_undo); } void pika_channel_flood (PikaChannel *channel, gboolean push_undo) { g_return_if_fail (PIKA_IS_CHANNEL (channel)); if (! pika_item_is_attached (PIKA_ITEM (channel))) push_undo = FALSE; PIKA_CHANNEL_GET_CLASS (channel)->flood (channel, push_undo); }