summaryrefslogtreecommitdiffstats
path: root/app/operations/layer-modes/gimpoperationlayermode.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--app/operations/layer-modes/gimpoperationlayermode.c914
1 files changed, 914 insertions, 0 deletions
diff --git a/app/operations/layer-modes/gimpoperationlayermode.c b/app/operations/layer-modes/gimpoperationlayermode.c
new file mode 100644
index 0000000..db74ed5
--- /dev/null
+++ b/app/operations/layer-modes/gimpoperationlayermode.c
@@ -0,0 +1,914 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpoperationlayermode.c
+ * Copyright (C) 2008 Michael Natterer <mitch@gimp.org>
+ * Copyright (C) 2008 Martin Nordholts <martinn@svn.gnome.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 <gegl-plugin.h>
+#include <cairo.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "libgimpbase/gimpbase.h"
+
+#include "../operations-types.h"
+
+#include "gimp-layer-modes.h"
+#include "gimpoperationlayermode.h"
+#include "gimpoperationlayermode-composite.h"
+
+
+/* the maximum number of samples to process in one go. used to limit
+ * the size of the buffers we allocate on the stack.
+ */
+#define GIMP_COMPOSITE_BLEND_MAX_SAMPLES ((1 << 18) /* 256 KiB */ / \
+ 16 /* bytes per pixel */ / \
+ 2 /* max number of buffers */)
+
+/* number of consecutive unblended samples (whose source or destination alpha
+ * is zero) above which to split the blending process, in order to avoid
+ * performing too many unnecessary conversions.
+ */
+#define GIMP_COMPOSITE_BLEND_SPLIT_THRESHOLD 32
+
+
+enum
+{
+ PROP_0,
+ PROP_LAYER_MODE,
+ PROP_OPACITY,
+ PROP_BLEND_SPACE,
+ PROP_COMPOSITE_SPACE,
+ PROP_COMPOSITE_MODE
+};
+
+
+typedef void (* CompositeFunc) (const gfloat *in,
+ const gfloat *layer,
+ const gfloat *comp,
+ const gfloat *mask,
+ float opacity,
+ gfloat *out,
+ gint samples);
+
+
+static void gimp_operation_layer_mode_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_operation_layer_mode_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void gimp_operation_layer_mode_prepare (GeglOperation *operation);
+static GeglRectangle gimp_operation_layer_mode_get_bounding_box (GeglOperation *operation);
+static gboolean gimp_operation_layer_mode_parent_process (GeglOperation *operation,
+ GeglOperationContext *context,
+ const gchar *output_prop,
+ const GeglRectangle *result,
+ gint level);
+
+static gboolean gimp_operation_layer_mode_process (GeglOperation *operation,
+ void *in,
+ void *layer,
+ void *mask,
+ void *out,
+ glong samples,
+ const GeglRectangle *roi,
+ gint level);
+
+static gboolean gimp_operation_layer_mode_real_parent_process (GeglOperation *operation,
+ GeglOperationContext *context,
+ const gchar *output_prop,
+ const GeglRectangle *result,
+ gint level);
+static gboolean gimp_operation_layer_mode_real_process (GeglOperation *operation,
+ void *in,
+ void *layer,
+ void *mask,
+ void *out,
+ glong samples,
+ const GeglRectangle *roi,
+ gint level);
+
+static gboolean process_last_node (GeglOperation *operation,
+ void *in,
+ void *layer,
+ void *mask,
+ void *out,
+ glong samples,
+ const GeglRectangle *roi,
+ gint level);
+
+
+G_DEFINE_TYPE (GimpOperationLayerMode, gimp_operation_layer_mode,
+ GEGL_TYPE_OPERATION_POINT_COMPOSER3)
+
+#define parent_class gimp_operation_layer_mode_parent_class
+
+
+static const Babl *gimp_layer_color_space_fish[3 /* from */][3 /* to */];
+
+static CompositeFunc composite_union = gimp_operation_layer_mode_composite_union;
+static CompositeFunc composite_clip_to_backdrop = gimp_operation_layer_mode_composite_clip_to_backdrop;
+static CompositeFunc composite_clip_to_layer = gimp_operation_layer_mode_composite_clip_to_layer;
+static CompositeFunc composite_intersection = gimp_operation_layer_mode_composite_intersection;
+
+static CompositeFunc composite_union_sub = gimp_operation_layer_mode_composite_union_sub;
+static CompositeFunc composite_clip_to_backdrop_sub = gimp_operation_layer_mode_composite_clip_to_backdrop_sub;
+static CompositeFunc composite_clip_to_layer_sub = gimp_operation_layer_mode_composite_clip_to_layer_sub;
+static CompositeFunc composite_intersection_sub = gimp_operation_layer_mode_composite_intersection_sub;
+
+
+static void
+gimp_operation_layer_mode_class_init (GimpOperationLayerModeClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GeglOperationClass *operation_class = GEGL_OPERATION_CLASS (klass);
+ GeglOperationPointComposer3Class *point_composer3_class = GEGL_OPERATION_POINT_COMPOSER3_CLASS (klass);
+
+ gegl_operation_class_set_keys (operation_class,
+ "name", "gimp:layer-mode", NULL);
+
+ object_class->set_property = gimp_operation_layer_mode_set_property;
+ object_class->get_property = gimp_operation_layer_mode_get_property;
+
+ operation_class->prepare = gimp_operation_layer_mode_prepare;
+ operation_class->get_bounding_box = gimp_operation_layer_mode_get_bounding_box;
+ operation_class->process = gimp_operation_layer_mode_parent_process;
+
+ point_composer3_class->process = gimp_operation_layer_mode_process;
+
+ klass->parent_process = gimp_operation_layer_mode_real_parent_process;
+ klass->process = gimp_operation_layer_mode_real_process;
+ klass->get_affected_region = NULL;
+
+ g_object_class_install_property (object_class, PROP_LAYER_MODE,
+ g_param_spec_enum ("layer-mode",
+ NULL, NULL,
+ GIMP_TYPE_LAYER_MODE,
+ GIMP_LAYER_MODE_NORMAL,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_OPACITY,
+ g_param_spec_double ("opacity",
+ NULL, NULL,
+ 0.0, 1.0, 1.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_BLEND_SPACE,
+ g_param_spec_enum ("blend-space",
+ NULL, NULL,
+ GIMP_TYPE_LAYER_COLOR_SPACE,
+ GIMP_LAYER_COLOR_SPACE_RGB_LINEAR,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+
+ g_object_class_install_property (object_class, PROP_COMPOSITE_SPACE,
+ g_param_spec_enum ("composite-space",
+ NULL, NULL,
+ GIMP_TYPE_LAYER_COLOR_SPACE,
+ GIMP_LAYER_COLOR_SPACE_RGB_LINEAR,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_COMPOSITE_MODE,
+ g_param_spec_enum ("composite-mode",
+ NULL, NULL,
+ GIMP_TYPE_LAYER_COMPOSITE_MODE,
+ GIMP_LAYER_COMPOSITE_UNION,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ gimp_layer_color_space_fish
+ /* from */ [GIMP_LAYER_COLOR_SPACE_RGB_LINEAR - 1]
+ /* to */ [GIMP_LAYER_COLOR_SPACE_RGB_PERCEPTUAL - 1] =
+ babl_fish ("RGBA float", "R'G'B'A float");
+ gimp_layer_color_space_fish
+ /* from */ [GIMP_LAYER_COLOR_SPACE_RGB_LINEAR - 1]
+ /* to */ [GIMP_LAYER_COLOR_SPACE_LAB - 1] =
+ babl_fish ("RGBA float", "CIE Lab alpha float");
+
+ gimp_layer_color_space_fish
+ /* from */ [GIMP_LAYER_COLOR_SPACE_RGB_PERCEPTUAL - 1]
+ /* to */ [GIMP_LAYER_COLOR_SPACE_RGB_LINEAR - 1] =
+ babl_fish ("R'G'B'A float", "RGBA float");
+ gimp_layer_color_space_fish
+ /* from */ [GIMP_LAYER_COLOR_SPACE_RGB_PERCEPTUAL - 1]
+ /* to */ [GIMP_LAYER_COLOR_SPACE_LAB - 1] =
+ babl_fish ("R'G'B'A float", "CIE Lab alpha float");
+
+ gimp_layer_color_space_fish
+ /* from */ [GIMP_LAYER_COLOR_SPACE_LAB - 1]
+ /* to */ [GIMP_LAYER_COLOR_SPACE_RGB_LINEAR - 1] =
+ babl_fish ("CIE Lab alpha float", "RGBA float");
+ gimp_layer_color_space_fish
+ /* from */ [GIMP_LAYER_COLOR_SPACE_LAB - 1]
+ /* to */ [GIMP_LAYER_COLOR_SPACE_RGB_PERCEPTUAL - 1] =
+ babl_fish ("CIE Lab alpha float", "R'G'B'A float");
+
+#if COMPILE_SSE2_INTRINISICS
+ if (gimp_cpu_accel_get_support () & GIMP_CPU_ACCEL_X86_SSE2)
+ composite_clip_to_backdrop = gimp_operation_layer_mode_composite_clip_to_backdrop_sse2;
+#endif
+}
+
+static void
+gimp_operation_layer_mode_init (GimpOperationLayerMode *self)
+{
+}
+
+static void
+gimp_operation_layer_mode_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpOperationLayerMode *self = GIMP_OPERATION_LAYER_MODE (object);
+
+ switch (property_id)
+ {
+ case PROP_LAYER_MODE:
+ self->layer_mode = g_value_get_enum (value);
+ break;
+
+ case PROP_OPACITY:
+ self->prop_opacity = g_value_get_double (value);
+ break;
+
+ case PROP_BLEND_SPACE:
+ self->blend_space = g_value_get_enum (value);
+ break;
+
+ case PROP_COMPOSITE_SPACE:
+ self->composite_space = g_value_get_enum (value);
+ break;
+
+ case PROP_COMPOSITE_MODE:
+ self->prop_composite_mode = g_value_get_enum (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_operation_layer_mode_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpOperationLayerMode *self = GIMP_OPERATION_LAYER_MODE (object);
+
+ switch (property_id)
+ {
+ case PROP_LAYER_MODE:
+ g_value_set_enum (value, self->layer_mode);
+ break;
+
+ case PROP_OPACITY:
+ g_value_set_double (value, self->prop_opacity);
+ break;
+
+ case PROP_BLEND_SPACE:
+ g_value_set_enum (value, self->blend_space);
+ break;
+
+ case PROP_COMPOSITE_SPACE:
+ g_value_set_enum (value, self->composite_space);
+ break;
+
+ case PROP_COMPOSITE_MODE:
+ g_value_set_enum (value, self->prop_composite_mode);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_operation_layer_mode_prepare (GeglOperation *operation)
+{
+ GimpOperationLayerMode *self = GIMP_OPERATION_LAYER_MODE (operation);
+ const GeglRectangle *input_extent;
+ const GeglRectangle *mask_extent;
+ const Babl *preferred_format;
+ const Babl *format;
+
+ self->composite_mode = self->prop_composite_mode;
+
+ if (self->composite_mode == GIMP_LAYER_COMPOSITE_AUTO)
+ {
+ self->composite_mode =
+ gimp_layer_mode_get_composite_mode (self->layer_mode);
+
+ g_warn_if_fail (self->composite_mode != GIMP_LAYER_COMPOSITE_AUTO);
+ }
+
+ self->function = gimp_layer_mode_get_function (self->layer_mode);
+ self->blend_function = gimp_layer_mode_get_blend_function (self->layer_mode);
+
+ input_extent = gegl_operation_source_get_bounding_box (operation, "input");
+ mask_extent = gegl_operation_source_get_bounding_box (operation, "aux2");
+
+ /* if the input pad has data, work as usual. */
+ if (input_extent && ! gegl_rectangle_is_empty (input_extent))
+ {
+ self->is_last_node = FALSE;
+
+ preferred_format = gegl_operation_get_source_format (operation, "input");
+ }
+ /* otherwise, we're the last node (corresponding to the bottom layer).
+ * in this case, we render the layer (as if) using UNION mode.
+ */
+ else
+ {
+ self->is_last_node = TRUE;
+
+ /* if the layer mode doesn't affect the source, use a shortcut
+ * function that only applies the opacity/mask to the layer.
+ */
+ if (! (gimp_operation_layer_mode_get_affected_region (self) &
+ GIMP_LAYER_COMPOSITE_REGION_SOURCE))
+ {
+ self->function = process_last_node;
+ }
+ /* otherwise, use the original process function, but force the
+ * composite mode to UNION.
+ */
+ else
+ {
+ self->composite_mode = GIMP_LAYER_COMPOSITE_UNION;
+ }
+
+ preferred_format = gegl_operation_get_source_format (operation, "aux");
+ }
+
+ self->has_mask = mask_extent && ! gegl_rectangle_is_empty (mask_extent);
+
+ format = gimp_layer_mode_get_format (self->layer_mode,
+ self->blend_space,
+ self->composite_space,
+ self->composite_mode,
+ preferred_format);
+
+ gegl_operation_set_format (operation, "input", format);
+ gegl_operation_set_format (operation, "output", format);
+ gegl_operation_set_format (operation, "aux", format);
+ gegl_operation_set_format (operation, "aux2", babl_format ("Y float"));
+}
+
+static GeglRectangle
+gimp_operation_layer_mode_get_bounding_box (GeglOperation *op)
+{
+ GimpOperationLayerMode *self = (gpointer) op;
+ GeglRectangle *in_rect;
+ GeglRectangle *aux_rect;
+ GeglRectangle *aux2_rect;
+ GeglRectangle src_rect = {};
+ GeglRectangle dst_rect = {};
+ GeglRectangle result;
+ GimpLayerCompositeRegion included_region;
+
+ in_rect = gegl_operation_source_get_bounding_box (op, "input");
+ aux_rect = gegl_operation_source_get_bounding_box (op, "aux");
+ aux2_rect = gegl_operation_source_get_bounding_box (op, "aux2");
+
+ if (in_rect)
+ dst_rect = *in_rect;
+
+ if (aux_rect)
+ {
+ src_rect = *aux_rect;
+
+ if (aux2_rect)
+ gegl_rectangle_intersect (&src_rect, &src_rect, aux2_rect);
+ }
+
+ if (self->is_last_node)
+ {
+ included_region = GIMP_LAYER_COMPOSITE_REGION_SOURCE;
+ }
+ else
+ {
+ included_region = gimp_layer_mode_get_included_region (self->layer_mode,
+ self->composite_mode);
+ }
+
+ if (self->prop_opacity == 0.0)
+ included_region &= ~GIMP_LAYER_COMPOSITE_REGION_SOURCE;
+
+ gegl_rectangle_intersect (&result, &src_rect, &dst_rect);
+
+ if (included_region & GIMP_LAYER_COMPOSITE_REGION_SOURCE)
+ gegl_rectangle_bounding_box (&result, &result, &src_rect);
+
+ if (included_region & GIMP_LAYER_COMPOSITE_REGION_DESTINATION)
+ gegl_rectangle_bounding_box (&result, &result, &dst_rect);
+
+ return result;
+}
+
+static gboolean
+gimp_operation_layer_mode_parent_process (GeglOperation *operation,
+ GeglOperationContext *context,
+ const gchar *output_prop,
+ const GeglRectangle *result,
+ gint level)
+{
+ GimpOperationLayerMode *point = GIMP_OPERATION_LAYER_MODE (operation);
+
+ point->opacity = point->prop_opacity;
+
+ /* if we have a mask, but it's not included in the output, pretend the
+ * opacity is 0, so that we don't composite 'aux' over 'input' as if there
+ * was no mask.
+ */
+ if (point->has_mask)
+ {
+ GObject *mask;
+ gboolean has_mask;
+
+ /* get the raw value. this does not increase the reference count. */
+ mask = gegl_operation_context_get_object (context, "aux2");
+
+ /* disregard 'mask' if it's not included in the roi. */
+ has_mask =
+ mask &&
+ gegl_rectangle_intersect (NULL,
+ gegl_buffer_get_extent (GEGL_BUFFER (mask)),
+ result);
+
+ if (! has_mask)
+ point->opacity = 0.0;
+ }
+
+ return GIMP_OPERATION_LAYER_MODE_GET_CLASS (point)->parent_process (
+ operation, context, output_prop, result, level);
+}
+
+static gboolean
+gimp_operation_layer_mode_process (GeglOperation *operation,
+ void *in,
+ void *layer,
+ void *mask,
+ void *out,
+ glong samples,
+ const GeglRectangle *roi,
+ gint level)
+{
+ return ((GimpOperationLayerMode *) operation)->function (
+ operation, in, layer, mask, out, samples, roi, level);
+}
+
+static gboolean
+gimp_operation_layer_mode_real_parent_process (GeglOperation *operation,
+ GeglOperationContext *context,
+ const gchar *output_prop,
+ const GeglRectangle *result,
+ gint level)
+{
+ GimpOperationLayerMode *point = GIMP_OPERATION_LAYER_MODE (operation);
+ GObject *input;
+ GObject *aux;
+ gboolean has_input;
+ gboolean has_aux;
+ GimpLayerCompositeRegion included_region;
+
+ /* get the raw values. this does not increase the reference count. */
+ input = gegl_operation_context_get_object (context, "input");
+ aux = gegl_operation_context_get_object (context, "aux");
+
+ /* disregard 'input' if it's not included in the roi. */
+ has_input =
+ input &&
+ gegl_rectangle_intersect (NULL,
+ gegl_buffer_get_extent (GEGL_BUFFER (input)),
+ result);
+
+ /* disregard 'aux' if it's not included in the roi, or if it's fully
+ * transparent.
+ */
+ has_aux =
+ aux &&
+ point->opacity != 0.0 &&
+ gegl_rectangle_intersect (NULL,
+ gegl_buffer_get_extent (GEGL_BUFFER (aux)),
+ result);
+
+ if (point->is_last_node)
+ {
+ included_region = GIMP_LAYER_COMPOSITE_REGION_SOURCE;
+ }
+ else
+ {
+ included_region = gimp_layer_mode_get_included_region (point->layer_mode,
+ point->composite_mode);
+ }
+
+ /* if there's no 'input' ... */
+ if (! has_input)
+ {
+ /* ... and there's 'aux', and the composite mode includes it (or we're
+ * the last node) ...
+ */
+ if (has_aux && (included_region & GIMP_LAYER_COMPOSITE_REGION_SOURCE))
+ {
+ GimpLayerCompositeRegion affected_region;
+
+ affected_region =
+ gimp_operation_layer_mode_get_affected_region (point);
+
+ /* ... and the op doesn't otherwise affect 'aux', or changes its
+ * alpha ...
+ */
+ if (! (affected_region & GIMP_LAYER_COMPOSITE_REGION_SOURCE) &&
+ point->opacity == 1.0 &&
+ ! gegl_operation_context_get_object (context, "aux2"))
+ {
+ /* pass 'aux' directly as output; */
+ gegl_operation_context_set_object (context, "output", aux);
+ return TRUE;
+ }
+
+ /* otherwise, if the op affects 'aux', or changes its alpha, process
+ * it even though there's no 'input';
+ */
+ }
+ /* otherwise, there's no 'aux', or the composite mode doesn't include it,
+ * and so ...
+ */
+ else
+ {
+ /* ... the output is empty. */
+ gegl_operation_context_set_object (context, "output", NULL);
+ return TRUE;
+ }
+ }
+ /* otherwise, if there's 'input' but no 'aux' ... */
+ else if (! has_aux)
+ {
+ /* ... and the composite mode includes 'input' ... */
+ if (included_region & GIMP_LAYER_COMPOSITE_REGION_DESTINATION)
+ {
+ GimpLayerCompositeRegion affected_region;
+
+ affected_region =
+ gimp_operation_layer_mode_get_affected_region (point);
+
+ /* ... and the op doesn't otherwise affect 'input' ... */
+ if (! (affected_region & GIMP_LAYER_COMPOSITE_REGION_DESTINATION))
+ {
+ /* pass 'input' directly as output; */
+ gegl_operation_context_set_object (context, "output", input);
+ return TRUE;
+ }
+
+ /* otherwise, if the op affects 'input', process it even though
+ * there's no 'aux';
+ */
+ }
+
+ /* otherwise, the output is fully transparent, but we process it anyway
+ * to maintain the 'input' color values.
+ */
+ }
+
+ /* FIXME: we don't actually handle the case where one of the inputs
+ * is NULL -- it'll just segfault. 'input' is not expected to be NULL,
+ * but 'aux' might be, currently.
+ */
+ if (! input || ! aux)
+ {
+ GObject *empty = G_OBJECT (gegl_buffer_new (NULL, NULL));
+
+ if (! input) gegl_operation_context_set_object (context, "input", empty);
+ if (! aux) gegl_operation_context_set_object (context, "aux", empty);
+
+ if (! input && ! aux)
+ gegl_object_set_has_forked (G_OBJECT (empty));
+
+ g_object_unref (empty);
+ }
+
+ /* chain up, which will create the needed buffers for our actual
+ * process function
+ */
+ return GEGL_OPERATION_CLASS (parent_class)->process (operation, context,
+ output_prop, result,
+ level);
+}
+
+static gboolean
+gimp_operation_layer_mode_real_process (GeglOperation *operation,
+ void *in_p,
+ void *layer_p,
+ void *mask_p,
+ void *out_p,
+ glong samples,
+ const GeglRectangle *roi,
+ gint level)
+{
+ GimpOperationLayerMode *layer_mode = (gpointer) operation;
+ gfloat *in = in_p;
+ gfloat *out = out_p;
+ gfloat *layer = layer_p;
+ gfloat *mask = mask_p;
+ gfloat opacity = layer_mode->opacity;
+ GimpLayerColorSpace blend_space = layer_mode->blend_space;
+ GimpLayerColorSpace composite_space = layer_mode->composite_space;
+ GimpLayerCompositeMode composite_mode = layer_mode->composite_mode;
+ GimpLayerModeBlendFunc blend_function = layer_mode->blend_function;
+ gboolean composite_needs_in_color;
+ gfloat *blend_in;
+ gfloat *blend_layer;
+ gfloat *blend_out;
+ const Babl *composite_to_blend_fish = NULL;
+ const Babl *blend_to_composite_fish = NULL;
+
+ /* make sure we don't process more than GIMP_COMPOSITE_BLEND_MAX_SAMPLES
+ * at a time, so that we don't overflow the stack if we allocate buffers
+ * on it. note that this has to be done with a nested function call,
+ * because alloca'd buffers remain for the duration of the stack frame.
+ */
+ while (samples > GIMP_COMPOSITE_BLEND_MAX_SAMPLES)
+ {
+ gimp_operation_layer_mode_real_process (operation,
+ in, layer, mask, out,
+ GIMP_COMPOSITE_BLEND_MAX_SAMPLES,
+ roi, level);
+
+ in += 4 * GIMP_COMPOSITE_BLEND_MAX_SAMPLES;
+ layer += 4 * GIMP_COMPOSITE_BLEND_MAX_SAMPLES;
+ if (mask)
+ mask += GIMP_COMPOSITE_BLEND_MAX_SAMPLES;
+ out += 4 * GIMP_COMPOSITE_BLEND_MAX_SAMPLES;
+
+ samples -= GIMP_COMPOSITE_BLEND_MAX_SAMPLES;
+ }
+
+ composite_needs_in_color =
+ composite_mode == GIMP_LAYER_COMPOSITE_UNION ||
+ composite_mode == GIMP_LAYER_COMPOSITE_CLIP_TO_BACKDROP;
+
+ blend_in = in;
+ blend_layer = layer;
+ blend_out = out;
+
+ if (blend_space != GIMP_LAYER_COLOR_SPACE_AUTO)
+ {
+ gimp_assert (composite_space >= 1 && composite_space < 4);
+ gimp_assert (blend_space >= 1 && blend_space < 4);
+
+ composite_to_blend_fish = gimp_layer_color_space_fish [composite_space - 1]
+ [blend_space - 1];
+
+ blend_to_composite_fish = gimp_layer_color_space_fish [blend_space - 1]
+ [composite_space - 1];
+ }
+
+ /* if we need to convert the samples between the composite and blend
+ * spaces...
+ */
+ if (composite_to_blend_fish)
+ {
+ gint i;
+ gint end;
+
+ if (in != out || composite_needs_in_color)
+ {
+ /* don't convert input in-place if we're not doing in-place output,
+ * or if we're going to need the original input for compositing.
+ */
+ blend_in = g_alloca (sizeof (gfloat) * 4 * samples);
+ }
+ blend_layer = g_alloca (sizeof (gfloat) * 4 * samples);
+
+ if (in == out) /* in-place detected, avoid clobbering since we need to
+ read 'in' for the compositing stage */
+ {
+ if (blend_layer != layer)
+ blend_out = blend_layer;
+ else
+ blend_out = g_alloca (sizeof (gfloat) * 4 * samples);
+ }
+
+ /* samples whose the source or destination alpha is zero are not blended,
+ * and therefore do not need to be converted. while it's generally
+ * desirable to perform conversion and blending in bulk, when we have
+ * more than a certain number of consecutive unblended samples, the cost
+ * of converting them outweighs the cost of splitting the process around
+ * them to avoid the conversion.
+ */
+
+ i = ALPHA;
+ end = 4 * samples + ALPHA;
+
+ while (TRUE)
+ {
+ gint first;
+ gint last;
+ gint count;
+
+ /* skip any unblended samples. the color values of `blend_out` for
+ * these samples are unconstrained, in particular, they may be NaN,
+ * but the alpha values should generally be finite, and specifically
+ * 0 when the source alpha is 0.
+ */
+ while (i < end && (in[i] == 0.0f || layer[i] == 0.0f))
+ {
+ blend_out[i] = 0.0f;
+ i += 4;
+ }
+
+ /* stop if there are no more samples */
+ if (i == end)
+ break;
+
+ /* otherwise, keep scanning the samples until we find
+ * GIMP_COMPOSITE_BLEND_SPLIT_THRESHOLD consecutive unblended
+ * samples.
+ */
+
+ first = i;
+ i += 4;
+ last = i;
+
+ while (i < end && i - last < 4 * GIMP_COMPOSITE_BLEND_SPLIT_THRESHOLD)
+ {
+ gboolean blended;
+
+ blended = (in[i] != 0.0f && layer[i] != 0.0f);
+
+ i += 4;
+ if (blended)
+ last = i;
+ }
+
+ /* convert and blend the samples in the range [first, last) */
+
+ count = (last - first) / 4;
+ first -= ALPHA;
+
+ babl_process (composite_to_blend_fish,
+ in + first, blend_in + first, count);
+ babl_process (composite_to_blend_fish,
+ layer + first, blend_layer + first, count);
+
+ blend_function (operation, blend_in + first, blend_layer + first,
+ blend_out + first, count);
+
+ babl_process (blend_to_composite_fish,
+ blend_out + first, blend_out + first, count);
+
+ /* make sure the alpha values of `blend_out` are valid for the
+ * trailing unblended samples.
+ */
+ for (; last < i; last += 4)
+ blend_out[last] = 0.0f;
+ }
+ }
+ else
+ {
+ /* if both blending and compositing use the same color space, things are
+ * much simpler.
+ */
+
+ if (in == out) /* in-place detected, avoid clobbering since we need to
+ read 'in' for the compositing stage */
+ {
+ blend_out = g_alloca (sizeof (gfloat) * 4 * samples);
+ }
+
+ blend_function (operation, blend_in, blend_layer, blend_out, samples);
+ }
+
+ if (! gimp_layer_mode_is_subtractive (layer_mode->layer_mode))
+ {
+ switch (composite_mode)
+ {
+ case GIMP_LAYER_COMPOSITE_UNION:
+ case GIMP_LAYER_COMPOSITE_AUTO:
+ composite_union (in, layer, blend_out, mask, opacity,
+ out, samples);
+ break;
+
+ case GIMP_LAYER_COMPOSITE_CLIP_TO_BACKDROP:
+ composite_clip_to_backdrop (in, layer, blend_out, mask, opacity,
+ out, samples);
+ break;
+
+ case GIMP_LAYER_COMPOSITE_CLIP_TO_LAYER:
+ composite_clip_to_layer (in, layer, blend_out, mask, opacity,
+ out, samples);
+ break;
+
+ case GIMP_LAYER_COMPOSITE_INTERSECTION:
+ composite_intersection (in, layer, blend_out, mask, opacity,
+ out, samples);
+ break;
+ }
+ }
+ else
+ {
+ switch (composite_mode)
+ {
+ case GIMP_LAYER_COMPOSITE_UNION:
+ case GIMP_LAYER_COMPOSITE_AUTO:
+ composite_union_sub (in, layer, blend_out, mask, opacity,
+ out, samples);
+ break;
+
+ case GIMP_LAYER_COMPOSITE_CLIP_TO_BACKDROP:
+ composite_clip_to_backdrop_sub (in, layer, blend_out, mask, opacity,
+ out, samples);
+ break;
+
+ case GIMP_LAYER_COMPOSITE_CLIP_TO_LAYER:
+ composite_clip_to_layer_sub (in, layer, blend_out, mask, opacity,
+ out, samples);
+ break;
+
+ case GIMP_LAYER_COMPOSITE_INTERSECTION:
+ composite_intersection_sub (in, layer, blend_out, mask, opacity,
+ out, samples);
+ break;
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean
+process_last_node (GeglOperation *operation,
+ void *in_p,
+ void *layer_p,
+ void *mask_p,
+ void *out_p,
+ glong samples,
+ const GeglRectangle *roi,
+ gint level)
+{
+ gfloat *out = out_p;
+ gfloat *layer = layer_p;
+ gfloat *mask = mask_p;
+ gfloat opacity = GIMP_OPERATION_LAYER_MODE (operation)->opacity;
+
+ while (samples--)
+ {
+ memcpy (out, layer, 3 * sizeof (gfloat));
+
+ out[ALPHA] = layer[ALPHA] * opacity;
+ if (mask)
+ out[ALPHA] *= *mask++;
+
+ layer += 4;
+ out += 4;
+ }
+
+ return TRUE;
+}
+
+
+/* public functions */
+
+
+GimpLayerCompositeRegion
+gimp_operation_layer_mode_get_affected_region (GimpOperationLayerMode *layer_mode)
+{
+ GimpOperationLayerModeClass *klass;
+
+ g_return_val_if_fail (GIMP_IS_OPERATION_LAYER_MODE (layer_mode),
+ GIMP_LAYER_COMPOSITE_REGION_INTERSECTION);
+
+ klass = GIMP_OPERATION_LAYER_MODE_GET_CLASS (layer_mode);
+
+ if (klass->get_affected_region)
+ return klass->get_affected_region (layer_mode);
+
+ return GIMP_LAYER_COMPOSITE_REGION_INTERSECTION;
+}