Skip to main content

async_tiff/
decoder.rs

1//! Decoders for different TIFF compression methods.
2
3use std::collections::HashMap;
4use std::fmt::Debug;
5use std::io::{Cursor, Read};
6
7use bytes::Bytes;
8use flate2::bufread::ZlibDecoder;
9
10use crate::error::{AsyncTiffError, AsyncTiffResult, TiffError, TiffUnsupportedError};
11use crate::tags::{Compression, PhotometricInterpretation};
12
13/// A registry of decoders.
14///
15/// This allows end users to register their own decoders, for custom compression methods, or
16/// override the default decoder implementations.
17///
18/// ```
19/// use async_tiff::decoder::DecoderRegistry;
20///
21/// // Default registry includes Deflate, LZW, JPEG, ZSTD.
22/// let registry = DecoderRegistry::default();
23///
24/// // Empty registry for manual configuration.
25/// let empty = DecoderRegistry::empty();
26/// ```
27#[derive(Debug)]
28pub struct DecoderRegistry(HashMap<Compression, Box<dyn Decoder>>);
29
30impl DecoderRegistry {
31    /// Create a new decoder registry with no decoders registered
32    pub fn empty() -> Self {
33        Self(HashMap::new())
34    }
35}
36
37impl AsRef<HashMap<Compression, Box<dyn Decoder>>> for DecoderRegistry {
38    fn as_ref(&self) -> &HashMap<Compression, Box<dyn Decoder>> {
39        &self.0
40    }
41}
42
43impl AsMut<HashMap<Compression, Box<dyn Decoder>>> for DecoderRegistry {
44    fn as_mut(&mut self) -> &mut HashMap<Compression, Box<dyn Decoder>> {
45        &mut self.0
46    }
47}
48
49impl Default for DecoderRegistry {
50    fn default() -> Self {
51        let mut registry = HashMap::with_capacity(6);
52        registry.insert(Compression::None, Box::new(UncompressedDecoder) as _);
53        registry.insert(Compression::Deflate, Box::new(DeflateDecoder) as _);
54        registry.insert(Compression::OldDeflate, Box::new(DeflateDecoder) as _);
55        #[cfg(feature = "lerc")]
56        registry.insert(Compression::LERC, Box::new(LercDecoder) as _);
57        #[cfg(feature = "lzma")]
58        registry.insert(Compression::LZMA, Box::new(LZMADecoder) as _);
59        registry.insert(Compression::LZW, Box::new(LZWDecoder) as _);
60        registry.insert(Compression::ModernJPEG, Box::new(JPEGDecoder) as _);
61        #[cfg(feature = "jpeg2k")]
62        registry.insert(Compression::JPEG2k, Box::new(JPEG2kDecoder) as _);
63        #[cfg(feature = "webp")]
64        registry.insert(Compression::WebP, Box::new(WebPDecoder) as _);
65        registry.insert(Compression::ZSTD, Box::new(ZstdDecoder) as _);
66        Self(registry)
67    }
68}
69
70/// A trait to decode a TIFF tile.
71pub trait Decoder: Debug + Send + Sync {
72    /// Decode a TIFF tile.
73    fn decode_tile(
74        &self,
75        buffer: Bytes,
76        photometric_interpretation: PhotometricInterpretation,
77        jpeg_tables: Option<&[u8]>,
78        samples_per_pixel: u16,
79        bits_per_sample: u16,
80        lerc_parameters: Option<&[u32]>,
81    ) -> AsyncTiffResult<Vec<u8>>;
82}
83
84/// A decoder for the Deflate compression method.
85#[derive(Debug, Clone)]
86pub struct DeflateDecoder;
87
88impl Decoder for DeflateDecoder {
89    fn decode_tile(
90        &self,
91        buffer: Bytes,
92        _photometric_interpretation: PhotometricInterpretation,
93        _jpeg_tables: Option<&[u8]>,
94        _samples_per_pixel: u16,
95        _bits_per_sample: u16,
96        _lerc_parameters: Option<&[u32]>,
97    ) -> AsyncTiffResult<Vec<u8>> {
98        let mut decoder = ZlibDecoder::new(Cursor::new(buffer));
99        let mut buf = Vec::new();
100        decoder.read_to_end(&mut buf)?;
101        Ok(buf)
102    }
103}
104
105/// A decoder for the JPEG compression method.
106#[derive(Debug, Clone)]
107pub struct JPEGDecoder;
108
109impl Decoder for JPEGDecoder {
110    fn decode_tile(
111        &self,
112        buffer: Bytes,
113        photometric_interpretation: PhotometricInterpretation,
114        jpeg_tables: Option<&[u8]>,
115        _samples_per_pixel: u16,
116        _bits_per_sample: u16,
117        _lerc_parameters: Option<&[u32]>,
118    ) -> AsyncTiffResult<Vec<u8>> {
119        decode_modern_jpeg(buffer, photometric_interpretation, jpeg_tables)
120    }
121}
122
123/// A decoder for the LERC compression method.
124#[cfg(feature = "lerc")]
125#[derive(Debug, Clone)]
126pub struct LercDecoder;
127
128/// Helper to decode and convert to bytes
129#[cfg(feature = "lerc")]
130fn decode_lerc<T: lerc::LercDataType + bytemuck::Pod>(
131    buffer: &[u8],
132    info: &lerc::BlobInfo,
133) -> AsyncTiffResult<Vec<u8>> {
134    let (data, _mask) = lerc::decode::<T>(
135        buffer,
136        info.width as usize,
137        info.height as usize,
138        info.depth as usize,
139        info.bands as usize,
140        info.masks as usize,
141    )
142    .map_err(|e| AsyncTiffError::General(format!("LERC decode failed: {e}")))?;
143
144    // TODO: in the future we could avoid this copy by allowing the return type of the decoder to
145    // be a typed array, not just Vec<u8>
146    Ok(bytemuck::cast_slice(&data).to_vec())
147}
148
149#[cfg(feature = "lerc")]
150impl Decoder for LercDecoder {
151    fn decode_tile(
152        &self,
153        buffer: Bytes,
154        _photometric_interpretation: PhotometricInterpretation,
155        _jpeg_tables: Option<&[u8]>,
156        _samples_per_pixel: u16,
157        _bits_per_sample: u16,
158        lerc_parameters: Option<&[u32]>,
159    ) -> AsyncTiffResult<Vec<u8>> {
160        // LercParameters[1] is the inner compression type:
161        //   0 = none, 1 = deflate, 2 = zstd
162        // Decompress the outer wrapper before passing to the LERC decoder.
163        let lerc_blob: Vec<u8> = match lerc_parameters.and_then(|p| p.get(1).copied()) {
164            Some(1) => {
165                let mut decoder = ZlibDecoder::new(Cursor::new(buffer));
166                let mut buf = Vec::new();
167                decoder.read_to_end(&mut buf)?;
168                buf
169            }
170            Some(2) => {
171                let mut decoder = zstd::Decoder::new(Cursor::new(buffer))?;
172                let mut buf = Vec::new();
173                decoder.read_to_end(&mut buf)?;
174                buf
175            }
176            _ => buffer.to_vec(),
177        };
178
179        let info = lerc::get_blob_info(&lerc_blob)
180            .map_err(|e| AsyncTiffError::General(format!("LERC get_blob_info failed: {e}")))?;
181
182        // LERC data_type mapping (from LERC C API):
183        // 0=i8, 1=u8, 2=i16, 3=u16, 4=i32, 5=u32, 6=f32, 7=f64
184        match info.data_type {
185            0 => decode_lerc::<i8>(&lerc_blob, &info),
186            1 => decode_lerc::<u8>(&lerc_blob, &info),
187            2 => decode_lerc::<i16>(&lerc_blob, &info),
188            3 => decode_lerc::<u16>(&lerc_blob, &info),
189            4 => decode_lerc::<i32>(&lerc_blob, &info),
190            5 => decode_lerc::<u32>(&lerc_blob, &info),
191            6 => decode_lerc::<f32>(&lerc_blob, &info),
192            7 => decode_lerc::<f64>(&lerc_blob, &info),
193            _ => Err(AsyncTiffError::General(format!(
194                "Unsupported LERC data type: {}",
195                info.data_type
196            ))),
197        }
198    }
199}
200
201/// A decoder for the LZMA compression method.
202#[derive(Debug, Clone)]
203#[cfg(feature = "lzma")]
204pub struct LZMADecoder;
205
206#[cfg(feature = "lzma")]
207impl Decoder for LZMADecoder {
208    fn decode_tile(
209        &self,
210        buffer: Bytes,
211        _photometric_interpretation: PhotometricInterpretation,
212        _jpeg_tables: Option<&[u8]>,
213        _samples_per_pixel: u16,
214        _bits_per_sample: u16,
215        _lerc_parameters: Option<&[u32]>,
216    ) -> AsyncTiffResult<Vec<u8>> {
217        use bytes::Buf;
218        use lzma_rust2::XzReader;
219
220        let mut reader = XzReader::new(buffer.reader(), false);
221        let mut out = Vec::new();
222        reader.read_to_end(&mut out)?;
223        Ok(out)
224    }
225}
226
227/// A decoder for the LZW compression method.
228#[derive(Debug, Clone)]
229pub struct LZWDecoder;
230
231impl Decoder for LZWDecoder {
232    fn decode_tile(
233        &self,
234        buffer: Bytes,
235        _photometric_interpretation: PhotometricInterpretation,
236        _jpeg_tables: Option<&[u8]>,
237        _samples_per_pixel: u16,
238        _bits_per_sample: u16,
239        _lerc_parameters: Option<&[u32]>,
240    ) -> AsyncTiffResult<Vec<u8>> {
241        // https://github.com/image-rs/image-tiff/blob/90ae5b8e54356a35e266fb24e969aafbcb26e990/src/decoder/stream.rs#L147
242        let mut decoder = weezl::decode::Decoder::with_tiff_size_switch(weezl::BitOrder::Msb, 8);
243        let decoded = decoder.decode(&buffer).expect("failed to decode LZW data");
244        Ok(decoded)
245    }
246}
247
248/// A decoder for the JPEG2000 compression method.
249#[cfg(feature = "jpeg2k")]
250#[derive(Debug, Clone)]
251pub struct JPEG2kDecoder;
252
253#[cfg(feature = "jpeg2k")]
254impl Decoder for JPEG2kDecoder {
255    fn decode_tile(
256        &self,
257        buffer: Bytes,
258        _photometric_interpretation: PhotometricInterpretation,
259        _jpeg_tables: Option<&[u8]>,
260        _samples_per_pixel: u16,
261        _bits_per_sample: u16,
262        _lerc_parameters: Option<&[u32]>,
263    ) -> AsyncTiffResult<Vec<u8>> {
264        let decoder = jpeg2k::DecodeParameters::new();
265
266        let image = jpeg2k::Image::from_bytes_with(&buffer, decoder)?;
267
268        let id = image.get_pixels(None)?;
269        match id.data {
270            jpeg2k::ImagePixelData::L8(items)
271            | jpeg2k::ImagePixelData::La8(items)
272            | jpeg2k::ImagePixelData::Rgb8(items)
273            | jpeg2k::ImagePixelData::Rgba8(items) => Ok(items),
274            jpeg2k::ImagePixelData::L16(items)
275            | jpeg2k::ImagePixelData::La16(items)
276            | jpeg2k::ImagePixelData::Rgb16(items)
277            | jpeg2k::ImagePixelData::Rgba16(items) => Ok(bytemuck::cast_vec(items)),
278        }
279    }
280}
281
282/// A decoder for the WebP compression method.
283#[cfg(feature = "webp")]
284#[derive(Debug, Clone)]
285pub struct WebPDecoder;
286
287#[cfg(feature = "webp")]
288impl Decoder for WebPDecoder {
289    fn decode_tile(
290        &self,
291        buffer: Bytes,
292        _photometric_interpretation: PhotometricInterpretation,
293        _jpeg_tables: Option<&[u8]>,
294        samples_per_pixel: u16,
295        bits_per_sample: u16,
296        _lerc_parameters: Option<&[u32]>,
297    ) -> AsyncTiffResult<Vec<u8>> {
298        let decoded = webp::Decoder::new(&buffer)
299            .decode()
300            .ok_or(AsyncTiffError::General("WebP decoding failed".to_string()))?;
301
302        let data = decoded.to_vec();
303
304        // WebP lossy compression may discard fully-opaque alpha channels.
305        // If the TIFF expects 4 samples but WebP decoded to 3, expand RGB to RGBA.
306        // Only do this for 8-bit data since WebP only supports 8-bit.
307        if samples_per_pixel == 4 && bits_per_sample == 8 && !decoded.is_alpha() {
308            let mut rgba = Vec::with_capacity(data.len() / 3 * 4);
309            for chunk in data.chunks_exact(3) {
310                rgba.extend_from_slice(chunk);
311                rgba.push(255); // opaque alpha
312            }
313            Ok(rgba)
314        } else {
315            Ok(data)
316        }
317    }
318}
319
320/// A decoder for uncompressed data.
321#[derive(Debug, Clone)]
322pub struct UncompressedDecoder;
323
324impl Decoder for UncompressedDecoder {
325    fn decode_tile(
326        &self,
327        buffer: Bytes,
328        _photometric_interpretation: PhotometricInterpretation,
329        _jpeg_tables: Option<&[u8]>,
330        _samples_per_pixel: u16,
331        _bits_per_sample: u16,
332        _lerc_parameters: Option<&[u32]>,
333    ) -> AsyncTiffResult<Vec<u8>> {
334        Ok(buffer.to_vec())
335    }
336}
337
338/// A decoder for the Zstd compression method.
339#[derive(Debug, Clone)]
340pub struct ZstdDecoder;
341
342impl Decoder for ZstdDecoder {
343    fn decode_tile(
344        &self,
345        buffer: Bytes,
346        _photometric_interpretation: PhotometricInterpretation,
347        _jpeg_tables: Option<&[u8]>,
348        _samples_per_pixel: u16,
349        _bits_per_sample: u16,
350        _lerc_parameters: Option<&[u32]>,
351    ) -> AsyncTiffResult<Vec<u8>> {
352        let mut decoder = zstd::Decoder::new(Cursor::new(buffer))?;
353        let mut buf = Vec::new();
354        decoder.read_to_end(&mut buf)?;
355        Ok(buf)
356    }
357}
358
359// https://github.com/image-rs/image-tiff/blob/3bfb43e83e31b0da476832067ada68a82b378b7b/src/decoder/image.rs#L389-L450
360fn decode_modern_jpeg(
361    buf: Bytes,
362    photometric_interpretation: PhotometricInterpretation,
363    jpeg_tables: Option<&[u8]>,
364) -> AsyncTiffResult<Vec<u8>> {
365    // Construct new jpeg_reader wrapping a SmartReader.
366    //
367    // JPEG compression in TIFF allows saving quantization and/or huffman tables in one central
368    // location. These `jpeg_tables` are simply prepended to the remaining jpeg image data. Because
369    // these `jpeg_tables` start with a `SOI` (HEX: `0xFFD8`) or __start of image__ marker which is
370    // also at the beginning of the remaining JPEG image data and would confuse the JPEG renderer,
371    // one of these has to be taken off. In this case the first two bytes of the remaining JPEG
372    // data is removed because it follows `jpeg_tables`. Similary, `jpeg_tables` ends with a `EOI`
373    // (HEX: `0xFFD9`) or __end of image__ marker, this has to be removed as well (last two bytes
374    // of `jpeg_tables`).
375    let reader = Cursor::new(buf);
376
377    let jpeg_reader = match jpeg_tables {
378        Some(jpeg_tables) => {
379            let mut reader = reader;
380            reader.read_exact(&mut [0; 2])?;
381
382            Box::new(Cursor::new(&jpeg_tables[..jpeg_tables.len() - 2]).chain(reader))
383                as Box<dyn Read>
384        }
385        None => Box::new(reader),
386    };
387
388    let mut decoder = jpeg::Decoder::new(jpeg_reader);
389
390    match photometric_interpretation {
391        PhotometricInterpretation::RGB => decoder.set_color_transform(jpeg::ColorTransform::RGB),
392        PhotometricInterpretation::WhiteIsZero
393        | PhotometricInterpretation::BlackIsZero
394        | PhotometricInterpretation::TransparencyMask => {
395            decoder.set_color_transform(jpeg::ColorTransform::None)
396        }
397        PhotometricInterpretation::CMYK => decoder.set_color_transform(jpeg::ColorTransform::CMYK),
398        PhotometricInterpretation::YCbCr => {
399            decoder.set_color_transform(jpeg::ColorTransform::YCbCr)
400        }
401        photometric_interpretation => {
402            return Err(TiffError::UnsupportedError(
403                TiffUnsupportedError::UnsupportedInterpretation(photometric_interpretation),
404            )
405            .into());
406        }
407    }
408
409    let data = decoder.decode()?;
410    Ok(data)
411}