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::block_decode;
9use crate::cache::{BlockCache, BlockKey, BlockKind};
10use crate::error::{Error, Result};
11use crate::header::ByteOrder;
12use crate::ifd::{Ifd, RasterLayout};
13use crate::source::TiffSource;
14use crate::{
15    allocate_decode_output, read_block_payload, read_gdal_block_payload, DecodeReadOptions,
16    GdalStructuralMetadata, Window,
17};
18
19const TAG_JPEG_TABLES: u16 = 347;
20
21pub(crate) fn read_window(
22    source: &dyn TiffSource,
23    ifd: &Ifd,
24    byte_order: ByteOrder,
25    cache: &BlockCache,
26    window: Window,
27    options: DecodeReadOptions<'_>,
28) -> Result<Vec<u8>> {
29    let layout = ifd.raster_layout()?;
30    if window.is_empty() {
31        return Ok(Vec::new());
32    }
33
34    let output_len = window.output_len(&layout)?;
35    let mut output = allocate_decode_output(output_len, options.decode_output_bytes)?;
36    let window_row_end = window.row_end();
37    let window_col_end = window.col_end();
38    let output_row_bytes = window.cols * layout.pixel_stride_bytes();
39
40    let relevant_specs = collect_tile_specs_for_window(ifd, &layout, window, None)?;
41
42    #[cfg(feature = "rayon")]
43    let decoded_blocks: Result<Vec<_>> = relevant_specs
44        .par_iter()
45        .map(|&spec| {
46            read_tile_block(
47                source,
48                ifd,
49                byte_order,
50                cache,
51                spec,
52                &layout,
53                options.gdal_structural_metadata,
54            )
55            .map(|block| (spec, block))
56        })
57        .collect();
58
59    #[cfg(not(feature = "rayon"))]
60    let decoded_blocks: Result<Vec<_>> = relevant_specs
61        .iter()
62        .map(|&spec| {
63            read_tile_block(
64                source,
65                ifd,
66                byte_order,
67                cache,
68                spec,
69                &layout,
70                options.gdal_structural_metadata,
71            )
72            .map(|block| (spec, block))
73        })
74        .collect();
75
76    for (spec, block) in decoded_blocks? {
77        let block = &*block;
78        let copy_row_start = spec.y.max(window.row_off);
79        let copy_row_end = (spec.y + spec.rows_in_tile).min(window_row_end);
80        let copy_col_start = spec.x.max(window.col_off);
81        let copy_col_end = (spec.x + spec.cols_in_tile).min(window_col_end);
82
83        let src_row_bytes = spec.tile_width
84            * if layout.planar_configuration == 1 {
85                layout.pixel_stride_bytes()
86            } else {
87                layout.bytes_per_sample
88            };
89
90        if layout.planar_configuration == 1 {
91            let copy_bytes_per_row = (copy_col_end - copy_col_start) * layout.pixel_stride_bytes();
92            for row in copy_row_start..copy_row_end {
93                let src_row_index = row - spec.y;
94                let dest_row_index = row - window.row_off;
95                let src_offset = src_row_index * src_row_bytes
96                    + (copy_col_start - spec.x) * layout.pixel_stride_bytes();
97                let dest_offset = dest_row_index * output_row_bytes
98                    + (copy_col_start - window.col_off) * layout.pixel_stride_bytes();
99                output[dest_offset..dest_offset + copy_bytes_per_row]
100                    .copy_from_slice(&block[src_offset..src_offset + copy_bytes_per_row]);
101            }
102        } else {
103            for row in copy_row_start..copy_row_end {
104                let src_row_index = row - spec.y;
105                let dest_row_index = row - window.row_off;
106                let src_row =
107                    &block[src_row_index * src_row_bytes..(src_row_index + 1) * src_row_bytes];
108                let dest_row = &mut output
109                    [dest_row_index * output_row_bytes..(dest_row_index + 1) * output_row_bytes];
110                for col in copy_col_start..copy_col_end {
111                    let src = &src_row[(col - spec.x) * layout.bytes_per_sample
112                        ..(col - spec.x + 1) * layout.bytes_per_sample];
113                    let pixel_base = (col - window.col_off) * layout.pixel_stride_bytes()
114                        + spec.plane * layout.bytes_per_sample;
115                    dest_row[pixel_base..pixel_base + layout.bytes_per_sample].copy_from_slice(src);
116                }
117            }
118        }
119    }
120
121    Ok(output)
122}
123
124pub(crate) fn read_window_band(
125    source: &dyn TiffSource,
126    ifd: &Ifd,
127    byte_order: ByteOrder,
128    cache: &BlockCache,
129    window: Window,
130    band_index: usize,
131    options: DecodeReadOptions<'_>,
132) -> Result<Vec<u8>> {
133    let layout = ifd.raster_layout()?;
134    if band_index >= layout.samples_per_pixel {
135        return Err(Error::BandIndexOutOfBounds {
136            index: band_index,
137            band_count: layout.samples_per_pixel,
138        });
139    }
140    if window.is_empty() {
141        return Ok(Vec::new());
142    }
143
144    let output_len = window.band_output_len(&layout)?;
145    let mut output = allocate_decode_output(output_len, options.decode_output_bytes)?;
146    let window_row_end = window.row_end();
147    let window_col_end = window.col_end();
148    let output_row_bytes = window.cols * layout.bytes_per_sample;
149
150    let relevant_specs = collect_tile_specs_for_window(ifd, &layout, window, Some(band_index))?;
151
152    #[cfg(feature = "rayon")]
153    let decoded_blocks: Result<Vec<_>> = relevant_specs
154        .par_iter()
155        .map(|&spec| {
156            read_tile_block(
157                source,
158                ifd,
159                byte_order,
160                cache,
161                spec,
162                &layout,
163                options.gdal_structural_metadata,
164            )
165            .map(|block| (spec, block))
166        })
167        .collect();
168
169    #[cfg(not(feature = "rayon"))]
170    let decoded_blocks: Result<Vec<_>> = relevant_specs
171        .iter()
172        .map(|&spec| {
173            read_tile_block(
174                source,
175                ifd,
176                byte_order,
177                cache,
178                spec,
179                &layout,
180                options.gdal_structural_metadata,
181            )
182            .map(|block| (spec, block))
183        })
184        .collect();
185
186    for (spec, block) in decoded_blocks? {
187        let block = &*block;
188        let copy_row_start = spec.y.max(window.row_off);
189        let copy_row_end = (spec.y + spec.rows_in_tile).min(window_row_end);
190        let copy_col_start = spec.x.max(window.col_off);
191        let copy_col_end = (spec.x + spec.cols_in_tile).min(window_col_end);
192
193        let src_row_bytes = spec.tile_width
194            * if layout.planar_configuration == 1 {
195                layout.pixel_stride_bytes()
196            } else {
197                layout.bytes_per_sample
198            };
199
200        if layout.planar_configuration == 1 {
201            let band_offset = band_index * layout.bytes_per_sample;
202            for row in copy_row_start..copy_row_end {
203                let src_row_index = row - spec.y;
204                let dest_row_index = row - window.row_off;
205                let src_row =
206                    &block[src_row_index * src_row_bytes..(src_row_index + 1) * src_row_bytes];
207                let dest_row = &mut output
208                    [dest_row_index * output_row_bytes..(dest_row_index + 1) * output_row_bytes];
209                for col in copy_col_start..copy_col_end {
210                    let src_base = (col - spec.x) * layout.pixel_stride_bytes() + band_offset;
211                    let dest_col_index = col - window.col_off;
212                    let dest_base = dest_col_index * layout.bytes_per_sample;
213                    dest_row[dest_base..dest_base + layout.bytes_per_sample]
214                        .copy_from_slice(&src_row[src_base..src_base + layout.bytes_per_sample]);
215                }
216            }
217        } else {
218            let copy_bytes_per_row = (copy_col_end - copy_col_start) * layout.bytes_per_sample;
219            for row in copy_row_start..copy_row_end {
220                let src_row_index = row - spec.y;
221                let dest_row_index = row - window.row_off;
222                let src_offset = src_row_index * src_row_bytes
223                    + (copy_col_start - spec.x) * layout.bytes_per_sample;
224                let dest_offset = dest_row_index * output_row_bytes
225                    + (copy_col_start - window.col_off) * layout.bytes_per_sample;
226                output[dest_offset..dest_offset + copy_bytes_per_row]
227                    .copy_from_slice(&block[src_offset..src_offset + copy_bytes_per_row]);
228            }
229        }
230    }
231
232    Ok(output)
233}
234
235fn collect_tile_specs_for_window(
236    ifd: &Ifd,
237    layout: &RasterLayout,
238    window: Window,
239    band_index: Option<usize>,
240) -> Result<Vec<TileBlockSpec>> {
241    let tile_width = ifd
242        .tile_width()
243        .ok_or(Error::TagNotFound(crate::ifd::TAG_TILE_WIDTH))? as usize;
244    let tile_height = ifd
245        .tile_height()
246        .ok_or(Error::TagNotFound(crate::ifd::TAG_TILE_LENGTH))? as usize;
247    if tile_width == 0 || tile_height == 0 {
248        return Err(Error::InvalidImageLayout(
249            "tile width and height must be greater than zero".into(),
250        ));
251    }
252
253    let offsets = ifd
254        .tile_offsets()
255        .ok_or(Error::TagNotFound(crate::ifd::TAG_TILE_OFFSETS))?;
256    let counts = ifd
257        .tile_byte_counts()
258        .ok_or(Error::TagNotFound(crate::ifd::TAG_TILE_BYTE_COUNTS))?;
259    if offsets.len() != counts.len() {
260        return Err(Error::InvalidImageLayout(format!(
261            "TileOffsets has {} entries but TileByteCounts has {}",
262            offsets.len(),
263            counts.len()
264        )));
265    }
266
267    let tiles_across = layout.width.div_ceil(tile_width);
268    let tiles_down = layout.height.div_ceil(tile_height);
269    let tiles_per_plane = tiles_across
270        .checked_mul(tiles_down)
271        .ok_or_else(tile_count_overflow)?;
272    let expected = match layout.planar_configuration {
273        1 => tiles_per_plane,
274        2 => tiles_per_plane
275            .checked_mul(layout.samples_per_pixel)
276            .ok_or_else(tile_count_overflow)?,
277        planar => return Err(Error::UnsupportedPlanarConfiguration(planar)),
278    };
279    if offsets.len() != expected {
280        return Err(Error::InvalidImageLayout(format!(
281            "expected {expected} tiles, found {}",
282            offsets.len()
283        )));
284    }
285
286    let first_tile_row = window.row_off / tile_height;
287    let last_tile_row = window.row_end().div_ceil(tile_height).min(tiles_down);
288    let first_tile_col = window.col_off / tile_width;
289    let last_tile_col = window.col_end().div_ceil(tile_width).min(tiles_across);
290    let plane_range = if layout.planar_configuration == 1 {
291        0..1
292    } else if let Some(band_index) = band_index {
293        band_index..band_index + 1
294    } else {
295        0..layout.samples_per_pixel
296    };
297    let spec_count = (last_tile_row - first_tile_row)
298        .saturating_mul(last_tile_col - first_tile_col)
299        .saturating_mul(plane_range.end - plane_range.start);
300    let mut specs = Vec::with_capacity(spec_count);
301
302    for plane in plane_range {
303        for tile_row in first_tile_row..last_tile_row {
304            for tile_col in first_tile_col..last_tile_col {
305                let plane_tile_index = tile_row
306                    .checked_mul(tiles_across)
307                    .and_then(|base| base.checked_add(tile_col))
308                    .ok_or_else(tile_count_overflow)?;
309                let tile_index = if layout.planar_configuration == 1 {
310                    plane_tile_index
311                } else {
312                    plane
313                        .checked_mul(tiles_per_plane)
314                        .and_then(|base| base.checked_add(plane_tile_index))
315                        .ok_or_else(tile_count_overflow)?
316                };
317                let x = tile_col * tile_width;
318                let y = tile_row * tile_height;
319                let cols_in_tile = tile_width.min(layout.width.saturating_sub(x));
320                let rows_in_tile = tile_height.min(layout.height.saturating_sub(y));
321                specs.push(TileBlockSpec {
322                    index: tile_index,
323                    plane,
324                    x,
325                    y,
326                    cols_in_tile,
327                    rows_in_tile,
328                    offset: offsets[tile_index],
329                    byte_count: counts[tile_index],
330                    tile_width,
331                    tile_height,
332                });
333            }
334        }
335    }
336
337    Ok(specs)
338}
339
340fn tile_count_overflow() -> Error {
341    Error::InvalidImageLayout("tile count overflows usize".into())
342}
343
344#[derive(Clone, Copy)]
345struct TileBlockSpec {
346    index: usize,
347    plane: usize,
348    x: usize,
349    y: usize,
350    cols_in_tile: usize,
351    rows_in_tile: usize,
352    offset: u64,
353    byte_count: u64,
354    tile_width: usize,
355    tile_height: usize,
356}
357
358fn read_tile_block(
359    source: &dyn TiffSource,
360    ifd: &Ifd,
361    byte_order: ByteOrder,
362    cache: &BlockCache,
363    spec: TileBlockSpec,
364    layout: &RasterLayout,
365    gdal_structural_metadata: Option<&GdalStructuralMetadata>,
366) -> Result<Arc<Vec<u8>>> {
367    let cache_key = BlockKey {
368        ifd_index: ifd.index,
369        kind: BlockKind::Tile,
370        block_index: spec.index,
371    };
372    if let Some(cached) = cache.get(&cache_key) {
373        return Ok(cached);
374    }
375
376    let jpeg_tables = ifd
377        .tag(TAG_JPEG_TABLES)
378        .and_then(|tag| tag.value.as_bytes());
379    let byte_count_limit =
380        block_decode::compressed_block_byte_count_limit(&block_decode::BlockDecodeRequest {
381            ifd,
382            layout: *layout,
383            byte_order,
384            compressed: &[],
385            index: spec.index,
386            jpeg_tables,
387            block_width: spec.tile_width,
388            block_height: spec.tile_height,
389        })?;
390    let compressed = match gdal_structural_metadata {
391        Some(metadata) => read_gdal_block_payload(
392            source,
393            metadata,
394            byte_order,
395            spec.offset,
396            spec.byte_count,
397            byte_count_limit,
398            spec.index,
399        )?,
400        None => read_block_payload(
401            source,
402            spec.offset,
403            spec.byte_count,
404            byte_count_limit,
405            spec.index,
406        )?,
407    };
408
409    let decoded = block_decode::decode_compressed_block(block_decode::BlockDecodeRequest {
410        ifd,
411        layout: *layout,
412        byte_order,
413        compressed: &compressed,
414        index: spec.index,
415        jpeg_tables,
416        block_width: spec.tile_width,
417        block_height: spec.tile_height,
418    })?;
419    Ok(cache.insert(cache_key, decoded))
420}