From acf60eeaf3b05be46a354a18b1704a31ca024e7c Mon Sep 17 00:00:00 2001 From: arnav-hiwarkar Date: Thu, 12 Nov 2026 16:24:17 +0530 Subject: [PATCH] #3487, #3488, #3489, #3490, #3491: Levels (Individual Controls): bound the curve only where it would overflow The 1e6 bound also capped curves that were finite before (gamma 0.1 on an HDR value 4 x Input White is 4^10, about 1.05e6). The bound is now 1e30: 1e30 times the widest output range (2e4) is still finite, and every curve below it renders as before. A test pins 4^10. --- crates/effects/src/color2.rs | 11 ++++++----- crates/effects/src/color2/tests.rs | 4 ++++ crates/gpu/src/shaders/fx_tone.wgsl | 2 +- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/crates/effects/src/color2.rs b/crates/effects/src/color2.rs index ad370ea1d..9df44d76f 100644 --- a/crates/effects/src/color2.rs +++ b/crates/effects/src/color2.rs @@ -257,11 +257,12 @@ impl Lv { } } -/// How far Levels (Individual Controls)' curve may reach, far beyond any image value: with gamma -/// down to 0 (rendered as 0.01, an exponent of 100) an HDR value a few times Input White would -/// overflow `f32`, and an infinite curve times an empty output range is NaN. Bounded, the output -/// stays finite over the whole valid range (levels ±10000). The GPU (`fxt_lv`) uses the same. -const LEVELS_IC_CURVE_LIMIT: f32 = 1e6; +/// How far Levels (Individual Controls)' curve may reach: with gamma down to 0 (rendered as 0.01, +/// an exponent of 100) an HDR value a few times Input White overflows `f32`, and an infinite curve +/// times an empty output range is NaN. 1e30 times the widest output range (2e4) is still finite +/// (`f32::MAX` is 3.4e38), and every curve that was finite before stays as it was below it. The +/// GPU (`fxt_lv`) uses the same. +const LEVELS_IC_CURVE_LIMIT: f32 = 1e30; fn levels_ic(ctx: &EffectCtx, mut b: Buf) -> Buf { let lv = LV_IDS.map(|ids| Lv { diff --git a/crates/effects/src/color2/tests.rs b/crates/effects/src/color2/tests.rs index 069d4c9f7..76338dd98 100644 --- a/crates/effects/src/color2/tests.rs +++ b/crates/effects/src/color2/tests.rs @@ -294,6 +294,10 @@ fn levels_ic_stays_finite_over_the_whole_range() { let out = run("ec.color.levelsic", &img, &lv); assert!(out.data.iter().flatten().all(|v| v.is_finite()), "{ib} {iw} {ob} {ow}: {:?}", out.data[0]); } + // The bound only stops the overflow: an HDR value that was finite before keeps its level + // (gamma 0.1 at 4 x Input White: 4^10). + let bright = run("ec.color.levelsic", &Image::filled(1, 1, [4.0, 4.0, 4.0, 1.0]), &[("rgb/rgbGamma", num(0.1)), ("clipToOutputWhite", Value::Enum(0))]); + assert_eq!(bright.data[0][0], 4.0f32.powf(10.0)); // Gamma 0 is the limit of a small gamma: below Input White the curve goes to 0. let dark = run("ec.color.levelsic", &Image::filled(1, 1, [0.5, 0.5, 0.5, 1.0]), &[("rgb/rgbGamma", num(0.0))]); assert!(dark.data[0][0] < 1e-6, "{:?}", dark.data[0]); diff --git a/crates/gpu/src/shaders/fx_tone.wgsl b/crates/gpu/src/shaders/fx_tone.wgsl index fb7dfe42b..bcd23baa7 100644 --- a/crates/gpu/src/shaders/fx_tone.wgsl +++ b/crates/gpu/src/shaders/fx_tone.wgsl @@ -41,7 +41,7 @@ fn fxt_lv(i: u32, v: f32, clip_b: bool, clip_w: bool) -> f32 { t = min(t, 1.0); } // color2::LEVELS_IC_CURVE_LIMIT keeps the curve finite. - let curve = clamp(fxt_gamma(t, max(data[o + 2u], 0.01)), -1e6, 1e6); + let curve = clamp(fxt_gamma(t, max(data[o + 2u], 0.01)), -1e30, 1e30); return data[o + 3u] + (data[o + 4u] - data[o + 3u]) * curve; }