Refuse a clipped highlight as a neutral
Sampling the overcast sky on a Canon 6D frame set tint to -100 and temperature to -15 for a patch the canvas showed as pure white. A clipped photosite is sensor white, not a colour: every channel stopped counting, so what the tap hands back is the as-shot multipliers themselves, which are strongly magenta, and the solver dutifully drove green to its stop. The display shader already fades such a pixel to a neutral of the same brightness before any operation runs, so the picker was balancing against something the photographer could not see. The probe now refuses a sample with any channel at or above the onset the shader fades from, the way the solver already refuses black. The threshold is one constant, CLIP_ONSET, formatted into the shader and read by the probe, so the two cannot drift apart.
This commit is contained in:
@@ -65,7 +65,8 @@ pub use history::{Edit, Entry as HistoryEntry, History, Step};
|
||||
pub use lens::{compose_warps, ComposedWarp, LensProfile, Tca, Warp};
|
||||
pub use operation::{
|
||||
compose, compose_with_framing, Affects, ComposedShader, Helper, Invalidation, Operation,
|
||||
OutputMode, Uniform, BASE_CURVE_POINTS, BASE_CURVE_UNIFORM_OFFSET, RESERVED_UNIFORM_FIELDS,
|
||||
OutputMode, Uniform, BASE_CURVE_POINTS, BASE_CURVE_UNIFORM_OFFSET, CLIP_ONSET,
|
||||
RESERVED_UNIFORM_FIELDS,
|
||||
};
|
||||
pub use preset::{LibraryParseError, NameError, Preset, PresetLibrary, Scope};
|
||||
pub use sidecar::{Sidecar, Version};
|
||||
|
||||
@@ -69,11 +69,13 @@ const ROUNDS: u32 = 3;
|
||||
|
||||
/// Below this a channel carries no ratio worth balancing.
|
||||
///
|
||||
/// A sample in the deep shadows, or one taken on a blown highlight where a
|
||||
/// channel has already clipped to nothing, has no white balance in it: the
|
||||
/// logarithms below would run away and the picker would slam a slider to its
|
||||
/// stop. Refusing is the honest answer, and the caller reports that the point
|
||||
/// was not usable rather than moving the photograph.
|
||||
/// A sample in the deep shadows has no white balance in it: the logarithms
|
||||
/// below would run away and the picker would slam a slider to its stop.
|
||||
/// Refusing is the honest answer, and the caller reports that the point was
|
||||
/// not usable rather than moving the photograph. The other end — a blown
|
||||
/// highlight, where every channel has stopped counting — is refused by the
|
||||
/// caller before the sample is taken, because only the caller can see the
|
||||
/// sensor value; see [`crate::operation::CLIP_ONSET`].
|
||||
const FLOOR: f32 = 1e-4;
|
||||
|
||||
/// TRACES: FR-DEV-3
|
||||
|
||||
@@ -492,6 +492,19 @@ pub const BASE_CURVE_UNIFORM_OFFSET: usize = 16;
|
||||
/// selection in [`compose_full`].
|
||||
pub const BASE_CURVE_POINTS: usize = 5;
|
||||
|
||||
/// Where the highlight desaturation begins: the fraction of the white level
|
||||
/// above which a photosite is treated as clipped.
|
||||
///
|
||||
/// A photosite this close to saturation has stopped counting, so its ratio
|
||||
/// to its neighbours is not a colour. The generated prologue fades a pixel
|
||||
/// above this toward a neutral of the same brightness before any operation
|
||||
/// runs, and the white balance probe refuses to sample one: a blown sky is
|
||||
/// sensor white, which the as-shot multipliers make magenta, and a solve
|
||||
/// over that slams tint to its stop. One number, so the two cannot drift
|
||||
/// apart — a probe that accepted what the shader had already desaturated
|
||||
/// would be balancing against a pixel the photographer cannot see.
|
||||
pub const CLIP_ONSET: f32 = 0.985;
|
||||
|
||||
/// Where an operation's own uniforms begin in the generated block.
|
||||
///
|
||||
/// The base fields, then framing's. Exported because `dr-gpu` writes the
|
||||
@@ -1016,6 +1029,9 @@ fn compose_inner(
|
||||
.to_string()
|
||||
};
|
||||
|
||||
// Formatted with Rust's `Display` so the shader reads the same threshold
|
||||
// the probe checks against; see `CLIP_ONSET`.
|
||||
let clip_onset = CLIP_ONSET;
|
||||
let source = format!(
|
||||
"// GENERATED — do not edit.
|
||||
//
|
||||
@@ -1086,7 +1102,7 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {{
|
||||
// A photosite at its white level carries no colour information — every
|
||||
// channel simply stopped counting — so the balance below must not be
|
||||
// allowed to tint it.
|
||||
let clipped = smoothstep(0.985, 1.0, max(c.r, max(c.g, c.b)));
|
||||
let clipped = smoothstep({clip_onset}, 1.0, max(c.r, max(c.g, c.b)));
|
||||
|
||||
c = c * u.as_shot_wb.rgb;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user