// ============================================================================
// BLENDER'S COLOUR BLEND MODES -- ONE TEXT, COMPILED AS BOTH GLSL AND C++.
//
// `ramp_blend` from `source/blender/blenkernel/intern/material.cc` and the HSV
// and HSL pairs from `source/blender/blenlib/intern/math_color.cc`, Blender
// source commit 61a36e08, BLENDER_VERSION 502. The enum is MA_RAMP_* from
// DNA_material_types.h.
//
// This is what the Mix node does for COLOUR data, and it is nineteen distinct
// functions, several of which are not a lerp of anything. proc-nodes
// implemented one of them (a plain lerp) and had no blend_type at all, so
// every colour Mix crossing the bridge came out as MIX regardless of what it
// said in Blender.
//
// The alpha channel is deliberately left alone by every mode except BLEND --
// that asymmetry is Blender's, not an oversight here.
//
// rgb_to_hsv is ported rather than using Godot's Color::get_h/s/v: Blender's
// adds 1e-20 to the divisors instead of branching on zero, so a grey or black
// input gives a defined hue and saturation rather than a NaN or an early out,
// and the four HSV modes are built on that behaviour.
//
// ---------------------------------------------------------------------------
// THE SUBSET
//
// NO REFERENCE OUT-PARAMETERS. The four conversions wrote three floats
// through `float &`; they return a vec3 now, and their call sites unpack it.
//
// NO ARRAY IN/OUT PARAMETER. Blender's signature has `r_col` as both the A
// input and the result; it is a vec4 in and a vec4 out here. The body still
// INDEXES it, which GLSL allows on a vector and which the C++ shim's
// operator[] provides.
// ============================================================================
#ifndef PN_BLENDER_RAMP_BLEND
#define PN_BLENDER_RAMP_BLEND
#ifndef BN_FN
#define BN_FN
#endif
const int BR_BLEND = 0;
const int BR_ADD = 1;
const int BR_MULT = 2;
const int BR_SUB = 3;
const int BR_SCREEN = 4;
const int BR_DIV = 5;
const int BR_DIFF = 6;
const int BR_DARK = 7;
const int BR_LIGHT = 8;
const int BR_OVERLAY = 9;
const int BR_DODGE = 10;
const int BR_BURN = 11;
const int BR_HUE = 12;
const int BR_SAT = 13;
const int BR_VAL = 14;
const int BR_COLOR = 15;
const int BR_SOFT = 16;
const int BR_LINEAR = 17;
const int BR_EXCLUSION = 18;
BN_FN vec3 br_rgb_to_hsv(float r, float g, float b) {
float h, s, v;
float k = 0.0f;
if (g < b) {
float t = g;
g = b;
b = t;
k = -1.0f;
}
float min_gb = b;
if (r < g) {
float t = r;
r = g;
g = t;
k = -2.0f / 6.0f - k;
min_gb = min(g, b);
}
float chroma = r - min_gb;
h = abs(k + (g - b) / (6.0f * chroma + 1e-20f));
s = chroma / (r + 1e-20f);
v = r;
return vec3(h, s, v);
}
BN_FN vec3 br_hsv_to_rgb(float h, float s, float v) {
float r, g, b;
float nr = abs(h * 6.0f - 3.0f) - 1.0f;
float ng = 2.0f - abs(h * 6.0f - 2.0f);
float nb = 2.0f - abs(h * 6.0f - 4.0f);
nr = clamp(nr, 0.0f, 1.0f);
nb = clamp(nb, 0.0f, 1.0f);
ng = clamp(ng, 0.0f, 1.0f);
r = ((nr - 1.0f) * s + 1.0f) * v;
g = ((ng - 1.0f) * s + 1.0f) * v;
b = ((nb - 1.0f) * s + 1.0f) * v;
return vec3(r, g, b);
}
// The HSL pair, same file. Not used by any blend mode -- it is here because
// Color Ramp's HSL colour mode needs it and this is where the math_color.cc
// ports live. Unlike rgb_to_hsv it DOES branch on the achromatic case rather
// than biasing the divisor, so grey gives h = s = 0 exactly.
BN_FN vec3 br_rgb_to_hsl(float r, float g, float b) {
float h, s, l;
float cmax = max(r, max(g, b));
float cmin = min(r, min(g, b));
l = min(1.0f, (cmax + cmin) / 2.0f);
if (cmax == cmin) {
h = s = 0.0f; // achromatic
} else {
float d = cmax - cmin;
s = l > 0.5f ? d / (2.0f - cmax - cmin) : d / (cmax + cmin);
if (cmax == r) {
h = (g - b) / d + (g < b ? 6.0f : 0.0f);
} else if (cmax == g) {
h = (b - r) / d + 2.0f;
} else {
h = (r - g) / d + 4.0f;
}
}
h /= 6.0f;
return vec3(h, s, l);
}
BN_FN vec3 br_hsl_to_rgb(float h, float s, float l) {
float r, g, b;
float nr = abs(h * 6.0f - 3.0f) - 1.0f;
float ng = 2.0f - abs(h * 6.0f - 2.0f);
float nb = 2.0f - abs(h * 6.0f - 4.0f);
nr = clamp(nr, 0.0f, 1.0f);
nb = clamp(nb, 0.0f, 1.0f);
ng = clamp(ng, 0.0f, 1.0f);
float chroma = (1.0f - abs(2.0f * l - 1.0f)) * s;
r = (nr - 0.5f) * chroma + l;
g = (ng - 0.5f) * chroma + l;
b = (nb - 0.5f) * chroma + l;
return vec3(r, g, b);
}
// r_col is BOTH the A input and the result, exactly as Blender's signature has
// it; col is B.
BN_FN vec4 br_ramp_blend(int type, vec4 r_col, float fac, vec4 col) {
float tmp;
float facm = 1.0f - fac;
switch (type) {
case BR_BLEND:
for (int i = 0; i < 4; i++) {
r_col[i] = facm * r_col[i] + fac * col[i];
}
break;
case BR_ADD:
for (int i = 0; i < 3; i++) {
r_col[i] += fac * col[i];
}
break;
case BR_MULT:
for (int i = 0; i < 3; i++) {
r_col[i] *= (facm + fac * col[i]);
}
break;
case BR_SCREEN:
for (int i = 0; i < 3; i++) {
r_col[i] = 1.0f - (facm + fac * (1.0f - col[i])) * (1.0f - r_col[i]);
}
break;
case BR_OVERLAY:
for (int i = 0; i < 3; i++) {
if (r_col[i] < 0.5f) {
r_col[i] *= (facm + 2.0f * fac * col[i]);
} else {
r_col[i] = 1.0f - (facm + 2.0f * fac * (1.0f - col[i])) * (1.0f - r_col[i]);
}
}
break;
case BR_SUB:
for (int i = 0; i < 3; i++) {
r_col[i] -= fac * col[i];
}
break;
case BR_DIV:
for (int i = 0; i < 3; i++) {
if (col[i] != 0.0f) {
r_col[i] = facm * r_col[i] + fac * r_col[i] / col[i];
}
}
break;
case BR_DIFF:
for (int i = 0; i < 3; i++) {
r_col[i] = facm * r_col[i] + fac * abs(r_col[i] - col[i]);
}
break;
case BR_EXCLUSION:
for (int i = 0; i < 3; i++) {
r_col[i] = max(facm * r_col[i] +
fac * (r_col[i] + col[i] - 2.0f * r_col[i] * col[i]),
0.0f);
}
break;
case BR_DARK:
for (int i = 0; i < 3; i++) {
r_col[i] = min(r_col[i], col[i]) * fac + r_col[i] * facm;
}
break;
case BR_LIGHT:
for (int i = 0; i < 3; i++) {
r_col[i] = max(r_col[i], col[i]) * fac + r_col[i] * facm;
}
break;
case BR_DODGE:
for (int i = 0; i < 3; i++) {
if (r_col[i] != 0.0f) {
tmp = 1.0f - fac * col[i];
if (tmp <= 0.0f) {
r_col[i] = 1.0f;
} else if ((tmp = r_col[i] / tmp) > 1.0f) {
r_col[i] = 1.0f;
} else {
r_col[i] = tmp;
}
}
}
break;
case BR_BURN:
for (int i = 0; i < 3; i++) {
tmp = facm + fac * col[i];
if (tmp <= 0.0f) {
r_col[i] = 0.0f;
} else if ((tmp = (1.0f - (1.0f - r_col[i]) / tmp)) < 0.0f) {
r_col[i] = 0.0f;
} else if (tmp > 1.0f) {
r_col[i] = 1.0f;
} else {
r_col[i] = tmp;
}
}
break;
case BR_HUE: {
float rH, rS, rV, colH, colS, colV, tr, tg, tb;
{ vec3 _hsv1 = br_rgb_to_hsv(col[0], col[1], col[2]); colH = _hsv1.x; colS = _hsv1.y; colV = _hsv1.z; }
if (colS != 0.0f) {
{ vec3 _hsv2 = br_rgb_to_hsv(r_col[0], r_col[1], r_col[2]); rH = _hsv2.x; rS = _hsv2.y; rV = _hsv2.z; }
{ vec3 _hsv3 = br_hsv_to_rgb(colH, rS, rV); tr = _hsv3.x; tg = _hsv3.y; tb = _hsv3.z; }
r_col[0] = facm * r_col[0] + fac * tr;
r_col[1] = facm * r_col[1] + fac * tg;
r_col[2] = facm * r_col[2] + fac * tb;
}
break;
}
case BR_SAT: {
float rH, rS, rV, colH, colS, colV;
{ vec3 _hsv4 = br_rgb_to_hsv(r_col[0], r_col[1], r_col[2]); rH = _hsv4.x; rS = _hsv4.y; rV = _hsv4.z; }
if (rS != 0.0f) {
{ vec3 _hsv5 = br_rgb_to_hsv(col[0], col[1], col[2]); colH = _hsv5.x; colS = _hsv5.y; colV = _hsv5.z; }
{ vec3 _hsv6 = br_hsv_to_rgb(rH, facm * rS + fac * colS, rV); r_col[0] = _hsv6.x; r_col[1] = _hsv6.y; r_col[2] = _hsv6.z; }
}
break;
}
case BR_VAL: {
float rH, rS, rV, colH, colS, colV;
{ vec3 _hsv7 = br_rgb_to_hsv(r_col[0], r_col[1], r_col[2]); rH = _hsv7.x; rS = _hsv7.y; rV = _hsv7.z; }
{ vec3 _hsv8 = br_rgb_to_hsv(col[0], col[1], col[2]); colH = _hsv8.x; colS = _hsv8.y; colV = _hsv8.z; }
{ vec3 _hsv9 = br_hsv_to_rgb(rH, rS, facm * rV + fac * colV); r_col[0] = _hsv9.x; r_col[1] = _hsv9.y; r_col[2] = _hsv9.z; }
break;
}
case BR_COLOR: {
float rH, rS, rV, colH, colS, colV, tr, tg, tb;
{ vec3 _hsv10 = br_rgb_to_hsv(col[0], col[1], col[2]); colH = _hsv10.x; colS = _hsv10.y; colV = _hsv10.z; }
if (colS != 0.0f) {
{ vec3 _hsv11 = br_rgb_to_hsv(r_col[0], r_col[1], r_col[2]); rH = _hsv11.x; rS = _hsv11.y; rV = _hsv11.z; }
{ vec3 _hsv12 = br_hsv_to_rgb(colH, colS, rV); tr = _hsv12.x; tg = _hsv12.y; tb = _hsv12.z; }
r_col[0] = facm * r_col[0] + fac * tr;
r_col[1] = facm * r_col[1] + fac * tg;
r_col[2] = facm * r_col[2] + fac * tb;
}
break;
}
case BR_SOFT: {
// The non-fac Screen mix first, then blended in.
float sc[3];
for (int i = 0; i < 3; i++) {
sc[i] = 1.0f - (1.0f - col[i]) * (1.0f - r_col[i]);
}
for (int i = 0; i < 3; i++) {
r_col[i] = facm * r_col[i] +
fac * (((1.0f - r_col[i]) * col[i] * r_col[i]) + (r_col[i] * sc[i]));
}
break;
}
case BR_LINEAR:
for (int i = 0; i < 3; i++) {
if (col[i] > 0.5f) {
r_col[i] = r_col[i] + fac * (2.0f * (col[i] - 0.5f));
} else {
r_col[i] = r_col[i] + fac * (2.0f * col[i] - 1.0f);
}
}
break;
}
return r_col;
}
#endif // PN_BLENDER_RAMP_BLEND