/* 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 * * pikadrawable-filters.c * * 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 "core-types.h" #include "gegl/pikaapplicator.h" #include "gegl/pika-gegl-apply-operation.h" #include "gegl/pika-gegl-loops.h" #include "pika.h" #include "pika-utils.h" #include "pikadrawable.h" #include "pikadrawable-filters.h" #include "pikadrawable-private.h" #include "pikafilter.h" #include "pikafilterstack.h" #include "pikaimage.h" #include "pikaimage-undo.h" #include "pikalayer.h" #include "pikaprogress.h" #include "pikaprojection.h" PikaContainer * pika_drawable_get_filters (PikaDrawable *drawable) { g_return_val_if_fail (PIKA_IS_DRAWABLE (drawable), NULL); return drawable->private->filter_stack; } gboolean pika_drawable_has_filters (PikaDrawable *drawable) { GList *list; g_return_val_if_fail (PIKA_IS_DRAWABLE (drawable), FALSE); for (list = PIKA_LIST (drawable->private->filter_stack)->queue->head; list; list = g_list_next (list)) { PikaFilter *filter = list->data; if (pika_filter_get_active (filter)) return TRUE; } return FALSE; } void pika_drawable_add_filter (PikaDrawable *drawable, PikaFilter *filter) { g_return_if_fail (PIKA_IS_DRAWABLE (drawable)); g_return_if_fail (PIKA_IS_FILTER (filter)); g_return_if_fail (pika_drawable_has_filter (drawable, filter) == FALSE); pika_container_add (drawable->private->filter_stack, PIKA_OBJECT (filter)); } void pika_drawable_remove_filter (PikaDrawable *drawable, PikaFilter *filter) { g_return_if_fail (PIKA_IS_DRAWABLE (drawable)); g_return_if_fail (PIKA_IS_FILTER (filter)); g_return_if_fail (pika_drawable_has_filter (drawable, filter) == TRUE); pika_container_remove (drawable->private->filter_stack, PIKA_OBJECT (filter)); } gboolean pika_drawable_has_filter (PikaDrawable *drawable, PikaFilter *filter) { g_return_val_if_fail (PIKA_IS_DRAWABLE (drawable), FALSE); g_return_val_if_fail (PIKA_IS_FILTER (filter), FALSE); return pika_container_have (drawable->private->filter_stack, PIKA_OBJECT (filter)); } gboolean pika_drawable_merge_filter (PikaDrawable *drawable, PikaFilter *filter, PikaProgress *progress, const gchar *undo_desc, const Babl *format, gboolean clip, gboolean cancellable, gboolean update) { PikaImage *image; PikaApplicator *applicator; gboolean applicator_cache = FALSE; const Babl *applicator_output_format = NULL; GeglBuffer *buffer = NULL; GeglBuffer *dest_buffer; GeglBuffer *undo_buffer = NULL; GeglRectangle undo_rect; GeglBuffer *cache = NULL; GeglRectangle *rects = NULL; gint n_rects = 0; GeglRectangle rect; gboolean success = TRUE; g_return_val_if_fail (PIKA_IS_DRAWABLE (drawable), FALSE); g_return_val_if_fail (PIKA_IS_FILTER (filter), FALSE); g_return_val_if_fail (progress == NULL || PIKA_IS_PROGRESS (progress), FALSE); image = pika_item_get_image (PIKA_ITEM (drawable)); applicator = pika_filter_get_applicator (filter); dest_buffer = pika_drawable_get_buffer (drawable); if (! format) format = pika_drawable_get_format (drawable); rect = gegl_node_get_bounding_box (pika_filter_get_node (filter)); if (! clip && gegl_rectangle_equal (&rect, gegl_buffer_get_extent (dest_buffer))) { clip = TRUE; } if (clip) { if (! pika_item_mask_intersect (PIKA_ITEM (drawable), &rect.x, &rect.y, &rect.width, &rect.height)) { return TRUE; } if (format != pika_drawable_get_format (drawable)) { buffer = gegl_buffer_new (gegl_buffer_get_extent (dest_buffer), format); dest_buffer = buffer; } } else { buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0, rect.width, rect.height), format); dest_buffer = g_object_new (GEGL_TYPE_BUFFER, "source", buffer, "shift-x", -rect.x, "shift-y", -rect.y, NULL); } if (applicator) { const GeglRectangle *crop_rect; crop_rect = pika_applicator_get_crop (applicator); if (crop_rect && ! gegl_rectangle_intersect (&rect, &rect, crop_rect)) return TRUE; /* the cache and its valid rectangles are the region that * has already been processed by this applicator. */ cache = pika_applicator_get_cache_buffer (applicator, &rects, &n_rects); /* skip the cache and output-format conversion while processing * the remaining area, so that the result is written directly to * the drawable's buffer. */ applicator_cache = pika_applicator_get_cache (applicator); applicator_output_format = pika_applicator_get_output_format (applicator); pika_applicator_set_cache (applicator, FALSE); if (applicator_output_format == format) pika_applicator_set_output_format (applicator, NULL); } if (! buffer) { gegl_rectangle_align_to_buffer ( &undo_rect, &rect, pika_drawable_get_buffer (drawable), GEGL_RECTANGLE_ALIGNMENT_SUPERSET); undo_buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0, undo_rect.width, undo_rect.height), pika_drawable_get_format (drawable)); pika_gegl_buffer_copy (pika_drawable_get_buffer (drawable), &undo_rect, GEGL_ABYSS_NONE, undo_buffer, GEGL_RECTANGLE (0, 0, 0, 0)); } pika_projection_stop_rendering (pika_image_get_projection (image)); /* make sure we have a source node - this connects the filter stack to the * underlying source node */ (void) pika_drawable_get_source_node (drawable); if (pika_gegl_apply_cached_operation (pika_drawable_get_buffer (drawable), progress, undo_desc, pika_filter_get_node (filter), FALSE, dest_buffer, &rect, FALSE, cache, rects, n_rects, cancellable)) { /* finished successfully */ if (clip) { if (buffer) { pika_drawable_set_buffer_full (drawable, TRUE, undo_desc, buffer, NULL, FALSE); } else { pika_drawable_push_undo (drawable, undo_desc, undo_buffer, undo_rect.x, undo_rect.y, undo_rect.width, undo_rect.height); } } else { PikaLayerMask *mask = NULL; gint offset_x; gint offset_y; pika_item_get_offset (PIKA_ITEM (drawable), &offset_x, &offset_y); if (PIKA_IS_LAYER (drawable)) mask = pika_layer_get_mask (PIKA_LAYER (drawable)); if (mask) { pika_image_undo_group_start (image, PIKA_UNDO_GROUP_DRAWABLE_MOD, undo_desc); } pika_drawable_set_buffer_full ( drawable, TRUE, undo_desc, buffer, GEGL_RECTANGLE (offset_x + rect.x, offset_y + rect.y, 0, 0), FALSE); if (mask) { pika_item_resize (PIKA_ITEM (mask), pika_get_default_context (image->pika), PIKA_FILL_TRANSPARENT, rect.width, rect.height, -rect.x, -rect.y); pika_image_undo_group_end (image); } } } else { /* canceled by the user */ if (clip) { pika_gegl_buffer_copy (undo_buffer, GEGL_RECTANGLE (0, 0, undo_rect.width, undo_rect.height), GEGL_ABYSS_NONE, pika_drawable_get_buffer (drawable), &undo_rect); } success = FALSE; } if (clip) { g_clear_object (&undo_buffer); g_clear_object (&buffer); } else { g_object_unref (buffer); g_object_unref (dest_buffer); } if (cache) { g_object_unref (cache); g_free (rects); } if (applicator) { pika_applicator_set_cache (applicator, applicator_cache); pika_applicator_set_output_format (applicator, applicator_output_format); } if (update) { pika_drawable_update (drawable, rect.x, rect.y, rect.width, rect.height); } return success; }