Put the RAW decoder behind a Decoder trait
FR-RAW-2 says a second decoder may be added for broader camera coverage without changing callers, and D2 names LibRaw as that second decoder. Nothing tested the claim: dr_decode was one decoder reached through free functions, so adding another would have meant editing every caller at the moment there was most pressure not to. Decoder is an object-safe trait over bytes: header_bytes, metadata, orientation, locate_preview, preview and decode. Rawler implements it by delegating to the existing free functions, so behaviour is unchanged, and dr_decode::default() hands it out as a &'static dyn Decoder, which is what the places that start work will name. JPEG recognition, decoding and completeness checks stay free functions: they are not a RAW decoder's to vary. Nothing in the trait takes a path or a SourceRef; the decoder states how much of a file it needs and where its preview sits, and the caller's storage fetches that.
This commit is contained in:
@@ -0,0 +1,120 @@
|
|||||||
|
//! 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,
|
||||||
|
/// and `Send + Sync` because the callers that need one most — the thumbnail
|
||||||
|
/// lanes, the export worker — run off the UI thread.
|
||||||
|
pub trait Decoder: Send + Sync {
|
||||||
|
/// 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<Metadata, DecodeError>;
|
||||||
|
|
||||||
|
/// 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<Orientation>;
|
||||||
|
|
||||||
|
/// 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<PreviewLocation>;
|
||||||
|
|
||||||
|
/// 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<Preview, DecodeError>;
|
||||||
|
|
||||||
|
/// Sensor data, for develop and export. The expensive path.
|
||||||
|
fn decode(&self, bytes: &[u8]) -> Result<RawImage, DecodeError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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<Metadata, DecodeError> {
|
||||||
|
crate::metadata(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn orientation(&self, header: &[u8]) -> Option<Orientation> {
|
||||||
|
crate::orientation(header)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn locate_preview(&self, header: &[u8], file_len: u64) -> Option<PreviewLocation> {
|
||||||
|
crate::locate_preview(header, file_len)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn preview(&self, bytes: &[u8], size: PreviewSize) -> Result<Preview, DecodeError> {
|
||||||
|
crate::extract_preview(bytes, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode(&self, bytes: &[u8]) -> Result<RawImage, DecodeError> {
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,14 +11,20 @@
|
|||||||
//!
|
//!
|
||||||
//! Fusing them would force a full decode where a header read suffices, which
|
//! Fusing them would force a full decode where a header read suffices, which
|
||||||
//! is exactly why Lightroom stalls ~2 s per image during culling.
|
//! is exactly why Lightroom stalls ~2 s per image during culling.
|
||||||
|
//!
|
||||||
|
//! Callers reach these through the [`Decoder`] trait rather than by name, so a
|
||||||
|
//! second decoder can be put behind them without changing any of them
|
||||||
|
//! (FR-RAW-2). [`Rawler`] is the one that ships; [`default`] hands it out.
|
||||||
|
|
||||||
pub mod base_curve;
|
pub mod base_curve;
|
||||||
|
mod decoder;
|
||||||
mod error;
|
mod error;
|
||||||
mod locate;
|
mod locate;
|
||||||
mod preview;
|
mod preview;
|
||||||
pub mod profile;
|
pub mod profile;
|
||||||
|
|
||||||
pub use base_curve::BaseCurve;
|
pub use base_curve::BaseCurve;
|
||||||
|
pub use decoder::{default, Decoder, Rawler};
|
||||||
pub use error::DecodeError;
|
pub use error::DecodeError;
|
||||||
pub use locate::{
|
pub use locate::{
|
||||||
defects, is_complete_jpeg, jpeg_metadata, locate_preview, tiff_metadata, BadLine, BadPixel,
|
defects, is_complete_jpeg, jpeg_metadata, locate_preview, tiff_metadata, BadLine, BadPixel,
|
||||||
|
|||||||
Reference in New Issue
Block a user