Skip to main content

async_tiff/
tile.rs

1use bytes::Bytes;
2
3use crate::array::Array;
4use crate::decoder::DecoderRegistry;
5use crate::error::{AsyncTiffResult, TiffError, TiffUnsupportedError};
6use crate::ifd::CompressedBytes;
7use crate::predictor::{fix_endianness, unpredict_float, unpredict_hdiff};
8use crate::reader::Endianness;
9use crate::tags::{Compression, PhotometricInterpretation, PlanarConfiguration, Predictor};
10use crate::DataType;
11
12/// A TIFF Tile response.
13///
14/// This contains the required information to decode the tile. Decoding is separated from fetching
15/// so that sync and async operations can be separated and non-blocking.
16///
17/// This is returned by `fetch_tile`.
18///
19/// A strip of a stripped tiff is an image-width, rows-per-strip tile.
20#[derive(Debug, Clone)]
21pub struct Tile {
22    pub(crate) x: usize,
23    pub(crate) y: usize,
24    pub(crate) data_type: Option<DataType>,
25    pub(crate) samples_per_pixel: u16,
26    pub(crate) bits_per_sample: u16,
27    pub(crate) endianness: Endianness,
28    pub(crate) width: u32,
29    pub(crate) height: u32,
30    pub(crate) planar_configuration: PlanarConfiguration,
31    pub(crate) predictor: Predictor,
32    pub(crate) compressed_bytes: CompressedBytes,
33    pub(crate) compression_method: Compression,
34    pub(crate) photometric_interpretation: PhotometricInterpretation,
35    pub(crate) jpeg_tables: Option<Bytes>,
36    /// LERC parameters from the LercParameters tag: [version, compression_type, ...]
37    /// compression_type: 0 = none, 1 = deflate, 2 = zstd
38    pub(crate) lerc_parameters: Option<Vec<u32>>,
39}
40
41impl Tile {
42    /// The column index of this tile.
43    pub fn x(&self) -> usize {
44        self.x
45    }
46
47    /// The row index of this tile.
48    pub fn y(&self) -> usize {
49        self.y
50    }
51
52    /// Access the compressed bytes underlying this tile.
53    ///
54    /// Note that [`Bytes`] is reference-counted, so it is very cheap to clone if needed.
55    pub fn compressed_bytes(&self) -> &CompressedBytes {
56        &self.compressed_bytes
57    }
58
59    /// Access the compression tag representing this tile.
60    pub fn compression_method(&self) -> Compression {
61        self.compression_method
62    }
63
64    /// Access the photometric interpretation tag representing this tile.
65    pub fn photometric_interpretation(&self) -> PhotometricInterpretation {
66        self.photometric_interpretation
67    }
68
69    /// Access the JPEG Tables, if any, from the IFD producing this tile.
70    ///
71    /// Note that [`Bytes`] is reference-counted, so it is very cheap to clone if needed.
72    pub fn jpeg_tables(&self) -> Option<&Bytes> {
73        self.jpeg_tables.as_ref()
74    }
75
76    /// Decode this tile to an [`Array`].
77    ///
78    /// Decoding is separate from data fetching so that sync and async operations do not block the
79    /// same runtime.
80    pub fn decode(self, decoder_registry: &DecoderRegistry) -> AsyncTiffResult<Array> {
81        let decoder = decoder_registry
82            .as_ref()
83            .get(&self.compression_method)
84            .ok_or(TiffError::UnsupportedError(
85                TiffUnsupportedError::UnsupportedCompression(self.compression_method),
86            ))?;
87
88        let samples = self.samples_per_pixel as usize;
89        let bits_per_sample = self.bits_per_sample;
90        // tile_width is the full encoded tile width — predictor must use this, not the cropped width
91        let tile_width = self.width as usize;
92
93        let mut decoded_tile = match &self.compressed_bytes {
94            CompressedBytes::Chunky(bytes) => decoder.decode_tile(
95                bytes.clone(),
96                self.photometric_interpretation,
97                self.jpeg_tables.as_deref(),
98                self.samples_per_pixel,
99                bits_per_sample,
100                self.lerc_parameters.as_deref(),
101            )?,
102            CompressedBytes::Planar(band_bytes) => {
103                let bytes_per_sample = (bits_per_sample as usize).div_ceil(8);
104                let total_size =
105                    band_bytes.len() * tile_width * (self.height as usize) * bytes_per_sample;
106                let mut result = Vec::with_capacity(total_size);
107
108                for band_data in band_bytes {
109                    let decoded_band = decoder.decode_tile(
110                        band_data.clone(),
111                        self.photometric_interpretation,
112                        self.jpeg_tables.as_deref(),
113                        1,
114                        bits_per_sample,
115                        self.lerc_parameters.as_deref(),
116                    )?;
117                    result.extend_from_slice(&decoded_band);
118                }
119
120                debug_assert_eq!(result.len(), total_size);
121                result
122            }
123        };
124
125        // Apply predictor on the full encoded tile width, then crop afterward.
126        let decoded = match self.predictor {
127            Predictor::None => {
128                fix_endianness(&mut decoded_tile, self.endianness, bits_per_sample);
129                decoded_tile
130            }
131            Predictor::Horizontal => unpredict_hdiff(
132                decoded_tile,
133                self.endianness,
134                samples,
135                bits_per_sample,
136                tile_width,
137            ),
138            Predictor::FloatingPoint => {
139                unpredict_float(decoded_tile, samples, bits_per_sample, tile_width)?
140            }
141        };
142
143        let shape = infer_shape(
144            self.planar_configuration,
145            self.width as _,
146            self.height as _,
147            samples,
148        );
149        Array::try_new(decoded, shape, self.data_type)
150    }
151}
152
153fn infer_shape(
154    planar_configuration: PlanarConfiguration,
155    width: usize,
156    height: usize,
157    samples_per_pixel: usize,
158) -> [usize; 3] {
159    match planar_configuration {
160        PlanarConfiguration::Chunky => [height, width, samples_per_pixel],
161        PlanarConfiguration::Planar => [samples_per_pixel, height, width],
162    }
163}