mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-14 19:27:01 -04:00
LR-HDR-EDIT: HDR settings, HDR tone map and the float HDR render
- `DevelopSettings.hdr`: enabled, headroom limit (`max_ev`) and SDR-rendition offsets (brightness, contrast, highlights, shadows, whites, clarity), with control specs (`hdr.maxEv`, `hdr.sdr*`) and copied with the Light group. - `ToneMap::hdr` / `display_hdr`: the SDR curve through the midtones, with highlights lifted up to `2^max_ev`; identical to the SDR tables at peak 1. - `OutputDepth::F32Hdr`: unclamped linear output up to the peak. The finish stage carries the peak through highlight desaturation, gamut mapping, encoding, curves, grain and LUT profiles; with HDR off every branch is the SDR code path, bit for bit. - `settings_for`: every non-HDR render (CPU and GPU) uses the SDR rendition. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
1cc0dde747
commit
791526aa8c
9 files changed
+322
-18
No files matched your search
@@ -140,6 +140,13 @@ controls! {
|
||||
"light.shadows" => light.shadows, "Shadows", Light, -100, 100, 0, 1, 0, Centered;
|
||||
"light.whites" => light.whites, "Whites", Light, -100, 100, 0, 1, 0, Centered;
|
||||
"light.blacks" => light.blacks, "Blacks", Light, -100, 100, 0, 1, 0, Centered;
|
||||
"hdr.maxEv" => hdr.max_ev, "HDR Headroom", Light, 0, 5, 3, 0.1, 1, Plain;
|
||||
"hdr.sdrBrightness" => hdr.sdr_brightness, "SDR Brightness", Light, -100, 100, 0, 1, 0, Centered;
|
||||
"hdr.sdrContrast" => hdr.sdr_contrast, "SDR Contrast", Light, -100, 100, 0, 1, 0, Centered;
|
||||
"hdr.sdrHighlights" => hdr.sdr_highlights, "SDR Highlights", Light, -100, 100, 0, 1, 0, Centered;
|
||||
"hdr.sdrShadows" => hdr.sdr_shadows, "SDR Shadows", Light, -100, 100, 0, 1, 0, Centered;
|
||||
"hdr.sdrWhites" => hdr.sdr_whites, "SDR Whites", Light, -100, 100, 0, 1, 0, Centered;
|
||||
"hdr.sdrClarity" => hdr.sdr_clarity, "SDR Clarity", Light, -100, 100, 0, 1, 0, Centered;
|
||||
"curve.highlights" => curve.highlights, "Highlights", Curve, -100, 100, 0, 1, 0, Centered;
|
||||
"curve.lights" => curve.lights, "Lights", Curve, -100, 100, 0, 1, 0, Centered;
|
||||
"curve.darks" => curve.darks, "Darks", Curve, -100, 100, 0, 1, 0, Centered;
|
||||
|
||||
@@ -98,7 +98,7 @@ impl SettingsGroup {
|
||||
SettingsGroup::Profile => &["profile"],
|
||||
SettingsGroup::Treatment => &["treatment"],
|
||||
SettingsGroup::WhiteBalance => &["wb"],
|
||||
SettingsGroup::Light => &["light"],
|
||||
SettingsGroup::Light => &["light", "hdr"],
|
||||
SettingsGroup::ToneCurve => &["curve"],
|
||||
SettingsGroup::Color => &["color"],
|
||||
SettingsGroup::ColorMixer => &["mixer", "bw_mix", "point_colors"],
|
||||
|
||||
@@ -14,6 +14,8 @@ pub struct DevelopSettings {
|
||||
pub treatment: Treatment,
|
||||
pub wb: WhiteBalance,
|
||||
pub light: Light,
|
||||
/// HDR editing: highlights above SDR white, and the SDR rendition derived from them.
|
||||
pub hdr: Hdr,
|
||||
pub curve: ToneCurve,
|
||||
pub color: ColorAdj,
|
||||
pub mixer: Mixer,
|
||||
@@ -48,6 +50,7 @@ impl Default for DevelopSettings {
|
||||
treatment: Treatment::Color,
|
||||
wb: WhiteBalance::default(),
|
||||
light: Light::default(),
|
||||
hdr: Hdr::default(),
|
||||
curve: ToneCurve::default(),
|
||||
color: ColorAdj::default(),
|
||||
mixer: Mixer::default(),
|
||||
@@ -931,3 +934,68 @@ impl Enhance {
|
||||
self.denoise_on.unwrap_or(self.denoise > 0.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// HDR editing. When enabled, an HDR render ([`Hdr::peak`]) keeps highlights above SDR white (1.0)
|
||||
/// up to `2^max_ev`; every SDR render (previews on SDR displays, 8/16-bit exports and a gain map's
|
||||
/// base image) uses the SDR rendition ([`DevelopSettings::sdr_rendition`]): the same edit with the
|
||||
/// `sdr_*` offsets applied.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct Hdr {
|
||||
pub enabled: bool,
|
||||
/// Headroom limit: stops above SDR white the HDR render may reach.
|
||||
pub max_ev: f64,
|
||||
/// SDR rendition offsets (−100..100): brightness (±1 EV), then added to the matching sliders.
|
||||
pub sdr_brightness: f64,
|
||||
pub sdr_contrast: f64,
|
||||
pub sdr_highlights: f64,
|
||||
pub sdr_shadows: f64,
|
||||
pub sdr_whites: f64,
|
||||
pub sdr_clarity: f64,
|
||||
}
|
||||
|
||||
impl Default for Hdr {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enabled: false,
|
||||
max_ev: Hdr::DEFAULT_MAX_EV,
|
||||
sdr_brightness: 0.0,
|
||||
sdr_contrast: 0.0,
|
||||
sdr_highlights: 0.0,
|
||||
sdr_shadows: 0.0,
|
||||
sdr_whites: 0.0,
|
||||
sdr_clarity: 0.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Hdr {
|
||||
pub const DEFAULT_MAX_EV: f64 = 3.0;
|
||||
pub const MAX_EV_LIMIT: f64 = 5.0;
|
||||
|
||||
/// Peak linear value of an HDR render relative to SDR white (1.0 when HDR is off).
|
||||
pub fn peak(&self) -> f32 {
|
||||
if self.enabled { (self.max_ev.clamp(0.0, Hdr::MAX_EV_LIMIT) as f32).exp2() } else { 1.0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl DevelopSettings {
|
||||
/// The settings an SDR render uses: unchanged when HDR is off; otherwise HDR off and the
|
||||
/// SDR rendition offsets applied to exposure, contrast, highlights, shadows, whites, clarity.
|
||||
pub fn sdr_rendition(&self) -> std::borrow::Cow<'_, DevelopSettings> {
|
||||
if !self.hdr.enabled {
|
||||
return std::borrow::Cow::Borrowed(self);
|
||||
}
|
||||
let h = self.hdr;
|
||||
let mut s = self.clone();
|
||||
s.hdr.enabled = false;
|
||||
let add = |v: f64, d: f64| (v + d).clamp(-100.0, 100.0);
|
||||
s.light.exposure = (s.light.exposure + h.sdr_brightness / 100.0).clamp(-5.0, 5.0);
|
||||
s.light.contrast = add(s.light.contrast, h.sdr_contrast);
|
||||
s.light.highlights = add(s.light.highlights, h.sdr_highlights);
|
||||
s.light.shadows = add(s.light.shadows, h.sdr_shadows);
|
||||
s.light.whites = add(s.light.whites, h.sdr_whites);
|
||||
s.effects.clarity = add(s.effects.clarity, h.sdr_clarity);
|
||||
std::borrow::Cow::Owned(s)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user