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 parking_lot::Mutex;
7#[cfg(feature = "rayon")]
8use rayon::prelude::*;
9
10use crate::block_decode;
11use crate::cache::{BlockCache, BlockKey, BlockKind};
12use crate::error::{Error, Result};
13use crate::header::ByteOrder;
14use crate::ifd::{Ifd, RasterLayout};
15use crate::source::TiffSource;
16use crate::{
17    allocate_decode_output, checked_layout_add, checked_layout_mul, read_block_payload,
18    read_gdal_block_payload, validate_decode_output_len, DecodeReadOptions, Window,
19};
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    let ifd_offset = ifd.offset();
34    let context = block_decode::BlockDecodeContext::new(ifd, layout, byte_order)?;
35
36    let output_len = window.output_len(&layout)?;
37    let mut output = allocate_decode_output(output_len, options.decode_output_bytes)?;
38
39    let relevant_specs = collect_tile_specs_for_window(ifd, &layout, window, None)?;
40
41    #[cfg(feature = "rayon")]
42    {
43        let output = Mutex::new(output.as_mut_slice());
44        relevant_specs.par_iter().try_for_each(|&spec| {
45            let block = read_tile_block(source, ifd_offset, cache, spec, &context, options)?;
46            copy_tile_window_block(&mut output.lock(), block.as_slice(), spec, &layout, window)?;
47            Ok::<(), Error>(())
48        })?;
49    }
50
51    #[cfg(not(feature = "rayon"))]
52    for spec in relevant_specs {
53        let block = read_tile_block(source, ifd_offset, cache, spec, &context, options)?;
54        copy_tile_window_block(&mut output, block.as_slice(), spec, &layout, window)?;
55    }
56
57    Ok(output)
58}
59
60pub(crate) fn read_window_band(
61    source: &dyn TiffSource,
62    ifd: &Ifd,
63    byte_order: ByteOrder,
64    cache: &BlockCache,
65    window: Window,
66    band_index: usize,
67    options: DecodeReadOptions<'_>,
68) -> Result<Vec<u8>> {
69    let layout = ifd.raster_layout()?;
70    if band_index >= layout.samples_per_pixel {
71        return Err(Error::BandIndexOutOfBounds {
72            index: band_index,
73            band_count: layout.samples_per_pixel,
74        });
75    }
76    if window.is_empty() {
77        return Ok(Vec::new());
78    }
79    let ifd_offset = ifd.offset();
80    let context = block_decode::BlockDecodeContext::new(ifd, layout, byte_order)?;
81
82    let output_len = window.band_output_len(&layout)?;
83    let mut output = allocate_decode_output(output_len, options.decode_output_bytes)?;
84
85    let relevant_specs = collect_tile_specs_for_window(ifd, &layout, window, Some(band_index))?;
86
87    #[cfg(feature = "rayon")]
88    {
89        let output = Mutex::new(output.as_mut_slice());
90        relevant_specs.par_iter().try_for_each(|&spec| {
91            let block = read_tile_block(source, ifd_offset, cache, spec, &context, options)?;
92            copy_tile_band_window_block(
93                &mut output.lock(),
94                block.as_slice(),
95                spec,
96                &layout,
97                window,
98                band_index,
99            )?;
100            Ok::<(), Error>(())
101        })?;
102    }
103
104    #[cfg(not(feature = "rayon"))]
105    for spec in relevant_specs {
106        let block = read_tile_block(source, ifd_offset, cache, spec, &context, options)?;
107        copy_tile_band_window_block(
108            &mut output,
109            block.as_slice(),
110            spec,
111            &layout,
112            window,
113            band_index,
114        )?;
115    }
116
117    Ok(output)
118}
119
120fn copy_tile_window_block(
121    output: &mut [u8],
122    block: &[u8],
123    spec: TileBlockSpec,
124    layout: &RasterLayout,
125    window: Window,
126) -> Result<()> {
127    let pixel_stride = layout.checked_pixel_stride_bytes()?;
128    let window_row_end = window.row_end();
129    let window_col_end = window.col_end();
130    let output_row_bytes = checked_layout_mul(window.cols, pixel_stride, "window row byte count")?;
131    let copy_row_start = spec.y.max(window.row_off);
132    let copy_row_end =
133        checked_layout_add(spec.y, spec.rows_in_tile, "tile row range")?.min(window_row_end);
134    let copy_col_start = spec.x.max(window.col_off);
135    let copy_col_end =
136        checked_layout_add(spec.x, spec.cols_in_tile, "tile column range")?.min(window_col_end);
137
138    let src_row_bytes = checked_layout_mul(
139        spec.tile_width,
140        if layout.planar_configuration == 1 {
141            pixel_stride
142        } else {
143            layout.bytes_per_sample
144        },
145        "tile source row byte count",
146    )?;
147
148    if layout.planar_configuration == 1 {
149        let copy_bytes_per_row = checked_layout_mul(
150            copy_col_end - copy_col_start,
151            pixel_stride,
152            "tile copy row byte count",
153        )?;
154        let src_col_offset = checked_layout_mul(
155            copy_col_start - spec.x,
156            pixel_stride,
157            "tile source column offset",
158        )?;
159        let dest_col_offset = checked_layout_mul(
160            copy_col_start - window.col_off,
161            pixel_stride,
162            "tile output column offset",
163        )?;
164        for row in copy_row_start..copy_row_end {
165            let src_row_index = row - spec.y;
166            let dest_row_index = row - window.row_off;
167            let src_offset = checked_layout_add(
168                checked_layout_mul(src_row_index, src_row_bytes, "tile source row offset")?,
169                src_col_offset,
170                "tile source offset",
171            )?;
172            let dest_offset = checked_layout_add(
173                checked_layout_mul(dest_row_index, output_row_bytes, "tile output row offset")?,
174                dest_col_offset,
175                "tile output offset",
176            )?;
177            let src_end =
178                checked_layout_add(src_offset, copy_bytes_per_row, "tile source copy range")?;
179            let dest_end =
180                checked_layout_add(dest_offset, copy_bytes_per_row, "tile output copy range")?;
181            output[dest_offset..dest_end].copy_from_slice(&block[src_offset..src_end]);
182        }
183    } else {
184        let plane_offset = checked_layout_mul(
185            spec.plane,
186            layout.bytes_per_sample,
187            "tile plane byte offset",
188        )?;
189        for row in copy_row_start..copy_row_end {
190            let src_row_index = row - spec.y;
191            let dest_row_index = row - window.row_off;
192            let src_row_offset =
193                checked_layout_mul(src_row_index, src_row_bytes, "tile source row offset")?;
194            let src_row_end =
195                checked_layout_add(src_row_offset, src_row_bytes, "tile source row range")?;
196            let dest_row_offset =
197                checked_layout_mul(dest_row_index, output_row_bytes, "tile output row offset")?;
198            let dest_row_end =
199                checked_layout_add(dest_row_offset, output_row_bytes, "tile output row range")?;
200            let src_row = &block[src_row_offset..src_row_end];
201            let dest_row = &mut output[dest_row_offset..dest_row_end];
202            for col in copy_col_start..copy_col_end {
203                let src_offset = checked_layout_mul(
204                    col - spec.x,
205                    layout.bytes_per_sample,
206                    "tile source column offset",
207                )?;
208                let src_end = checked_layout_add(
209                    src_offset,
210                    layout.bytes_per_sample,
211                    "tile source sample range",
212                )?;
213                let src = &src_row[src_offset..src_end];
214                let pixel_base = checked_layout_add(
215                    checked_layout_mul(
216                        col - window.col_off,
217                        pixel_stride,
218                        "tile output pixel offset",
219                    )?,
220                    plane_offset,
221                    "tile output sample offset",
222                )?;
223                let pixel_end = checked_layout_add(
224                    pixel_base,
225                    layout.bytes_per_sample,
226                    "tile output sample range",
227                )?;
228                dest_row[pixel_base..pixel_end].copy_from_slice(src);
229            }
230        }
231    }
232    Ok(())
233}
234
235fn copy_tile_band_window_block(
236    output: &mut [u8],
237    block: &[u8],
238    spec: TileBlockSpec,
239    layout: &RasterLayout,
240    window: Window,
241    band_index: usize,
242) -> Result<()> {
243    let pixel_stride = layout.checked_pixel_stride_bytes()?;
244    let window_row_end = window.row_end();
245    let window_col_end = window.col_end();
246    let output_row_bytes = checked_layout_mul(
247        window.cols,
248        layout.bytes_per_sample,
249        "window band row byte count",
250    )?;
251    let copy_row_start = spec.y.max(window.row_off);
252    let copy_row_end =
253        checked_layout_add(spec.y, spec.rows_in_tile, "tile row range")?.min(window_row_end);
254    let copy_col_start = spec.x.max(window.col_off);
255    let copy_col_end =
256        checked_layout_add(spec.x, spec.cols_in_tile, "tile column range")?.min(window_col_end);
257
258    let src_row_bytes = checked_layout_mul(
259        spec.tile_width,
260        if layout.planar_configuration == 1 {
261            pixel_stride
262        } else {
263            layout.bytes_per_sample
264        },
265        "tile source row byte count",
266    )?;
267
268    if layout.planar_configuration == 1 {
269        let band_offset =
270            checked_layout_mul(band_index, layout.bytes_per_sample, "band byte offset")?;
271        for row in copy_row_start..copy_row_end {
272            let src_row_index = row - spec.y;
273            let dest_row_index = row - window.row_off;
274            let src_row_offset =
275                checked_layout_mul(src_row_index, src_row_bytes, "tile source row offset")?;
276            let src_row_end =
277                checked_layout_add(src_row_offset, src_row_bytes, "tile source row range")?;
278            let dest_row_offset =
279                checked_layout_mul(dest_row_index, output_row_bytes, "tile output row offset")?;
280            let dest_row_end =
281                checked_layout_add(dest_row_offset, output_row_bytes, "tile output row range")?;
282            let src_row = &block[src_row_offset..src_row_end];
283            let dest_row = &mut output[dest_row_offset..dest_row_end];
284            for col in copy_col_start..copy_col_end {
285                let src_base = checked_layout_add(
286                    checked_layout_mul(col - spec.x, pixel_stride, "tile source column offset")?,
287                    band_offset,
288                    "tile source band offset",
289                )?;
290                let dest_col_index = col - window.col_off;
291                let dest_base = checked_layout_mul(
292                    dest_col_index,
293                    layout.bytes_per_sample,
294                    "tile output sample offset",
295                )?;
296                let src_end = checked_layout_add(
297                    src_base,
298                    layout.bytes_per_sample,
299                    "tile source sample range",
300                )?;
301                let dest_end = checked_layout_add(
302                    dest_base,
303                    layout.bytes_per_sample,
304                    "tile output sample range",
305                )?;
306                dest_row[dest_base..dest_end].copy_from_slice(&src_row[src_base..src_end]);
307            }
308        }
309    } else {
310        let copy_bytes_per_row = checked_layout_mul(
311            copy_col_end - copy_col_start,
312            layout.bytes_per_sample,
313            "tile copy row byte count",
314        )?;
315        let src_col_offset = checked_layout_mul(
316            copy_col_start - spec.x,
317            layout.bytes_per_sample,
318            "tile source column offset",
319        )?;
320        let dest_col_offset = checked_layout_mul(
321            copy_col_start - window.col_off,
322            layout.bytes_per_sample,
323            "tile output column offset",
324        )?;
325        for row in copy_row_start..copy_row_end {
326            let src_row_index = row - spec.y;
327            let dest_row_index = row - window.row_off;
328            let src_offset = checked_layout_add(
329                checked_layout_mul(src_row_index, src_row_bytes, "tile source row offset")?,
330                src_col_offset,
331                "tile source offset",
332            )?;
333            let dest_offset = checked_layout_add(
334                checked_layout_mul(dest_row_index, output_row_bytes, "tile output row offset")?,
335                dest_col_offset,
336                "tile output offset",
337            )?;
338            let src_end =
339                checked_layout_add(src_offset, copy_bytes_per_row, "tile source copy range")?;
340            let dest_end =
341                checked_layout_add(dest_offset, copy_bytes_per_row, "tile output copy range")?;
342            output[dest_offset..dest_end].copy_from_slice(&block[src_offset..src_end]);
343        }
344    }
345    Ok(())
346}
347
348fn collect_tile_specs_for_window(
349    ifd: &Ifd,
350    layout: &RasterLayout,
351    window: Window,
352    band_index: Option<usize>,
353) -> Result<Vec<TileBlockSpec>> {
354    let tile_width = ifd
355        .tile_width()
356        .ok_or(Error::TagNotFound(crate::ifd::TAG_TILE_WIDTH))? as usize;
357    let tile_height = ifd
358        .tile_height()
359        .ok_or(Error::TagNotFound(crate::ifd::TAG_TILE_LENGTH))? as usize;
360    if tile_width == 0 || tile_height == 0 {
361        return Err(Error::InvalidImageLayout(
362            "tile width and height must be greater than zero".into(),
363        ));
364    }
365
366    let offsets = ifd
367        .tile_offsets()
368        .ok_or(Error::TagNotFound(crate::ifd::TAG_TILE_OFFSETS))?;
369    let counts = ifd
370        .tile_byte_counts()
371        .ok_or(Error::TagNotFound(crate::ifd::TAG_TILE_BYTE_COUNTS))?;
372    if offsets.len() != counts.len() {
373        return Err(Error::InvalidImageLayout(format!(
374            "TileOffsets has {} entries but TileByteCounts has {}",
375            offsets.len(),
376            counts.len()
377        )));
378    }
379
380    let tiles_across = layout.width.div_ceil(tile_width);
381    let tiles_down = layout.height.div_ceil(tile_height);
382    let tiles_per_plane = tiles_across
383        .checked_mul(tiles_down)
384        .ok_or_else(tile_count_overflow)?;
385    let expected = match layout.planar_configuration {
386        1 => tiles_per_plane,
387        2 => tiles_per_plane
388            .checked_mul(layout.samples_per_pixel)
389            .ok_or_else(tile_count_overflow)?,
390        planar => return Err(Error::UnsupportedPlanarConfiguration(planar)),
391    };
392    if offsets.len() != expected {
393        return Err(Error::InvalidImageLayout(format!(
394            "expected {expected} tiles, found {}",
395            offsets.len()
396        )));
397    }
398
399    let first_tile_row = window.row_off / tile_height;
400    let last_tile_row = window.row_end().div_ceil(tile_height).min(tiles_down);
401    let first_tile_col = window.col_off / tile_width;
402    let last_tile_col = window.col_end().div_ceil(tile_width).min(tiles_across);
403    let plane_range = if layout.planar_configuration == 1 {
404        0..1
405    } else if let Some(band_index) = band_index {
406        band_index..band_index + 1
407    } else {
408        0..layout.samples_per_pixel
409    };
410    let spec_count = (last_tile_row - first_tile_row)
411        .saturating_mul(last_tile_col - first_tile_col)
412        .saturating_mul(plane_range.end - plane_range.start);
413    let mut specs = Vec::with_capacity(spec_count);
414
415    for plane in plane_range {
416        for tile_row in first_tile_row..last_tile_row {
417            for tile_col in first_tile_col..last_tile_col {
418                let plane_tile_index = tile_row
419                    .checked_mul(tiles_across)
420                    .and_then(|base| base.checked_add(tile_col))
421                    .ok_or_else(tile_count_overflow)?;
422                let tile_index = if layout.planar_configuration == 1 {
423                    plane_tile_index
424                } else {
425                    plane
426                        .checked_mul(tiles_per_plane)
427                        .and_then(|base| base.checked_add(plane_tile_index))
428                        .ok_or_else(tile_count_overflow)?
429                };
430                let x = tile_col * tile_width;
431                let y = tile_row * tile_height;
432                let cols_in_tile = tile_width.min(layout.width.saturating_sub(x));
433                let rows_in_tile = tile_height.min(layout.height.saturating_sub(y));
434                specs.push(TileBlockSpec {
435                    index: tile_index,
436                    plane,
437                    x,
438                    y,
439                    cols_in_tile,
440                    rows_in_tile,
441                    offset: offsets[tile_index],
442                    byte_count: counts[tile_index],
443                    tile_width,
444                    tile_height,
445                });
446            }
447        }
448    }
449
450    Ok(specs)
451}
452
453fn tile_count_overflow() -> Error {
454    Error::InvalidImageLayout("tile count overflows usize".into())
455}
456
457#[derive(Clone, Copy)]
458struct TileBlockSpec {
459    index: usize,
460    plane: usize,
461    x: usize,
462    y: usize,
463    cols_in_tile: usize,
464    rows_in_tile: usize,
465    offset: u64,
466    byte_count: u64,
467    tile_width: usize,
468    tile_height: usize,
469}
470
471fn read_tile_block(
472    source: &dyn TiffSource,
473    ifd_offset: u64,
474    cache: &BlockCache,
475    spec: TileBlockSpec,
476    context: &block_decode::BlockDecodeContext<'_>,
477    options: DecodeReadOptions<'_>,
478) -> Result<Arc<Vec<u8>>> {
479    let decode_request = block_decode::BlockDecodeRequest {
480        context,
481        compressed: &[],
482        index: spec.index,
483        block_width: spec.tile_width,
484        block_height: spec.tile_height,
485    };
486    let decoded_len = block_decode::decoded_block_len(&decode_request)?;
487    validate_decode_output_len(decoded_len, options.decode_output_bytes)?;
488
489    let cache_key = BlockKey {
490        ifd_offset,
491        kind: BlockKind::Tile,
492        block_index: spec.index,
493    };
494    if let Some(cached) = cache.get(&cache_key) {
495        return Ok(cached);
496    }
497
498    // GDAL SPARSE_OK semantics: a block with no on-disk payload (zero offset
499    // or zero byte count) decodes as implicit zero fill.
500    if spec.offset == 0 || spec.byte_count == 0 {
501        let decoded = allocate_decode_output(decoded_len, options.decode_output_bytes)?;
502        return Ok(cache.insert(cache_key, decoded));
503    }
504
505    let byte_count_limit = block_decode::compressed_block_byte_count_limit(&decode_request)?;
506    let compressed = match options.gdal_structural_metadata {
507        Some(metadata) => read_gdal_block_payload(
508            source,
509            metadata,
510            context.byte_order,
511            spec.offset,
512            spec.byte_count,
513            byte_count_limit,
514            spec.index,
515        )?,
516        None => read_block_payload(
517            source,
518            spec.offset,
519            spec.byte_count,
520            byte_count_limit,
521            spec.index,
522        )?,
523    };
524
525    let decoded = block_decode::decode_compressed_block(block_decode::BlockDecodeRequest {
526        context,
527        compressed: &compressed,
528        index: spec.index,
529        block_width: spec.tile_width,
530        block_height: spec.tile_height,
531    })?;
532    Ok(cache.insert(cache_key, decoded))
533}