mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-28 12:08:10 -06:00
Apply #580's updated head (drops the DSC-R1 SR2 refusal)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
2ecb495d18
commit
4a3f9e9b6d
Vendored
+4
-49
@@ -64,10 +64,6 @@ pub(crate) fn dynamic_range_optimizer(note: &makernote::MakerNote) -> Option<boo
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
/// Maker-note `FileFormat` (ExifTool Sony tag names): `1 0 0 0` is SR2, `2 0 0 0` ARW 1.0, `3 …` and later ARW 2.x+.
|
||||
/// The DSC-R1's SR2 stores 16-bit samples that are spread uniformly over 2–65340 (median ~33000, three quarters
|
||||
/// above any white level): encrypted, so it is refused and opens from its preview instead of decoding to noise.
|
||||
const MN_FILE_FORMAT: u16 = 0xb000;
|
||||
|
||||
/// Inverse of Sony's maker-note byte substitution. ExifTool's Sony tag documentation states that the data of
|
||||
/// tags `0x2010`, `0x9050` and `0x94xx` "is encrypted by a simple substitution cipher" (no decoder source was
|
||||
@@ -494,13 +490,6 @@ pub(crate) fn decode(bytes: &[u8], mode: Mode) -> Result<RawImage> {
|
||||
let tiff = Tiff::parse(bytes)?;
|
||||
let ifd0 = &tiff.ifds[0];
|
||||
let raw = raw_ifd(&tiff).ok_or_else(|| RawError::Unsupported("ARW without a CFA image IFD (old ARW or SR2)".into()))?;
|
||||
let mn = tiff
|
||||
.exif()
|
||||
.and_then(|e| e.get(t::MAKER_NOTE))
|
||||
.and_then(|e| makernote::parse_makernote(bytes, e.offset, e.count() as u64, tiff.order, &ifd0.string(t::MAKE).unwrap_or_default()));
|
||||
if mn.as_ref().and_then(|m| m.ifd.u64s(MN_FILE_FORMAT)).as_deref() == Some(&[1, 0, 0, 0]) {
|
||||
return Err(RawError::Unsupported("Sony SR2 (DSC-R1): the raw data is encrypted, not decoded yet".into()));
|
||||
}
|
||||
let info = raw.image()?;
|
||||
let (w, h) = (info.width as usize, info.height as usize);
|
||||
if w * h > crate::MAX_SAMPLES {
|
||||
@@ -579,6 +568,10 @@ pub(crate) fn decode(bytes: &[u8], mode: Mode) -> Result<RawImage> {
|
||||
raw.f64(t::WHITE_LEVEL).map(|v| v as f32).filter(|v| *v > 0.0).unwrap_or_else(|| super::white_from_data(samples, scale_bits))
|
||||
};
|
||||
let model = ifd0.string(t::MODEL).unwrap_or_default();
|
||||
let mn = tiff
|
||||
.exif()
|
||||
.and_then(|e| e.get(t::MAKER_NOTE))
|
||||
.and_then(|e| makernote::parse_makernote(bytes, e.offset, e.count() as u64, tiff.order, &ifd0.string(t::MAKE).unwrap_or_default()));
|
||||
let wb = raw
|
||||
.f64s(WB_RGGB)
|
||||
.and_then(|v| rggb_gains(&v))
|
||||
@@ -678,44 +671,6 @@ mod tests {
|
||||
assert_eq!(recorded_size_crop((4240, 2832), Some((4240, 0)), 4288, 2848), Some(Rect::new(0, 0, 4240, 2832)));
|
||||
}
|
||||
|
||||
/// A Sony CFA file whose maker note carries only `FileFormat` (tag 0xb000) = `format`.
|
||||
fn sony_with_file_format(format: [u8; 4]) -> Vec<u8> {
|
||||
use lightcraft_tiff::{IfdBuilder, ImageData, TiffWriter, Value};
|
||||
// Sony maker note: 12-byte header, then an IFD (one entry whose 4-byte value is stored inline)
|
||||
let mut note = b"SONY DSC \0\0\0".to_vec();
|
||||
note.extend(1u16.to_le_bytes());
|
||||
note.extend(MN_FILE_FORMAT.to_le_bytes());
|
||||
note.extend(1u16.to_le_bytes()); // BYTE
|
||||
note.extend(4u32.to_le_bytes());
|
||||
note.extend(format);
|
||||
note.extend(0u32.to_le_bytes());
|
||||
let mut exif = IfdBuilder::new();
|
||||
exif.set(t::MAKER_NOTE, Value::Undefined(note));
|
||||
let mut raw = IfdBuilder::new();
|
||||
raw.set(t::MAKE, Value::Ascii("SONY".into()));
|
||||
raw.set(t::MODEL, Value::Ascii("DSC-R1".into()));
|
||||
raw.set(t::IMAGE_WIDTH, Value::Long(vec![8]));
|
||||
raw.set(t::IMAGE_LENGTH, Value::Long(vec![4]));
|
||||
raw.set(t::BITS_PER_SAMPLE, Value::Short(vec![14]));
|
||||
raw.set(t::SAMPLES_PER_PIXEL, Value::Short(vec![1]));
|
||||
raw.set(t::PHOTOMETRIC, Value::Short(vec![photometric::CFA]));
|
||||
raw.set(t::COMPRESSION, Value::Short(vec![1]));
|
||||
raw.set_child(t::EXIF_IFD, exif);
|
||||
raw.set_image(ImageData::Strips { rows_per_strip: 4, strips: vec![vec![0x10u8; 8 * 4 * 2]] });
|
||||
TiffWriter::default().write(&[raw]).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sr2_is_refused_not_decoded_as_noise() {
|
||||
for mode in [Mode::Header, Mode::Full] {
|
||||
let err = decode(&sony_with_file_format([1, 0, 0, 0]), mode).unwrap_err();
|
||||
assert!(matches!(err, RawError::Unsupported(ref m) if m.contains("SR2")), "{mode:?}: {err:?}");
|
||||
// the same file as an ARW 2.x decodes
|
||||
let ok = decode(&sony_with_file_format([3, 0, 0, 0]), mode);
|
||||
assert!(ok.is_ok(), "{mode:?}: {ok:?}");
|
||||
}
|
||||
}
|
||||
|
||||
/// A 32767-compressed ARW whose single strip is `strip` bytes for a 32 x 4 image.
|
||||
fn arw_with_strip(strip: usize) -> Vec<u8> {
|
||||
use lightcraft_tiff::{IfdBuilder, ImageData, TiffWriter, Value};
|
||||
|
||||
+11
-20
@@ -19,7 +19,6 @@ const KNOWN_UNSUPPORTED: &[&str] = &[
|
||||
"orf-olympus-em", // Olympus compressed ORF
|
||||
"-he.nef", // Nikon High Efficiency (HE / HE★) NEF, issue #193
|
||||
"sraw", // Canon sRAW / mRAW
|
||||
"sony-r1-sr2", // Sony SR2 (DSC-R1): encrypted raw data, opens from its preview (#535)
|
||||
];
|
||||
|
||||
#[test]
|
||||
@@ -541,21 +540,20 @@ fn corpus_sony_sr2_white_balance() {
|
||||
/// Issue #535: pre-2017 Sony bodies without crop tags shot in the camera's 16:9 mode. The ILCE-7SM2 records the
|
||||
/// 16:9 size in `FullImageSize`, the DSLR-A580 only in the Exif image size; both crops are centred vertically (each
|
||||
/// camera JPEG registered on its raw) and drop the right-edge padding, so the image matches its JPEG's aspect ratio
|
||||
/// and gets a camera look. The DSC-R1's SR2 stores encrypted samples and must open as its preview, not as noise.
|
||||
/// and gets a camera look.
|
||||
#[test]
|
||||
fn corpus_sony_16x9_crops_and_sr2() {
|
||||
fn corpus_sony_16x9_crops() {
|
||||
let dir = corpus_root().join("raw");
|
||||
let read = |name: &str| match std::fs::read(dir.join(name)) {
|
||||
Ok(bytes) => Some(bytes),
|
||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
|
||||
eprintln!("skip: {name} absent");
|
||||
None
|
||||
}
|
||||
Err(e) => panic!("{name}: {e}"),
|
||||
};
|
||||
let mut seen = 0;
|
||||
for (name, (x, y, w, h)) in [("arw-sony-a7sm2-16x9.arw", (0, 232, 4240, 2384)), ("arw-sony-a580-16x9.arw", (0, 260, 4912, 2760))] {
|
||||
let Some(bytes) = read(name) else { continue };
|
||||
let bytes = match std::fs::read(dir.join(name)) {
|
||||
Ok(bytes) => bytes,
|
||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
|
||||
eprintln!("skip: {name} absent");
|
||||
continue;
|
||||
}
|
||||
Err(e) => panic!("{name}: {e}"),
|
||||
};
|
||||
let img = decode(&bytes).unwrap_or_else(|e| panic!("{name}: {e}"));
|
||||
let c = img.crop;
|
||||
assert_eq!((c.x, c.y, c.width, c.height), (x, y, w, h), "{name}: crop");
|
||||
@@ -567,14 +565,7 @@ fn corpus_sony_16x9_crops_and_sr2() {
|
||||
assert!((aspect / preview - 1.0).abs() < 0.02, "{name}: crop aspect {aspect} vs camera JPEG {preview}");
|
||||
seen += 1;
|
||||
}
|
||||
if let Some(bytes) = read("arw-sony-r1-sr2.sr2") {
|
||||
for r in [decode(&bytes).map(|_| ()), probe_info(&bytes).map(|_| ())] {
|
||||
assert!(matches!(r, Err(RawError::Unsupported(ref why)) if why.contains("SR2")), "DSC-R1 SR2: {r:?}");
|
||||
}
|
||||
assert!(embedded_preview(&bytes).is_some(), "DSC-R1 SR2: no preview to open");
|
||||
seen += 1;
|
||||
}
|
||||
eprintln!("Sony 16:9 crops and SR2 checked on {seen} files");
|
||||
eprintln!("Sony 16:9 crops checked on {seen} files");
|
||||
}
|
||||
|
||||
/// The embedded JPEG as linear RGB, reduced to `gw × gh` cells.
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+1
-2
@@ -405,7 +405,7 @@ const RAW_SAMPLES: &[(&str, &str, &str)] = &[
|
||||
"7cc338a0abc8fdad32d61006f1f8f412297b93e040c7efba572eb2bd8f8e8be2",
|
||||
),
|
||||
// pre-2017 bodies in the camera's 16:9 mode (#535): the ILCE-7SM2 records it in FullImageSize, the DSLR-A580 only
|
||||
// in the Exif image size; and the DSC-R1's SR2, whose raw data is encrypted (opens from its preview)
|
||||
// in the Exif image size
|
||||
(
|
||||
"arw-sony-a7sm2-16x9.arw",
|
||||
"https://raw.pixls.us/data/Sony/ILCE-7SM2/DSC01005.ARW",
|
||||
@@ -416,7 +416,6 @@ const RAW_SAMPLES: &[(&str, &str, &str)] = &[
|
||||
"https://raw.pixls.us/data/Sony/DSLR-A580/RAW_SONY_A580.ARW",
|
||||
"5b0924d39151239dce19e92e08318f4f62a5a8ac276bd7180ec8463d2cfee709",
|
||||
),
|
||||
("arw-sony-r1-sr2.sr2", "https://raw.pixls.us/data/Sony/DSC-R1/_DSC1477.SR2", "921c5f2513dd1671a9089e73fc778fc3573bc4cb70a3a555d0414661d0e62274"),
|
||||
// CR2 colour-filter layouts differ by model (issue #85): CR2CFAPattern 3 (GBRG) and 1 (RGGB) samples
|
||||
(
|
||||
"cr2-canon-40d.cr2",
|
||||
|
||||
Reference in New Issue
Block a user