Skip to main content

tiff_reader/
strip.rs

1//! Strip-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_strip_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_strip_block(source, ifd_offset, cache, spec, &context, options)?;
46            copy_strip_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_strip_block(source, ifd_offset, cache, spec, &context, options)?;
54        copy_strip_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_strip_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_strip_block(source, ifd_offset, cache, spec, &context, options)?;
92            copy_strip_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_strip_block(source, ifd_offset, cache, spec, &context, options)?;
107        copy_strip_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_strip_window_block(
121    output: &mut [u8],
122    block: &[u8],
123    spec: StripBlockSpec,
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 output_row_bytes = checked_layout_mul(window.cols, pixel_stride, "window row byte count")?;
130    let block_row_end = checked_layout_add(spec.row_start, spec.rows_in_strip, "strip row range")?;
131    let copy_row_start = spec.row_start.max(window.row_off);
132    let copy_row_end = block_row_end.min(window_row_end);
133
134    if layout.planar_configuration == 1 {
135        let src_row_bytes = layout.checked_row_bytes()?;
136        let src_col_offset =
137            checked_layout_mul(window.col_off, pixel_stride, "strip source column offset")?;
138        let copy_bytes_per_row = output_row_bytes;
139        for row in copy_row_start..copy_row_end {
140            let src_row_index = row - spec.row_start;
141            let dest_row_index = row - window.row_off;
142            let src_offset = checked_layout_add(
143                checked_layout_mul(src_row_index, src_row_bytes, "strip source row offset")?,
144                src_col_offset,
145                "strip source offset",
146            )?;
147            let dest_offset =
148                checked_layout_mul(dest_row_index, output_row_bytes, "strip output row offset")?;
149            let src_end =
150                checked_layout_add(src_offset, copy_bytes_per_row, "strip source copy range")?;
151            let dest_end =
152                checked_layout_add(dest_offset, copy_bytes_per_row, "strip output copy range")?;
153            output[dest_offset..dest_end].copy_from_slice(&block[src_offset..src_end]);
154        }
155    } else {
156        let src_row_bytes = layout.checked_sample_plane_row_bytes()?;
157        let plane_offset = checked_layout_mul(
158            spec.plane,
159            layout.bytes_per_sample,
160            "strip plane byte offset",
161        )?;
162        for row in copy_row_start..copy_row_end {
163            let src_row_index = row - spec.row_start;
164            let dest_row_index = row - window.row_off;
165            let src_row_offset =
166                checked_layout_mul(src_row_index, src_row_bytes, "strip source row offset")?;
167            let src_row_end =
168                checked_layout_add(src_row_offset, src_row_bytes, "strip source row range")?;
169            let dest_row_offset =
170                checked_layout_mul(dest_row_index, output_row_bytes, "strip output row offset")?;
171            let dest_row_end =
172                checked_layout_add(dest_row_offset, output_row_bytes, "strip output row range")?;
173            let src_row = &block[src_row_offset..src_row_end];
174            let dest_row = &mut output[dest_row_offset..dest_row_end];
175            for col in window.col_off..window.col_end() {
176                let src_offset =
177                    checked_layout_mul(col, layout.bytes_per_sample, "strip source column offset")?;
178                let src_end = checked_layout_add(
179                    src_offset,
180                    layout.bytes_per_sample,
181                    "strip source sample range",
182                )?;
183                let src = &src_row[src_offset..src_end];
184                let dest_col_index = col - window.col_off;
185                let pixel_base = checked_layout_add(
186                    checked_layout_mul(dest_col_index, pixel_stride, "strip output pixel offset")?,
187                    plane_offset,
188                    "strip output sample offset",
189                )?;
190                let pixel_end = checked_layout_add(
191                    pixel_base,
192                    layout.bytes_per_sample,
193                    "strip output sample range",
194                )?;
195                dest_row[pixel_base..pixel_end].copy_from_slice(src);
196            }
197        }
198    }
199    Ok(())
200}
201
202fn copy_strip_band_window_block(
203    output: &mut [u8],
204    block: &[u8],
205    spec: StripBlockSpec,
206    layout: &RasterLayout,
207    window: Window,
208    band_index: usize,
209) -> Result<()> {
210    let pixel_stride = layout.checked_pixel_stride_bytes()?;
211    let window_row_end = window.row_end();
212    let output_row_bytes = checked_layout_mul(
213        window.cols,
214        layout.bytes_per_sample,
215        "window band row byte count",
216    )?;
217    let block_row_end = checked_layout_add(spec.row_start, spec.rows_in_strip, "strip row range")?;
218    let copy_row_start = spec.row_start.max(window.row_off);
219    let copy_row_end = block_row_end.min(window_row_end);
220
221    if layout.planar_configuration == 1 {
222        let src_row_bytes = layout.checked_row_bytes()?;
223        let band_offset =
224            checked_layout_mul(band_index, layout.bytes_per_sample, "band byte offset")?;
225        for row in copy_row_start..copy_row_end {
226            let src_row_index = row - spec.row_start;
227            let dest_row_index = row - window.row_off;
228            let src_row_offset =
229                checked_layout_mul(src_row_index, src_row_bytes, "strip source row offset")?;
230            let src_row_end =
231                checked_layout_add(src_row_offset, src_row_bytes, "strip source row range")?;
232            let dest_row_offset =
233                checked_layout_mul(dest_row_index, output_row_bytes, "strip output row offset")?;
234            let dest_row_end =
235                checked_layout_add(dest_row_offset, output_row_bytes, "strip output row range")?;
236            let src_row = &block[src_row_offset..src_row_end];
237            let dest_row = &mut output[dest_row_offset..dest_row_end];
238            for col in window.col_off..window.col_end() {
239                let src_base = checked_layout_add(
240                    checked_layout_mul(col, pixel_stride, "strip source column offset")?,
241                    band_offset,
242                    "strip source band offset",
243                )?;
244                let dest_col_index = col - window.col_off;
245                let dest_base = checked_layout_mul(
246                    dest_col_index,
247                    layout.bytes_per_sample,
248                    "strip output sample offset",
249                )?;
250                let src_end = checked_layout_add(
251                    src_base,
252                    layout.bytes_per_sample,
253                    "strip source sample range",
254                )?;
255                let dest_end = checked_layout_add(
256                    dest_base,
257                    layout.bytes_per_sample,
258                    "strip output sample range",
259                )?;
260                dest_row[dest_base..dest_end].copy_from_slice(&src_row[src_base..src_end]);
261            }
262        }
263    } else {
264        let src_row_bytes = layout.checked_sample_plane_row_bytes()?;
265        let src_col_offset = checked_layout_mul(
266            window.col_off,
267            layout.bytes_per_sample,
268            "strip source column offset",
269        )?;
270        let copy_bytes_per_row = output_row_bytes;
271        for row in copy_row_start..copy_row_end {
272            let src_row_index = row - spec.row_start;
273            let dest_row_index = row - window.row_off;
274            let src_offset = checked_layout_add(
275                checked_layout_mul(src_row_index, src_row_bytes, "strip source row offset")?,
276                src_col_offset,
277                "strip source offset",
278            )?;
279            let dest_offset =
280                checked_layout_mul(dest_row_index, output_row_bytes, "strip output row offset")?;
281            let src_end =
282                checked_layout_add(src_offset, copy_bytes_per_row, "strip source copy range")?;
283            let dest_end =
284                checked_layout_add(dest_offset, copy_bytes_per_row, "strip output copy range")?;
285            output[dest_offset..dest_end].copy_from_slice(&block[src_offset..src_end]);
286        }
287    }
288    Ok(())
289}
290
291fn collect_strip_specs_for_window(
292    ifd: &Ifd,
293    layout: &RasterLayout,
294    window: Window,
295    band_index: Option<usize>,
296) -> Result<Vec<StripBlockSpec>> {
297    let offsets = ifd
298        .strip_offsets()
299        .ok_or(Error::TagNotFound(crate::ifd::TAG_STRIP_OFFSETS))?;
300    let counts = ifd
301        .strip_byte_counts()
302        .ok_or(Error::TagNotFound(crate::ifd::TAG_STRIP_BYTE_COUNTS))?;
303    if offsets.len() != counts.len() {
304        return Err(Error::InvalidImageLayout(format!(
305            "StripOffsets has {} entries but StripByteCounts has {}",
306            offsets.len(),
307            counts.len()
308        )));
309    }
310
311    let rows_per_strip = ifd.rows_per_strip();
312    if rows_per_strip == 0 {
313        return Err(Error::InvalidImageLayout(
314            "RowsPerStrip must be greater than zero".into(),
315        ));
316    }
317    let rows_per_strip = rows_per_strip as usize;
318    let strips_per_plane = layout.height.div_ceil(rows_per_strip);
319    let expected = match layout.planar_configuration {
320        1 => strips_per_plane,
321        2 => strips_per_plane
322            .checked_mul(layout.samples_per_pixel)
323            .ok_or_else(strip_count_overflow)?,
324        planar => return Err(Error::UnsupportedPlanarConfiguration(planar)),
325    };
326    if offsets.len() != expected {
327        return Err(Error::InvalidImageLayout(format!(
328            "expected {expected} strips, found {}",
329            offsets.len()
330        )));
331    }
332
333    let first_strip = window.row_off / rows_per_strip;
334    let last_strip = window
335        .row_end()
336        .div_ceil(rows_per_strip)
337        .min(strips_per_plane);
338    let plane_range = if layout.planar_configuration == 1 {
339        0..1
340    } else if let Some(band_index) = band_index {
341        band_index..band_index + 1
342    } else {
343        0..layout.samples_per_pixel
344    };
345    let spec_count = (last_strip - first_strip)
346        .checked_mul(plane_range.end - plane_range.start)
347        .ok_or_else(strip_count_overflow)?;
348    let mut specs = Vec::with_capacity(spec_count);
349
350    for plane in plane_range {
351        for plane_strip_index in first_strip..last_strip {
352            let strip_index = if layout.planar_configuration == 1 {
353                plane_strip_index
354            } else {
355                plane
356                    .checked_mul(strips_per_plane)
357                    .and_then(|base| base.checked_add(plane_strip_index))
358                    .ok_or_else(strip_count_overflow)?
359            };
360            let row_start = plane_strip_index
361                .checked_mul(rows_per_strip)
362                .ok_or_else(strip_count_overflow)?;
363            let rows_in_strip = rows_per_strip.min(layout.height.saturating_sub(row_start));
364            specs.push(StripBlockSpec {
365                index: strip_index,
366                plane,
367                row_start,
368                offset: offsets[strip_index],
369                byte_count: counts[strip_index],
370                rows_in_strip,
371            });
372        }
373    }
374
375    Ok(specs)
376}
377
378fn strip_count_overflow() -> Error {
379    Error::InvalidImageLayout("strip count overflows usize".into())
380}
381
382#[derive(Clone, Copy)]
383struct StripBlockSpec {
384    index: usize,
385    plane: usize,
386    row_start: usize,
387    offset: u64,
388    byte_count: u64,
389    rows_in_strip: usize,
390}
391
392fn read_strip_block(
393    source: &dyn TiffSource,
394    ifd_offset: u64,
395    cache: &BlockCache,
396    spec: StripBlockSpec,
397    context: &block_decode::BlockDecodeContext<'_>,
398    options: DecodeReadOptions<'_>,
399) -> Result<Arc<Vec<u8>>> {
400    let decode_request = block_decode::BlockDecodeRequest {
401        context,
402        compressed: &[],
403        index: spec.index,
404        block_width: context.layout.width,
405        block_height: spec.rows_in_strip,
406    };
407    let decoded_len = block_decode::decoded_block_len(&decode_request)?;
408    validate_decode_output_len(decoded_len, options.decode_output_bytes)?;
409
410    let cache_key = BlockKey {
411        ifd_offset,
412        kind: BlockKind::Strip,
413        block_index: spec.index,
414    };
415    if let Some(cached) = cache.get(&cache_key) {
416        return Ok(cached);
417    }
418
419    // GDAL SPARSE_OK semantics: a block with no on-disk payload (zero offset
420    // or zero byte count) decodes as implicit zero fill.
421    if spec.offset == 0 || spec.byte_count == 0 {
422        let decoded = allocate_decode_output(decoded_len, options.decode_output_bytes)?;
423        return Ok(cache.insert(cache_key, decoded));
424    }
425
426    let byte_count_limit = block_decode::compressed_block_byte_count_limit(&decode_request)?;
427    let compressed = match options.gdal_structural_metadata {
428        Some(metadata) => read_gdal_block_payload(
429            source,
430            metadata,
431            context.byte_order,
432            spec.offset,
433            spec.byte_count,
434            byte_count_limit,
435            spec.index,
436        )?,
437        None => read_block_payload(
438            source,
439            spec.offset,
440            spec.byte_count,
441            byte_count_limit,
442            spec.index,
443        )?,
444    };
445
446    let decoded = block_decode::decode_compressed_block(block_decode::BlockDecodeRequest {
447        context,
448        compressed: &compressed,
449        index: spec.index,
450        block_width: context.layout.width,
451        block_height: spec.rows_in_strip,
452    })?;
453    Ok(cache.insert(cache_key, decoded))
454}