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::{AsyncTiffResult, TiffError, TiffUnsupportedError};
11use crate::tags::{CompressionMethod, 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<CompressionMethod, 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<CompressionMethod, Box<dyn Decoder>>> for DecoderRegistry {
38    fn as_ref(&self) -> &HashMap<CompressionMethod, Box<dyn Decoder>> {
39        &self.0
40    }
41}
42
43impl AsMut<HashMap<CompressionMethod, Box<dyn Decoder>>> for DecoderRegistry {
44    fn as_mut(&mut self) -> &mut HashMap<CompressionMethod, 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(CompressionMethod::None, Box::new(UncompressedDecoder) as _);
53        registry.insert(CompressionMethod::Deflate, Box::new(DeflateDecoder) as _);
54        registry.insert(CompressionMethod::OldDeflate, Box::new(DeflateDecoder) as _);
55        registry.insert(CompressionMethod::LZW, Box::new(LZWDecoder) as _);
56        registry.insert(CompressionMethod::ModernJPEG, Box::new(JPEGDecoder) as _);
57        registry.insert(CompressionMethod::ZSTD, Box::new(ZstdDecoder) as _);
58        #[cfg(feature = "jpeg2k")]
59        registry.insert(CompressionMethod::JPEG2k, Box::new(JPEG2kDecoder) as _);
60        Self(registry)
61    }
62}
63
64/// A trait to decode a TIFF tile.
65pub trait Decoder: Debug + Send + Sync {
66    /// Decode a TIFF tile.
67    fn decode_tile(
68        &self,
69        buffer: Bytes,
70        photometric_interpretation: PhotometricInterpretation,
71        jpeg_tables: Option<&[u8]>,
72    ) -> AsyncTiffResult<Vec<u8>>;
73}
74
75/// A decoder for the Deflate compression method.
76#[derive(Debug, Clone)]
77pub struct DeflateDecoder;
78
79impl Decoder for DeflateDecoder {
80    fn decode_tile(
81        &self,
82        buffer: Bytes,
83        _photometric_interpretation: PhotometricInterpretation,
84        _jpeg_tables: Option<&[u8]>,
85    ) -> AsyncTiffResult<Vec<u8>> {
86        let mut decoder = ZlibDecoder::new(Cursor::new(buffer));
87        let mut buf = Vec::new();
88        decoder.read_to_end(&mut buf)?;
89        Ok(buf)
90    }
91}
92
93/// A decoder for the JPEG compression method.
94#[derive(Debug, Clone)]
95pub struct JPEGDecoder;
96
97impl Decoder for JPEGDecoder {
98    fn decode_tile(
99        &self,
100        buffer: Bytes,
101        photometric_interpretation: PhotometricInterpretation,
102        jpeg_tables: Option<&[u8]>,
103    ) -> AsyncTiffResult<Vec<u8>> {
104        decode_modern_jpeg(buffer, photometric_interpretation, jpeg_tables)
105    }
106}
107
108/// A decoder for the LZW compression method.
109#[derive(Debug, Clone)]
110pub struct LZWDecoder;
111
112impl Decoder for LZWDecoder {
113    fn decode_tile(
114        &self,
115        buffer: Bytes,
116        _photometric_interpretation: PhotometricInterpretation,
117        _jpeg_tables: Option<&[u8]>,
118    ) -> AsyncTiffResult<Vec<u8>> {
119        // https://github.com/image-rs/image-tiff/blob/90ae5b8e54356a35e266fb24e969aafbcb26e990/src/decoder/stream.rs#L147
120        let mut decoder = weezl::decode::Decoder::with_tiff_size_switch(weezl::BitOrder::Msb, 8);
121        let decoded = decoder.decode(&buffer).expect("failed to decode LZW data");
122        Ok(decoded)
123    }
124}
125
126/// A decoder for the JPEG2000 compression method.
127#[cfg(feature = "jpeg2k")]
128#[derive(Debug, Clone)]
129pub struct JPEG2kDecoder;
130
131#[cfg(feature = "jpeg2k")]
132impl Decoder for JPEG2kDecoder {
133    fn decode_tile(
134        &self,
135        buffer: Bytes,
136        _photometric_interpretation: PhotometricInterpretation,
137        _jpeg_tables: Option<&[u8]>,
138    ) -> AsyncTiffResult<Vec<u8>> {
139        let decoder = jpeg2k::DecodeParameters::new();
140
141        let image = jpeg2k::Image::from_bytes_with(&buffer, decoder)?;
142
143        let id = image.get_pixels(None)?;
144        match id.data {
145            jpeg2k::ImagePixelData::L8(items)
146            | jpeg2k::ImagePixelData::La8(items)
147            | jpeg2k::ImagePixelData::Rgb8(items)
148            | jpeg2k::ImagePixelData::Rgba8(items) => Ok(items),
149            jpeg2k::ImagePixelData::L16(items)
150            | jpeg2k::ImagePixelData::La16(items)
151            | jpeg2k::ImagePixelData::Rgb16(items)
152            | jpeg2k::ImagePixelData::Rgba16(items) => Ok(bytemuck::cast_vec(items)),
153        }
154    }
155}
156
157/// A decoder for uncompressed data.
158#[derive(Debug, Clone)]
159pub struct UncompressedDecoder;
160
161impl Decoder for UncompressedDecoder {
162    fn decode_tile(
163        &self,
164        buffer: Bytes,
165        _photometric_interpretation: PhotometricInterpretation,
166        _jpeg_tables: Option<&[u8]>,
167    ) -> AsyncTiffResult<Vec<u8>> {
168        Ok(buffer.to_vec())
169    }
170}
171
172/// A decoder for the Zstd compression method.
173#[derive(Debug, Clone)]
174pub struct ZstdDecoder;
175
176impl Decoder for ZstdDecoder {
177    fn decode_tile(
178        &self,
179        buffer: Bytes,
180        _photometric_interpretation: PhotometricInterpretation,
181        _jpeg_tables: Option<&[u8]>,
182    ) -> AsyncTiffResult<Vec<u8>> {
183        let mut decoder = zstd::Decoder::new(Cursor::new(buffer))?;
184        let mut buf = Vec::new();
185        decoder.read_to_end(&mut buf)?;
186        Ok(buf)
187    }
188}
189
190// https://github.com/image-rs/image-tiff/blob/3bfb43e83e31b0da476832067ada68a82b378b7b/src/decoder/image.rs#L389-L450
191fn decode_modern_jpeg(
192    buf: Bytes,
193    photometric_interpretation: PhotometricInterpretation,
194    jpeg_tables: Option<&[u8]>,
195) -> AsyncTiffResult<Vec<u8>> {
196    // Construct new jpeg_reader wrapping a SmartReader.
197    //
198    // JPEG compression in TIFF allows saving quantization and/or huffman tables in one central
199    // location. These `jpeg_tables` are simply prepended to the remaining jpeg image data. Because
200    // these `jpeg_tables` start with a `SOI` (HEX: `0xFFD8`) or __start of image__ marker which is
201    // also at the beginning of the remaining JPEG image data and would confuse the JPEG renderer,
202    // one of these has to be taken off. In this case the first two bytes of the remaining JPEG
203    // data is removed because it follows `jpeg_tables`. Similary, `jpeg_tables` ends with a `EOI`
204    // (HEX: `0xFFD9`) or __end of image__ marker, this has to be removed as well (last two bytes
205    // of `jpeg_tables`).
206    let reader = Cursor::new(buf);
207
208    let jpeg_reader = match jpeg_tables {
209        Some(jpeg_tables) => {
210            let mut reader = reader;
211            reader.read_exact(&mut [0; 2])?;
212
213            Box::new(Cursor::new(&jpeg_tables[..jpeg_tables.len() - 2]).chain(reader))
214                as Box<dyn Read>
215        }
216        None => Box::new(reader),
217    };
218
219    let mut decoder = jpeg::Decoder::new(jpeg_reader);
220
221    match photometric_interpretation {
222        PhotometricInterpretation::RGB => decoder.set_color_transform(jpeg::ColorTransform::RGB),
223        PhotometricInterpretation::WhiteIsZero
224        | PhotometricInterpretation::BlackIsZero
225        | PhotometricInterpretation::TransparencyMask => {
226            decoder.set_color_transform(jpeg::ColorTransform::None)
227        }
228        PhotometricInterpretation::CMYK => decoder.set_color_transform(jpeg::ColorTransform::CMYK),
229        PhotometricInterpretation::YCbCr => {
230            decoder.set_color_transform(jpeg::ColorTransform::YCbCr)
231        }
232        photometric_interpretation => {
233            return Err(TiffError::UnsupportedError(
234                TiffUnsupportedError::UnsupportedInterpretation(photometric_interpretation),
235            )
236            .into());
237        }
238    }
239
240    let data = decoder.decode()?;
241    Ok(data)
242}