diff --git a/mpv/hdr-toys.conf b/mpv/hdr-toys.conf new file mode 100644 index 0000000..842d437 --- /dev/null +++ b/mpv/hdr-toys.conf @@ -0,0 +1,59 @@ +target-colorspace-hint=no + +tone-mapping=clip +gamut-mapping-mode=clip + +[bt.2100-pq] +profile-cond=get("video-params/primaries") == "bt.2020" and get("video-params/gamma") == "pq" +profile-restore=copy +target-prim=bt.2020 +target-trc=pq +glsl-shader=~~/shaders/hdr-toys/utils/clip_both.glsl +glsl-shader=~~/shaders/hdr-toys/transfer-function/pq_inv.glsl +glsl-shader=~~/shaders/hdr-toys/tone-mapping/astra.glsl +glsl-shader=~~/shaders/hdr-toys/gamut-mapping/jedypod.glsl +glsl-shader=~~/shaders/hdr-toys/transfer-function/bt1886.glsl +# glsl-shader-opts=reference_white=100 + +[bt.2100-hlg] +profile-cond=get("video-params/primaries") == "bt.2020" and get("video-params/gamma") == "hlg" +profile-restore=copy +target-prim=bt.2020 +target-trc=hlg +glsl-shader=~~/shaders/hdr-toys/utils/clip_both.glsl +glsl-shader=~~/shaders/hdr-toys/transfer-function/hlg_inv.glsl +glsl-shader=~~/shaders/hdr-toys/tone-mapping/astra.glsl +glsl-shader=~~/shaders/hdr-toys/gamut-mapping/jedypod.glsl +glsl-shader=~~/shaders/hdr-toys/transfer-function/bt1886.glsl + +[bt.2020] +profile-cond=get("video-params/primaries") == "bt.2020" and get("video-params/gamma") == "bt.1886" +profile-restore=copy +target-prim=bt.2020 +target-trc=bt.1886 +glsl-shader=~~/shaders/hdr-toys/transfer-function/bt1886_inv.glsl +glsl-shader=~~/shaders/hdr-toys/gamut-mapping/bottosson.glsl +glsl-shader=~~/shaders/hdr-toys/transfer-function/bt1886.glsl + +[openexr] +profile-cond=get("file-format") == "exr_pipe" +profile-restore=copy +target-prim=bt.2020 +target-trc=linear +scale=bilinear +glsl-shader=~~/shaders/hdr-toys/utils/clip_black.glsl +glsl-shader=~~/shaders/hdr-toys/tone-mapping/astra.glsl +glsl-shader=~~/shaders/hdr-toys/gamut-mapping/jedypod.glsl +glsl-shader=~~/shaders/hdr-toys/transfer-function/bt1886.glsl + +[radiance] +profile-cond=get("file-format") == "hdr_pipe" +profile-restore=copy +target-prim=bt.2020 +target-trc=linear +vf=format:gamma=linear +scale=bilinear +glsl-shader=~~/shaders/hdr-toys/utils/clip_black.glsl +glsl-shader=~~/shaders/hdr-toys/tone-mapping/astra.glsl +glsl-shader=~~/shaders/hdr-toys/gamut-mapping/jedypod.glsl +glsl-shader=~~/shaders/hdr-toys/transfer-function/bt1886.glsl diff --git a/mpv/input.conf b/mpv/input.conf index 78541b6..da11055 100755 --- a/mpv/input.conf +++ b/mpv/input.conf @@ -25,6 +25,12 @@ alt+i script-binding uosc/keybinds #! Utils > Key bindings O script-binding uosc/show-in-directory #! Utils > Show in directory # script-binding uosc/open-config-directory #! Utils > Open config directory # script-binding uosc/update #! Utils > Update uosc +shift+h apply-profile HDR_MODE:SDR_HDR_EFFECT #! HDR > inverse-tone-mapping +ctrl+shift+h apply-profile HDR_MODE:SDR_HDR_EFFECT restore #! HDR > Undo inverse-tone-mapping +# apply-profile HDR_MODE:SDR #! HDR > SDR under HDR +# apply-profile HDR_MODE:SDR restore #! HDR > Revert SDR under HDR +# apply-profile HDR_MODE:DOVI #! HDR > Dolby Vision +# apply-profile HDR_MODE:DOVI restore #! HDR > Dolby Vision CTRL+1 no-osd change-list glsl-shaders set "~~/shaders/Anime4K_Clamp_Highlights.glsl:~~/shaders/Anime4K_Restore_CNN_VL.glsl:~~/shaders/Anime4K_Upscale_CNN_x2_VL.glsl:~~/shaders/Anime4K_AutoDownscalePre_x2.glsl:~~/shaders/Anime4K_AutoDownscalePre_x4.glsl:~~/shaders/Anime4K_Upscale_CNN_x2_M.glsl"; show-text "Anime4K: Mode A (HQ)" CTRL+2 no-osd change-list glsl-shaders set "~~/shaders/Anime4K_Clamp_Highlights.glsl:~~/shaders/Anime4K_Restore_CNN_Soft_VL.glsl:~~/shaders/Anime4K_Upscale_CNN_x2_VL.glsl:~~/shaders/Anime4K_AutoDownscalePre_x2.glsl:~~/shaders/Anime4K_AutoDownscalePre_x4.glsl:~~/shaders/Anime4K_Upscale_CNN_x2_M.glsl"; show-text "Anime4K: Mode B (HQ)" diff --git a/mpv/mpv.conf b/mpv/mpv.conf index 647ccb2..060bb9b 100755 --- a/mpv/mpv.conf +++ b/mpv/mpv.conf @@ -115,17 +115,38 @@ target-colorspace-hint=auto # target-peak=auto # gamut-mapping-mode=perceptual # hdr-contrast-recovery=1.0 -# -# [HDR_MODE:DOVI] -# profile-restore=copy -# #Automatically enables profile if DoVi video is played. Seems to work but maybe there a better way? + +[HDR_MODE:DOVI] +profile-restore=copy +#Automatically enables profile if DoVi video is played. Seems to work but maybe there a better way? # profile-cond=(p["video-dec-params/gamma"] == "auto") -# target-trc=pq -# target-prim=bt.2020 -# #Adjust this to the peak brightness of your display. e.g. 800 for LG CX -# target-peak=1500 -# #Only necessary if you are switching between different profiles like me SDR_TO_HDR tonemap profile -# tone-mapping-mode=auto +target-trc=pq +target-prim=bt.2020 +#Adjust this to the peak brightness of your display. e.g. 800 for LG CX +target-peak=1037 +#Only necessary if you are switching between different profiles like me SDR_TO_HDR tonemap profile +tone-mapping-mode=auto + +[HDR_MODE:SDR] +profile-restore=copy +target-trc=pq +target-prim=bt.2020 +#Seems to be some kind of magic number, higher values do not have any effect +target-peak=200 +tone-mapping=bt.2390 +tone-mapping-mode=rgb +inverse-tone-mapping=yes + +[HDR_MODE:SDR_HDR_EFFECT] +profile-restore=copy +target-trc=pq +target-prim=bt.2020 +# Higher value = stronger effect +target-peak=400 +tone-mapping=spline +# All other values make the colors look awful in my opinion. +tone-mapping-mode=rgb +inverse-tone-mapping=yes ################################### # Protocol Specific Configuration # diff --git a/mpv/scripts/hdr-toys.lua b/mpv/scripts/hdr-toys.lua new file mode 100644 index 0000000..bd8c9af --- /dev/null +++ b/mpv/scripts/hdr-toys.lua @@ -0,0 +1,13 @@ +local options = require("mp.options") + +local o = { + temporal_stable_time = 1 / 3, +} +options.read_options(o, _, function() end) + +mp.observe_property("container-fps", "native", function (property, value) + if not value then return end + value = value * o.temporal_stable_time + value = math.floor(value + 0.5) + mp.command("no-osd set glsl-shader-opts temporal_stable_frames=" .. value) +end) diff --git a/mpv/shaders/hdr-toys/gamut-mapping/bottosson.glsl b/mpv/shaders/hdr-toys/gamut-mapping/bottosson.glsl new file mode 100644 index 0000000..41d02ce --- /dev/null +++ b/mpv/shaders/hdr-toys/gamut-mapping/bottosson.glsl @@ -0,0 +1,350 @@ +// https://bottosson.github.io/posts/gamutclipping/ +// https://www.shadertoy.com/view/7sXcWn + +//!PARAM softness_scale +//!TYPE float +//!MINIMUM 0.0 +//!MAXIMUM 1.0 +0.3 + +//!HOOK OUTPUT +//!BIND HOOKED +//!DESC gamut mapping (bottosson, soft) + +float cbrt(float x) { + return sign(x) * pow(abs(x), 1.0 / 3.0); +} + +vec3 cbrt(vec3 color) { + return vec3( + cbrt(color.x), + cbrt(color.y), + cbrt(color.z) + ); +} + +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_LMS(vec3 XYZ) { + return XYZ * mat3( + 0.8190224379967030, 0.3619062600528904, -0.1288737815209879, + 0.0329836539323885, 0.9292868615863434, 0.0361446663506424, + 0.0481771893596242, 0.2642395317527308, 0.6335478284694309 + ); +} + +vec3 LMS_to_XYZ(vec3 LMS) { + return LMS * mat3( + 1.2268798758459243, -0.5578149944602171, 0.2813910456659647, + -0.0405757452148008, 1.1122868032803170, -0.0717110580655164, + -0.0763729366746601, -0.4214933324022432, 1.5869240198367816 + ); +} + +vec3 LMS_to_Lab(vec3 LMS) { + return LMS * mat3( + 0.2104542683093140, 0.7936177747023054, -0.0040720430116193, + 1.9779985324311684, -2.4285922420485799, 0.4505937096174110, + 0.0259040424655478, 0.7827717124575296, -0.8086757549230774 + ); +} + +vec3 Lab_to_LMS(vec3 Lab) { + return Lab * mat3( + 1.0000000000000000, 0.3963377773761749, 0.2158037573099136, + 1.0000000000000000, -0.1055613458156586, -0.0638541728258133, + 1.0000000000000000, -0.0894841775298119, -1.2914855480194092 + ); +} + +vec3 RGB_to_Lab(vec3 color) { + color = RGB_to_XYZ(color); + color = XYZ_to_LMS(color); + color = cbrt(color); + color = LMS_to_Lab(color); + return color; +} + +vec3 Lab_to_RGB(vec3 color) { + color = Lab_to_LMS(color); + color = pow(color, vec3(3.0)); + color = LMS_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); +} + + + + + +vec3 output_RGB_to_XYZ(vec3 RGB) { + mat3 M = mat3( + 0.41239079926595934, 0.357584339383878, 0.1804807884018343, + 0.21263900587151027, 0.715168678767756, 0.07219231536073371, + 0.01933081871559182, 0.11919477979462598, 0.9505321522496607); + return RGB * M; +} + +vec3 output_XYZ_to_RGB(vec3 XYZ) { + mat3 M = mat3( + 3.2409699419045226, -1.537383177570094, -0.4986107602930034, + -0.9692436362808796, 1.8759675015077202, 0.04155505740717559, + 0.05563007969699366, -0.20397695888897652, 1.0569715142428786); + return XYZ * M; +} + +float findCenter(vec3 x) { + float a = 1.9779985324311684 * x.x - 2.4285922420485799 * x.y + 0.4505937096174110 * x.z; + float b = 0.0259040424655478 * x.x + 0.7827717124575296 * x.y - 0.8086757549230774 * x.z; + float C = sqrt(a*a+b*b); + + // Matrix derived for max(l,m,s) to be as close to macadam limit as possible + // this makes it some kind of g0-like estimate + mat3 M = mat3( + 2.26923008, -1.43594808, 0.166718, + -0.98545265, 2.12616699, -0.14071434, + -0.02985871, -0.25753239, 1.2873911); + x = x*M; + + float x_min = min(x.r,min(x.g,x.b)); + float x_max = max(x.r,max(x.g,x.b)); + + float c = 0.5*(x_max+x_min); + float s = (x_max-x_min); + + // math trickery to create values close to c and s, but without producing hard edges + vec3 y = (x-c)/s; + float c_smooth = c + dot(y*y*y, vec3(1.0/3.0))*s; + float s_smooth = sqrt(dot(x-c,x-c)/2.0); + + return c_smooth; +} + +vec2 findCenterAndPurity(vec3 x) { + // Matrix derived for (c_smooth+s_smooth) to be an approximation of the macadam limit + // this makes it some kind of g0-like estimate + mat3 M = mat3( + 2.26775149, -1.43293879, 0.1651873, + -0.98535505, 2.1260072, -0.14065215, + -0.02501605, -0.26349465, 1.2885107); + + x = x*M; + + float x_min = min(x.r,min(x.g,x.b)); + float x_max = max(x.r,max(x.g,x.b)); + + float c = 0.5*(x_max+x_min); + float s = (x_max-x_min); + + // math trickery to create values close to c and s, but without producing hard edges + vec3 y = (x-c)/s; + float c_smooth = c + dot(y*y*y, vec3(1.0/3.0))*s; + float s_smooth = sqrt(dot(x-c,x-c)/2.0); + return vec2(c_smooth, s_smooth); +} + + +vec3 toLms(vec3 c) { + vec3 lms_ = XYZ_to_LMS(output_RGB_to_XYZ(c)); + return sign(lms_)*pow(abs(lms_), vec3(1.0/3.0)); +} + +float calculateC(vec3 lms) { + // Most of this could be precomputed + // Creating a transform that maps R,G,B in the target gamut to have same distance from grey axis + + vec3 lmsR = toLms(vec3(1.0,0.0,0.0)); + vec3 lmsG = toLms(vec3(0.0,1.0,0.0)); + vec3 lmsB = toLms(vec3(0.0,0.0,1.0)); + + vec3 uDir = (lmsR - lmsG)/sqrt(2.0); + vec3 vDir = (lmsR + lmsG - 2.0*lmsB)/sqrt(6.0); + + mat3 to_uv = inverse(mat3( + 1.0, uDir.x, vDir.x, + 1.0, uDir.y, vDir.y, + 1.0, uDir.z, vDir.z + )); + + vec3 _uv = lms * to_uv; + + return sqrt(_uv.y*_uv.y + _uv.z*_uv.z); +} + +vec3 calculateLCh(vec3 c) { + vec3 lms = toLms(c); + + float maxLms = findCenter(lms); + + float a = 1.9779985324311684 * lms.x - 2.4285922420485799 * lms.y + 0.4505937096174110 * lms.z; + float b = 0.0259040424655478 * lms.x + 0.7827717124575296 * lms.y - 0.8086757549230774 * lms.z; + + float C = sqrt(a*a+b*b); + + return vec3(maxLms, C, atan(-b, -a)); +} + +vec2 expandShape(vec3 rgb, vec2 ST) { + vec3 LCh = calculateLCh(rgb); + vec2 STnew = vec2(LCh.x/LCh.y, (1.0-LCh.x)/LCh.y); + STnew = (STnew + 3.0*STnew*STnew*LCh.y); + + return vec2(min(ST.x, STnew.x), min(ST.y, STnew.y)); +} + +float expandScale(vec3 rgb, vec2 ST, float scale) { + vec3 LCh = calculateLCh(rgb); + float Cnew = (1.0/((ST.x/LCh.x) + (ST.y/(1.0-LCh.x)))); + + return max(LCh.y/Cnew, scale); +} + +vec2 approximateShape() { + float m = -softness_scale*0.2; + float s = 1.0 + (softness_scale*0.2+softness_scale*0.8); + + vec2 ST = vec2(1000.0,1000.0); + ST = expandShape(m+s*vec3(1.0,0.0,0.0), ST); + ST = expandShape(m+s*vec3(1.0,1.0,0.0), ST); + ST = expandShape(m+s*vec3(0.0,1.0,0.0), ST); + ST = expandShape(m+s*vec3(0.0,1.0,1.0), ST); + ST = expandShape(m+s*vec3(0.0,0.0,1.0), ST); + ST = expandShape(m+s*vec3(1.0,0.0,1.0), ST); + + float scale = 0.0; + scale = expandScale(m+s*vec3(1.0,0.0,0.0), ST, scale); + scale = expandScale(m+s*vec3(1.0,1.0,0.0), ST, scale); + scale = expandScale(m+s*vec3(0.0,1.0,0.0), ST, scale); + scale = expandScale(m+s*vec3(0.0,1.0,1.0), ST, scale); + scale = expandScale(m+s*vec3(0.0,0.0,1.0), ST, scale); + scale = expandScale(m+s*vec3(1.0,0.0,1.0), ST, scale); + + return ST/scale; +} + +vec3 compute(float L, float hue, float sat) { + vec3 c = vec3(L, cos(hue), sin(hue)); + + float l_ = + 0.3963377773761749 * c.y + 0.2158037573099136 * c.z; + float m_ = - 0.1055613458156586 * c.y - 0.0638541728258133 * c.z; + float s_ = - 0.0894841775298119 * c.y - 1.2914855480194092 * c.z; + + vec3 lms = vec3(l_,m_,s_); + + vec2 MC = findCenterAndPurity(lms); + + lms -= MC.x; + + lms *= sat; + + lms += c.x; + + lms = lms*lms*lms; + + vec3 rgb = output_XYZ_to_RGB(LMS_to_XYZ(lms)); + + return rgb; +} + +vec3 softSaturate(vec3 x, vec3 a) { + a = clamp(a, 0.0,softness_scale); + a = 1.0+a; + x = min(x, a); + vec3 b = (a-1.0)*sqrt(a/(2.0-a)); + return 1.0 - (sqrt((x-a)*(x-a) + b*b) - b)/(sqrt(a*a+b*b)-b); +} + +vec3 softClipColor(vec3 color) { + // soft clip of rgb values to avoid artifacts of hard clipping + // causes hues distortions, but is a smooth mapping + + float maxRGB = max(max(color.r, color.g), color.b); + float minRGB = min(min(color.r, color.g), color.b); + + float grey = 0.2; + + vec3 x = color-grey; + + vec3 xsgn = sign(x); + vec3 xscale = 0.5 + xsgn*(0.5-grey); + x /= xscale; + + float softness_0 = maxRGB/(1.0+softness_scale)*softness_scale; + float softness_1 = (1.0-minRGB)/(1.0+softness_scale)*softness_scale; + + vec3 softness = vec3(0.5)*(softness_0+softness_1 + xsgn*(softness_1 - softness_0)); + + return grey + xscale*xsgn*softSaturate(abs(x), softness); +} + + + + + +vec4 hook() { + vec4 color = HOOKED_tex(HOOKED_pos); + + vec3 lch = Lab_to_LCh(RGB_to_Lab(color.rgb)); + + float L = lch.x; + float C = lch.y; + float h = lch.z; + + // if (L >= 1.0) { + // return vec4(vec3(1.0, 1.0, 1.0), color.a); + // } + + // if (L <= 0.0) { + // return vec4(vec3(0.0, 0.0, 0.0), color.a); + // } + + if (C <= 1e-6) { + return color; + } + + vec2 ST = approximateShape(); + float C_smooth = (1.0 / ((ST.x / L) + (ST.y / max(1.0 - L, 1e-6)))); + color.rgb = compute(L, h, C / sqrt(C * C / C_smooth / C_smooth + 1.0)); + color.rgb = softClipColor(color.rgb); + + return color; +} diff --git a/mpv/shaders/hdr-toys/gamut-mapping/clip.glsl b/mpv/shaders/hdr-toys/gamut-mapping/clip.glsl new file mode 100644 index 0000000..c72a24f --- /dev/null +++ b/mpv/shaders/hdr-toys/gamut-mapping/clip.glsl @@ -0,0 +1,512 @@ +// RGB to RGB conversion, includes chromatic adaptation transform +// All coordinates are based on the CIE 1931 2° chromaticity diagram + +//!HOOK OUTPUT +//!BIND HOOKED +//!DESC gamut mapping (clip) + +// You can use custom chromaticity here. +// Example: BT.709 with a D93 white point: Chromaticity(BT709.r, BT709.g, BT709.b, D93) +// You can also define custom coordinates: Chromaticity(vec2(0.7347, 0.2653), BT709.g, BT709.b, D65) + +#define from BT2020 +#define to BT709 + +// White points of standard illuminants +// https://en.wikipedia.org/wiki/Standard_illuminant#White_points_of_standard_illuminants + +const vec2 A = vec2(0.44757, 0.40745); +const vec2 B = vec2(0.34842, 0.35161); +const vec2 C = vec2(0.31006, 0.31616); +const vec2 D50 = vec2(0.34567, 0.35850); +const vec2 D55 = vec2(0.33242, 0.34743); +const vec2 D65 = vec2(0.31271, 0.32902); +const vec2 D75 = vec2(0.29902, 0.31485); +const vec2 D93 = vec2(0.28315, 0.29711); +const vec2 E = vec2(1.0/3.0, 1.0/3.0); +const vec2 F2 = vec2(0.37208, 0.37529); +const vec2 F7 = vec2(0.31292, 0.32933); +const vec2 F11 = vec2(0.38052, 0.37713); +const vec2 DCI = vec2(0.31400, 0.35100); +// It is also known as D60 +const vec2 ACES = vec2(0.32168, 0.33767); +// Colour Matching Between OLED and CRT +// https://www.sony.jp/products/catalog/FUN_WhitePaper_OLED_ColorMatching_V1_00.pdf +const vec2 BRAVIA = vec2(0.3067, 0.318); + +// https://en.wikipedia.org/wiki/Standard_illuminant#Illuminant_series_D +vec2 CIE_D(float T) { + // Compensate for the loss caused by the accuracy difference between the old and new standards + // c2 = 1.4387768775039337 + // https://en.wikipedia.org/wiki/Planckian_locus#Planckian_locus_in_the_XYZ_color_space + T = (T * 1.4388) / 1.438; + + // This formula is applicable to temperatures ranging from 4000K to 25000K + T = clamp(T, 4000.0, 25000.0); + + float t1 = 1000.0 / T; + float t2 = t1 * t1; + float t3 = t1 * t2; + + float x = + T <= 7000.0 + ? 0.244063 + 0.09911 * t1 + 2.9678 * t2 - 4.607 * t3 + : 0.23704 + 0.24748 * t1 + 1.9018 * t2 - 2.0064 * t3; + + // Daylight locus + float y = -0.275 + 2.87 * x - 3.0 * x * x; + + return vec2(x, y); +} + +// https://en.wikipedia.org/wiki/Planckian_locus#Approximation +vec2 Kang(float T) { + // This formula is applicable to temperatures ranging from 1667K to 25000K + T = clamp(T, 1667.0, 25000.0); + + float t1 = 1000.0 / T; + float t2 = t1 * t1; + float t3 = t1 * t2; + + float x = + T <= 4000.0 + ? -0.2661239 * t3 - 0.234358 * t2 + 0.8776956 * t1 + 0.17991 + : -3.0258469 * t3 + 2.1070379 * t2 + 0.2226347 * t1 + 0.24039; + + float x2 = x * x; + float x3 = x2 * x; + + float y = + T <= 2222.0 + ? -1.1063814 * x3 - 1.3481102 * x2 - 2.18555832 * x - 0.20219683 + : T <= 4000.0 + ? -0.9549476 * x3 - 1.37418593 * x2 - 2.09137015 * x - 0.16748867 + : 3.081758 * x3 - 5.8733867 * x2 + 3.75112997 * x - 0.37001483; + + return vec2(x, y); +} + +// Chromaticities +// https://www.itu.int/rec/T-REC-H.273 +// https://github.com/colour-science/colour/tree/develop/colour/models/rgb/datasets + +struct Chromaticity { + vec2 r, g, b, w; +}; + +// ITU-R BT.2020, ITU-R BT.2100 +const Chromaticity BT2020 = Chromaticity( + vec2(0.708, 0.292), + vec2(0.170, 0.797), + vec2(0.131, 0.046), + D65 +); + +// ITU-R BT.709, IEC 61966-2-1 (sRGB) +const Chromaticity BT709 = Chromaticity( + vec2(0.64, 0.33), + vec2(0.30, 0.60), + vec2(0.15, 0.06), + D65 +); + +// ITU-R BT.601 (525 lines), SMPTE ST 240 +const Chromaticity BT601_525 = Chromaticity( + vec2(0.630, 0.340), + vec2(0.310, 0.595), + vec2(0.155, 0.070), + D65 +); + +// ITU-R BT.601 (625 lines), BT.470 (B/G), EBU 3213-E +const Chromaticity BT601_625 = Chromaticity( + vec2(0.64, 0.33), + vec2(0.29, 0.60), + vec2(0.15, 0.06), + D65 +); + +// ITU-R BT.470 (M) +const Chromaticity BT470m = Chromaticity( + vec2(0.67, 0.33), + vec2(0.21, 0.71), + vec2(0.14, 0.08), + C +); + +// P3-DCI (Theater) +const Chromaticity P3DCI = Chromaticity( + vec2(0.680, 0.320), + vec2(0.265, 0.690), + vec2(0.150, 0.060), + DCI +); + +// P3-D65 (Display) +const Chromaticity P3D65 = Chromaticity( + P3DCI.r, + P3DCI.g, + P3DCI.b, + D65 +); + +// P3-D60 (ACES Cinema) +const Chromaticity P3D60 = Chromaticity( + P3DCI.r, + P3DCI.g, + P3DCI.b, + ACES +); + +// ITU-T H.273 (Generic film) +const Chromaticity H273_8 = Chromaticity( + vec2(0.681, 0.319), + vec2(0.243, 0.692), + vec2(0.145, 0.049), + C +); + +// ITU-T H.273 (No corresponding industry specification identified) +const Chromaticity H273_22 = Chromaticity( + vec2(0.630, 0.340), + vec2(0.295, 0.605), + vec2(0.155, 0.077), + D65 +); + +// CIE RGB (CIE 1931 color space) +const Chromaticity CIERGB = Chromaticity( + vec2(0.73474284, 0.26525716), + vec2(0.27377903, 0.7174777), + vec2(0.16655563, 0.00891073), + E +); + +// CIE XYZ (CIE 1931 color space) +const Chromaticity XYZ = Chromaticity( + vec2(1.0, 0.0), + vec2(0.0, 1.0), + vec2(0.0, 0.0), + E +); + +// CIE XYZ (CIE 1931 color space, D65 whitepoint) +const Chromaticity XYZD65 = Chromaticity( + XYZ.r, + XYZ.g, + XYZ.b, + D65 +); + +// CIE XYZ (CIE 1931 color space, D50 whitepoint) +const Chromaticity XYZD50 = Chromaticity( + XYZ.r, + XYZ.g, + XYZ.b, + D50 +); + +// Grayscale, Monochrome +const Chromaticity GRAY = Chromaticity( + vec2(0.0, 1.0), + vec2(0.0, 1.0), + vec2(0.0, 1.0), + E +); + +// Adobe RGB (1998) +const Chromaticity AdobeRGB = Chromaticity( + vec2(0.64, 0.33), + vec2(0.21, 0.71), + vec2(0.15, 0.06), + D65 +); + +// Adobe Wide Gamut RGB +const Chromaticity AdobeWideGamutRGB = Chromaticity( + vec2(0.7347, 0.2653), + vec2(0.1152, 0.8264), + vec2(0.1566, 0.0177), + D50 +); + +// ROMM (ProPhoto RGB) +const Chromaticity ROMM = Chromaticity( + vec2(0.734699, 0.265301), + vec2(0.159597, 0.840403), + vec2(0.036598, 0.000105), + D50 +); + +// AP0 (ACES 2065-1) +const Chromaticity AP0 = Chromaticity( + vec2(0.7347, 0.2653), + vec2(0.0000, 1.0000), + vec2(0.0001, -0.0770), + ACES +); + +// AP1 (ACEScg, cc, cct, proxy) +const Chromaticity AP1 = Chromaticity( + vec2(0.713, 0.293), + vec2(0.165, 0.830), + vec2(0.128, 0.044), + ACES +); + +// ARRI Wide Gamut 3 +const Chromaticity AWG3 = Chromaticity( + vec2(0.684, 0.313), + vec2(0.221, 0.848), + vec2(0.0861, -0.102), + D65 +); + +// ARRI Wide Gamut 4 +const Chromaticity AWG4 = Chromaticity( + vec2(0.7347, 0.2653), + vec2(0.1424, 0.8576), + vec2(0.0991, -0.0308), + D65 +); + +// RED Wide Gamut RGB +const Chromaticity RWG = Chromaticity( + vec2(0.780308, 0.304253), + vec2(0.121595, 1.493994), + vec2(0.095612, -0.084589), + D65 +); + +// DaVinci Wide Gamut +const Chromaticity DWG = Chromaticity( + vec2(0.8000, 0.3130), + vec2(0.1682, 0.9877), + vec2(0.0790, -0.1155), + D65 +); + +// FilmLight E-Gamut +const Chromaticity EGAMUT = Chromaticity( + vec2(0.8000, 0.3177), + vec2(0.1800, 0.9000), + vec2(0.0650, -0.0805), + D65 +); + +// FilmLight E-Gamut 2 +const Chromaticity EGAMUT2 = Chromaticity( + vec2(0.8300, 0.3100), + vec2(0.1500, 0.9500), + vec2(0.0650, -0.0805), + D65 +); + +// FUJIFILM F-Gamut C +const Chromaticity FGAMUTC = Chromaticity( + vec2(0.7347, 0.2653), + vec2(0.0263, 0.9737), + vec2(0.1173, -0.0224), + D65 +); + +// Sony S-Gamut3/S-Gamut +const Chromaticity SGAMUT = Chromaticity( + vec2(0.73, 0.280), + vec2(0.14, 0.855), + vec2(0.10, -0.050), + D65 +); + +// Sony S-Gamut.Cine +const Chromaticity SGAMUTCINE = Chromaticity( + vec2(0.766, 0.275), + vec2(0.225, 0.800), + vec2(0.089, -0.087), + D65 +); + +// Canon Cinema Gamut +const Chromaticity CINEMA_GAMUT = Chromaticity( + vec2(0.74, 0.27), + vec2(0.17, 1.14), + vec2(0.08, -0.10), + D65 +); + +// Panasonic V-Gamut +const Chromaticity VGAMUT = Chromaticity( + vec2(0.730, 0.28), + vec2(0.165, 0.84), + vec2(0.100, -0.03), + D65 +); + +// DJI D-Gamut +const Chromaticity DGAMUT = Chromaticity( + vec2(0.71, 0.31), + vec2(0.21, 0.88), + vec2(0.09, -0.08), + D65 +); + +// Chromatic adaptation transform +// https://en.wikipedia.org/wiki/LMS_color_space + +// It is also known as von Kries +const mat3 HPE = mat3( + 0.40024, 0.70760, -0.08081, + -0.22630, 1.16532, 0.04570, + 0.00000, 0.00000, 0.91822 +); + +const mat3 Bradford = mat3( + 0.8951, 0.2664, -0.1614, + -0.7502, 1.7135, 0.0367, + 0.0389, -0.0685, 1.0296 +); + +const mat3 CAT97 = mat3( + 0.8562, 0.3372, -0.1934, + -0.8360, 1.8327, 0.0033, + 0.0357, -0.0469, 1.0112 +); + +const mat3 CAT02 = mat3( + 0.7328, 0.4296, -0.1624, + -0.7036, 1.6975, 0.0061, + 0.0030, 0.0136, 0.9834 +); + +const mat3 CAT16 = mat3( + 0.401288, 0.650173, -0.051461, + -0.250268, 1.204414, 0.045854, + -0.002079, 0.048952, 0.953127 +); + +// Other constants + +const mat3 Identity3 = mat3( + 1.0, 0.0, 0.0, + 0.0, 1.0, 0.0, + 0.0, 0.0, 1.0 +); + +const mat3 SingularY3 = mat3( + 0.0, 1.0, 0.0, + 0.0, 1.0, 0.0, + 0.0, 1.0, 0.0 +); + +// http://www.brucelindbloom.com/Eqn_xyY_to_XYZ.html +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); +} + +// http://www.brucelindbloom.com/Eqn_XYZ_to_xyY.html +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); +} + +// http://www.brucelindbloom.com/Eqn_RGB_XYZ_Matrix.html +mat3 RGB_to_XYZ(Chromaticity C) { + if (C == GRAY) + return Identity3; + + vec3 r = xyY_to_XYZ(vec3(C.r, 1.0)); + vec3 g = xyY_to_XYZ(vec3(C.g, 1.0)); + vec3 b = xyY_to_XYZ(vec3(C.b, 1.0)); + vec3 w = xyY_to_XYZ(vec3(C.w, 1.0)); + + mat3 xyz = transpose(mat3(r, g, b)); + + vec3 scale = w * inverse(xyz); + mat3 scale_diag = mat3( + scale.x, 0.0, 0.0, + 0.0, scale.y, 0.0, + 0.0, 0.0, scale.z + ); + + return scale_diag * xyz; +} + +mat3 XYZ_to_RGB(Chromaticity C) { + if (C == GRAY) + return SingularY3; + + return inverse(RGB_to_XYZ(C)); +} + +// http://www.brucelindbloom.com/Eqn_ChromAdapt.html +mat3 adaptation(vec2 w1, vec2 w2, mat3 cat) { + vec3 src_xyz = xyY_to_XYZ(vec3(w1, 1.0)); + vec3 dst_xyz = xyY_to_XYZ(vec3(w2, 1.0)); + + vec3 src_lms = src_xyz * cat; + vec3 dst_lms = dst_xyz * cat; + + vec3 scale = dst_lms / src_lms; + mat3 scale_diag = mat3( + scale.x, 0.0, 0.0, + 0.0, scale.y, 0.0, + 0.0, 0.0, scale.z + ); + + return cat * scale_diag * inverse(cat); +} + +// CAM16 uses CAT16 as cat and equal-energy illuminant (E) as wt. +// https://www.researchgate.net/publication/318152296_Comprehensive_color_solutions_CAM16_CAT16_and_CAM16-UCS +// Android uses Bradford as cat and D50 as wt. +// https://android.googlesource.com/platform/frameworks/base/+/master/graphics/java/android/graphics/ColorSpace.java +mat3 adaptation_two_step(vec2 w1, vec2 w2, vec2 wt, mat3 cat) { + return adaptation(w1, wt, cat) * adaptation(wt, w2, cat); +} + +mat3 adaptation_two_step(vec2 w1, vec2 w2) { + return adaptation_two_step(w1, w2, E, CAT16); +} + +mat3 RGB_to_RGB(Chromaticity c1, Chromaticity c2) { + mat3 m = Identity3; + if (c1 != c2) { + m *= RGB_to_XYZ(c1); + if (c1.w != c2.w) { + m *= adaptation_two_step(c1.w, c2.w); + } + m *= XYZ_to_RGB(c2); + } + return m; +} + +vec3 RGB_to_RGB(vec3 c, Chromaticity c1, Chromaticity c2) { + return c * RGB_to_RGB(c1, c2); +} + +vec4 hook() { + vec4 color = HOOKED_tex(HOOKED_pos); + + color.rgb = RGB_to_RGB(color.rgb, from, to); + + return color; +} diff --git a/mpv/shaders/hdr-toys/gamut-mapping/false.glsl b/mpv/shaders/hdr-toys/gamut-mapping/false.glsl new file mode 100644 index 0000000..28aa773 --- /dev/null +++ b/mpv/shaders/hdr-toys/gamut-mapping/false.glsl @@ -0,0 +1,104 @@ +// Visualizes the out of gamut colors using false color. + +//!HOOK OUTPUT +//!BIND HOOKED +//!DESC gamut mapping (false color) + +float cbrt(float x) { + return sign(x) * pow(abs(x), 1.0 / 3.0); +} + +vec3 cbrt(vec3 color) { + return vec3( + cbrt(color.x), + cbrt(color.y), + cbrt(color.z) + ); +} + +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_LMS(vec3 XYZ) { + return XYZ * mat3( + 0.8190224379967030, 0.3619062600528904, -0.1288737815209879, + 0.0329836539323885, 0.9292868615863434, 0.0361446663506424, + 0.0481771893596242, 0.2642395317527308, 0.6335478284694309 + ); +} + +vec3 LMS_to_XYZ(vec3 LMS) { + return LMS * mat3( + 1.2268798758459243, -0.5578149944602171, 0.2813910456659647, + -0.0405757452148008, 1.1122868032803170, -0.0717110580655164, + -0.0763729366746601, -0.4214933324022432, 1.5869240198367816 + ); +} + +vec3 LMS_to_Lab(vec3 LMS) { + return LMS * mat3( + 0.2104542683093140, 0.7936177747023054, -0.0040720430116193, + 1.9779985324311684, -2.4285922420485799, 0.4505937096174110, + 0.0259040424655478, 0.7827717124575296, -0.8086757549230774 + ); +} + +vec3 Lab_to_LMS(vec3 Lab) { + return Lab * mat3( + 1.0000000000000000, 0.3963377773761749, 0.2158037573099136, + 1.0000000000000000, -0.1055613458156586, -0.0638541728258133, + 1.0000000000000000, -0.0894841775298119, -1.2914855480194092 + ); +} + +vec3 RGB_to_Lab(vec3 color) { + color = RGB_to_XYZ(color); + color = XYZ_to_LMS(color); + color = cbrt(color); + color = LMS_to_Lab(color); + return color; +} + +vec3 Lab_to_RGB(vec3 color) { + color = Lab_to_LMS(color); + color = pow(color, vec3(3.0)); + color = LMS_to_XYZ(color); + color = XYZ_to_RGB(color); + return color; +} + +vec3 BT2020_to_BT709(vec3 color) { + return color * mat3( + 1.66049100210843540, -0.58764113878854950, -0.072849863319884740, + -0.12455047452159074, 1.13289989712595960, -0.008349422604369515, + -0.01815076335490526, -0.10057889800800737, 1.118729661362913000 + ); +} + +vec4 hook() { + vec4 color = HOOKED_tex(HOOKED_pos); + + vec3 color_dst = BT2020_to_BT709(color.rgb); + vec3 color_dst_cliped = clamp(color_dst, 0.0, 1.0); + bool is_in_gamut = color_dst == color_dst_cliped; + + color.rgb = RGB_to_Lab(color.rgb); + color.rgb = is_in_gamut ? vec3(color.x, vec2(0.0)) : vec3(0.5, color.yz); + color.rgb = Lab_to_RGB(color.rgb); + color.rgb = BT2020_to_BT709(color.rgb); + + return color; +} diff --git a/mpv/shaders/hdr-toys/gamut-mapping/jedypod.glsl b/mpv/shaders/hdr-toys/gamut-mapping/jedypod.glsl new file mode 100644 index 0000000..ef72271 --- /dev/null +++ b/mpv/shaders/hdr-toys/gamut-mapping/jedypod.glsl @@ -0,0 +1,88 @@ +// https://github.com/jedypod/gamut-compress +// https://github.com/ampas/aces-dev/blob/dev/transforms/ctl/lmt/LMT.Academy.ReferenceGamutCompress.ctl + +//!PARAM cyan_limit +//!TYPE float +//!MINIMUM 1.01 +//!MAXIMUM 2.0 +1.216 + +//!PARAM magenta_limit +//!TYPE float +//!MINIMUM 1.01 +//!MAXIMUM 2.0 +1.035 + +//!PARAM yellow_limit +//!TYPE float +//!MINIMUM 1.01 +//!MAXIMUM 2.0 +1.076 + +//!PARAM cyan_threshold +//!TYPE float +//!MINIMUM 0.0 +//!MAXIMUM 1.0 +0.998 + +//!PARAM magenta_threshold +//!TYPE float +//!MINIMUM 0.0 +//!MAXIMUM 1.0 +0.940 + +//!PARAM yellow_threshold +//!TYPE float +//!MINIMUM 0.0 +//!MAXIMUM 1.0 +0.977 + +//!HOOK OUTPUT +//!BIND HOOKED +//!DESC gamut mapping (jedypod) + +// Parabolic compression function +// https://www.desmos.com/calculator/khowxlu6xh +float parabolic(float x, float t0, float x0, float y0) { + float s = (y0 - t0) / sqrt(x0 - y0); + float ox = t0 - s * s / 4.0; + float oy = t0 - s * sqrt(s * s / 4.0); + return (x < t0 ? x : s * sqrt(x - ox) + oy); +} + +vec3 gamut_compress(vec3 rgb) { + // Achromatic axis + float ac = max(max(rgb.r, rgb.g), rgb.b); + + // Inverse RGB Ratios: distance from achromatic axis + vec3 d = ac == 0.0 ? vec3(0.0) : (ac - rgb) / abs(ac); + + // Compressed distance + vec3 cd = vec3( + parabolic(d.x, cyan_threshold, cyan_limit, 1.0), + parabolic(d.y, magenta_threshold, magenta_limit, 1.0), + parabolic(d.z, yellow_threshold, yellow_limit, 1.0) + ); + + // Inverse RGB Ratios to RGB + vec3 crgb = ac - cd * abs(ac); + + return crgb; +} + +vec3 BT2020_to_BT709(vec3 color) { + return color * mat3( + 1.66049100210843540, -0.58764113878854950, -0.072849863319884740, + -0.12455047452159074, 1.13289989712595960, -0.008349422604369515, + -0.01815076335490526, -0.10057889800800737, 1.118729661362913000 + ); +} + +vec4 hook() { + vec4 color = HOOKED_tex(HOOKED_pos); + + color.rgb = BT2020_to_BT709(color.rgb); + color.rgb = gamut_compress(color.rgb); + + return color; +} diff --git a/mpv/shaders/hdr-toys/tone-mapping/aces.glsl b/mpv/shaders/hdr-toys/tone-mapping/aces.glsl new file mode 100644 index 0000000..f721fb1 --- /dev/null +++ b/mpv/shaders/hdr-toys/tone-mapping/aces.glsl @@ -0,0 +1,346 @@ +// The Academy Color Encoding System (ACES) +// https://github.com/ampas/aces-core/tree/v1.3.1 + +//!HOOK OUTPUT +//!BIND HOOKED +//!DESC tone mapping (aces) + +const float TINY = 1e-5; +const float HALF_POS_INF = 31744.0; + +// Gamut Compress Parameters +const float LIM_CYAN = 1.147; +const float LIM_MAGENTA = 1.264; +const float LIM_YELLOW = 1.312; + +const float THR_CYAN = 0.815; +const float THR_MAGENTA = 0.803; +const float THR_YELLOW = 0.880; + +const float PWR = 1.2; + +// "Glow" module constants +const float RRT_GLOW_GAIN = 0.05; +const float RRT_GLOW_MID = 0.08; + +// Red modifier constants +const float RRT_RED_SCALE = 0.82; +const float RRT_RED_PIVOT = 0.03; +const float RRT_RED_HUE = 0.0; +const float RRT_RED_WIDTH = 135.0; + +// Desaturation constants +const float RRT_SAT_FACTOR = 0.96; +const float ODT_SAT_FACTOR = 0.93; + +// Gamma compensation factor +const float DIM_SURROUND_GAMMA = 0.9811; + + +const mat3 RGB_to_XYZ = mat3( + 0.6369580483012914, 0.14461690358620832, 0.1688809751641721, + 0.2627002120112671, 0.6779980715188708, 0.05930171646986196, + 0.000000000000000, 0.028072693049087428, 1.060985057710791 +); + +const mat3 XYZ_to_RGB = mat3( + 1.716651187971268, -0.355670783776392, -0.253366281373660, + -0.666684351832489, 1.616481236634939, 0.0157685458139111, + 0.017639857445311, -0.042770613257809, 0.942103121235474 +); + +const mat3 AP0_to_XYZ = mat3( + 0.9525523959, 0.0000000000, 0.0000936786, + 0.3439664498, 0.7281660966, -0.0721325464, + 0.0000000000, 0.0000000000, 1.0088251844 +); + +const mat3 XYZ_to_AP0 = mat3( + 1.0498110175, 0.0000000000, -0.0000974845, + -0.4959030231, 1.3733130458, 0.0982400361, + 0.0000000000, 0.0000000000, 0.9912520182 +); + +const mat3 AP1_to_XYZ = mat3( + 0.6624541811, 0.1340042065, 0.1561876870, + 0.2722287168, 0.6740817658, 0.0536895174, + -0.0055746495, 0.0040607335, 1.0103391003 +); + +const mat3 XYZ_to_AP1 = mat3( + 1.6410233797, -0.3248032942, -0.2364246952, + -0.6636628587, 1.6153315917, 0.0167563477, + 0.0117218943, -0.0082844420, 0.9883948585 +); + +const vec3 LUMINANCE_AP1 = vec3(0.2722287168, 0.6740817658, 0.0536895174); + +const mat3 D60_to_D65_CAT = mat3( + 0.987224, -0.00611327, 0.0159533, + -0.00759836, 1.00186, 0.00533002, + 0.00307257, -0.00509595, 1.08168 +); + +const mat3 D65_to_D60_CAT = mat3( + 1.0130349238541335252, 0.0061053088545854651618, -0.014970963195236360098, + 0.0076982295895192892886, 0.9981648317745535941, -0.0050320341346474782061, + -0.0028413125165573776196, 0.0046851555780399034147, 0.92450665292696206889 +); + +// Power compression function +// https://www.desmos.com/calculator/iwcyjg6av0 +float compress(float dist, float lim, float thr, float pwr) { + float scl = (lim - thr) / pow(pow((1.0 - thr) / (lim - thr), -pwr) - 1.0, 1.0 / pwr); + float c = thr + (dist - thr) / (pow(1.0 + pow((dist - thr) / scl, pwr), 1.0 / pwr)); + return (dist < thr ? dist : c); +} + +vec3 reference_gamut_compress(vec3 rgb) { + // Achromatic axis + float ac = max(max(rgb.r, rgb.g), rgb.b); + + // Inverse RGB Ratios: distance from achromatic axis + vec3 d = ac == 0.0 ? vec3(0.0) : (ac - rgb) / abs(ac); + + // Compressed distance + vec3 cd = vec3( + compress(d.x, LIM_CYAN, THR_CYAN, PWR), + compress(d.y, LIM_MAGENTA, THR_MAGENTA, PWR), + compress(d.z, LIM_YELLOW, THR_YELLOW, PWR) + ); + + // Inverse RGB Ratios to RGB + vec3 crgb = ac - cd * abs(ac); + + return crgb; +} + +float rgb_to_saturation(vec3 rgb) { + float mi = min(min(rgb.r, rgb.g), rgb.b); + float ma = max(max(rgb.r, rgb.g), rgb.b); + return (max(ma, TINY) - max(mi, TINY)) / max(ma, 1e-2); +} + +// Converts RGB to a luminance proxy, here called YC +// YC is ~ Y + K * Chroma +// Constant YC is a cone-shaped surface in RGB space, with the tip on the +// neutral axis, towards white. +// YC is normalized: RGB 1 1 1 maps to YC = 1 +// +// ycRadiusWeight defaults to 1.75, although can be overridden in function +// call to rgb_to_yc +// ycRadiusWeight = 1 -> YC for pure cyan, magenta, yellow == YC for neutral +// of same value +// ycRadiusWeight = 2 -> YC for pure red, green, blue == YC for neutral of +// same value. +float rgb_to_yc(vec3 rgb) { + float ycRadiusWeight = 1.75; + + float r = rgb.r; + float g = rgb.g; + float b = rgb.b; + + float chroma = sqrt(b * (b - g) + g * (g - r) + r * (r - b)); + + return (b + g + r + ycRadiusWeight * chroma) / 3.0; +} + +// Sigmoid function in the range 0 to 1 spanning -2 to +2. +float sigmoid_shaper(float x) { + float t = max(1.0 - abs(x / 2.0), 0.0); + float y = 1.0 + sign(x) * (1.0 - t * t); + return y / 2.0; +} + +float glow_fwd(float ycIn, float glowGainIn, float glowMid) { + float glowGainOut; + + if (ycIn <= 2.0 / 3.0 * glowMid) { + glowGainOut = glowGainIn; + } else if ( ycIn >= 2.0 * glowMid) { + glowGainOut = 0.0; + } else { + glowGainOut = glowGainIn * (glowMid / ycIn - 1.0 / 2.0); + } + + return glowGainOut; +} + +// Returns a geometric hue angle in degrees (0-360) based on RGB values. +// For neutral colors, hue is undefined and the function will return a quiet NaN value. +float rgb_to_hue(vec3 rgb) { + // RGB triplets where RGB are equal have an undefined hue + float hue = 0.0; + + if (!(rgb.x == rgb.y && rgb.y == rgb.z)) { + float x = sqrt(3.0) * (rgb.y - rgb.z); + float y = 2.0 * rgb.x - rgb.y - rgb.z; + hue = degrees(atan(y, x)); + } + + return (hue < 0.0) ? hue + 360.0 : hue; +} + +float center_hue(float hue, float centerH) { + float hueCentered = hue - centerH; + if (hueCentered < -180.0) { + hueCentered = hueCentered + 360.0; + } else if (hueCentered > 180.0) { + hueCentered = hueCentered - 360.0; + } + return hueCentered; +} + +// Fitting of RRT + ODT (RGB monitor 100 nits dim) from: +// https://github.com/colour-science/colour-unity/blob/master/Assets/Colour/Notebooks/CIECAM02_Unity.ipynb +// RMSE: 0.0012846272106 +vec3 tonescale(vec3 ap1) { + float a = 2.785085; + float b = 0.107772; + float c = 2.936045; + float d = 0.887122; + float e = 0.806889; + return (ap1 * (a * ap1 + b)) / (ap1 * (c * ap1 + d) + e); +} + +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 multiplo = Y / max(y, 1e-6); + + float z = 1.0 - x - y; + float X = x * multiplo; + float Z = z * multiplo; + + return vec3(X, Y, Z); +} + +vec3 darkSurround_to_dimSurround(vec3 linearCV) { + vec3 XYZ = linearCV * AP1_to_XYZ; + vec3 xyY = XYZ_to_xyY(XYZ); + + xyY.z = clamp(xyY.z, 0.0, HALF_POS_INF); + xyY.z = pow(xyY.z, DIM_SURROUND_GAMMA); + + XYZ = xyY_to_XYZ(xyY); + return XYZ * XYZ_to_AP1; +} + +vec3 ACES(vec3 color) { + vec3 ap0; + vec3 ap1; + vec3 cv; + + // Look Modification Transforms (LMTs) + ap1 = color * RGB_to_XYZ * D65_to_D60_CAT * XYZ_to_AP1; + + ap1 = reference_gamut_compress(ap1); + + ap0 = ap1 * AP1_to_XYZ * XYZ_to_AP0; + + + // Reference Rendering Transform (RRT) + + // Glow module + float saturation = rgb_to_saturation(ap0); + float ycIn = rgb_to_yc(ap0); + float s = sigmoid_shaper((saturation - 0.4) / 0.2); + float addedGlow = 1.0 + glow_fwd(ycIn, RRT_GLOW_GAIN * s, RRT_GLOW_MID); + ap0 *= addedGlow; + + // Red modifier + float hue = rgb_to_hue(ap0); + float centeredHue = center_hue(hue, RRT_RED_HUE); + // hueWeight = cubic_basis_shaper(centeredHue, RRT_RED_WIDTH); + float hueWeight = smoothstep(0.0, 1.0, 1.0 - abs(2.0 * centeredHue / RRT_RED_WIDTH)); + hueWeight *= hueWeight; + + ap0.r += hueWeight * saturation * (RRT_RED_PIVOT - ap0.r) * (1.0 - RRT_RED_SCALE); + + // ACES to RGB rendering space + ap1 = ap0 * AP0_to_XYZ * XYZ_to_AP1; + + // avoids saturated negative colors from becoming positive in the matrix + ap1 = clamp(ap1, 0.0, HALF_POS_INF); + + // Global desaturation + ap1 = mix(vec3(dot(ap1, LUMINANCE_AP1)), ap1, RRT_SAT_FACTOR); + + + // Output Device Transform (ODT) + + // Apply the tonescale independently in rendering-space RGB + ap1 = tonescale(ap1); + + // Apply gamma adjustment to compensate for dim surround + cv = darkSurround_to_dimSurround(ap1); + + // Apply desaturation to compensate for luminance difference + cv = mix(vec3(dot(cv, LUMINANCE_AP1)), cv, ODT_SAT_FACTOR); + + // Convert to display primary encoding + cv = cv * AP1_to_XYZ * D60_to_D65_CAT * XYZ_to_RGB; + + return cv; +} + +vec4 hook() { + vec4 color = HOOKED_tex(HOOKED_pos); + + color.rgb = ACES(color.rgb); + + return color; +} + +//!HOOK OUTPUT +//!BIND HOOKED +//!DESC black point compensation + +// https://www.color.org/WP40-Black_Point_Compensation_2010-07-27.pdf + +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 black_point_compensation(vec3 XYZ, float s, float d) { + float r = (1.0 - d) / (1.0 - s); + return r * XYZ + (1.0 - r) * RGB_to_XYZ(vec3(1.0)); +} + +vec4 hook() { + vec4 color = HOOKED_tex(HOOKED_pos); + + color.rgb = RGB_to_XYZ(color.rgb); + color.rgb = black_point_compensation(color.rgb, 0.0, 0.001); + color.rgb = XYZ_to_RGB(color.rgb); + + return color; +} diff --git a/mpv/shaders/hdr-toys/tone-mapping/astra.glsl b/mpv/shaders/hdr-toys/tone-mapping/astra.glsl new file mode 100644 index 0000000..ba01fa6 --- /dev/null +++ b/mpv/shaders/hdr-toys/tone-mapping/astra.glsl @@ -0,0 +1,936 @@ +// Astra, a tone mapping operator designed to preserve the creator's intent + +// shoulder segment: http://filmicworlds.com/blog/filmic-tonemapping-with-piecewise-power-curves/ +// toe segment: https://technorgb.blogspot.com/2018/02/hyperbola-tone-mapping.html +// working space: https://doi.org/10.1364/OE.25.015131 +// hk effect: https://doi.org/10.1364/OE.534073 +// chroma correction: https://www.itu.int/pub/R-REP-BT.2408 +// dynamic metadata: https://github.com/mpv-player/mpv/pull/15239 +// fast gaussian blur: https://www.rastergrid.com/blog/2010/09/efficient-gaussian-blur-with-linear-sampling/ + +//!PARAM min_luma +//!TYPE float +0.0 + +//!PARAM max_luma +//!TYPE float +0.0 + +//!PARAM max_cll +//!TYPE float +0.0 + +//!PARAM max_fall +//!TYPE float +0.0 + +//!PARAM scene_max_r +//!TYPE float +0.0 + +//!PARAM scene_max_g +//!TYPE float +0.0 + +//!PARAM scene_max_b +//!TYPE float +0.0 + +//!PARAM scene_avg +//!TYPE float +0.0 + +//!PARAM max_pq_y +//!TYPE float +0.0 + +//!PARAM avg_pq_y +//!TYPE float +0.0 + +//!PARAM reference_white +//!TYPE float +//!MINIMUM 0.0 +//!MAXIMUM 1000.0 +203.0 + +//!PARAM auto_exposure_anchor +//!TYPE float +//!MINIMUM 0.0 +//!MAXIMUM 1.0 +0.75 + +//!PARAM hk_effect_compensate_scaling +//!TYPE float +//!MINIMUM 0.0 +//!MAXIMUM 1.0 +1.0 + +//!PARAM chroma_correction_scaling +//!TYPE float +//!MINIMUM 0.0 +//!MAXIMUM 1.0 +1.0 + +//!PARAM spatial_stable_iterations +//!TYPE uint +//!MINIMUM 0 +//!MAXIMUM 8 +2 + +//!PARAM temporal_stable_frames +//!TYPE uint +//!MINIMUM 0 +//!MAXIMUM 120 +8 + +//!PARAM enable_metering +//!TYPE uint +//!MINIMUM 0 +//!MAXIMUM 1 +1 + +//!PARAM preview_metering +//!TYPE uint +//!MINIMUM 0 +//!MAXIMUM 1 +0 + +//!BUFFER METERED +//!VAR uint metered_max_i +//!STORAGE + +//!BUFFER METERED_TEMPORAL +//!VAR uint metered_max_i_t[128] +//!STORAGE + +//!HOOK OUTPUT +//!BIND HOOKED +//!SAVE METERING +//!COMPONENTS 1 +//!WIDTH 512 +//!HEIGHT 288 +//!WHEN enable_metering 0 > max_pq_y 0 = * scene_max_r 0 = * scene_max_g 0 = * scene_max_b 0 = * +//!DESC metering (feature map) + +const vec3 y_coef = vec3(0.2627002120112671, 0.6779980715188708, 0.05930171646986196); + +const float m1 = 2610.0 / 4096.0 / 4.0; +const float m2 = 2523.0 / 4096.0 * 128.0; +const float c1 = 3424.0 / 4096.0; +const float c2 = 2413.0 / 4096.0 * 32.0; +const float c3 = 2392.0 / 4096.0 * 32.0; +const float pw = 10000.0; + +float pq_eotf_inv(float x) { + float t = pow(x / pw, m1); + return pow((c1 + c2 * t) / (1.0 + c3 * t), m2); +} + +vec4 hook() { + vec4 color = HOOKED_tex(HOOKED_pos); + float l = dot(color.rgb * reference_white, y_coef); + float i = pq_eotf_inv(l); + return vec4(i, vec3(0.0)); +} + +//!HOOK OUTPUT +//!BIND METERING +//!SAVE METERING +//!WHEN spatial_stable_iterations 0 > +//!DESC metering (spatial stabilization, blur, horizonal) + +const vec4 offset = vec4(0.0, 1.411764705882353, 3.2941176470588234, 5.176470588235294); +const vec4 weight = vec4(0.1964825501511404, 0.2969069646728344, 0.09447039785044732, 0.010381362401148057); +const vec2 direction = vec2(1.0, 0.0); + +vec4 hook(){ + uint i = 0; + vec4 c = METERING_texOff(offset[i]) * weight[i]; + for (i = 1; i < 4; i++) { + c += METERING_texOff( direction * offset[i]) * weight[i]; + c += METERING_texOff(-direction * offset[i]) * weight[i]; + } + return c; +} + +//!HOOK OUTPUT +//!BIND METERING +//!SAVE METERING +//!WHEN spatial_stable_iterations 0 > +//!DESC metering (spatial stabilization, blur, vertical) + +const vec4 offset = vec4(0.0, 1.411764705882353, 3.2941176470588234, 5.176470588235294); +const vec4 weight = vec4(0.1964825501511404, 0.2969069646728344, 0.09447039785044732, 0.010381362401148057); +const vec2 direction = vec2(0.0, 1.0); + +vec4 hook(){ + uint i = 0; + vec4 c = METERING_texOff(offset[i]) * weight[i]; + for (i = 1; i < 4; i++) { + c += METERING_texOff( direction * offset[i]) * weight[i]; + c += METERING_texOff(-direction * offset[i]) * weight[i]; + } + return c; +} + +//!HOOK OUTPUT +//!BIND METERING +//!SAVE METERING +//!WHEN spatial_stable_iterations 1 > +//!DESC metering (spatial stabilization, blur, horizonal) + +const vec4 offset = vec4(0.0, 1.411764705882353, 3.2941176470588234, 5.176470588235294); +const vec4 weight = vec4(0.1964825501511404, 0.2969069646728344, 0.09447039785044732, 0.010381362401148057); +const vec2 direction = vec2(1.0, 0.0); + +vec4 hook(){ + uint i = 0; + vec4 c = METERING_texOff(offset[i]) * weight[i]; + for (i = 1; i < 4; i++) { + c += METERING_texOff( direction * offset[i]) * weight[i]; + c += METERING_texOff(-direction * offset[i]) * weight[i]; + } + return c; +} + +//!HOOK OUTPUT +//!BIND METERING +//!SAVE METERING +//!WHEN spatial_stable_iterations 1 > +//!DESC metering (spatial stabilization, blur, vertical) + +const vec4 offset = vec4(0.0, 1.411764705882353, 3.2941176470588234, 5.176470588235294); +const vec4 weight = vec4(0.1964825501511404, 0.2969069646728344, 0.09447039785044732, 0.010381362401148057); +const vec2 direction = vec2(0.0, 1.0); + +vec4 hook(){ + uint i = 0; + vec4 c = METERING_texOff(offset[i]) * weight[i]; + for (i = 1; i < 4; i++) { + c += METERING_texOff( direction * offset[i]) * weight[i]; + c += METERING_texOff(-direction * offset[i]) * weight[i]; + } + return c; +} + +//!HOOK OUTPUT +//!BIND METERING +//!SAVE METERING +//!WHEN spatial_stable_iterations 2 > +//!DESC metering (spatial stabilization, blur, horizonal) + +const vec4 offset = vec4(0.0, 1.411764705882353, 3.2941176470588234, 5.176470588235294); +const vec4 weight = vec4(0.1964825501511404, 0.2969069646728344, 0.09447039785044732, 0.010381362401148057); +const vec2 direction = vec2(1.0, 0.0); + +vec4 hook(){ + uint i = 0; + vec4 c = METERING_texOff(offset[i]) * weight[i]; + for (i = 1; i < 4; i++) { + c += METERING_texOff( direction * offset[i]) * weight[i]; + c += METERING_texOff(-direction * offset[i]) * weight[i]; + } + return c; +} + +//!HOOK OUTPUT +//!BIND METERING +//!SAVE METERING +//!WHEN spatial_stable_iterations 2 > +//!DESC metering (spatial stabilization, blur, vertical) + +const vec4 offset = vec4(0.0, 1.411764705882353, 3.2941176470588234, 5.176470588235294); +const vec4 weight = vec4(0.1964825501511404, 0.2969069646728344, 0.09447039785044732, 0.010381362401148057); +const vec2 direction = vec2(0.0, 1.0); + +vec4 hook(){ + uint i = 0; + vec4 c = METERING_texOff(offset[i]) * weight[i]; + for (i = 1; i < 4; i++) { + c += METERING_texOff( direction * offset[i]) * weight[i]; + c += METERING_texOff(-direction * offset[i]) * weight[i]; + } + return c; +} + +//!HOOK OUTPUT +//!BIND METERING +//!SAVE METERING +//!WHEN spatial_stable_iterations 3 > +//!DESC metering (spatial stabilization, blur, horizonal) + +const vec4 offset = vec4(0.0, 1.411764705882353, 3.2941176470588234, 5.176470588235294); +const vec4 weight = vec4(0.1964825501511404, 0.2969069646728344, 0.09447039785044732, 0.010381362401148057); +const vec2 direction = vec2(1.0, 0.0); + +vec4 hook(){ + uint i = 0; + vec4 c = METERING_texOff(offset[i]) * weight[i]; + for (i = 1; i < 4; i++) { + c += METERING_texOff( direction * offset[i]) * weight[i]; + c += METERING_texOff(-direction * offset[i]) * weight[i]; + } + return c; +} + +//!HOOK OUTPUT +//!BIND METERING +//!SAVE METERING +//!WHEN spatial_stable_iterations 3 > +//!DESC metering (spatial stabilization, blur, vertical) + +const vec4 offset = vec4(0.0, 1.411764705882353, 3.2941176470588234, 5.176470588235294); +const vec4 weight = vec4(0.1964825501511404, 0.2969069646728344, 0.09447039785044732, 0.010381362401148057); +const vec2 direction = vec2(0.0, 1.0); + +vec4 hook(){ + uint i = 0; + vec4 c = METERING_texOff(offset[i]) * weight[i]; + for (i = 1; i < 4; i++) { + c += METERING_texOff( direction * offset[i]) * weight[i]; + c += METERING_texOff(-direction * offset[i]) * weight[i]; + } + return c; +} + +//!HOOK OUTPUT +//!BIND METERING +//!SAVE METERING +//!WHEN spatial_stable_iterations 4 > +//!DESC metering (spatial stabilization, blur, horizonal) + +const vec4 offset = vec4(0.0, 1.411764705882353, 3.2941176470588234, 5.176470588235294); +const vec4 weight = vec4(0.1964825501511404, 0.2969069646728344, 0.09447039785044732, 0.010381362401148057); +const vec2 direction = vec2(1.0, 0.0); + +vec4 hook(){ + uint i = 0; + vec4 c = METERING_texOff(offset[i]) * weight[i]; + for (i = 1; i < 4; i++) { + c += METERING_texOff( direction * offset[i]) * weight[i]; + c += METERING_texOff(-direction * offset[i]) * weight[i]; + } + return c; +} + +//!HOOK OUTPUT +//!BIND METERING +//!SAVE METERING +//!WHEN spatial_stable_iterations 4 > +//!DESC metering (spatial stabilization, blur, vertical) + +const vec4 offset = vec4(0.0, 1.411764705882353, 3.2941176470588234, 5.176470588235294); +const vec4 weight = vec4(0.1964825501511404, 0.2969069646728344, 0.09447039785044732, 0.010381362401148057); +const vec2 direction = vec2(0.0, 1.0); + +vec4 hook(){ + uint i = 0; + vec4 c = METERING_texOff(offset[i]) * weight[i]; + for (i = 1; i < 4; i++) { + c += METERING_texOff( direction * offset[i]) * weight[i]; + c += METERING_texOff(-direction * offset[i]) * weight[i]; + } + return c; +} + +//!HOOK OUTPUT +//!BIND METERING +//!SAVE METERING +//!WHEN spatial_stable_iterations 5 > +//!DESC metering (spatial stabilization, blur, horizonal) + +const vec4 offset = vec4(0.0, 1.411764705882353, 3.2941176470588234, 5.176470588235294); +const vec4 weight = vec4(0.1964825501511404, 0.2969069646728344, 0.09447039785044732, 0.010381362401148057); +const vec2 direction = vec2(1.0, 0.0); + +vec4 hook(){ + uint i = 0; + vec4 c = METERING_texOff(offset[i]) * weight[i]; + for (i = 1; i < 4; i++) { + c += METERING_texOff( direction * offset[i]) * weight[i]; + c += METERING_texOff(-direction * offset[i]) * weight[i]; + } + return c; +} + +//!HOOK OUTPUT +//!BIND METERING +//!SAVE METERING +//!WHEN spatial_stable_iterations 5 > +//!DESC metering (spatial stabilization, blur, vertical) + +const vec4 offset = vec4(0.0, 1.411764705882353, 3.2941176470588234, 5.176470588235294); +const vec4 weight = vec4(0.1964825501511404, 0.2969069646728344, 0.09447039785044732, 0.010381362401148057); +const vec2 direction = vec2(0.0, 1.0); + +vec4 hook(){ + uint i = 0; + vec4 c = METERING_texOff(offset[i]) * weight[i]; + for (i = 1; i < 4; i++) { + c += METERING_texOff( direction * offset[i]) * weight[i]; + c += METERING_texOff(-direction * offset[i]) * weight[i]; + } + return c; +} + +//!HOOK OUTPUT +//!BIND METERING +//!SAVE METERING +//!WHEN spatial_stable_iterations 6 > +//!DESC metering (spatial stabilization, blur, horizonal) + +const vec4 offset = vec4(0.0, 1.411764705882353, 3.2941176470588234, 5.176470588235294); +const vec4 weight = vec4(0.1964825501511404, 0.2969069646728344, 0.09447039785044732, 0.010381362401148057); +const vec2 direction = vec2(1.0, 0.0); + +vec4 hook(){ + uint i = 0; + vec4 c = METERING_texOff(offset[i]) * weight[i]; + for (i = 1; i < 4; i++) { + c += METERING_texOff( direction * offset[i]) * weight[i]; + c += METERING_texOff(-direction * offset[i]) * weight[i]; + } + return c; +} + +//!HOOK OUTPUT +//!BIND METERING +//!SAVE METERING +//!WHEN spatial_stable_iterations 6 > +//!DESC metering (spatial stabilization, blur, vertical) + +const vec4 offset = vec4(0.0, 1.411764705882353, 3.2941176470588234, 5.176470588235294); +const vec4 weight = vec4(0.1964825501511404, 0.2969069646728344, 0.09447039785044732, 0.010381362401148057); +const vec2 direction = vec2(0.0, 1.0); + +vec4 hook(){ + uint i = 0; + vec4 c = METERING_texOff(offset[i]) * weight[i]; + for (i = 1; i < 4; i++) { + c += METERING_texOff( direction * offset[i]) * weight[i]; + c += METERING_texOff(-direction * offset[i]) * weight[i]; + } + return c; +} + +//!HOOK OUTPUT +//!BIND METERING +//!SAVE METERING +//!WHEN spatial_stable_iterations 7 > +//!DESC metering (spatial stabilization, blur, horizonal) + +const vec4 offset = vec4(0.0, 1.411764705882353, 3.2941176470588234, 5.176470588235294); +const vec4 weight = vec4(0.1964825501511404, 0.2969069646728344, 0.09447039785044732, 0.010381362401148057); +const vec2 direction = vec2(1.0, 0.0); + +vec4 hook(){ + uint i = 0; + vec4 c = METERING_texOff(offset[i]) * weight[i]; + for (i = 1; i < 4; i++) { + c += METERING_texOff( direction * offset[i]) * weight[i]; + c += METERING_texOff(-direction * offset[i]) * weight[i]; + } + return c; +} + +//!HOOK OUTPUT +//!BIND METERING +//!SAVE METERING +//!WHEN spatial_stable_iterations 7 > +//!DESC metering (spatial stabilization, blur, vertical) + +const vec4 offset = vec4(0.0, 1.411764705882353, 3.2941176470588234, 5.176470588235294); +const vec4 weight = vec4(0.1964825501511404, 0.2969069646728344, 0.09447039785044732, 0.010381362401148057); +const vec2 direction = vec2(0.0, 1.0); + +vec4 hook(){ + uint i = 0; + vec4 c = METERING_texOff(offset[i]) * weight[i]; + for (i = 1; i < 4; i++) { + c += METERING_texOff( direction * offset[i]) * weight[i]; + c += METERING_texOff(-direction * offset[i]) * weight[i]; + } + return c; +} + +//!HOOK OUTPUT +//!BIND METERING +//!BIND METERED +//!SAVE EMPTY +//!COMPUTE 32 32 +//!DESC metering (data, max) + +shared uint local_max; + +void hook() { + if (gl_GlobalInvocationID.x == 0 && gl_GlobalInvocationID.y == 0) { + metered_max_i = 0; + } + + if (gl_LocalInvocationIndex == 0) { + local_max = 0; + } + + memoryBarrierShared(); + barrier(); + + float value = METERING_tex(METERING_pos).x; + uint rounded = uint(value * 4095.0 + 0.5); + atomicMax(local_max, rounded); + + memoryBarrierShared(); + barrier(); + + if (gl_LocalInvocationIndex == 0) { + atomicMax(metered_max_i, local_max); + } +} + +//!HOOK OUTPUT +//!BIND METERING +//!BIND METERED +//!BIND METERED_TEMPORAL +//!SAVE EMPTY +//!WIDTH 1 +//!HEIGHT 1 +//!COMPUTE 1 1 +//!WHEN temporal_stable_frames +//!DESC metering (temporal stabilization) + +void temporal_prepend() { + for (uint i = temporal_stable_frames - 1; i > 0; i--) { + metered_max_i_t[i] = metered_max_i_t[i - 1]; + } + metered_max_i_t[0] = metered_max_i; +} + +float temporal_harmonic_mean() { + float sum = 0.0; + for (uint i = 0; i < temporal_stable_frames; i++) { + float current = float(metered_max_i_t[i]); + sum += 1.0 / max(current, 1e-6); + } + return temporal_stable_frames / sum; +} + +void temporal_fill() { + for (uint i = 0; i < temporal_stable_frames; i++) { + metered_max_i_t[i] = metered_max_i; + } +} + +float temporal_predict() { + float sum_x = 0.0; + float sum_y = 0.0; + float sum_x2 = 0.0; + float sum_xy = 0.0; + + float n = temporal_stable_frames; + float xp = float(n + 1); + + for (int i = 0; i < n; i++) { + float x = float(i + 1); + sum_x += x; + sum_y += metered_max_i_t[i]; + sum_x2 += x * x; + sum_xy += x * metered_max_i_t[i]; + } + + float a = (n * sum_xy - sum_x * sum_y) / (n * sum_x2 - sum_x * sum_x); + float b = (sum_y - a * sum_x) / float(n); + + return a * xp + b; +} + +bool is_sence_changed(float m, float p) { + float black = 16.0; + if (black > metered_max_i) + return true; + + float tolerance = 36.0; + float im = float(m) / 4095.0; + float ip = float(p) / 4095.0; + float delta = 720 * abs(im - ip); + return delta > tolerance; +} + +void hook() { + float p = temporal_predict(); + temporal_prepend(); + float m = temporal_harmonic_mean(); + + if (is_sence_changed(m, p)) { + temporal_fill(); + return; + } + + metered_max_i = uint(m + 0.5); +} + +//!HOOK OUTPUT +//!BIND HOOKED +//!BIND METERING +//!BIND METERED +//!WHEN preview_metering +//!DESC metering (preview) + +bool almost_equal(float a, float b, float epsilon) { + return abs(a - b) < epsilon; +} + +vec4 hook() { + float metering = METERING_tex(METERING_pos).x; + float lmi = float(metered_max_i) / 4095.0; + + vec3 color = vec3(metering); + + float delta = 720 * abs(metering - lmi); + if (delta < 4.0) + color = vec3(1.0, 0.0, 0.0); + + if (almost_equal(1.0 - METERING_pos.y, lmi, 1e-3)) + color = vec3(0.0, 1.0, 0.0); + + return vec4(color, 1.0); +} + +//!HOOK OUTPUT +//!BIND HOOKED +//!BIND METERED +//!DESC tone mapping (astra) + +const float m1 = 2610.0 / 4096.0 / 4.0; +const float m2 = 2523.0 / 4096.0 * 128.0; +const float c1 = 3424.0 / 4096.0; +const float c2 = 2413.0 / 4096.0 * 32.0; +const float c3 = 2392.0 / 4096.0 * 32.0; +const float pw = 10000.0; + +float pq_eotf_inv(float x) { + float t = pow(x / pw, m1); + return pow((c1 + c2 * t) / (1.0 + c3 * t), m2); +} + +vec3 pq_eotf_inv(vec3 color) { + return vec3( + pq_eotf_inv(color.r), + pq_eotf_inv(color.g), + pq_eotf_inv(color.b) + ); +} + +float pq_eotf(float x) { + float t = pow(x, 1.0 / m2); + return pow(max(t - c1, 0.0) / (c2 - c3 * t), 1.0 / m1) * pw; +} + +vec3 pq_eotf(vec3 color) { + return vec3( + pq_eotf(color.r), + pq_eotf(color.g), + pq_eotf(color.b) + ); +} + +vec3 RGB_to_XYZ(vec3 RGB) { + return RGB * mat3( + 0.6369580483012914, 0.14461690358620832, 0.1688809751641721, + 0.2627002120112671, 0.6779980715188708, 0.05930171646986196, + 0.0 , 0.028072693049087428, 1.060985057710791 + ); +} + +vec3 XYZ_to_RGB(vec3 XYZ) { + return XYZ * mat3( + 1.716651187971268, -0.355670783776392, -0.25336628137366, + -0.666684351832489, 1.616481236634939, 0.0157685458139111, + 0.017639857445311, -0.042770613257809, 0.942103121235474 + ); +} + +const float b = 1.15; +const float g = 0.66; + +vec3 XYZ_to_XYZm(vec3 XYZ) { + float Xm = (b * XYZ.x) - ((b - 1.0) * XYZ.z); + float Ym = (g * XYZ.y) - ((g - 1.0) * XYZ.x); + return vec3(Xm, Ym, XYZ.z); +} + +vec3 XYZm_to_XYZ(vec3 XYZm) { + float Xa = (XYZm.x + ((b - 1.0) * XYZm.z)) / b; + float Ya = (XYZm.y + ((g - 1.0) * Xa)) / g; + return vec3(Xa, Ya, XYZm.z); +} + +vec3 XYZ_to_LMS(vec3 XYZ) { + return XYZ * mat3( + 0.41478972, 0.579999, 0.0146480, + -0.2015100, 1.120649, 0.0531008, + -0.0166008, 0.264800, 0.6684799 + ); +} + +vec3 LMS_to_XYZ(vec3 LMS) { + return LMS * mat3( + 1.9242264357876067, -1.0047923125953657, 0.037651404030618, + 0.35031676209499907, 0.7264811939316552, -0.06538442294808501, + -0.09098281098284752, -0.3127282905230739, 1.5227665613052603 + ); +} + +vec3 LMS_to_Iab(vec3 LMS) { + return LMS * mat3( + 0.5, 0.5, 0.0, + 3.524000, -4.066708, 0.542708, + 0.199076, 1.096799, -1.295875 + ); +} + +vec3 Iab_to_LMS(vec3 Iab) { + return Iab * mat3( + 1.0, 0.1386050432715393, 0.0580473161561189, + 1.0, -0.1386050432715393, -0.0580473161561189, + 1.0, -0.0960192420263190, -0.8118918960560390 + ); +} + +const float d = -0.56; +const float d0 = 1.6295499532821566e-11; + +float I_to_J(float I) { + return ((1.0 + d) * I) / (1.0 + (d * I)) - d0; +} + +float J_to_I(float J) { + return (J + d0) / (1.0 + d - d * (J + d0)); +} + +float hke_fh_hellwig(float h, float a1, float a2, float a3, float a4, float a5) { + return a1 * cos(h) + a2 * cos(2.0 * h) + a3 * sin(h) + a4 * sin(2.0 * h) + a5; +} + +float hke_fh_high(float h, float k1, float k2, float k3, float k4) { + h = mod(mod(degrees(h), 360.0) + 360.0, 360.0); + float by = k1 * abs(sin(radians((h - 90.0)/ 2.0))) + k2; + float r = h <= 90.0 || h >= 270.0 ? k3 * abs(cos(radians(h))) + k4 : 0.0; + return by + r; +} + +float hke_fh_liao(float h, float k3, float k4, float k5) { + h = mod(mod(degrees(h), 360.0) + 360.0, 360.0); + return k3 * abs(log(((h + k4) / (90.0 + k4)))) + k5; +} + +float hke_fh(float h) { + float result = hke_fh_liao(h, 0.3495, 45.0, 0.1567); + return result * hk_effect_compensate_scaling; +} + +float J_to_Jhk(vec3 JCh) { + float J = JCh.x; + float C = JCh.y; + float h = JCh.z; + return J + C * hke_fh(h); +} + +float Jhk_to_J(vec3 JCh) { + float J = JCh.x; + float C = JCh.y; + float h = JCh.z; + return J - C * hke_fh(h); +} + +// https://github.com/color-js/color.js/pull/629 +const float epsilon = 0.0002363; + +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); +} + +vec3 RGB_to_Jab(vec3 color) { + color *= reference_white; + color = RGB_to_XYZ(color); + color = XYZ_to_XYZm(color); + color = XYZ_to_LMS(color); + color = pq_eotf_inv(color); + color = LMS_to_Iab(color); + color.x = I_to_J(color.x); + color.x = J_to_Jhk(Lab_to_LCh(color)); + return color; +} + +vec3 Jab_to_RGB(vec3 color) { + color.x = Jhk_to_J(Lab_to_LCh(color)); + color.x = J_to_I(color.x); + color = Iab_to_LMS(color); + color = pq_eotf(color); + color = LMS_to_XYZ(color); + color = XYZm_to_XYZ(color); + color = XYZ_to_RGB(color); + color /= reference_white; + return color; +} + +float get_max_i() { + if (max_pq_y > 0.0) + return max_pq_y; + + if (scene_max_r > 0.0 || scene_max_g > 0.0 || scene_max_b > 0.0) { + vec3 scene_max_rgb = vec3(scene_max_r, scene_max_g, scene_max_b); + return pq_eotf_inv(RGB_to_XYZ(scene_max_rgb).y); + } + + if (enable_metering > 0) + return float(metered_max_i) / 4095.0; + + if (max_cll > 0.0) + return pq_eotf_inv(max_cll); + + if (max_luma > 0.0) + return pq_eotf_inv(max_luma); + + return pq_eotf_inv(1000.0); +} + +float get_min_i() { + if (min_luma > 0.0) + return pq_eotf_inv(min_luma); + + return pq_eotf_inv(0.001); +} + +float get_avg_i() { + if (avg_pq_y > 0.0) + return avg_pq_y; + + if (scene_avg > 0.0) + return pq_eotf_inv(scene_avg); + + // if (max_fall > 0.0) + // return pq_eotf_inv(max_fall); + + return 0.0; +} + +float ev = 0.0; + +vec3 auto_exposure(vec3 color) { + if (auto_exposure_anchor <= 0.0) + return color; + + float avg_i = get_avg_i(); + + if (avg_i <= 0.0) + return color; + + float ach = pq_eotf(J_to_I( + auto_exposure_anchor * + I_to_J(pq_eotf_inv(reference_white)) + )); + float avg = pq_eotf(avg_i); + float mxx = pq_eotf(get_max_i()); + float ref = reference_white; + float old = 100.0; + + float ev_min = min(log2(max(ref / mxx, 1e-6)), 0.0); + float ev_max = max(log2(max(ref / old, 1e-6)), 0.0); + + ev = log2(max(ach / avg, 1e-6)); + ev = clamp(ev, ev_min, ev_max); + + return color * exp2(ev); +} + +float f(float x, float iw, float ib, float ow, float ob) { + float midgray = 0.5 * ow; + float shadow = mix(midgray, ob, 0.66); + float highlight = mix(midgray, ow, 0.04); + + float x0 = ib; + float y0 = ob; + float x1 = shadow; + float y1 = shadow; + float x2 = highlight; + float y2 = highlight; + float x3 = iw; + float y3 = ow; + + float al = (y2 - y1) / (x2 - x1); + + if (x < x1) { + float at = al * (x1 - x0) * (x1 - x0) * (y1 - y0) * (y1 - y0) / ((y1 - y0 - al * (x1 - x0)) * (y1 - y0 - al * (x1 - x0))); + float bt = al * (x1 - x0) * (x1 - x0) / (y1 - y0 - al * (x1 - x0)); + float ct = (y1 - y0) * (y1 - y0) / (y1 - y0 - al * (x1 - x0)); + x = -at / (x - x0 + bt) + ct + y0; + } else if (x < x2) { + float bl = y1 - al * x1; + x = al * x + bl; + } else { + float bs = al * (x3 - x2) / (y3 - y2); + float as = log(y3 - y2) - bs * log(x3 - x2); + x = -exp(as + bs * log(max(-(x - x3), 1e-6))) + y3; + } + + return clamp(x, ob, ow); +} + +float curve(float x) { + float ow = I_to_J(pq_eotf_inv(reference_white)); + float ob = I_to_J(pq_eotf_inv(reference_white / 1000.0)); + float iw = get_max_i(); + float ib = get_min_i(); + + if (ev != 0.0) { + iw = pq_eotf_inv(pq_eotf(iw) * exp2(ev)); + ib = pq_eotf_inv(pq_eotf(ib) * exp2(ev)); + } + + iw = max(I_to_J(iw), ow + 1e-3); + ib = min(I_to_J(ib), ob - 1e-3); + + return f(x, iw, ib, ow, ob); +} + +vec2 chroma_correction(vec2 ab, float i1, float i2) { + float r1 = i1 / max(i2, 1e-6); + float r2 = i2 / max(i1, 1e-6); + return ab * mix(1.0, min(r1, r2), chroma_correction_scaling); +} + +vec3 tone_mapping(vec3 iab) { + float i2 = curve(iab.x); + vec2 ab2 = chroma_correction(iab.yz, iab.x, i2); + return vec3(i2, ab2); +} + +vec4 hook() { + vec4 color = HOOKED_tex(HOOKED_pos); + + color.rgb = auto_exposure(color.rgb); + color.rgb = RGB_to_Jab(color.rgb); + color.rgb = tone_mapping(color.rgb); + color.rgb = Jab_to_RGB(color.rgb); + + return color; +} diff --git a/mpv/shaders/hdr-toys/tone-mapping/bt2390.glsl b/mpv/shaders/hdr-toys/tone-mapping/bt2390.glsl new file mode 100644 index 0000000..a02b1b5 --- /dev/null +++ b/mpv/shaders/hdr-toys/tone-mapping/bt2390.glsl @@ -0,0 +1,764 @@ +// ITU-R BT.2390 EETF +// https://www.itu.int/pub/R-REP-BT.2390 +// https://www.itu.int/pub/R-REP-BT.2408 + +//!PARAM min_luma +//!TYPE float +0.0 + +//!PARAM max_luma +//!TYPE float +0.0 + +//!PARAM max_cll +//!TYPE float +0.0 + +//!PARAM scene_max_r +//!TYPE float +0.0 + +//!PARAM scene_max_g +//!TYPE float +0.0 + +//!PARAM scene_max_b +//!TYPE float +0.0 + +//!PARAM max_pq_y +//!TYPE float +0.0 + +//!PARAM reference_white +//!TYPE float +//!MINIMUM 0.0 +//!MAXIMUM 1000.0 +203.0 + +//!PARAM chroma_correction_scaling +//!TYPE float +//!MINIMUM 0.0 +//!MAXIMUM 1.0 +1.0 + +//!PARAM representation +//!TYPE ENUM int +ictcp +ycbcr +yrgb +prergb +maxrgb + +//!HOOK OUTPUT +//!BIND HOOKED +//!WHEN representation 0 = +//!DESC tone mapping (bt.2390, ICtCp) + +const float m1 = 2610.0 / 4096.0 / 4.0; +const float m2 = 2523.0 / 4096.0 * 128.0; +const float c1 = 3424.0 / 4096.0; +const float c2 = 2413.0 / 4096.0 * 32.0; +const float c3 = 2392.0 / 4096.0 * 32.0; +const float pw = 10000.0; + +float pq_eotf_inv(float x) { + float t = pow(x / pw, m1); + return pow((c1 + c2 * t) / (1.0 + c3 * t), m2); +} + +vec3 pq_eotf_inv(vec3 color) { + return vec3( + pq_eotf_inv(color.r), + pq_eotf_inv(color.g), + pq_eotf_inv(color.b) + ); +} + +float pq_eotf(float x) { + float t = pow(x, 1.0 / m2); + return pow(max(t - c1, 0.0) / (c2 - c3 * t), 1.0 / m1) * pw; +} + +vec3 pq_eotf(vec3 color) { + return vec3( + pq_eotf(color.r), + pq_eotf(color.g), + pq_eotf(color.b) + ); +} + +vec3 RGB_to_XYZ(vec3 RGB) { + return RGB * mat3( + 0.6369580483012914, 0.14461690358620832, 0.1688809751641721, + 0.2627002120112671, 0.6779980715188708, 0.05930171646986196, + 0.0 , 0.028072693049087428, 1.060985057710791 + ); +} + +vec3 XYZ_to_RGB(vec3 XYZ) { + return XYZ * mat3( + 1.716651187971268, -0.355670783776392, -0.25336628137366, + -0.666684351832489, 1.616481236634939, 0.0157685458139111, + 0.017639857445311, -0.042770613257809, 0.942103121235474 + ); +} + +vec3 XYZ_to_LMS(vec3 XYZ) { + return XYZ * mat3( + 0.3592832590121217, 0.6976051147779502, -0.0358915932320290, + -0.1920808463704993, 1.1004767970374321, 0.0753748658519118, + 0.0070797844607479, 0.0748396662186362, 0.8433265453898765 + ); +} + +vec3 LMS_to_XYZ(vec3 LMS) { + return LMS * mat3( + 2.0701522183894223, -1.3263473389671563, 0.2066510476294053, + 0.3647385209748072, 0.6805660249472273, -0.0453045459220347, + -0.0497472075358123, -0.0492609666966131, 1.1880659249923042 + ); +} + +vec3 LMS_to_ICtCp(vec3 LMS) { + return LMS * mat3( + 2048.0 / 4096.0, 2048.0 / 4096.0, 0.0 / 4096.0, + 6610.0 / 4096.0, -13613.0 / 4096.0, 7003.0 / 4096.0, + 17933.0 / 4096.0, -17390.0 / 4096.0, -543.0 / 4096.0 + ); +} + +vec3 ICtCp_to_LMS(vec3 ICtCp) { + return ICtCp * mat3( + 1.0, 0.0086090370379328, 0.1110296250030260, + 1.0, -0.0086090370379328, -0.1110296250030260, + 1.0, 0.5600313357106791, -0.3206271749873189 + ); +} + +vec3 RGB_to_ICtCp(vec3 color) { + color *= reference_white; + color = RGB_to_XYZ(color); + color = XYZ_to_LMS(color); + color = pq_eotf_inv(color); + color = LMS_to_ICtCp(color); + return color; +} + +vec3 ICtCp_to_RGB(vec3 color) { + color = ICtCp_to_LMS(color); + color = pq_eotf(color); + color = LMS_to_XYZ(color); + color = XYZ_to_RGB(color); + color /= reference_white; + return color; +} + +float get_max_i() { + if (max_pq_y > 0.0) + return max_pq_y; + + if (scene_max_r > 0.0 || scene_max_g > 0.0 || scene_max_b > 0.0) { + vec3 scene_max_rgb = vec3(scene_max_r, scene_max_g, scene_max_b); + return pq_eotf_inv(RGB_to_XYZ(scene_max_rgb).y); + } + + if (max_cll > 0.0) + return pq_eotf_inv(max_cll); + + if (max_luma > 0.0) + return pq_eotf_inv(max_luma); + + return pq_eotf_inv(1000.0); +} + +float get_min_i() { + if (min_luma > 0.0) + return pq_eotf_inv(min_luma); + + return pq_eotf_inv(0.001); +} + +float f(float x, float iw, float ib, float ow, float ob) { + float minLum = (ob - ib) / (iw - ib); + float maxLum = (ow - ib) / (iw - ib); + + float KS = 1.5 * maxLum - 0.5; + float b = minLum; + + // E1 + x = (x - ib) / (iw - ib); + + // E2 + if (KS <= x) { + float TB = (x - KS) / (1.0 - KS); + float TB2 = TB * TB; + float TB3 = TB * TB2; + + float PB = (2.0 * TB3 - 3.0 * TB2 + 1.0) * KS + + (TB3 - 2.0 * TB2 + TB) * (1.0 - KS) + + (-2.0 * TB3 + 3.0 * TB2) * maxLum; + + x = PB; + } + + // E3 + if (0.0 <= x) { + x = x + b * pow((1.0 - x), 4.0); + } + + // E4 + x = x * (iw - ib) + ib; + + return clamp(x, ob, ow); +} + +float curve(float x) { + float ow = pq_eotf_inv(reference_white); + float ob = pq_eotf_inv(reference_white / 1000.0); + float iw = max(get_max_i(), ow + 1e-3); + float ib = min(get_min_i(), ob - 1e-3); + return f(x, iw, ib, ow, ob); +} + +vec2 chroma_correction(vec2 ab, float i1, float i2) { + float r1 = i1 / max(i2, 1e-6); + float r2 = i2 / max(i1, 1e-6); + return ab * mix(1.0, min(r1, r2), chroma_correction_scaling); +} + +vec3 tone_mapping(vec3 iab) { + float i2 = curve(iab.x); + vec2 ab2 = chroma_correction(iab.yz, iab.x, i2); + return vec3(i2, ab2); +} + +vec4 hook() { + vec4 color = HOOKED_tex(HOOKED_pos); + + color.rgb = RGB_to_ICtCp(color.rgb); + color.rgb = tone_mapping(color.rgb); + color.rgb = ICtCp_to_RGB(color.rgb); + + return color; +} + +//!HOOK OUTPUT +//!BIND HOOKED +//!WHEN representation 1 = +//!DESC tone mapping (bt.2390, Y'Cb'Cr') + +const float m1 = 2610.0 / 4096.0 / 4.0; +const float m2 = 2523.0 / 4096.0 * 128.0; +const float c1 = 3424.0 / 4096.0; +const float c2 = 2413.0 / 4096.0 * 32.0; +const float c3 = 2392.0 / 4096.0 * 32.0; +const float pw = 10000.0; + +float pq_eotf_inv(float x) { + float t = pow(x / pw, m1); + return pow((c1 + c2 * t) / (1.0 + c3 * t), m2); +} + +vec3 pq_eotf_inv(vec3 color) { + return vec3( + pq_eotf_inv(color.r), + pq_eotf_inv(color.g), + pq_eotf_inv(color.b) + ); +} + +float pq_eotf(float x) { + float t = pow(x, 1.0 / m2); + return pow(max(t - c1, 0.0) / (c2 - c3 * t), 1.0 / m1) * pw; +} + +vec3 pq_eotf(vec3 color) { + return vec3( + pq_eotf(color.r), + pq_eotf(color.g), + pq_eotf(color.b) + ); +} + +const vec3 y_coef = vec3(0.2627002120112671, 0.6779980715188708, 0.05930171646986196); + +const float a = y_coef.r; +const float b = y_coef.g; +const float c = y_coef.b; +const float d = 2.0 * (1.0 - c); +const float e = 2.0 * (1.0 - a); + +vec3 RGB_to_YCbCr(vec3 RGB) { + return RGB * mat3( + a, b, c, + -a / d, -b / d, 0.5, + 0.5, -b / e, -c / e + ); +} + +vec3 YCbCr_to_RGB(vec3 YCbCr) { + return YCbCr * mat3( + 1.0, 0.0, e, + 1.0, -c / b * d, -a / b * e, + 1.0, d, 0.0 + ); +} + +float get_max_i() { + if (max_pq_y > 0.0) + return max_pq_y; + + if (scene_max_r > 0.0 || scene_max_g > 0.0 || scene_max_b > 0.0) { + vec3 scene_max_rgb = vec3(scene_max_r, scene_max_g, scene_max_b); + return pq_eotf_inv(dot(scene_max_rgb, y_coef)); + } + + if (max_cll > 0.0) + return pq_eotf_inv(max_cll); + + if (max_luma > 0.0) + return pq_eotf_inv(max_luma); + + return pq_eotf_inv(1000.0); +} + +float get_min_i() { + if (min_luma > 0.0) + return pq_eotf_inv(min_luma); + + return pq_eotf_inv(0.001); +} + +float f(float x, float iw, float ib, float ow, float ob) { + float minLum = (ob - ib) / (iw - ib); + float maxLum = (ow - ib) / (iw - ib); + + float KS = 1.5 * maxLum - 0.5; + float b = minLum; + + // E1 + x = (x - ib) / (iw - ib); + + // E2 + if (KS <= x) { + float TB = (x - KS) / (1.0 - KS); + float TB2 = TB * TB; + float TB3 = TB * TB2; + + float PB = (2.0 * TB3 - 3.0 * TB2 + 1.0) * KS + + (TB3 - 2.0 * TB2 + TB) * (1.0 - KS) + + (-2.0 * TB3 + 3.0 * TB2) * maxLum; + + x = PB; + } + + // E3 + if (0.0 <= x) { + x = x + b * pow((1.0 - x), 4.0); + } + + // E4 + x = x * (iw - ib) + ib; + + return clamp(x, ob, ow); +} + +float curve(float x) { + float ow = pq_eotf_inv(reference_white); + float ob = pq_eotf_inv(reference_white / 1000.0); + float iw = max(get_max_i(), ow + 1e-3); + float ib = min(get_min_i(), ob - 1e-3); + return f(x, iw, ib, ow, ob); +} + +vec2 chroma_correction(vec2 ab, float i1, float i2) { + float r1 = i1 / max(i2, 1e-6); + float r2 = i2 / max(i1, 1e-6); + return ab * mix(1.0, min(r1, r2), chroma_correction_scaling); +} + +vec3 tone_mapping(vec3 iab) { + float i2 = curve(iab.x); + vec2 ab2 = chroma_correction(iab.yz, iab.x, i2); + return vec3(i2, ab2); +} + +vec4 hook() { + vec4 color = HOOKED_tex(HOOKED_pos); + + color.rgb = pq_eotf_inv(color.rgb * reference_white); + color.rgb = RGB_to_YCbCr(color.rgb); + color.rgb = tone_mapping(color.rgb); + color.rgb = YCbCr_to_RGB(color.rgb); + color.rgb = pq_eotf(color.rgb) / reference_white; + + return color; +} + +//!HOOK OUTPUT +//!BIND HOOKED +//!WHEN representation 2 = +//!DESC tone mapping (bt.2390, YRGB) + +const float m1 = 2610.0 / 4096.0 / 4.0; +const float m2 = 2523.0 / 4096.0 * 128.0; +const float c1 = 3424.0 / 4096.0; +const float c2 = 2413.0 / 4096.0 * 32.0; +const float c3 = 2392.0 / 4096.0 * 32.0; +const float pw = 10000.0; + +float pq_eotf_inv(float x) { + float t = pow(x / pw, m1); + return pow((c1 + c2 * t) / (1.0 + c3 * t), m2); +} + +vec3 pq_eotf_inv(vec3 color) { + return vec3( + pq_eotf_inv(color.r), + pq_eotf_inv(color.g), + pq_eotf_inv(color.b) + ); +} + +float pq_eotf(float x) { + float t = pow(x, 1.0 / m2); + return pow(max(t - c1, 0.0) / (c2 - c3 * t), 1.0 / m1) * pw; +} + +vec3 pq_eotf(vec3 color) { + return vec3( + pq_eotf(color.r), + pq_eotf(color.g), + pq_eotf(color.b) + ); +} + +const vec3 y_coef = vec3(0.2627002120112671, 0.6779980715188708, 0.05930171646986196); + +float get_max_i() { + if (max_pq_y > 0.0) + return max_pq_y; + + if (scene_max_r > 0.0 || scene_max_g > 0.0 || scene_max_b > 0.0) { + vec3 scene_max_rgb = vec3(scene_max_r, scene_max_g, scene_max_b); + return pq_eotf_inv(dot(scene_max_rgb, y_coef)); + } + + if (max_cll > 0.0) + return pq_eotf_inv(max_cll); + + if (max_luma > 0.0) + return pq_eotf_inv(max_luma); + + return pq_eotf_inv(1000.0); +} + +float get_min_i() { + if (min_luma > 0.0) + return pq_eotf_inv(min_luma); + + return pq_eotf_inv(0.001); +} + +float f(float x, float iw, float ib, float ow, float ob) { + float minLum = (ob - ib) / (iw - ib); + float maxLum = (ow - ib) / (iw - ib); + + float KS = 1.5 * maxLum - 0.5; + float b = minLum; + + // E1 + x = (x - ib) / (iw - ib); + + // E2 + if (KS <= x) { + float TB = (x - KS) / (1.0 - KS); + float TB2 = TB * TB; + float TB3 = TB * TB2; + + float PB = (2.0 * TB3 - 3.0 * TB2 + 1.0) * KS + + (TB3 - 2.0 * TB2 + TB) * (1.0 - KS) + + (-2.0 * TB3 + 3.0 * TB2) * maxLum; + + x = PB; + } + + // E3 + if (0.0 <= x) { + x = x + b * pow((1.0 - x), 4.0); + } + + // E4 + x = x * (iw - ib) + ib; + + return clamp(x, ob, ow); +} + +float curve(float x) { + float ow = pq_eotf_inv(reference_white); + float ob = pq_eotf_inv(reference_white / 1000.0); + float iw = max(get_max_i(), ow + 1e-3); + float ib = min(get_min_i(), ob - 1e-3); + return f(x, iw, ib, ow, ob); +} + +vec3 tone_mapping(vec3 rgb) { + float y1 = dot(rgb, y_coef) * reference_white; + float y2 = pq_eotf(curve(pq_eotf_inv(y1))); + return (y2 / max(y1, 1e-6)) * rgb; +} + +vec4 hook() { + vec4 color = HOOKED_tex(HOOKED_pos); + + color.rgb = tone_mapping(color.rgb); + + return color; +} + +//!HOOK OUTPUT +//!BIND HOOKED +//!WHEN representation 3 = +//!DESC tone mapping (bt.2390, R'G'B') + +const float m1 = 2610.0 / 4096.0 / 4.0; +const float m2 = 2523.0 / 4096.0 * 128.0; +const float c1 = 3424.0 / 4096.0; +const float c2 = 2413.0 / 4096.0 * 32.0; +const float c3 = 2392.0 / 4096.0 * 32.0; +const float pw = 10000.0; + +float pq_eotf_inv(float x) { + float t = pow(x / pw, m1); + return pow((c1 + c2 * t) / (1.0 + c3 * t), m2); +} + +vec3 pq_eotf_inv(vec3 color) { + return vec3( + pq_eotf_inv(color.r), + pq_eotf_inv(color.g), + pq_eotf_inv(color.b) + ); +} + +float pq_eotf(float x) { + float t = pow(x, 1.0 / m2); + return pow(max(t - c1, 0.0) / (c2 - c3 * t), 1.0 / m1) * pw; +} + +vec3 pq_eotf(vec3 color) { + return vec3( + pq_eotf(color.r), + pq_eotf(color.g), + pq_eotf(color.b) + ); +} + +const vec3 y_coef = vec3(0.2627002120112671, 0.6779980715188708, 0.05930171646986196); + +float get_max_i() { + if (max_pq_y > 0.0) + return max_pq_y; + + if (scene_max_r > 0.0 || scene_max_g > 0.0 || scene_max_b > 0.0) { + vec3 scene_max_rgb = vec3(scene_max_r, scene_max_g, scene_max_b); + return pq_eotf_inv(dot(scene_max_rgb, y_coef)); + } + + if (max_cll > 0.0) + return pq_eotf_inv(max_cll); + + if (max_luma > 0.0) + return pq_eotf_inv(max_luma); + + return pq_eotf_inv(1000.0); +} + +float get_min_i() { + if (min_luma > 0.0) + return pq_eotf_inv(min_luma); + + return pq_eotf_inv(0.001); +} + +float f(float x, float iw, float ib, float ow, float ob) { + float minLum = (ob - ib) / (iw - ib); + float maxLum = (ow - ib) / (iw - ib); + + float KS = 1.5 * maxLum - 0.5; + float b = minLum; + + // E1 + x = (x - ib) / (iw - ib); + + // E2 + if (KS <= x) { + float TB = (x - KS) / (1.0 - KS); + float TB2 = TB * TB; + float TB3 = TB * TB2; + + float PB = (2.0 * TB3 - 3.0 * TB2 + 1.0) * KS + + (TB3 - 2.0 * TB2 + TB) * (1.0 - KS) + + (-2.0 * TB3 + 3.0 * TB2) * maxLum; + + x = PB; + } + + // E3 + if (0.0 <= x) { + x = x + b * pow((1.0 - x), 4.0); + } + + // E4 + x = x * (iw - ib) + ib; + + return clamp(x, ob, ow); +} + +float curve(float x) { + float ow = pq_eotf_inv(reference_white); + float ob = pq_eotf_inv(reference_white / 1000.0); + float iw = max(get_max_i(), ow + 1e-3); + float ib = min(get_min_i(), ob - 1e-3); + return f(x, iw, ib, ow, ob); +} + +vec3 tone_mapping(vec3 rgb) { + return vec3( + curve(rgb.r), + curve(rgb.g), + curve(rgb.b) + ); +} + +vec4 hook() { + vec4 color = HOOKED_tex(HOOKED_pos); + + color.rgb = pq_eotf_inv(color.rgb * reference_white); + color.rgb = tone_mapping(color.rgb); + color.rgb = pq_eotf(color.rgb) / reference_white; + + return color; +} + +//!HOOK OUTPUT +//!BIND HOOKED +//!WHEN representation 4 = +//!DESC tone mapping (bt.2390, maxRGB) + +const float m1 = 2610.0 / 4096.0 / 4.0; +const float m2 = 2523.0 / 4096.0 * 128.0; +const float c1 = 3424.0 / 4096.0; +const float c2 = 2413.0 / 4096.0 * 32.0; +const float c3 = 2392.0 / 4096.0 * 32.0; +const float pw = 10000.0; + +float pq_eotf_inv(float x) { + float t = pow(x / pw, m1); + return pow((c1 + c2 * t) / (1.0 + c3 * t), m2); +} + +vec3 pq_eotf_inv(vec3 color) { + return vec3( + pq_eotf_inv(color.r), + pq_eotf_inv(color.g), + pq_eotf_inv(color.b) + ); +} + +float pq_eotf(float x) { + float t = pow(x, 1.0 / m2); + return pow(max(t - c1, 0.0) / (c2 - c3 * t), 1.0 / m1) * pw; +} + +vec3 pq_eotf(vec3 color) { + return vec3( + pq_eotf(color.r), + pq_eotf(color.g), + pq_eotf(color.b) + ); +} + +const vec3 y_coef = vec3(0.2627002120112671, 0.6779980715188708, 0.05930171646986196); + +float get_max_i() { + if (max_pq_y > 0.0) + return max_pq_y; + + if (scene_max_r > 0.0 || scene_max_g > 0.0 || scene_max_b > 0.0) { + vec3 scene_max_rgb = vec3(scene_max_r, scene_max_g, scene_max_b); + return pq_eotf_inv(dot(scene_max_rgb, y_coef)); + } + + if (max_cll > 0.0) + return pq_eotf_inv(max_cll); + + if (max_luma > 0.0) + return pq_eotf_inv(max_luma); + + return pq_eotf_inv(1000.0); +} + +float get_min_i() { + if (min_luma > 0.0) + return pq_eotf_inv(min_luma); + + return pq_eotf_inv(0.001); +} + +float f(float x, float iw, float ib, float ow, float ob) { + float minLum = (ob - ib) / (iw - ib); + float maxLum = (ow - ib) / (iw - ib); + + float KS = 1.5 * maxLum - 0.5; + float b = minLum; + + // E1 + x = (x - ib) / (iw - ib); + + // E2 + if (KS <= x) { + float TB = (x - KS) / (1.0 - KS); + float TB2 = TB * TB; + float TB3 = TB * TB2; + + float PB = (2.0 * TB3 - 3.0 * TB2 + 1.0) * KS + + (TB3 - 2.0 * TB2 + TB) * (1.0 - KS) + + (-2.0 * TB3 + 3.0 * TB2) * maxLum; + + x = PB; + } + + // E3 + if (0.0 <= x) { + x = x + b * pow((1.0 - x), 4.0); + } + + // E4 + x = x * (iw - ib) + ib; + + return clamp(x, ob, ow); +} + +float curve(float x) { + float ow = pq_eotf_inv(reference_white); + float ob = pq_eotf_inv(reference_white / 1000.0); + float iw = max(get_max_i(), ow + 1e-3); + float ib = min(get_min_i(), ob - 1e-3); + return f(x, iw, ib, ow, ob); +} + +vec3 tone_mapping(vec3 rgb) { + float m1 = max(max(rgb.r, rgb.g), rgb.b) * reference_white; + float m2 = pq_eotf(curve(pq_eotf_inv(m1))); + return (m2 / max(m1, 1e-6)) * rgb; +} + +vec4 hook() { + vec4 color = HOOKED_tex(HOOKED_pos); + + color.rgb = tone_mapping(color.rgb); + + return color; +} diff --git a/mpv/shaders/hdr-toys/tone-mapping/bt2446a.glsl b/mpv/shaders/hdr-toys/tone-mapping/bt2446a.glsl new file mode 100644 index 0000000..d9aa93e --- /dev/null +++ b/mpv/shaders/hdr-toys/tone-mapping/bt2446a.glsl @@ -0,0 +1,142 @@ +// ITU-R BT.2446 Conversion Method A +// https://www.itu.int/pub/R-REP-BT.2446 + +//!PARAM max_luma +//!TYPE float +0.0 + +//!PARAM max_cll +//!TYPE float +0.0 + +//!PARAM reference_white +//!TYPE float +//!MINIMUM 0.0 +//!MAXIMUM 1000.0 +203.0 + +//!HOOK OUTPUT +//!BIND HOOKED +//!DESC tone mapping (bt.2446a) + +const vec3 y_coef = vec3(0.2627002120112671, 0.6779980715188708, 0.05930171646986196); + +const float a = y_coef.r; +const float b = y_coef.g; +const float c = y_coef.b; +const float d = 2.0 * (1.0 - c); +const float e = 2.0 * (1.0 - a); + +vec3 RGB_to_YCbCr(vec3 RGB) { + return RGB * mat3( + a, b, c, + -a / d, -b / d, 0.5, + 0.5, -b / e, -c / e + ); +} + +vec3 YCbCr_to_RGB(vec3 YCbCr) { + return YCbCr * mat3( + 1.0, 0.0, e, + 1.0, -c / b * d, -a / b * e, + 1.0, d, 0.0 + ); +} + +float get_max_l() { + if (max_cll > 0.0) + return max_cll; + + if (max_luma > 0.0) + return max_luma; + + return 1000.0; +} + +float f(float Y) { + Y = pow(Y, 1.0 / 2.4); + + float pHDR = 1.0 + 32.0 * pow(get_max_l() / 10000.0, 1.0 / 2.4); + float pSDR = 1.0 + 32.0 * pow(reference_white / 10000.0, 1.0 / 2.4); + + float Yp = log(1.0 + (pHDR - 1.0) * Y) / log(pHDR); + + float Yc; + if (Yp <= 0.7399) Yc = Yp * 1.0770; + else if (Yp < 0.9909) Yc = Yp * (-1.1510 * Yp + 2.7811) - 0.6302; + else Yc = Yp * 0.5000 + 0.5000; + + float Ysdr = (pow(pSDR, Yc) - 1.0) / (pSDR - 1.0); + + Y = pow(Ysdr, 2.4); + + return Y; +} + +float curve(float Y) { + return f(Y); +} + +vec3 tone_mapping(vec3 YCbCr) { + YCbCr /= get_max_l() / reference_white; + + float Y = YCbCr.r; + float Cb = YCbCr.g; + float Cr = YCbCr.b; + + float Ysdr = curve(Y); + + float Yr = Ysdr / max(1.1 * Y, 1e-6); + Cb *= Yr; + Cr *= Yr; + Y = Ysdr - max(0.1 * Cr, 0.0); + + return vec3(Y, Cb, Cr); +} + +vec4 hook() { + vec4 color = HOOKED_tex(HOOKED_pos); + + color.rgb = RGB_to_YCbCr(color.rgb); + color.rgb = tone_mapping(color.rgb); + color.rgb = YCbCr_to_RGB(color.rgb); + + return color; +} + +//!HOOK OUTPUT +//!BIND HOOKED +//!DESC black point compensation + +// https://www.color.org/WP40-Black_Point_Compensation_2010-07-27.pdf + +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 black_point_compensation(vec3 XYZ, float s, float d) { + float r = (1.0 - d) / (1.0 - s); + return r * XYZ + (1.0 - r) * RGB_to_XYZ(vec3(1.0)); +} + +vec4 hook() { + vec4 color = HOOKED_tex(HOOKED_pos); + + color.rgb = RGB_to_XYZ(color.rgb); + color.rgb = black_point_compensation(color.rgb, 0.0, 0.001); + color.rgb = XYZ_to_RGB(color.rgb); + + return color; +} diff --git a/mpv/shaders/hdr-toys/tone-mapping/bt2446c.glsl b/mpv/shaders/hdr-toys/tone-mapping/bt2446c.glsl new file mode 100644 index 0000000..f2d0b2c --- /dev/null +++ b/mpv/shaders/hdr-toys/tone-mapping/bt2446c.glsl @@ -0,0 +1,315 @@ +// 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; +} diff --git a/mpv/shaders/hdr-toys/tone-mapping/false.glsl b/mpv/shaders/hdr-toys/tone-mapping/false.glsl new file mode 100644 index 0000000..d64cb2e --- /dev/null +++ b/mpv/shaders/hdr-toys/tone-mapping/false.glsl @@ -0,0 +1,325 @@ +// Visualizes the image using false color + +// You can preview the colors in Visual Studio Code by the following plugin +// https://marketplace.visualstudio.com/items?itemName=naumovs.color-highlight + +//!PARAM mode +//!TYPE ENUM int +luminance +exposure + +//!PARAM reference_white +//!TYPE float +//!MINIMUM 0.0 +//!MAXIMUM 1000.0 +203.0 + +//!HOOK OUTPUT +//!BIND HOOKED +//!WHEN mode 0 = +//!DESC tone mapping (false color, luminance) + +// oklch(0.99500 0.00 000.0) >10000 nits +// oklch(0.94659 0.11 005.0) 10000 nits +// oklch(0.83878 0.33 025.0) 4000 nits +// oklch(0.73097 0.33 090.0) 2000 nits +// oklch(0.52324 0.33 130.0) 1000 nits +// oklch(0.33922 0.24 245.0) brighter than SDR +// oklch(0.56925 0.00 000.0) SDR +// oklch(0.20104 0.16 350.0) darker than SDR +// oklch(0.13040 0.08 350.0) 0nits + +float cbrt(float x) { + return sign(x) * pow(abs(x), 1.0 / 3.0); +} + +vec3 cbrt(vec3 color) { + return vec3( + cbrt(color.x), + cbrt(color.y), + cbrt(color.z) + ); +} + +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_LMS(vec3 XYZ) { + return XYZ * mat3( + 0.8190224379967030, 0.3619062600528904, -0.1288737815209879, + 0.0329836539323885, 0.9292868615863434, 0.0361446663506424, + 0.0481771893596242, 0.2642395317527308, 0.6335478284694309 + ); +} + +vec3 LMS_to_XYZ(vec3 LMS) { + return LMS * mat3( + 1.2268798758459243, -0.5578149944602171, 0.2813910456659647, + -0.0405757452148008, 1.1122868032803170, -0.0717110580655164, + -0.0763729366746601, -0.4214933324022432, 1.5869240198367816 + ); +} + +vec3 LMS_to_Lab(vec3 LMS) { + return LMS * mat3( + 0.2104542683093140, 0.7936177747023054, -0.0040720430116193, + 1.9779985324311684, -2.4285922420485799, 0.4505937096174110, + 0.0259040424655478, 0.7827717124575296, -0.8086757549230774 + ); +} + +vec3 Lab_to_LMS(vec3 Lab) { + return Lab * mat3( + 1.0000000000000000, 0.3963377773761749, 0.2158037573099136, + 1.0000000000000000, -0.1055613458156586, -0.0638541728258133, + 1.0000000000000000, -0.0894841775298119, -1.2914855480194092 + ); +} + +vec3 RGB_to_Lab(vec3 color) { + color = RGB_to_XYZ(color); + color = XYZ_to_LMS(color); + color = cbrt(color); + color = LMS_to_Lab(color); + return color; +} + +vec3 Lab_to_RGB(vec3 color) { + color = Lab_to_LMS(color); + color = pow(color, vec3(3.0)); + color = LMS_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 l(float x, float a, float b) { + float y = (x - a) / (b - a); + return clamp(y , 0.0, 1.0); +} + +vec4 hook() { + vec4 color = HOOKED_tex(HOOKED_pos); + + float y = RGB_to_XYZ(color.rgb).y * reference_white; + + float l5 = 10000.0; + float l4 = 4000.0; + float l3 = 2000.0; + float l2 = 1000.0; + float l1 = reference_white; + float l0 = reference_white / 1000.0; + float lb = 0.0; + + vec3 cw = vec3(0.99500, 0.00, radians(000.0)); + vec3 c5 = vec3(0.94659, 0.11, radians(005.0)); + vec3 c4 = vec3(0.83878, 0.33, radians(025.0)); + vec3 c3 = vec3(0.73097, 0.33, radians(090.0)); + vec3 c2 = vec3(0.52324, 0.33, radians(130.0)); + vec3 c1 = vec3(0.33922, 0.24, radians(245.0)); + vec3 c0 = vec3(0.20104, 0.16, radians(350.0)); + vec3 cb = vec3(0.13040, 0.08, radians(350.0)); + + if (y > l5) color.rgb = Lab_to_RGB(LCh_to_Lab(cw)); + else if (y > l4) color.rgb = Lab_to_RGB(LCh_to_Lab(mix(c4, c5, l(y, l4, l5)))); + else if (y > l3) color.rgb = Lab_to_RGB(LCh_to_Lab(mix(c3, c4, l(y, l3, l4)))); + else if (y > l2) color.rgb = Lab_to_RGB(LCh_to_Lab(mix(c2, c3, l(y, l2, l3)))); + else if (y > l1) color.rgb = Lab_to_RGB(LCh_to_Lab(mix(c1, c2, l(y, l1, l2)))); + else if (y > l0) color.rgb = vec3(l(y, l0, l1)); + else color.rgb = Lab_to_RGB(LCh_to_Lab(mix(cb, c0, l(y, lb, l0)))); + + return color; +} + +//!HOOK OUTPUT +//!BIND HOOKED +//!WHEN mode 1 = +//!DESC tone mapping (false color, exposure) + +// Inspired by the Ansel Adams' Zone System +// https://en.wikipedia.org/wiki/Zone_System#Zones_as_tone_and_texture +// Expanded exposure stops based on the de facto devices' dynamic range + +// oklch(0.99500 0.00 000.0) overexposure +// oklch(0.94659 0.11 005.0) +7 stops +// oklch(0.89269 0.22 015.0) +6 stops +// oklch(0.83878 0.33 025.0) +5 stops +// oklch(0.78487 0.11 060.0) +4 stops +// oklch(0.73097 0.33 090.0) +3 stops +// oklch(0.67706 0.22 105.0) +2 stops +// oklch(0.62315 0.11 120.0) +1 stop +// oklch(0.56925 0.00 000.0) middle gray +// oklch(0.52324 0.33 130.0) -1 stop +// oklch(0.47724 0.22 145.0) -2 stops +// oklch(0.43123 0.11 160.0) -3 stops +// oklch(0.38523 0.32 220.0) -4 stops +// oklch(0.33922 0.24 245.0) -5 stops +// oklch(0.29322 0.24 290.0) -6 stops +// oklch(0.24721 0.16 320.0) -7 stops +// oklch(0.20104 0.08 350.0) -8 stops +// oklch(0.13040 0.00 000.0) underexposure + +float cbrt(float x) { + return sign(x) * pow(abs(x), 1.0 / 3.0); +} + +vec3 cbrt(vec3 color) { + return vec3( + cbrt(color.x), + cbrt(color.y), + cbrt(color.z) + ); +} + +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_LMS(vec3 XYZ) { + return XYZ * mat3( + 0.8190224379967030, 0.3619062600528904, -0.1288737815209879, + 0.0329836539323885, 0.9292868615863434, 0.0361446663506424, + 0.0481771893596242, 0.2642395317527308, 0.6335478284694309 + ); +} + +vec3 LMS_to_XYZ(vec3 LMS) { + return LMS * mat3( + 1.2268798758459243, -0.5578149944602171, 0.2813910456659647, + -0.0405757452148008, 1.1122868032803170, -0.0717110580655164, + -0.0763729366746601, -0.4214933324022432, 1.5869240198367816 + ); +} + +vec3 LMS_to_Lab(vec3 LMS) { + return LMS * mat3( + 0.2104542683093140, 0.7936177747023054, -0.0040720430116193, + 1.9779985324311684, -2.4285922420485799, 0.4505937096174110, + 0.0259040424655478, 0.7827717124575296, -0.8086757549230774 + ); +} + +vec3 Lab_to_LMS(vec3 Lab) { + return Lab * mat3( + 1.0000000000000000, 0.3963377773761749, 0.2158037573099136, + 1.0000000000000000, -0.1055613458156586, -0.0638541728258133, + 1.0000000000000000, -0.0894841775298119, -1.2914855480194092 + ); +} + +vec3 RGB_to_Lab(vec3 color) { + color = RGB_to_XYZ(color); + color = XYZ_to_LMS(color); + color = cbrt(color); + color = LMS_to_Lab(color); + return color; +} + +vec3 Lab_to_RGB(vec3 color) { + color = Lab_to_LMS(color); + color = pow(color, vec3(3.0)); + color = LMS_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); +} + +vec4 hook() { + vec4 color = HOOKED_tex(HOOKED_pos); + + float stops = log2(max(RGB_to_XYZ(color.rgb).y, 1e-6) / 0.18); + + if (stops >= 7.5) color.rgb = Lab_to_RGB(LCh_to_Lab(vec3(0.99500, 0.00, radians(000.0)))); + else if (stops >= 6.5) color.rgb = Lab_to_RGB(LCh_to_Lab(vec3(0.94659, 0.11, radians(005.0)))); + else if (stops >= 5.5) color.rgb = Lab_to_RGB(LCh_to_Lab(vec3(0.89269, 0.22, radians(015.0)))); + else if (stops >= 4.5) color.rgb = Lab_to_RGB(LCh_to_Lab(vec3(0.83878, 0.33, radians(025.0)))); + else if (stops >= 3.5) color.rgb = Lab_to_RGB(LCh_to_Lab(vec3(0.78487, 0.11, radians(060.0)))); + else if (stops >= 2.5) color.rgb = Lab_to_RGB(LCh_to_Lab(vec3(0.73097, 0.33, radians(090.0)))); + else if (stops >= 1.5) color.rgb = Lab_to_RGB(LCh_to_Lab(vec3(0.67706, 0.22, radians(105.0)))); + else if (stops >= 0.5) color.rgb = Lab_to_RGB(LCh_to_Lab(vec3(0.62315, 0.11, radians(120.0)))); + else if (stops >= -0.5) color.rgb = Lab_to_RGB(LCh_to_Lab(vec3(0.56925, 0.00, radians(000.0)))); + else if (stops >= -1.5) color.rgb = Lab_to_RGB(LCh_to_Lab(vec3(0.52324, 0.33, radians(130.0)))); + else if (stops >= -2.5) color.rgb = Lab_to_RGB(LCh_to_Lab(vec3(0.47724, 0.22, radians(145.0)))); + else if (stops >= -3.5) color.rgb = Lab_to_RGB(LCh_to_Lab(vec3(0.43123, 0.11, radians(160.0)))); + else if (stops >= -4.5) color.rgb = Lab_to_RGB(LCh_to_Lab(vec3(0.38523, 0.32, radians(220.0)))); + else if (stops >= -5.5) color.rgb = Lab_to_RGB(LCh_to_Lab(vec3(0.33922, 0.24, radians(245.0)))); + else if (stops >= -6.5) color.rgb = Lab_to_RGB(LCh_to_Lab(vec3(0.29322, 0.24, radians(290.0)))); + else if (stops >= -7.5) color.rgb = Lab_to_RGB(LCh_to_Lab(vec3(0.24721, 0.16, radians(320.0)))); + else if (stops >= -8.5) color.rgb = Lab_to_RGB(LCh_to_Lab(vec3(0.20104, 0.08, radians(350.0)))); + else color.rgb = Lab_to_RGB(LCh_to_Lab(vec3(0.13040, 0.00, radians(000.0)))); + + return color; +} diff --git a/mpv/shaders/hdr-toys/tone-mapping/linear.glsl b/mpv/shaders/hdr-toys/tone-mapping/linear.glsl new file mode 100644 index 0000000..c0030f7 --- /dev/null +++ b/mpv/shaders/hdr-toys/tone-mapping/linear.glsl @@ -0,0 +1,203 @@ +// Linear applies a simple scaling to the I component + +//!PARAM min_luma +//!TYPE float +0.0 + +//!PARAM max_luma +//!TYPE float +0.0 + +//!PARAM max_cll +//!TYPE float +0.0 + +//!PARAM scene_max_r +//!TYPE float +0.0 + +//!PARAM scene_max_g +//!TYPE float +0.0 + +//!PARAM scene_max_b +//!TYPE float +0.0 + +//!PARAM max_pq_y +//!TYPE float +0.0 + +//!PARAM reference_white +//!TYPE float +//!MINIMUM 0.0 +//!MAXIMUM 1000.0 +203.0 + +//!PARAM chroma_correction_scaling +//!TYPE float +//!MINIMUM 0.0 +//!MAXIMUM 1.0 +1.0 + +//!HOOK OUTPUT +//!BIND HOOKED +//!DESC tone mapping (linear) + +const float m1 = 2610.0 / 4096.0 / 4.0; +const float m2 = 2523.0 / 4096.0 * 128.0; +const float c1 = 3424.0 / 4096.0; +const float c2 = 2413.0 / 4096.0 * 32.0; +const float c3 = 2392.0 / 4096.0 * 32.0; +const float pw = 10000.0; + +float pq_eotf_inv(float x) { + float t = pow(x / pw, m1); + return pow((c1 + c2 * t) / (1.0 + c3 * t), m2); +} + +vec3 pq_eotf_inv(vec3 color) { + return vec3( + pq_eotf_inv(color.r), + pq_eotf_inv(color.g), + pq_eotf_inv(color.b) + ); +} + +float pq_eotf(float x) { + float t = pow(x, 1.0 / m2); + return pow(max(t - c1, 0.0) / (c2 - c3 * t), 1.0 / m1) * pw; +} + +vec3 pq_eotf(vec3 color) { + return vec3( + pq_eotf(color.r), + pq_eotf(color.g), + pq_eotf(color.b) + ); +} + +vec3 RGB_to_XYZ(vec3 RGB) { + return RGB * mat3( + 0.6369580483012914, 0.14461690358620832, 0.1688809751641721, + 0.2627002120112671, 0.6779980715188708, 0.05930171646986196, + 0.0 , 0.028072693049087428, 1.060985057710791 + ); +} + +vec3 XYZ_to_RGB(vec3 XYZ) { + return XYZ * mat3( + 1.716651187971268, -0.355670783776392, -0.25336628137366, + -0.666684351832489, 1.616481236634939, 0.0157685458139111, + 0.017639857445311, -0.042770613257809, 0.942103121235474 + ); +} + +vec3 XYZ_to_LMS(vec3 XYZ) { + return XYZ * mat3( + 0.3592832590121217, 0.6976051147779502, -0.0358915932320290, + -0.1920808463704993, 1.1004767970374321, 0.0753748658519118, + 0.0070797844607479, 0.0748396662186362, 0.8433265453898765 + ); +} + +vec3 LMS_to_XYZ(vec3 LMS) { + return LMS * mat3( + 2.0701522183894223, -1.3263473389671563, 0.2066510476294053, + 0.3647385209748072, 0.6805660249472273, -0.0453045459220347, + -0.0497472075358123, -0.0492609666966131, 1.1880659249923042 + ); +} + +vec3 LMS_to_ICtCp(vec3 LMS) { + return LMS * mat3( + 2048.0 / 4096.0, 2048.0 / 4096.0, 0.0 / 4096.0, + 6610.0 / 4096.0, -13613.0 / 4096.0, 7003.0 / 4096.0, + 17933.0 / 4096.0, -17390.0 / 4096.0, -543.0 / 4096.0 + ); +} + +vec3 ICtCp_to_LMS(vec3 ICtCp) { + return ICtCp * mat3( + 1.0, 0.0086090370379328, 0.1110296250030260, + 1.0, -0.0086090370379328, -0.1110296250030260, + 1.0, 0.5600313357106791, -0.3206271749873189 + ); +} + +vec3 RGB_to_ICtCp(vec3 color) { + color *= reference_white; + color = RGB_to_XYZ(color); + color = XYZ_to_LMS(color); + color = pq_eotf_inv(color); + color = LMS_to_ICtCp(color); + return color; +} + +vec3 ICtCp_to_RGB(vec3 color) { + color = ICtCp_to_LMS(color); + color = pq_eotf(color); + color = LMS_to_XYZ(color); + color = XYZ_to_RGB(color); + color /= reference_white; + return color; +} + +float get_max_i() { + if (max_pq_y > 0.0) + return max_pq_y; + + if (scene_max_r > 0.0 || scene_max_g > 0.0 || scene_max_b > 0.0) { + vec3 scene_max_rgb = vec3(scene_max_r, scene_max_g, scene_max_b); + return pq_eotf_inv(RGB_to_XYZ(scene_max_rgb).y); + } + + if (max_cll > 0.0) + return pq_eotf_inv(max_cll); + + if (max_luma > 0.0) + return pq_eotf_inv(max_luma); + + return pq_eotf_inv(1000.0); +} + +float get_min_i() { + if (min_luma > 0.0) + return pq_eotf_inv(min_luma); + + return pq_eotf_inv(0.001); +} + +float f(float x, float a, float b, float c, float d) { + return (x - a) * (d - c) / (b - a) + c; +} + +float curve(float x) { + float ow = pq_eotf_inv(reference_white); + float ob = pq_eotf_inv(reference_white / 1000.0); + float iw = max(get_max_i(), ow + 1e-3); + float ib = min(get_min_i(), ob - 1e-3); + return f(x, ib, iw, ob, ow); +} + +vec2 chroma_correction(vec2 ab, float i1, float i2) { + float r1 = i1 / max(i2, 1e-6); + float r2 = i2 / max(i1, 1e-6); + return ab * mix(1.0, min(r1, r2), chroma_correction_scaling); +} + +vec3 tone_mapping(vec3 iab) { + float i2 = curve(iab.x); + vec2 ab2 = chroma_correction(iab.yz, iab.x, i2); + return vec3(i2, ab2); +} + +vec4 hook() { + vec4 color = HOOKED_tex(HOOKED_pos); + + color.rgb = RGB_to_ICtCp(color.rgb); + color.rgb = tone_mapping(color.rgb); + color.rgb = ICtCp_to_RGB(color.rgb); + + return color; +} diff --git a/mpv/shaders/hdr-toys/tone-mapping/reinhard.glsl b/mpv/shaders/hdr-toys/tone-mapping/reinhard.glsl new file mode 100644 index 0000000..2a37a8a --- /dev/null +++ b/mpv/shaders/hdr-toys/tone-mapping/reinhard.glsl @@ -0,0 +1,229 @@ +// Photographic tone reproduction for digital images +// https://doi.org/10.1145/566654.566575 + +//!PARAM max_luma +//!TYPE float +0.0 + +//!PARAM max_cll +//!TYPE float +0.0 + +//!PARAM scene_max_r +//!TYPE float +0.0 + +//!PARAM scene_max_g +//!TYPE float +0.0 + +//!PARAM scene_max_b +//!TYPE float +0.0 + +//!PARAM max_pq_y +//!TYPE float +0.0 + +//!PARAM reference_white +//!TYPE float +//!MINIMUM 0.0 +//!MAXIMUM 1000.0 +203.0 + +//!PARAM chroma_correction_scaling +//!TYPE float +//!MINIMUM 0.0 +//!MAXIMUM 1.0 +1.0 + +//!HOOK OUTPUT +//!BIND HOOKED +//!DESC tone mapping (reinhard) + +const float m1 = 2610.0 / 4096.0 / 4.0; +const float m2 = 2523.0 / 4096.0 * 128.0; +const float c1 = 3424.0 / 4096.0; +const float c2 = 2413.0 / 4096.0 * 32.0; +const float c3 = 2392.0 / 4096.0 * 32.0; +const float pw = 10000.0; + +float pq_eotf_inv(float x) { + float t = pow(x / pw, m1); + return pow((c1 + c2 * t) / (1.0 + c3 * t), m2); +} + +vec3 pq_eotf_inv(vec3 color) { + return vec3( + pq_eotf_inv(color.r), + pq_eotf_inv(color.g), + pq_eotf_inv(color.b) + ); +} + +float pq_eotf(float x) { + float t = pow(x, 1.0 / m2); + return pow(max(t - c1, 0.0) / (c2 - c3 * t), 1.0 / m1) * pw; +} + +vec3 pq_eotf(vec3 color) { + return vec3( + pq_eotf(color.r), + pq_eotf(color.g), + pq_eotf(color.b) + ); +} + +vec3 RGB_to_XYZ(vec3 RGB) { + return RGB * mat3( + 0.6369580483012914, 0.14461690358620832, 0.1688809751641721, + 0.2627002120112671, 0.6779980715188708, 0.05930171646986196, + 0.0 , 0.028072693049087428, 1.060985057710791 + ); +} + +vec3 XYZ_to_RGB(vec3 XYZ) { + return XYZ * mat3( + 1.716651187971268, -0.355670783776392, -0.25336628137366, + -0.666684351832489, 1.616481236634939, 0.0157685458139111, + 0.017639857445311, -0.042770613257809, 0.942103121235474 + ); +} + +vec3 XYZ_to_LMS(vec3 XYZ) { + return XYZ * mat3( + 0.3592832590121217, 0.6976051147779502, -0.0358915932320290, + -0.1920808463704993, 1.1004767970374321, 0.0753748658519118, + 0.0070797844607479, 0.0748396662186362, 0.8433265453898765 + ); +} + +vec3 LMS_to_XYZ(vec3 LMS) { + return LMS * mat3( + 2.0701522183894223, -1.3263473389671563, 0.2066510476294053, + 0.3647385209748072, 0.6805660249472273, -0.0453045459220347, + -0.0497472075358123, -0.0492609666966131, 1.1880659249923042 + ); +} + +vec3 LMS_to_ICtCp(vec3 LMS) { + return LMS * mat3( + 2048.0 / 4096.0, 2048.0 / 4096.0, 0.0 / 4096.0, + 6610.0 / 4096.0, -13613.0 / 4096.0, 7003.0 / 4096.0, + 17933.0 / 4096.0, -17390.0 / 4096.0, -543.0 / 4096.0 + ); +} + +vec3 ICtCp_to_LMS(vec3 ICtCp) { + return ICtCp * mat3( + 1.0, 0.0086090370379328, 0.1110296250030260, + 1.0, -0.0086090370379328, -0.1110296250030260, + 1.0, 0.5600313357106791, -0.3206271749873189 + ); +} + +vec3 RGB_to_ICtCp(vec3 color) { + color *= reference_white; + color = RGB_to_XYZ(color); + color = XYZ_to_LMS(color); + color = pq_eotf_inv(color); + color = LMS_to_ICtCp(color); + return color; +} + +vec3 ICtCp_to_RGB(vec3 color) { + color = ICtCp_to_LMS(color); + color = pq_eotf(color); + color = LMS_to_XYZ(color); + color = XYZ_to_RGB(color); + color /= reference_white; + return color; +} + +float get_max_l() { + if (max_pq_y > 0.0) + return pq_eotf(max_pq_y); + + if (scene_max_r > 0.0 || scene_max_g > 0.0 || scene_max_b > 0.0) { + vec3 scene_max_rgb = vec3(scene_max_r, scene_max_g, scene_max_b); + return RGB_to_XYZ(scene_max_rgb).y; + } + + if (max_cll > 0.0) + return max_cll; + + if (max_luma > 0.0) + return max_luma; + + return 1000.0; +} + +float f(float x, float w) { + float simple = x / (1.0 + x); + float extended = simple * (1.0 + x / (w * w)); + return extended; +} + +float curve(float x) { + float w = get_max_l(); + return f(x, w); +} + +vec2 chroma_correction(vec2 ab, float i1, float i2) { + float r1 = i1 / max(i2, 1e-6); + float r2 = i2 / max(i1, 1e-6); + return ab * mix(1.0, min(r1, r2), chroma_correction_scaling); +} + +vec3 tone_mapping(vec3 iab) { + float i2 = pq_eotf_inv(curve(pq_eotf(iab.x) / reference_white) * reference_white); + vec2 ab2 = chroma_correction(iab.yz, iab.x, i2); + return vec3(i2, ab2); +} + +vec4 hook() { + vec4 color = HOOKED_tex(HOOKED_pos); + + color.rgb = RGB_to_ICtCp(color.rgb); + color.rgb = tone_mapping(color.rgb); + color.rgb = ICtCp_to_RGB(color.rgb); + + return color; +} + +//!HOOK OUTPUT +//!BIND HOOKED +//!DESC black point compensation + +// https://www.color.org/WP40-Black_Point_Compensation_2010-07-27.pdf + +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 black_point_compensation(vec3 XYZ, float s, float d) { + float r = (1.0 - d) / (1.0 - s); + return r * XYZ + (1.0 - r) * RGB_to_XYZ(vec3(1.0)); +} + +vec4 hook() { + vec4 color = HOOKED_tex(HOOKED_pos); + + color.rgb = RGB_to_XYZ(color.rgb); + color.rgb = black_point_compensation(color.rgb, 0.0, 0.001); + color.rgb = XYZ_to_RGB(color.rgb); + + return color; +} diff --git a/mpv/shaders/hdr-toys/tone-mapping/st2094-10.glsl b/mpv/shaders/hdr-toys/tone-mapping/st2094-10.glsl new file mode 100644 index 0000000..326c711 --- /dev/null +++ b/mpv/shaders/hdr-toys/tone-mapping/st2094-10.glsl @@ -0,0 +1,431 @@ +// ST 2094-10:2021 - SMPTE Standard - Dynamic Metadata for Color Volume Transform - Application #1 +// https://ieeexplore.ieee.org/document/9405553 + +//!PARAM min_luma +//!TYPE float +0.0 + +//!PARAM max_luma +//!TYPE float +0.0 + +//!PARAM max_cll +//!TYPE float +0.0 + +//!PARAM max_fall +//!TYPE float +0.0 + +//!PARAM scene_max_r +//!TYPE float +0.0 + +//!PARAM scene_max_g +//!TYPE float +0.0 + +//!PARAM scene_max_b +//!TYPE float +0.0 + +//!PARAM scene_avg +//!TYPE float +0.0 + +//!PARAM max_pq_y +//!TYPE float +0.0 + +//!PARAM avg_pq_y +//!TYPE float +0.0 + +//!PARAM reference_white +//!TYPE float +//!MINIMUM 0.0 +//!MAXIMUM 1000.0 +203.0 + +//!PARAM chroma_correction_scaling +//!TYPE float +//!MINIMUM 0.0 +//!MAXIMUM 1.0 +0.5 + +//!BUFFER METERED +//!VAR float metered_avg_l +//!STORAGE + +//!HOOK OUTPUT +//!BIND HOOKED +//!SAVE AVG +//!COMPONENTS 1 +//!WIDTH 1024 +//!HEIGHT 1024 +//!WHEN avg_pq_y 0 = scene_avg 0 = * +//!DESC tone mapping (st2094-10, average, 1024) + +const vec3 y_coef = vec3(0.2627002120112671, 0.6779980715188708, 0.05930171646986196); + +const float m1 = 2610.0 / 4096.0 / 4.0; +const float m2 = 2523.0 / 4096.0 * 128.0; +const float c1 = 3424.0 / 4096.0; +const float c2 = 2413.0 / 4096.0 * 32.0; +const float c3 = 2392.0 / 4096.0 * 32.0; +const float pw = 10000.0; + +float pq_eotf_inv(float x) { + float t = pow(x / pw, m1); + return pow((c1 + c2 * t) / (1.0 + c3 * t), m2); +} + +vec4 hook() { + vec4 color = HOOKED_tex(HOOKED_pos); + float l = dot(color.rgb * reference_white, y_coef); + float i = pq_eotf_inv(l); + return vec4(i, vec3(0.0)); +} + +//!HOOK OUTPUT +//!BIND AVG +//!SAVE AVG +//!WIDTH AVG.w 2 / +//!HEIGHT AVG.h 2 / +//!WHEN avg_pq_y 0 = scene_avg 0 = * +//!DESC tone mapping (st2094-10, average, 512) +vec4 hook() { return AVG_tex(AVG_pos); } + +//!HOOK OUTPUT +//!BIND AVG +//!SAVE AVG +//!WIDTH AVG.w 2 / +//!HEIGHT AVG.h 2 / +//!WHEN avg_pq_y 0 = scene_avg 0 = * +//!DESC tone mapping (st2094-10, average, 256) +vec4 hook() { return AVG_tex(AVG_pos); } + +//!HOOK OUTPUT +//!BIND AVG +//!SAVE AVG +//!WIDTH AVG.w 2 / +//!HEIGHT AVG.h 2 / +//!WHEN avg_pq_y 0 = scene_avg 0 = * +//!DESC tone mapping (st2094-10, average, 128) +vec4 hook() { return AVG_tex(AVG_pos); } + +//!HOOK OUTPUT +//!BIND AVG +//!SAVE AVG +//!WIDTH AVG.w 2 / +//!HEIGHT AVG.h 2 / +//!WHEN avg_pq_y 0 = scene_avg 0 = * +//!DESC tone mapping (st2094-10, average, 64) +vec4 hook() { return AVG_tex(AVG_pos); } + +//!HOOK OUTPUT +//!BIND AVG +//!SAVE AVG +//!WIDTH AVG.w 2 / +//!HEIGHT AVG.h 2 / +//!WHEN avg_pq_y 0 = scene_avg 0 = * +//!DESC tone mapping (st2094-10, average, 32) +vec4 hook() { return AVG_tex(AVG_pos); } + +//!HOOK OUTPUT +//!BIND AVG +//!SAVE AVG +//!WIDTH AVG.w 2 / +//!HEIGHT AVG.h 2 / +//!WHEN avg_pq_y 0 = scene_avg 0 = * +//!DESC tone mapping (st2094-10, average, 16) +vec4 hook() { return AVG_tex(AVG_pos); } + +//!HOOK OUTPUT +//!BIND AVG +//!SAVE AVG +//!WIDTH AVG.w 2 / +//!HEIGHT AVG.h 2 / +//!WHEN avg_pq_y 0 = scene_avg 0 = * +//!DESC tone mapping (st2094-10, average, 8) +vec4 hook() { return AVG_tex(AVG_pos); } + +//!HOOK OUTPUT +//!BIND AVG +//!SAVE AVG +//!WIDTH AVG.w 2 / +//!HEIGHT AVG.h 2 / +//!WHEN avg_pq_y 0 = scene_avg 0 = * +//!DESC tone mapping (st2094-10, average, 4) +vec4 hook() { return AVG_tex(AVG_pos); } + +//!HOOK OUTPUT +//!BIND AVG +//!SAVE AVG +//!WIDTH AVG.w 2 / +//!HEIGHT AVG.h 2 / +//!WHEN avg_pq_y 0 = scene_avg 0 = * +//!DESC tone mapping (st2094-10, average, 2) +vec4 hook() { return AVG_tex(AVG_pos); } + +//!HOOK OUTPUT +//!BIND AVG +//!BIND METERED +//!SAVE AVG +//!WIDTH 1 +//!HEIGHT 1 +//!COMPUTE 1 1 +//!WHEN avg_pq_y 0 = scene_avg 0 = * +//!DESC tone mapping (st2094-10, average, 1) + +void hook() { + metered_avg_l = AVG_tex(AVG_pos).x; +} + +//!HOOK OUTPUT +//!BIND HOOKED +//!BIND METERED +//!DESC tone mapping (st2094-10) + +const float m1 = 2610.0 / 4096.0 / 4.0; +const float m2 = 2523.0 / 4096.0 * 128.0; +const float c1 = 3424.0 / 4096.0; +const float c2 = 2413.0 / 4096.0 * 32.0; +const float c3 = 2392.0 / 4096.0 * 32.0; +const float pw = 10000.0; + +float pq_eotf_inv(float x) { + float t = pow(x / pw, m1); + return pow((c1 + c2 * t) / (1.0 + c3 * t), m2); +} + +vec3 pq_eotf_inv(vec3 color) { + return vec3( + pq_eotf_inv(color.r), + pq_eotf_inv(color.g), + pq_eotf_inv(color.b) + ); +} + +float pq_eotf(float x) { + float t = pow(x, 1.0 / m2); + return pow(max(t - c1, 0.0) / (c2 - c3 * t), 1.0 / m1) * pw; +} + +vec3 pq_eotf(vec3 color) { + return vec3( + pq_eotf(color.r), + pq_eotf(color.g), + pq_eotf(color.b) + ); +} + +vec3 RGB_to_XYZ(vec3 RGB) { + return RGB * mat3( + 0.6369580483012914, 0.14461690358620832, 0.1688809751641721, + 0.2627002120112671, 0.6779980715188708, 0.05930171646986196, + 0.0 , 0.028072693049087428, 1.060985057710791 + ); +} + +vec3 XYZ_to_RGB(vec3 XYZ) { + return XYZ * mat3( + 1.716651187971268, -0.355670783776392, -0.25336628137366, + -0.666684351832489, 1.616481236634939, 0.0157685458139111, + 0.017639857445311, -0.042770613257809, 0.942103121235474 + ); +} + +vec3 XYZ_to_LMS(vec3 XYZ) { + return XYZ * mat3( + 0.3592832590121217, 0.6976051147779502, -0.0358915932320290, + -0.1920808463704993, 1.1004767970374321, 0.0753748658519118, + 0.0070797844607479, 0.0748396662186362, 0.8433265453898765 + ); +} + +vec3 LMS_to_XYZ(vec3 LMS) { + return LMS * mat3( + 2.0701522183894223, -1.3263473389671563, 0.2066510476294053, + 0.3647385209748072, 0.6805660249472273, -0.0453045459220347, + -0.0497472075358123, -0.0492609666966131, 1.1880659249923042 + ); +} + +vec3 LMS_to_ICtCp(vec3 LMS) { + return LMS * mat3( + 2048.0 / 4096.0, 2048.0 / 4096.0, 0.0 / 4096.0, + 6610.0 / 4096.0, -13613.0 / 4096.0, 7003.0 / 4096.0, + 17933.0 / 4096.0, -17390.0 / 4096.0, -543.0 / 4096.0 + ); +} + +vec3 ICtCp_to_LMS(vec3 ICtCp) { + return ICtCp * mat3( + 1.0, 0.0086090370379328, 0.1110296250030260, + 1.0, -0.0086090370379328, -0.1110296250030260, + 1.0, 0.5600313357106791, -0.3206271749873189 + ); +} + +vec3 RGB_to_ICtCp(vec3 color) { + color *= reference_white; + color = RGB_to_XYZ(color); + color = XYZ_to_LMS(color); + color = pq_eotf_inv(color); + color = LMS_to_ICtCp(color); + return color; +} + +vec3 ICtCp_to_RGB(vec3 color) { + color = ICtCp_to_LMS(color); + color = pq_eotf(color); + color = LMS_to_XYZ(color); + color = XYZ_to_RGB(color); + color /= reference_white; + return color; +} + +float get_max_l() { + if (max_pq_y > 0.0) + return pq_eotf(max_pq_y); + + if (scene_max_r > 0.0 || scene_max_g > 0.0 || scene_max_b > 0.0) { + vec3 scene_max_rgb = vec3(scene_max_r, scene_max_g, scene_max_b); + return RGB_to_XYZ(scene_max_rgb).y; + } + + if (max_cll > 0.0) + return max_cll; + + if (max_luma > 0.0) + return max_luma; + + return 1000.0; +} + +float get_min_l() { + if (min_luma > 0.0) + return min_luma; + + return 0.001; +} + +float get_avg_l() { + if (avg_pq_y > 0.0) + return pq_eotf(avg_pq_y); + + if (scene_avg > 0.0) + return scene_avg; + + if (metered_avg_l > 0.0) + return pq_eotf(clamp(metered_avg_l, 0.1, 0.5)); + + if (max_fall > 0.0) + return max_fall; + + return pq_eotf(0.3); +} + +// n: contrast [0.5, 1.5] +// o: offset [-0.5, 0.5] +// g: gain [0.5, 1.5] +// p: gamma [0.5, 1.5] +float f( + float x, + float iw, float ib, float ow, float ob, float adapt, + float n, float o, float g, float p +) { + float x1 = ib; + float y1 = ob; + + float x3 = iw; + float y3 = ow; + + float x2 = adapt; + float y2 = sqrt(x2 * sqrt(y3 * y1)); + + x2 = clamp(x2, x1 + 0.001, 0.9 * x3); + y2 = clamp(y2, y1 + 0.001, 0.9 * y3); + + float a = x3 * y3 * (x1 - x2) + x2 * y2 * (x3 - x1) + x1 * y1 * (x2 - x3); + + mat3 cmat = mat3( + x2 * x3 * (y2 - y3), x1 * x3 * (y3 - y1), x1 * x2 * (y1 - y2), + x3 * y3 - x2 * y2 , x1 * y1 - x3 * y3 , x2 * y2 - x1 * y1 , + x3 - x2 , x1 - x3 , x2 - x1 + ); + + vec3 coeffs = vec3(y1, y2, y3) * cmat / a; + + float c1 = coeffs.r; + float c2 = coeffs.g; + float c3 = coeffs.b; + + x = clamp(x, x1, x3); + x = pow(x, n); + x = (c1 + c2 * x) / (1.0 + c3 * x); + x = pow(min(max(0.0, ((x / y3) * g) + o), 1.0), p) * y3; + x = clamp(x, y1, y3); + + return x; +} + +float f(float x, float iw, float ib, float ow, float ob, float adapt) { + return f(x, iw, ib, ow, ob, adapt, 1.0, 0.0, 1.0, 1.0); +} + +float curve(float x) { + float ow = 1.0; + float ob = 0.001; + float iw = max(get_max_l() / reference_white, ow + 1e-3); + float ib = min(get_min_l() / reference_white, ob - 1e-3); + float avg = get_avg_l() / reference_white; + return f(x, iw, ib, ow, ob, avg); +} + +vec2 chroma_correction(vec2 ab, float i1, float i2) { + float r1 = i1 / max(i2, 1e-6); + float r2 = i2 / max(i1, 1e-6); + return ab * mix(1.0, min(r1, r2), chroma_correction_scaling); +} + +vec3 tone_mapping(vec3 iab) { + float i2 = pq_eotf_inv(curve(pq_eotf(iab.x) / reference_white) * reference_white); + vec2 ab2 = chroma_correction(iab.yz, iab.x, i2); + return vec3(i2, ab2); +} + +// c: chroma compensation weight [-0.5, 0.5] +// s: saturation gain [-0.5, 0.5] +vec3 gamut_adjustment(vec3 f, float c, float s) { + float y = RGB_to_XYZ(f).y; + return f * pow((1.0 + c) * f / y, vec3(s)); +} + +vec3 gamut_adjustment(vec3 f) { + return gamut_adjustment(f, 0.0, 0.0); +} + +// t: tone detail factor [0, 1]; +vec3 detail_managenment(vec3 p, float t) { + // TODO: do what? + vec3 q = p; + return p * (1.0 - t) + q * t; +} + +vec3 detail_managenment(vec3 p) { + return detail_managenment(p, 0.0); +} + +vec4 hook() { + vec4 color = HOOKED_tex(HOOKED_pos); + + color.rgb = RGB_to_ICtCp(color.rgb); + color.rgb = tone_mapping(color.rgb); + color.rgb = ICtCp_to_RGB(color.rgb); + color.rgb = gamut_adjustment(color.rgb); + color.rgb = detail_managenment(color.rgb); + + return color; +} diff --git a/mpv/shaders/hdr-toys/transfer-function/bt1886.glsl b/mpv/shaders/hdr-toys/transfer-function/bt1886.glsl new file mode 100644 index 0000000..95a317a --- /dev/null +++ b/mpv/shaders/hdr-toys/transfer-function/bt1886.glsl @@ -0,0 +1,28 @@ +// https://www.itu.int/rec/R-REC-BT.1886 + +//!HOOK OUTPUT +//!BIND HOOKED +//!DESC transfer function (bt.1886) + +float bt1886_eotf_inv(float L, float gamma, float Lw, float Lb) { + float a = pow(pow(Lw, 1.0 / gamma) - pow(Lb, 1.0 / gamma), gamma); + float b = pow(Lb, 1.0 / gamma) / (pow(Lw, 1.0 / gamma) - pow(Lb, 1.0 / gamma)); + float V = pow(max(L / a, 0.0), 1.0 / gamma) - b; + return V; +} + +vec3 bt1886_eotf_inv(vec3 color, float gamma, float Lw, float Lb) { + return vec3( + bt1886_eotf_inv(color.r, gamma, Lw, Lb), + bt1886_eotf_inv(color.g, gamma, Lw, Lb), + bt1886_eotf_inv(color.b, gamma, Lw, Lb) + ); +} + +vec4 hook() { + vec4 color = HOOKED_tex(HOOKED_pos); + + color.rgb = bt1886_eotf_inv(color.rgb, 2.4, 1.0, 0.001); + + return color; +} diff --git a/mpv/shaders/hdr-toys/transfer-function/bt1886_inv.glsl b/mpv/shaders/hdr-toys/transfer-function/bt1886_inv.glsl new file mode 100644 index 0000000..cbe4a5b --- /dev/null +++ b/mpv/shaders/hdr-toys/transfer-function/bt1886_inv.glsl @@ -0,0 +1,28 @@ +// https://www.itu.int/rec/R-REC-BT.1886 + +//!HOOK OUTPUT +//!BIND HOOKED +//!DESC transfer function (bt.1886, inverse) + +float bt1886_eotf(float V, float gamma, float Lw, float Lb) { + float a = pow(pow(Lw, 1.0 / gamma) - pow(Lb, 1.0 / gamma), gamma); + float b = pow(Lb, 1.0 / gamma) / (pow(Lw, 1.0 / gamma) - pow(Lb, 1.0 / gamma)); + float L = a * pow(max(V + b, 0.0), gamma); + return L; +} + +vec3 bt1886_eotf(vec3 color, float gamma, float Lw, float Lb) { + return vec3( + bt1886_eotf(color.r, gamma, Lw, Lb), + bt1886_eotf(color.g, gamma, Lw, Lb), + bt1886_eotf(color.b, gamma, Lw, Lb) + ); +} + +vec4 hook() { + vec4 color = HOOKED_tex(HOOKED_pos); + + color.rgb = bt1886_eotf(color.rgb, 2.4, 1.0, 0.001); + + return color; +} diff --git a/mpv/shaders/hdr-toys/transfer-function/bt709.glsl b/mpv/shaders/hdr-toys/transfer-function/bt709.glsl new file mode 100644 index 0000000..380cd46 --- /dev/null +++ b/mpv/shaders/hdr-toys/transfer-function/bt709.glsl @@ -0,0 +1,30 @@ +// https://www.itu.int/rec/R-REC-BT.601 +// https://www.itu.int/rec/R-REC-BT.709 +// https://www.itu.int/rec/R-REC-BT.2020 + +//!HOOK OUTPUT +//!BIND HOOKED +//!DESC transfer function (bt.709) + +const float beta = 0.018053968510807; +const float alpha = 1.0 + 5.5 * beta; + +float bt709_oetf(float L) { + return L < beta ? 4.5 * L : alpha * pow(L, 0.45) - (alpha - 1.0); +} + +vec3 bt709_oetf(vec3 color) { + return vec3( + bt709_oetf(color.r), + bt709_oetf(color.g), + bt709_oetf(color.b) + ); +} + +vec4 hook() { + vec4 color = HOOKED_tex(HOOKED_pos); + + color.rgb = bt709_oetf(color.rgb); + + return color; +} diff --git a/mpv/shaders/hdr-toys/transfer-function/bt709_inv.glsl b/mpv/shaders/hdr-toys/transfer-function/bt709_inv.glsl new file mode 100644 index 0000000..e09513e --- /dev/null +++ b/mpv/shaders/hdr-toys/transfer-function/bt709_inv.glsl @@ -0,0 +1,30 @@ +// https://www.itu.int/rec/R-REC-BT.601 +// https://www.itu.int/rec/R-REC-BT.709 +// https://www.itu.int/rec/R-REC-BT.2020 + +//!HOOK OUTPUT +//!BIND HOOKED +//!DESC transfer function (bt.709, inverse) + +const float beta = 0.018053968510807; +const float alpha = 1.0 + 5.5 * beta; + +float bt709_oetf_inv(float V) { + return V < 4.5 * beta ? V / 4.5 : pow((V + (alpha - 1.0)) / alpha, 1.0 / 0.45); +} + +vec3 bt709_oetf_inv(vec3 color) { + return vec3( + bt709_oetf_inv(color.r), + bt709_oetf_inv(color.g), + bt709_oetf_inv(color.b) + ); +} + +vec4 hook() { + vec4 color = HOOKED_tex(HOOKED_pos); + + color.rgb = bt709_oetf_inv(color.rgb); + + return color; +} diff --git a/mpv/shaders/hdr-toys/transfer-function/hlg.glsl b/mpv/shaders/hdr-toys/transfer-function/hlg.glsl new file mode 100644 index 0000000..543062c --- /dev/null +++ b/mpv/shaders/hdr-toys/transfer-function/hlg.glsl @@ -0,0 +1,60 @@ +// https://www.arib.or.jp/kikaku/kikaku_hoso/std-b67.html +// https://www.bbc.co.uk/rd/projects/high-dynamic-range +// https://www.itu.int/rec/R-REC-BT.2100 + +// https://www.itu.int/pub/R-REP-BT.2390 +// extended gamma model for Lw is outside 400-2000 cd/m²: +// 1.2 * pow(1.111, log2(Lw / 1000.0)) * pow(0.98, log2(Lamb / 5.0)) + +//!PARAM reference_white +//!TYPE float +//!MINIMUM 0.0 +//!MAXIMUM 1000.0 +203.0 + +//!HOOK OUTPUT +//!BIND HOOKED +//!DESC transfer function (hlg) + +const vec3 y_coef = vec3(0.2627002120112671, 0.6779980715188708, 0.05930171646986196); + +const float Lw = 1000.0; +const float Lb = 0.0; +const float Lamb = 5.0; + +const float gamma = 1.2 + 0.42 * log(Lw / 1000.0) / log(10.0) - 0.076 * log(Lamb / 5.0) / log(10.0); +const float alpha = Lw; +const float beta = sqrt(3.0 * pow((Lb / Lw), 1.0 / gamma)); + +const float a = 0.17883277; +const float b = 1.0 - 4.0 * a; +const float c = 0.5 - a * log(4.0 * a); + +float hlg_oetf(float x) { + return x <= 1.0 / 12.0 ? sqrt(3.0 * x) : a * log(12.0 * x - b) + c; +} + +vec3 hlg_oetf(vec3 color) { + return vec3( + hlg_oetf(color.r), + hlg_oetf(color.g), + hlg_oetf(color.b) + ); +} + +vec3 hlg_ootf_inv(vec3 color) { + float Y = dot(color, y_coef); + return Y == 0.0 ? vec3(0.0) : pow(Y / alpha, (1.0 - gamma) / gamma) * (color / alpha); +} + +vec3 hlg_eotf_inv(vec3 color) { + return (hlg_oetf(hlg_ootf_inv(color)) - beta) / (1.0 - beta); +} + +vec4 hook() { + vec4 color = HOOKED_tex(HOOKED_pos); + + color.rgb = hlg_eotf_inv(color.rgb * reference_white); + + return color; +} diff --git a/mpv/shaders/hdr-toys/transfer-function/hlg_inv.glsl b/mpv/shaders/hdr-toys/transfer-function/hlg_inv.glsl new file mode 100644 index 0000000..c50d3b5 --- /dev/null +++ b/mpv/shaders/hdr-toys/transfer-function/hlg_inv.glsl @@ -0,0 +1,56 @@ +// https://www.arib.or.jp/kikaku/kikaku_hoso/std-b67.html +// https://www.bbc.co.uk/rd/projects/high-dynamic-range +// https://www.itu.int/rec/R-REC-BT.2100 + +//!PARAM reference_white +//!TYPE float +//!MINIMUM 0.0 +//!MAXIMUM 1000.0 +203.0 + +//!HOOK OUTPUT +//!BIND HOOKED +//!DESC transfer function (hlg, inverse) + +const vec3 y_coef = vec3(0.2627002120112671, 0.6779980715188708, 0.05930171646986196); + +const float Lw = 1000.0; +const float Lb = 0.0; +const float Lamb = 5.0; + +const float gamma = 1.2 + 0.42 * log(Lw / 1000.0) / log(10.0) - 0.076 * log(Lamb / 5.0) / log(10.0); +const float alpha = Lw; +const float beta = sqrt(3.0 * pow((Lb / Lw), 1.0 / gamma)); + +const float a = 0.17883277; +const float b = 1.0 - 4.0 * a; +const float c = 0.5 - a * log(4.0 * a); + +float hlg_oetf_inv(float x) { + return x <= 1.0 / 2.0 ? pow(x, 2.0) / 3.0 : (exp((x - c) / a) + b) / 12.0; +} + +vec3 hlg_oetf_inv(vec3 color) { + return vec3( + hlg_oetf_inv(color.r), + hlg_oetf_inv(color.g), + hlg_oetf_inv(color.b) + ); +} + +vec3 hlg_ootf(vec3 color) { + float Y = dot(color, y_coef); + return alpha * pow(Y, gamma - 1.0) * color; +} + +vec3 hlg_eotf(vec3 color) { + return hlg_ootf(hlg_oetf_inv(max((1.0 - beta) * color + beta, 0.0))); +} + +vec4 hook() { + vec4 color = HOOKED_tex(HOOKED_pos); + + color.rgb = hlg_eotf(color.rgb) / reference_white; + + return color; +} diff --git a/mpv/shaders/hdr-toys/transfer-function/pq.glsl b/mpv/shaders/hdr-toys/transfer-function/pq.glsl new file mode 100644 index 0000000..2b3509e --- /dev/null +++ b/mpv/shaders/hdr-toys/transfer-function/pq.glsl @@ -0,0 +1,43 @@ +// https://ieeexplore.ieee.org/document/7291452 +// https://www.itu.int/rec/R-REC-BT.2100 + +// https://www.itu.int/pub/R-REP-BT.2390 +// pq ootf: 100.0 * bt1886_eotf(bt709_oetf(59.5208 * x), 2.4, 1.0, 0.0) + +//!PARAM reference_white +//!TYPE float +//!MINIMUM 0.0 +//!MAXIMUM 1000.0 +203.0 + +//!HOOK OUTPUT +//!BIND HOOKED +//!DESC transfer function (pq) + +const float m1 = 2610.0 / 4096.0 / 4.0; +const float m2 = 2523.0 / 4096.0 * 128.0; +const float c1 = 3424.0 / 4096.0; +const float c2 = 2413.0 / 4096.0 * 32.0; +const float c3 = 2392.0 / 4096.0 * 32.0; +const float pw = 10000.0; + +float pq_eotf_inv(float x) { + float t = pow(x / pw, m1); + return pow((c1 + c2 * t) / (1.0 + c3 * t), m2); +} + +vec3 pq_eotf_inv(vec3 color) { + return vec3( + pq_eotf_inv(color.r), + pq_eotf_inv(color.g), + pq_eotf_inv(color.b) + ); +} + +vec4 hook() { + vec4 color = HOOKED_tex(HOOKED_pos); + + color.rgb = pq_eotf_inv(color.rgb * reference_white); + + return color; +} diff --git a/mpv/shaders/hdr-toys/transfer-function/pq_inv.glsl b/mpv/shaders/hdr-toys/transfer-function/pq_inv.glsl new file mode 100644 index 0000000..b8732df --- /dev/null +++ b/mpv/shaders/hdr-toys/transfer-function/pq_inv.glsl @@ -0,0 +1,40 @@ +// https://ieeexplore.ieee.org/document/7291452 +// https://www.itu.int/rec/R-REC-BT.2100 + +//!PARAM reference_white +//!TYPE float +//!MINIMUM 0.0 +//!MAXIMUM 1000.0 +203.0 + +//!HOOK OUTPUT +//!BIND HOOKED +//!DESC transfer function (pq, inverse) + +const float m1 = 2610.0 / 4096.0 / 4.0; +const float m2 = 2523.0 / 4096.0 * 128.0; +const float c1 = 3424.0 / 4096.0; +const float c2 = 2413.0 / 4096.0 * 32.0; +const float c3 = 2392.0 / 4096.0 * 32.0; +const float pw = 10000.0; + +float pq_eotf(float x) { + float t = pow(x, 1.0 / m2); + return pow(max(t - c1, 0.0) / (c2 - c3 * t), 1.0 / m1) * pw; +} + +vec3 pq_eotf(vec3 color) { + return vec3( + pq_eotf(color.r), + pq_eotf(color.g), + pq_eotf(color.b) + ); +} + +vec4 hook() { + vec4 color = HOOKED_tex(HOOKED_pos); + + color.rgb = pq_eotf(color.rgb) / reference_white; + + return color; +} diff --git a/mpv/shaders/hdr-toys/transfer-function/srgb.glsl b/mpv/shaders/hdr-toys/transfer-function/srgb.glsl new file mode 100644 index 0000000..371152a --- /dev/null +++ b/mpv/shaders/hdr-toys/transfer-function/srgb.glsl @@ -0,0 +1,31 @@ +// https://github.com/ampas/aces-core/blob/dev/lib/Lib.Academy.DisplayEncoding.ctl +// moncurve with gamma of 2.4 and offset of 0.055 matches the EOTF found in IEC 61966-2-1:1999 (sRGB) + +//!HOOK OUTPUT +//!BIND HOOKED +//!DESC transfer function (srgb) + +const float gamma = 2.4; +const float offset = 0.055; + +float monitor_curve_eotf_inv(float y) { + const float yb = pow(offset * gamma / ((gamma - 1.0) * (1.0 + offset)), gamma); + const float rs = pow((gamma - 1.0) / offset, gamma - 1.0) * pow((1.0 + offset) / gamma, gamma); + return y >= yb ? (1.0 + offset) * pow(y, 1.0 / gamma) - offset : y * rs; +} + +vec3 monitor_curve_eotf_inv(vec3 color) { + return vec3( + monitor_curve_eotf_inv(color.r), + monitor_curve_eotf_inv(color.g), + monitor_curve_eotf_inv(color.b) + ); +} + +vec4 hook() { + vec4 color = HOOKED_tex(HOOKED_pos); + + color.rgb = monitor_curve_eotf_inv(color.rgb); + + return color; +} diff --git a/mpv/shaders/hdr-toys/transfer-function/srgb_inv.glsl b/mpv/shaders/hdr-toys/transfer-function/srgb_inv.glsl new file mode 100644 index 0000000..8eea068 --- /dev/null +++ b/mpv/shaders/hdr-toys/transfer-function/srgb_inv.glsl @@ -0,0 +1,31 @@ +// https://github.com/ampas/aces-core/blob/dev/lib/Lib.Academy.DisplayEncoding.ctl +// moncurve with gamma of 2.4 and offset of 0.055 matches the EOTF found in IEC 61966-2-1:1999 (sRGB) + +//!HOOK OUTPUT +//!BIND HOOKED +//!DESC transfer function (srgb, inverse) + +const float gamma = 2.4; +const float offset = 0.055; + +float monitor_curve_eotf(float x) { + const float fs = ((gamma - 1.0) / offset) * pow(offset * gamma / ((gamma - 1.0) * (1.0 + offset)), gamma); + const float xb = offset / (gamma - 1.0); + return x >= xb ? pow((x + offset) / (1.0 + offset), gamma) : x * fs; +} + +vec3 monitor_curve_eotf(vec3 color) { + return vec3( + monitor_curve_eotf(color.r), + monitor_curve_eotf(color.g), + monitor_curve_eotf(color.b) + ); +} + +vec4 hook() { + vec4 color = HOOKED_tex(HOOKED_pos); + + color.rgb = monitor_curve_eotf(color.rgb); + + return color; +} diff --git a/mpv/shaders/hdr-toys/utils/clip_black.glsl b/mpv/shaders/hdr-toys/utils/clip_black.glsl new file mode 100644 index 0000000..872440a --- /dev/null +++ b/mpv/shaders/hdr-toys/utils/clip_black.glsl @@ -0,0 +1,11 @@ +//!HOOK OUTPUT +//!BIND HOOKED +//!DESC clip code value (black) + +vec4 hook() { + vec4 color = HOOKED_tex(HOOKED_pos); + + color.rgb = max(color.rgb, 0.0); + + return color; +} diff --git a/mpv/shaders/hdr-toys/utils/clip_both.glsl b/mpv/shaders/hdr-toys/utils/clip_both.glsl new file mode 100644 index 0000000..b84d32c --- /dev/null +++ b/mpv/shaders/hdr-toys/utils/clip_both.glsl @@ -0,0 +1,11 @@ +//!HOOK OUTPUT +//!BIND HOOKED +//!DESC clip code value (black, white) + +vec4 hook() { + vec4 color = HOOKED_tex(HOOKED_pos); + + color.rgb = clamp(color.rgb, 0.0, 1.0); + + return color; +} diff --git a/mpv/shaders/hdr-toys/utils/clip_white.glsl b/mpv/shaders/hdr-toys/utils/clip_white.glsl new file mode 100644 index 0000000..b8a20b5 --- /dev/null +++ b/mpv/shaders/hdr-toys/utils/clip_white.glsl @@ -0,0 +1,11 @@ +//!HOOK OUTPUT +//!BIND HOOKED +//!DESC clip code value (white) + +vec4 hook() { + vec4 color = HOOKED_tex(HOOKED_pos); + + color.rgb = min(color.rgb, 1.0); + + return color; +} diff --git a/mpv/shaders/hdr-toys/utils/edge_detection.glsl b/mpv/shaders/hdr-toys/utils/edge_detection.glsl new file mode 100644 index 0000000..61c5417 --- /dev/null +++ b/mpv/shaders/hdr-toys/utils/edge_detection.glsl @@ -0,0 +1,83 @@ +// https://anirban-karchaudhuri.medium.com/edge-detection-methods-comparison-9e4b75a9bf87 + +//!HOOK OUTPUT +//!BIND HOOKED +//!DESC edge detection + +#define tex HOOKED_texOff + + +const mat3 prewitt_x = mat3( + 1.0, 0.0, -1.0, + 1.0, 0.0, -1.0, + 1.0, 0.0, -1.0 +); + +const mat3 prewitt_y = mat3( + 1.0, 1.0, 1.0, + 0.0, 0.0, 0.0, + -1.0, -1.0, -1.0 +); + +const mat3 sobel_x = mat3( + 1.0, 0.0, -1.0, + 2.0, 0.0, -2.0, + 1.0, 0.0, -1.0 +); + +const mat3 sobel_y = mat3( + 1.0, 2.0, 1.0, + 0.0, 0.0, 0.0, + -1.0, -2.0, -1.0 +); + +const mat3 laplacian_p = mat3( + 0.0, 1.0, 0.0, + 1.0, -4.0, 1.0, + 0.0, 1.0, 0.0 +); + +const mat3 laplacian_n = mat3( + 0.0, -1.0, 0.0, + -1.0, 4.0, -1.0, + 0.0, -1.0, 0.0 +); + +const float base = 0.0; +const uvec2 k_size = uvec2(3, 3); +const vec2 k_size_h = vec2(k_size / 2); + +vec3 conv(mat3 k) { + vec3 x = vec3(base); + for (uint i = 0; i < k_size.x; i++) + for (uint j = 0; j < k_size.y; j++) + x += tex(vec2(j, i) - k_size_h).rgb * k[i][j]; + return x; +} + +vec3 prewitt() { + vec3 x = conv(prewitt_x); + vec3 y = conv(prewitt_y); + vec3 g = abs(sqrt(x * x + y * y)); + return g; +} + +vec3 sobel() { + vec3 x = conv(sobel_x); + vec3 y = conv(sobel_y); + vec3 g = abs(sqrt(x * x + y * y)); + return g; +} + +vec3 laplacian() { + vec3 x = conv(laplacian_p); + return x; +} + +vec4 hook() { + vec4 color = vec4(vec3(0.0), 1.0); + + color.rgb = laplacian(); + + return color; +} diff --git a/mpv/shaders/hdr-toys/utils/exposure.glsl b/mpv/shaders/hdr-toys/utils/exposure.glsl new file mode 100644 index 0000000..6bee92e --- /dev/null +++ b/mpv/shaders/hdr-toys/utils/exposure.glsl @@ -0,0 +1,28 @@ +// https://en.wikipedia.org/wiki/Exposure_value + +//!PARAM exposure_value +//!TYPE float +//!MINIMUM -64 +//!MAXIMUM 64 +0.0 + +//!HOOK OUTPUT +//!BIND HOOKED +//!WHEN exposure_value +//!DESC exposure + +float exposure(float x, float ev) { + return x * exp2(ev); +} + +vec3 exposure(vec3 x, float ev) { + return x * exp2(ev); +} + +vec4 hook() { + vec4 color = HOOKED_tex(HOOKED_pos); + + color.rgb = exposure(color.rgb, exposure_value); + + return color; +} diff --git a/mpv/shaders/hdr-toys/utils/invert.glsl b/mpv/shaders/hdr-toys/utils/invert.glsl new file mode 100644 index 0000000..542e346 --- /dev/null +++ b/mpv/shaders/hdr-toys/utils/invert.glsl @@ -0,0 +1,21 @@ +// invert the signal + +//!HOOK OUTPUT +//!BIND HOOKED +//!DESC signal invert + +float invert(float x, float w) { + return -x + w; +} + +vec3 invert(vec3 x, float w) { + return -x + w; +} + +vec4 hook() { + vec4 color = HOOKED_tex(HOOKED_pos); + + color.rgb = invert(color.rgb, 1.0); + + return color; +} diff --git a/mpv/shaders/hdr-toys/utils/range.glsl b/mpv/shaders/hdr-toys/utils/range.glsl new file mode 100644 index 0000000..5bbe654 --- /dev/null +++ b/mpv/shaders/hdr-toys/utils/range.glsl @@ -0,0 +1,38 @@ +// from "full" to "limited" signal range + +//!PARAM black +//!TYPE float +0.0625 + +//!PARAM white +//!TYPE float +0.91796875 + +//!PARAM depth +//!TYPE float +10.0 + +//!HOOK OUTPUT +//!BIND HOOKED +//!DESC signal range scaling + +float range(float x, float w, float b) { + return x * (w - b) + b; +} + +vec3 range(vec3 x, float w, float b) { + return x * (w - b) + b; +} + +vec4 hook() { + vec4 color = HOOKED_tex(HOOKED_pos); + + float l = exp2(depth); + float d = l - 1.0; + float b = l * black / d; + float w = l * white / d; + + color.rgb = range(color.rgb, w, b); + + return color; +} diff --git a/mpv/shaders/hdr-toys/utils/range_inv.glsl b/mpv/shaders/hdr-toys/utils/range_inv.glsl new file mode 100644 index 0000000..bb42839 --- /dev/null +++ b/mpv/shaders/hdr-toys/utils/range_inv.glsl @@ -0,0 +1,38 @@ +// from "limited" to "full" signal range + +//!PARAM black +//!TYPE float +0.0625 + +//!PARAM white +//!TYPE float +0.91796875 + +//!PARAM depth +//!TYPE float +10.0 + +//!HOOK OUTPUT +//!BIND HOOKED +//!DESC signal range scaling (inverse) + +float range_inv(float x, float w, float b) { + return (x - b) / (w - b); +} + +vec3 range_inv(vec3 x, float w, float b) { + return (x - b) / (w - b); +} + +vec4 hook() { + vec4 color = HOOKED_tex(HOOKED_pos); + + float l = exp2(depth); + float d = l - 1.0; + float b = l * black / d; + float w = l * white / d; + + color.rgb = range_inv(color.rgb, w, b); + + return color; +} diff --git a/mpv/shaders/hdr-toys/utils/transform.glsl b/mpv/shaders/hdr-toys/utils/transform.glsl new file mode 100644 index 0000000..9f044e0 --- /dev/null +++ b/mpv/shaders/hdr-toys/utils/transform.glsl @@ -0,0 +1,83 @@ +// https://developer.mozilla.org/en-US/docs/Web/CSS/transform + +//!PARAM scale_x +//!TYPE float +1.0 + +//!PARAM scale_y +//!TYPE float +1.0 + +//!PARAM translate_x +//!TYPE float +0.0 + +//!PARAM translate_y +//!TYPE float +0.0 + +//!PARAM skew_x +//!TYPE float +0.0 + +//!PARAM skew_y +//!TYPE float +0.0 + +//!PARAM rotate +//!TYPE float +0.0 + +//!PARAM background +//!TYPE ENUM int +black +border +repeat +mirror + +//!HOOK OUTPUT +//!BIND HOOKED +//!DESC transform +//!WHEN scale_x 1 - scale_y 1 - + translate_x + translate_y + skew_x + skew_y + rotate + + +vec4 hook() { + vec2 pos = HOOKED_pos; + vec2 size = HOOKED_size; + vec2 align = vec2(0.5, 0.5); // center + + pos -= align; + + if (scale_x != 1.0 || scale_y != 1.0) + pos /= vec2(scale_x, scale_y); + + if (translate_x != 0.0 || translate_y != 0.0) + pos -= vec2(translate_x, translate_y) * vec2(scale_x, scale_y); + + if (skew_x != 0.0 || skew_y != 0.0) + pos = mat2(1.0, -tan(radians(skew_y)), -tan(radians(skew_x)), 1.0) * pos; + + if (rotate != 0.0) { + pos *= size; + float c = length(pos); + float h = atan(pos.y, pos.x) - radians(rotate); + float a = cos(h) * c; + float b = sin(h) * c; + pos = vec2(a, b); + pos = floor(pos); + pos /= size; + } + + pos += align; + + if (background == black) + if (pos != clamp(pos, 0.0, 1.0)) + return vec4(vec3(0.0), 1.0); + else if (background == border) + pos = clamp(pos, 0.0, 1.0); + else if (background == repeat) + pos = mod(pos, 1.0); + else if (background == mirror) + pos = abs(1.0 - abs(mod(pos, 2.0) - 1.0)); + + return HOOKED_tex(pos); +}