diff --git a/crates/raw/Cargo.toml b/crates/raw/Cargo.toml index eff61537..d02c7e28 100644 --- a/crates/raw/Cargo.toml +++ b/crates/raw/Cargo.toml @@ -17,13 +17,14 @@ corpus = [] [dependencies] # Adobe DNG compression 8: zlib (RFC 1950) with the TIFF predictor (pure-Rust backend). flate2 = { workspace = true } -# JPEG XL-compressed DNG (DNG 1.7, e.g. Samsung Expert RAW). Pure Rust, MIT OR Apache-2.0. Pinned -# exactly: it parses untrusted files; moving the pin is a reviewed change (re-run the raw fuzz target). -jxl-oxide = { version = "=0.12.6", default-features = false } # Parallel bands on native; wasm builds stay single-threaded. [target.'cfg(not(target_arch = "wasm32"))'.dependencies] rayon = { workspace = true } +# JPEG XL-compressed DNG (DNG 1.7, e.g. Samsung Expert RAW). Pure Rust, MIT OR Apache-2.0. Pinned +# exactly: it parses untrusted files; moving the pin is a reviewed change (re-run the raw fuzz target). +# Native only: it added 0.88 MB to the browser build's wasm, which has a hard size cap +# (packaging/web/package.sh); there JPEG XL DNG is reported as unsupported (src/jxl.rs). jxl-oxide = { version = "=0.12.6", default-features = false, features = ["rayon"] } [dev-dependencies] diff --git a/crates/raw/README.md b/crates/raw/README.md index c2afe3a3..2f85f5e8 100644 --- a/crates/raw/README.md +++ b/crates/raw/README.md @@ -2,7 +2,7 @@ A clean-room, pure-Rust camera raw decoder and developer. The crate is standalone (no workspace dependencies; its decoding dependencies are `flate2` for Deflate DNG and `jxl-oxide`, a pure-Rust -JPEG XL decoder, for DNG 1.7), has no `unsafe`, does no I/O (`&[u8]` in), builds for `wasm32-unknown-unknown` +JPEG XL decoder, for DNG 1.7, native builds only), has no `unsafe`, does no I/O (`&[u8]` in), builds for `wasm32-unknown-unknown` (sequential there, rayon-parallel on native) and never panics on hostile input: every offset is bounds-checked and sizes are checked against `Limits` before allocating. @@ -58,7 +58,7 @@ camera colour tables were copied. | Format | Status | |---|---| -| DNG | Uncompressed (8–16 bit, packed or not), Deflate (compression 8, predictors 1 and 2), lossless JPEG and JPEG XL (DNG 1.7, e.g. Samsung Expert RAW; decoded by `jxl-oxide`); strips and tiles; CFA (Bayer) and LinearRaw; LinearizationTable, BlackLevel (+ repeat, DeltaH/V), WhiteLevel, ActiveArea, DefaultCrop, ColorMatrix1/2, CameraCalibration, ForwardMatrix, AnalogBalance, AsShotNeutral / AsShotWhiteXY, BaselineExposure, Orientation, OpcodeList2 GainMap (lens shading) | +| DNG | Uncompressed (8–16 bit, packed or not), Deflate (compression 8, predictors 1 and 2), lossless JPEG and JPEG XL (DNG 1.7, e.g. Samsung Expert RAW; decoded by `jxl-oxide`; not in wasm32 builds, where it is `Unsupported`); strips and tiles; CFA (Bayer) and LinearRaw; LinearizationTable, BlackLevel (+ repeat, DeltaH/V), WhiteLevel, ActiveArea, DefaultCrop, ColorMatrix1/2, CameraCalibration, ForwardMatrix, AnalogBalance, AsShotNeutral / AsShotWhiteXY, BaselineExposure, Orientation, OpcodeList2 GainMap (lens shading) | | DNG (lossy JPEG, floating point; opcodes other than GainMap; ProfileGainTableMap) | Unsupported / not applied (reported) | | CR2 | Lossless JPEG with slices, borders and as-shot white balance from the maker note, black measured on the masked border. CR2 has no CFA tag and the row phase varies by model, so it is measured from the data (the green diagonal), with a Canon model-ID table as the fallback (see `src/cr2.rs`) | | CR2 sRAW / mRAW | Unsupported | diff --git a/crates/raw/src/jxl.rs b/crates/raw/src/jxl.rs index 3522ed5d..0717dbb3 100644 --- a/crates/raw/src/jxl.rs +++ b/crates/raw/src/jxl.rs @@ -5,14 +5,26 @@ //! Expert RAW writes LinearRaw this way. Decoding is done by `jxl-oxide`, a pure-Rust decoder; //! like heic-rs in `photocraft-heif`, every call into it runs under `catch_unwind` so a decoder //! panic on a malformed file becomes [`RawError::Malformed`]. +//! +//! The browser build (wasm32) leaves the decoder out: jxl-oxide added 0.88 MB to a wasm that +//! must stay under the per-file cap of static hosts (packaging/web/package.sh), for a format only +//! a few phones write. There, these segments are [`RawError::Unsupported`] with a clear message. +#[cfg(not(target_arch = "wasm32"))] use jxl_oxide::{AllocTracker, JxlImage}; use crate::Limits; use crate::error::{RawError, Result}; +/// The browser build has no JPEG XL decoder (see the module docs). +#[cfg(target_arch = "wasm32")] +pub(crate) fn decode(_src: &[u8], _w: usize, _h: usize, _samples: usize, _bits: u32, _limits: &Limits) -> Result> { + Err(RawError::unsupported("JPEG XL-compressed DNG (DNG 1.7) can't be opened in the browser version of PhotoCraft; open it in the desktop app")) +} + /// Decodes one segment into `w * h * samples` interleaved samples at `bits` per sample (the /// TIFF BitsPerSample, which DNG requires the codestream's bit depth to match). +#[cfg(not(target_arch = "wasm32"))] pub(crate) fn decode(src: &[u8], w: usize, h: usize, samples: usize, bits: u32, limits: &Limits) -> Result> { limits.check(w as u64, h as u64, samples as u64 * 2)?; // jxl-oxide decodes into f32/i32 planes: allow a few times the output size, and never more @@ -54,6 +66,7 @@ pub(crate) fn decode(src: &[u8], w: usize, h: usize, samples: usize, bits: u32, }) } +#[cfg(not(target_arch = "wasm32"))] fn guarded(f: impl FnOnce() -> Result) -> Result { std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)).unwrap_or_else(|_| Err(RawError::malformed("the JPEG XL decoder failed on this file"))) }