LR-EDIT-OPTICS-PROFILE: Sony embedded corrections on by default for ARW

An ILCE-7RM6 ARW with Sony's distortion table now imports with Enable
Lens Corrections on: Photo::camera_defaults already turns the correction
on for any file whose probe yields embedded lens data, and the ARW
decoder emits it since the previous commit. No new default rule is
needed, so RW2 and DNG keep theirs (on when the file carries a
correction) and stored settings are never rewritten.

Tests pin it: a synthetic ARW imports with the correction on (another
body's tables are not read: off), RW2 / DNG defaults are unchanged, and
an ARW catalogued without lens data or switched off by the user keeps
its settings across a library reopen. The Optics panel's hint drops
"(DNG lens opcodes only)" in every locale.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
dev-null-undefined
2026-10-13 13:09:40 +02:00
co-authored by Claude
parent b677fbac0a
commit d982a073c9
11 changed files with 118 additions and 12 deletions
+108
View File
@@ -2296,4 +2296,112 @@ mod tests {
assert_eq!(s.quick_view_job(id, 1600, true).unwrap().run().quick, Some(QuickSource::Small));
let _ = std::fs::remove_dir_all(&dir);
}
/// A Sony ARW of `model`: a 64 × 48 uncompressed 14-bit CFA strip in a SubIFD (as `crates/raw/tests/vendor_robust.rs`
/// lays it out) whose IFD carries the full-frame 16–35 mm distortion table (tag 0x7037).
fn sony_arw(model: &str) -> Vec<u8> {
use lightcraft_tiff::tags::{self as t, photometric};
use lightcraft_tiff::{ByteOrder, IfdBuilder, ImageData, TiffWriter, Value};
// 3:2, the aspect Sony's lens opcodes are limited to
let (w, h) = (72u32, 48u32);
let mut raw = IfdBuilder::new();
raw.set(t::NEW_SUBFILE_TYPE, Value::Long(vec![0]));
raw.set(t::IMAGE_WIDTH, Value::Long(vec![w]));
raw.set(t::IMAGE_LENGTH, Value::Long(vec![h]));
raw.set(t::BITS_PER_SAMPLE, Value::Short(vec![14]));
raw.set(t::COMPRESSION, Value::Short(vec![1]));
raw.set(t::PHOTOMETRIC, Value::Short(vec![photometric::CFA]));
raw.set(t::CFA_REPEAT_PATTERN_DIM, Value::Short(vec![2, 2]));
raw.set(t::CFA_PATTERN_EP, Value::Byte(vec![0, 1, 1, 2]));
raw.set(0x7037, Value::SShort(vec![16, 0, 2, 7, 16, 28, 43, 61, 83, 109, 139, 175, 217, 267, 325, 395, 479]));
raw.set_image(ImageData::Strips { rows_per_strip: h, strips: vec![2000u16.to_le_bytes().repeat((w * h) as usize)] });
let mut ifd0 = IfdBuilder::new();
ifd0.set(t::MAKE, Value::Ascii("SONY".into()));
ifd0.set(t::MODEL, Value::Ascii(model.into()));
ifd0.add_sub_ifd(raw);
TiffWriter::new(ByteOrder::Little, false).write(&[ifd0]).unwrap()
}
/// Sony's embedded distortion correction is on by default: an ARW of a verified body imports with "Enable Lens
/// Corrections" on and still counts as unedited; another body's tables aren't read, so its correction is off.
#[test]
fn arw_with_lens_data_imports_with_profile_corrections_on() {
let dir = std::env::temp_dir().join(format!("lc-arw-lens-default-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let mut s = crate::Session::new().with_fs();
for (model, on) in [("ILCE-7RM6", true), ("ILCE-7M4", false)] {
let path = dir.join(format!("{model}.arw"));
std::fs::write(&path, sony_arw(model)).unwrap();
let r = s.execute("library.import", &serde_json::json!({"paths": [path.to_string_lossy()]})).unwrap();
let p = s.catalog.photo(lightcraft_catalog::PhotoId(r["imported"][0].as_u64().unwrap())).unwrap();
assert_eq!((p.format.as_str(), p.embedded_lens.is_some(), p.develop.optics.lens_profile), ("ARW", on, on), "{model}");
assert!(!p.is_edited(), "{model}: as imported");
}
let _ = std::fs::remove_dir_all(&dir);
}
/// RW2 and DNG keep their default: the correction is on when the file carries one (RW2 distortion, a DNG's
/// opcodes), off when it doesn't.
#[test]
fn rw2_and_dng_defaults_unchanged() {
use lightcraft_catalog::{Photo, PhotoId, Source};
let s = crate::Session::new();
let lens = lightcraft_develop::EmbeddedLens { warp: Some(Default::default()), ..Default::default() };
for format in ["RW2", "DNG"] {
for embedded in [None, Some(lens)] {
let name = format!("x.{format}");
let mut p = Photo::new(PhotoId(1), Source::File { path: format!("/{name}") }, &name, format, 64, 48, "");
p.kind = MediaKind::Raw;
p.embedded_lens = embedded;
crate::import::apply_import_defaults(&s, &mut p);
assert_eq!(p.develop.optics.lens_profile, embedded.is_some(), "{format} {embedded:?}");
}
}
}
/// Stored settings stay as they are when the library opens again: an ARW catalogued before its lens data was read
/// (none, correction off) keeps the correction off and counts as unedited, and one whose user switched the
/// correction off keeps it off.
#[test]
fn existing_photo_settings_are_not_rewritten() {
use lightcraft_catalog::{Op, PhotoId};
let dir = std::env::temp_dir().join(format!("lc-arw-lens-existing-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
let (lib, files) = (dir.join("lib"), dir.join("files"));
std::fs::create_dir_all(&files).unwrap();
let mut s = crate::Session::new().with_fs();
s.open_library(&lib, false).unwrap();
let import = |s: &mut crate::Session, name: &str| {
let path = files.join(name);
// trailing bytes of its own: not a duplicate of the other file
std::fs::write(&path, [sony_arw("ILCE-7RM6"), name.as_bytes().to_vec()].concat()).unwrap();
let r = s.execute("library.import", &serde_json::json!({"paths": [path.to_string_lossy()]})).unwrap();
PhotoId(r["imported"][0].as_u64().unwrap())
};
let old = import(&mut s, "old.arw");
// as a release that didn't read the Sony tables catalogued it
let mut d = (*s.catalog.photo(old).unwrap().develop).clone();
d.optics.lens_profile = false;
let ops = vec![
Op::SetEmbeddedLens { id: old, lens: None },
Op::SetDevelop { id: old, settings: std::sync::Arc::new(d), label: "x".into(), edited: None },
];
s.commit("x", Op::Batch { ops }).unwrap();
let off = import(&mut s, "off.arw");
s.execute("develop.merge", &serde_json::json!({"settings": {"optics": {"lens_profile": false}}})).unwrap();
let before: Vec<_> = [old, off].iter().map(|id| s.catalog.photo(*id).unwrap().clone()).collect();
assert!(before.iter().all(|p| !p.develop.optics.lens_profile));
s.persist().unwrap();
drop(s);
let mut s = crate::Session::new().with_fs();
s.open_library(&lib, false).unwrap();
for b in &before {
let p = s.catalog.photo(b.id).unwrap();
assert_eq!((&p.develop, &p.embedded_lens), (&b.develop, &b.embedded_lens), "{}", p.file_name);
}
assert!(!s.catalog.photo(old).unwrap().is_edited(), "still as imported");
drop(s);
let _ = std::fs::remove_dir_all(&dir);
}
}