/* GIMP - The GNU Image Manipulation Program * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * gimpoperationlayermode.c * Copyright (C) 2008 Michael Natterer * Copyright (C) 2008 Martin Nordholts * * 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 "libgimpcolor/gimpcolor.h" #include "libgimpbase/gimpbase.h" #include "libgimpmath/gimpmath.h" #include "../operations-types.h" #include "gimpoperationlayermode.h" enum { PROP_0, PROP_LAYER_MODE, PROP_LINEAR, PROP_OPACITY, PROP_BLEND_SPACE, PROP_COMPOSITE_SPACE, PROP_COMPOSITE_MODE }; 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 gboolean gimp_operation_layer_mode_process (GeglOperation *operation, GeglOperationContext *context, const gchar *output_prop, const GeglRectangle *result, gint level); static GimpLayerModeAffectMask gimp_operation_layer_mode_real_get_affect_mask (GimpOperationLayerMode *layer_mode); G_DEFINE_TYPE (GimpOperationLayerMode, gimp_operation_layer_mode, GEGL_TYPE_OPERATION_POINT_COMPOSER3) #define parent_class gimp_operation_layer_mode_parent_class const Babl *_gimp_fish_rgba_to_perceptual = NULL; const Babl *_gimp_fish_perceptual_to_rgba = NULL; const Babl *_gimp_fish_perceptual_to_laba = NULL; const Babl *_gimp_fish_rgba_to_laba = NULL; const Babl *_gimp_fish_laba_to_rgba = NULL; const Babl *_gimp_fish_laba_to_perceptual = NULL; typedef void (*CompFun)(gfloat *in, gfloat *layer, gfloat *comp, gfloat *mask, float opacity, gfloat *out, gint samples); static inline void compfun_src_atop (gfloat *in, gfloat *layer, gfloat *comp, gfloat *mask, gfloat opacity, gfloat *out, gint samples); static inline void compfun_dst_atop (gfloat *in, gfloat *layer, gfloat *comp, gfloat *mask, gfloat opacity, gfloat *out, gint samples); static inline void compfun_src_in (gfloat *in, gfloat *layer, gfloat *comp, gfloat *mask, gfloat opacity, gfloat *out, gint samples); static inline void compfun_src_over (gfloat *in, gfloat *layer, gfloat *comp, gfloat *mask, gfloat opacity, gfloat *out, gint samples); static CompFun compfun_src_atop_dispatch = compfun_src_atop; static CompFun compfun_dst_atop_dispatch = compfun_dst_atop; static CompFun compfun_src_in_dispatch = compfun_src_in; static CompFun compfun_src_over_dispatch = compfun_src_over; #if COMPILE_SSE2_INTRINISICS static inline void compfun_src_atop_sse2 (gfloat *in, gfloat *layer, gfloat *comp, gfloat *mask, gfloat opacity, gfloat *out, gint samples); #endif static void gimp_operation_layer_mode_class_init (GimpOperationLayerModeClass *klass) { GObjectClass *object_class; GeglOperationClass *operation_class; GeglOperationPointComposer3Class *point_composer3_class; #if COMPILE_SSE2_INTRINISICS if (gimp_cpu_accel_get_support () & GIMP_CPU_ACCEL_X86_SSE2) compfun_src_atop_dispatch = compfun_src_atop_sse2; #endif object_class = G_OBJECT_CLASS (klass); operation_class = GEGL_OPERATION_CLASS (klass); 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->process = gimp_operation_layer_mode_process; point_composer3_class->process = gimp_operation_layer_mode_process_pixels; klass->get_affect_mask = gimp_operation_layer_mode_real_get_affect_mask; 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_LINEAR, g_param_spec_boolean ("linear", NULL, NULL, FALSE, 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_SRC_OVER, GIMP_PARAM_READWRITE | G_PARAM_CONSTRUCT)); _gimp_fish_rgba_to_perceptual = babl_fish ("RGBA float", "R'G'B'A float"); _gimp_fish_perceptual_to_rgba = babl_fish ("R'G'B'A float", "RGBA float"); _gimp_fish_perceptual_to_laba = babl_fish ("R'G'B'A float", "CIE Lab alpha float"); _gimp_fish_rgba_to_laba = babl_fish ("RGBA float", "CIE Lab alpha float"); _gimp_fish_laba_to_rgba = babl_fish ("CIE Lab alpha float", "RGBA float"); _gimp_fish_laba_to_perceptual = babl_fish ("CIE Lab alpha float", "R'G'B'A float"); } 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_LINEAR: self->linear = g_value_get_boolean (value); break; case PROP_OPACITY: self->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->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_LINEAR: g_value_set_boolean (value, self->linear); break; case PROP_OPACITY: g_value_set_double (value, self->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->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 Babl *format; if (self->linear) format = babl_format ("RGBA float"); else format = babl_format ("R'G'B'A float"); 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 gboolean gimp_operation_layer_mode_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; /* 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 there's no 'input' ... */ if (! has_input) { /* ... and there's 'aux', and the composite mode includes it ... */ if (has_aux && (point->composite_mode == GIMP_LAYER_COMPOSITE_SRC_OVER || point->composite_mode == GIMP_LAYER_COMPOSITE_DST_ATOP)) { GimpLayerModeAffectMask affect_mask; affect_mask = gimp_operation_layer_mode_get_affect_mask (point); /* ... and the op doesn't otherwise affect 'aux', or changes its * alpha ... */ if (! (affect_mask & GIMP_LAYER_MODE_AFFECT_SRC) && 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 (point->composite_mode == GIMP_LAYER_COMPOSITE_SRC_OVER || point->composite_mode == GIMP_LAYER_COMPOSITE_SRC_ATOP) { GimpLayerModeAffectMask affect_mask; affect_mask = gimp_operation_layer_mode_get_affect_mask (point); /* ... and the op doesn't otherwise affect 'input' ... */ if (! (affect_mask & GIMP_LAYER_MODE_AFFECT_DST)) { /* 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 GimpLayerModeAffectMask gimp_operation_layer_mode_real_get_affect_mask (GimpOperationLayerMode *layer_mode) { /* most modes only affect the overlapping regions. */ return GIMP_LAYER_MODE_AFFECT_NONE; } /* public functions */ GimpLayerModeAffectMask gimp_operation_layer_mode_get_affect_mask (GimpOperationLayerMode *layer_mode) { g_return_val_if_fail (GIMP_IS_OPERATION_LAYER_MODE (layer_mode), GIMP_LAYER_MODE_AFFECT_NONE); return GIMP_OPERATION_LAYER_MODE_GET_CLASS (layer_mode)->get_affect_mask (layer_mode); } /* compositing and blending functions */ static inline GimpBlendFunc gimp_layer_mode_get_blend_fun (GimpLayerMode mode); static inline void gimp_composite_blend (gpointer op, gfloat *in, gfloat *layer, gfloat *mask, gfloat *out, glong samples, GimpBlendFunc blend_func); gboolean gimp_operation_layer_mode_process_pixels (GeglOperation *operation, void *in, void *layer, void *mask, void *out, glong samples, const GeglRectangle *roi, gint level) { GimpOperationLayerMode *layer_mode = (GimpOperationLayerMode*)(operation); gimp_composite_blend (operation, in, layer, mask, out, samples, gimp_layer_mode_get_blend_fun (layer_mode->layer_mode)); return TRUE; } static inline void compfun_src_atop (gfloat *in, gfloat *layer, gfloat *comp, gfloat *mask, gfloat opacity, gfloat *out, gint samples) { while (samples--) { gfloat layer_alpha = layer[ALPHA] * opacity; if (mask) layer_alpha *= *mask; if (layer_alpha == 0.0f) { out[RED] = in[RED]; out[GREEN] = in[GREEN]; out[BLUE] = in[BLUE]; } else { gint b; for (b = RED; b < ALPHA; b++) out[b] = layer[b] * layer_alpha + in[b] * (1.0f - layer_alpha); } out[ALPHA] = in[ALPHA]; in += 4; out += 4; layer += 4; if (mask) mask++; } } static inline void compfun_src_over (gfloat *in, gfloat *layer, gfloat *comp, gfloat *mask, gfloat opacity, gfloat *out, gint samples) { while (samples--) { gfloat new_alpha; gfloat layer_alpha = layer[ALPHA] * opacity; if (mask) layer_alpha *= *mask; new_alpha = layer_alpha + (1.0f - layer_alpha) * in[ALPHA]; if (layer_alpha == 0.0f || new_alpha == 0.0f) { out[RED] = in[RED]; out[GREEN] = in[GREEN]; out[BLUE] = in[BLUE]; } else { gfloat ratio = layer_alpha / new_alpha; gint b; for (b = RED; b < ALPHA; b++) out[b] = ratio * (in[ALPHA] * (comp[b] - layer[b]) + layer[b] - in[b]) + in[b]; } out[ALPHA] = new_alpha; in += 4; layer += 4; comp += 4; out += 4; if (mask) mask++; } } static inline void compfun_dst_atop (gfloat *in, gfloat *layer, gfloat *comp, gfloat *mask, gfloat opacity, gfloat *out, gint samples) { while (samples--) { gfloat layer_alpha = layer[ALPHA] * opacity; if (mask) layer_alpha *= *mask; if (layer_alpha == 0.0f) { out[RED] = in[RED]; out[GREEN] = in[GREEN]; out[BLUE] = in[BLUE]; } else { gint b; for (b = RED; b < ALPHA; b++) out[b] = comp[b] * in[ALPHA] + layer[b] * (1.0f - in[ALPHA]); } out[ALPHA] = layer_alpha; in += 4; layer += 4; comp += 4; out += 4; if (mask) mask++; } } static inline void compfun_src_in (gfloat *in, gfloat *layer, gfloat *comp, gfloat *mask, gfloat opacity, gfloat *out, gint samples) { while (samples--) { gfloat new_alpha = in[ALPHA] * layer[ALPHA] * opacity; if (mask) new_alpha *= *mask; if (new_alpha == 0.0f) { out[RED] = in[RED]; out[GREEN] = in[GREEN]; out[BLUE] = in[BLUE]; } else { out[RED] = layer[RED]; out[GREEN] = layer[GREEN]; out[BLUE] = layer[BLUE]; } out[ALPHA] = new_alpha; in += 4; out += 4; layer += 4; if (mask) mask++; } } #if COMPILE_SSE2_INTRINISICS #include static inline void compfun_src_atop_sse2 (gfloat *in, gfloat *layer, gfloat *comp, gfloat *mask, gfloat opacity, gfloat *out, gint samples) { if ((((uintptr_t)in) | /* alignment check */ ((uintptr_t)mask) | ((uintptr_t)comp) | ((uintptr_t)layer) | ((uintptr_t)out) ) & 0x0F) { return compfun_src_atop (in, layer, comp, mask, opacity, out, samples); } else { const __v4sf *v_in = (const __v4sf*) in; const __v4sf *v_layer = (const __v4sf*) layer; __v4sf *v_out = (__v4sf*) out; const __v4sf v_one = _mm_set1_ps (1.0f); const __v4sf v_opacity = _mm_set1_ps (opacity); while (samples--) { __v4sf alpha, rgba_in, rgba_layer; rgba_in = *v_in ++; rgba_layer = *v_layer++; alpha = (__v4sf)_mm_shuffle_epi32((__m128i)rgba_layer,_MM_SHUFFLE(3,3,3,3)) * v_opacity; if (mask) { alpha = alpha * _mm_set1_ps (*mask++); } if (_mm_ucomigt_ss (alpha, _mm_setzero_ps ())) { __v4sf out_pixel, out_pixel_rbaa, out_alpha; out_alpha = (__v4sf)_mm_shuffle_epi32((__m128i)rgba_in,_MM_SHUFFLE(3,3,3,3)); out_pixel = rgba_layer * alpha + rgba_in * (v_one - alpha); out_pixel_rbaa = _mm_shuffle_ps (out_pixel, out_alpha, _MM_SHUFFLE (3, 3, 2, 0)); out_pixel = _mm_shuffle_ps (out_pixel, out_pixel_rbaa, _MM_SHUFFLE (2, 1, 1, 0)); *v_out++ = out_pixel; } else { *v_out ++ = rgba_in; } } } } #endif static inline void gimp_composite_blend (gpointer op, gfloat *in, gfloat *layer, gfloat *mask, gfloat *out, glong samples, GimpBlendFunc blend_func) { GimpOperationLayerMode *layer_mode = op; 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; gfloat *blend_in = in; gfloat *blend_layer = layer; gfloat *blend_out = out; gfloat *composite_in = NULL; gfloat *composite_layer = NULL; gboolean composite_needs_in_color = composite_mode == GIMP_LAYER_COMPOSITE_SRC_OVER || composite_mode == GIMP_LAYER_COMPOSITE_SRC_ATOP; gboolean composite_needs_layer_color = composite_mode == GIMP_LAYER_COMPOSITE_SRC_OVER || composite_mode == GIMP_LAYER_COMPOSITE_DST_ATOP; const Babl *fish_to_blend = NULL; const Babl *fish_to_composite = NULL; const Babl *fish_from_composite = NULL; switch (blend_space) { default: case GIMP_LAYER_COLOR_SPACE_RGB_LINEAR: fish_to_blend = NULL; switch (composite_space) { case GIMP_LAYER_COLOR_SPACE_LAB: fish_to_composite = _gimp_fish_rgba_to_laba; fish_from_composite = _gimp_fish_laba_to_rgba; break; default: case GIMP_LAYER_COLOR_SPACE_RGB_LINEAR: fish_to_composite = NULL; fish_from_composite = NULL; break; case GIMP_LAYER_COLOR_SPACE_RGB_PERCEPTUAL: fish_to_composite = _gimp_fish_rgba_to_perceptual; fish_from_composite = _gimp_fish_perceptual_to_rgba; break; } break; case GIMP_LAYER_COLOR_SPACE_LAB: fish_to_blend = _gimp_fish_rgba_to_laba; switch (composite_space) { case GIMP_LAYER_COLOR_SPACE_LAB: default: fish_to_composite = NULL; fish_from_composite = _gimp_fish_laba_to_rgba; break; case GIMP_LAYER_COLOR_SPACE_RGB_LINEAR: fish_to_composite = _gimp_fish_laba_to_rgba; fish_from_composite = NULL; break; case GIMP_LAYER_COLOR_SPACE_RGB_PERCEPTUAL: fish_to_composite = _gimp_fish_laba_to_perceptual; fish_from_composite = _gimp_fish_perceptual_to_rgba; break; } break; case GIMP_LAYER_COLOR_SPACE_RGB_PERCEPTUAL: fish_to_blend = _gimp_fish_rgba_to_perceptual; switch (composite_space) { case GIMP_LAYER_COLOR_SPACE_LAB: default: fish_to_composite = _gimp_fish_perceptual_to_laba; fish_from_composite = NULL; break; case GIMP_LAYER_COLOR_SPACE_RGB_LINEAR: fish_to_composite = _gimp_fish_perceptual_to_rgba; fish_from_composite = NULL; break; case GIMP_LAYER_COLOR_SPACE_RGB_PERCEPTUAL: fish_to_composite = NULL; fish_from_composite = _gimp_fish_perceptual_to_rgba; break; } break; } if (in == out) /* in-place detected, avoid clobbering since we need to read it for the compositing stage */ blend_out = g_alloca (sizeof (gfloat) * 4 * samples); if (fish_to_blend) { if (in != out || (composite_needs_in_color && composite_space == GIMP_LAYER_COLOR_SPACE_RGB_LINEAR)) { /* 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); babl_process (fish_to_blend, in, blend_in, samples); babl_process (fish_to_blend, layer, blend_layer, samples); } blend_func (blend_in, blend_layer, blend_out, samples); composite_in = blend_in; composite_layer = blend_layer; if (fish_to_composite) { if (composite_space == GIMP_LAYER_COLOR_SPACE_RGB_LINEAR) { composite_in = in; composite_layer = layer; } else { if (composite_needs_in_color) { if (composite_in == in && in != out) composite_in = g_alloca (sizeof (gfloat) * 4 * samples); babl_process (fish_to_composite, blend_in, composite_in, samples); } if (composite_needs_layer_color) { if (composite_layer == layer) composite_layer = g_alloca (sizeof (gfloat) * 4 * samples); babl_process (fish_to_composite, blend_layer, composite_layer, samples); } } babl_process (fish_to_composite, blend_out, blend_out, samples); } switch (composite_mode) { case GIMP_LAYER_COMPOSITE_SRC_ATOP: default: compfun_src_atop_dispatch (composite_in, blend_out, NULL, mask, opacity, out, samples); break; case GIMP_LAYER_COMPOSITE_SRC_OVER: compfun_src_over_dispatch (composite_in, composite_layer, blend_out, mask, opacity, out, samples); break; case GIMP_LAYER_COMPOSITE_DST_ATOP: compfun_dst_atop_dispatch (composite_in, composite_layer, blend_out, mask, opacity, out, samples); break; case GIMP_LAYER_COMPOSITE_SRC_IN: compfun_src_in_dispatch (composite_in, blend_out, NULL, mask, opacity, out, samples); break; } if (fish_from_composite) { babl_process (fish_from_composite, out, out, samples); } } static inline void blendfun_screen (const float *dest, const float *src, float *out, int samples) { while (samples--) { if (dest[ALPHA] != 0.0f && src[ALPHA] != 0.0f) { gint c; for (c = 0; c < 3; c++) out[c] = 1.0f - (1.0f - dest[c]) * (1.0f - src[c]); } out[ALPHA] = src[ALPHA]; out += 4; src += 4; dest += 4; } } static inline void /* aka linear_dodge */ blendfun_addition (const float *dest, const float *src, float *out, int samples) { while (samples--) { if (dest[ALPHA] != 0.0f && src[ALPHA] != 0.0f) { gint c; for (c = 0; c < 3; c++) out[c] = dest[c] + src[c]; } out[ALPHA] = src[ALPHA]; out += 4; src += 4; dest += 4; } } static inline void blendfun_linear_burn (const float *dest, const float *src, float *out, int samples) { while (samples--) { if (dest[ALPHA] != 0.0f && src[ALPHA] != 0.0f) { gint c; for (c = 0; c < 3; c++) out[c] = dest[c] + src[c] - 1.0f; } out[ALPHA] = src[ALPHA]; out += 4; src += 4; dest += 4; } } static inline void blendfun_subtract (const float *dest, const float *src, float *out, int samples) { while (samples--) { if (dest[ALPHA] != 0.0f && src[ALPHA] != 0.0f) { gint c; for (c = 0; c < 3; c++) out[c] = dest[c] - src[c]; } out[ALPHA] = src[ALPHA]; out += 4; src += 4; dest += 4; } } static inline void blendfun_multiply (const float *dest, const float *src, float *out, int samples) { while (samples--) { if (dest[ALPHA] != 0.0f && src[ALPHA] != 0.0f) { gint c; for (c = 0; c < 3; c++) out[c] = dest[c] * src[c]; } out[ALPHA] = src[ALPHA]; out += 4; src += 4; dest += 4; } } static inline void blendfun_normal (const float *dest, const float *src, float *out, int samples) { while (samples--) { if (dest[ALPHA] != 0.0f && src[ALPHA] != 0.0f) { gint c; for (c = 0; c < 3; c++) out[c] = src[c]; } out[ALPHA] = src[ALPHA]; out += 4; src += 4; dest += 4; } } static inline void blendfun_burn (const float *dest, const float *src, float *out, int samples) { while (samples--) { if (dest[ALPHA] != 0.0f && src[ALPHA] != 0.0f) { gint c; for (c = 0; c < 3; c++) { gfloat comp = 1.0f - (1.0f - dest[c]) / src[c]; /* The CLAMP macro is deliberately inlined and written * to map comp == NAN (0 / 0) -> 1 */ out[c] = comp < 0 ? 0.0f : comp < 1.0f ? comp : 1.0f; } } out[ALPHA] = src[ALPHA]; out += 4; src += 4; dest += 4; } } static inline void blendfun_darken_only (const float *dest, const float *src, float *out, int samples) { while (samples--) { if (dest[ALPHA] != 0.0f && src[ALPHA] != 0.0f) { gint c; for (c = 0; c < 3; c++) out[c] = MIN (dest[c], src[c]); } out[ALPHA] = src[ALPHA]; out += 4; src += 4; dest += 4; } } static inline void blendfun_luminance_lighten_only (const float *dest, const float *src, float *out, int samples) { while (samples--) { if (dest[ALPHA] != 0.0f && src[ALPHA] != 0.0f) { gint c; float dest_luminance = GIMP_RGB_LUMINANCE(dest[0], dest[1], dest[2]); float src_luminance = GIMP_RGB_LUMINANCE(src[0], src[1], src[2]); if (dest_luminance >= src_luminance) for (c = 0; c < 3; c++) out[c] = dest[c]; else for (c = 0; c < 3; c++) out[c] = src[c]; } out[ALPHA] = src[ALPHA]; out += 4; src += 4; dest += 4; } } static inline void blendfun_luminance_darken_only (const float *dest, const float *src, float *out, int samples) { while (samples--) { if (dest[ALPHA] != 0.0f && src[ALPHA] != 0.0f) { gint c; float dest_luminance = GIMP_RGB_LUMINANCE(dest[0], dest[1], dest[2]); float src_luminance = GIMP_RGB_LUMINANCE(src[0], src[1], src[2]); if (dest_luminance <= src_luminance) for (c = 0; c < 3; c++) out[c] = dest[c]; else for (c = 0; c < 3; c++) out[c] = src[c]; } out[ALPHA] = src[ALPHA]; out += 4; src += 4; dest += 4; } } static inline void blendfun_lighten_only (const float *dest, const float *src, float *out, int samples) { while (samples--) { if (dest[ALPHA] != 0.0f && src[ALPHA] != 0.0f) { gint c; for (c = 0; c < 3; c++) out[c] = MAX (dest[c], src[c]); } out[ALPHA] = src[ALPHA]; out += 4; src += 4; dest += 4; } } static inline void blendfun_difference (const float *dest, const float *src, float *out, int samples) { while (samples--) { if (dest[ALPHA] != 0.0f && src[ALPHA] != 0.0f) { gint c; for (c = 0; c < 3; c++) { out[c] = dest[c] - src[c]; if (out[c] < 0) out[c] = -out[c]; } } out[ALPHA] = src[ALPHA]; out += 4; src += 4; dest += 4; } } static inline void blendfun_divide (const float *dest, const float *src, float *out, int samples) { while (samples--) { if (dest[ALPHA] != 0.0f && src[ALPHA] != 0.0f) { gint c; for (c = 0; c < 3; c++) { gfloat comp = dest[c] / src[c]; /* make infinities(or NaN) correspond to a high number, * to get more predictable math, ideally higher than 5.0 * but it seems like some babl conversions might be * acting up then */ if (!(comp > -42949672.0f && comp < 5.0f)) comp = 5.0f; out[c] = comp; } } out[ALPHA] = src[ALPHA]; out += 4; src += 4; dest += 4; } } static inline void blendfun_dodge (const float *dest, const float *src, float *out, int samples) { while (samples--) { if (dest[ALPHA] != 0.0f && src[ALPHA] != 0.0f) { gint c; for (c = 0; c < 3; c++) { gfloat comp = dest[c] / (1.0f - src[c]); comp = MIN (comp, 1.0f); out[c] = comp; } } out[ALPHA] = src[ALPHA]; out += 4; src += 4; dest += 4; } } static inline void blendfun_grain_extract (const float *dest, const float *src, float *out, int samples) { while (samples--) { if (dest[ALPHA] != 0.0f && src[ALPHA] != 0.0f) { gint c; for (c = 0; c < 3; c++) out[c] = dest[c] - src[c] + 0.5f; } out[ALPHA] = src[ALPHA]; out += 4; src += 4; dest += 4; } } static inline void blendfun_grain_merge (const float *dest, const float *src, float *out, int samples) { while (samples--) { if (dest[ALPHA] != 0.0f && src[ALPHA] != 0.0f) { gint c; for (c = 0; c < 3; c++) out[c] = dest[c] + src[c] - 0.5f; } out[ALPHA] = src[ALPHA]; out += 4; src += 4; dest += 4; } } static inline void blendfun_hardlight (const float *dest, const float *src, float *out, int samples) { while (samples--) { if (dest[ALPHA] != 0.0f && src[ALPHA] != 0.0f) { gint c; for (c = 0; c < 3; c++) { gfloat comp; if (src[c] > 0.5f) { comp = (1.0f - dest[c]) * (1.0f - (src[c] - 0.5f) * 2.0f); comp = MIN (1 - comp, 1); } else { comp = dest[c] * (src[c] * 2.0f); comp = MIN (comp, 1.0f); } out[c] = comp; } } out[ALPHA] = src[ALPHA]; out += 4; src += 4; dest += 4; } } static inline void blendfun_softlight (const float *dest, const float *src, float *out, int samples) { while (samples--) { if (dest[ALPHA] != 0.0f && src[ALPHA] != 0.0f) { gint c; for (c = 0; c < 3; c++) { gfloat multiply = dest[c] * src[c]; gfloat screen = 1.0f - (1.0f - dest[c]) * (1.0f - src[c]); gfloat comp = (1.0f - dest[c]) * multiply + dest[c] * screen; out[c] = comp; } } out[ALPHA] = src[ALPHA]; out += 4; src += 4; dest += 4; } } static inline void blendfun_overlay (const float *dest, const float *src, float *out, int samples) { while (samples--) { if (dest[ALPHA] != 0.0f && src[ALPHA] != 0.0f) { gint c; for (c = 0; c < 3; c++) { gfloat comp; if (dest[c] < 0.5f) { comp = 2.0f * dest[c] * src[c]; } else { comp = 1.0f - 2.0f * (1.0f - src[c]) * (1.0f - dest[c]); } out[c] = comp; } } out[ALPHA] = src[ALPHA]; out += 4; src += 4; dest += 4; } } static inline void blendfun_hsv_color (const float *dest, const float *src, float *out, int samples) { while (samples--) { if (dest[ALPHA] != 0.0f && src[ALPHA] != 0.0f) { GimpRGB dest_rgb = { dest[0], dest[1], dest[2] }; GimpRGB src_rgb = { src[0], src[1], src[2] }; GimpHSL src_hsl, dest_hsl; gimp_rgb_to_hsl (&dest_rgb, &dest_hsl); gimp_rgb_to_hsl (&src_rgb, &src_hsl); dest_hsl.h = src_hsl.h; dest_hsl.s = src_hsl.s; gimp_hsl_to_rgb (&dest_hsl, &dest_rgb); out[RED] = dest_rgb.r; out[GREEN] = dest_rgb.g; out[BLUE] = dest_rgb.b; } out[ALPHA] = src[ALPHA]; out += 4; src += 4; dest += 4; } } static inline void blendfun_hsv_hue (const float *dest, const float *src, float *out, int samples) { while (samples--) { if (dest[ALPHA] != 0.0f && src[ALPHA] != 0.0f) { GimpRGB dest_rgb = { dest[0], dest[1], dest[2] }; GimpRGB src_rgb = { src[0], src[1], src[2] }; GimpHSV src_hsv, dest_hsv; gimp_rgb_to_hsv (&dest_rgb, &dest_hsv); gimp_rgb_to_hsv (&src_rgb, &src_hsv); dest_hsv.h = src_hsv.h; gimp_hsv_to_rgb (&dest_hsv, &dest_rgb); out[RED] = dest_rgb.r; out[GREEN] = dest_rgb.g; out[BLUE] = dest_rgb.b; } out[ALPHA] = src[ALPHA]; out += 4; src += 4; dest += 4; } } static inline void blendfun_hsv_saturation (const float *dest, const float *src, float *out, int samples) { while (samples--) { if (dest[ALPHA] != 0.0f && src[ALPHA] != 0.0f) { GimpRGB dest_rgb = { dest[0], dest[1], dest[2] }; GimpRGB src_rgb = { src[0], src[1], src[2] }; GimpHSV src_hsv, dest_hsv; gimp_rgb_to_hsv (&dest_rgb, &dest_hsv); gimp_rgb_to_hsv (&src_rgb, &src_hsv); dest_hsv.s = src_hsv.s; gimp_hsv_to_rgb (&dest_hsv, &dest_rgb); out[RED] = dest_rgb.r; out[GREEN] = dest_rgb.g; out[BLUE] = dest_rgb.b; } out[ALPHA] = src[ALPHA]; out += 4; src += 4; dest += 4; } } static inline void blendfun_hsv_value (const float *dest, const float *src, float *out, int samples) { while (samples--) { if (dest[ALPHA] != 0.0f && src[ALPHA] != 0.0f) { GimpRGB dest_rgb = { dest[0], dest[1], dest[2] }; GimpRGB src_rgb = { src[0], src[1], src[2] }; GimpHSV src_hsv, dest_hsv; gimp_rgb_to_hsv (&dest_rgb, &dest_hsv); gimp_rgb_to_hsv (&src_rgb, &src_hsv); dest_hsv.v = src_hsv.v; gimp_hsv_to_rgb (&dest_hsv, &dest_rgb); out[RED] = dest_rgb.r; out[GREEN] = dest_rgb.g; out[BLUE] = dest_rgb.b; } out[ALPHA] = src[ALPHA]; out += 4; src += 4; dest += 4; } } static inline void blendfun_lch_chroma (const float *dest, const float *src, float *out, int samples) { while (samples--) { if (dest[ALPHA] != 0.0f && src[ALPHA] != 0.0f) { gfloat A1 = dest[1]; gfloat B1 = dest[2]; gfloat c1 = hypotf (A1, B1); if (c1 != 0.0f) { gfloat A2 = src[1]; gfloat B2 = src[2]; gfloat c2 = hypotf (A2, B2); gfloat A = c2 * A1 / c1; gfloat B = c2 * B1 / c1; out[0] = dest[0]; out[1] = A; out[2] = B; } } out[ALPHA] = src[ALPHA]; out += 4; src += 4; dest += 4; } } static inline void blendfun_lch_color (const float *dest, const float *src, float *out, int samples) { while (samples--) { if (dest[ALPHA] != 0.0f && src[ALPHA] != 0.0f) { out[0] = dest[0]; out[1] = src[1]; out[2] = src[2]; } out[ALPHA] = src[ALPHA]; out += 4; src += 4; dest += 4; } } static inline void blendfun_lch_hue (const float *dest, const float *src, float *out, int samples) { while (samples--) { if (dest[ALPHA] != 0.0f && src[ALPHA] != 0.0f) { gfloat A2 = src[1]; gfloat B2 = src[2]; gfloat c2 = hypotf (A2, B2); if (c2 > 0.1f) { gfloat A1 = dest[1]; gfloat B1 = dest[2]; gfloat c1 = hypotf (A1, B1); gfloat A = c1 * A2 / c2; gfloat B = c1 * B2 / c2; out[0] = dest[0]; out[1] = A; out[2] = B; } else { out[0] = dest[0]; out[1] = dest[1]; out[2] = dest[2]; } } out[ALPHA] = src[ALPHA]; out += 4; src += 4; dest += 4; } } static inline void blendfun_lch_lightness (const float *dest, const float *src, float *out, int samples) { while (samples--) { if (dest[ALPHA] != 0.0f && src[ALPHA] != 0.0f) { out[0] = src[0]; out[1] = dest[1]; out[2] = dest[2]; } out[ALPHA] = src[ALPHA]; out += 4; src += 4; dest += 4; } } static inline void blendfun_copy (const float *dest, const float *src, float *out, int samples) { while (samples--) { gint c; for (c = 0; c < 4; c++) out[c] = src[c]; out[ALPHA] = src[ALPHA]; out += 4; src += 4; dest += 4; } } /* added according to: http://www.deepskycolors.com/archivo/2010/04/21/formulas-for-Photoshop-blending-modes.html */ static inline void blendfun_vivid_light (const float *dest, const float *src, float *out, int samples) { while (samples--) { if (dest[ALPHA] != 0.0f && src[ALPHA] != 0.0f) { gint c; for (c = 0; c < 3; c++) { gfloat comp; if (src[c] > 0.5f) { comp = (1.0f - (1.0f - dest[c]) / (2.0f * (src[c]))); } else { comp = dest[c] / (1.0f - 2.0f * (src[c] - 0.5)); } comp = MIN (comp, 1.0f); out[c] = comp; } } out[ALPHA] = src[ALPHA]; out += 4; src += 4; dest += 4; } } /* added according to: http://www.deepskycolors.com/archivo/2010/04/21/formulas-for-Photoshop-blending-modes.html */ static inline void blendfun_linear_light (const float *dest, const float *src, float *out, int samples) { while (samples--) { if (dest[ALPHA] != 0.0f && src[ALPHA] != 0.0f) { gint c; for (c = 0; c < 3; c++) { gfloat comp; if (src[c] > 0.5f) { comp = dest[c] + 2.0 * (src[c] - 0.5); } else { comp = dest[c] + 2.0 * src[c] - 1.0; } out[c] = comp; } } out[ALPHA] = src[ALPHA]; out += 4; src += 4; dest += 4; } } /* added according to: http://www.deepskycolors.com/archivo/2010/04/21/formulas-for-Photoshop-blending-modes.html */ static inline void blendfun_pin_light (const float *dest, const float *src, float *out, int samples) { while (samples--) { if (dest[ALPHA] != 0.0f && src[ALPHA] != 0.0f) { gint c; for (c = 0; c < 3; c++) { gfloat comp; if (src[c] > 0.5f) { comp = MAX(dest[c], 2 * (src[c] - 0.5)); } else { comp = MIN(dest[c], 2 * src[c]); } out[c] = comp; } } out[ALPHA] = src[ALPHA]; out += 4; src += 4; dest += 4; } } static inline void blendfun_exclusion (const float *dest, const float *src, float *out, int samples) { while (samples--) { if (dest[ALPHA] != 0.0f && src[ALPHA] != 0.0f) { gint c; for (c = 0; c < 3; c++) { out[c] = 0.5f - 2.0f * (dest[c] - 0.5f) * (src[c] - 0.5f); } } out[ALPHA] = src[ALPHA]; out += 4; src += 4; dest += 4; } } static inline void dummy_fun(void) { } static inline GimpBlendFunc gimp_layer_mode_get_blend_fun (GimpLayerMode mode) { switch (mode) { case GIMP_LAYER_MODE_SCREEN_LINEAR: case GIMP_LAYER_MODE_SCREEN: return blendfun_screen; case GIMP_LAYER_MODE_ADDITION_LINEAR: case GIMP_LAYER_MODE_ADDITION: return blendfun_addition; case GIMP_LAYER_MODE_SUBTRACT_LINEAR: case GIMP_LAYER_MODE_SUBTRACT: return blendfun_subtract; case GIMP_LAYER_MODE_MULTIPLY_LINEAR: case GIMP_LAYER_MODE_MULTIPLY: return blendfun_multiply; case GIMP_LAYER_MODE_NORMAL_LINEAR: case GIMP_LAYER_MODE_NORMAL: return blendfun_normal; case GIMP_LAYER_MODE_BURN_LINEAR: case GIMP_LAYER_MODE_BURN: return blendfun_burn; case GIMP_LAYER_MODE_GRAIN_MERGE_LINEAR: case GIMP_LAYER_MODE_GRAIN_MERGE: return blendfun_grain_merge; case GIMP_LAYER_MODE_GRAIN_EXTRACT_LINEAR: case GIMP_LAYER_MODE_GRAIN_EXTRACT: return blendfun_grain_extract; case GIMP_LAYER_MODE_DODGE_LINEAR: case GIMP_LAYER_MODE_DODGE: return blendfun_dodge; case GIMP_LAYER_MODE_OVERLAY_LINEAR: case GIMP_LAYER_MODE_OVERLAY: return blendfun_overlay; case GIMP_LAYER_MODE_HSV_COLOR: return blendfun_hsv_color; case GIMP_LAYER_MODE_HSV_HUE: return blendfun_hsv_hue; case GIMP_LAYER_MODE_HSV_SATURATION: return blendfun_hsv_saturation; case GIMP_LAYER_MODE_HSV_VALUE: return blendfun_hsv_value; case GIMP_LAYER_MODE_LCH_CHROMA: return blendfun_lch_chroma; case GIMP_LAYER_MODE_LCH_COLOR: return blendfun_lch_color; case GIMP_LAYER_MODE_LCH_HUE: return blendfun_lch_hue; case GIMP_LAYER_MODE_LCH_LIGHTNESS: return blendfun_lch_lightness; case GIMP_LAYER_MODE_HARDLIGHT_LINEAR: case GIMP_LAYER_MODE_HARDLIGHT: return blendfun_hardlight; case GIMP_LAYER_MODE_SOFTLIGHT_LINEAR: case GIMP_LAYER_MODE_SOFTLIGHT: return blendfun_softlight; case GIMP_LAYER_MODE_DIVIDE: case GIMP_LAYER_MODE_DIVIDE_LINEAR: return blendfun_divide; case GIMP_LAYER_MODE_DIFFERENCE_LINEAR: case GIMP_LAYER_MODE_DIFFERENCE: return blendfun_difference; case GIMP_LAYER_MODE_DARKEN_ONLY: return blendfun_darken_only; case GIMP_LAYER_MODE_LIGHTEN_ONLY: return blendfun_lighten_only; case GIMP_LAYER_MODE_LUMINANCE_DARKEN_ONLY: case GIMP_LAYER_MODE_LUMA_DARKEN_ONLY: return blendfun_luminance_darken_only; case GIMP_LAYER_MODE_LUMINANCE_LIGHTEN_ONLY: case GIMP_LAYER_MODE_LUMA_LIGHTEN_ONLY: return blendfun_luminance_lighten_only; case GIMP_LAYER_MODE_VIVID_LIGHT_LINEAR: case GIMP_LAYER_MODE_VIVID_LIGHT: return blendfun_vivid_light; case GIMP_LAYER_MODE_PIN_LIGHT_LINEAR: case GIMP_LAYER_MODE_PIN_LIGHT: return blendfun_pin_light; case GIMP_LAYER_MODE_LINEAR_LIGHT_LINEAR: case GIMP_LAYER_MODE_LINEAR_LIGHT: return blendfun_linear_light; case GIMP_LAYER_MODE_EXCLUSION_LINEAR: case GIMP_LAYER_MODE_EXCLUSION: return blendfun_exclusion; case GIMP_LAYER_MODE_LINEAR_BURN_LINEAR: case GIMP_LAYER_MODE_LINEAR_BURN: return blendfun_linear_burn; case GIMP_LAYER_MODE_DISSOLVE: case GIMP_LAYER_MODE_BEHIND: case GIMP_LAYER_MODE_BEHIND_LINEAR: case GIMP_LAYER_MODE_MULTIPLY_LEGACY: case GIMP_LAYER_MODE_SCREEN_LEGACY: case GIMP_LAYER_MODE_OVERLAY_LEGACY: case GIMP_LAYER_MODE_DIFFERENCE_LEGACY: case GIMP_LAYER_MODE_ADDITION_LEGACY: case GIMP_LAYER_MODE_SUBTRACT_LEGACY: case GIMP_LAYER_MODE_DARKEN_ONLY_LEGACY: case GIMP_LAYER_MODE_LIGHTEN_ONLY_LEGACY: case GIMP_LAYER_MODE_HSV_HUE_LEGACY: case GIMP_LAYER_MODE_HSV_SATURATION_LEGACY: case GIMP_LAYER_MODE_HSV_COLOR_LEGACY: case GIMP_LAYER_MODE_HSV_VALUE_LEGACY: case GIMP_LAYER_MODE_DIVIDE_LEGACY: case GIMP_LAYER_MODE_DODGE_LEGACY: case GIMP_LAYER_MODE_BURN_LEGACY: case GIMP_LAYER_MODE_HARDLIGHT_LEGACY: case GIMP_LAYER_MODE_SOFTLIGHT_LEGACY: case GIMP_LAYER_MODE_GRAIN_EXTRACT_LEGACY: case GIMP_LAYER_MODE_GRAIN_MERGE_LEGACY: case GIMP_LAYER_MODE_COLOR_ERASE: case GIMP_LAYER_MODE_ERASE: case GIMP_LAYER_MODE_REPLACE: case GIMP_LAYER_MODE_ANTI_ERASE: return (void*)dummy_fun; } return (void*)dummy_fun; } struct _GimpOperationLayerModeInfo { GimpLayerMode layer_mode; gchar *mode_name; gchar *op_name; GimpLayerModeFlags flags; GimpLayerCompositeMode composite_mode; GimpLayerColorSpace composite_space; GimpLayerColorSpace blend_space; }; static GimpLayerModeInfo gimp_layer_mode_infos[]= { { GIMP_LAYER_MODE_NORMAL, "gimp:normal", 0, }, { GIMP_LAYER_MODE_DISSOLVE, "gimp:dissolve", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, }, { GIMP_LAYER_MODE_BEHIND, "gimp:behind", 0, }, { GIMP_LAYER_MODE_MULTIPLY_LEGACY, "gimp:multiply-legacy", GIMP_LAYER_MODE_FLAG_LEGACY }, { GIMP_LAYER_MODE_SCREEN_LEGACY, "gimp:screen-legacy", GIMP_LAYER_MODE_FLAG_LEGACY, }, { GIMP_LAYER_MODE_OVERLAY_LEGACY, "gimp:overlay-legacy", GIMP_LAYER_MODE_FLAG_LEGACY, }, { GIMP_LAYER_MODE_DIFFERENCE_LEGACY, "gimp:difference-legacy", GIMP_LAYER_MODE_FLAG_LEGACY, }, { GIMP_LAYER_MODE_ADDITION_LEGACY, "gimp:addition-legacy", GIMP_LAYER_MODE_FLAG_LEGACY, }, { GIMP_LAYER_MODE_SUBTRACT_LEGACY, "gimp:subtract-legacy", GIMP_LAYER_MODE_FLAG_LEGACY, }, { GIMP_LAYER_MODE_DARKEN_ONLY_LEGACY, "gimp:darken-only-legacy", GIMP_LAYER_MODE_FLAG_LEGACY, }, { GIMP_LAYER_MODE_LIGHTEN_ONLY_LEGACY, "gimp:lighten-only-legacy", GIMP_LAYER_MODE_FLAG_LEGACY, }, { GIMP_LAYER_MODE_HSV_HUE_LEGACY, "gimp:hsv-hue-legacy", GIMP_LAYER_MODE_FLAG_LEGACY, }, { GIMP_LAYER_MODE_HSV_SATURATION_LEGACY, "gimp:hsv-saturation-legacy", GIMP_LAYER_MODE_FLAG_LEGACY, }, { GIMP_LAYER_MODE_HSV_COLOR_LEGACY, "gimp:hsv-color-legacy", GIMP_LAYER_MODE_FLAG_LEGACY, }, { GIMP_LAYER_MODE_HSV_VALUE_LEGACY, "gimp:hsv-value-legacy", GIMP_LAYER_MODE_FLAG_LEGACY, }, { GIMP_LAYER_MODE_DIVIDE_LEGACY, "gimp:divide-legacy", GIMP_LAYER_MODE_FLAG_LEGACY, }, { GIMP_LAYER_MODE_DODGE_LEGACY, "gimp:dodge-legacy", GIMP_LAYER_MODE_FLAG_LEGACY, }, { GIMP_LAYER_MODE_BURN_LEGACY, "gimp:burn-legacy", GIMP_LAYER_MODE_FLAG_LEGACY, }, { GIMP_LAYER_MODE_HARDLIGHT_LEGACY, "gimp:hardlight-legacy", GIMP_LAYER_MODE_FLAG_LEGACY, }, { GIMP_LAYER_MODE_SOFTLIGHT_LEGACY, "gimp:softlight-legacy", GIMP_LAYER_MODE_FLAG_LEGACY, }, { GIMP_LAYER_MODE_GRAIN_EXTRACT_LEGACY, "gimp:grain-extract-legacy", GIMP_LAYER_MODE_FLAG_LEGACY, }, { GIMP_LAYER_MODE_GRAIN_MERGE_LEGACY, "gimp:grain-merge-legacy", GIMP_LAYER_MODE_FLAG_LEGACY, }, { GIMP_LAYER_MODE_COLOR_ERASE, "gimp:color-erase", 0, }, { GIMP_LAYER_MODE_OVERLAY, "gimp:layer-mode", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, GIMP_LAYER_COMPOSITE_SRC_ATOP, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR, GIMP_LAYER_COLOR_SPACE_RGB_PERCEPTUAL }, { GIMP_LAYER_MODE_LCH_HUE, "gimp:layer-mode", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, GIMP_LAYER_COMPOSITE_SRC_ATOP, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR, GIMP_LAYER_COLOR_SPACE_LAB }, { GIMP_LAYER_MODE_LCH_CHROMA, "gimp:layer-mode", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, GIMP_LAYER_COMPOSITE_SRC_ATOP, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR, GIMP_LAYER_COLOR_SPACE_LAB }, { GIMP_LAYER_MODE_LCH_COLOR, "gimp:layer-mode", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, GIMP_LAYER_COMPOSITE_SRC_ATOP, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR, GIMP_LAYER_COLOR_SPACE_LAB }, { GIMP_LAYER_MODE_LCH_LIGHTNESS, "gimp:layer-mode", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, GIMP_LAYER_COMPOSITE_SRC_ATOP, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR, GIMP_LAYER_COLOR_SPACE_LAB }, { GIMP_LAYER_MODE_NORMAL_LINEAR, "gimp:normal", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, }, { GIMP_LAYER_MODE_BEHIND_LINEAR, "gimp:behind", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, }, { GIMP_LAYER_MODE_MULTIPLY, "gimp:layer-mode", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, GIMP_LAYER_COMPOSITE_SRC_ATOP, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR, GIMP_LAYER_COLOR_SPACE_RGB_PERCEPTUAL }, { GIMP_LAYER_MODE_MULTIPLY_LINEAR, "gimp:layer-mode", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, GIMP_LAYER_COMPOSITE_SRC_ATOP, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR }, { GIMP_LAYER_MODE_SCREEN, "gimp:layer-mode", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, GIMP_LAYER_COMPOSITE_SRC_ATOP, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR, GIMP_LAYER_COLOR_SPACE_RGB_PERCEPTUAL }, { GIMP_LAYER_MODE_SCREEN_LINEAR, "gimp:layer-mode", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, GIMP_LAYER_COMPOSITE_SRC_ATOP, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR }, { GIMP_LAYER_MODE_OVERLAY_LINEAR, "gimp:layer-mode", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, GIMP_LAYER_COMPOSITE_SRC_ATOP, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR }, { GIMP_LAYER_MODE_DIFFERENCE, "gimp:layer-mode", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, GIMP_LAYER_COMPOSITE_SRC_ATOP, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR, GIMP_LAYER_COLOR_SPACE_RGB_PERCEPTUAL }, { GIMP_LAYER_MODE_DIFFERENCE_LINEAR, "gimp:layer-mode", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, GIMP_LAYER_COMPOSITE_SRC_ATOP, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR }, { GIMP_LAYER_MODE_ADDITION, "gimp:layer-mode", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, GIMP_LAYER_COMPOSITE_SRC_ATOP, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR, GIMP_LAYER_COLOR_SPACE_RGB_PERCEPTUAL }, { GIMP_LAYER_MODE_ADDITION_LINEAR, "gimp:layer-mode", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, GIMP_LAYER_COMPOSITE_SRC_ATOP, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR }, { GIMP_LAYER_MODE_SUBTRACT, "gimp:layer-mode", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, GIMP_LAYER_COMPOSITE_SRC_ATOP, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR, GIMP_LAYER_COLOR_SPACE_RGB_PERCEPTUAL }, { GIMP_LAYER_MODE_SUBTRACT_LINEAR, "gimp:layer-mode", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, GIMP_LAYER_COMPOSITE_SRC_ATOP, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR }, { GIMP_LAYER_MODE_DARKEN_ONLY, "gimp:layer-mode", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, GIMP_LAYER_COMPOSITE_SRC_ATOP, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR }, { GIMP_LAYER_MODE_LIGHTEN_ONLY, "gimp:layer-mode", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, GIMP_LAYER_COMPOSITE_SRC_ATOP, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR }, { GIMP_LAYER_MODE_HSV_HUE, "gimp:layer-mode", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, GIMP_LAYER_COMPOSITE_SRC_ATOP, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR, GIMP_LAYER_COLOR_SPACE_RGB_PERCEPTUAL }, { GIMP_LAYER_MODE_HSV_SATURATION, "gimp:layer-mode", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, GIMP_LAYER_COMPOSITE_SRC_ATOP, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR, GIMP_LAYER_COLOR_SPACE_RGB_PERCEPTUAL }, { GIMP_LAYER_MODE_HSV_COLOR, "gimp:layer-mode", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, GIMP_LAYER_COMPOSITE_SRC_ATOP, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR, GIMP_LAYER_COLOR_SPACE_RGB_PERCEPTUAL }, { GIMP_LAYER_MODE_HSV_VALUE, "gimp:layer-mode", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, GIMP_LAYER_COMPOSITE_SRC_ATOP, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR, GIMP_LAYER_COLOR_SPACE_RGB_PERCEPTUAL }, { GIMP_LAYER_MODE_DIVIDE, "gimp:layer-mode", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, GIMP_LAYER_COMPOSITE_SRC_ATOP, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR, GIMP_LAYER_COLOR_SPACE_RGB_PERCEPTUAL }, { GIMP_LAYER_MODE_DIVIDE_LINEAR, "gimp:layer-mode", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, GIMP_LAYER_COMPOSITE_SRC_ATOP, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR }, { GIMP_LAYER_MODE_DODGE, "gimp:layer-mode", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, GIMP_LAYER_COMPOSITE_SRC_ATOP, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR, GIMP_LAYER_COLOR_SPACE_RGB_PERCEPTUAL }, { GIMP_LAYER_MODE_DODGE_LINEAR, "gimp:layer-mode", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, GIMP_LAYER_COMPOSITE_SRC_ATOP, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR }, { GIMP_LAYER_MODE_BURN, "gimp:layer-mode", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, GIMP_LAYER_COMPOSITE_SRC_ATOP, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR, GIMP_LAYER_COLOR_SPACE_RGB_PERCEPTUAL }, { GIMP_LAYER_MODE_BURN_LINEAR, "gimp:layer-mode", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, GIMP_LAYER_COMPOSITE_SRC_ATOP, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR }, { GIMP_LAYER_MODE_HARDLIGHT, "gimp:layer-mode", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, GIMP_LAYER_COMPOSITE_SRC_ATOP, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR, GIMP_LAYER_COLOR_SPACE_RGB_PERCEPTUAL }, { GIMP_LAYER_MODE_HARDLIGHT_LINEAR, "gimp:layer-mode", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, GIMP_LAYER_COMPOSITE_SRC_ATOP, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR }, { GIMP_LAYER_MODE_SOFTLIGHT, "gimp:layer-mode", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, GIMP_LAYER_COMPOSITE_SRC_ATOP, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR, GIMP_LAYER_COLOR_SPACE_RGB_PERCEPTUAL }, { GIMP_LAYER_MODE_SOFTLIGHT_LINEAR, "gimp:layer-mode", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, GIMP_LAYER_COMPOSITE_SRC_ATOP, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR }, { GIMP_LAYER_MODE_GRAIN_EXTRACT, "gimp:layer-mode", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, GIMP_LAYER_COMPOSITE_SRC_ATOP, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR, GIMP_LAYER_COLOR_SPACE_RGB_PERCEPTUAL }, { GIMP_LAYER_MODE_GRAIN_EXTRACT_LINEAR, "gimp:layer-mode", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, GIMP_LAYER_COMPOSITE_SRC_ATOP, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR }, { GIMP_LAYER_MODE_GRAIN_MERGE, "gimp:layer-mode", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, GIMP_LAYER_COMPOSITE_SRC_ATOP, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR, GIMP_LAYER_COLOR_SPACE_RGB_PERCEPTUAL }, { GIMP_LAYER_MODE_GRAIN_MERGE_LINEAR, "gimp:layer-mode", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, GIMP_LAYER_COMPOSITE_SRC_ATOP, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR }, { GIMP_LAYER_MODE_VIVID_LIGHT, "gimp:layer-mode", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, GIMP_LAYER_COMPOSITE_SRC_ATOP, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR, GIMP_LAYER_COLOR_SPACE_RGB_PERCEPTUAL }, { GIMP_LAYER_MODE_VIVID_LIGHT_LINEAR, "gimp:layer-mode", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, GIMP_LAYER_COMPOSITE_SRC_ATOP, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR }, { GIMP_LAYER_MODE_PIN_LIGHT, "gimp:layer-mode", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, GIMP_LAYER_COMPOSITE_SRC_ATOP, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR, GIMP_LAYER_COLOR_SPACE_RGB_PERCEPTUAL }, { GIMP_LAYER_MODE_PIN_LIGHT_LINEAR, "gimp:layer-mode", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, GIMP_LAYER_COMPOSITE_SRC_ATOP, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR }, { GIMP_LAYER_MODE_LINEAR_LIGHT, "gimp:layer-mode", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, GIMP_LAYER_COMPOSITE_SRC_ATOP, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR, GIMP_LAYER_COLOR_SPACE_RGB_PERCEPTUAL }, { GIMP_LAYER_MODE_LINEAR_LIGHT_LINEAR, "gimp:layer-mode", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, GIMP_LAYER_COMPOSITE_SRC_ATOP, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR }, { GIMP_LAYER_MODE_EXCLUSION, "gimp:layer-mode", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, GIMP_LAYER_COMPOSITE_SRC_ATOP, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR, GIMP_LAYER_COLOR_SPACE_RGB_PERCEPTUAL }, { GIMP_LAYER_MODE_EXCLUSION_LINEAR, "gimp:layer-mode", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, GIMP_LAYER_COMPOSITE_SRC_ATOP, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR }, { GIMP_LAYER_MODE_LINEAR_BURN, "gimp:layer-mode", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, GIMP_LAYER_COMPOSITE_SRC_ATOP, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR, GIMP_LAYER_COLOR_SPACE_RGB_PERCEPTUAL }, { GIMP_LAYER_MODE_LINEAR_BURN_LINEAR, "gimp:layer-mode", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, GIMP_LAYER_COMPOSITE_SRC_ATOP, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR }, { GIMP_LAYER_MODE_LUMA_DARKEN_ONLY, "gimp:layer-mode", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, GIMP_LAYER_COMPOSITE_SRC_ATOP, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR, GIMP_LAYER_COLOR_SPACE_RGB_PERCEPTUAL }, { GIMP_LAYER_MODE_LUMINANCE_DARKEN_ONLY, "gimp:layer-mode", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, GIMP_LAYER_COMPOSITE_SRC_ATOP, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR }, { GIMP_LAYER_MODE_LUMA_LIGHTEN_ONLY, "gimp:layer-mode", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, GIMP_LAYER_COMPOSITE_SRC_ATOP, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR, GIMP_LAYER_COLOR_SPACE_RGB_PERCEPTUAL }, { GIMP_LAYER_MODE_LUMINANCE_LIGHTEN_ONLY, "gimp:layer-mode", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, GIMP_LAYER_COMPOSITE_SRC_ATOP, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR, GIMP_LAYER_COLOR_SPACE_RGB_LINEAR }, { GIMP_LAYER_MODE_ERASE, "gimp:erase", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, }, { GIMP_LAYER_MODE_REPLACE, "gimp:replace", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, }, { GIMP_LAYER_MODE_ANTI_ERASE, "gimp:anti-erase", GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA, }, }; const GimpLayerModeInfo *gimp_layer_mode_info (GimpLayerMode mode) { int i; for (i = 0; i < sizeof (gimp_layer_mode_infos) / sizeof (gimp_layer_mode_infos[0]); i++) { if (gimp_layer_mode_infos[i].layer_mode == mode) return &gimp_layer_mode_infos[i]; } return NULL; } #if 0 /* the following is test code for verifying that the above data matches the apis it takes over for */ #include "core/gimp-layer-modes.h" void verify_layer_mode_info (void); void verify_layer_mode_info (void) { int i; for (i = 0; i < sizeof (gimp_layer_mode_infos) / sizeof (gimp_layer_mode_infos[0]); i++) { GimpLayerModeInfo *info = &gimp_layer_mode_infos[i]; printf ("%i - %s\n", i, info->op_name); int mode = info->layer_mode; if (info->layer_mode != i) { printf (" enum mismatch\n"); } if ((gimp_layer_mode_is_legacy (mode) != ((info->flags & GIMP_LAYER_MODE_FLAG_LEGACY) != 0))) printf (" legacy mismatch\n"); if ((gimp_layer_mode_wants_linear_data (mode) != ((info->flags & GIMP_LAYER_MODE_FLAG_WANTS_LINEAR_DATA) != 0))) printf (" wants linear data mismatch\n"); if (!strcmp (info->op_name, "gimp:layer-mode")) { if (gimp_layer_mode_get_blend_space (mode) != info->blend_space) printf (" blend space mismatch\n"); if (gimp_layer_mode_get_composite_space (mode) != info->composite_space) printf (" composite space mismatch\n"); if (gimp_layer_mode_get_composite_mode (mode) != info->composite_mode) printf (" composite mode mismatch\n"); if (strcmp (gimp_layer_mode_get_operation (mode), info->op_name)) printf (" operation mismatch\n"); } } exit(0); } #endif