Files
gimp/app/gegl/gimp-gegl-apply-operation.c
Ell 4110f7b7b1 app: use GimpChunkIterator in gimp_gegl_apply_cached_operation()
In gimp_gegl_apply_cached_operation(), replace the use of
GeglProcessor with GimpChunkIterator, so that we use the same
chunking logic as for rendering projections.  This has the
advantage of better chunk alignment to the tile grid and dynamic
chunk sizing, which improve performance.

Use chunking even when there's no progress indication, since it
generally results in better cache locality.
2019-01-12 04:53:00 -05:00

741 lines
26 KiB
C

/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* gimp-apply-operation.c
* Copyright (C) 2012 Øyvind Kolås <pippin@gimp.org>
* Sven Neumann <sven@gimp.org>
* Michael Natterer <mitch@gimp.org>
*
* 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 <cairo.h>
#include <gio/gio.h>
#include <gegl.h>
#include "gimp-gegl-types.h"
#include "core/gimp-transform-utils.h"
#include "core/gimp-utils.h"
#include "core/gimpchunkiterator.h"
#include "core/gimpprogress.h"
#include "gimp-gegl-apply-operation.h"
#include "gimp-gegl-loops.h"
#include "gimp-gegl-nodes.h"
#include "gimp-gegl-utils.h"
void
gimp_gegl_apply_operation (GeglBuffer *src_buffer,
GimpProgress *progress,
const gchar *undo_desc,
GeglNode *operation,
GeglBuffer *dest_buffer,
const GeglRectangle *dest_rect,
gboolean crop_input)
{
gimp_gegl_apply_cached_operation (src_buffer,
progress, undo_desc,
operation,
dest_buffer,
dest_rect,
crop_input,
NULL, NULL, 0,
FALSE);
}
static void
gimp_gegl_apply_operation_cancel (GimpProgress *progress,
gboolean *cancel)
{
*cancel = TRUE;
}
gboolean
gimp_gegl_apply_cached_operation (GeglBuffer *src_buffer,
GimpProgress *progress,
const gchar *undo_desc,
GeglNode *operation,
GeglBuffer *dest_buffer,
const GeglRectangle *dest_rect,
gboolean crop_input,
GeglBuffer *cache,
const GeglRectangle *valid_rects,
gint n_valid_rects,
gboolean cancelable)
{
GeglNode *gegl;
GeglNode *effect;
GeglNode *dest_node;
GeglNode *operation_src_node = NULL;
GimpChunkIterator *iter;
cairo_region_t *region;
GeglRectangle rect = { 0, };
gboolean progress_started = FALSE;
gboolean cancel = FALSE;
gint all_pixels;
gint done_pixels;
g_return_val_if_fail (src_buffer == NULL || GEGL_IS_BUFFER (src_buffer), FALSE);
g_return_val_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress), FALSE);
g_return_val_if_fail (GEGL_IS_NODE (operation), FALSE);
g_return_val_if_fail (GEGL_IS_BUFFER (dest_buffer), FALSE);
g_return_val_if_fail (cache == NULL || GEGL_IS_BUFFER (cache), FALSE);
g_return_val_if_fail (valid_rects == NULL || cache != NULL, FALSE);
g_return_val_if_fail (valid_rects == NULL || n_valid_rects != 0, FALSE);
if (dest_rect)
{
rect = *dest_rect;
}
else
{
rect = *GEGL_RECTANGLE (0, 0, gegl_buffer_get_width (dest_buffer),
gegl_buffer_get_height (dest_buffer));
}
gegl = gegl_node_new ();
if (! gegl_node_get_parent (operation))
gegl_node_add_child (gegl, operation);
effect = operation;
if (src_buffer)
{
GeglNode *src_node;
/* dup() because reading and writing the same buffer doesn't
* work with area ops when working in chunks. See bug #701875.
*/
if (src_buffer == dest_buffer)
src_buffer = gegl_buffer_dup (src_buffer);
else
g_object_ref (src_buffer);
src_node = gegl_node_new_child (gegl,
"operation", "gegl:buffer-source",
"buffer", src_buffer,
NULL);
g_object_unref (src_buffer);
if (crop_input)
{
GeglNode *crop_node;
crop_node = gegl_node_new_child (gegl,
"operation", "gegl:crop",
"x", (gdouble) rect.x,
"y", (gdouble) rect.y,
"width", (gdouble) rect.width,
"height", (gdouble) rect.height,
NULL);
gegl_node_connect_to (src_node, "output",
crop_node, "input");
src_node = crop_node;
}
operation_src_node = gegl_node_get_producer (operation, "input", NULL);
if (! gegl_node_has_pad (operation, "input"))
{
effect = gegl_node_new_child (gegl,
"operation", "gimp:normal",
NULL);
gegl_node_connect_to (operation, "output",
effect, "aux");
}
gegl_node_connect_to (src_node, "output",
effect, "input");
}
dest_node = gegl_node_new_child (gegl,
"operation", "gegl:write-buffer",
"buffer", dest_buffer,
NULL);
gegl_node_connect_to (effect, "output",
dest_node, "input");
if (progress)
{
if (gimp_progress_is_active (progress))
{
if (undo_desc)
gimp_progress_set_text_literal (progress, undo_desc);
progress_started = FALSE;
cancelable = FALSE;
}
else
{
gimp_progress_start (progress, cancelable, "%s", undo_desc);
if (cancelable)
g_signal_connect (progress, "cancel",
G_CALLBACK (gimp_gegl_apply_operation_cancel),
&cancel);
progress_started = TRUE;
}
}
else
{
cancelable = FALSE;
}
gegl_buffer_freeze_changed (dest_buffer);
all_pixels = rect.width * rect.height;
done_pixels = 0;
region = cairo_region_create_rectangle ((cairo_rectangle_int_t *) &rect);
if (cache)
{
gint i;
for (i = 0; i < n_valid_rects; i++)
{
GeglRectangle valid_rect;
if (! gegl_rectangle_intersect (&valid_rect,
&valid_rects[i], &rect))
{
continue;
}
gimp_gegl_buffer_copy (cache, &valid_rect, GEGL_ABYSS_NONE,
dest_buffer, &valid_rect);
cairo_region_subtract_rectangle (region,
(cairo_rectangle_int_t *)
&valid_rect);
done_pixels += valid_rect.width * valid_rect.height;
if (progress)
{
gimp_progress_set_value (progress,
(gdouble) done_pixels /
(gdouble) all_pixels);
}
}
}
iter = gimp_chunk_iterator_new (region);
while (gimp_chunk_iterator_next (iter))
{
GeglRectangle render_rect;
if (progress)
{
while (! cancel && g_main_context_pending (NULL))
g_main_context_iteration (NULL, FALSE);
if (cancel)
break;
}
while (gimp_chunk_iterator_get_rect (iter, &render_rect))
{
gint rect_pixels = render_rect.width * render_rect.height;
gegl_node_blit (dest_node, 1.0, &render_rect, NULL, NULL, 0,
GEGL_BLIT_DEFAULT);
done_pixels += rect_pixels;
}
if (progress)
{
gimp_progress_set_value (progress,
(gdouble) done_pixels /
(gdouble) all_pixels);
}
}
gegl_buffer_thaw_changed (dest_buffer);
g_object_unref (gegl);
if (operation_src_node)
{
gegl_node_connect_to (operation_src_node, "output",
operation, "input");
}
if (progress_started)
{
gimp_progress_end (progress);
if (cancelable)
g_signal_handlers_disconnect_by_func (progress,
gimp_gegl_apply_operation_cancel,
&cancel);
}
return ! cancel;
}
void
gimp_gegl_apply_dither (GeglBuffer *src_buffer,
GimpProgress *progress,
const gchar *undo_desc,
GeglBuffer *dest_buffer,
gint levels,
gint dither_type)
{
GeglNode *node;
g_return_if_fail (GEGL_IS_BUFFER (src_buffer));
g_return_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress));
g_return_if_fail (GEGL_IS_BUFFER (dest_buffer));
levels = CLAMP (levels, 2, 65536);
node = gegl_node_new_child (NULL,
"operation", "gegl:dither",
"red-levels", levels,
"green-levels", levels,
"blue-levels", levels,
"alpha-bits", levels,
"dither-method", dither_type,
NULL);
gimp_gegl_apply_operation (src_buffer, progress, undo_desc,
node, dest_buffer, NULL, FALSE);
g_object_unref (node);
}
void
gimp_gegl_apply_flatten (GeglBuffer *src_buffer,
GimpProgress *progress,
const gchar *undo_desc,
GeglBuffer *dest_buffer,
const GimpRGB *background,
const Babl *space,
GimpLayerColorSpace composite_space)
{
GeglNode *node;
g_return_if_fail (GEGL_IS_BUFFER (src_buffer));
g_return_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress));
g_return_if_fail (GEGL_IS_BUFFER (dest_buffer));
g_return_if_fail (background != NULL);
node = gimp_gegl_create_flatten_node (background, space, composite_space);
gimp_gegl_apply_operation (src_buffer, progress, undo_desc,
node, dest_buffer, NULL, FALSE);
g_object_unref (node);
}
void
gimp_gegl_apply_feather (GeglBuffer *src_buffer,
GimpProgress *progress,
const gchar *undo_desc,
GeglBuffer *dest_buffer,
const GeglRectangle *dest_rect,
gdouble radius_x,
gdouble radius_y)
{
g_return_if_fail (GEGL_IS_BUFFER (src_buffer));
g_return_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress));
g_return_if_fail (GEGL_IS_BUFFER (dest_buffer));
/* 3.5 is completely magic and picked to visually match the old
* gaussian_blur_region() on a crappy laptop display
*/
gimp_gegl_apply_gaussian_blur (src_buffer,
progress, undo_desc,
dest_buffer, dest_rect,
radius_x / 3.5,
radius_y / 3.5);
}
void
gimp_gegl_apply_border (GeglBuffer *src_buffer,
GimpProgress *progress,
const gchar *undo_desc,
GeglBuffer *dest_buffer,
const GeglRectangle *dest_rect,
gint radius_x,
gint radius_y,
GimpChannelBorderStyle style,
gboolean edge_lock)
{
GeglNode *node;
g_return_if_fail (GEGL_IS_BUFFER (src_buffer));
g_return_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress));
g_return_if_fail (GEGL_IS_BUFFER (dest_buffer));
switch (style)
{
case GIMP_CHANNEL_BORDER_STYLE_HARD:
case GIMP_CHANNEL_BORDER_STYLE_FEATHERED:
{
gboolean feather = style == GIMP_CHANNEL_BORDER_STYLE_FEATHERED;
node = gegl_node_new_child (NULL,
"operation", "gimp:border",
"radius-x", radius_x,
"radius-y", radius_y,
"feather", feather,
"edge-lock", edge_lock,
NULL);
}
break;
case GIMP_CHANNEL_BORDER_STYLE_SMOOTH:
{
GeglNode *input, *output;
GeglNode *grow, *shrink, *subtract;
node = gegl_node_new ();
input = gegl_node_get_input_proxy (node, "input");
output = gegl_node_get_output_proxy (node, "output");
/* Duplicate special-case behavior of "gimp:border". */
if (radius_x == 1 && radius_y == 1)
{
grow = gegl_node_new_child (node,
"operation", "gegl:nop",
NULL);
shrink = gegl_node_new_child (node,
"operation", "gimp:shrink",
"radius-x", 1,
"radius-y", 1,
"edge-lock", edge_lock,
NULL);
}
else
{
grow = gegl_node_new_child (node,
"operation", "gimp:grow",
"radius-x", radius_x,
"radius-y", radius_y,
NULL);
shrink = gegl_node_new_child (node,
"operation", "gimp:shrink",
"radius-x", radius_x + 1,
"radius-y", radius_y + 1,
"edge-lock", edge_lock,
NULL);
}
subtract = gegl_node_new_child (node,
"operation", "gegl:subtract",
NULL);
gegl_node_link_many (input, grow, subtract, output, NULL);
gegl_node_link (input, shrink);
gegl_node_connect_to (shrink, "output", subtract, "aux");
}
break;
default:
gimp_assert_not_reached ();
}
gimp_gegl_apply_operation (src_buffer, progress, undo_desc,
node, dest_buffer, dest_rect, TRUE);
g_object_unref (node);
}
void
gimp_gegl_apply_grow (GeglBuffer *src_buffer,
GimpProgress *progress,
const gchar *undo_desc,
GeglBuffer *dest_buffer,
const GeglRectangle *dest_rect,
gint radius_x,
gint radius_y)
{
GeglNode *node;
g_return_if_fail (GEGL_IS_BUFFER (src_buffer));
g_return_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress));
g_return_if_fail (GEGL_IS_BUFFER (dest_buffer));
node = gegl_node_new_child (NULL,
"operation", "gimp:grow",
"radius-x", radius_x,
"radius-y", radius_y,
NULL);
gimp_gegl_apply_operation (src_buffer, progress, undo_desc,
node, dest_buffer, dest_rect, TRUE);
g_object_unref (node);
}
void
gimp_gegl_apply_shrink (GeglBuffer *src_buffer,
GimpProgress *progress,
const gchar *undo_desc,
GeglBuffer *dest_buffer,
const GeglRectangle *dest_rect,
gint radius_x,
gint radius_y,
gboolean edge_lock)
{
GeglNode *node;
g_return_if_fail (GEGL_IS_BUFFER (src_buffer));
g_return_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress));
g_return_if_fail (GEGL_IS_BUFFER (dest_buffer));
node = gegl_node_new_child (NULL,
"operation", "gimp:shrink",
"radius-x", radius_x,
"radius-y", radius_y,
"edge-lock", edge_lock,
NULL);
gimp_gegl_apply_operation (src_buffer, progress, undo_desc,
node, dest_buffer, dest_rect, TRUE);
g_object_unref (node);
}
void
gimp_gegl_apply_flood (GeglBuffer *src_buffer,
GimpProgress *progress,
const gchar *undo_desc,
GeglBuffer *dest_buffer,
const GeglRectangle *dest_rect)
{
GeglNode *node;
g_return_if_fail (GEGL_IS_BUFFER (src_buffer));
g_return_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress));
g_return_if_fail (GEGL_IS_BUFFER (dest_buffer));
node = gegl_node_new_child (NULL,
"operation", "gimp:flood",
NULL);
gimp_gegl_apply_operation (src_buffer, progress, undo_desc,
node, dest_buffer, dest_rect, TRUE);
g_object_unref (node);
}
void
gimp_gegl_apply_gaussian_blur (GeglBuffer *src_buffer,
GimpProgress *progress,
const gchar *undo_desc,
GeglBuffer *dest_buffer,
const GeglRectangle *dest_rect,
gdouble std_dev_x,
gdouble std_dev_y)
{
GeglNode *node;
g_return_if_fail (GEGL_IS_BUFFER (src_buffer));
g_return_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress));
g_return_if_fail (GEGL_IS_BUFFER (dest_buffer));
node = gegl_node_new_child (NULL,
"operation", "gegl:gaussian-blur",
"std-dev-x", std_dev_x,
"std-dev-y", std_dev_y,
NULL);
gimp_gegl_apply_operation (src_buffer, progress, undo_desc,
node, dest_buffer, dest_rect, FALSE);
g_object_unref (node);
}
void
gimp_gegl_apply_invert_gamma (GeglBuffer *src_buffer,
GimpProgress *progress,
const gchar *undo_desc,
GeglBuffer *dest_buffer)
{
GeglNode *node;
g_return_if_fail (GEGL_IS_BUFFER (src_buffer));
g_return_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress));
g_return_if_fail (GEGL_IS_BUFFER (dest_buffer));
node = gegl_node_new_child (NULL,
"operation", "gegl:invert-gamma",
NULL);
gimp_gegl_apply_operation (src_buffer, progress, undo_desc,
node, dest_buffer, NULL, FALSE);
g_object_unref (node);
}
void
gimp_gegl_apply_invert_linear (GeglBuffer *src_buffer,
GimpProgress *progress,
const gchar *undo_desc,
GeglBuffer *dest_buffer)
{
GeglNode *node;
g_return_if_fail (GEGL_IS_BUFFER (src_buffer));
g_return_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress));
g_return_if_fail (GEGL_IS_BUFFER (dest_buffer));
node = gegl_node_new_child (NULL,
"operation", "gegl:invert-linear",
NULL);
gimp_gegl_apply_operation (src_buffer, progress, undo_desc,
node, dest_buffer, NULL, FALSE);
g_object_unref (node);
}
void
gimp_gegl_apply_opacity (GeglBuffer *src_buffer,
GimpProgress *progress,
const gchar *undo_desc,
GeglBuffer *dest_buffer,
GeglBuffer *mask,
gint mask_offset_x,
gint mask_offset_y,
gdouble opacity)
{
GeglNode *node;
g_return_if_fail (GEGL_IS_BUFFER (src_buffer));
g_return_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress));
g_return_if_fail (GEGL_IS_BUFFER (dest_buffer));
g_return_if_fail (mask == NULL || GEGL_IS_BUFFER (mask));
node = gimp_gegl_create_apply_opacity_node (mask,
mask_offset_x,
mask_offset_y,
opacity);
gimp_gegl_apply_operation (src_buffer, progress, undo_desc,
node, dest_buffer, NULL, FALSE);
g_object_unref (node);
}
void
gimp_gegl_apply_scale (GeglBuffer *src_buffer,
GimpProgress *progress,
const gchar *undo_desc,
GeglBuffer *dest_buffer,
GimpInterpolationType interpolation_type,
gdouble x,
gdouble y)
{
GeglNode *node;
g_return_if_fail (GEGL_IS_BUFFER (src_buffer));
g_return_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress));
g_return_if_fail (GEGL_IS_BUFFER (dest_buffer));
node = gegl_node_new_child (NULL,
"operation", "gegl:scale-ratio",
"origin-x", 0.0,
"origin-y", 0.0,
"sampler", interpolation_type,
"abyss-policy", GEGL_ABYSS_CLAMP,
"x", x,
"y", y,
NULL);
gimp_gegl_apply_operation (src_buffer, progress, undo_desc,
node, dest_buffer, NULL, FALSE);
g_object_unref (node);
}
void
gimp_gegl_apply_set_alpha (GeglBuffer *src_buffer,
GimpProgress *progress,
const gchar *undo_desc,
GeglBuffer *dest_buffer,
gdouble value)
{
GeglNode *node;
g_return_if_fail (GEGL_IS_BUFFER (src_buffer));
g_return_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress));
g_return_if_fail (GEGL_IS_BUFFER (dest_buffer));
node = gegl_node_new_child (NULL,
"operation", "gimp:set-alpha",
"value", value,
NULL);
gimp_gegl_apply_operation (src_buffer, progress, undo_desc,
node, dest_buffer, NULL, FALSE);
g_object_unref (node);
}
void
gimp_gegl_apply_threshold (GeglBuffer *src_buffer,
GimpProgress *progress,
const gchar *undo_desc,
GeglBuffer *dest_buffer,
gdouble value)
{
GeglNode *node;
g_return_if_fail (GEGL_IS_BUFFER (src_buffer));
g_return_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress));
g_return_if_fail (GEGL_IS_BUFFER (dest_buffer));
node = gegl_node_new_child (NULL,
"operation", "gegl:threshold",
"value", value,
NULL);
gimp_gegl_apply_operation (src_buffer, progress, undo_desc,
node, dest_buffer, NULL, FALSE);
g_object_unref (node);
}
void
gimp_gegl_apply_transform (GeglBuffer *src_buffer,
GimpProgress *progress,
const gchar *undo_desc,
GeglBuffer *dest_buffer,
GimpInterpolationType interpolation_type,
GimpMatrix3 *transform)
{
GeglNode *node;
g_return_if_fail (GEGL_IS_BUFFER (src_buffer));
g_return_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress));
g_return_if_fail (GEGL_IS_BUFFER (dest_buffer));
node = gegl_node_new_child (NULL,
"operation", "gegl:transform",
"near-z", GIMP_TRANSFORM_NEAR_Z,
"sampler", interpolation_type,
NULL);
gimp_gegl_node_set_matrix (node, transform);
gimp_gegl_apply_operation (src_buffer, progress, undo_desc,
node, dest_buffer, NULL, FALSE);
g_object_unref (node);
}