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;
11use crate::tiff::tags::{CompressionMethod, PhotometricInterpretation};
12use crate::tiff::{TiffError, TiffUnsupportedError};
13
14/// A registry of decoders.
15///
16/// This allows end users to register their own decoders, for custom compression methods, or
17/// override the default decoder implementations.
18#[derive(Debug)]
19pub struct DecoderRegistry(HashMap<CompressionMethod, Box<dyn Decoder>>);
20
21impl DecoderRegistry {
22    /// Create a new decoder registry with no decoders registered
23    pub fn new() -> Self {
24        Self(HashMap::new())
25    }
26}
27
28impl AsRef<HashMap<CompressionMethod, Box<dyn Decoder>>> for DecoderRegistry {
29    fn as_ref(&self) -> &HashMap<CompressionMethod, Box<dyn Decoder>> {
30        &self.0
31    }
32}
33
34impl AsMut<HashMap<CompressionMethod, Box<dyn Decoder>>> for DecoderRegistry {
35    fn as_mut(&mut self) -> &mut HashMap<CompressionMethod, Box<dyn Decoder>> {
36        &mut self.0
37    }
38}
39
40impl Default for DecoderRegistry {
41    fn default() -> Self {
42        let mut registry = HashMap::with_capacity(5);
43        registry.insert(CompressionMethod::None, Box::new(UncompressedDecoder) as _);
44        registry.insert(CompressionMethod::Deflate, Box::new(DeflateDecoder) as _);
45        registry.insert(CompressionMethod::OldDeflate, Box::new(DeflateDecoder) as _);
46        registry.insert(CompressionMethod::LZW, Box::new(LZWDecoder) as _);
47        registry.insert(CompressionMethod::ModernJPEG, Box::new(JPEGDecoder) as _);
48        Self(registry)
49    }
50}
51
52/// A trait to decode a TIFF tile.
53pub trait Decoder: Debug + Send + Sync {
54    /// Decode a TIFF tile.
55    fn decode_tile(
56        &self,
57        buffer: Bytes,
58        photometric_interpretation: PhotometricInterpretation,
59        jpeg_tables: Option<&[u8]>,
60    ) -> AsyncTiffResult<Bytes>;
61}
62
63/// A decoder for the Deflate compression method.
64#[derive(Debug, Clone)]
65pub struct DeflateDecoder;
66
67impl Decoder for DeflateDecoder {
68    fn decode_tile(
69        &self,
70        buffer: Bytes,
71        _photometric_interpretation: PhotometricInterpretation,
72        _jpeg_tables: Option<&[u8]>,
73    ) -> AsyncTiffResult<Bytes> {
74        let mut decoder = ZlibDecoder::new(Cursor::new(buffer));
75        let mut buf = Vec::new();
76        decoder.read_to_end(&mut buf)?;
77        Ok(buf.into())
78    }
79}
80
81/// A decoder for the JPEG compression method.
82#[derive(Debug, Clone)]
83pub struct JPEGDecoder;
84
85impl Decoder for JPEGDecoder {
86    fn decode_tile(
87        &self,
88        buffer: Bytes,
89        photometric_interpretation: PhotometricInterpretation,
90        jpeg_tables: Option<&[u8]>,
91    ) -> AsyncTiffResult<Bytes> {
92        decode_modern_jpeg(buffer, photometric_interpretation, jpeg_tables)
93    }
94}
95
96/// A decoder for the LZW compression method.
97#[derive(Debug, Clone)]
98pub struct LZWDecoder;
99
100impl Decoder for LZWDecoder {
101    fn decode_tile(
102        &self,
103        buffer: Bytes,
104        _photometric_interpretation: PhotometricInterpretation,
105        _jpeg_tables: Option<&[u8]>,
106    ) -> AsyncTiffResult<Bytes> {
107        // https://github.com/image-rs/image-tiff/blob/90ae5b8e54356a35e266fb24e969aafbcb26e990/src/decoder/stream.rs#L147
108        let mut decoder = weezl::decode::Decoder::with_tiff_size_switch(weezl::BitOrder::Msb, 8);
109        let decoded = decoder.decode(&buffer).expect("failed to decode LZW data");
110        Ok(decoded.into())
111    }
112}
113
114/// A decoder for uncompressed data.
115#[derive(Debug, Clone)]
116pub struct UncompressedDecoder;
117
118impl Decoder for UncompressedDecoder {
119    fn decode_tile(
120        &self,
121        buffer: Bytes,
122        _photometric_interpretation: PhotometricInterpretation,
123        _jpeg_tables: Option<&[u8]>,
124    ) -> AsyncTiffResult<Bytes> {
125        Ok(buffer)
126    }
127}
128
129// https://github.com/image-rs/image-tiff/blob/3bfb43e83e31b0da476832067ada68a82b378b7b/src/decoder/image.rs#L389-L450
130fn decode_modern_jpeg(
131    buf: Bytes,
132    photometric_interpretation: PhotometricInterpretation,
133    jpeg_tables: Option<&[u8]>,
134) -> AsyncTiffResult<Bytes> {
135    // Construct new jpeg_reader wrapping a SmartReader.
136    //
137    // JPEG compression in TIFF allows saving quantization and/or huffman tables in one central
138    // location. These `jpeg_tables` are simply prepended to the remaining jpeg image data. Because
139    // these `jpeg_tables` start with a `SOI` (HEX: `0xFFD8`) or __start of image__ marker which is
140    // also at the beginning of the remaining JPEG image data and would confuse the JPEG renderer,
141    // one of these has to be taken off. In this case the first two bytes of the remaining JPEG
142    // data is removed because it follows `jpeg_tables`. Similary, `jpeg_tables` ends with a `EOI`
143    // (HEX: `0xFFD9`) or __end of image__ marker, this has to be removed as well (last two bytes
144    // of `jpeg_tables`).
145    let reader = Cursor::new(buf);
146
147    let jpeg_reader = match jpeg_tables {
148        Some(jpeg_tables) => {
149            let mut reader = reader;
150            reader.read_exact(&mut [0; 2])?;
151
152            Box::new(Cursor::new(&jpeg_tables[..jpeg_tables.len() - 2]).chain(reader))
153                as Box<dyn Read>
154        }
155        None => Box::new(reader),
156    };
157
158    let mut decoder = jpeg::Decoder::new(jpeg_reader);
159
160    match photometric_interpretation {
161        PhotometricInterpretation::RGB => decoder.set_color_transform(jpeg::ColorTransform::RGB),
162        PhotometricInterpretation::WhiteIsZero
163        | PhotometricInterpretation::BlackIsZero
164        | PhotometricInterpretation::TransparencyMask => {
165            decoder.set_color_transform(jpeg::ColorTransform::None)
166        }
167        PhotometricInterpretation::CMYK => decoder.set_color_transform(jpeg::ColorTransform::CMYK),
168        PhotometricInterpretation::YCbCr => {
169            decoder.set_color_transform(jpeg::ColorTransform::YCbCr)
170        }
171        photometric_interpretation => {
172            return Err(TiffError::UnsupportedError(
173                TiffUnsupportedError::UnsupportedInterpretation(photometric_interpretation),
174            )
175            .into());
176        }
177    }
178
179    let data = decoder.decode()?;
180    Ok(data.into())
181}