348 lines
11 KiB
C
348 lines
11 KiB
C
/* 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 <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <gdk-pixbuf/gdk-pixbuf.h>
|
|
#include <gegl.h>
|
|
#include <cairo.h>
|
|
|
|
#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;
|
|
}
|