LR-IMP-PROFILES: carry masks over from presets, .lrtemplate and XMP sidecars

Local corrections stored as crs: structures (MaskGroupBasedCorrections and the older
gradient / circular gradient / paint containers) become our masks: linear and radial
gradients, brushes (dab runs → strokes), Subject / Sky, luminance and depth ranges, with
add / subtract / intersect and invert, and all local sliders. lightcraft-meta's XmpData gains
`values`, a structured view of every property (arrays of structs survive); .lrtemplate Lua
tables convert into the same shape.
Applying a preset adds its masks to the photo's own (once; Amount scales them); sidecar masks
replace them and radial masks are fitted to the photo's aspect. Unsupported components
(colour range, other AI selections) are reported as "Mask: <kind>".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Brandon Thomas
2026-10-02 20:31:40 -04:00
co-authored by Claude Opus 5.5
parent 1dc501bc3a
commit b7d497d049
10 changed files with 536 additions and 18 deletions
+22 -1
View File
@@ -208,8 +208,29 @@ impl Preset {
pub fn from_settings(id: &str, name: &str, group: &str, s: &DevelopSettings, groups: &[SettingsGroup]) -> Preset {
Preset { id: id.into(), name: name.into(), group: group.into(), settings: extract_groups(s, groups), favorite: false, builtin: false }
}
/// Apply at `amount` (1.0 = 100 %). Masks in the preset are added to the photo's own (ones it
/// already has are skipped), their effect scaled by the amount.
pub fn apply(&self, s: &DevelopSettings, amount: f64) -> DevelopSettings {
apply_partial(s, &self.settings, amount)
let Some(masks) = self.settings.get("masks").and_then(Value::as_array).filter(|m| !m.is_empty()) else {
return apply_partial(s, &self.settings, amount);
};
let mut rest = self.settings.clone();
if let Some(o) = rest.as_object_mut() {
o.remove("masks");
}
let mut out = apply_partial(s, &rest, amount);
let mut next = out.masks.iter().map(|m| m.id).max().unwrap_or(0);
for m in masks {
let Ok(mut m) = serde_json::from_value::<crate::Mask>(m.clone()) else { continue };
if out.masks.iter().any(|q| q.name == m.name && q.components == m.components) {
continue;
}
next += 1;
m.id = next;
m.adjust.amount = (m.adjust.amount * amount).clamp(0.0, 200.0);
out.masks.push(m);
}
out
}
}