Skip to main content

tiff_reader/
tile.rs

1//! Tile-based data access for TIFF images.
2
3use std::sync::Arc;
4
5#[cfg(feature = "rayon")]
6use rayon::prelude::*;
7
8use crate::cache::{BlockCache, BlockKey, BlockKind};
9use crate::error::{Error, Result};
10use crate::filters;
11use crate::header::ByteOrder;
12use crate::ifd::{Ifd, RasterLayout};
13use crate::source::TiffSource;
14use crate::{GdalStructuralMetadata, Window};
15
16const TAG_JPEG_TABLES: u16 = 347;
17
18pub(crate) fn read_window(
19    source: &dyn TiffSource,
20    ifd: &Ifd,
21    byte_order: ByteOrder,
22    cache: &BlockCache,
23    window: Window,
24    gdal_structural_metadata: Option<&GdalStructuralMetadata>,
25) -> Result<Vec<u8>> {
26    let layout = ifd.raster_layout()?;
27    if window.is_empty() {
28        return Ok(Vec::new());
29    }
30
31    let output_len = window.output_len(&layout)?;
32    let mut output = vec![0u8; output_len];
33    let window_row_end = window.row_end();
34    let window_col_end = window.col_end();
35    let output_row_bytes = window.cols * layout.pixel_stride_bytes();
36
37    let specs = collect_tile_specs(ifd, &layout)?;
38    let relevant_specs: Vec<_> = specs
39        .iter()
40        .copied()
41        .filter(|spec| {
42            let spec_row_end = spec.y + spec.rows_in_tile;
43            let spec_col_end = spec.x + spec.cols_in_tile;
44            spec.y < window_row_end
45                && spec_row_end > window.row_off
46                && spec.x < window_col_end
47                && spec_col_end > window.col_off
48        })
49        .collect();
50
51    #[cfg(feature = "rayon")]
52    let decoded_blocks: Result<Vec<_>> = relevant_specs
53        .par_iter()
54        .map(|&spec| {
55            read_tile_block(
56                source,
57                ifd,
58                byte_order,
59                cache,
60                spec,
61                &layout,
62                gdal_structural_metadata,
63            )
64            .map(|block| (spec, block))
65        })
66        .collect();
67
68    #[cfg(not(feature = "rayon"))]
69    let decoded_blocks: Result<Vec<_>> = relevant_specs
70        .iter()
71        .map(|&spec| {
72            read_tile_block(
73                source,
74                ifd,
75                byte_order,
76                cache,
77                spec,
78                &layout,
79                gdal_structural_metadata,
80            )
81            .map(|block| (spec, block))
82        })
83        .collect();
84
85    for (spec, block) in decoded_blocks? {
86        let block = &*block;
87        let copy_row_start = spec.y.max(window.row_off);
88        let copy_row_end = (spec.y + spec.rows_in_tile).min(window_row_end);
89        let copy_col_start = spec.x.max(window.col_off);
90        let copy_col_end = (spec.x + spec.cols_in_tile).min(window_col_end);
91
92        let src_row_bytes = spec.tile_width
93            * if layout.planar_configuration == 1 {
94                layout.pixel_stride_bytes()
95            } else {
96                layout.bytes_per_sample
97            };
98
99        if layout.planar_configuration == 1 {
100            let copy_bytes_per_row = (copy_col_end - copy_col_start) * layout.pixel_stride_bytes();
101            for row in copy_row_start..copy_row_end {
102                let src_row_index = row - spec.y;
103                let dest_row_index = row - window.row_off;
104                let src_offset = src_row_index * src_row_bytes
105                    + (copy_col_start - spec.x) * layout.pixel_stride_bytes();
106                let dest_offset = dest_row_index * output_row_bytes
107                    + (copy_col_start - window.col_off) * layout.pixel_stride_bytes();
108                output[dest_offset..dest_offset + copy_bytes_per_row]
109                    .copy_from_slice(&block[src_offset..src_offset + copy_bytes_per_row]);
110            }
111        } else {
112            for row in copy_row_start..copy_row_end {
113                let src_row_index = row - spec.y;
114                let dest_row_index = row - window.row_off;
115                let src_row =
116                    &block[src_row_index * src_row_bytes..(src_row_index + 1) * src_row_bytes];
117                let dest_row = &mut output
118                    [dest_row_index * output_row_bytes..(dest_row_index + 1) * output_row_bytes];
119                for col in copy_col_start..copy_col_end {
120                    let src = &src_row[(col - spec.x) * layout.bytes_per_sample
121                        ..(col - spec.x + 1) * layout.bytes_per_sample];
122                    let pixel_base = (col - window.col_off) * layout.pixel_stride_bytes()
123                        + spec.plane * layout.bytes_per_sample;
124                    dest_row[pixel_base..pixel_base + layout.bytes_per_sample].copy_from_slice(src);
125                }
126            }
127        }
128    }
129
130    Ok(output)
131}
132
133fn collect_tile_specs(ifd: &Ifd, layout: &RasterLayout) -> Result<Vec<TileBlockSpec>> {
134    let tile_width = ifd
135        .tile_width()
136        .ok_or(Error::TagNotFound(crate::ifd::TAG_TILE_WIDTH))? as usize;
137    let tile_height = ifd
138        .tile_height()
139        .ok_or(Error::TagNotFound(crate::ifd::TAG_TILE_LENGTH))? as usize;
140    if tile_width == 0 || tile_height == 0 {
141        return Err(Error::InvalidImageLayout(
142            "tile width and height must be greater than zero".into(),
143        ));
144    }
145
146    let offsets = ifd
147        .tile_offsets()
148        .ok_or(Error::TagNotFound(crate::ifd::TAG_TILE_OFFSETS))?;
149    let counts = ifd
150        .tile_byte_counts()
151        .ok_or(Error::TagNotFound(crate::ifd::TAG_TILE_BYTE_COUNTS))?;
152    if offsets.len() != counts.len() {
153        return Err(Error::InvalidImageLayout(format!(
154            "TileOffsets has {} entries but TileByteCounts has {}",
155            offsets.len(),
156            counts.len()
157        )));
158    }
159
160    let tiles_across = layout.width.div_ceil(tile_width);
161    let tiles_down = layout.height.div_ceil(tile_height);
162    let tiles_per_plane = tiles_across * tiles_down;
163    let expected = match layout.planar_configuration {
164        1 => tiles_per_plane,
165        2 => tiles_per_plane * layout.samples_per_pixel,
166        planar => return Err(Error::UnsupportedPlanarConfiguration(planar)),
167    };
168    if offsets.len() != expected {
169        return Err(Error::InvalidImageLayout(format!(
170            "expected {expected} tiles, found {}",
171            offsets.len()
172        )));
173    }
174
175    Ok((0..expected)
176        .map(|tile_index| {
177            let plane = if layout.planar_configuration == 1 {
178                0
179            } else {
180                tile_index / tiles_per_plane
181            };
182            let plane_tile_index = if layout.planar_configuration == 1 {
183                tile_index
184            } else {
185                tile_index % tiles_per_plane
186            };
187            let tile_row = plane_tile_index / tiles_across;
188            let tile_col = plane_tile_index % tiles_across;
189            let x = tile_col * tile_width;
190            let y = tile_row * tile_height;
191            let cols_in_tile = tile_width.min(layout.width.saturating_sub(x));
192            let rows_in_tile = tile_height.min(layout.height.saturating_sub(y));
193            TileBlockSpec {
194                index: tile_index,
195                plane,
196                x,
197                y,
198                cols_in_tile,
199                rows_in_tile,
200                offset: offsets[tile_index],
201                byte_count: counts[tile_index],
202                tile_width,
203                tile_height,
204            }
205        })
206        .collect())
207}
208
209#[derive(Clone, Copy)]
210struct TileBlockSpec {
211    index: usize,
212    plane: usize,
213    x: usize,
214    y: usize,
215    cols_in_tile: usize,
216    rows_in_tile: usize,
217    offset: u64,
218    byte_count: u64,
219    tile_width: usize,
220    tile_height: usize,
221}
222
223fn read_tile_block(
224    source: &dyn TiffSource,
225    ifd: &Ifd,
226    byte_order: ByteOrder,
227    cache: &BlockCache,
228    spec: TileBlockSpec,
229    layout: &RasterLayout,
230    gdal_structural_metadata: Option<&GdalStructuralMetadata>,
231) -> Result<Arc<Vec<u8>>> {
232    let cache_key = BlockKey {
233        ifd_index: ifd.index,
234        kind: BlockKind::Tile,
235        block_index: spec.index,
236    };
237    if let Some(cached) = cache.get(&cache_key) {
238        return Ok(cached);
239    }
240
241    let compressed = if let Some(bytes) = source.as_slice() {
242        let start = usize::try_from(spec.offset).map_err(|_| Error::OffsetOutOfBounds {
243            offset: spec.offset,
244            length: spec.byte_count,
245            data_len: bytes.len() as u64,
246        })?;
247        let len = usize::try_from(spec.byte_count).map_err(|_| Error::OffsetOutOfBounds {
248            offset: spec.offset,
249            length: spec.byte_count,
250            data_len: bytes.len() as u64,
251        })?;
252        let end = start.checked_add(len).ok_or(Error::OffsetOutOfBounds {
253            offset: spec.offset,
254            length: spec.byte_count,
255            data_len: bytes.len() as u64,
256        })?;
257        if end > bytes.len() {
258            return Err(Error::OffsetOutOfBounds {
259                offset: spec.offset,
260                length: spec.byte_count,
261                data_len: bytes.len() as u64,
262            });
263        }
264        bytes[start..end].to_vec()
265    } else {
266        let len = usize::try_from(spec.byte_count).map_err(|_| Error::OffsetOutOfBounds {
267            offset: spec.offset,
268            length: spec.byte_count,
269            data_len: source.len(),
270        })?;
271        source.read_exact_at(spec.offset, len)?
272    };
273
274    let compressed = match gdal_structural_metadata {
275        Some(metadata) => metadata
276            .unwrap_block(&compressed, byte_order, spec.offset)?
277            .to_vec(),
278        None => compressed,
279    };
280
281    let jpeg_tables = ifd
282        .tag(TAG_JPEG_TABLES)
283        .and_then(|tag| tag.value.as_bytes());
284    let samples = if layout.planar_configuration == 1 {
285        layout.samples_per_pixel
286    } else {
287        1
288    };
289    let row_bytes = spec.tile_width * samples * layout.bytes_per_sample;
290    let expected_len = spec
291        .tile_height
292        .checked_mul(row_bytes)
293        .ok_or_else(|| Error::InvalidImageLayout("tile size overflows usize".into()))?;
294    let mut decoded = filters::decompress(
295        ifd.compression(),
296        &compressed,
297        spec.index,
298        jpeg_tables,
299        expected_len,
300    )?;
301    if decoded.len() < expected_len {
302        return Err(Error::DecompressionFailed {
303            index: spec.index,
304            reason: format!(
305                "decoded tile is too small: expected at least {expected_len} bytes, found {}",
306                decoded.len()
307            ),
308        });
309    }
310    if decoded.len() > expected_len {
311        decoded.truncate(expected_len);
312    }
313
314    for row in decoded.chunks_exact_mut(row_bytes) {
315        filters::fix_endianness_and_predict(
316            row,
317            layout.bits_per_sample,
318            samples as u16,
319            byte_order,
320            layout.predictor,
321        )?;
322    }
323
324    Ok(cache.insert(cache_key, decoded))
325}