From ca35e7f888fd7db64a17b2b597b4c7c5a4ff9108 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Wed, 30 Sep 2026 23:28:51 -0400 Subject: [PATCH] M2.2: base profiles (Color/Neutral/Vivid/Landscape/Portrait/Monochrome) with amount Profiles are our own looks applied underneath the user's sliders. Co-Authored-By: Claude Opus 5.5 --- crates/pipeline/src/finish.rs | 6 ++- crates/pipeline/src/lib.rs | 2 + crates/pipeline/src/profiles.rs | 71 +++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 crates/pipeline/src/profiles.rs diff --git a/crates/pipeline/src/finish.rs b/crates/pipeline/src/finish.rs index d57c1375..ff874b8f 100644 --- a/crates/pipeline/src/finish.rs +++ b/crates/pipeline/src/finish.rs @@ -101,7 +101,11 @@ fn grain_noise(x: f32, y: f32, seed: u32) -> f32 { pub fn finish(p: &Prepared, s: &DevelopSettings, frame: &Frame, info: &SourceInfo) -> Rgba8 { let (w, h) = (p.img.width, p.img.height); - let tone = if info.raw { ToneMap::new(s.light.contrast, s.light.whites, s.light.blacks) } else { ToneMap::display(s.light.contrast, s.light.whites, s.light.blacks) }; + let tone = if info.raw { + ToneMap::new(s.light.contrast, s.light.whites, s.light.blacks) + } else { + ToneMap::display(s.light.contrast, s.light.whites, s.light.blacks) + }; let ops = ColorOps::new(s); let curves = curve_luts(&s.curve); let vig = if s.section_enabled("effects") { vignette(s) } else { None }; diff --git a/crates/pipeline/src/lib.rs b/crates/pipeline/src/lib.rs index 7247fb1f..bebaa18e 100644 --- a/crates/pipeline/src/lib.rs +++ b/crates/pipeline/src/lib.rs @@ -22,6 +22,7 @@ mod finish; pub mod geometry; mod local; pub mod masks; +pub mod profiles; mod tone; use lightcraft_develop::{DevelopSettings, Treatment}; @@ -104,6 +105,7 @@ pub fn render(src: &Rgb32f, info: &SourceInfo, s: &DevelopSettings, req: &Render } }; let mut t = t0; + let s = &*profiles::effective(s); let frame = geometry::Frame::new(src.width, src.height, s, req.apply_crop); let (w, h) = frame.fit(req.max_w, req.max_h); let mut img = frame.sample(src, w, h); diff --git a/crates/pipeline/src/profiles.rs b/crates/pipeline/src/profiles.rs new file mode 100644 index 00000000..ff95eed6 --- /dev/null +++ b/crates/pipeline/src/profiles.rs @@ -0,0 +1,71 @@ +//! Base rendering profiles. A profile is a look applied *underneath* the user's sliders (the sliders +//! stay at their displayed values); `profile.amount` (0–200 %) scales it. All looks are our own. + +use std::borrow::Cow; + +use lightcraft_develop::{DevelopSettings, Treatment}; + +/// The settings the pipeline actually renders: user settings plus the profile's look. +pub fn effective(s: &DevelopSettings) -> Cow<'_, DevelopSettings> { + let k = (s.profile.amount / 100.0).clamp(0.0, 2.0); + let id = s.profile.id.as_str(); + if id == "lc.color" || id.is_empty() || k == 0.0 { + return Cow::Borrowed(s); + } + let mut e = s.clone(); + let add = |v: &mut f64, d: f64, lo: f64, hi: f64| *v = (*v + d * k).clamp(lo, hi); + match id { + "lc.neutral" => { + add(&mut e.light.contrast, -18.0, -100.0, 100.0); + add(&mut e.color.saturation, -10.0, -100.0, 100.0); + add(&mut e.light.highlights, -10.0, -100.0, 100.0); + add(&mut e.light.shadows, 8.0, -100.0, 100.0); + } + "lc.vivid" => { + add(&mut e.light.contrast, 16.0, -100.0, 100.0); + add(&mut e.color.saturation, 14.0, -100.0, 100.0); + add(&mut e.color.vibrance, 14.0, -100.0, 100.0); + add(&mut e.light.blacks, -6.0, -100.0, 100.0); + } + "lc.landscape" => { + add(&mut e.light.contrast, 10.0, -100.0, 100.0); + add(&mut e.color.vibrance, 10.0, -100.0, 100.0); + add(&mut e.mixer.green.sat, 14.0, -100.0, 100.0); + add(&mut e.mixer.aqua.sat, 10.0, -100.0, 100.0); + add(&mut e.mixer.blue.sat, 14.0, -100.0, 100.0); + add(&mut e.mixer.blue.lum, -6.0, -100.0, 100.0); + add(&mut e.effects.clarity, 6.0, -100.0, 100.0); + } + "lc.portrait" => { + add(&mut e.light.contrast, -6.0, -100.0, 100.0); + add(&mut e.mixer.orange.sat, -8.0, -100.0, 100.0); + add(&mut e.mixer.orange.lum, 6.0, -100.0, 100.0); + add(&mut e.mixer.red.sat, -5.0, -100.0, 100.0); + add(&mut e.effects.texture, -6.0, -100.0, 100.0); + } + "lc.mono" => { + e.treatment = Treatment::Bw; + add(&mut e.light.contrast, 8.0, -100.0, 100.0); + } + _ => return Cow::Borrowed(s), + } + Cow::Owned(e) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn default_profile_is_borrowed_and_others_change_things() { + let s = DevelopSettings::default(); + assert!(matches!(effective(&s), Cow::Borrowed(_))); + for id in ["lc.neutral", "lc.vivid", "lc.landscape", "lc.portrait", "lc.mono"] { + let mut t = s.clone(); + t.profile.id = id.into(); + assert_ne!(*effective(&t), s, "{id}"); + t.profile.amount = 0.0; + assert_eq!(*effective(&t), t, "{id} at 0%"); + } + } +}