Gimp/app/gegl/gimpoperationpointlayermode.c
Martin Nordholts 3cd7f45a2b Reuse the blending formula from the legacy Soft light. (Actually the
* app/gegl/gimpoperationpointlayermode.c
(gimp_operation_point_layer_mode_process): Reuse the blending
formula from the legacy Soft light. (Actually the formula comes
from legacy Overlay but legacy Overlay and Soft light blends
pixels exactly the same.) I hereby declare the porting of the
layer modes to this GEGL operation complete. Summary:

Completely works the same:

  Normal, Dissolve, Behind, Color Erase, Erase, Anti Erase

Works the same for 100% opaque layers:

  Lighten only, Screen, Dodge, Addition, Darken only, Multiply,
  Dodge, Soft light, Hard light, Difference, Subtract, Grain
  extract, Grain merge, Divide, Hue, Saturation, Color, Value

Works different but similar:

  Overlay now uses the SVG 1.2 overlay formula which is different
  but similar to legacy Overlay

  Replace needs to be externally masked to not replace too much,
  but that is outside the scope of the layer mode porting.

svn path=/trunk/; revision=27541
2008-11-03 23:00:40 +00:00

624 lines
19 KiB
C

/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* gimpoperationpointlayermode.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 2 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, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "config.h"
#include <gegl-plugin.h>
#include "libgimpcolor/gimpcolor.h"
#include "gegl-types.h"
#include "paint-funcs/paint-funcs.h"
#include "gimpoperationpointlayermode.h"
/* The size of the area of which an evenly transparent Dissolve layer
* repeats its dissolve pattern
*/
#define DISSOLVE_REPEAT_WIDTH 400
#define DISSOLVE_REPEAT_HEIGHT 300
#define DISSOLVE_SEED 737893334
#define R (RED)
#define G (GREEN)
#define B (BLUE)
#define A (ALPHA)
#define inA (in[A])
#define inCa (in[c])
#define inC (in[A] ? in[c] / in[A] : 0.0)
#define layA (lay[A])
#define layCa (lay[c])
#define layC (lay[A] ? lay[c] / lay[A] : 0.0)
#define outCa (out[c])
#define outA (out[A])
#define outC (out[A] ? out[c] / out[A] : 0.0)
#define newCa (new[c])
#define EACH_CHANNEL(expr) \
for (c = RED; c < ALPHA; c++) \
{ \
expr; \
}
enum
{
PROP_0,
PROP_BLEND_MODE
};
struct _GimpOperationPointLayerModeClass
{
GeglOperationPointComposerClass parent_class;
};
struct _GimpOperationPointLayerMode
{
GeglOperationPointComposer parent_instance;
GimpLayerModeEffects blend_mode;
};
static void gimp_operation_point_layer_mode_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
static void gimp_operation_point_layer_mode_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec);
static void gimp_operation_point_layer_mode_prepare (GeglOperation *operation);
static gboolean gimp_operation_point_layer_mode_process (GeglOperation *operation,
void *in_buf,
void *aux_buf,
void *out_buf,
glong samples,
const GeglRectangle *roi);
G_DEFINE_TYPE (GimpOperationPointLayerMode, gimp_operation_point_layer_mode,
GEGL_TYPE_OPERATION_POINT_COMPOSER)
static guint32 dissolve_lut[DISSOLVE_REPEAT_WIDTH * DISSOLVE_REPEAT_HEIGHT];
static void
gimp_operation_point_layer_mode_class_init (GimpOperationPointLayerModeClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GeglOperationClass *operation_class = GEGL_OPERATION_CLASS (klass);
GeglOperationPointComposerClass *point_class = GEGL_OPERATION_POINT_COMPOSER_CLASS (klass);
GRand *rand = g_rand_new_with_seed (DISSOLVE_SEED);
int i;
object_class->set_property = gimp_operation_point_layer_mode_set_property;
object_class->get_property = gimp_operation_point_layer_mode_get_property;
operation_class->name = "gimp:point-layer-mode";
operation_class->description = "GIMP point layer mode operation";
operation_class->categories = "compositors";
operation_class->prepare = gimp_operation_point_layer_mode_prepare;
point_class->process = gimp_operation_point_layer_mode_process;
g_object_class_install_property (object_class, PROP_BLEND_MODE,
g_param_spec_enum ("blend-mode", NULL, NULL,
GIMP_TYPE_LAYER_MODE_EFFECTS,
GIMP_NORMAL_MODE,
GIMP_PARAM_READWRITE));
for (i = 0; i < DISSOLVE_REPEAT_WIDTH * DISSOLVE_REPEAT_HEIGHT; i++)
dissolve_lut[i] = g_rand_int (rand);
}
static void
gimp_operation_point_layer_mode_init (GimpOperationPointLayerMode *self)
{
}
static void
gimp_operation_point_layer_mode_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
GimpOperationPointLayerMode *self = GIMP_OPERATION_POINT_LAYER_MODE (object);
switch (property_id)
{
case PROP_BLEND_MODE:
self->blend_mode = g_value_get_enum (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gimp_operation_point_layer_mode_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
GimpOperationPointLayerMode *self = GIMP_OPERATION_POINT_LAYER_MODE (object);
switch (property_id)
{
case PROP_BLEND_MODE:
g_value_set_enum (value, self->blend_mode);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gimp_operation_point_layer_mode_prepare (GeglOperation *operation)
{
Babl *format = babl_format ("RaGaBaA float");
gegl_operation_set_format (operation, "input", format);
gegl_operation_set_format (operation, "output", format);
gegl_operation_set_format (operation, "aux", format);
}
static void
gimp_operation_point_layer_mode_get_new_color_hsv (GimpLayerModeEffects blend_mode,
const gfloat *in,
const gfloat *lay,
gfloat *new)
{
GimpRGB inRGB;
GimpHSV inHSV;
GimpRGB layRGB;
GimpHSV layHSV;
GimpRGB newRGB;
GimpHSV newHSV;
gimp_rgb_set (&inRGB, in[R], in[G], in[B]);
gimp_rgb_set (&layRGB, lay[R], lay[G], lay[B]);
gimp_rgb_to_hsv (&inRGB, &inHSV);
gimp_rgb_to_hsv (&layRGB, &layHSV);
switch (blend_mode)
{
case GIMP_HUE_MODE:
gimp_hsv_set (&newHSV, layHSV.h, inHSV.s, inHSV.v);
break;
case GIMP_SATURATION_MODE:
gimp_hsv_set (&newHSV, inHSV.h, layHSV.s, inHSV.v);
break;
case GIMP_COLOR_MODE:
gimp_hsv_set (&newHSV, layHSV.h, layHSV.s, inHSV.v);
break;
case GIMP_VALUE_MODE:
gimp_hsv_set (&newHSV, inHSV.h, inHSV.s, layHSV.v);
break;
default:
g_assert_not_reached ();
break;
}
gimp_hsv_to_rgb (&newHSV, &newRGB);
new[R] = newRGB.r;
new[G] = newRGB.g;
new[B] = newRGB.b;
}
static void
gimp_operation_point_layer_mode_get_new_color_hsl (GimpLayerModeEffects blend_mode,
const gfloat *in,
const gfloat *lay,
gfloat *new)
{
GimpRGB inRGB;
GimpHSL inHSL;
GimpRGB layRGB;
GimpHSL layHSL;
GimpRGB newRGB;
GimpHSL newHSL;
gimp_rgb_set (&inRGB, in[R], in[G], in[B]);
gimp_rgb_set (&layRGB, lay[R], lay[G], lay[B]);
gimp_rgb_to_hsl (&inRGB, &inHSL);
gimp_rgb_to_hsl (&layRGB, &layHSL);
switch (blend_mode)
{
case GIMP_HUE_MODE:
gimp_hsl_set (&newHSL, layHSL.h, inHSL.s, inHSL.l);
break;
case GIMP_SATURATION_MODE:
gimp_hsl_set (&newHSL, inHSL.h, layHSL.s, inHSL.l);
break;
case GIMP_COLOR_MODE:
gimp_hsl_set (&newHSL, layHSL.h, layHSL.s, inHSL.l);
break;
case GIMP_VALUE_MODE:
gimp_hsl_set (&newHSL, inHSL.h, inHSL.s, layHSL.l);
break;
default:
g_assert_not_reached ();
break;
}
gimp_hsl_to_rgb (&newHSL, &newRGB);
new[R] = newRGB.r;
new[G] = newRGB.g;
new[B] = newRGB.b;
}
static void
gimp_operation_point_layer_mode_get_color_erase_color (const gfloat *in,
const gfloat *lay,
gfloat *out)
{
GimpRGB inRGB;
GimpRGB layRGB;
if (inA <= 0.0)
gimp_rgba_set (&inRGB, 0.0, 0.0, 0.0, inA);
else
gimp_rgba_set (&inRGB, in[R] / inA, in[G] / inA, in[B] / inA, inA);
if (layA <= 0.0)
gimp_rgba_set (&layRGB, 0.0, 0.0, 0.0, layA);
else
gimp_rgba_set (&layRGB, lay[R] / layA, lay[G] / layA, lay[B] / layA, layA);
paint_funcs_color_erase_helper (&inRGB, &layRGB);
out[A] = inRGB.a;
out[R] = inRGB.r * out[A];
out[G] = inRGB.g * out[A];
out[B] = inRGB.b * out[A];
}
static gboolean
gimp_operation_point_layer_mode_process (GeglOperation *operation,
void *in_buf,
void *aux_buf,
void *out_buf,
glong samples,
const GeglRectangle *roi)
{
GimpOperationPointLayerMode *self = GIMP_OPERATION_POINT_LAYER_MODE (operation);
GimpLayerModeEffects blend_mode = self->blend_mode;
gfloat *in = in_buf; /* composite of layers below */
gfloat *lay = aux_buf; /* layer */
gfloat *out = out_buf; /* resulting composite */
glong sample = samples;
gint c = 0;
gint x = 0;
gint y = 0;
gfloat new[3] = { 0.0, 0.0, 0.0 };
while (sample--)
{
switch (blend_mode)
{
case GIMP_ERASE_MODE:
case GIMP_ANTI_ERASE_MODE:
case GIMP_COLOR_ERASE_MODE:
case GIMP_REPLACE_MODE:
case GIMP_DISSOLVE_MODE:
/* These modes handle alpha themselves */
break;
default:
/* Porter-Duff model for the rest */
outA = layA + inA - layA * inA;
}
switch (blend_mode)
{
case GIMP_ERASE_MODE:
/* Eraser mode */
outA = inA - inA * layA;
if (inA <= 0.0)
EACH_CHANNEL (
outCa = 0.0)
else
EACH_CHANNEL (
outCa = inC * outA);
break;
case GIMP_ANTI_ERASE_MODE:
/* Eraser mode */
outA = inA + (1 - inA) * layA;
if (inA <= 0.0)
EACH_CHANNEL (
outCa = 0.0)
else
EACH_CHANNEL (
outCa = inC * outA);
break;
case GIMP_COLOR_ERASE_MODE:
/* Paint mode */
gimp_operation_point_layer_mode_get_color_erase_color (in, lay, out);
break;
case GIMP_REPLACE_MODE:
/* Filter fade mode */
outA = layA;
EACH_CHANNEL(
outCa = layCa);
break;
case GIMP_DISSOLVE_MODE:
/* The layer pixel has layA probability of being composited
* with 100% opacity, else not all
*/
x = (roi->x + sample - (sample / roi->width) * roi->width) % DISSOLVE_REPEAT_WIDTH;
y = (roi->y + sample / roi->width) % DISSOLVE_REPEAT_HEIGHT;
if (layA * G_MAXUINT32 >= dissolve_lut[y * DISSOLVE_REPEAT_WIDTH + x])
{
outA = 1.0;
EACH_CHANNEL (
outCa = layC);
}
else
{
outA = inA;
EACH_CHANNEL (
outCa = inCa);
}
break;
case GIMP_NORMAL_MODE:
/* Porter-Duff A over B */
EACH_CHANNEL (
outCa = layCa + inCa * (1 - layA));
break;
case GIMP_BEHIND_MODE:
/* Porter-Duff B over A */
EACH_CHANNEL (
outCa = inCa + layCa * (1 - inA));
break;
case GIMP_MULTIPLY_MODE:
/* SVG 1.2 multiply */
EACH_CHANNEL (
outCa = layCa * inCa + layCa * (1 - inA) + inCa * (1 - layA));
break;
case GIMP_SCREEN_MODE:
/* SVG 1.2 screen */
EACH_CHANNEL (
outCa = layCa + inCa - layCa * inCa);
break;
case GIMP_DIFFERENCE_MODE:
/* SVG 1.2 difference */
EACH_CHANNEL (
outCa = inCa + layCa - 2 * MIN (layCa * inA, inCa * layA));
break;
case GIMP_DARKEN_ONLY_MODE:
/* SVG 1.2 darken */
EACH_CHANNEL (
outCa = MIN (layCa * inA, inCa * layA) + layCa * (1 - inA) + inCa * (1 - layA));
break;
case GIMP_LIGHTEN_ONLY_MODE:
/* SVG 1.2 lighten */
EACH_CHANNEL (
outCa = MAX (layCa * inA, inCa * layA) + layCa * (1 - inA) + inCa * (1 - layA));
break;
case GIMP_OVERLAY_MODE:
/* SVG 1.2 overlay */
EACH_CHANNEL (
if (2 * inCa < inA)
outCa = 2 * layCa * inCa + layCa * (1 - inA) + inCa * (1 - layA);
else
outCa = layA * inA - 2 * (inA - inCa) * (layA - layCa) + layCa * (1 - inA) + inCa * (1 - layA));
break;
case GIMP_DODGE_MODE:
/* SVG 1.2 color-dodge */
EACH_CHANNEL (
if (layCa * inA + inCa * layA >= layA * inA)
outCa = layA * inA + layCa * (1 - inA) + inCa * (1 - layA);
else
outCa = inCa * layA / (1 - layC) + layCa * (1 - inA) + inCa * (1 - layA));
break;
case GIMP_BURN_MODE:
/* SVG 1.2 color-burn */
EACH_CHANNEL (
if (layCa * inA + inCa * layA <= layA * inA)
outCa = layCa * (1 - inA) + inCa * (1 - layA);
else
outCa = layA * (layCa * inA + inCa * layA - layA * inA) / layCa + layCa * (1 - inA) + inCa * (1 - layA));
break;
case GIMP_HARDLIGHT_MODE:
/* SVG 1.2 hard-light */
EACH_CHANNEL (
if (2 * layCa < layA)
outCa = 2 * layCa * inCa + layCa * (1 - inA) + inCa * (1 - layA);
else
outCa = layA * inA - 2 * (inA - inCa) * (layA - layCa) + layCa * (1 - inA) + inCa * (1 - layA));
break;
case GIMP_SOFTLIGHT_MODE:
/* Custom SVG 1.2:
*
* f(Sc, Dc) = Dc * (Dc + (2 * Sc * (1 - Dc)))
*/
EACH_CHANNEL (
outCa = inCa * (layA * inC + (2 * layCa * (1 - inC))) + layCa * (1 - inA) + inCa * (1 - layA));
break;
case GIMP_ADDITION_MODE:
/* Custom SVG 1.2:
*
* if Dc + Sc >= 1
* f(Sc, Dc) = 1
* otherwise
* f(Sc, Dc) = Dc + Sc
*/
EACH_CHANNEL (
if (layCa * inA + inCa * layA >= layA * inA)
outCa = layA * inA + layCa * (1 - inA) + inCa * (1 - layA);
else
outCa = inCa + layCa);
break;
case GIMP_SUBTRACT_MODE:
/* Custom SVG 1.2:
*
* if Dc - Sc <= 0
* f(Sc, Dc) = 0
* otherwise
* f(Sc, Dc) = Dc - Sc
*/
EACH_CHANNEL (
if (inCa * layA - layCa * inA <= 0)
outCa = layCa * (1 - inA) + inCa * (1 - layA);
else
outCa = inCa + layCa - 2 * layCa * inA);
break;
case GIMP_GRAIN_EXTRACT_MODE:
/* Custom SVG 1.2:
*
* if Dc - Sc + 0.5 >= 1
* f(Sc, Dc) = 1
* otherwise if Dc - Sc + 0.5 <= 0
* f(Sc, Dc) = 0
* otherwise
* f(Sc, Dc) = f(Sc, Dc) = Dc - Sc + 0.5
*/
EACH_CHANNEL (
if (inCa * layA - layCa * inA + 0.5 * layA * inA >= layA * inA)
outCa = layA * inA + layCa * (1 - inA) + inCa * (1 - layA);
else if (inCa * layA - layCa * inA + 0.5 * layA * inA <= 0)
outCa = layCa * (1 - inA) + inCa * (1 - layA);
else
outCa = inCa + layCa - 2 * layCa * inA + 0.5 * inA * layA);
break;
case GIMP_GRAIN_MERGE_MODE:
/* Custom SVG 1.2:
*
* if Dc + Sc - 0.5 >= 1
* f(Sc, Dc) = 1
* otherwise if Dc + Sc - 0.5 <= 0
* f(Sc, Dc) = 0
* otherwise
* f(Sc, Dc) = f(Sc, Dc) = Dc + Sc - 0.5
*/
EACH_CHANNEL (
if (inCa * layA + layCa * inA - 0.5 * layA * inA >= layA * inA)
outCa = layA * inA + layCa * (1 - inA) + inCa * (1 - layA);
else if (inCa * layA + layCa * inA - 0.5 * layA * inA <= 0)
outCa = layCa * (1 - inA) + inCa * (1 - layA);
else
outCa = inCa + layCa - 0.5 * inA * layA);
break;
case GIMP_DIVIDE_MODE:
/* Custom SVG 1.2:
*
* if Dc / Sc > 1
* f(Sc, Dc) = 1
* otherwise
* f(Sc, Dc) = Dc / Sc
*/
EACH_CHANNEL (
if (layA == 0.0 || inCa / layCa > inA / layA)
outCa = layA * inA + layCa * (1 - inA) + inCa * (1 - layA);
else
outCa = inCa * layA * layA / layCa + layCa * (1 - inA) + inCa * (1 - layA));
break;
case GIMP_HUE_MODE:
case GIMP_SATURATION_MODE:
case GIMP_VALUE_MODE:
/* Custom SVG 1.2:
*
* f(Sc, Dc) = New color depending on mode
*/
gimp_operation_point_layer_mode_get_new_color_hsv (blend_mode,
in,
lay,
new);
EACH_CHANNEL (
outCa = newCa * layA * inA + layCa * (1 - inA) + inCa * (1 - layA));
break;
case GIMP_COLOR_MODE:
/* Custom SVG 1.2:
*
* f(Sc, Dc) = New color
*/
gimp_operation_point_layer_mode_get_new_color_hsl (blend_mode,
in,
lay,
new);
EACH_CHANNEL (
outCa = newCa * layA * inA + layCa * (1 - inA) + inCa * (1 - layA));
break;
default:
g_error ("Unknown layer mode");
break;
}
in += 4;
lay += 4;
out += 4;
}
return TRUE;
}