Merge PR #2177: LRC-DEV-AUTOSYNC: sync HDR state while retaining per-photo tuning

crates/engine/src/lib.rs: keep both explicit comparison leaves (camera_local_tone and hdr).
crates/engine/src/tests.rs: keep both sides' tests.
This commit is contained in:
aiongg
2026-10-22 02:36:58 +00:00
3 changed files with 65 additions and 0 deletions
+3
View File
@@ -956,6 +956,9 @@ impl Session {
// switching camera local tone mapping off is a change without copying untouched values.
old_json["profile"]["camera_local_tone"] = Value::Bool(old.profile.camera_local_tone);
new_json["profile"]["camera_local_tone"] = Value::Bool(new.profile.camera_local_tone);
// Compare actual HDR leaves even when persisted defaults omit the whole HDR object.
old_json["hdr"] = serde_json::json!(old.hdr);
new_json["hdr"] = serde_json::json!(new.hdr);
let Some(mut delta) = json_delta(&old_json, &new_json) else { return Vec::new() };
if let Some(o) = delta.as_object_mut() {
for k in ["spots", "red_eye", "version", "process"] {
+61
View File
@@ -1012,6 +1012,24 @@ fn auto_sync_camera_local_tone_changes_only_when_edited() {
}
}
#[test]
fn auto_sync_hdr_off_returns_selected_photos_to_sdr() {
use lightcraft_catalog::PhotoId;
let mut s = demo();
let ids: Vec<u64> = s.catalog.photos().take(2).map(|p| p.id.0).collect();
assert_eq!(ids.len(), 2);
for id in &ids {
assert_eq!(s.develop_of(PhotoId(*id)).unwrap().hdr, lightcraft_develop::Hdr::default(), "default-HDR fixture");
}
s.execute("library.select", &json!({"ids": ids, "active": ids[0]})).unwrap();
s.execute("develop.autoSync", &json!({"on": true})).unwrap();
let enabled = |s: &Session| ids.iter().map(|id| s.develop_of(PhotoId(*id)).unwrap().hdr.enabled).collect::<Vec<_>>();
s.execute("develop.hdr", &json!({"enabled": true})).unwrap();
assert_eq!(enabled(&s), vec![true, true], "HDR On must reach both selected photos");
s.execute("develop.hdr", &json!({"enabled": false})).unwrap();
assert_eq!(enabled(&s), vec![false, false], "HDR Off must reach both selected photos");
}
#[test]
fn json_delta_keeps_only_changes() {
let a = json!({"light": {"exposure": 0, "contrast": 5}, "masks": [1], "x": 1});
@@ -1777,3 +1795,46 @@ fn malformed_photo_targets_are_refused_not_widened() {
let preset = s.presets.first().unwrap().id.clone();
s.execute("preset.apply", &json!({"id": preset})).unwrap();
}
#[test]
fn auto_sync_hdr_enabled_preserves_per_photo_tuning() {
use lightcraft_develop::{DevelopSettings, Hdr, ProcessVersion};
let mut s = demo();
let ids: Vec<_> = s.catalog.photos().take(2).map(|p| p.id).collect();
assert_eq!(ids.len(), 2);
s.execute("develop.autoSync", &json!({"on": false})).unwrap();
// Keep distinct stored processes explicit: the default is now V2; no rendering claim is made.
let mut expected = [DevelopSettings::default(), DevelopSettings { process: ProcessVersion(2), ..DevelopSettings::default() }];
expected[0].process = ProcessVersion(1);
expected[0].light.exposure = 0.5;
expected[0].light.contrast = 12.0;
expected[1].light.exposure = -0.25;
expected[1].light.contrast = -22.0;
expected[1].hdr = Hdr {
enabled: false,
max_ev: 4.0,
sdr_brightness: 11.0,
sdr_contrast: -12.0,
sdr_highlights: 13.0,
sdr_shadows: -14.0,
sdr_whites: 15.0,
sdr_clarity: -16.0,
};
for (id, settings) in ids.iter().zip(&expected) {
s.set_develop(*id, settings.clone(), "HDR tuning fixture").unwrap();
}
s.execute("library.select", &json!({"ids": ids.iter().map(|id| id.0).collect::<Vec<_>>(), "active": ids[0].0})).unwrap();
s.execute("develop.autoSync", &json!({"on": true})).unwrap();
let selection = s.selection.clone();
for enabled in [true, false] {
s.execute("develop.hdr", &json!({"enabled": enabled})).unwrap();
for (id, settings) in ids.iter().zip(&mut expected) {
settings.hdr.enabled = enabled;
let actual = s.develop_of(*id).unwrap();
assert_eq!(actual.hdr, settings.hdr, "HDR tuning must survive enabled={enabled} for photo {id:?}");
assert_eq!(actual.as_ref(), &*settings, "other per-photo edits and process must survive enabled={enabled}");
}
assert_eq!(s.selection, selection, "selection and active photo must survive enabled={enabled}");
assert!(s.auto_sync, "Auto Sync must remain enabled");
}
}
+1
View File
@@ -842,6 +842,7 @@ Latin stays upright. Advanced Japanese composition remains open. Regression cove
| 2026-10-12 | minor | LR-IMP-CAMERA-COVERAGE: Pentax PEFs without area tags lose their masked border lines (issue #1271); status unchanged (🟡) |
| 2026-10-12 | minor | Camera-look deep shadows follow the JPEG toe (#1307); camera calibration remains partial |
| 2026-10-11 | minor | LR-IMP-PREVIEW-READ: a preview reads the raw's header and its embedded JPEG instead of the whole file (a 56 MB RAF: 56.5 MB → 5.9 MB, ~10 s → 0.96 s over SMB, byte-identical pixels), and an unedited raw's grid thumbnail is that camera JPEG itself, so no sensor decode is queued behind it. A CR3's JPEG track and a TIFF raw whose preview tag sits past the prefix are read through `lightcraft_raw::sparse::preview_plan`, which names the ranges `embedded_preview` reads; the formats it cannot describe (CRW/MRW/X3F, NEF/ORF/PEF) keep the whole-file path |
| 2026-10-11 | minor | LRC-DEV-AUTOSYNC: preserve per-photo HDR tuning when synchronizing HDR enabled state; status unchanged. |
| 2026-10-12 | minor | LR-BEHAV-ACCESS: canvas handles by keyboard with AccessKit names and values; Tab order test; status stays 🟡 |
| 2026-10-12 | minor | LR-BEHAV-ACCESS: AccessKit tree audit test; text fields named, sliders expose range and Increment / Decrement; status stays 🟡 |
| 2026-10-12 | minor | LR-PREF-DISPLAY: on Linux, previews follow the display profile the desktop assigns in colord |