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