//! TRACES: FR-RAW-2 //! The seam a second decoder plugs into. //! //! D2 keeps LibRaw as the fallback for bodies rawler does not cover. Adding //! it later should be a new `impl Decoder`, not an edit to every caller that //! reads a header, cuts a thumbnail or opens a photograph for export — which //! is what the free functions alone would have made it. So the callers take a //! `&dyn Decoder`, and only the places that start a job name [`default`]. //! //! Bytes in, always. Nothing here takes a path or a `SourceRef`: resolving a //! file to bytes is `Storage`'s job at the caller, so the same decoder serves a //! local file, an Android document and a range fetched from Nextcloud. The //! decoder's part in that is to say how much of a file it needs //! ([`Decoder::header_bytes`]) and where its preview sits //! ([`Decoder::locate_preview`]); the storage layer fetches exactly that. //! //! What stays a free function is what is not a decoder's to vary: recognising //! a JPEG ([`crate::probe`]), decoding one ([`crate::decode_jpeg`]) and //! checking one is whole ([`crate::is_complete_jpeg`]). A second RAW decoder //! would not read a JPEG differently. use dr_types::Orientation; use crate::{DecodeError, Metadata, Preview, PreviewLocation, PreviewSize, RawImage}; /// TRACES: FR-RAW-2 /// A RAW decoder, over bytes. /// /// Object-safe so a caller can hold `&dyn Decoder` without becoming generic, /// `Send + Sync` because the callers that need one most — the thumbnail /// lanes, the export worker — run off the UI thread, and `Debug` so a job /// description that carries one can still be printed. pub trait Decoder: Send + Sync + std::fmt::Debug { /// How much of the start of a file [`Self::metadata`] and /// [`Self::locate_preview`] need. A caller reading over a network fetches /// this range and no more. fn header_bytes(&self) -> u64; /// Capture metadata, from a header or a whole file, without touching /// sensor data. fn metadata(&self, bytes: &[u8]) -> Result; /// How the stored pixels are turned, from a header. `None` where the file /// does not say, which callers take as upright. fn orientation(&self, header: &[u8]) -> Option; /// Where the embedded preview best suited to a thumbnail sits in the file, /// from its header, so a remote caller can fetch that range alone. fn locate_preview(&self, header: &[u8], file_len: u64) -> Option; /// The embedded preview at the size asked for, falling through the ladder /// to the next size where the file lacks it. fn preview(&self, bytes: &[u8], size: PreviewSize) -> Result; /// Sensor data, for develop and export. The expensive path. fn decode(&self, bytes: &[u8]) -> Result; } /// TRACES: FR-RAW-2 /// The decoder the application ships: rawler for sensor data and the /// previews it knows, DarkRoom's own container walk for headers and ranges. /// /// Its methods are the crate's free functions, unchanged. They stay public /// for the tools and examples that read one file and have no caller to keep /// decoder-agnostic. #[derive(Debug, Clone, Copy, Default)] pub struct Rawler; impl Decoder for Rawler { fn header_bytes(&self) -> u64 { crate::HEADER_BYTES } fn metadata(&self, bytes: &[u8]) -> Result { crate::metadata(bytes) } fn orientation(&self, header: &[u8]) -> Option { crate::orientation(header) } fn locate_preview(&self, header: &[u8], file_len: u64) -> Option { crate::locate_preview(header, file_len) } fn preview(&self, bytes: &[u8], size: PreviewSize) -> Result { crate::extract_preview(bytes, size) } fn decode(&self, bytes: &[u8]) -> Result { crate::decode(bytes) } } /// TRACES: FR-RAW-2 /// The decoder a job uses unless it was handed another. /// /// Named by the places that start work — a thread, a UI handler — and by /// nothing below them. Returning `&'static dyn Decoder` rather than `Rawler` /// is the point: a caller that only has this cannot reach past the trait. pub fn default() -> &'static dyn Decoder { static RAWLER: Rawler = Rawler; &RAWLER } #[cfg(test)] mod tests { use super::*; /// The default is the shipped decoder, reached through the trait: same /// header budget, and the same answer to bytes neither can read. #[test] fn the_default_is_rawler_behind_the_trait() { let d = default(); assert_eq!(d.header_bytes(), crate::HEADER_BYTES); let junk = [0u8; 64]; assert_eq!(d.metadata(&junk).is_err(), crate::metadata(&junk).is_err()); assert!(d.decode(&junk).is_err()); assert_eq!(d.orientation(&junk), crate::orientation(&junk)); } }