#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.
This commit is contained in:
arnav-hiwarkar committed 2026-11-12 16:24:17 +05:30
1 parent 22e66e3d97
commit acf60eeaf3
3 files changed
+11 -6

No files matched your search

+6 -5
View File
@@ -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 {
+4
View File
@@ -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]);
+1 -1
View File
@@ -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;
}