Skip to main content

tiff_reader/
lib.rs

1//! Pure-Rust, read-only TIFF and BigTIFF decoder.
2//!
3//! Supports:
4//! - **TIFF** (classic): `II`/`MM` byte order mark + version 42
5//! - **BigTIFF**: `II`/`MM` byte order mark + version 43
6//! - **Sources**: mmap, in-memory bytes, or any custom random-access source
7//! - **Reads**: full rasters, windows, and single storage-domain bands
8//! - **Compression**: None, Deflate, LZW, PackBits, LERC, JPEG (feature), ZSTD (feature)
9//!
10//! TIFF-side `LERC+DEFLATE` is supported unconditionally. TIFF-side
11//! `LERC+ZSTD` requires the default `zstd` feature.
12//!
13//! # Example
14//!
15//! ```no_run
16//! use tiff_reader::TiffFile;
17//!
18//! let file = TiffFile::open("image.tif").unwrap();
19//! println!("byte order: {:?}", file.byte_order());
20//! println!("IFD count: {}", file.ifd_count());
21//!
22//! let ifd = file.ifd(0).unwrap();
23//! println!("  width: {}", ifd.width());
24//! println!("  height: {}", ifd.height());
25//! println!("  bits per sample: {:?}", ifd.bits_per_sample());
26//!
27//! let samples: ndarray::ArrayD<u16> = file.read_image(0).unwrap();
28//! ```
29
30mod block_decode;
31pub mod cache;
32pub mod error;
33pub mod filters;
34pub mod header;
35pub mod ifd;
36pub mod io;
37mod pixel;
38pub mod source;
39pub mod strip;
40pub mod tag;
41pub mod tile;
42
43use std::path::Path;
44use std::sync::Arc;
45
46use cache::BlockCache;
47use error::{Error, Result};
48use ndarray::{ArrayD, IxDyn};
49use source::{BytesSource, MmapSource, SharedSource, TiffSource};
50
51pub use error::Error as TiffError;
52pub use header::ByteOrder;
53pub use ifd::{Ifd, RasterLayout};
54pub use tag::{Tag, TagValue};
55pub use tiff_core::constants;
56pub use tiff_core::sample::TiffSample;
57pub use tiff_core::TagType;
58pub use tiff_core::{
59    ColorMap, ColorModel, ExtraSample, InkSet, PhotometricInterpretation, YCbCrPositioning,
60};
61
62/// Configuration for opening a TIFF file.
63#[derive(Debug, Clone, Copy)]
64pub struct OpenOptions {
65    /// Maximum bytes held in the decoded strip/tile cache.
66    pub block_cache_bytes: usize,
67    /// Maximum number of cached strips/tiles.
68    pub block_cache_slots: usize,
69}
70
71impl Default for OpenOptions {
72    fn default() -> Self {
73        Self {
74            block_cache_bytes: 64 * 1024 * 1024,
75            block_cache_slots: 257,
76        }
77    }
78}
79
80/// A memory-mapped TIFF file handle.
81pub struct TiffFile {
82    source: SharedSource,
83    header: header::TiffHeader,
84    ifds: Vec<ifd::Ifd>,
85    block_cache: Arc<BlockCache>,
86    gdal_structural_metadata: Option<GdalStructuralMetadata>,
87}
88
89#[derive(Debug, Clone, Copy)]
90pub(crate) struct GdalStructuralMetadata {
91    block_leader_size_as_u32: bool,
92    block_trailer_repeats_last_4_bytes: bool,
93}
94
95#[derive(Debug, Clone, Copy, PartialEq, Eq)]
96pub(crate) struct Window {
97    pub row_off: usize,
98    pub col_off: usize,
99    pub rows: usize,
100    pub cols: usize,
101}
102
103impl Window {
104    pub(crate) fn is_empty(self) -> bool {
105        self.rows == 0 || self.cols == 0
106    }
107
108    pub(crate) fn row_end(self) -> usize {
109        self.row_off + self.rows
110    }
111
112    pub(crate) fn col_end(self) -> usize {
113        self.col_off + self.cols
114    }
115
116    pub(crate) fn output_len(self, layout: &RasterLayout) -> Result<usize> {
117        self.cols
118            .checked_mul(self.rows)
119            .and_then(|pixels| pixels.checked_mul(layout.pixel_stride_bytes()))
120            .ok_or_else(|| Error::InvalidImageLayout("window size overflows usize".into()))
121    }
122
123    pub(crate) fn band_output_len(self, layout: &RasterLayout) -> Result<usize> {
124        self.cols
125            .checked_mul(self.rows)
126            .and_then(|pixels| pixels.checked_mul(layout.bytes_per_sample))
127            .ok_or_else(|| Error::InvalidImageLayout("window band size overflows usize".into()))
128    }
129}
130
131impl GdalStructuralMetadata {
132    fn from_prefix(bytes: &[u8]) -> Option<Self> {
133        let text = std::str::from_utf8(bytes).ok()?;
134        if !text.contains("GDAL_STRUCTURAL_METADATA_SIZE=") {
135            return None;
136        }
137
138        Some(Self {
139            block_leader_size_as_u32: text.contains("BLOCK_LEADER=SIZE_AS_UINT4"),
140            block_trailer_repeats_last_4_bytes: text
141                .contains("BLOCK_TRAILER=LAST_4_BYTES_REPEATED"),
142        })
143    }
144
145    pub(crate) fn unwrap_block<'a>(
146        &self,
147        raw: &'a [u8],
148        byte_order: ByteOrder,
149        offset: u64,
150    ) -> Result<&'a [u8]> {
151        if self.block_leader_size_as_u32 {
152            if raw.len() < 4 {
153                return Ok(raw);
154            }
155            let declared_len = match byte_order {
156                ByteOrder::LittleEndian => u32::from_le_bytes(raw[..4].try_into().unwrap()),
157                ByteOrder::BigEndian => u32::from_be_bytes(raw[..4].try_into().unwrap()),
158            } as usize;
159            if let Some(payload_end) = 4usize.checked_add(declared_len) {
160                if payload_end <= raw.len() {
161                    if self.block_trailer_repeats_last_4_bytes {
162                        let trailer_end = payload_end.checked_add(4).ok_or_else(|| {
163                            Error::InvalidImageLayout("GDAL block trailer overflows usize".into())
164                        })?;
165                        if trailer_end <= raw.len() {
166                            let expected = &raw[payload_end - 4..payload_end];
167                            let trailer = &raw[payload_end..trailer_end];
168                            if expected != trailer {
169                                return Err(Error::InvalidImageLayout(format!(
170                                    "GDAL block trailer mismatch at offset {offset}"
171                                )));
172                            }
173                        }
174                    }
175                    return Ok(&raw[4..payload_end]);
176                }
177            }
178        }
179
180        if self.block_trailer_repeats_last_4_bytes && raw.len() >= 8 {
181            let split = raw.len() - 4;
182            if raw[split - 4..split] == raw[split..] {
183                return Ok(&raw[..split]);
184            }
185        }
186
187        Ok(raw)
188    }
189}
190
191pub(crate) fn read_gdal_block_payload(
192    source: &dyn TiffSource,
193    metadata: &GdalStructuralMetadata,
194    byte_order: ByteOrder,
195    offset: u64,
196    byte_count: u64,
197) -> Result<Vec<u8>> {
198    let wrapped_extra = 4u64
199        .checked_add(if metadata.block_trailer_repeats_last_4_bytes {
200            4
201        } else {
202            0
203        })
204        .ok_or_else(|| Error::InvalidImageLayout("GDAL block wrapper overflows u64".into()))?;
205
206    let mut candidates = Vec::with_capacity(2);
207    if metadata.block_leader_size_as_u32 && offset >= 4 {
208        candidates.push((
209            offset - 4,
210            byte_count.checked_add(wrapped_extra).ok_or_else(|| {
211                Error::InvalidImageLayout("GDAL wrapped block length overflows u64".into())
212            })?,
213        ));
214    }
215    candidates.push((offset, byte_count));
216
217    let mut fallback: Option<Result<Vec<u8>>> = None;
218    for (candidate_offset, candidate_len) in candidates {
219        let len = usize::try_from(candidate_len).map_err(|_| Error::OffsetOutOfBounds {
220            offset: candidate_offset,
221            length: candidate_len,
222            data_len: source.len(),
223        })?;
224        let raw = match source.read_exact_at(candidate_offset, len) {
225            Ok(raw) => raw,
226            Err(err) => {
227                if fallback.is_none() {
228                    fallback = Some(Err(err));
229                }
230                continue;
231            }
232        };
233        match metadata.unwrap_block(&raw, byte_order, candidate_offset) {
234            Ok(payload) => {
235                if candidate_offset != offset
236                    && payload.len() == usize::try_from(byte_count).unwrap_or(usize::MAX)
237                {
238                    return Ok(payload.to_vec());
239                }
240                fallback = Some(Ok(payload.to_vec()));
241            }
242            Err(err) => {
243                if fallback.is_none() {
244                    fallback = Some(Err(err));
245                }
246            }
247        }
248    }
249
250    match fallback {
251        Some(result) => result,
252        None => Ok(Vec::new()),
253    }
254}
255
256const GDAL_STRUCTURAL_METADATA_PREFIX: &str = "GDAL_STRUCTURAL_METADATA_SIZE=";
257
258// TiffSample trait and impls are provided by tiff-core and re-exported above.
259
260impl TiffFile {
261    /// Open a TIFF file from disk using memory-mapped I/O.
262    pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
263        Self::open_with_options(path, OpenOptions::default())
264    }
265
266    /// Open a TIFF file from disk with explicit decoder options.
267    pub fn open_with_options<P: AsRef<Path>>(path: P, options: OpenOptions) -> Result<Self> {
268        let source: SharedSource = Arc::new(MmapSource::open(path.as_ref())?);
269        Self::from_source_with_options(source, options)
270    }
271
272    /// Open a TIFF file from an owned byte buffer (WASM-compatible).
273    pub fn from_bytes(data: Vec<u8>) -> Result<Self> {
274        Self::from_bytes_with_options(data, OpenOptions::default())
275    }
276
277    /// Open a TIFF file from bytes with explicit decoder options.
278    pub fn from_bytes_with_options(data: Vec<u8>, options: OpenOptions) -> Result<Self> {
279        let source: SharedSource = Arc::new(BytesSource::new(data));
280        Self::from_source_with_options(source, options)
281    }
282
283    /// Open a TIFF file from an arbitrary random-access source.
284    pub fn from_source(source: SharedSource) -> Result<Self> {
285        Self::from_source_with_options(source, OpenOptions::default())
286    }
287
288    /// Open a TIFF file from an arbitrary random-access source with options.
289    pub fn from_source_with_options(source: SharedSource, options: OpenOptions) -> Result<Self> {
290        let header_len = usize::try_from(source.len().min(16)).unwrap_or(16);
291        let header_bytes = source.read_exact_at(0, header_len)?;
292        let header = header::TiffHeader::parse(&header_bytes)?;
293        let gdal_structural_metadata = parse_gdal_structural_metadata(source.as_ref());
294        let ifds = ifd::parse_ifd_chain(source.as_ref(), &header)?;
295        Ok(Self {
296            source,
297            header,
298            ifds,
299            block_cache: Arc::new(BlockCache::new(
300                options.block_cache_bytes,
301                options.block_cache_slots,
302            )),
303            gdal_structural_metadata,
304        })
305    }
306
307    /// Returns the byte order of the TIFF file.
308    pub fn byte_order(&self) -> ByteOrder {
309        self.header.byte_order
310    }
311
312    /// Returns `true` if this is a BigTIFF file.
313    pub fn is_bigtiff(&self) -> bool {
314        self.header.is_bigtiff()
315    }
316
317    /// Returns the number of IFDs (images/pages) in the file.
318    pub fn ifd_count(&self) -> usize {
319        self.ifds.len()
320    }
321
322    /// Returns the IFD at the given index.
323    pub fn ifd(&self, index: usize) -> Result<&Ifd> {
324        self.ifds.get(index).ok_or(Error::IfdNotFound(index))
325    }
326
327    /// Returns all parsed IFDs.
328    pub fn ifds(&self) -> &[Ifd] {
329        &self.ifds
330    }
331
332    /// Returns the raw file bytes.
333    pub fn raw_bytes(&self) -> Option<&[u8]> {
334        self.source.as_slice()
335    }
336
337    /// Returns the backing source.
338    pub fn source(&self) -> &dyn TiffSource {
339        self.source.as_ref()
340    }
341
342    /// Parse an IFD at an arbitrary file offset.
343    pub fn read_ifd_at_offset(&self, offset: u64) -> Result<Ifd> {
344        ifd::parse_ifd_at(self.source.as_ref(), &self.header, offset)
345    }
346
347    /// Decode an image into native-endian interleaved storage sample bytes.
348    pub fn read_image_bytes(&self, ifd_index: usize) -> Result<Vec<u8>> {
349        let ifd = self.ifd(ifd_index)?;
350        self.read_image_bytes_from_ifd(ifd)
351    }
352
353    /// Decode an arbitrary IFD into native-endian interleaved storage sample bytes.
354    pub fn read_image_bytes_from_ifd(&self, ifd: &Ifd) -> Result<Vec<u8>> {
355        self.read_image_sample_bytes_from_ifd(ifd)
356    }
357
358    /// Decode an image into native-endian interleaved color-decoded pixel bytes.
359    pub fn read_decoded_image_bytes(&self, ifd_index: usize) -> Result<Vec<u8>> {
360        let ifd = self.ifd(ifd_index)?;
361        self.read_decoded_image_bytes_from_ifd(ifd)
362    }
363
364    /// Decode an arbitrary IFD into native-endian interleaved color-decoded
365    /// pixel bytes.
366    pub fn read_decoded_image_bytes_from_ifd(&self, ifd: &Ifd) -> Result<Vec<u8>> {
367        let layout = ifd.decoded_raster_layout()?;
368        self.decode_window_pixel_bytes(
369            ifd,
370            Window {
371                row_off: 0,
372                col_off: 0,
373                rows: layout.height,
374                cols: layout.width,
375            },
376        )
377    }
378
379    /// Decode an image into native-endian interleaved storage sample bytes.
380    ///
381    /// This is an explicit alias for [`Self::read_image_bytes`].
382    pub fn read_image_sample_bytes(&self, ifd_index: usize) -> Result<Vec<u8>> {
383        let ifd = self.ifd(ifd_index)?;
384        self.read_image_sample_bytes_from_ifd(ifd)
385    }
386
387    /// Decode an arbitrary IFD into native-endian interleaved storage sample
388    /// bytes.
389    ///
390    /// This is an explicit alias for [`Self::read_image_bytes_from_ifd`].
391    pub fn read_image_sample_bytes_from_ifd(&self, ifd: &Ifd) -> Result<Vec<u8>> {
392        let layout = ifd.raster_layout()?;
393        self.decode_window_sample_bytes(
394            ifd,
395            Window {
396                row_off: 0,
397                col_off: 0,
398                rows: layout.height,
399                cols: layout.width,
400            },
401        )
402    }
403
404    /// Decode a pixel window into native-endian interleaved storage sample
405    /// bytes.
406    pub fn read_window_bytes(
407        &self,
408        ifd_index: usize,
409        row_off: usize,
410        col_off: usize,
411        rows: usize,
412        cols: usize,
413    ) -> Result<Vec<u8>> {
414        let ifd = self.ifd(ifd_index)?;
415        self.read_window_bytes_from_ifd(ifd, row_off, col_off, rows, cols)
416    }
417
418    /// Decode a pixel window into native-endian interleaved color-decoded pixel
419    /// bytes.
420    pub fn read_decoded_window_bytes(
421        &self,
422        ifd_index: usize,
423        row_off: usize,
424        col_off: usize,
425        rows: usize,
426        cols: usize,
427    ) -> Result<Vec<u8>> {
428        let ifd = self.ifd(ifd_index)?;
429        self.read_decoded_window_bytes_from_ifd(ifd, row_off, col_off, rows, cols)
430    }
431
432    /// Decode a pixel window into native-endian interleaved storage sample
433    /// bytes.
434    ///
435    /// This is an explicit alias for [`Self::read_window_bytes`].
436    pub fn read_window_sample_bytes(
437        &self,
438        ifd_index: usize,
439        row_off: usize,
440        col_off: usize,
441        rows: usize,
442        cols: usize,
443    ) -> Result<Vec<u8>> {
444        let ifd = self.ifd(ifd_index)?;
445        self.read_window_sample_bytes_from_ifd(ifd, row_off, col_off, rows, cols)
446    }
447
448    /// Decode a pixel window from an arbitrary IFD into native-endian
449    /// interleaved storage sample bytes.
450    pub fn read_window_bytes_from_ifd(
451        &self,
452        ifd: &Ifd,
453        row_off: usize,
454        col_off: usize,
455        rows: usize,
456        cols: usize,
457    ) -> Result<Vec<u8>> {
458        self.read_window_sample_bytes_from_ifd(ifd, row_off, col_off, rows, cols)
459    }
460
461    /// Decode a pixel window from an arbitrary IFD into native-endian
462    /// interleaved color-decoded pixel bytes.
463    pub fn read_decoded_window_bytes_from_ifd(
464        &self,
465        ifd: &Ifd,
466        row_off: usize,
467        col_off: usize,
468        rows: usize,
469        cols: usize,
470    ) -> Result<Vec<u8>> {
471        let layout = ifd.decoded_raster_layout()?;
472        let window = validate_window(&layout, row_off, col_off, rows, cols)?;
473        self.decode_window_pixel_bytes(ifd, window)
474    }
475
476    /// Decode a pixel window from an arbitrary IFD into native-endian
477    /// interleaved storage sample bytes.
478    ///
479    /// This is an explicit alias for [`Self::read_window_bytes_from_ifd`].
480    pub fn read_window_sample_bytes_from_ifd(
481        &self,
482        ifd: &Ifd,
483        row_off: usize,
484        col_off: usize,
485        rows: usize,
486        cols: usize,
487    ) -> Result<Vec<u8>> {
488        let layout = ifd.raster_layout()?;
489        let window = validate_window(&layout, row_off, col_off, rows, cols)?;
490        self.decode_window_sample_bytes(ifd, window)
491    }
492
493    /// Decode a single storage-domain band into native-endian sample bytes.
494    pub fn read_band_bytes(&self, ifd_index: usize, band_index: usize) -> Result<Vec<u8>> {
495        let ifd = self.ifd(ifd_index)?;
496        self.read_band_bytes_from_ifd(ifd, band_index)
497    }
498
499    /// Decode a single storage-domain band from an arbitrary IFD into
500    /// native-endian sample bytes.
501    pub fn read_band_bytes_from_ifd(&self, ifd: &Ifd, band_index: usize) -> Result<Vec<u8>> {
502        let layout = ifd.raster_layout()?;
503        self.read_band_window_bytes_from_ifd(ifd, band_index, 0, 0, layout.height, layout.width)
504    }
505
506    /// Decode a pixel window from one storage-domain band into native-endian
507    /// sample bytes.
508    pub fn read_band_window_bytes(
509        &self,
510        ifd_index: usize,
511        band_index: usize,
512        row_off: usize,
513        col_off: usize,
514        rows: usize,
515        cols: usize,
516    ) -> Result<Vec<u8>> {
517        let ifd = self.ifd(ifd_index)?;
518        self.read_band_window_bytes_from_ifd(ifd, band_index, row_off, col_off, rows, cols)
519    }
520
521    /// Decode a pixel window from one storage-domain band in an arbitrary IFD
522    /// into native-endian sample bytes.
523    pub fn read_band_window_bytes_from_ifd(
524        &self,
525        ifd: &Ifd,
526        band_index: usize,
527        row_off: usize,
528        col_off: usize,
529        rows: usize,
530        cols: usize,
531    ) -> Result<Vec<u8>> {
532        let layout = ifd.raster_layout()?;
533        validate_band_index(&layout, band_index)?;
534        let window = validate_window(&layout, row_off, col_off, rows, cols)?;
535        self.decode_window_sample_band_bytes(ifd, window, band_index)
536    }
537
538    fn decode_window_sample_bytes(&self, ifd: &Ifd, window: Window) -> Result<Vec<u8>> {
539        if window.is_empty() {
540            return Ok(Vec::new());
541        }
542
543        if ifd.is_tiled() {
544            tile::read_window(
545                self.source.as_ref(),
546                ifd,
547                self.byte_order(),
548                &self.block_cache,
549                window,
550                self.gdal_structural_metadata.as_ref(),
551            )
552        } else {
553            strip::read_window(
554                self.source.as_ref(),
555                ifd,
556                self.byte_order(),
557                &self.block_cache,
558                window,
559                self.gdal_structural_metadata.as_ref(),
560            )
561        }
562    }
563
564    fn decode_window_sample_band_bytes(
565        &self,
566        ifd: &Ifd,
567        window: Window,
568        band_index: usize,
569    ) -> Result<Vec<u8>> {
570        if window.is_empty() {
571            return Ok(Vec::new());
572        }
573
574        let layout = ifd.raster_layout()?;
575        validate_band_index(&layout, band_index)?;
576        if ifd.is_tiled() {
577            tile::read_window_band(
578                self.source.as_ref(),
579                ifd,
580                self.byte_order(),
581                &self.block_cache,
582                window,
583                band_index,
584                self.gdal_structural_metadata.as_ref(),
585            )
586        } else {
587            strip::read_window_band(
588                self.source.as_ref(),
589                ifd,
590                self.byte_order(),
591                &self.block_cache,
592                window,
593                band_index,
594                self.gdal_structural_metadata.as_ref(),
595            )
596        }
597    }
598
599    fn decode_window_pixel_bytes(&self, ifd: &Ifd, window: Window) -> Result<Vec<u8>> {
600        let storage_layout = ifd.raster_layout()?;
601        let sample_bytes = self.decode_window_sample_bytes(ifd, window)?;
602        let (_, pixels) = pixel::decode_pixels(
603            ifd,
604            &storage_layout,
605            window.cols,
606            window.rows,
607            &sample_bytes,
608        )?;
609        Ok(pixels)
610    }
611
612    /// Decode a window into a typed ndarray of storage-domain samples.
613    ///
614    /// Single-band rasters are returned as shape `[rows, cols]`.
615    /// Multi-band rasters are returned as shape `[rows, cols, samples_per_pixel]`.
616    pub fn read_window<T: TiffSample>(
617        &self,
618        ifd_index: usize,
619        row_off: usize,
620        col_off: usize,
621        rows: usize,
622        cols: usize,
623    ) -> Result<ArrayD<T>> {
624        let ifd = self.ifd(ifd_index)?;
625        self.read_window_from_ifd(ifd, row_off, col_off, rows, cols)
626    }
627
628    /// Decode a window from an arbitrary IFD into a typed ndarray of
629    /// storage-domain samples.
630    pub fn read_window_from_ifd<T: TiffSample>(
631        &self,
632        ifd: &Ifd,
633        row_off: usize,
634        col_off: usize,
635        rows: usize,
636        cols: usize,
637    ) -> Result<ArrayD<T>> {
638        self.read_window_samples_from_ifd(ifd, row_off, col_off, rows, cols)
639    }
640
641    /// Decode a window into a typed ndarray of color-decoded pixels.
642    ///
643    /// Single-channel decoded rasters are returned as shape `[rows, cols]`.
644    /// Multi-channel decoded rasters are returned as shape `[rows, cols, channels]`.
645    pub fn read_decoded_window<T: TiffSample>(
646        &self,
647        ifd_index: usize,
648        row_off: usize,
649        col_off: usize,
650        rows: usize,
651        cols: usize,
652    ) -> Result<ArrayD<T>> {
653        let ifd = self.ifd(ifd_index)?;
654        self.read_decoded_window_from_ifd(ifd, row_off, col_off, rows, cols)
655    }
656
657    /// Decode a window from an arbitrary IFD into a typed ndarray of
658    /// color-decoded pixels.
659    pub fn read_decoded_window_from_ifd<T: TiffSample>(
660        &self,
661        ifd: &Ifd,
662        row_off: usize,
663        col_off: usize,
664        rows: usize,
665        cols: usize,
666    ) -> Result<ArrayD<T>> {
667        let layout = ifd.decoded_raster_layout()?;
668        let window = validate_window(&layout, row_off, col_off, rows, cols)?;
669        if !T::matches_layout(&layout) {
670            return Err(Error::TypeMismatch {
671                expected: T::type_name(),
672                actual: format!(
673                    "sample_format={} bits_per_sample={}",
674                    layout.sample_format, layout.bits_per_sample
675                ),
676            });
677        }
678
679        let decoded = self.decode_window_pixel_bytes(ifd, window)?;
680        let values = T::decode_many(&decoded);
681        let shape = if layout.samples_per_pixel == 1 {
682            vec![window.rows, window.cols]
683        } else {
684            vec![window.rows, window.cols, layout.samples_per_pixel]
685        };
686        ArrayD::from_shape_vec(IxDyn(&shape), values).map_err(|e| {
687            Error::InvalidImageLayout(format!("failed to build ndarray from decoded raster: {e}"))
688        })
689    }
690
691    /// Decode a window into a typed ndarray of storage-domain samples.
692    ///
693    /// Single-band rasters are returned as shape `[rows, cols]`.
694    /// Multi-band rasters are returned as shape `[rows, cols, samples_per_pixel]`.
695    pub fn read_window_samples<T: TiffSample>(
696        &self,
697        ifd_index: usize,
698        row_off: usize,
699        col_off: usize,
700        rows: usize,
701        cols: usize,
702    ) -> Result<ArrayD<T>> {
703        let ifd = self.ifd(ifd_index)?;
704        self.read_window_samples_from_ifd(ifd, row_off, col_off, rows, cols)
705    }
706
707    /// Decode a window from an arbitrary IFD into a typed ndarray of
708    /// storage-domain samples.
709    pub fn read_window_samples_from_ifd<T: TiffSample>(
710        &self,
711        ifd: &Ifd,
712        row_off: usize,
713        col_off: usize,
714        rows: usize,
715        cols: usize,
716    ) -> Result<ArrayD<T>> {
717        let layout = ifd.raster_layout()?;
718        let window = validate_window(&layout, row_off, col_off, rows, cols)?;
719        if !T::matches_layout(&layout) {
720            return Err(Error::TypeMismatch {
721                expected: T::type_name(),
722                actual: format!(
723                    "sample_format={} bits_per_sample={}",
724                    layout.sample_format, layout.bits_per_sample
725                ),
726            });
727        }
728
729        let decoded = self.decode_window_sample_bytes(ifd, window)?;
730        let values = T::decode_many(&decoded);
731        let shape = if layout.samples_per_pixel == 1 {
732            vec![window.rows, window.cols]
733        } else {
734            vec![window.rows, window.cols, layout.samples_per_pixel]
735        };
736        ArrayD::from_shape_vec(IxDyn(&shape), values).map_err(|e| {
737            Error::InvalidImageLayout(format!("failed to build ndarray from storage raster: {e}"))
738        })
739    }
740
741    /// Decode one storage-domain band into a typed `[height, width]` ndarray.
742    pub fn read_band<T: TiffSample>(
743        &self,
744        ifd_index: usize,
745        band_index: usize,
746    ) -> Result<ArrayD<T>> {
747        let ifd = self.ifd(ifd_index)?;
748        self.read_band_from_ifd(ifd, band_index)
749    }
750
751    /// Decode one storage-domain band from an arbitrary IFD into a typed
752    /// `[height, width]` ndarray.
753    pub fn read_band_from_ifd<T: TiffSample>(
754        &self,
755        ifd: &Ifd,
756        band_index: usize,
757    ) -> Result<ArrayD<T>> {
758        let layout = ifd.raster_layout()?;
759        self.read_band_window_from_ifd(ifd, band_index, 0, 0, layout.height, layout.width)
760    }
761
762    /// Decode a window from one storage-domain band into a typed
763    /// `[rows, cols]` ndarray.
764    pub fn read_band_window<T: TiffSample>(
765        &self,
766        ifd_index: usize,
767        band_index: usize,
768        row_off: usize,
769        col_off: usize,
770        rows: usize,
771        cols: usize,
772    ) -> Result<ArrayD<T>> {
773        let ifd = self.ifd(ifd_index)?;
774        self.read_band_window_from_ifd(ifd, band_index, row_off, col_off, rows, cols)
775    }
776
777    /// Decode a window from one storage-domain band in an arbitrary IFD into a
778    /// typed `[rows, cols]` ndarray.
779    pub fn read_band_window_from_ifd<T: TiffSample>(
780        &self,
781        ifd: &Ifd,
782        band_index: usize,
783        row_off: usize,
784        col_off: usize,
785        rows: usize,
786        cols: usize,
787    ) -> Result<ArrayD<T>> {
788        let layout = ifd.raster_layout()?;
789        validate_band_index(&layout, band_index)?;
790        let window = validate_window(&layout, row_off, col_off, rows, cols)?;
791        if !T::matches_layout(&layout) {
792            return Err(Error::TypeMismatch {
793                expected: T::type_name(),
794                actual: format!(
795                    "sample_format={} bits_per_sample={}",
796                    layout.sample_format, layout.bits_per_sample
797                ),
798            });
799        }
800
801        let decoded = self.decode_window_sample_band_bytes(ifd, window, band_index)?;
802        let values = T::decode_many(&decoded);
803        ArrayD::from_shape_vec(IxDyn(&[window.rows, window.cols]), values).map_err(|e| {
804            Error::InvalidImageLayout(format!("failed to build ndarray from band raster: {e}"))
805        })
806    }
807
808    /// Decode an image into a typed ndarray of storage-domain samples.
809    ///
810    /// Single-band rasters are returned as shape `[height, width]`.
811    /// Multi-band rasters are returned as shape `[height, width, samples_per_pixel]`.
812    pub fn read_image<T: TiffSample>(&self, ifd_index: usize) -> Result<ArrayD<T>> {
813        let ifd = self.ifd(ifd_index)?;
814        self.read_image_from_ifd(ifd)
815    }
816
817    /// Decode an arbitrary IFD into a typed ndarray of storage-domain samples.
818    pub fn read_image_from_ifd<T: TiffSample>(&self, ifd: &Ifd) -> Result<ArrayD<T>> {
819        self.read_image_samples_from_ifd(ifd)
820    }
821
822    /// Decode an image into a typed ndarray of color-decoded pixels.
823    ///
824    /// Single-channel decoded rasters are returned as shape `[height, width]`.
825    /// Multi-channel decoded rasters are returned as shape
826    /// `[height, width, channels]`.
827    pub fn read_decoded_image<T: TiffSample>(&self, ifd_index: usize) -> Result<ArrayD<T>> {
828        let ifd = self.ifd(ifd_index)?;
829        self.read_decoded_image_from_ifd(ifd)
830    }
831
832    /// Decode an arbitrary IFD into a typed ndarray of color-decoded pixels.
833    pub fn read_decoded_image_from_ifd<T: TiffSample>(&self, ifd: &Ifd) -> Result<ArrayD<T>> {
834        let layout = ifd.decoded_raster_layout()?;
835        if !T::matches_layout(&layout) {
836            return Err(Error::TypeMismatch {
837                expected: T::type_name(),
838                actual: format!(
839                    "sample_format={} bits_per_sample={}",
840                    layout.sample_format, layout.bits_per_sample
841                ),
842            });
843        }
844
845        self.read_decoded_window_from_ifd(ifd, 0, 0, layout.height, layout.width)
846    }
847
848    /// Decode an image into a typed ndarray of storage-domain samples.
849    ///
850    /// This is an explicit alias for [`Self::read_image`].
851    pub fn read_image_samples<T: TiffSample>(&self, ifd_index: usize) -> Result<ArrayD<T>> {
852        let ifd = self.ifd(ifd_index)?;
853        self.read_image_samples_from_ifd(ifd)
854    }
855
856    /// Decode an arbitrary IFD into a typed ndarray of storage-domain samples.
857    ///
858    /// This is an explicit alias for [`Self::read_image_from_ifd`].
859    pub fn read_image_samples_from_ifd<T: TiffSample>(&self, ifd: &Ifd) -> Result<ArrayD<T>> {
860        let layout = ifd.raster_layout()?;
861        if !T::matches_layout(&layout) {
862            return Err(Error::TypeMismatch {
863                expected: T::type_name(),
864                actual: format!(
865                    "sample_format={} bits_per_sample={}",
866                    layout.sample_format, layout.bits_per_sample
867                ),
868            });
869        }
870
871        self.read_window_samples_from_ifd(ifd, 0, 0, layout.height, layout.width)
872    }
873}
874
875fn validate_window(
876    layout: &RasterLayout,
877    row_off: usize,
878    col_off: usize,
879    rows: usize,
880    cols: usize,
881) -> Result<Window> {
882    let row_end = row_off
883        .checked_add(rows)
884        .ok_or_else(|| Error::InvalidImageLayout("window row range overflows usize".into()))?;
885    let col_end = col_off
886        .checked_add(cols)
887        .ok_or_else(|| Error::InvalidImageLayout("window column range overflows usize".into()))?;
888    if row_end > layout.height || col_end > layout.width {
889        return Err(Error::InvalidImageLayout(format!(
890            "window [{row_off}..{row_end}, {col_off}..{col_end}) exceeds raster bounds {}x{}",
891            layout.height, layout.width
892        )));
893    }
894    Ok(Window {
895        row_off,
896        col_off,
897        rows,
898        cols,
899    })
900}
901
902fn validate_band_index(layout: &RasterLayout, band_index: usize) -> Result<()> {
903    if band_index >= layout.samples_per_pixel {
904        return Err(Error::BandIndexOutOfBounds {
905            index: band_index,
906            band_count: layout.samples_per_pixel,
907        });
908    }
909    Ok(())
910}
911
912fn parse_gdal_structural_metadata(source: &dyn TiffSource) -> Option<GdalStructuralMetadata> {
913    let available_len = usize::try_from(source.len().checked_sub(8)?).ok()?;
914    if available_len == 0 {
915        return None;
916    }
917
918    let probe_len = available_len.min(64);
919    let probe = source.read_exact_at(8, probe_len).ok()?;
920    let total_len = parse_gdal_structural_metadata_len(&probe)?;
921    if total_len == 0 || total_len > available_len {
922        return None;
923    }
924
925    let bytes = source.read_exact_at(8, total_len).ok()?;
926    GdalStructuralMetadata::from_prefix(&bytes)
927}
928
929fn parse_gdal_structural_metadata_len(bytes: &[u8]) -> Option<usize> {
930    let text = std::str::from_utf8(bytes).ok()?;
931    let newline_index = text.find('\n')?;
932    let header = &text[..newline_index];
933    let value = header.strip_prefix(GDAL_STRUCTURAL_METADATA_PREFIX)?;
934    let digits: String = value.chars().take_while(|ch| ch.is_ascii_digit()).collect();
935    if digits.is_empty() {
936        return None;
937    }
938    let payload_len: usize = digits.parse().ok()?;
939    newline_index.checked_add(1)?.checked_add(payload_len)
940}
941
942#[cfg(test)]
943mod tests {
944    use std::collections::BTreeMap;
945    use std::sync::atomic::{AtomicUsize, Ordering};
946    use std::sync::Arc;
947
948    use super::{
949        parse_gdal_structural_metadata, parse_gdal_structural_metadata_len, Error,
950        GdalStructuralMetadata, TiffFile, GDAL_STRUCTURAL_METADATA_PREFIX,
951    };
952    use crate::source::{BytesSource, TiffSource};
953    use flate2::{write::ZlibEncoder, Compression as FlateCompression};
954
955    fn le_u16(value: u16) -> [u8; 2] {
956        value.to_le_bytes()
957    }
958
959    fn le_u32(value: u32) -> [u8; 4] {
960        value.to_le_bytes()
961    }
962
963    fn inline_short(value: u16) -> Vec<u8> {
964        let mut bytes = [0u8; 4];
965        bytes[..2].copy_from_slice(&le_u16(value));
966        bytes.to_vec()
967    }
968
969    fn build_stripped_tiff(
970        width: u32,
971        height: u32,
972        image_data: &[u8],
973        overrides: &[(u16, u16, u32, Vec<u8>)],
974    ) -> Vec<u8> {
975        let mut entries = BTreeMap::new();
976        entries.insert(256, (4, 1, le_u32(width).to_vec()));
977        entries.insert(257, (4, 1, le_u32(height).to_vec()));
978        entries.insert(258, (3, 1, [8, 0, 0, 0].to_vec()));
979        entries.insert(259, (3, 1, [1, 0, 0, 0].to_vec()));
980        entries.insert(273, (4, 1, Vec::new()));
981        entries.insert(277, (3, 1, [1, 0, 0, 0].to_vec()));
982        entries.insert(278, (4, 1, le_u32(height).to_vec()));
983        entries.insert(279, (4, 1, le_u32(image_data.len() as u32).to_vec()));
984        for &(tag, ty, count, ref value) in overrides {
985            entries.insert(tag, (ty, count, value.clone()));
986        }
987
988        let ifd_offset = 8u32;
989        let ifd_size = 2 + entries.len() * 12 + 4;
990        let mut next_data_offset = ifd_offset as usize + ifd_size;
991        let image_offset = next_data_offset as u32;
992        next_data_offset += image_data.len();
993
994        let mut data = Vec::with_capacity(next_data_offset);
995        data.extend_from_slice(b"II");
996        data.extend_from_slice(&le_u16(42));
997        data.extend_from_slice(&le_u32(ifd_offset));
998        data.extend_from_slice(&le_u16(entries.len() as u16));
999
1000        let mut deferred = Vec::new();
1001        for (tag, (ty, count, value)) in entries {
1002            data.extend_from_slice(&le_u16(tag));
1003            data.extend_from_slice(&le_u16(ty));
1004            data.extend_from_slice(&le_u32(count));
1005            if tag == 273 {
1006                data.extend_from_slice(&le_u32(image_offset));
1007            } else if value.len() <= 4 {
1008                let mut inline = [0u8; 4];
1009                inline[..value.len()].copy_from_slice(&value);
1010                data.extend_from_slice(&inline);
1011            } else {
1012                let offset = next_data_offset as u32;
1013                data.extend_from_slice(&le_u32(offset));
1014                next_data_offset += value.len();
1015                deferred.push(value);
1016            }
1017        }
1018        data.extend_from_slice(&le_u32(0));
1019        data.extend_from_slice(image_data);
1020        for value in deferred {
1021            data.extend_from_slice(&value);
1022        }
1023        data
1024    }
1025
1026    #[allow(clippy::too_many_arguments)]
1027    fn build_lerc2_header_v2(
1028        width: u32,
1029        height: u32,
1030        valid_pixel_count: u32,
1031        image_type: i32,
1032        max_z_error: f64,
1033        z_min: f64,
1034        z_max: f64,
1035        payload_len: usize,
1036    ) -> Vec<u8> {
1037        let blob_size = 58 + 4 + payload_len;
1038        let mut bytes = Vec::with_capacity(blob_size);
1039        bytes.extend_from_slice(b"Lerc2 ");
1040        bytes.extend_from_slice(&2i32.to_le_bytes());
1041        bytes.extend_from_slice(&height.to_le_bytes());
1042        bytes.extend_from_slice(&width.to_le_bytes());
1043        bytes.extend_from_slice(&valid_pixel_count.to_le_bytes());
1044        bytes.extend_from_slice(&8i32.to_le_bytes());
1045        bytes.extend_from_slice(&(blob_size as i32).to_le_bytes());
1046        bytes.extend_from_slice(&image_type.to_le_bytes());
1047        bytes.extend_from_slice(&max_z_error.to_le_bytes());
1048        bytes.extend_from_slice(&z_min.to_le_bytes());
1049        bytes.extend_from_slice(&z_max.to_le_bytes());
1050        bytes
1051    }
1052
1053    #[allow(clippy::too_many_arguments)]
1054    fn build_lerc2_header_v4(
1055        width: u32,
1056        height: u32,
1057        depth: u32,
1058        valid_pixel_count: u32,
1059        image_type: i32,
1060        max_z_error: f64,
1061        z_min: f64,
1062        z_max: f64,
1063        payload_len: usize,
1064    ) -> Vec<u8> {
1065        let blob_size = 66 + 4 + payload_len;
1066        let mut bytes = Vec::with_capacity(blob_size);
1067        bytes.extend_from_slice(b"Lerc2 ");
1068        bytes.extend_from_slice(&4i32.to_le_bytes());
1069        bytes.extend_from_slice(&0u32.to_le_bytes());
1070        bytes.extend_from_slice(&height.to_le_bytes());
1071        bytes.extend_from_slice(&width.to_le_bytes());
1072        bytes.extend_from_slice(&depth.to_le_bytes());
1073        bytes.extend_from_slice(&valid_pixel_count.to_le_bytes());
1074        bytes.extend_from_slice(&8i32.to_le_bytes());
1075        bytes.extend_from_slice(&(blob_size as i32).to_le_bytes());
1076        bytes.extend_from_slice(&image_type.to_le_bytes());
1077        bytes.extend_from_slice(&max_z_error.to_le_bytes());
1078        bytes.extend_from_slice(&z_min.to_le_bytes());
1079        bytes.extend_from_slice(&z_max.to_le_bytes());
1080        bytes
1081    }
1082
1083    fn finalize_lerc2_v4_with_checksum(mut bytes: Vec<u8>) -> Vec<u8> {
1084        let blob_size = bytes.len() as i32;
1085        bytes[34..38].copy_from_slice(&blob_size.to_le_bytes());
1086        let checksum = fletcher32(&bytes[14..blob_size as usize]);
1087        bytes[10..14].copy_from_slice(&checksum.to_le_bytes());
1088        bytes
1089    }
1090
1091    fn fletcher32(bytes: &[u8]) -> u32 {
1092        let mut sum1 = 0xffffu32;
1093        let mut sum2 = 0xffffu32;
1094        let mut words = bytes.len() / 2;
1095        let mut index = 0usize;
1096
1097        while words > 0 {
1098            let chunk = words.min(359);
1099            words -= chunk;
1100            for _ in 0..chunk {
1101                sum1 += (bytes[index] as u32) << 8;
1102                index += 1;
1103                sum2 += sum1 + bytes[index] as u32;
1104                sum1 += bytes[index] as u32;
1105                index += 1;
1106            }
1107            sum1 = (sum1 & 0xffff) + (sum1 >> 16);
1108            sum2 = (sum2 & 0xffff) + (sum2 >> 16);
1109        }
1110
1111        if bytes.len() & 1 != 0 {
1112            sum1 += (bytes[index] as u32) << 8;
1113            sum2 += sum1;
1114        }
1115
1116        sum1 = (sum1 & 0xffff) + (sum1 >> 16);
1117        sum2 = (sum2 & 0xffff) + (sum2 >> 16);
1118        (sum2 << 16) | (sum1 & 0xffff)
1119    }
1120
1121    fn encode_mask_rle(mask: &[u8]) -> Vec<u8> {
1122        let bitset_len = mask.len().div_ceil(8);
1123        let mut bitset = vec![0u8; bitset_len];
1124        for (index, &value) in mask.iter().enumerate() {
1125            if value != 0 {
1126                bitset[index >> 3] |= 1 << (7 - (index & 7));
1127            }
1128        }
1129
1130        let mut encoded = Vec::with_capacity(bitset_len + 4);
1131        encoded.extend_from_slice(&(bitset_len as i16).to_le_bytes());
1132        encoded.extend_from_slice(&bitset);
1133        encoded.extend_from_slice(&i16::MIN.to_le_bytes());
1134        encoded
1135    }
1136
1137    fn build_lerc_tiff(
1138        width: u32,
1139        height: u32,
1140        image_data: &[u8],
1141        bits_per_sample: u16,
1142        sample_format: u16,
1143        samples_per_pixel: u16,
1144        lerc_parameters: Option<[u32; 2]>,
1145    ) -> Vec<u8> {
1146        let mut overrides = vec![
1147            (258u16, 3u16, 1u32, inline_short(bits_per_sample)),
1148            (259u16, 3u16, 1u32, inline_short(34887)),
1149            (277u16, 3u16, 1u32, inline_short(samples_per_pixel)),
1150            (279u16, 4u16, 1u32, le_u32(image_data.len() as u32).to_vec()),
1151        ];
1152        if sample_format != 1 {
1153            overrides.push((339u16, 3u16, 1u32, inline_short(sample_format)));
1154        }
1155        if let Some([version, additional_compression]) = lerc_parameters {
1156            overrides.push((
1157                50674u16,
1158                4u16,
1159                2u32,
1160                [version, additional_compression]
1161                    .into_iter()
1162                    .flat_map(le_u32)
1163                    .collect(),
1164            ));
1165        }
1166        build_stripped_tiff(width, height, image_data, &overrides)
1167    }
1168
1169    fn build_tiled_tiff(
1170        width: u32,
1171        height: u32,
1172        tile_width: u32,
1173        tile_height: u32,
1174        tiles: &[&[u8]],
1175    ) -> Vec<u8> {
1176        let mut entries = BTreeMap::new();
1177        entries.insert(256, (4, 1, le_u32(width).to_vec()));
1178        entries.insert(257, (4, 1, le_u32(height).to_vec()));
1179        entries.insert(258, (3, 1, [8, 0, 0, 0].to_vec()));
1180        entries.insert(259, (3, 1, [1, 0, 0, 0].to_vec()));
1181        entries.insert(277, (3, 1, [1, 0, 0, 0].to_vec()));
1182        entries.insert(322, (4, 1, le_u32(tile_width).to_vec()));
1183        entries.insert(323, (4, 1, le_u32(tile_height).to_vec()));
1184        entries.insert(
1185            325,
1186            (
1187                4,
1188                tiles.len() as u32,
1189                tiles
1190                    .iter()
1191                    .flat_map(|tile| le_u32(tile.len() as u32))
1192                    .collect(),
1193            ),
1194        );
1195
1196        let ifd_offset = 8u32;
1197        let ifd_size = 2 + (entries.len() + 1) * 12 + 4;
1198        let mut tile_data_offset = ifd_offset as usize + ifd_size;
1199        let tile_offsets: Vec<u32> = tiles
1200            .iter()
1201            .map(|tile| {
1202                let offset = tile_data_offset as u32;
1203                tile_data_offset += tile.len();
1204                offset
1205            })
1206            .collect();
1207        entries.insert(
1208            324,
1209            (
1210                4,
1211                tile_offsets.len() as u32,
1212                tile_offsets
1213                    .iter()
1214                    .flat_map(|offset| le_u32(*offset))
1215                    .collect(),
1216            ),
1217        );
1218
1219        let mut next_data_offset = tile_data_offset;
1220        let mut data = Vec::with_capacity(next_data_offset);
1221        data.extend_from_slice(b"II");
1222        data.extend_from_slice(&le_u16(42));
1223        data.extend_from_slice(&le_u32(ifd_offset));
1224        data.extend_from_slice(&le_u16(entries.len() as u16));
1225
1226        let mut deferred = Vec::new();
1227        for (tag, (ty, count, value)) in entries {
1228            data.extend_from_slice(&le_u16(tag));
1229            data.extend_from_slice(&le_u16(ty));
1230            data.extend_from_slice(&le_u32(count));
1231            if value.len() <= 4 {
1232                let mut inline = [0u8; 4];
1233                inline[..value.len()].copy_from_slice(&value);
1234                data.extend_from_slice(&inline);
1235            } else {
1236                let offset = next_data_offset as u32;
1237                data.extend_from_slice(&le_u32(offset));
1238                next_data_offset += value.len();
1239                deferred.push(value);
1240            }
1241        }
1242        data.extend_from_slice(&le_u32(0));
1243        for tile in tiles {
1244            data.extend_from_slice(tile);
1245        }
1246        for value in deferred {
1247            data.extend_from_slice(&value);
1248        }
1249        data
1250    }
1251
1252    fn build_multi_strip_tiff(width: u32, rows: &[&[u8]]) -> Vec<u8> {
1253        let mut entries = BTreeMap::new();
1254        entries.insert(256, (4, 1, le_u32(width).to_vec()));
1255        entries.insert(257, (4, 1, le_u32(rows.len() as u32).to_vec()));
1256        entries.insert(258, (3, 1, [8, 0, 0, 0].to_vec()));
1257        entries.insert(259, (3, 1, [1, 0, 0, 0].to_vec()));
1258        entries.insert(277, (3, 1, [1, 0, 0, 0].to_vec()));
1259        entries.insert(278, (4, 1, le_u32(1).to_vec()));
1260        entries.insert(
1261            279,
1262            (
1263                4,
1264                rows.len() as u32,
1265                rows.iter()
1266                    .flat_map(|row| le_u32(row.len() as u32))
1267                    .collect(),
1268            ),
1269        );
1270
1271        let ifd_offset = 8u32;
1272        let ifd_size = 2 + (entries.len() + 1) * 12 + 4;
1273        let mut strip_data_offset = ifd_offset as usize + ifd_size;
1274        let strip_offsets: Vec<u32> = rows
1275            .iter()
1276            .map(|row| {
1277                let offset = strip_data_offset as u32;
1278                strip_data_offset += row.len();
1279                offset
1280            })
1281            .collect();
1282        entries.insert(
1283            273,
1284            (
1285                4,
1286                strip_offsets.len() as u32,
1287                strip_offsets
1288                    .iter()
1289                    .flat_map(|offset| le_u32(*offset))
1290                    .collect(),
1291            ),
1292        );
1293
1294        let mut next_data_offset = strip_data_offset;
1295        let mut data = Vec::with_capacity(next_data_offset);
1296        data.extend_from_slice(b"II");
1297        data.extend_from_slice(&le_u16(42));
1298        data.extend_from_slice(&le_u32(ifd_offset));
1299        data.extend_from_slice(&le_u16(entries.len() as u16));
1300
1301        let mut deferred = Vec::new();
1302        for (tag, (ty, count, value)) in entries {
1303            data.extend_from_slice(&le_u16(tag));
1304            data.extend_from_slice(&le_u16(ty));
1305            data.extend_from_slice(&le_u32(count));
1306            if value.len() <= 4 {
1307                let mut inline = [0u8; 4];
1308                inline[..value.len()].copy_from_slice(&value);
1309                data.extend_from_slice(&inline);
1310            } else {
1311                let offset = next_data_offset as u32;
1312                data.extend_from_slice(&le_u32(offset));
1313                next_data_offset += value.len();
1314                deferred.push(value);
1315            }
1316        }
1317        data.extend_from_slice(&le_u32(0));
1318        for row in rows {
1319            data.extend_from_slice(row);
1320        }
1321        for value in deferred {
1322            data.extend_from_slice(&value);
1323        }
1324        data
1325    }
1326
1327    fn build_planar_stripped_tiff(width: u32, height: u32, planes: &[&[u8]]) -> Vec<u8> {
1328        let mut entries = BTreeMap::new();
1329        entries.insert(256, (4, 1, le_u32(width).to_vec()));
1330        entries.insert(257, (4, 1, le_u32(height).to_vec()));
1331        entries.insert(258, (3, 1, [8, 0, 0, 0].to_vec()));
1332        entries.insert(259, (3, 1, [1, 0, 0, 0].to_vec()));
1333        entries.insert(262, (3, 1, [2, 0, 0, 0].to_vec()));
1334        entries.insert(277, (3, 1, inline_short(planes.len() as u16)));
1335        entries.insert(278, (4, 1, le_u32(height).to_vec()));
1336        entries.insert(284, (3, 1, [2, 0, 0, 0].to_vec()));
1337        entries.insert(
1338            279,
1339            (
1340                4,
1341                planes.len() as u32,
1342                planes
1343                    .iter()
1344                    .flat_map(|plane| le_u32(plane.len() as u32))
1345                    .collect(),
1346            ),
1347        );
1348
1349        let ifd_offset = 8u32;
1350        let ifd_size = 2 + (entries.len() + 1) * 12 + 4;
1351        let mut strip_data_offset = ifd_offset as usize + ifd_size;
1352        let strip_offsets: Vec<u32> = planes
1353            .iter()
1354            .map(|plane| {
1355                let offset = strip_data_offset as u32;
1356                strip_data_offset += plane.len();
1357                offset
1358            })
1359            .collect();
1360        entries.insert(
1361            273,
1362            (
1363                4,
1364                strip_offsets.len() as u32,
1365                strip_offsets
1366                    .iter()
1367                    .flat_map(|offset| le_u32(*offset))
1368                    .collect(),
1369            ),
1370        );
1371
1372        let mut next_data_offset = strip_data_offset;
1373        let mut data = Vec::with_capacity(next_data_offset);
1374        data.extend_from_slice(b"II");
1375        data.extend_from_slice(&le_u16(42));
1376        data.extend_from_slice(&le_u32(ifd_offset));
1377        data.extend_from_slice(&le_u16(entries.len() as u16));
1378
1379        let mut deferred = Vec::new();
1380        for (tag, (ty, count, value)) in entries {
1381            data.extend_from_slice(&le_u16(tag));
1382            data.extend_from_slice(&le_u16(ty));
1383            data.extend_from_slice(&le_u32(count));
1384            if value.len() <= 4 {
1385                let mut inline = [0u8; 4];
1386                inline[..value.len()].copy_from_slice(&value);
1387                data.extend_from_slice(&inline);
1388            } else {
1389                let offset = next_data_offset as u32;
1390                data.extend_from_slice(&le_u32(offset));
1391                next_data_offset += value.len();
1392                deferred.push(value);
1393            }
1394        }
1395        data.extend_from_slice(&le_u32(0));
1396        for plane in planes {
1397            data.extend_from_slice(plane);
1398        }
1399        for value in deferred {
1400            data.extend_from_slice(&value);
1401        }
1402        data
1403    }
1404
1405    struct CountingSource {
1406        bytes: Vec<u8>,
1407        reads: AtomicUsize,
1408    }
1409
1410    impl CountingSource {
1411        fn new(bytes: Vec<u8>) -> Self {
1412            Self {
1413                bytes,
1414                reads: AtomicUsize::new(0),
1415            }
1416        }
1417
1418        fn reset_reads(&self) {
1419            self.reads.store(0, Ordering::SeqCst);
1420        }
1421
1422        fn reads(&self) -> usize {
1423            self.reads.load(Ordering::SeqCst)
1424        }
1425    }
1426
1427    impl TiffSource for CountingSource {
1428        fn len(&self) -> u64 {
1429            self.bytes.len() as u64
1430        }
1431
1432        fn read_exact_at(&self, offset: u64, len: usize) -> crate::error::Result<Vec<u8>> {
1433            self.reads.fetch_add(1, Ordering::SeqCst);
1434            let start =
1435                usize::try_from(offset).map_err(|_| crate::error::Error::OffsetOutOfBounds {
1436                    offset,
1437                    length: len as u64,
1438                    data_len: self.len(),
1439                })?;
1440            let end = start
1441                .checked_add(len)
1442                .ok_or(crate::error::Error::OffsetOutOfBounds {
1443                    offset,
1444                    length: len as u64,
1445                    data_len: self.len(),
1446                })?;
1447            if end > self.bytes.len() {
1448                return Err(crate::error::Error::OffsetOutOfBounds {
1449                    offset,
1450                    length: len as u64,
1451                    data_len: self.len(),
1452                });
1453            }
1454            Ok(self.bytes[start..end].to_vec())
1455        }
1456    }
1457
1458    #[test]
1459    fn reads_stripped_u8_image() {
1460        let data = build_stripped_tiff(2, 2, &[1, 2, 3, 4], &[]);
1461        let file = TiffFile::from_bytes(data).unwrap();
1462        let image = file.read_image::<u8>(0).unwrap();
1463        assert_eq!(image.shape(), &[2, 2]);
1464        let (values, offset) = image.into_raw_vec_and_offset();
1465        assert_eq!(offset, Some(0));
1466        assert_eq!(values, vec![1, 2, 3, 4]);
1467    }
1468
1469    #[test]
1470    fn reads_single_chunky_band_and_window() {
1471        let data = build_stripped_tiff(
1472            2,
1473            2,
1474            &[
1475                1, 10, 100, //
1476                2, 20, 110, //
1477                3, 30, 120, //
1478                4, 40, 130,
1479            ],
1480            &[
1481                (262, 3, 1, inline_short(2)),
1482                (277, 3, 1, inline_short(3)),
1483                (279, 4, 1, le_u32(12).to_vec()),
1484            ],
1485        );
1486        let file = TiffFile::from_bytes(data).unwrap();
1487
1488        let green = file.read_band::<u8>(0, 1).unwrap();
1489        assert_eq!(green.shape(), &[2, 2]);
1490        let (green_values, offset) = green.into_raw_vec_and_offset();
1491        assert_eq!(offset, Some(0));
1492        assert_eq!(green_values, vec![10, 20, 30, 40]);
1493
1494        let blue_window = file.read_band_window::<u8>(0, 2, 0, 1, 2, 1).unwrap();
1495        assert_eq!(blue_window.shape(), &[2, 1]);
1496        let (blue_values, offset) = blue_window.into_raw_vec_and_offset();
1497        assert_eq!(offset, Some(0));
1498        assert_eq!(blue_values, vec![110, 130]);
1499
1500        let err = file.read_band::<u8>(0, 3).unwrap_err();
1501        assert!(matches!(
1502            err,
1503            Error::BandIndexOutOfBounds {
1504                index: 3,
1505                band_count: 3
1506            }
1507        ));
1508    }
1509
1510    #[test]
1511    fn planar_band_reads_only_requested_plane() {
1512        let data = build_planar_stripped_tiff(
1513            2,
1514            2,
1515            &[&[1, 2, 3, 4], &[10, 20, 30, 40], &[100, 110, 120, 130]],
1516        );
1517        let source = Arc::new(CountingSource::new(data));
1518        let file = TiffFile::from_source(source.clone()).unwrap();
1519        source.reset_reads();
1520
1521        let blue = file.read_band::<u8>(0, 2).unwrap();
1522        assert_eq!(blue.shape(), &[2, 2]);
1523        let (values, offset) = blue.into_raw_vec_and_offset();
1524        assert_eq!(offset, Some(0));
1525        assert_eq!(values, vec![100, 110, 120, 130]);
1526        assert_eq!(source.reads(), 1);
1527    }
1528
1529    #[test]
1530    fn keeps_subbyte_palette_reads_raw_and_offers_explicit_decoded_pixels() {
1531        let mut color_map = Vec::new();
1532        color_map.extend((0u16..16).map(|value| value * 17 * 257));
1533        color_map.extend((0u16..16).map(|value| (15 - value) * 17 * 257));
1534        color_map.extend((0u16..16).map(|value| value * 8 * 257));
1535        let data = build_stripped_tiff(
1536            4,
1537            1,
1538            &[0x01, 0x23],
1539            &[
1540                (258, 3, 1, inline_short(4)),
1541                (262, 3, 1, inline_short(3)),
1542                (
1543                    320,
1544                    3,
1545                    color_map.len() as u32,
1546                    color_map.iter().flat_map(|value| le_u16(*value)).collect(),
1547                ),
1548            ],
1549        );
1550        let file = TiffFile::from_bytes(data).unwrap();
1551
1552        let image = file.read_image::<u8>(0).unwrap();
1553        assert_eq!(image.shape(), &[1, 4]);
1554        let (values, offset) = image.into_raw_vec_and_offset();
1555        assert_eq!(offset, Some(0));
1556        assert_eq!(values, vec![0, 1, 2, 3]);
1557
1558        let image = file.read_decoded_image::<u8>(0).unwrap();
1559        assert_eq!(image.shape(), &[1, 4, 3]);
1560        let (values, offset) = image.into_raw_vec_and_offset();
1561        assert_eq!(offset, Some(0));
1562        assert_eq!(
1563            values,
1564            vec![
1565                0, 255, 0, //
1566                17, 238, 8, //
1567                34, 221, 16, //
1568                51, 204, 24
1569            ]
1570        );
1571
1572        let sample_bytes = file.read_image_sample_bytes(0).unwrap();
1573        assert_eq!(sample_bytes, vec![0, 1, 2, 3]);
1574    }
1575
1576    #[test]
1577    fn keeps_subsampled_ycbcr_reads_raw_and_offers_explicit_decoded_pixels() {
1578        let data = build_stripped_tiff(
1579            2,
1580            2,
1581            &[10u8, 20, 30, 40, 128, 128],
1582            &[
1583                (
1584                    258,
1585                    3,
1586                    3,
1587                    [8u16, 8, 8].into_iter().flat_map(le_u16).collect(),
1588                ),
1589                (262, 3, 1, inline_short(6)),
1590                (277, 3, 1, inline_short(3)),
1591                (530, 3, 2, [2u16, 2].into_iter().flat_map(le_u16).collect()),
1592            ],
1593        );
1594        let file = TiffFile::from_bytes(data).unwrap();
1595
1596        let image = file.read_image::<u8>(0).unwrap();
1597        assert_eq!(image.shape(), &[2, 2, 3]);
1598        let (values, offset) = image.into_raw_vec_and_offset();
1599        assert_eq!(offset, Some(0));
1600        assert_eq!(
1601            values,
1602            vec![
1603                10, 128, 128, //
1604                20, 128, 128, //
1605                30, 128, 128, //
1606                40, 128, 128
1607            ]
1608        );
1609
1610        let image = file.read_decoded_image::<u8>(0).unwrap();
1611        assert_eq!(image.shape(), &[2, 2, 3]);
1612        let (rgb, offset) = image.into_raw_vec_and_offset();
1613        assert_eq!(offset, Some(0));
1614        assert_eq!(
1615            rgb,
1616            vec![
1617                10, 10, 10, //
1618                20, 20, 20, //
1619                30, 30, 30, //
1620                40, 40, 40
1621            ]
1622        );
1623
1624        let samples = file.read_image_samples::<u8>(0).unwrap();
1625        let (values, offset) = samples.into_raw_vec_and_offset();
1626        assert_eq!(offset, Some(0));
1627        assert_eq!(
1628            values,
1629            vec![
1630                10, 128, 128, //
1631                20, 128, 128, //
1632                30, 128, 128, //
1633                40, 128, 128
1634            ]
1635        );
1636    }
1637
1638    #[test]
1639    fn reads_horizontal_predictor_u16_strip() {
1640        let encoded = [1, 0, 1, 0, 2, 0];
1641        let data = build_stripped_tiff(
1642            3,
1643            1,
1644            &encoded,
1645            &[
1646                (258, 3, 1, [16, 0, 0, 0].to_vec()),
1647                (317, 3, 1, [2, 0, 0, 0].to_vec()),
1648            ],
1649        );
1650        let file = TiffFile::from_bytes(data).unwrap();
1651        let image = file.read_image::<u16>(0).unwrap();
1652        assert_eq!(image.shape(), &[1, 3]);
1653        let (values, offset) = image.into_raw_vec_and_offset();
1654        assert_eq!(offset, Some(0));
1655        assert_eq!(values, vec![1, 2, 4]);
1656    }
1657
1658    #[test]
1659    fn reads_lerc_f32_strip() {
1660        let mut blob = build_lerc2_header_v2(2, 2, 4, 6, 0.0, 1.0, 4.0, 1 + 16);
1661        blob.extend_from_slice(&0u32.to_le_bytes());
1662        blob.push(1);
1663        for value in [1.0f32, 2.0, 3.0, 4.0] {
1664            blob.extend_from_slice(&value.to_le_bytes());
1665        }
1666
1667        let data = build_lerc_tiff(2, 2, &blob, 32, 3, 1, None);
1668        let file = TiffFile::from_bytes(data).unwrap();
1669        let image = file.read_image::<f32>(0).unwrap();
1670        let (values, offset) = image.into_raw_vec_and_offset();
1671        assert_eq!(offset, Some(0));
1672        assert_eq!(values, vec![1.0, 2.0, 3.0, 4.0]);
1673    }
1674
1675    #[test]
1676    fn reads_lerc_masked_f32_strip_as_nan() {
1677        let mask = [1u8, 0, 1, 1];
1678        let encoded_mask = encode_mask_rle(&mask);
1679        let mut blob =
1680            build_lerc2_header_v2(2, 2, 3, 6, 0.0, 1.0, 4.0, encoded_mask.len() + 1 + 12);
1681        blob.extend_from_slice(&(encoded_mask.len() as u32).to_le_bytes());
1682        blob.extend_from_slice(&encoded_mask);
1683        blob.push(1);
1684        for value in [1.0f32, 3.0, 4.0] {
1685            blob.extend_from_slice(&value.to_le_bytes());
1686        }
1687
1688        let data = build_lerc_tiff(2, 2, &blob, 32, 3, 1, None);
1689        let file = TiffFile::from_bytes(data).unwrap();
1690        let image = file.read_image::<f32>(0).unwrap();
1691        let (values, offset) = image.into_raw_vec_and_offset();
1692        assert_eq!(offset, Some(0));
1693        assert_eq!(values[0], 1.0);
1694        assert!(values[1].is_nan());
1695        assert_eq!(values[2], 3.0);
1696        assert_eq!(values[3], 4.0);
1697    }
1698
1699    #[test]
1700    fn reads_lerc_chunky_rgb_band_set_strip() {
1701        let mut red = build_lerc2_header_v2(2, 1, 2, 1, 0.0, 1.0, 1.0, 0);
1702        red.extend_from_slice(&0u32.to_le_bytes());
1703        let mut green = build_lerc2_header_v2(2, 1, 2, 1, 0.0, 2.0, 2.0, 0);
1704        green.extend_from_slice(&0u32.to_le_bytes());
1705        let mut blue = build_lerc2_header_v2(2, 1, 2, 1, 0.0, 3.0, 3.0, 0);
1706        blue.extend_from_slice(&0u32.to_le_bytes());
1707
1708        let mut blob = red;
1709        blob.extend_from_slice(&green);
1710        blob.extend_from_slice(&blue);
1711
1712        let data = build_lerc_tiff(2, 1, &blob, 8, 1, 3, None);
1713        let file = TiffFile::from_bytes(data).unwrap();
1714        let image = file.read_image::<u8>(0).unwrap();
1715        assert_eq!(image.shape(), &[1, 2, 3]);
1716        let (values, offset) = image.into_raw_vec_and_offset();
1717        assert_eq!(offset, Some(0));
1718        assert_eq!(values, vec![1, 2, 3, 1, 2, 3]);
1719    }
1720
1721    #[test]
1722    fn reads_lerc_chunky_rgb_depth_blob_strip() {
1723        let mut blob = build_lerc2_header_v4(2, 1, 3, 2, 1, 0.0, 1.0, 6.0, 6 + 6 + 1 + 6);
1724        blob.extend_from_slice(&0u32.to_le_bytes());
1725        for value in [1u8, 2, 3] {
1726            blob.extend_from_slice(&value.to_le_bytes());
1727        }
1728        for value in [4u8, 5, 6] {
1729            blob.extend_from_slice(&value.to_le_bytes());
1730        }
1731        blob.push(1);
1732        blob.extend_from_slice(&[1, 2, 3, 4, 5, 6]);
1733        let blob = finalize_lerc2_v4_with_checksum(blob);
1734
1735        let data = build_lerc_tiff(2, 1, &blob, 8, 1, 3, Some([4, 0]));
1736        let file = TiffFile::from_bytes(data).unwrap();
1737        let image = file.read_image::<u8>(0).unwrap();
1738        assert_eq!(image.shape(), &[1, 2, 3]);
1739        let (values, offset) = image.into_raw_vec_and_offset();
1740        assert_eq!(offset, Some(0));
1741        assert_eq!(values, vec![1, 2, 3, 4, 5, 6]);
1742    }
1743
1744    #[test]
1745    fn rejects_lerc2_blob_size_before_checksum_range_without_panicking() {
1746        let mut blob = build_lerc2_header_v4(1, 1, 1, 1, 1, 0.0, 1.0, 1.0, 0);
1747        blob[34..38].copy_from_slice(&8i32.to_le_bytes());
1748
1749        let data = build_lerc_tiff(1, 1, &blob, 8, 1, 1, Some([4, 0]));
1750        let file = TiffFile::from_bytes(data).unwrap();
1751        let error = file.read_image_bytes(0).unwrap_err();
1752        assert!(error.to_string().contains("invalid Lerc2 v4 blob size 8"));
1753    }
1754
1755    #[test]
1756    fn rejects_lerc2_header_dimensions_before_allocating_mask() {
1757        let mut blob = build_lerc2_header_v2(u32::MAX, u32::MAX, 1, 1, 0.0, 0.0, 1.0, 4);
1758        blob.extend_from_slice(&4u32.to_le_bytes());
1759        blob.extend_from_slice(&[0, 0, 0, 0]);
1760
1761        let data = build_lerc_tiff(1, 1, &blob, 8, 1, 1, None);
1762        let file = TiffFile::from_bytes(data).unwrap();
1763        let error = file.read_image_bytes(0).unwrap_err();
1764        assert!(error.to_string().contains("LERC raster dimensions"));
1765    }
1766
1767    #[test]
1768    fn rejects_truncated_lerc2_header_dimensions_before_decoder() {
1769        let blob = build_lerc2_header_v2(u32::MAX, u32::MAX, 1, 1, 0.0, 0.0, 1.0, 64);
1770
1771        let data = build_lerc_tiff(1, 1, &blob, 8, 1, 1, None);
1772        let file = TiffFile::from_bytes(data).unwrap();
1773        let error = file.read_image_bytes(0).unwrap_err();
1774        assert!(error.to_string().contains("LERC raster dimensions"));
1775    }
1776
1777    #[test]
1778    fn reads_lerc_deflate_f32_strip() {
1779        let mut blob = build_lerc2_header_v2(2, 2, 4, 6, 0.0, 1.0, 4.0, 1 + 16);
1780        blob.extend_from_slice(&0u32.to_le_bytes());
1781        blob.push(1);
1782        for value in [1.0f32, 2.0, 3.0, 4.0] {
1783            blob.extend_from_slice(&value.to_le_bytes());
1784        }
1785
1786        let mut encoder = ZlibEncoder::new(Vec::new(), FlateCompression::default());
1787        std::io::Write::write_all(&mut encoder, &blob).unwrap();
1788        let compressed = encoder.finish().unwrap();
1789
1790        let data = build_lerc_tiff(2, 2, &compressed, 32, 3, 1, Some([2, 1]));
1791        let file = TiffFile::from_bytes(data).unwrap();
1792        let image = file.read_image::<f32>(0).unwrap();
1793        let (values, offset) = image.into_raw_vec_and_offset();
1794        assert_eq!(offset, Some(0));
1795        assert_eq!(values, vec![1.0, 2.0, 3.0, 4.0]);
1796    }
1797
1798    #[cfg(feature = "zstd")]
1799    #[test]
1800    fn reads_lerc_zstd_f32_strip() {
1801        let mut blob = build_lerc2_header_v2(2, 2, 4, 6, 0.0, 1.0, 4.0, 1 + 16);
1802        blob.extend_from_slice(&0u32.to_le_bytes());
1803        blob.push(1);
1804        for value in [1.0f32, 2.0, 3.0, 4.0] {
1805            blob.extend_from_slice(&value.to_le_bytes());
1806        }
1807
1808        let compressed = zstd::stream::encode_all(std::io::Cursor::new(blob), 0).unwrap();
1809        let data = build_lerc_tiff(2, 2, &compressed, 32, 3, 1, Some([2, 2]));
1810        let file = TiffFile::from_bytes(data).unwrap();
1811        let image = file.read_image::<f32>(0).unwrap();
1812        let (values, offset) = image.into_raw_vec_and_offset();
1813        assert_eq!(offset, Some(0));
1814        assert_eq!(values, vec![1.0, 2.0, 3.0, 4.0]);
1815    }
1816
1817    #[test]
1818    fn reads_stripped_u8_window() {
1819        let data = build_multi_strip_tiff(
1820            4,
1821            &[
1822                &[1, 2, 3, 4],
1823                &[5, 6, 7, 8],
1824                &[9, 10, 11, 12],
1825                &[13, 14, 15, 16],
1826            ],
1827        );
1828        let file = TiffFile::from_bytes(data).unwrap();
1829        let window = file.read_window::<u8>(0, 1, 1, 2, 2).unwrap();
1830        assert_eq!(window.shape(), &[2, 2]);
1831        let (values, offset) = window.into_raw_vec_and_offset();
1832        assert_eq!(offset, Some(0));
1833        assert_eq!(values, vec![6, 7, 10, 11]);
1834    }
1835
1836    #[test]
1837    fn reads_tiled_u8_window() {
1838        let data = build_tiled_tiff(
1839            4,
1840            4,
1841            2,
1842            2,
1843            &[
1844                &[1, 2, 5, 6],
1845                &[3, 4, 7, 8],
1846                &[9, 10, 13, 14],
1847                &[11, 12, 15, 16],
1848            ],
1849        );
1850        let file = TiffFile::from_bytes(data).unwrap();
1851        let window = file.read_window::<u8>(0, 1, 1, 2, 2).unwrap();
1852        assert_eq!(window.shape(), &[2, 2]);
1853        let (values, offset) = window.into_raw_vec_and_offset();
1854        assert_eq!(offset, Some(0));
1855        assert_eq!(values, vec![6, 7, 10, 11]);
1856    }
1857
1858    #[test]
1859    fn windowed_tiled_reads_only_intersecting_blocks() {
1860        let data = build_tiled_tiff(
1861            4,
1862            4,
1863            2,
1864            2,
1865            &[
1866                &[1, 2, 5, 6],
1867                &[3, 4, 7, 8],
1868                &[9, 10, 13, 14],
1869                &[11, 12, 15, 16],
1870            ],
1871        );
1872        let source = Arc::new(CountingSource::new(data));
1873        let file = TiffFile::from_source(source.clone()).unwrap();
1874        source.reset_reads();
1875
1876        let window = file.read_window::<u8>(0, 0, 0, 2, 2).unwrap();
1877        let (values, offset) = window.into_raw_vec_and_offset();
1878        assert_eq!(offset, Some(0));
1879        assert_eq!(values, vec![1, 2, 5, 6]);
1880        assert_eq!(source.reads(), 1);
1881    }
1882
1883    #[test]
1884    fn unwraps_gdal_structural_metadata_block() {
1885        let metadata = GdalStructuralMetadata::from_prefix(
1886            b"GDAL_STRUCTURAL_METADATA_SIZE=000174 bytes\nBLOCK_LEADER=SIZE_AS_UINT4\nBLOCK_TRAILER=LAST_4_BYTES_REPEATED\n",
1887        )
1888        .unwrap();
1889
1890        let payload = [1u8, 2, 3, 4];
1891        let mut block = Vec::new();
1892        block.extend_from_slice(&(payload.len() as u32).to_le_bytes());
1893        block.extend_from_slice(&payload);
1894        block.extend_from_slice(&payload[payload.len() - 4..]);
1895
1896        let unwrapped = metadata
1897            .unwrap_block(&block, crate::ByteOrder::LittleEndian, 256)
1898            .unwrap();
1899        assert_eq!(unwrapped, payload);
1900    }
1901
1902    #[test]
1903    fn rejects_gdal_structural_metadata_trailer_mismatch() {
1904        let metadata = GdalStructuralMetadata::from_prefix(
1905            b"GDAL_STRUCTURAL_METADATA_SIZE=000174 bytes\nBLOCK_LEADER=SIZE_AS_UINT4\nBLOCK_TRAILER=LAST_4_BYTES_REPEATED\n",
1906        )
1907        .unwrap();
1908
1909        let block = [
1910            4u8, 0, 0, 0, //
1911            1, 2, 3, 4, //
1912            4, 3, 2, 1,
1913        ];
1914
1915        let error = metadata
1916            .unwrap_block(&block, crate::ByteOrder::LittleEndian, 512)
1917            .unwrap_err();
1918        assert!(error.to_string().contains("GDAL block trailer mismatch"));
1919    }
1920
1921    #[test]
1922    fn parses_gdal_structural_metadata_before_binary_prefix_data() {
1923        let rest = "LAYOUT=IFDS_BEFORE_DATA\nBLOCK_ORDER=ROW_MAJOR\nBLOCK_LEADER=SIZE_AS_UINT4\nBLOCK_TRAILER=LAST_4_BYTES_REPEATED\nKNOWN_INCOMPATIBLE_EDITION=NO\n";
1924        let prefix = format!(
1925            "{GDAL_STRUCTURAL_METADATA_PREFIX}{:06} bytes\n{rest}",
1926            rest.len()
1927        );
1928
1929        let mut bytes = vec![0u8; 8];
1930        bytes.extend_from_slice(prefix.as_bytes());
1931        bytes.extend_from_slice(&[0xff, 0x00, 0x80, 0x7f]);
1932
1933        let source = BytesSource::new(bytes);
1934        let metadata = parse_gdal_structural_metadata(&source).unwrap();
1935        assert!(metadata.block_leader_size_as_u32);
1936        assert!(metadata.block_trailer_repeats_last_4_bytes);
1937    }
1938
1939    #[test]
1940    fn parses_gdal_structural_metadata_declared_length_as_header_plus_payload() {
1941        let rest = "LAYOUT=IFDS_BEFORE_DATA\nBLOCK_ORDER=ROW_MAJOR\n";
1942        let prefix = format!(
1943            "{GDAL_STRUCTURAL_METADATA_PREFIX}{:06} bytes\n{rest}",
1944            rest.len()
1945        );
1946        assert_eq!(
1947            parse_gdal_structural_metadata_len(prefix.as_bytes()),
1948            Some(prefix.len())
1949        );
1950    }
1951
1952    #[test]
1953    fn leaves_payload_only_gdal_block_unchanged() {
1954        let metadata = GdalStructuralMetadata {
1955            block_leader_size_as_u32: true,
1956            block_trailer_repeats_last_4_bytes: true,
1957        };
1958        let payload = [0x80u8, 0x1a, 0xcf, 0x68, 0x43, 0x9a, 0x11, 0x08];
1959        let unwrapped = metadata
1960            .unwrap_block(&payload, crate::ByteOrder::LittleEndian, 570)
1961            .unwrap();
1962        assert_eq!(unwrapped, payload);
1963    }
1964
1965    #[test]
1966    fn rejects_zero_rows_per_strip_without_panicking() {
1967        let data = build_stripped_tiff(2, 2, &[1, 2, 3, 4], &[(278, 4, 1, le_u32(0).to_vec())]);
1968        let file = TiffFile::from_bytes(data).unwrap();
1969        let error = file.read_image_bytes(0).unwrap_err();
1970        assert!(error.to_string().contains("RowsPerStrip"));
1971    }
1972}