dotfiles/mpv/shaders/hdr-toys/tone-mapping/bt2446c.glsl
2025-05-26 00:03:04 +10:00

315 lines
7 KiB
GLSL

// ITU-R BT.2446 Conversion Method C
// https://www.itu.int/pub/R-REP-BT.2446
//!PARAM reference_white
//!TYPE float
//!MINIMUM 0.0
//!MAXIMUM 1000.0
203.0
//!PARAM alpha
//!TYPE float
//!MINIMUM 0.00
//!MAXIMUM 0.33
0.04
//!PARAM sigma
//!TYPE float
//!MINIMUM 0.0
//!MAXIMUM 1.0
0.33
//!HOOK OUTPUT
//!BIND HOOKED
//!WHEN alpha
//!DESC tone mapping (bt.2446c, crosstalk)
// The crosstalk matrix is applied such that saturations of linear signals are reduced to achromatic to
// avoid hue changes caused by clipping of compressed highlight parts.
vec3 crosstalk(vec3 x, float a) {
float b = 1.0 - 2.0 * a;
mat3 transform = mat3(
b, a, a,
a, b, a,
a, a, b
);
return x * transform;
}
vec4 hook() {
vec4 color = HOOKED_tex(HOOKED_pos);
color.rgb = crosstalk(color.rgb, alpha);
return color;
}
//!HOOK OUTPUT
//!BIND HOOKED
//!WHEN sigma
//!DESC tone mapping (bt.2446c, chroma correction)
// Optional processing of chroma correction above HDR Reference White
// In SDR production, highlight parts are sometimes intentionally expressed as white. The processing
// described in this section is optionally used to shift chroma above HDR Reference White to achromatic
// when the converted SDR content requires a degree of consistency for SDR production content. This
// processing is applied as needed before the tone-mapping processing.
vec3 RGB_to_XYZ(vec3 RGB) {
return RGB * mat3(
0.6369580483012914, 0.14461690358620832, 0.1688809751641721,
0.2627002120112671, 0.6779980715188708, 0.05930171646986196,
0.000000000000000, 0.028072693049087428, 1.060985057710791
);
}
vec3 XYZ_to_RGB(vec3 XYZ) {
return XYZ * mat3(
1.716651187971268, -0.355670783776392, -0.253366281373660,
-0.666684351832489, 1.616481236634939, 0.0157685458139111,
0.017639857445311, -0.042770613257809, 0.942103121235474
);
}
float cbrt(float x) {
return sign(x) * pow(abs(x), 1.0 / 3.0);
}
const float delta = 6.0 / 29.0;
const float deltac = delta * 2.0 / 3.0;
float f(float x) {
return x > pow(delta, 3.0) ?
cbrt(x) :
deltac + x / (3.0 * pow(delta, 2.0));
}
vec3 f(vec3 x) {
return vec3(f(x.x), f(x.y), f(x.z));
}
float f_inv(float x) {
return x > delta ?
pow(x, 3.0) :
(x - deltac) * (3.0 * pow(delta, 2.0));
}
vec3 f_inv(vec3 x) {
return vec3(f_inv(x.x), f_inv(x.y), f_inv(x.z));
}
const vec3 XYZn = vec3(0.95047, 1.00000, 1.08883);
vec3 XYZ_to_Lab(vec3 XYZ) {
XYZ = f(XYZ / XYZn);
float X = XYZ.x;
float Y = XYZ.y;
float Z = XYZ.z;
float L = 116.0 * Y - 16.0;
float a = 500.0 * (X - Y);
float b = 200.0 * (Y - Z);
return vec3(L, a, b);
}
vec3 Lab_to_XYZ(vec3 Lab) {
float L = Lab.x;
float a = Lab.y;
float b = Lab.z;
float Y = (L + 16.0) / 116.0;
float X = Y + a / 500.0;
float Z = Y - b / 200.0;
vec3 XYZ = f_inv(vec3(X, Y, Z)) * XYZn;
return XYZ;
}
vec3 RGB_to_Lab(vec3 color) {
color = RGB_to_XYZ(color);
color = XYZ_to_Lab(color);
return color;
}
vec3 Lab_to_RGB(vec3 color) {
color = Lab_to_XYZ(color);
color = XYZ_to_RGB(color);
return color;
}
const float epsilon = 1e-6;
vec3 Lab_to_LCh(vec3 Lab) {
float L = Lab.x;
float a = Lab.y;
float b = Lab.z;
float C = length(vec2(a, b));
float h = (abs(a) < epsilon && abs(b) < epsilon) ? 0.0 : atan(b, a);
return vec3(L, C, h);
}
vec3 LCh_to_Lab(vec3 LCh) {
float L = LCh.x;
float C = LCh.y;
float h = LCh.z;
C = max(C, 0.0);
float a = C * cos(h);
float b = C * sin(h);
return vec3(L, a, b);
}
float chroma_correction(float L, float Lref, float Lmax, float sigma) {
return L <= Lref ? 1.0 : max(1.0 - sigma * (L - Lref) / (Lmax - Lref), 0.0);
}
vec4 hook() {
vec4 color = HOOKED_tex(HOOKED_pos);
const float Lref = RGB_to_Lab(vec3(1.0)).x;
const float Lmax = RGB_to_Lab(vec3(1000.0 / reference_white)).x;
color.rgb = RGB_to_Lab(color.rgb);
color.rgb = Lab_to_LCh(color.rgb);
color.y *= chroma_correction(color.x, Lref, Lmax, sigma);
color.rgb = LCh_to_Lab(color.rgb);
color.rgb = Lab_to_RGB(color.rgb);
return color;
}
//!HOOK OUTPUT
//!BIND HOOKED
//!DESC tone mapping (bt.2446c)
vec3 RGB_to_XYZ(vec3 RGB) {
return RGB * mat3(
0.6369580483012914, 0.14461690358620832, 0.1688809751641721,
0.2627002120112671, 0.6779980715188708, 0.05930171646986196,
0.000000000000000, 0.028072693049087428, 1.060985057710791
);
}
vec3 XYZ_to_RGB(vec3 XYZ) {
return XYZ * mat3(
1.716651187971268, -0.355670783776392, -0.253366281373660,
-0.666684351832489, 1.616481236634939, 0.0157685458139111,
0.017639857445311, -0.042770613257809, 0.942103121235474
);
}
vec3 XYZ_to_xyY(vec3 XYZ) {
float X = XYZ.x;
float Y = XYZ.y;
float Z = XYZ.z;
float divisor = X + Y + Z;
if (divisor == 0.0) divisor = 1e-6;
float x = X / divisor;
float y = Y / divisor;
return vec3(x, y, Y);
}
vec3 xyY_to_XYZ(vec3 xyY) {
float x = xyY.x;
float y = xyY.y;
float Y = xyY.z;
float multiplier = Y / max(y, 1e-6);
float z = 1.0 - x - y;
float X = x * multiplier;
float Z = z * multiplier;
return vec3(X, Y, Z);
}
const float ip = 0.58535; // linear length
const float k1 = 0.83802; // linear strength
const float k3 = 0.74204; // shoulder strength
float f(float Y, float k1, float k3, float ip) {
ip /= k1;
float k2 = (k1 * ip) * (1.0 - k3);
float k4 = (k1 * ip) - (k2 * log(1.0 - k3));
return Y < ip ? Y * k1 : log((Y / ip) - k3) * k2 + k4;
}
float curve(float x) {
return f(x, k1, k3, ip);
}
vec4 hook() {
vec4 color = HOOKED_tex(HOOKED_pos);
color.rgb = RGB_to_XYZ(color.rgb);
color.rgb = XYZ_to_xyY(color.rgb);
color.z = curve(color.z);
color.rgb = xyY_to_XYZ(color.rgb);
color.rgb = XYZ_to_RGB(color.rgb);
return color;
}
//!HOOK OUTPUT
//!BIND HOOKED
//!WHEN alpha
//!DESC tone mapping (bt.2446c, inverse crosstalk)
// The inverse crosstalk matrix is applied to ensure that the original hues of input HDR images are
// recovered.
vec3 crosstalk_inv(vec3 x, float a) {
float b = 1.0 - a;
float c = 1.0 / (1.0 - 3.0 * a);
mat3 transform = mat3(
b, -a, -a,
-a, b, -a,
-a, -a, b
);
return x * transform * c;
}
vec4 hook() {
vec4 color = HOOKED_tex(HOOKED_pos);
color.rgb = crosstalk_inv(color.rgb, alpha);
return color;
}
//!HOOK OUTPUT
//!BIND HOOKED
//!DESC tone mapping (bt.2446c, signal scaling)
// Handling 109% range (super-whites) and black point compensation
float f(float x, float a, float b, float c, float d) {
return (x - a) * (d - c) / (b - a) + c;
}
vec3 f(vec3 x, float a, float b, float c, float d) {
return vec3(
f(x.x, a, b, c, d),
f(x.y, a, b, c, d),
f(x.z, a, b, c, d)
);
}
vec4 hook() {
vec4 color = HOOKED_tex(HOOKED_pos);
color.rgb = f(color.rgb, 0.0, 1019.0 / 940.0, 0.001, 1.0);
return color;
}