mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-25 10:37:11 -06:00
Decode lossless CRX and version 0x100/0x200 C-RAW in portable Rust, including sensor geometry, timed white balance and black/white levels. Verify all sensor samples against six independent M50/R100/R8 references. Any CR3 decode error falls back to the embedded JPEG, as CR3 files did before this decoder. The full sensor buffer is allocated only after the first plane decodes, and plane buffers grow row by row. Tile planes decode in parallel with a windowed bit reader (24 MP: 424 -> 89 ms lossless, 519 -> 138 ms C-RAW on an M4). Add guarded CR3 camera-JPEG colour fitting with independent framing validation, relative white balance and cache invalidation. Validation: cargo xtask ci.
54 lines
2.5 KiB
Rust
54 lines
2.5 KiB
Rust
use lightcraft_meta::{MergeRules, Metadata, extract, merge_xmp, parse_iptc, parse_xmp, read_exif, write_xmp};
|
|
use proptest::prelude::*;
|
|
|
|
proptest! {
|
|
#![proptest_config(ProptestConfig { cases: 1500, .. ProptestConfig::default() })]
|
|
|
|
#[test]
|
|
fn random_containers_never_panic(mut data in proptest::collection::vec(any::<u8>(), 0..600), kind in 0usize..6) {
|
|
let heads: [&[u8]; 6] = [b"\xff\xd8\xff\xe1", b"\x89PNG\r\n\x1a\n", b"RIFF\0\0\0\0WEBP", b"II*\0", b"MM\0*", b"\0\0\0\x18ftypcrx "];
|
|
let h = heads[kind];
|
|
if data.len() >= h.len() {
|
|
data[..h.len()].copy_from_slice(h);
|
|
}
|
|
let _ = extract(&data);
|
|
let _ = read_exif(&data);
|
|
let _ = parse_iptc(&data);
|
|
}
|
|
|
|
#[test]
|
|
fn random_text_xmp_never_panics(s in ".{0,400}") {
|
|
let _ = parse_xmp(&s);
|
|
}
|
|
|
|
/// Merging into arbitrary (also damaged) packets never panics, and a merge that succeeds
|
|
/// reads back with the written values.
|
|
#[test]
|
|
fn merge_never_panics(cut in 0usize..900, junk in ".{0,20}", rating in 0i8..=5) {
|
|
let base = write_xmp(&Metadata { title: Some("t".into()), rating: Some(1), ..Default::default() }, Some("{}"));
|
|
let mut s: String = base.chars().take(cut).collect();
|
|
s.push_str(&junk);
|
|
s.extend(base.chars().skip(cut));
|
|
let fresh = write_xmp(&Metadata { rating: Some(rating), ..Default::default() }, Some("{\"x\":1}"));
|
|
let rules = MergeRules { owned: &["xmp:Rating", "dc:title", "lc:*"], owned_if_present: &[] };
|
|
if let Ok(out) = merge_xmp(&s, &fresh, rules) {
|
|
let d = parse_xmp(&out).unwrap();
|
|
prop_assert_eq!(d.metadata.rating, Some(rating));
|
|
prop_assert_eq!(d.lc_settings.as_deref(), Some("{\"x\":1}"));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn user_text_roundtrips(title in "[a-zA-Z0-9 &<>\"'éü€;|/\\\\]{1,40}", kw in proptest::collection::vec("[a-zA-Z0-9 &<>\"'|]{1,12}", 0..5), rating in -1i8..=5) {
|
|
let title = title.trim().to_string();
|
|
prop_assume!(!title.is_empty());
|
|
let kw: Vec<String> = kw.into_iter().map(|k| k.trim().to_string()).filter(|k| !k.is_empty()).collect();
|
|
let m = Metadata { title: Some(title.clone()), keywords: kw.clone(), rating: Some(rating), ..Default::default() };
|
|
let d = parse_xmp(&write_xmp(&m, Some(&title))).unwrap();
|
|
prop_assert_eq!(d.metadata.title, Some(title.clone()));
|
|
prop_assert_eq!(d.metadata.keywords, kw);
|
|
prop_assert_eq!(d.metadata.rating, Some(rating));
|
|
prop_assert_eq!(d.lc_settings, Some(title));
|
|
}
|
|
}
|