47#include <Video/VideoEnums.hpp>
61static inline bool isLuminanceBasedTonemap(::Video::Tonemap mode)
65 case ::Video::Tonemap::BT_2390:
66 case ::Video::Tonemap::BT_2446:
67 case ::Video::Tonemap::Reinhard:
92static constexpr auto TONEMAP_BT2390 = R
"_(
93// BT.2390 EETF — operates on max-RGB channel, preserves hue ratios.
94// Input: linear light, 1.0 = content peak.
95// Output: linear light, [0, 1].
97// PQ forward: linear [0,1] (where 1.0 = 10000 nits) → PQ [0,1]
98float pqForward(float Y) {
99 float Ym1 = pow(max(Y, 0.0), 0.1593017578125);
100 return pow((0.8359375 + 18.8515625 * Ym1) / (1.0 + 18.6875 * Ym1), 78.84375);
103// PQ inverse: PQ [0,1] → linear [0,1] (where 1.0 = 10000 nits)
104float pqInverse(float N) {
105 float Nm = pow(max(N, 0.0), 1.0 / 78.84375);
106 return pow(max(Nm - 0.8359375, 0.0) / (18.8515625 - 18.6875 * Nm), 1.0 / 0.1593017578125);
109// BT.2390 hermite spline
110// Maps [KS, srcPeakPQ] → [KS, dstPeakPQ] with slope=1 at KS, slope=0 at end.
111float bt2390_eetf(float E, float KS, float srcPeakPQ, float dstPeakPQ) {
112 if (E < KS) return E;
113 if (srcPeakPQ <= KS) return dstPeakPQ;
115 // Normalize to [0,1] in the [KS, srcPeakPQ] range
116 float t = (E - KS) / (srcPeakPQ - KS);
120 // Hermite basis functions:
121 // h00(t) = 2t³ - 3t² + 1 (value at t=0: 1, at t=1: 0)
122 // h10(t) = t³ - 2t² + t (tangent at t=0: 1, at t=1: 0)
123 // h01(t) = -2t³ + 3t² (value at t=0: 0, at t=1: 1)
125 // P(t) = h00(t)*KS + h10(t)*(srcPeakPQ-KS) + h01(t)*dstPeakPQ
127 // At t=0: P = KS (continuity with linear segment)
128 // At t=1: P = dstPeakPQ (reaches target peak)
129 // dP/dt at t=0 = (srcPeakPQ-KS) → dP/dE = 1 (slope continuity)
130 // dP/dt at t=1 = 0 (smooth roll-off)
131 float p = (2.0 * t3 - 3.0 * t2 + 1.0) * KS
132 + (t3 - 2.0 * t2 + t) * (srcPeakPQ - KS)
133 + (-2.0 * t3 + 3.0 * t2) * dstPeakPQ;
138vec3 tonemap(vec3 color) {
139 const float srcPeakNits = contentPeakNits;
140 const float dstPeakNits = sdrPeakNits;
142 // Convert nits to PQ domain
143 float srcPeakPQ = pqForward(srcPeakNits / 10000.0);
144 float dstPeakPQ = pqForward(dstPeakNits / 10000.0);
146 // Knee start: BT.2390 formula
147 float KS = 1.5 * dstPeakPQ - 0.5;
149 // Operate on max-RGB to preserve hue ratios
150 float maxC = max(color.r, max(color.g, color.b));
151 if (maxC <= 0.0) return color;
153 // Convert maxC from normalized (1.0=peak) to absolute PQ
154 float absLinear = maxC * srcPeakNits / 10000.0;
155 float pqVal = pqForward(absLinear);
158 float mappedPQ = bt2390_eetf(pqVal, KS, srcPeakPQ, dstPeakPQ);
160 // Convert back to linear, normalize to [0,1] relative to target peak
161 float mappedLinear = pqInverse(mappedPQ);
162 float dstLinear = pqInverse(dstPeakPQ);
163 float ratio = mappedLinear / max(dstLinear, 1e-10);
165 // Scale all channels by the same ratio
166 float scale = ratio / maxC;
167 return clamp(color * scale, 0.0, 1.0);
182static constexpr auto TONEMAP_BT2446A = R
"_(
183// BT.2446-inspired log tone curve with chroma scaling
184// Input: linear light, 1.0 = content peak.
185// Output: linear light [0,1].
187vec3 tonemap(vec3 color) {
188 const float srcPeak = contentPeakNits;
189 const float dstPeak = sdrPeakNits;
191 // BT.2020 luminance coefficients (input is BT.2020 primaries)
192 const vec3 lumaCoeff = vec3(0.2627, 0.6780, 0.0593);
194 // Convert to absolute nits
195 vec3 absColor = color * srcPeak;
198 float Y = dot(absColor, lumaCoeff);
199 if (Y <= 0.0) return vec3(0.0);
201 // Normalized luminance (0-1 range relative to srcPeak)
202 float Yn = Y / srcPeak;
204 // Parametric log compression: rho adapts to source peak
205 float rho = 1.0 + 32.0 * pow(srcPeak / 10000.0, 1.0 / 2.4);
206 float Yt = log(1.0 + (rho - 1.0) * Yn) / log(rho);
208 // Scale to destination peak
209 float Yout = Yt * dstPeak;
211 // Chroma scaling: scale proportionally to luminance change
212 float chromaScale = Yout / Y;
214 vec3 result = absColor * chromaScale;
217 return clamp(result, 0.0, 1.0);
230static constexpr auto TONEMAP_REINHARD = R
"_(
231// Reinhard tone mapping (extended with white point)
232// Input: linear light in BT.2020 primaries, 1.0 = content peak.
233// Output: linear light [0,1].
235vec3 tonemap(vec3 color) {
236 vec3 c = color * (contentPeakNits / sdrPeakNits);
238 // BT.2020 luminance coefficients
239 const vec3 lumaCoeff = vec3(0.2627, 0.6780, 0.0593);
240 float Lin = dot(c, lumaCoeff);
241 if (Lin <= 0.0) return vec3(0.0);
243 float whitePoint = contentPeakNits / sdrPeakNits;
244 float wp2 = whitePoint * whitePoint;
247 float Lout = (Lin * (1.0 + Lin / wp2)) / (1.0 + Lin);
249 return clamp(c * (Lout / Lin), 0.0, 1.0);
262static constexpr auto TONEMAP_HABLE = R
"_(
263// Hable / Uncharted 2 filmic tone mapping
264// Input: linear light, 1.0 = content peak.
265// Output: linear light [0,1].
267vec3 hableCurve(vec3 x) {
268 const float A = 0.15; // Shoulder strength
269 const float B = 0.50; // Linear strength
270 const float C = 0.10; // Linear angle
271 const float D = 0.20; // Toe strength
272 const float E = 0.02; // Toe numerator
273 const float F = 0.30; // Toe denominator
274 return ((x * (A * x + C * B) + D * E) / (x * (A * x + B) + D * F)) - E / F;
277vec3 tonemap(vec3 color) {
278 vec3 c = color * (contentPeakNits / sdrPeakNits);
280 float exposureBias = 2.0;
281 vec3 mapped = hableCurve(c * exposureBias);
283 float W = contentPeakNits / sdrPeakNits;
284 vec3 whiteScale = 1.0 / hableCurve(vec3(W));
286 return clamp(mapped * whiteScale, 0.0, 1.0);
298static constexpr auto TONEMAP_ACES2 = R
"_(
299// ACES filmic tone mapping (Hill fit)
300// Input: linear light in BT.709/sRGB primaries, 1.0 = content peak.
301// Output: linear light [0,1] in BT.709/sRGB primaries.
303vec3 tonemap(vec3 color) {
304 vec3 c = color * (contentPeakNits / sdrPeakNits);
307 const mat3 ACESInputMat = mat3(
308 0.59719, 0.07600, 0.02840,
309 0.35458, 0.90834, 0.13383,
310 0.04823, 0.01566, 0.83777
314 const mat3 ACESOutputMat = mat3(
315 1.60475, -0.10208, -0.00327,
316 -0.53108, 1.10813, -0.07276,
317 -0.07367, -0.00605, 1.07602
320 vec3 v = ACESInputMat * c;
321 vec3 a = v * (v + 0.0245786) - 0.000090537;
322 vec3 b = v * (0.983729 * v + 0.4329510) + 0.238081;
324 v = ACESOutputMat * v;
326 return clamp(v, 0.0, 1.0);
339static constexpr auto TONEMAP_AGX = R
"_(
340// Minimal AgX tone mapping (algebraic, no LUT)
341// Input: linear light in BT.709/sRGB primaries, 1.0 = content peak.
342// Output: linear light [0,1] in BT.709/sRGB primaries.
344vec3 agxDefaultContrastApprox(vec3 x) {
347 return + 15.5 * x4 * x2
356vec3 agx(vec3 color) {
357 // sRGB/BT.709 linear → AgX log space
358 const mat3 agxTransform = mat3(
359 0.842479062253094, 0.0423282422610123, 0.0423756549057051,
360 0.0784335999999992, 0.878468636469772, 0.0784336,
361 0.0792237451477643, 0.0791661274605434, 0.879142973793104
364 vec3 val = agxTransform * color;
365 val = max(val, 1e-10);
367 const float minEV = -12.47393;
368 const float maxEV = 4.026069;
369 val = clamp(log2(val), minEV, maxEV);
370 val = (val - minEV) / (maxEV - minEV);
372 val = agxDefaultContrastApprox(val);
376vec3 agxEotf(vec3 color) {
377 // AgX → sRGB/BT.709 linear
378 const mat3 agxInvTransform = mat3(
379 1.19687900512017, -0.0528968517574562, -0.0529716355144438,
380 -0.0980208811401368, 1.15190312990417, -0.0980434501171241,
381 -0.0990297440797205, -0.0989611768448433, 1.15107367264116
383 return agxInvTransform * color;
386vec3 tonemap(vec3 color) {
387 vec3 c = color * (contentPeakNits / sdrPeakNits);
392 return clamp(val, 0.0, 1.0);
405static constexpr auto TONEMAP_PBR_NEUTRAL = R
"_(
406// Khronos PBR Neutral tone mapping (2024)
407// Input: linear light, 1.0 = content peak.
408// Output: linear light [0,1].
410vec3 pbrNeutralToneMapping(vec3 color) {
411 const float startCompression = 0.8 - 0.04;
412 const float desaturation = 0.15;
414 float x = min(color.r, min(color.g, color.b));
415 float offset = (x < 0.08) ? x - 6.25 * x * x : 0.04;
418 float peak = max(color.r, max(color.g, color.b));
419 if (peak < startCompression) return color;
421 float d = 1.0 - startCompression;
422 float newPeak = 1.0 - d * d / (peak + d - startCompression);
423 color *= newPeak / peak;
425 float g = 1.0 - 1.0 / (desaturation * (peak - newPeak) + 1.0);
426 return mix(color, vec3(newPeak), g);
429vec3 tonemap(vec3 color) {
430 vec3 c = color * (contentPeakNits / sdrPeakNits);
431 return clamp(pbrNeutralToneMapping(c), 0.0, 1.0);
439static constexpr auto TONEMAP_CLAMP = R
"_(
440vec3 tonemap(vec3 color) {
441 return clamp(color, 0.0, 1.0);
449static inline QString tonemapConstants(
float contentPeakNits = 1000.0f,
float sdrPeakNits = 203.0f)
452const float contentPeakNits = %1;
453const float sdrPeakNits = %2;
455 .arg(contentPeakNits, 0, 'f', 1)
456 .arg(sdrPeakNits, 0,
'f', 1);
463static inline QString tonemapShader(
464 ::Video::Tonemap mode,
465 float contentPeakNits = 1000.0f,
466 float sdrPeakNits = 203.0f)
469 shader.reserve(4096);
471 shader += tonemapConstants(contentPeakNits, sdrPeakNits);
475 case ::Video::Tonemap::BT_2390: shader += TONEMAP_BT2390;
break;
476 case ::Video::Tonemap::BT_2446: shader += TONEMAP_BT2446A;
break;
477 case ::Video::Tonemap::Reinhard: shader += TONEMAP_REINHARD;
break;
478 case ::Video::Tonemap::Hable: shader += TONEMAP_HABLE;
break;
479 case ::Video::Tonemap::ACES2: shader += TONEMAP_ACES2;
break;
480 case ::Video::Tonemap::AgX: shader += TONEMAP_AGX;
break;
481 case ::Video::Tonemap::PBR_Neutral: shader += TONEMAP_PBR_NEUTRAL;
break;
482 case ::Video::Tonemap::Clamp:
483 default: shader += TONEMAP_CLAMP;
break;
493static constexpr auto TONEMAP_BT2020_TO_BT709_MATRIX = R
"_(
494const mat3 bt2020ToBt709 = mat3(
495 1.6605, -0.1246, -0.0182,
496 -0.5876, 1.1329, -0.1006,
497 -0.0728, -0.0083, 1.1187
501static constexpr auto TONEMAP_BT2020_TO_DISPLAY_P3_MATRIX = R
"_(
502const mat3 bt2020ToDisplayP3 = mat3(
503 1.3434, -0.0653, -0.0029,
504 -0.2822, 1.0760, -0.0416,
505 -0.0612, -0.0107, 1.0445
509static constexpr auto TONEMAP_SRGB_OETF = R
"_(
510vec3 srgbOetf(vec3 c) {
512 vec3 hi = 1.055 * pow(max(c, 0.0), vec3(1.0 / 2.4)) - 0.055;
513 return mix(lo, hi, step(vec3(0.0031308), c));
517static constexpr auto TONEMAP_GAMMA22_OETF = R
"_(
518vec3 gamma22Oetf(vec3 c) {
519 return pow(max(c, 0.0), vec3(1.0 / 2.2));
Graphics rendering pipeline for ossia score.
Definition Filter/PreviewWidget.hpp:12