mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-24 18:18:12 -06:00
LR-PROF-CREATIVE: 16 own creative profiles (Film, Cinematic, Muted, B&W) as amount-scaled settings deltas
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
b5a873a631
commit
9c4526129e
@@ -263,8 +263,40 @@ pub const PROFILES: &[ProfileInfo] = &[
|
||||
ProfileInfo { id: "lc.landscape", name: "Landscape", group: "Basic" },
|
||||
ProfileInfo { id: "lc.portrait", name: "Portrait", group: "Basic" },
|
||||
ProfileInfo { id: "lc.mono", name: "Monochrome", group: "Basic" },
|
||||
ProfileInfo { id: "lc.film.warm-print", name: "Warm Print", group: "Film" },
|
||||
ProfileInfo { id: "lc.film.cool-fade", name: "Cool Fade", group: "Film" },
|
||||
ProfileInfo { id: "lc.film.golden-hour", name: "Golden Hour", group: "Film" },
|
||||
ProfileInfo { id: "lc.film.faded-slide", name: "Faded Slide", group: "Film" },
|
||||
ProfileInfo { id: "lc.cine.teal-amber", name: "Teal & Amber", group: "Cinematic" },
|
||||
ProfileInfo { id: "lc.cine.night-blue", name: "Night Blue", group: "Cinematic" },
|
||||
ProfileInfo { id: "lc.cine.desert-heat", name: "Desert Heat", group: "Cinematic" },
|
||||
ProfileInfo { id: "lc.cine.neon-dusk", name: "Neon Dusk", group: "Cinematic" },
|
||||
ProfileInfo { id: "lc.muted.matte-soft", name: "Matte Soft", group: "Muted" },
|
||||
ProfileInfo { id: "lc.muted.bleached", name: "Bleached", group: "Muted" },
|
||||
ProfileInfo { id: "lc.muted.pastel-haze", name: "Pastel Haze", group: "Muted" },
|
||||
ProfileInfo { id: "lc.muted.quiet-green", name: "Quiet Green", group: "Muted" },
|
||||
ProfileInfo { id: "lc.bw.mono-rich", name: "Mono Rich", group: "B&W" },
|
||||
ProfileInfo { id: "lc.bw.red-filter", name: "Mono Red Filter", group: "B&W" },
|
||||
ProfileInfo { id: "lc.bw.soft", name: "Mono Soft", group: "B&W" },
|
||||
ProfileInfo { id: "lc.bw.sepia", name: "Sepia Tone", group: "B&W" },
|
||||
];
|
||||
|
||||
/// Profile groups in menu/browser order.
|
||||
pub fn profile_groups() -> Vec<&'static str> {
|
||||
let mut out: Vec<&'static str> = Vec::new();
|
||||
for p in PROFILES {
|
||||
if !out.contains(&p.group) {
|
||||
out.push(p.group);
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// A profile by id.
|
||||
pub fn profile(id: &str) -> Option<&'static ProfileInfo> {
|
||||
PROFILES.iter().find(|p| p.id == id)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -280,6 +312,35 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_profile_has_a_look_and_ids_are_unique() {
|
||||
let looks = crate::pipeline::profiles::LOOK_IDS;
|
||||
let mut ids = std::collections::HashSet::new();
|
||||
for p in PROFILES {
|
||||
assert!(ids.insert(p.id), "{}", p.id);
|
||||
assert!(p.id == "lc.color" || looks.contains(&p.id), "{} has no look", p.id);
|
||||
}
|
||||
assert!(PROFILES.len() >= 22);
|
||||
assert_eq!(profile_groups(), ["Basic", "Film", "Cinematic", "Muted", "B&W"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn creative_profiles_render_without_moving_the_sliders() {
|
||||
let mut s = Session::with_demo();
|
||||
let id = s.active().unwrap();
|
||||
s.execute("develop.set", &json!({"control": "light.exposure", "value": 0.4})).unwrap();
|
||||
let before = s.develop_of(id).unwrap();
|
||||
let plain = s.render_now(id, 96, 96).unwrap().image;
|
||||
s.execute("develop.profile", &json!({"id": "lc.cine.teal-amber", "amount": 150})).unwrap();
|
||||
let after = s.develop_of(id).unwrap();
|
||||
assert_eq!((after.profile.id.as_str(), after.profile.amount), ("lc.cine.teal-amber", 150.0));
|
||||
let mut same = (*after).clone();
|
||||
same.profile = before.profile.clone();
|
||||
assert_eq!(same, *before, "only the profile changed");
|
||||
let looked = s.render_now(id, 96, 96).unwrap().image;
|
||||
assert_ne!(plain.as_bytes(), looked.as_bytes());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn slugs() {
|
||||
assert_eq!(slug("Warm & Soft!"), "warm-soft");
|
||||
|
||||
@@ -348,7 +348,7 @@ pub fn before_settings(s: &DevelopSettings) -> DevelopSettings {
|
||||
}
|
||||
|
||||
pub(crate) fn is_bw(s: &DevelopSettings) -> bool {
|
||||
s.treatment == Treatment::Bw || s.profile.id == "lc.mono"
|
||||
s.treatment == Treatment::Bw || s.profile.id == "lc.mono" || s.profile.id.starts_with("lc.bw.")
|
||||
}
|
||||
|
||||
/// `LIGHTCRAFT_PROFILE` is set: print per-stage timings to stderr.
|
||||
|
||||
+265
-26
@@ -1,9 +1,45 @@
|
||||
//! 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.
|
||||
//!
|
||||
//! Every look is a set of *deltas* on the user's settings, each multiplied by the amount: slider
|
||||
//! offsets (contrast, saturation, colour mixer, B&W mix…), colour-grading wheels (added as colour
|
||||
//! vectors to the user's wheels) and point-curve fades (black/white output levels composed after
|
||||
//! the user's point curves). The CPU pipeline and the GPU path both render [`effective`]
|
||||
//! settings, so a profile needs no kernel of its own. No look adds grain.
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
use lightcraft_develop::{DevelopSettings, Treatment};
|
||||
use lightcraft_develop::{DevelopSettings, Treatment, Wheel};
|
||||
use lightcraft_geom::Point;
|
||||
|
||||
/// Add a colour-grading wheel (`hue` °, `sat` 0..100, `lum` −100..100) scaled by `k` to `w`, as a
|
||||
/// colour vector (so a profile's tint combines with the user's instead of replacing it).
|
||||
fn wheel(w: &mut Wheel, hue: f64, sat: f64, lum: f64, k: f64) {
|
||||
let (ua, ub) = (w.sat * w.hue.to_radians().cos(), w.sat * w.hue.to_radians().sin());
|
||||
let (pa, pb) = (sat * k * hue.to_radians().cos(), sat * k * hue.to_radians().sin());
|
||||
let (a, b) = (ua + pa, ub + pb);
|
||||
let s = a.hypot(b);
|
||||
if s > 1e-9 {
|
||||
w.hue = b.atan2(a).to_degrees().rem_euclid(360.0);
|
||||
} else if w.sat == 0.0 {
|
||||
w.hue = hue;
|
||||
}
|
||||
w.sat = s.clamp(0.0, 100.0);
|
||||
w.lum = (w.lum + lum * k).clamp(-100.0, 100.0);
|
||||
}
|
||||
|
||||
/// Fade a point curve: output black level `black` and white level `white` (0..1, moved from the
|
||||
/// identity by `k`), composed after the user's curve (`y → b + y·(w − b)`).
|
||||
fn fade(pts: &mut Vec<Point>, black: f64, white: f64, k: f64) {
|
||||
let b = (black * k).clamp(0.0, 0.45);
|
||||
let w = (1.0 - (1.0 - white) * k).clamp(0.55, 1.0);
|
||||
if pts.len() < 2 {
|
||||
*pts = vec![Point::new(0.0, 0.0), Point::new(1.0, 1.0)];
|
||||
}
|
||||
for p in pts.iter_mut() {
|
||||
p.y = b + p.y.clamp(0.0, 1.0) * (w - b);
|
||||
}
|
||||
}
|
||||
|
||||
/// The settings the pipeline actually renders: user settings plus the profile's look.
|
||||
pub fn effective(s: &DevelopSettings) -> Cow<'_, DevelopSettings> {
|
||||
@@ -13,45 +49,199 @@ pub fn effective(s: &DevelopSettings) -> Cow<'_, DevelopSettings> {
|
||||
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);
|
||||
let add = |v: &mut f64, d: f64| *v = (*v + d * k).clamp(-100.0, 100.0);
|
||||
match id {
|
||||
// ---- Basic
|
||||
"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);
|
||||
add(&mut e.light.contrast, -18.0);
|
||||
add(&mut e.color.saturation, -10.0);
|
||||
add(&mut e.light.highlights, -10.0);
|
||||
add(&mut e.light.shadows, 8.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);
|
||||
add(&mut e.light.contrast, 16.0);
|
||||
add(&mut e.color.saturation, 14.0);
|
||||
add(&mut e.color.vibrance, 14.0);
|
||||
add(&mut e.light.blacks, -6.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);
|
||||
add(&mut e.light.contrast, 10.0);
|
||||
add(&mut e.color.vibrance, 10.0);
|
||||
add(&mut e.mixer.green.sat, 14.0);
|
||||
add(&mut e.mixer.aqua.sat, 10.0);
|
||||
add(&mut e.mixer.blue.sat, 14.0);
|
||||
add(&mut e.mixer.blue.lum, -6.0);
|
||||
add(&mut e.effects.clarity, 6.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);
|
||||
add(&mut e.light.contrast, -6.0);
|
||||
add(&mut e.mixer.orange.sat, -8.0);
|
||||
add(&mut e.mixer.orange.lum, 6.0);
|
||||
add(&mut e.mixer.red.sat, -5.0);
|
||||
add(&mut e.effects.texture, -6.0);
|
||||
}
|
||||
"lc.mono" => {
|
||||
e.treatment = Treatment::Bw;
|
||||
add(&mut e.light.contrast, 8.0, -100.0, 100.0);
|
||||
add(&mut e.light.contrast, 8.0);
|
||||
}
|
||||
// ---- Film
|
||||
"lc.film.warm-print" => {
|
||||
add(&mut e.light.contrast, 6.0);
|
||||
add(&mut e.color.saturation, -4.0);
|
||||
add(&mut e.mixer.orange.lum, 4.0);
|
||||
fade(&mut e.curve.master, 0.04, 0.97, k);
|
||||
wheel(&mut e.grading.highlights, 40.0, 18.0, 0.0, k);
|
||||
wheel(&mut e.grading.shadows, 25.0, 8.0, 0.0, k);
|
||||
}
|
||||
"lc.film.cool-fade" => {
|
||||
add(&mut e.light.contrast, -8.0);
|
||||
add(&mut e.color.saturation, -10.0);
|
||||
fade(&mut e.curve.master, 0.07, 0.95, k);
|
||||
wheel(&mut e.grading.shadows, 210.0, 16.0, 0.0, k);
|
||||
wheel(&mut e.grading.highlights, 190.0, 6.0, 0.0, k);
|
||||
}
|
||||
"lc.film.golden-hour" => {
|
||||
add(&mut e.color.vibrance, 10.0);
|
||||
add(&mut e.curve.highlights, -8.0);
|
||||
add(&mut e.mixer.orange.sat, 8.0);
|
||||
add(&mut e.mixer.yellow.hue, -6.0);
|
||||
wheel(&mut e.grading.global, 45.0, 10.0, 0.0, k);
|
||||
wheel(&mut e.grading.highlights, 35.0, 20.0, 0.0, k);
|
||||
}
|
||||
"lc.film.faded-slide" => {
|
||||
add(&mut e.light.contrast, 14.0);
|
||||
add(&mut e.color.vibrance, 8.0);
|
||||
add(&mut e.color.saturation, 6.0);
|
||||
add(&mut e.curve.shadows, 10.0);
|
||||
fade(&mut e.curve.blue, 0.06, 1.0, k);
|
||||
fade(&mut e.curve.red, 0.0, 0.97, k);
|
||||
}
|
||||
// ---- Cinematic
|
||||
"lc.cine.teal-amber" => {
|
||||
add(&mut e.light.contrast, 10.0);
|
||||
add(&mut e.mixer.orange.sat, 6.0);
|
||||
add(&mut e.mixer.green.hue, 20.0);
|
||||
add(&mut e.mixer.green.sat, -20.0);
|
||||
add(&mut e.mixer.blue.hue, -8.0);
|
||||
wheel(&mut e.grading.shadows, 195.0, 30.0, 0.0, k);
|
||||
wheel(&mut e.grading.highlights, 38.0, 24.0, 0.0, k);
|
||||
}
|
||||
"lc.cine.night-blue" => {
|
||||
add(&mut e.light.contrast, 12.0);
|
||||
add(&mut e.color.saturation, -12.0);
|
||||
add(&mut e.curve.darks, -8.0);
|
||||
wheel(&mut e.grading.shadows, 220.0, 28.0, -4.0, k);
|
||||
wheel(&mut e.grading.midtones, 215.0, 10.0, 0.0, k);
|
||||
}
|
||||
"lc.cine.desert-heat" => {
|
||||
add(&mut e.light.contrast, 8.0);
|
||||
add(&mut e.curve.lights, 6.0);
|
||||
add(&mut e.mixer.blue.sat, -25.0);
|
||||
add(&mut e.mixer.aqua.sat, -20.0);
|
||||
wheel(&mut e.grading.global, 32.0, 16.0, 0.0, k);
|
||||
wheel(&mut e.grading.highlights, 48.0, 12.0, 0.0, k);
|
||||
}
|
||||
"lc.cine.neon-dusk" => {
|
||||
add(&mut e.light.contrast, 10.0);
|
||||
add(&mut e.color.vibrance, 18.0);
|
||||
add(&mut e.mixer.magenta.sat, 12.0);
|
||||
add(&mut e.mixer.purple.sat, 12.0);
|
||||
wheel(&mut e.grading.shadows, 265.0, 22.0, 0.0, k);
|
||||
wheel(&mut e.grading.highlights, 330.0, 14.0, 0.0, k);
|
||||
}
|
||||
// ---- Muted
|
||||
"lc.muted.matte-soft" => {
|
||||
add(&mut e.light.contrast, -12.0);
|
||||
add(&mut e.color.saturation, -14.0);
|
||||
add(&mut e.curve.highlights, -6.0);
|
||||
fade(&mut e.curve.master, 0.08, 0.96, k);
|
||||
}
|
||||
"lc.muted.bleached" => {
|
||||
add(&mut e.light.contrast, 22.0);
|
||||
add(&mut e.color.saturation, -38.0);
|
||||
add(&mut e.curve.lights, 6.0);
|
||||
add(&mut e.effects.clarity, 8.0);
|
||||
}
|
||||
"lc.muted.pastel-haze" => {
|
||||
add(&mut e.light.contrast, -18.0);
|
||||
add(&mut e.color.saturation, -20.0);
|
||||
add(&mut e.color.vibrance, 12.0);
|
||||
fade(&mut e.curve.master, 0.1, 1.0, k);
|
||||
wheel(&mut e.grading.highlights, 330.0, 6.0, 0.0, k);
|
||||
wheel(&mut e.grading.shadows, 200.0, 6.0, 0.0, k);
|
||||
}
|
||||
"lc.muted.quiet-green" => {
|
||||
add(&mut e.light.contrast, -4.0);
|
||||
add(&mut e.color.saturation, -8.0);
|
||||
add(&mut e.mixer.green.sat, -30.0);
|
||||
add(&mut e.mixer.green.hue, -10.0);
|
||||
add(&mut e.mixer.yellow.sat, -18.0);
|
||||
add(&mut e.mixer.aqua.sat, -15.0);
|
||||
fade(&mut e.curve.master, 0.04, 1.0, k);
|
||||
}
|
||||
// ---- B&W
|
||||
"lc.bw.mono-rich" => {
|
||||
e.treatment = Treatment::Bw;
|
||||
add(&mut e.light.contrast, 18.0);
|
||||
add(&mut e.curve.darks, -8.0);
|
||||
add(&mut e.curve.lights, 6.0);
|
||||
add(&mut e.bw_mix.red, 8.0);
|
||||
add(&mut e.bw_mix.orange, 10.0);
|
||||
add(&mut e.bw_mix.blue, -12.0);
|
||||
}
|
||||
"lc.bw.red-filter" => {
|
||||
e.treatment = Treatment::Bw;
|
||||
add(&mut e.light.contrast, 12.0);
|
||||
add(&mut e.bw_mix.red, 40.0);
|
||||
add(&mut e.bw_mix.orange, 30.0);
|
||||
add(&mut e.bw_mix.yellow, 10.0);
|
||||
add(&mut e.bw_mix.green, -20.0);
|
||||
add(&mut e.bw_mix.aqua, -30.0);
|
||||
add(&mut e.bw_mix.blue, -45.0);
|
||||
}
|
||||
"lc.bw.soft" => {
|
||||
e.treatment = Treatment::Bw;
|
||||
add(&mut e.light.contrast, -14.0);
|
||||
add(&mut e.bw_mix.orange, 12.0);
|
||||
fade(&mut e.curve.master, 0.06, 0.97, k);
|
||||
}
|
||||
"lc.bw.sepia" => {
|
||||
e.treatment = Treatment::Bw;
|
||||
add(&mut e.light.contrast, 6.0);
|
||||
fade(&mut e.curve.master, 0.04, 0.98, k);
|
||||
wheel(&mut e.grading.shadows, 28.0, 18.0, 0.0, k);
|
||||
wheel(&mut e.grading.highlights, 45.0, 14.0, 0.0, k);
|
||||
}
|
||||
_ => return Cow::Borrowed(s),
|
||||
}
|
||||
Cow::Owned(e)
|
||||
}
|
||||
|
||||
/// Ids of the looks [`effective`] implements (besides the identity `lc.color`).
|
||||
pub const LOOK_IDS: &[&str] = &[
|
||||
"lc.neutral",
|
||||
"lc.vivid",
|
||||
"lc.landscape",
|
||||
"lc.portrait",
|
||||
"lc.mono",
|
||||
"lc.film.warm-print",
|
||||
"lc.film.cool-fade",
|
||||
"lc.film.golden-hour",
|
||||
"lc.film.faded-slide",
|
||||
"lc.cine.teal-amber",
|
||||
"lc.cine.night-blue",
|
||||
"lc.cine.desert-heat",
|
||||
"lc.cine.neon-dusk",
|
||||
"lc.muted.matte-soft",
|
||||
"lc.muted.bleached",
|
||||
"lc.muted.pastel-haze",
|
||||
"lc.muted.quiet-green",
|
||||
"lc.bw.mono-rich",
|
||||
"lc.bw.red-filter",
|
||||
"lc.bw.soft",
|
||||
"lc.bw.sepia",
|
||||
];
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -60,12 +250,61 @@ mod tests {
|
||||
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"] {
|
||||
for id in LOOK_IDS {
|
||||
let mut t = s.clone();
|
||||
t.profile.id = id.into();
|
||||
assert_ne!(*effective(&t), s, "{id}");
|
||||
t.profile.id = (*id).into();
|
||||
assert_ne!(*effective(&t), t, "{id}");
|
||||
t.profile.amount = 0.0;
|
||||
assert_eq!(*effective(&t), t, "{id} at 0%");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn looks_scale_with_amount_and_never_add_grain() {
|
||||
let mut s = DevelopSettings::default();
|
||||
s.light.contrast = 5.0;
|
||||
for id in LOOK_IDS {
|
||||
s.profile.id = (*id).into();
|
||||
let at = |a: f64| {
|
||||
let mut t = s.clone();
|
||||
t.profile.amount = a;
|
||||
effective(&t).into_owned()
|
||||
};
|
||||
let (half, full, double) = (at(50.0), at(100.0), at(200.0));
|
||||
// slider deltas are linear in the amount
|
||||
let d = |e: &DevelopSettings| e.light.contrast - s.light.contrast;
|
||||
assert!((d(&full) - 2.0 * d(&half)).abs() < 1e-9, "{id}");
|
||||
assert!((d(&double) - 2.0 * d(&full)).abs() < 1e-9, "{id}");
|
||||
// wheels and point-curve fades grow with the amount
|
||||
let lift = |e: &DevelopSettings| e.curve.master.first().map_or(0.0, |p| p.y);
|
||||
for (a, b) in [(&half, &full), (&full, &double)] {
|
||||
assert!(a.grading.shadows.sat <= b.grading.shadows.sat + 1e-9, "{id}");
|
||||
assert!(a.grading.highlights.sat <= b.grading.highlights.sat + 1e-9, "{id}");
|
||||
assert!(lift(a) <= lift(b) + 1e-9, "{id}");
|
||||
}
|
||||
if lift(&full) > 0.0 {
|
||||
assert!((lift(&full) - 2.0 * lift(&half)).abs() < 1e-9, "{id}");
|
||||
}
|
||||
assert_eq!(full.grain, s.grain, "{id} adds no grain");
|
||||
assert_eq!(full.profile, s.profile, "{id}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wheels_and_fades_compose_with_user_settings() {
|
||||
let mut s = DevelopSettings::default();
|
||||
s.grading.shadows = Wheel { hue: 195.0, sat: 10.0, lum: 0.0 };
|
||||
s.curve.master = vec![Point::new(0.0, 0.0), Point::new(0.5, 0.6), Point::new(1.0, 1.0)];
|
||||
s.profile.id = "lc.cine.teal-amber".into();
|
||||
let e = effective(&s);
|
||||
assert!((e.grading.shadows.hue - 195.0).abs() < 1e-6 && (e.grading.shadows.sat - 40.0).abs() < 1e-6, "{:?}", e.grading.shadows);
|
||||
assert_eq!(e.curve.master, s.curve.master, "no fade in this look: the user's curve stays");
|
||||
s.profile.id = "lc.muted.matte-soft".into();
|
||||
let e = effective(&s);
|
||||
assert_eq!(e.curve.master.len(), 3, "user points kept");
|
||||
let ys: Vec<f64> = e.curve.master.iter().map(|p| p.y).collect();
|
||||
for (y, want) in ys.iter().zip([0.08, 0.08 + 0.6 * 0.88, 0.96]) {
|
||||
assert!((y - want).abs() < 1e-9, "{ys:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -388,5 +388,5 @@ pub fn now_ms() -> f64 {
|
||||
|
||||
/// Whether the settings render in black & white.
|
||||
pub fn is_bw(d: &lightcraft_develop::DevelopSettings) -> bool {
|
||||
d.treatment == lightcraft_develop::Treatment::Bw || d.profile.id == "lc.mono"
|
||||
d.treatment == lightcraft_develop::Treatment::Bw || d.profile.id == "lc.mono" || d.profile.id.starts_with("lc.bw.")
|
||||
}
|
||||
|
||||
+3
-3
@@ -27,7 +27,7 @@ and prints the summary; `cargo xtask parity --write` refreshes the summary table
|
||||
| D. Search & filter (FILT) | 2 | 7 | 4 | 0 | 2/4 (50%) | 0/4 (0%) |
|
||||
| E. Metadata (META) | 2 | 2 | 2 | 0 | 2/2 (100%) | 0/2 (0%) |
|
||||
| F. Edit panel — global adjustments (EDIT) | 36 | 2 | 10 | 1 | 28/28 (100%) | 8/14 (57%) |
|
||||
| G. Profiles (PROF) | 3 | 1 | 6 | 0 | 2/3 (67%) | 1/3 (33%) |
|
||||
| G. Profiles (PROF) | 4 | 1 | 5 | 0 | 2/3 (67%) | 1/3 (33%) |
|
||||
| H. Crop & rotate (CROP) | 7 | 2 | 0 | 1 | 6/6 (100%) | 1/3 (33%) |
|
||||
| I. Remove / healing (REM) | 3 | 3 | 4 | 2 | 2/4 (50%) | 1/3 (33%) |
|
||||
| J. Red eye (EYE) | 0 | 0 | 2 | 0 | — | 0/1 (0%) |
|
||||
@@ -48,7 +48,7 @@ and prints the summary; `cargo xtask parity --write` refreshes the summary table
|
||||
| Y. Menus | 42 | 19 | 24 | 8 | 37/47 (79%) | 3/23 (13%) |
|
||||
| Z. Keyboard shortcuts (desktop) | 45 | 16 | 19 | 1 | 39/52 (75%) | 6/23 (26%) |
|
||||
| Lightroom Classic extras | 1 | 17 | 61 | 9 | — | 1/21 (5%) |
|
||||
| **Total** | 190 | 107 | 203 | 37 | 150/198 (76%) | 34/144 (24%) |
|
||||
| **Total** | 191 | 107 | 202 | 37 | 150/198 (76%) | 34/144 (24%) |
|
||||
<!-- /parity:summary -->
|
||||
|
||||
## Top gaps
|
||||
@@ -297,7 +297,7 @@ under the UI command that wraps it (a no-op error); the UI command now wins.
|
||||
| LR-PROF-ADOBE | Standard raw looks (own equivalents) | P0 | ✅ | `crates/engine/src/presets.rs` (`PROFILES`), `crates/pipeline/src/profiles.rs` | six own looks: Color, Neutral, Vivid, Landscape, Portrait, Monochrome |
|
||||
| LR-PROF-ADAPTIVE | Adaptive profiles | P2 | ⬜ | | |
|
||||
| LR-PROF-CAMERA | Camera-matching looks | P2 | ⬜ | | |
|
||||
| LR-PROF-CREATIVE | Creative profiles (own) | P2 | ⬜ | | |
|
||||
| LR-PROF-CREATIVE | Creative profiles (own) | P2 | ✅ | `cmd:develop.profile`, `crates/pipeline/src/profiles.rs` | 16 own looks in Film / Cinematic / Muted / B&W (tone + point-curve fades, colour grading, mixer / B&W mix); scale with `ctl:profile.amount`; sliders untouched |
|
||||
| LR-PROF-LEGACY | Legacy profiles | P2 | ⬜ | | |
|
||||
| LR-PROF-NONRAW | Profiles for non-raw files | P0 | ✅ | `cmd:develop.profile` | same looks apply to JPEG/TIFF |
|
||||
| LR-PROF-AMOUNT | Profile amount | P1 | ✅ | `ctl:profile.amount` | |
|
||||
|
||||
Reference in New Issue
Block a user