filmcraft/crates/dng/tests/oracle_libraw.rs

98 lines
3.8 KiB
Rust

//! External oracle: LibRaw's `unprocessed_raw` sample program (an independent DNG reader) must
//! read the raw samples of our synthetic DNGs exactly as written and as we decode them: packed
//! uncompressed data at every depth in both byte orders, and lossless JPEG with every predictor.
//! Skipped when `unprocessed_raw` is not installed (or `FILMCRAFT_UNPROCESSED_RAW` names it).
//! LibRaw is only run, never linked or read.
use std::path::PathBuf;
use std::process::Command;
use filmcraft_dng::Dng;
use filmcraft_dng::writer::{Compression, Layout, Options, write};
fn tool() -> Option<String> {
let t = std::env::var("FILMCRAFT_UNPROCESSED_RAW").unwrap_or_else(|_| "unprocessed_raw".into());
Command::new(&t).arg("-q").output().ok().map(|_| t)
}
fn picture(w: usize, h: usize, bits: u32, seed: u32) -> Vec<u16> {
let mut s = seed.wrapping_mul(2_654_435_761).wrapping_add(7);
let max = (1u32 << bits) - 1;
(0..w * h)
.map(|i| {
s ^= s << 13;
s ^= s >> 17;
s ^= s << 5;
let (x, y) = (i % w, i / w);
(((x * 97 + y * 41) as u32 % (max + 1)) ^ (s & 15)).min(max) as u16
})
.collect()
}
/// The 16-bit samples of a binary PGM.
fn pgm(b: &[u8]) -> Option<(usize, usize, Vec<u16>)> {
let mut fields = Vec::new();
let mut i = 0;
while fields.len() < 4 {
while b.get(i)?.is_ascii_whitespace() {
i += 1;
}
let start = i;
while !b.get(i)?.is_ascii_whitespace() {
i += 1;
}
fields.push(std::str::from_utf8(&b[start..i]).ok()?.to_string());
}
let (w, h): (usize, usize) = (fields[1].parse().ok()?, fields[2].parse().ok()?);
let data = b.get(i + 1..)?;
Some((w, h, data.as_chunks::<2>().0.iter().map(|c| u16::from_be_bytes(*c)).collect()))
}
#[test]
fn libraw_reads_our_raw_samples() {
let Some(tool) = tool() else {
eprintln!("SKIPPED: unprocessed_raw (LibRaw samples) not installed");
return;
};
let dir: PathBuf = std::env::temp_dir().join(format!("filmcraft-dng-oracle-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let (w, h) = (40, 26);
let mut cases: Vec<(String, Options, u32)> = Vec::new();
for bits in [8, 10, 12, 14, 16] {
for be in [false, true] {
cases.push((
format!("u{bits}{}", if be { "be" } else { "le" }),
Options { width: w, height: h, bits, big_endian: be, layout: Layout::Strips { rows: 7 }, ..Default::default() },
bits,
));
}
}
for predictor in 1..=7 {
for components in [1, 2] {
let o = Options {
width: w,
height: h,
bits: 14,
layout: Layout::Tiles { width: 16, height: 16 },
compression: Compression::LosslessJpeg { predictor, components, restart_lines: 0, pt: 0 },
..Default::default()
};
cases.push((format!("lj{predictor}c{components}"), o, 14));
}
}
for (i, (name, o, bits)) in cases.iter().enumerate() {
let px = picture(w, h, *bits, i as u32);
let f = write(&px, o);
assert_eq!(Dng::parse(&f).unwrap().raw().unwrap().data, px, "{name}: ours");
let path = dir.join(format!("{name}.dng"));
std::fs::write(&path, &f).unwrap();
let out = Command::new(&tool).arg("-q").arg(&path).output().unwrap();
assert!(out.status.success(), "{name}: {}", String::from_utf8_lossy(&out.stderr));
let (pw, ph, theirs) = pgm(&std::fs::read(dir.join(format!("{name}.dng.pgm"))).unwrap()).unwrap();
assert_eq!((pw, ph), (w, h), "{name}");
assert_eq!(theirs, px, "{name}: LibRaw");
eprintln!("{name}: LibRaw agrees");
}
let _ = std::fs::remove_dir_all(&dir);
}