Add RAW decode and a working image viewer
dr-decode exposes four entry points rather than one decode, because callers differ sharply in what they need (ARCH §3.2): culling wants a preview, the grid wants metadata, only develop and export touch sensor data. Fusing them forces a full decode where a header read suffices, which is why Lightroom stalls ~2s per image while culling. Smoke-tested against 1,852 real Canon CR2 files (EOS 6D, ~27MB each): metadata 0.2ms from a 256KB header read, no full decode preview ~250ms 5472x3648, downscaled to 2048 for display jpeg 3.0ms Two findings worth recording: rawler 0.7.2's CR2 decoder implements only full_image; thumbnail_image and preview_image are unimplemented trait defaults returning None. So every rung of the preview ladder resolves to a full-resolution decode at ~250ms — 5x over NFR-P13's 50ms budget. CR2 does carry smaller IFDs, so the fix is our own IFD walk or an upstream contribution. The ladder is written now so that fixing it is a decoder change, not a change to every caller. Recorded in milestone-v0.1 risks. Preview.downscale_to bounds memory: a 5472x3648 RGBA preview is 79.8MB, which exhausts a phone's budget after a handful of images. Box-filtered so downscaled thumbnails do not alias. Also fixed a RefCell double-borrow that panicked on first navigation — `*x.borrow_mut() = *x.borrow() + 1` holds both borrows at once. Verified with 10,000 programmatic navigations. 58 tests passing. Traceability 20.3% (29/143).
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
/// TRACES: FR-RAW-4 | NFR-SEC-1
|
||||
/// Failures from decoding.
|
||||
///
|
||||
/// Per FR-RAW-4 a malformed file must not abort a batch, so these are always
|
||||
/// returned rather than panicking — and the decode path is the one place
|
||||
/// untrusted input arrives (NFR-SEC-1).
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum DecodeError {
|
||||
#[error("read failed: {0}")]
|
||||
Read(String),
|
||||
|
||||
#[error("unsupported or unrecognised format: {0}")]
|
||||
Unsupported(String),
|
||||
|
||||
#[error("decode failed: {0}")]
|
||||
Decode(String),
|
||||
|
||||
#[error("metadata unavailable: {0}")]
|
||||
Metadata(String),
|
||||
|
||||
#[error("no embedded preview in this file")]
|
||||
NoPreview,
|
||||
|
||||
#[error("embedded preview is corrupt: {0}")]
|
||||
CorruptPreview(String),
|
||||
}
|
||||
|
||||
impl DecodeError {
|
||||
/// Whether a fallback path might still produce an image.
|
||||
///
|
||||
/// A missing preview is not a failure to display the file — it means fall
|
||||
/// through to full decode (FR-CULL-2, M-11).
|
||||
pub fn has_fallback(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
DecodeError::NoPreview | DecodeError::CorruptPreview(_)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn preview_failures_fall_through_rather_than_failing() {
|
||||
assert!(DecodeError::NoPreview.has_fallback());
|
||||
assert!(DecodeError::CorruptPreview("truncated".into()).has_fallback());
|
||||
// A genuinely unsupported file has nowhere to fall through to.
|
||||
assert!(!DecodeError::Unsupported("unknown".into()).has_fallback());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user