/* 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 "core-types.h" #include "gegl/pika-gegl-apply-operation.h" #include "gegl/pika-gegl-loops.h" #include "gegl/pika-gegl-mask-combine.h" #include "pika.h" #include "pikachannel.h" #include "pikachannel-select.h" #include "pikachannel-combine.h" #include "pikacontainer.h" #include "pikaimage.h" #include "pikaimage-new.h" #include "pikapickable.h" #include "pikapickable-contiguous-region.h" #include "pikascanconvert.h" #include "vectors/pikastroke.h" #include "vectors/pikavectors.h" #include "pika-intl.h" /* basic selection functions */ void pika_channel_select_rectangle (PikaChannel *channel, gint x, gint y, gint w, gint h, PikaChannelOps op, gboolean feather, gdouble feather_radius_x, gdouble feather_radius_y, gboolean push_undo) { g_return_if_fail (PIKA_IS_CHANNEL (channel)); g_return_if_fail (pika_item_is_attached (PIKA_ITEM (channel))); if (push_undo) pika_channel_push_undo (channel, C_("undo-type", "Rectangle Select")); /* if feathering for rect, make a new mask with the * rectangle and feather that with the old mask */ if (feather) { PikaItem *item = PIKA_ITEM (channel); GeglBuffer *add_on; add_on = gegl_buffer_new (GEGL_RECTANGLE (0, 0, pika_item_get_width (item), pika_item_get_height (item)), babl_format ("Y float")); pika_gegl_mask_combine_rect (add_on, PIKA_CHANNEL_OP_REPLACE, x, y, w, h); pika_gegl_apply_feather (add_on, NULL, NULL, add_on, NULL, feather_radius_x, feather_radius_y, TRUE); pika_channel_combine_buffer (channel, add_on, op, 0, 0); g_object_unref (add_on); } else { pika_channel_combine_rect (channel, op, x, y, w, h); } } void pika_channel_select_ellipse (PikaChannel *channel, gint x, gint y, gint w, gint h, PikaChannelOps op, gboolean antialias, gboolean feather, gdouble feather_radius_x, gdouble feather_radius_y, gboolean push_undo) { g_return_if_fail (PIKA_IS_CHANNEL (channel)); g_return_if_fail (pika_item_is_attached (PIKA_ITEM (channel))); if (push_undo) pika_channel_push_undo (channel, C_("undo-type", "Ellipse Select")); /* if feathering for rect, make a new mask with the * rectangle and feather that with the old mask */ if (feather) { PikaItem *item = PIKA_ITEM (channel); GeglBuffer *add_on; add_on = gegl_buffer_new (GEGL_RECTANGLE (0, 0, pika_item_get_width (item), pika_item_get_height (item)), babl_format ("Y float")); pika_gegl_mask_combine_ellipse (add_on, PIKA_CHANNEL_OP_REPLACE, x, y, w, h, antialias); pika_gegl_apply_feather (add_on, NULL, NULL, add_on, NULL, feather_radius_x, feather_radius_y, TRUE); pika_channel_combine_buffer (channel, add_on, op, 0, 0); g_object_unref (add_on); } else { pika_channel_combine_ellipse (channel, op, x, y, w, h, antialias); } } void pika_channel_select_round_rect (PikaChannel *channel, gint x, gint y, gint w, gint h, gdouble corner_radius_x, gdouble corner_radius_y, PikaChannelOps op, gboolean antialias, gboolean feather, gdouble feather_radius_x, gdouble feather_radius_y, gboolean push_undo) { g_return_if_fail (PIKA_IS_CHANNEL (channel)); g_return_if_fail (pika_item_is_attached (PIKA_ITEM (channel))); if (push_undo) pika_channel_push_undo (channel, C_("undo-type", "Rounded Rectangle Select")); /* if feathering for rect, make a new mask with the * rectangle and feather that with the old mask */ if (feather) { PikaItem *item = PIKA_ITEM (channel); GeglBuffer *add_on; add_on = gegl_buffer_new (GEGL_RECTANGLE (0, 0, pika_item_get_width (item), pika_item_get_height (item)), babl_format ("Y float")); pika_gegl_mask_combine_ellipse_rect (add_on, PIKA_CHANNEL_OP_REPLACE, x, y, w, h, corner_radius_x, corner_radius_y, antialias); pika_gegl_apply_feather (add_on, NULL, NULL, add_on, NULL, feather_radius_x, feather_radius_y, TRUE); pika_channel_combine_buffer (channel, add_on, op, 0, 0); g_object_unref (add_on); } else { pika_channel_combine_ellipse_rect (channel, op, x, y, w, h, corner_radius_x, corner_radius_y, antialias); } } /* select by PikaScanConvert functions */ void pika_channel_select_scan_convert (PikaChannel *channel, const gchar *undo_desc, PikaScanConvert *scan_convert, gint offset_x, gint offset_y, PikaChannelOps op, gboolean antialias, gboolean feather, gdouble feather_radius_x, gdouble feather_radius_y, gboolean push_undo) { PikaItem *item; GeglBuffer *add_on; g_return_if_fail (PIKA_IS_CHANNEL (channel)); g_return_if_fail (pika_item_is_attached (PIKA_ITEM (channel))); g_return_if_fail (undo_desc != NULL); g_return_if_fail (scan_convert != NULL); if (push_undo) pika_channel_push_undo (channel, undo_desc); item = PIKA_ITEM (channel); add_on = gegl_buffer_new (GEGL_RECTANGLE (0, 0, pika_item_get_width (item), pika_item_get_height (item)), babl_format ("Y float")); pika_scan_convert_render (scan_convert, add_on, offset_x, offset_y, antialias); if (feather) pika_gegl_apply_feather (add_on, NULL, NULL, add_on, NULL, feather_radius_x, feather_radius_y, TRUE); pika_channel_combine_buffer (channel, add_on, op, 0, 0); g_object_unref (add_on); } void pika_channel_select_polygon (PikaChannel *channel, const gchar *undo_desc, gint n_points, const PikaVector2 *points, PikaChannelOps op, gboolean antialias, gboolean feather, gdouble feather_radius_x, gdouble feather_radius_y, gboolean push_undo) { PikaScanConvert *scan_convert; g_return_if_fail (PIKA_IS_CHANNEL (channel)); g_return_if_fail (pika_item_is_attached (PIKA_ITEM (channel))); g_return_if_fail (undo_desc != NULL); scan_convert = pika_scan_convert_new (); pika_scan_convert_add_polyline (scan_convert, n_points, points, TRUE); pika_channel_select_scan_convert (channel, undo_desc, scan_convert, 0, 0, op, antialias, feather, feather_radius_x, feather_radius_y, push_undo); pika_scan_convert_free (scan_convert); } void pika_channel_select_vectors (PikaChannel *channel, const gchar *undo_desc, PikaVectors *vectors, PikaChannelOps op, gboolean antialias, gboolean feather, gdouble feather_radius_x, gdouble feather_radius_y, gboolean push_undo) { const PikaBezierDesc *bezier; g_return_if_fail (PIKA_IS_CHANNEL (channel)); g_return_if_fail (pika_item_is_attached (PIKA_ITEM (channel))); g_return_if_fail (undo_desc != NULL); g_return_if_fail (PIKA_IS_VECTORS (vectors)); bezier = pika_vectors_get_bezier (vectors); if (bezier && bezier->num_data > 4) { PikaScanConvert *scan_convert; scan_convert = pika_scan_convert_new (); pika_scan_convert_add_bezier (scan_convert, bezier); pika_channel_select_scan_convert (channel, undo_desc, scan_convert, 0, 0, op, antialias, feather, feather_radius_x, feather_radius_y, push_undo); pika_scan_convert_free (scan_convert); } } /* select by PikaChannel functions */ void pika_channel_select_buffer (PikaChannel *channel, const gchar *undo_desc, GeglBuffer *add_on, gint offset_x, gint offset_y, PikaChannelOps op, gboolean feather, gdouble feather_radius_x, gdouble feather_radius_y) { g_return_if_fail (PIKA_IS_CHANNEL (channel)); g_return_if_fail (pika_item_is_attached (PIKA_ITEM (channel))); g_return_if_fail (undo_desc != NULL); g_return_if_fail (GEGL_IS_BUFFER (add_on)); pika_channel_push_undo (channel, undo_desc); if (feather) { PikaItem *item = PIKA_ITEM (channel); GeglBuffer *add_on2; add_on2 = gegl_buffer_new (GEGL_RECTANGLE (0, 0, pika_item_get_width (item), pika_item_get_height (item)), babl_format ("Y float")); pika_gegl_mask_combine_buffer (add_on2, add_on, PIKA_CHANNEL_OP_REPLACE, offset_x, offset_y); pika_gegl_apply_feather (add_on2, NULL, NULL, add_on2, NULL, feather_radius_x, feather_radius_y, TRUE); pika_channel_combine_buffer (channel, add_on2, op, 0, 0); g_object_unref (add_on2); } else { pika_channel_combine_buffer (channel, add_on, op, offset_x, offset_y); } } void pika_channel_select_channel (PikaChannel *channel, const gchar *undo_desc, PikaChannel *add_on, gint offset_x, gint offset_y, PikaChannelOps op, gboolean feather, gdouble feather_radius_x, gdouble feather_radius_y) { g_return_if_fail (PIKA_IS_CHANNEL (channel)); g_return_if_fail (pika_item_is_attached (PIKA_ITEM (channel))); g_return_if_fail (undo_desc != NULL); g_return_if_fail (PIKA_IS_CHANNEL (add_on)); pika_channel_select_buffer (channel, undo_desc, pika_drawable_get_buffer (PIKA_DRAWABLE (add_on)), offset_x, offset_y, op, feather, feather_radius_x, feather_radius_y); } void pika_channel_select_alpha (PikaChannel *channel, PikaDrawable *drawable, PikaChannelOps op, gboolean feather, gdouble feather_radius_x, gdouble feather_radius_y) { PikaItem *item; PikaChannel *add_on; gint off_x, off_y; const gchar *undo_desc = NULL; g_return_if_fail (PIKA_IS_CHANNEL (channel)); g_return_if_fail (pika_item_is_attached (PIKA_ITEM (channel))); g_return_if_fail (PIKA_IS_DRAWABLE (drawable)); item = PIKA_ITEM (channel); if (pika_drawable_has_alpha (drawable)) { add_on = pika_channel_new_from_alpha (pika_item_get_image (item), drawable, NULL, NULL); } else { /* no alpha is equivalent to completely opaque alpha, * so simply select the whole layer's extents. --mitch */ add_on = pika_channel_new_mask (pika_item_get_image (item), pika_item_get_width (PIKA_ITEM (drawable)), pika_item_get_height (PIKA_ITEM (drawable))); pika_channel_all (add_on, FALSE); } switch (op) { case PIKA_CHANNEL_OP_ADD: undo_desc = C_("undo-type", "Add Alpha to Selection"); break; case PIKA_CHANNEL_OP_SUBTRACT: undo_desc = C_("undo-type", "Subtract Alpha from Selection"); break; case PIKA_CHANNEL_OP_REPLACE: undo_desc = C_("undo-type", "Alpha to Selection"); break; case PIKA_CHANNEL_OP_INTERSECT: undo_desc = C_("undo-type", "Intersect Alpha with Selection"); break; } pika_item_get_offset (PIKA_ITEM (drawable), &off_x, &off_y); pika_channel_select_channel (channel, undo_desc, add_on, off_x, off_y, op, feather, feather_radius_x, feather_radius_y); g_object_unref (add_on); } void pika_channel_select_component (PikaChannel *channel, PikaChannelType component, PikaChannelOps op, gboolean feather, gdouble feather_radius_x, gdouble feather_radius_y) { PikaItem *item; PikaChannel *add_on; const gchar *desc; gchar *undo_desc; g_return_if_fail (PIKA_IS_CHANNEL (channel)); g_return_if_fail (pika_item_is_attached (PIKA_ITEM (channel))); item = PIKA_ITEM (channel); add_on = pika_channel_new_from_component (pika_item_get_image (item), component, NULL, NULL); if (feather) pika_channel_feather (add_on, feather_radius_x, feather_radius_y, TRUE, FALSE /* no undo */); pika_enum_get_value (PIKA_TYPE_CHANNEL_TYPE, component, NULL, NULL, &desc, NULL); undo_desc = g_strdup_printf (C_("undo-type", "%s Channel to Selection"), desc); pika_channel_select_channel (channel, undo_desc, add_on, 0, 0, op, FALSE, 0.0, 0.0); g_free (undo_desc); g_object_unref (add_on); } void pika_channel_select_fuzzy (PikaChannel *channel, PikaDrawable *drawable, gboolean sample_merged, gint x, gint y, gfloat threshold, gboolean select_transparent, PikaSelectCriterion select_criterion, gboolean diagonal_neighbors, PikaChannelOps op, gboolean antialias, gboolean feather, gdouble feather_radius_x, gdouble feather_radius_y) { PikaPickable *pickable; GeglBuffer *add_on; gint add_on_x = 0; gint add_on_y = 0; g_return_if_fail (PIKA_IS_CHANNEL (channel)); g_return_if_fail (pika_item_is_attached (PIKA_ITEM (channel))); g_return_if_fail (PIKA_IS_DRAWABLE (drawable)); if (sample_merged) pickable = PIKA_PICKABLE (pika_item_get_image (PIKA_ITEM (drawable))); else pickable = PIKA_PICKABLE (drawable); add_on = pika_pickable_contiguous_region_by_seed (pickable, antialias, threshold, select_transparent, select_criterion, diagonal_neighbors, x, y); if (! sample_merged) pika_item_get_offset (PIKA_ITEM (drawable), &add_on_x, &add_on_y); pika_channel_select_buffer (channel, C_("undo-type", "Fuzzy Select"), add_on, add_on_x, add_on_y, op, feather, feather_radius_x, feather_radius_y); g_object_unref (add_on); } void pika_channel_select_by_color (PikaChannel *channel, GList *drawables, gboolean sample_merged, const PikaRGB *color, gfloat threshold, gboolean select_transparent, PikaSelectCriterion select_criterion, PikaChannelOps op, gboolean antialias, gboolean feather, gdouble feather_radius_x, gdouble feather_radius_y) { PikaPickable *pickable; GeglBuffer *add_on; PikaImage *image; PikaImage *sel_image = NULL; gint add_on_x = 0; gint add_on_y = 0; g_return_if_fail (PIKA_IS_CHANNEL (channel)); g_return_if_fail (pika_item_is_attached (PIKA_ITEM (channel))); g_return_if_fail (drawables != NULL); g_return_if_fail (color != NULL); image = pika_item_get_image (drawables->data); if (sample_merged) { pickable = PIKA_PICKABLE (image); } else { if (g_list_length (drawables) == 1) { pickable = PIKA_PICKABLE (drawables->data); } else { sel_image = pika_image_new_from_drawables (image->pika, drawables, FALSE, FALSE); pika_container_remove (image->pika->images, PIKA_OBJECT (sel_image)); pickable = PIKA_PICKABLE (sel_image); pika_pickable_flush (pickable); } } add_on = pika_pickable_contiguous_region_by_color (pickable, antialias, threshold, select_transparent, select_criterion, color); if (! sample_merged && ! sel_image) pika_item_get_offset (PIKA_ITEM (drawables->data), &add_on_x, &add_on_y); pika_channel_select_buffer (channel, C_("undo-type", "Select by Color"), add_on, add_on_x, add_on_y, op, feather, feather_radius_x, feather_radius_y); g_object_unref (add_on); if (sel_image) g_object_unref (sel_image); } void pika_channel_select_by_index (PikaChannel *channel, PikaDrawable *drawable, gint index, PikaChannelOps op, gboolean feather, gdouble feather_radius_x, gdouble feather_radius_y) { GeglBuffer *add_on; gint add_on_x = 0; gint add_on_y = 0; g_return_if_fail (PIKA_IS_CHANNEL (channel)); g_return_if_fail (pika_item_is_attached (PIKA_ITEM (channel))); g_return_if_fail (PIKA_IS_DRAWABLE (drawable)); g_return_if_fail (pika_drawable_is_indexed (drawable)); add_on = gegl_buffer_new (GEGL_RECTANGLE (0, 0, pika_item_get_width (PIKA_ITEM (drawable)), pika_item_get_height (PIKA_ITEM (drawable))), babl_format ("Y float")); pika_gegl_index_to_mask (pika_drawable_get_buffer (drawable), NULL, pika_drawable_get_format_without_alpha (drawable), add_on, NULL, index); pika_item_get_offset (PIKA_ITEM (drawable), &add_on_x, &add_on_y); pika_channel_select_buffer (channel, C_("undo-type", "Select by Indexed Color"), add_on, add_on_x, add_on_y, op, feather, feather_radius_x, feather_radius_y); g_object_unref (add_on); }