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