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**: file-backed random access, opt-in 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), WebP (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, FileSource, 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
62const DEFAULT_DECODE_OUTPUT_BYTES: usize = 1024 * 1024 * 1024;
63
64/// Configuration for opening a TIFF file.
65#[derive(Debug, Clone, Copy)]
66pub struct OpenOptions {
67    /// Maximum bytes held in the decoded strip/tile cache.
68    pub block_cache_bytes: usize,
69    /// Maximum number of cached strips/tiles.
70    pub block_cache_slots: usize,
71    /// Maximum IFDs, tag entries, and per-tag/aggregate tag-value bytes parsed from metadata.
72    pub parse_budgets: ParseBudgets,
73    /// Maximum bytes allocated for a single decoded output buffer.
74    pub decode_output_bytes: usize,
75}
76
77impl Default for OpenOptions {
78    fn default() -> Self {
79        Self {
80            block_cache_bytes: 64 * 1024 * 1024,
81            block_cache_slots: 257,
82            parse_budgets: ParseBudgets::default(),
83            decode_output_bytes: DEFAULT_DECODE_OUTPUT_BYTES,
84        }
85    }
86}
87
88/// A TIFF file handle.
89pub struct TiffFile {
90    source: SharedSource,
91    header: header::TiffHeader,
92    ifds: Vec<ifd::Ifd>,
93    parse_budgets: ParseBudgets,
94    decode_output_bytes: usize,
95    block_cache: Arc<BlockCache>,
96    gdal_structural_metadata: Option<GdalStructuralMetadata>,
97}
98
99#[derive(Debug, Clone, Copy)]
100pub(crate) struct GdalStructuralMetadata {
101    block_leader_size_as_u32: bool,
102    block_trailer_repeats_last_4_bytes: bool,
103}
104
105#[derive(Debug, Clone, Copy, PartialEq, Eq)]
106pub(crate) struct Window {
107    pub row_off: usize,
108    pub col_off: usize,
109    pub rows: usize,
110    pub cols: usize,
111}
112
113#[derive(Debug, Clone, Copy)]
114pub(crate) struct DecodeReadOptions<'a> {
115    pub decode_output_bytes: usize,
116    pub gdal_structural_metadata: Option<&'a GdalStructuralMetadata>,
117}
118
119impl Window {
120    pub(crate) fn is_empty(self) -> bool {
121        self.rows == 0 || self.cols == 0
122    }
123
124    pub(crate) fn row_end(self) -> usize {
125        self.row_off + self.rows
126    }
127
128    pub(crate) fn col_end(self) -> usize {
129        self.col_off + self.cols
130    }
131
132    pub(crate) fn output_len(self, layout: &RasterLayout) -> Result<usize> {
133        let pixel_stride = layout.checked_pixel_stride_bytes()?;
134        self.cols
135            .checked_mul(self.rows)
136            .and_then(|pixels| pixels.checked_mul(pixel_stride))
137            .ok_or_else(|| Error::InvalidImageLayout("window size overflows usize".into()))
138    }
139
140    pub(crate) fn band_output_len(self, layout: &RasterLayout) -> Result<usize> {
141        self.cols
142            .checked_mul(self.rows)
143            .and_then(|pixels| pixels.checked_mul(layout.bytes_per_sample))
144            .ok_or_else(|| Error::InvalidImageLayout("window band size overflows usize".into()))
145    }
146}
147
148pub(crate) fn checked_layout_add(lhs: usize, rhs: usize, context: &'static str) -> Result<usize> {
149    lhs.checked_add(rhs)
150        .ok_or_else(|| Error::InvalidImageLayout(format!("{context} overflows usize")))
151}
152
153pub(crate) fn checked_layout_mul(lhs: usize, rhs: usize, context: &'static str) -> Result<usize> {
154    lhs.checked_mul(rhs)
155        .ok_or_else(|| Error::InvalidImageLayout(format!("{context} overflows usize")))
156}
157
158pub(crate) fn allocate_decode_output(output_len: usize, budget: usize) -> Result<Vec<u8>> {
159    let mut output = allocate_decode_output_capacity(output_len, budget)?;
160    output.resize(output_len, 0);
161    Ok(output)
162}
163
164pub(crate) fn allocate_decode_output_capacity(output_len: usize, budget: usize) -> Result<Vec<u8>> {
165    validate_decode_output_len(output_len, budget)?;
166    let mut output = Vec::new();
167    output
168        .try_reserve_exact(output_len)
169        .map_err(|error| Error::DecodeOutputAllocationFailed {
170            requested: output_len,
171            reason: error.to_string(),
172        })?;
173    Ok(output)
174}
175
176pub(crate) fn copy_decode_output(bytes: &[u8], budget: usize) -> Result<Vec<u8>> {
177    let mut output = allocate_decode_output_capacity(bytes.len(), budget)?;
178    output.extend_from_slice(bytes);
179    Ok(output)
180}
181
182pub(crate) fn validate_decode_output_len(output_len: usize, budget: usize) -> Result<()> {
183    if output_len > budget {
184        return Err(Error::DecodeOutputTooLarge {
185            requested: output_len,
186            limit: budget,
187        });
188    }
189    Ok(())
190}
191
192impl GdalStructuralMetadata {
193    fn from_prefix(bytes: &[u8]) -> Option<Self> {
194        let text = std::str::from_utf8(bytes).ok()?;
195        if !text.contains("GDAL_STRUCTURAL_METADATA_SIZE=") {
196            return None;
197        }
198
199        Some(Self {
200            block_leader_size_as_u32: text.contains("BLOCK_LEADER=SIZE_AS_UINT4"),
201            block_trailer_repeats_last_4_bytes: text
202                .contains("BLOCK_TRAILER=LAST_4_BYTES_REPEATED"),
203        })
204    }
205
206    pub(crate) fn unwrap_block<'a>(
207        &self,
208        raw: &'a [u8],
209        byte_order: ByteOrder,
210        offset: u64,
211    ) -> Result<&'a [u8]> {
212        if self.block_leader_size_as_u32 {
213            if raw.len() < 4 {
214                return Ok(raw);
215            }
216            let declared_len = match byte_order {
217                ByteOrder::LittleEndian => u32::from_le_bytes(raw[..4].try_into().unwrap()),
218                ByteOrder::BigEndian => u32::from_be_bytes(raw[..4].try_into().unwrap()),
219            } as usize;
220            if let Some(payload_end) = 4usize.checked_add(declared_len) {
221                if payload_end <= raw.len() {
222                    if self.block_trailer_repeats_last_4_bytes {
223                        let trailer_end = payload_end.checked_add(4).ok_or_else(|| {
224                            Error::InvalidImageLayout("GDAL block trailer overflows usize".into())
225                        })?;
226                        if trailer_end <= raw.len() {
227                            let expected = &raw[payload_end - 4..payload_end];
228                            let trailer = &raw[payload_end..trailer_end];
229                            if expected != trailer {
230                                return Err(Error::InvalidImageLayout(format!(
231                                    "GDAL block trailer mismatch at offset {offset}"
232                                )));
233                            }
234                        }
235                    }
236                    return Ok(&raw[4..payload_end]);
237                }
238            }
239        }
240
241        if self.block_trailer_repeats_last_4_bytes && raw.len() >= 8 {
242            let split = raw.len() - 4;
243            if raw[split - 4..split] == raw[split..] {
244                return Ok(&raw[..split]);
245            }
246        }
247
248        Ok(raw)
249    }
250}
251
252pub(crate) fn read_block_payload(
253    source: &dyn TiffSource,
254    offset: u64,
255    byte_count: u64,
256    byte_count_limit: usize,
257    index: usize,
258) -> Result<Vec<u8>> {
259    let len = validate_block_byte_count(index, byte_count, byte_count_limit)?;
260    if let Some(bytes) = source.as_slice() {
261        let start = usize::try_from(offset).map_err(|_| Error::OffsetOutOfBounds {
262            offset,
263            length: byte_count,
264            data_len: bytes.len() as u64,
265        })?;
266        let end = start.checked_add(len).ok_or(Error::OffsetOutOfBounds {
267            offset,
268            length: byte_count,
269            data_len: bytes.len() as u64,
270        })?;
271        if end > bytes.len() {
272            return Err(Error::OffsetOutOfBounds {
273                offset,
274                length: byte_count,
275                data_len: bytes.len() as u64,
276            });
277        }
278        Ok(bytes[start..end].to_vec())
279    } else {
280        source.read_exact_at(offset, len)
281    }
282}
283
284pub(crate) fn read_gdal_block_payload(
285    source: &dyn TiffSource,
286    metadata: &GdalStructuralMetadata,
287    byte_order: ByteOrder,
288    offset: u64,
289    byte_count: u64,
290    byte_count_limit: usize,
291    index: usize,
292) -> Result<Vec<u8>> {
293    let payload_len = validate_block_byte_count(index, byte_count, byte_count_limit)?;
294
295    // GDAL's COG ghost area wraps each block in a 4-byte size leader (plus an
296    // optional repeated 4-byte trailer) while the IFD offset points at the
297    // payload itself. Read the wrapped copy first; it wins outright when its
298    // unwrapped payload has exactly the declared length.
299    let wrapped_result = (metadata.block_leader_size_as_u32 && offset >= 4).then(|| {
300        read_wrapped_gdal_block(
301            source,
302            metadata,
303            byte_order,
304            offset,
305            byte_count,
306            byte_count_limit,
307            index,
308        )
309    });
310    if let Some(Ok(payload)) = &wrapped_result {
311        if payload.len() == payload_len {
312            return Ok(payload.clone());
313        }
314    }
315
316    // Otherwise a successful direct read of the declared payload range wins,
317    // with a mismatched-but-readable wrapped payload as the last resort.
318    let direct_result = source
319        .read_exact_at(offset, payload_len)
320        .and_then(|raw| Ok(metadata.unwrap_block(&raw, byte_order, offset)?.to_vec()));
321    match direct_result {
322        Ok(payload) => {
323            if payload.len() > byte_count_limit {
324                return Err(block_byte_count_too_large(
325                    index,
326                    payload.len() as u64,
327                    byte_count_limit,
328                ));
329            }
330            Ok(payload)
331        }
332        Err(direct_error) => match wrapped_result {
333            Some(Ok(payload)) => Ok(payload),
334            Some(Err(wrapped_error)) => Err(wrapped_error),
335            None => Err(direct_error),
336        },
337    }
338}
339
340/// Read and unwrap the leader-prefixed copy of a GDAL ghost-area block.
341fn read_wrapped_gdal_block(
342    source: &dyn TiffSource,
343    metadata: &GdalStructuralMetadata,
344    byte_order: ByteOrder,
345    offset: u64,
346    byte_count: u64,
347    byte_count_limit: usize,
348    index: usize,
349) -> Result<Vec<u8>> {
350    let wrapper_extra = if metadata.block_trailer_repeats_last_4_bytes {
351        8u64
352    } else {
353        4u64
354    };
355    let wrapped_offset = offset - 4;
356    let wrapped_len = byte_count.checked_add(wrapper_extra).ok_or_else(|| {
357        Error::InvalidImageLayout("GDAL wrapped block length overflows u64".into())
358    })?;
359    let len = usize::try_from(wrapped_len).map_err(|_| Error::OffsetOutOfBounds {
360        offset: wrapped_offset,
361        length: wrapped_len,
362        data_len: source.len(),
363    })?;
364    let raw = source.read_exact_at(wrapped_offset, len)?;
365    let payload = metadata.unwrap_block(&raw, byte_order, wrapped_offset)?;
366    if payload.len() > byte_count_limit {
367        return Err(block_byte_count_too_large(
368            index,
369            payload.len() as u64,
370            byte_count_limit,
371        ));
372    }
373    Ok(payload.to_vec())
374}
375
376fn validate_block_byte_count(
377    index: usize,
378    byte_count: u64,
379    byte_count_limit: usize,
380) -> Result<usize> {
381    let len = usize::try_from(byte_count)
382        .map_err(|_| block_byte_count_too_large(index, byte_count, byte_count_limit))?;
383    if len > byte_count_limit {
384        return Err(block_byte_count_too_large(
385            index,
386            byte_count,
387            byte_count_limit,
388        ));
389    }
390    Ok(len)
391}
392
393fn block_byte_count_too_large(index: usize, byte_count: u64, byte_count_limit: usize) -> Error {
394    Error::DecompressionFailed {
395        index,
396        reason: format!(
397            "encoded block byte count {byte_count} exceeds TIFF block read budget {byte_count_limit}"
398        ),
399    }
400}
401
402const GDAL_STRUCTURAL_METADATA_PREFIX: &str = "GDAL_STRUCTURAL_METADATA_SIZE=";
403
404// TiffSample trait and impls are provided by tiff-core and re-exported above.
405
406impl TiffFile {
407    /// Open a TIFF file from disk using safe file-backed I/O.
408    pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
409        Self::open_with_options(path, OpenOptions::default())
410    }
411
412    /// Open a TIFF file from disk using safe file-backed I/O with explicit decoder options.
413    pub fn open_with_options<P: AsRef<Path>>(path: P, options: OpenOptions) -> Result<Self> {
414        let source: SharedSource = Arc::new(FileSource::open(path.as_ref())?);
415        Self::from_source_with_options(source, options)
416    }
417
418    /// Open a TIFF file from disk using memory-mapped I/O.
419    ///
420    /// # Safety
421    ///
422    /// The caller must guarantee that the mapped file is not mutated or
423    /// truncated while the returned `TiffFile` is alive. This includes writes
424    /// through other file handles and writes from other processes.
425    pub unsafe fn open_mmap<P: AsRef<Path>>(path: P) -> Result<Self> {
426        unsafe { Self::open_mmap_with_options(path, OpenOptions::default()) }
427    }
428
429    /// Open a TIFF file from disk using memory-mapped I/O with explicit decoder options.
430    ///
431    /// # Safety
432    ///
433    /// The caller must guarantee that the mapped file is not mutated or
434    /// truncated while the returned `TiffFile` is alive. This includes writes
435    /// through other file handles and writes from other processes.
436    pub unsafe fn open_mmap_with_options<P: AsRef<Path>>(
437        path: P,
438        options: OpenOptions,
439    ) -> Result<Self> {
440        let source: SharedSource = Arc::new(unsafe { MmapSource::open(path.as_ref())? });
441        Self::from_source_with_options(source, options)
442    }
443
444    /// Open a TIFF file from an owned byte buffer (WASM-compatible).
445    pub fn from_bytes(data: Vec<u8>) -> Result<Self> {
446        Self::from_bytes_with_options(data, OpenOptions::default())
447    }
448
449    /// Open a TIFF file from bytes with explicit decoder options.
450    pub fn from_bytes_with_options(data: Vec<u8>, options: OpenOptions) -> Result<Self> {
451        let source: SharedSource = Arc::new(BytesSource::new(data));
452        Self::from_source_with_options(source, options)
453    }
454
455    /// Open a TIFF file from an arbitrary random-access source.
456    pub fn from_source(source: SharedSource) -> Result<Self> {
457        Self::from_source_with_options(source, OpenOptions::default())
458    }
459
460    /// Open a TIFF file from an arbitrary random-access source with options.
461    pub fn from_source_with_options(source: SharedSource, options: OpenOptions) -> Result<Self> {
462        let header_len = usize::try_from(source.len().min(16)).unwrap_or(16);
463        let header_bytes = source.read_exact_at(0, header_len)?;
464        let header = header::TiffHeader::parse(&header_bytes)?;
465        let gdal_structural_metadata = parse_gdal_structural_metadata(source.as_ref());
466        let ifds =
467            ifd::parse_ifd_chain_with_budgets(source.as_ref(), &header, options.parse_budgets)?;
468        Ok(Self {
469            source,
470            header,
471            ifds,
472            parse_budgets: options.parse_budgets,
473            decode_output_bytes: options.decode_output_bytes,
474            block_cache: Arc::new(BlockCache::new(
475                options.block_cache_bytes,
476                options.block_cache_slots,
477            )),
478            gdal_structural_metadata,
479        })
480    }
481
482    /// Returns the byte order of the TIFF file.
483    pub fn byte_order(&self) -> ByteOrder {
484        self.header.byte_order
485    }
486
487    /// Returns `true` if this is a BigTIFF file.
488    pub fn is_bigtiff(&self) -> bool {
489        self.header.is_bigtiff()
490    }
491
492    /// Returns the number of IFDs (images/pages) in the file.
493    pub fn ifd_count(&self) -> usize {
494        self.ifds.len()
495    }
496
497    /// Returns the IFD at the given index.
498    pub fn ifd(&self, index: usize) -> Result<&Ifd> {
499        self.ifds.get(index).ok_or(Error::IfdNotFound(index))
500    }
501
502    /// Returns all parsed IFDs.
503    pub fn ifds(&self) -> &[Ifd] {
504        &self.ifds
505    }
506
507    /// Returns the raw file bytes when the source exposes a resident immutable slice.
508    ///
509    /// This returns `Some` for in-memory and memory-mapped sources. It returns
510    /// `None` for the default safe file-backed source.
511    pub fn raw_bytes(&self) -> Option<&[u8]> {
512        self.source.as_slice()
513    }
514
515    /// Returns the backing source.
516    pub fn source(&self) -> &dyn TiffSource {
517        self.source.as_ref()
518    }
519
520    fn decode_read_options(&self) -> DecodeReadOptions<'_> {
521        DecodeReadOptions {
522            decode_output_bytes: self.decode_output_bytes,
523            gdal_structural_metadata: self.gdal_structural_metadata.as_ref(),
524        }
525    }
526
527    /// Parse an IFD at an arbitrary file offset.
528    pub fn read_ifd_at_offset(&self, offset: u64) -> Result<Ifd> {
529        ifd::parse_ifd_at_with_budgets(
530            self.source.as_ref(),
531            &self.header,
532            offset,
533            self.parse_budgets,
534        )
535    }
536
537    /// Decode an image into native-endian interleaved storage sample bytes.
538    pub fn read_image_bytes(&self, ifd_index: usize) -> Result<Vec<u8>> {
539        let ifd = self.ifd(ifd_index)?;
540        self.read_image_bytes_from_ifd(ifd)
541    }
542
543    /// Decode an arbitrary IFD into native-endian interleaved storage sample bytes.
544    pub fn read_image_bytes_from_ifd(&self, ifd: &Ifd) -> Result<Vec<u8>> {
545        let layout = ifd.raster_layout()?;
546        self.decode_window_sample_bytes(
547            ifd,
548            Window {
549                row_off: 0,
550                col_off: 0,
551                rows: layout.height,
552                cols: layout.width,
553            },
554        )
555    }
556
557    /// Decode an image into native-endian interleaved color-decoded pixel bytes.
558    pub fn read_decoded_image_bytes(&self, ifd_index: usize) -> Result<Vec<u8>> {
559        let ifd = self.ifd(ifd_index)?;
560        self.read_decoded_image_bytes_from_ifd(ifd)
561    }
562
563    /// Decode an arbitrary IFD into native-endian interleaved color-decoded
564    /// pixel bytes.
565    pub fn read_decoded_image_bytes_from_ifd(&self, ifd: &Ifd) -> Result<Vec<u8>> {
566        let layout = ifd.decoded_raster_layout()?;
567        self.decode_window_pixel_bytes(
568            ifd,
569            Window {
570                row_off: 0,
571                col_off: 0,
572                rows: layout.height,
573                cols: layout.width,
574            },
575        )
576    }
577
578    /// Decode a pixel window into native-endian interleaved storage sample
579    /// bytes.
580    pub fn read_window_bytes(
581        &self,
582        ifd_index: usize,
583        row_off: usize,
584        col_off: usize,
585        rows: usize,
586        cols: usize,
587    ) -> Result<Vec<u8>> {
588        let ifd = self.ifd(ifd_index)?;
589        self.read_window_bytes_from_ifd(ifd, row_off, col_off, rows, cols)
590    }
591
592    /// Decode a pixel window into native-endian interleaved color-decoded pixel
593    /// bytes.
594    pub fn read_decoded_window_bytes(
595        &self,
596        ifd_index: usize,
597        row_off: usize,
598        col_off: usize,
599        rows: usize,
600        cols: usize,
601    ) -> Result<Vec<u8>> {
602        let ifd = self.ifd(ifd_index)?;
603        self.read_decoded_window_bytes_from_ifd(ifd, row_off, col_off, rows, cols)
604    }
605
606    /// Decode a pixel window from an arbitrary IFD into native-endian
607    /// interleaved storage sample bytes.
608    pub fn read_window_bytes_from_ifd(
609        &self,
610        ifd: &Ifd,
611        row_off: usize,
612        col_off: usize,
613        rows: usize,
614        cols: usize,
615    ) -> Result<Vec<u8>> {
616        let layout = ifd.raster_layout()?;
617        let window = validate_window(&layout, row_off, col_off, rows, cols)?;
618        self.decode_window_sample_bytes(ifd, window)
619    }
620
621    /// Decode a pixel window from an arbitrary IFD into native-endian
622    /// interleaved color-decoded pixel bytes.
623    pub fn read_decoded_window_bytes_from_ifd(
624        &self,
625        ifd: &Ifd,
626        row_off: usize,
627        col_off: usize,
628        rows: usize,
629        cols: usize,
630    ) -> Result<Vec<u8>> {
631        let layout = ifd.decoded_raster_layout()?;
632        let window = validate_window(&layout, row_off, col_off, rows, cols)?;
633        self.decode_window_pixel_bytes(ifd, window)
634    }
635
636    /// Decode a single storage-domain band into native-endian sample bytes.
637    pub fn read_band_bytes(&self, ifd_index: usize, band_index: usize) -> Result<Vec<u8>> {
638        let ifd = self.ifd(ifd_index)?;
639        self.read_band_bytes_from_ifd(ifd, band_index)
640    }
641
642    /// Decode a single storage-domain band from an arbitrary IFD into
643    /// native-endian sample bytes.
644    pub fn read_band_bytes_from_ifd(&self, ifd: &Ifd, band_index: usize) -> Result<Vec<u8>> {
645        let layout = ifd.raster_layout()?;
646        self.read_band_window_bytes_from_ifd(ifd, band_index, 0, 0, layout.height, layout.width)
647    }
648
649    /// Decode a pixel window from one storage-domain band into native-endian
650    /// sample bytes.
651    pub fn read_band_window_bytes(
652        &self,
653        ifd_index: usize,
654        band_index: usize,
655        row_off: usize,
656        col_off: usize,
657        rows: usize,
658        cols: usize,
659    ) -> Result<Vec<u8>> {
660        let ifd = self.ifd(ifd_index)?;
661        self.read_band_window_bytes_from_ifd(ifd, band_index, row_off, col_off, rows, cols)
662    }
663
664    /// Decode a pixel window from one storage-domain band in an arbitrary IFD
665    /// into native-endian sample bytes.
666    pub fn read_band_window_bytes_from_ifd(
667        &self,
668        ifd: &Ifd,
669        band_index: usize,
670        row_off: usize,
671        col_off: usize,
672        rows: usize,
673        cols: usize,
674    ) -> Result<Vec<u8>> {
675        let layout = ifd.raster_layout()?;
676        validate_band_index(&layout, band_index)?;
677        let window = validate_window(&layout, row_off, col_off, rows, cols)?;
678        self.decode_window_sample_band_bytes(ifd, window, band_index)
679    }
680
681    fn decode_window_sample_bytes(&self, ifd: &Ifd, window: Window) -> Result<Vec<u8>> {
682        if window.is_empty() {
683            return Ok(Vec::new());
684        }
685
686        if ifd.is_tiled() {
687            tile::read_window(
688                self.source.as_ref(),
689                ifd,
690                self.byte_order(),
691                &self.block_cache,
692                window,
693                self.decode_read_options(),
694            )
695        } else {
696            strip::read_window(
697                self.source.as_ref(),
698                ifd,
699                self.byte_order(),
700                &self.block_cache,
701                window,
702                self.decode_read_options(),
703            )
704        }
705    }
706
707    fn decode_window_sample_band_bytes(
708        &self,
709        ifd: &Ifd,
710        window: Window,
711        band_index: usize,
712    ) -> Result<Vec<u8>> {
713        if window.is_empty() {
714            return Ok(Vec::new());
715        }
716
717        let layout = ifd.raster_layout()?;
718        validate_band_index(&layout, band_index)?;
719        if ifd.is_tiled() {
720            tile::read_window_band(
721                self.source.as_ref(),
722                ifd,
723                self.byte_order(),
724                &self.block_cache,
725                window,
726                band_index,
727                self.decode_read_options(),
728            )
729        } else {
730            strip::read_window_band(
731                self.source.as_ref(),
732                ifd,
733                self.byte_order(),
734                &self.block_cache,
735                window,
736                band_index,
737                self.decode_read_options(),
738            )
739        }
740    }
741
742    fn decode_window_pixel_bytes(&self, ifd: &Ifd, window: Window) -> Result<Vec<u8>> {
743        let storage_layout = ifd.raster_layout()?;
744        let sample_bytes = self.decode_window_sample_bytes(ifd, window)?;
745        let (_, pixels) = pixel::decode_pixels(
746            ifd,
747            &storage_layout,
748            window.cols,
749            window.rows,
750            &sample_bytes,
751            self.decode_output_bytes,
752        )?;
753        Ok(pixels)
754    }
755
756    /// Decode a window into a typed ndarray of storage-domain samples.
757    ///
758    /// Single-band rasters are returned as shape `[rows, cols]`.
759    /// Multi-band rasters are returned as shape `[rows, cols, samples_per_pixel]`.
760    pub fn read_window<T: TiffSample>(
761        &self,
762        ifd_index: usize,
763        row_off: usize,
764        col_off: usize,
765        rows: usize,
766        cols: usize,
767    ) -> Result<ArrayD<T>> {
768        let ifd = self.ifd(ifd_index)?;
769        self.read_window_from_ifd(ifd, row_off, col_off, rows, cols)
770    }
771
772    /// Decode a window from an arbitrary IFD into a typed ndarray of
773    /// storage-domain samples.
774    pub fn read_window_from_ifd<T: TiffSample>(
775        &self,
776        ifd: &Ifd,
777        row_off: usize,
778        col_off: usize,
779        rows: usize,
780        cols: usize,
781    ) -> Result<ArrayD<T>> {
782        let layout = ifd.raster_layout()?;
783        let window = validate_window(&layout, row_off, col_off, rows, cols)?;
784        if !T::matches_layout(&layout) {
785            return Err(Error::TypeMismatch {
786                expected: T::type_name(),
787                actual: format!(
788                    "sample_format={} bits_per_sample={}",
789                    layout.sample_format, layout.bits_per_sample
790                ),
791            });
792        }
793
794        let decoded = self.decode_window_sample_bytes(ifd, window)?;
795        let values = T::decode_many(&decoded);
796        let shape = if layout.samples_per_pixel == 1 {
797            vec![window.rows, window.cols]
798        } else {
799            vec![window.rows, window.cols, layout.samples_per_pixel]
800        };
801        ArrayD::from_shape_vec(IxDyn(&shape), values).map_err(|e| {
802            Error::InvalidImageLayout(format!("failed to build ndarray from storage raster: {e}"))
803        })
804    }
805
806    /// Decode a window into a typed ndarray of color-decoded pixels.
807    ///
808    /// Single-channel decoded rasters are returned as shape `[rows, cols]`.
809    /// Multi-channel decoded rasters are returned as shape `[rows, cols, channels]`.
810    pub fn read_decoded_window<T: TiffSample>(
811        &self,
812        ifd_index: usize,
813        row_off: usize,
814        col_off: usize,
815        rows: usize,
816        cols: usize,
817    ) -> Result<ArrayD<T>> {
818        let ifd = self.ifd(ifd_index)?;
819        self.read_decoded_window_from_ifd(ifd, row_off, col_off, rows, cols)
820    }
821
822    /// Decode a window from an arbitrary IFD into a typed ndarray of
823    /// color-decoded pixels.
824    pub fn read_decoded_window_from_ifd<T: TiffSample>(
825        &self,
826        ifd: &Ifd,
827        row_off: usize,
828        col_off: usize,
829        rows: usize,
830        cols: usize,
831    ) -> Result<ArrayD<T>> {
832        let layout = ifd.decoded_raster_layout()?;
833        let window = validate_window(&layout, row_off, col_off, rows, cols)?;
834        if !T::matches_layout(&layout) {
835            return Err(Error::TypeMismatch {
836                expected: T::type_name(),
837                actual: format!(
838                    "sample_format={} bits_per_sample={}",
839                    layout.sample_format, layout.bits_per_sample
840                ),
841            });
842        }
843
844        let decoded = self.decode_window_pixel_bytes(ifd, window)?;
845        let values = T::decode_many(&decoded);
846        let shape = if layout.samples_per_pixel == 1 {
847            vec![window.rows, window.cols]
848        } else {
849            vec![window.rows, window.cols, layout.samples_per_pixel]
850        };
851        ArrayD::from_shape_vec(IxDyn(&shape), values).map_err(|e| {
852            Error::InvalidImageLayout(format!("failed to build ndarray from decoded raster: {e}"))
853        })
854    }
855
856    /// Decode one storage-domain band into a typed `[height, width]` ndarray.
857    pub fn read_band<T: TiffSample>(
858        &self,
859        ifd_index: usize,
860        band_index: usize,
861    ) -> Result<ArrayD<T>> {
862        let ifd = self.ifd(ifd_index)?;
863        self.read_band_from_ifd(ifd, band_index)
864    }
865
866    /// Decode one storage-domain band from an arbitrary IFD into a typed
867    /// `[height, width]` ndarray.
868    pub fn read_band_from_ifd<T: TiffSample>(
869        &self,
870        ifd: &Ifd,
871        band_index: usize,
872    ) -> Result<ArrayD<T>> {
873        let layout = ifd.raster_layout()?;
874        self.read_band_window_from_ifd(ifd, band_index, 0, 0, layout.height, layout.width)
875    }
876
877    /// Decode a window from one storage-domain band into a typed
878    /// `[rows, cols]` ndarray.
879    pub fn read_band_window<T: TiffSample>(
880        &self,
881        ifd_index: usize,
882        band_index: usize,
883        row_off: usize,
884        col_off: usize,
885        rows: usize,
886        cols: usize,
887    ) -> Result<ArrayD<T>> {
888        let ifd = self.ifd(ifd_index)?;
889        self.read_band_window_from_ifd(ifd, band_index, row_off, col_off, rows, cols)
890    }
891
892    /// Decode a window from one storage-domain band in an arbitrary IFD into a
893    /// typed `[rows, cols]` ndarray.
894    pub fn read_band_window_from_ifd<T: TiffSample>(
895        &self,
896        ifd: &Ifd,
897        band_index: usize,
898        row_off: usize,
899        col_off: usize,
900        rows: usize,
901        cols: usize,
902    ) -> Result<ArrayD<T>> {
903        let layout = ifd.raster_layout()?;
904        validate_band_index(&layout, band_index)?;
905        let window = validate_window(&layout, row_off, col_off, rows, cols)?;
906        if !T::matches_layout(&layout) {
907            return Err(Error::TypeMismatch {
908                expected: T::type_name(),
909                actual: format!(
910                    "sample_format={} bits_per_sample={}",
911                    layout.sample_format, layout.bits_per_sample
912                ),
913            });
914        }
915
916        let decoded = self.decode_window_sample_band_bytes(ifd, window, band_index)?;
917        let values = T::decode_many(&decoded);
918        ArrayD::from_shape_vec(IxDyn(&[window.rows, window.cols]), values).map_err(|e| {
919            Error::InvalidImageLayout(format!("failed to build ndarray from band raster: {e}"))
920        })
921    }
922
923    /// Decode an image into a typed ndarray of storage-domain samples.
924    ///
925    /// Single-band rasters are returned as shape `[height, width]`.
926    /// Multi-band rasters are returned as shape `[height, width, samples_per_pixel]`.
927    pub fn read_image<T: TiffSample>(&self, ifd_index: usize) -> Result<ArrayD<T>> {
928        let ifd = self.ifd(ifd_index)?;
929        self.read_image_from_ifd(ifd)
930    }
931
932    /// Decode an arbitrary IFD into a typed ndarray of storage-domain samples.
933    pub fn read_image_from_ifd<T: TiffSample>(&self, ifd: &Ifd) -> Result<ArrayD<T>> {
934        let layout = ifd.raster_layout()?;
935        if !T::matches_layout(&layout) {
936            return Err(Error::TypeMismatch {
937                expected: T::type_name(),
938                actual: format!(
939                    "sample_format={} bits_per_sample={}",
940                    layout.sample_format, layout.bits_per_sample
941                ),
942            });
943        }
944
945        self.read_window_from_ifd(ifd, 0, 0, layout.height, layout.width)
946    }
947
948    /// Decode an image into a typed ndarray of color-decoded pixels.
949    ///
950    /// Single-channel decoded rasters are returned as shape `[height, width]`.
951    /// Multi-channel decoded rasters are returned as shape
952    /// `[height, width, channels]`.
953    pub fn read_decoded_image<T: TiffSample>(&self, ifd_index: usize) -> Result<ArrayD<T>> {
954        let ifd = self.ifd(ifd_index)?;
955        self.read_decoded_image_from_ifd(ifd)
956    }
957
958    /// Decode an arbitrary IFD into a typed ndarray of color-decoded pixels.
959    pub fn read_decoded_image_from_ifd<T: TiffSample>(&self, ifd: &Ifd) -> Result<ArrayD<T>> {
960        let layout = ifd.decoded_raster_layout()?;
961        if !T::matches_layout(&layout) {
962            return Err(Error::TypeMismatch {
963                expected: T::type_name(),
964                actual: format!(
965                    "sample_format={} bits_per_sample={}",
966                    layout.sample_format, layout.bits_per_sample
967                ),
968            });
969        }
970
971        self.read_decoded_window_from_ifd(ifd, 0, 0, layout.height, layout.width)
972    }
973}
974
975fn validate_window(
976    layout: &RasterLayout,
977    row_off: usize,
978    col_off: usize,
979    rows: usize,
980    cols: usize,
981) -> Result<Window> {
982    let row_end = row_off
983        .checked_add(rows)
984        .ok_or_else(|| Error::InvalidImageLayout("window row range overflows usize".into()))?;
985    let col_end = col_off
986        .checked_add(cols)
987        .ok_or_else(|| Error::InvalidImageLayout("window column range overflows usize".into()))?;
988    if row_end > layout.height || col_end > layout.width {
989        return Err(Error::InvalidImageLayout(format!(
990            "window [{row_off}..{row_end}, {col_off}..{col_end}) exceeds raster bounds {}x{}",
991            layout.height, layout.width
992        )));
993    }
994    Ok(Window {
995        row_off,
996        col_off,
997        rows,
998        cols,
999    })
1000}
1001
1002fn validate_band_index(layout: &RasterLayout, band_index: usize) -> Result<()> {
1003    if band_index >= layout.samples_per_pixel {
1004        return Err(Error::BandIndexOutOfBounds {
1005            index: band_index,
1006            band_count: layout.samples_per_pixel,
1007        });
1008    }
1009    Ok(())
1010}
1011
1012fn parse_gdal_structural_metadata(source: &dyn TiffSource) -> Option<GdalStructuralMetadata> {
1013    let available_len = usize::try_from(source.len().checked_sub(8)?).ok()?;
1014    if available_len == 0 {
1015        return None;
1016    }
1017
1018    let probe_len = available_len.min(64);
1019    let probe = source.read_exact_at(8, probe_len).ok()?;
1020    let total_len = parse_gdal_structural_metadata_len(&probe)?;
1021    if total_len == 0 || total_len > available_len {
1022        return None;
1023    }
1024
1025    let bytes = source.read_exact_at(8, total_len).ok()?;
1026    GdalStructuralMetadata::from_prefix(&bytes)
1027}
1028
1029fn parse_gdal_structural_metadata_len(bytes: &[u8]) -> Option<usize> {
1030    let text = std::str::from_utf8(bytes).ok()?;
1031    let newline_index = text.find('\n')?;
1032    let header = &text[..newline_index];
1033    let value = header.strip_prefix(GDAL_STRUCTURAL_METADATA_PREFIX)?;
1034    let digits: String = value.chars().take_while(|ch| ch.is_ascii_digit()).collect();
1035    if digits.is_empty() {
1036        return None;
1037    }
1038    let payload_len: usize = digits.parse().ok()?;
1039    newline_index.checked_add(1)?.checked_add(payload_len)
1040}
1041
1042#[cfg(test)]
1043mod tests {
1044    use std::collections::BTreeMap;
1045    use std::fs;
1046    use std::path::PathBuf;
1047    use std::sync::atomic::{AtomicUsize, Ordering};
1048    use std::sync::Arc;
1049    use std::time::{SystemTime, UNIX_EPOCH};
1050
1051    use super::{
1052        parse_gdal_structural_metadata, parse_gdal_structural_metadata_len, Error,
1053        GdalStructuralMetadata, OpenOptions, ParseBudgets, TiffFile,
1054        GDAL_STRUCTURAL_METADATA_PREFIX,
1055    };
1056    use crate::source::{BytesSource, TiffSource};
1057    use flate2::{write::ZlibEncoder, Compression as FlateCompression};
1058
1059    fn le_u16(value: u16) -> [u8; 2] {
1060        value.to_le_bytes()
1061    }
1062
1063    fn le_u32(value: u32) -> [u8; 4] {
1064        value.to_le_bytes()
1065    }
1066
1067    fn le_u64(value: u64) -> [u8; 8] {
1068        value.to_le_bytes()
1069    }
1070
1071    fn temp_tiff_path(test_name: &str) -> PathBuf {
1072        let nanos = SystemTime::now()
1073            .duration_since(UNIX_EPOCH)
1074            .unwrap()
1075            .as_nanos();
1076        std::env::temp_dir().join(format!(
1077            "geotiff-rust-{test_name}-{}-{nanos}.tif",
1078            std::process::id()
1079        ))
1080    }
1081
1082    fn bigtiff_header(first_ifd_offset: u64) -> Vec<u8> {
1083        let mut bytes = Vec::new();
1084        bytes.extend_from_slice(b"II");
1085        bytes.extend_from_slice(&le_u16(43));
1086        bytes.extend_from_slice(&le_u16(8));
1087        bytes.extend_from_slice(&le_u16(0));
1088        bytes.extend_from_slice(&le_u64(first_ifd_offset));
1089        bytes
1090    }
1091
1092    fn inline_short(value: u16) -> Vec<u8> {
1093        let mut bytes = [0u8; 4];
1094        bytes[..2].copy_from_slice(&le_u16(value));
1095        bytes.to_vec()
1096    }
1097
1098    fn build_stripped_tiff(
1099        width: u32,
1100        height: u32,
1101        image_data: &[u8],
1102        overrides: &[(u16, u16, u32, Vec<u8>)],
1103    ) -> Vec<u8> {
1104        let mut entries = BTreeMap::new();
1105        entries.insert(256, (4, 1, le_u32(width).to_vec()));
1106        entries.insert(257, (4, 1, le_u32(height).to_vec()));
1107        entries.insert(258, (3, 1, [8, 0, 0, 0].to_vec()));
1108        entries.insert(259, (3, 1, [1, 0, 0, 0].to_vec()));
1109        entries.insert(273, (4, 1, Vec::new()));
1110        entries.insert(277, (3, 1, [1, 0, 0, 0].to_vec()));
1111        entries.insert(278, (4, 1, le_u32(height).to_vec()));
1112        entries.insert(279, (4, 1, le_u32(image_data.len() as u32).to_vec()));
1113        for &(tag, ty, count, ref value) in overrides {
1114            entries.insert(tag, (ty, count, value.clone()));
1115        }
1116
1117        let ifd_offset = 8u32;
1118        let ifd_size = 2 + entries.len() * 12 + 4;
1119        let mut next_data_offset = ifd_offset as usize + ifd_size;
1120        let image_offset = next_data_offset as u32;
1121        next_data_offset += image_data.len();
1122
1123        let mut data = Vec::with_capacity(next_data_offset);
1124        data.extend_from_slice(b"II");
1125        data.extend_from_slice(&le_u16(42));
1126        data.extend_from_slice(&le_u32(ifd_offset));
1127        data.extend_from_slice(&le_u16(entries.len() as u16));
1128
1129        let mut deferred = Vec::new();
1130        for (tag, (ty, count, value)) in entries {
1131            data.extend_from_slice(&le_u16(tag));
1132            data.extend_from_slice(&le_u16(ty));
1133            data.extend_from_slice(&le_u32(count));
1134            if tag == 273 {
1135                data.extend_from_slice(&le_u32(image_offset));
1136            } else if value.len() <= 4 {
1137                let mut inline = [0u8; 4];
1138                inline[..value.len()].copy_from_slice(&value);
1139                data.extend_from_slice(&inline);
1140            } else {
1141                let offset = next_data_offset as u32;
1142                data.extend_from_slice(&le_u32(offset));
1143                next_data_offset += value.len();
1144                deferred.push(value);
1145            }
1146        }
1147        data.extend_from_slice(&le_u32(0));
1148        data.extend_from_slice(image_data);
1149        for value in deferred {
1150            data.extend_from_slice(&value);
1151        }
1152        data
1153    }
1154
1155    /// Build a classic TIFF whose chain holds one 1x1 uncompressed IFD per
1156    /// pixel value, with every tag value stored inline.
1157    fn build_multi_ifd_tiff(pixel_values: &[u8]) -> Vec<u8> {
1158        const IFD_ENTRY_COUNT: usize = 8;
1159        const IFD_SIZE: usize = 2 + IFD_ENTRY_COUNT * 12 + 4;
1160        const IFD_STRIDE: usize = IFD_SIZE + 1; // one 1x1 u8 strip per IFD
1161
1162        let mut data = Vec::with_capacity(8 + pixel_values.len() * IFD_STRIDE);
1163        data.extend_from_slice(b"II");
1164        data.extend_from_slice(&le_u16(42));
1165        data.extend_from_slice(&le_u32(8));
1166
1167        for (index, &pixel) in pixel_values.iter().enumerate() {
1168            let ifd_offset = 8 + index * IFD_STRIDE;
1169            debug_assert_eq!(data.len(), ifd_offset);
1170            let image_offset = (ifd_offset + IFD_SIZE) as u32;
1171            let next_ifd_offset = if index + 1 < pixel_values.len() {
1172                (ifd_offset + IFD_STRIDE) as u32
1173            } else {
1174                0
1175            };
1176
1177            data.extend_from_slice(&le_u16(IFD_ENTRY_COUNT as u16));
1178            for (tag, ty, value) in [
1179                (256u16, 4u16, le_u32(1).to_vec()),
1180                (257, 4, le_u32(1).to_vec()),
1181                (258, 3, inline_short(8)),
1182                (259, 3, inline_short(1)),
1183                (273, 4, le_u32(image_offset).to_vec()),
1184                (277, 3, inline_short(1)),
1185                (278, 4, le_u32(1).to_vec()),
1186                (279, 4, le_u32(1).to_vec()),
1187            ] {
1188                data.extend_from_slice(&le_u16(tag));
1189                data.extend_from_slice(&le_u16(ty));
1190                data.extend_from_slice(&le_u32(1));
1191                let mut inline = [0u8; 4];
1192                inline[..value.len()].copy_from_slice(&value);
1193                data.extend_from_slice(&inline);
1194            }
1195            data.extend_from_slice(&le_u32(next_ifd_offset));
1196            data.push(pixel);
1197        }
1198        data
1199    }
1200
1201    #[test]
1202    fn block_cache_does_not_collide_between_chain_index_and_ifd_offset() {
1203        // Ten chained IFDs: chain IFD #8 shares its numeric index with the
1204        // file offset (8) of the first IFD.
1205        let pixel_values: Vec<u8> = (0..10u8).map(|index| 100 + index).collect();
1206        let file = TiffFile::from_bytes(build_multi_ifd_tiff(&pixel_values)).unwrap();
1207        assert_eq!(file.ifd_count(), 10);
1208
1209        // Populate the block cache from chain IFD 8 first.
1210        assert_eq!(file.read_image_bytes(8).unwrap(), vec![pixel_values[8]]);
1211
1212        // Re-parsing the first IFD by offset must decode its own strip, not
1213        // return the cached strip of chain IFD 8.
1214        let first_ifd = file.read_ifd_at_offset(8).unwrap();
1215        assert_eq!(
1216            file.read_image_bytes_from_ifd(&first_ifd).unwrap(),
1217            vec![pixel_values[0]]
1218        );
1219    }
1220
1221    #[allow(clippy::too_many_arguments)]
1222    fn build_lerc2_header_v2(
1223        width: u32,
1224        height: u32,
1225        valid_pixel_count: u32,
1226        image_type: i32,
1227        max_z_error: f64,
1228        z_min: f64,
1229        z_max: f64,
1230        payload_len: usize,
1231    ) -> Vec<u8> {
1232        let blob_size = 58 + 4 + payload_len;
1233        let mut bytes = Vec::with_capacity(blob_size);
1234        bytes.extend_from_slice(b"Lerc2 ");
1235        bytes.extend_from_slice(&2i32.to_le_bytes());
1236        bytes.extend_from_slice(&height.to_le_bytes());
1237        bytes.extend_from_slice(&width.to_le_bytes());
1238        bytes.extend_from_slice(&valid_pixel_count.to_le_bytes());
1239        bytes.extend_from_slice(&8i32.to_le_bytes());
1240        bytes.extend_from_slice(&(blob_size as i32).to_le_bytes());
1241        bytes.extend_from_slice(&image_type.to_le_bytes());
1242        bytes.extend_from_slice(&max_z_error.to_le_bytes());
1243        bytes.extend_from_slice(&z_min.to_le_bytes());
1244        bytes.extend_from_slice(&z_max.to_le_bytes());
1245        bytes
1246    }
1247
1248    #[allow(clippy::too_many_arguments)]
1249    fn build_lerc2_header_v4(
1250        width: u32,
1251        height: u32,
1252        depth: u32,
1253        valid_pixel_count: u32,
1254        image_type: i32,
1255        max_z_error: f64,
1256        z_min: f64,
1257        z_max: f64,
1258        payload_len: usize,
1259    ) -> Vec<u8> {
1260        let blob_size = 66 + 4 + payload_len;
1261        let mut bytes = Vec::with_capacity(blob_size);
1262        bytes.extend_from_slice(b"Lerc2 ");
1263        bytes.extend_from_slice(&4i32.to_le_bytes());
1264        bytes.extend_from_slice(&0u32.to_le_bytes());
1265        bytes.extend_from_slice(&height.to_le_bytes());
1266        bytes.extend_from_slice(&width.to_le_bytes());
1267        bytes.extend_from_slice(&depth.to_le_bytes());
1268        bytes.extend_from_slice(&valid_pixel_count.to_le_bytes());
1269        bytes.extend_from_slice(&8i32.to_le_bytes());
1270        bytes.extend_from_slice(&(blob_size as i32).to_le_bytes());
1271        bytes.extend_from_slice(&image_type.to_le_bytes());
1272        bytes.extend_from_slice(&max_z_error.to_le_bytes());
1273        bytes.extend_from_slice(&z_min.to_le_bytes());
1274        bytes.extend_from_slice(&z_max.to_le_bytes());
1275        bytes
1276    }
1277
1278    fn finalize_lerc2_v4_with_checksum(mut bytes: Vec<u8>) -> Vec<u8> {
1279        let blob_size = bytes.len() as i32;
1280        bytes[34..38].copy_from_slice(&blob_size.to_le_bytes());
1281        let checksum = fletcher32(&bytes[14..blob_size as usize]);
1282        bytes[10..14].copy_from_slice(&checksum.to_le_bytes());
1283        bytes
1284    }
1285
1286    fn fletcher32(bytes: &[u8]) -> u32 {
1287        let mut sum1 = 0xffffu32;
1288        let mut sum2 = 0xffffu32;
1289        let mut words = bytes.len() / 2;
1290        let mut index = 0usize;
1291
1292        while words > 0 {
1293            let chunk = words.min(359);
1294            words -= chunk;
1295            for _ in 0..chunk {
1296                sum1 += (bytes[index] as u32) << 8;
1297                index += 1;
1298                sum2 += sum1 + bytes[index] as u32;
1299                sum1 += bytes[index] as u32;
1300                index += 1;
1301            }
1302            sum1 = (sum1 & 0xffff) + (sum1 >> 16);
1303            sum2 = (sum2 & 0xffff) + (sum2 >> 16);
1304        }
1305
1306        if bytes.len() & 1 != 0 {
1307            sum1 += (bytes[index] as u32) << 8;
1308            sum2 += sum1;
1309        }
1310
1311        sum1 = (sum1 & 0xffff) + (sum1 >> 16);
1312        sum2 = (sum2 & 0xffff) + (sum2 >> 16);
1313        (sum2 << 16) | (sum1 & 0xffff)
1314    }
1315
1316    fn encode_mask_rle(mask: &[u8]) -> Vec<u8> {
1317        let bitset_len = mask.len().div_ceil(8);
1318        let mut bitset = vec![0u8; bitset_len];
1319        for (index, &value) in mask.iter().enumerate() {
1320            if value != 0 {
1321                bitset[index >> 3] |= 1 << (7 - (index & 7));
1322            }
1323        }
1324
1325        let mut encoded = Vec::with_capacity(bitset_len + 4);
1326        encoded.extend_from_slice(&(bitset_len as i16).to_le_bytes());
1327        encoded.extend_from_slice(&bitset);
1328        encoded.extend_from_slice(&i16::MIN.to_le_bytes());
1329        encoded
1330    }
1331
1332    fn build_lerc_tiff(
1333        width: u32,
1334        height: u32,
1335        image_data: &[u8],
1336        bits_per_sample: u16,
1337        sample_format: u16,
1338        samples_per_pixel: u16,
1339        lerc_parameters: Option<[u32; 2]>,
1340    ) -> Vec<u8> {
1341        let mut overrides = vec![
1342            (258u16, 3u16, 1u32, inline_short(bits_per_sample)),
1343            (259u16, 3u16, 1u32, inline_short(34887)),
1344            (277u16, 3u16, 1u32, inline_short(samples_per_pixel)),
1345            (279u16, 4u16, 1u32, le_u32(image_data.len() as u32).to_vec()),
1346        ];
1347        if sample_format != 1 {
1348            overrides.push((339u16, 3u16, 1u32, inline_short(sample_format)));
1349        }
1350        if let Some([version, additional_compression]) = lerc_parameters {
1351            overrides.push((
1352                50674u16,
1353                4u16,
1354                2u32,
1355                [version, additional_compression]
1356                    .into_iter()
1357                    .flat_map(le_u32)
1358                    .collect(),
1359            ));
1360        }
1361        build_stripped_tiff(width, height, image_data, &overrides)
1362    }
1363
1364    fn build_tiled_tiff(
1365        width: u32,
1366        height: u32,
1367        tile_width: u32,
1368        tile_height: u32,
1369        tiles: &[&[u8]],
1370    ) -> Vec<u8> {
1371        build_tiled_tiff_with_overrides(width, height, tile_width, tile_height, tiles, &[])
1372    }
1373
1374    fn build_tiled_tiff_with_overrides(
1375        width: u32,
1376        height: u32,
1377        tile_width: u32,
1378        tile_height: u32,
1379        tiles: &[&[u8]],
1380        overrides: &[(u16, u16, u32, Vec<u8>)],
1381    ) -> Vec<u8> {
1382        let mut entries = BTreeMap::new();
1383        entries.insert(256, (4, 1, le_u32(width).to_vec()));
1384        entries.insert(257, (4, 1, le_u32(height).to_vec()));
1385        entries.insert(258, (3, 1, [8, 0, 0, 0].to_vec()));
1386        entries.insert(259, (3, 1, [1, 0, 0, 0].to_vec()));
1387        entries.insert(277, (3, 1, [1, 0, 0, 0].to_vec()));
1388        entries.insert(322, (4, 1, le_u32(tile_width).to_vec()));
1389        entries.insert(323, (4, 1, le_u32(tile_height).to_vec()));
1390        entries.insert(
1391            325,
1392            (
1393                4,
1394                tiles.len() as u32,
1395                tiles
1396                    .iter()
1397                    .flat_map(|tile| le_u32(tile.len() as u32))
1398                    .collect(),
1399            ),
1400        );
1401        for &(tag, ty, count, ref value) in overrides {
1402            entries.insert(tag, (ty, count, value.clone()));
1403        }
1404
1405        let ifd_offset = 8u32;
1406        let ifd_size = 2 + (entries.len() + 1) * 12 + 4;
1407        let mut tile_data_offset = ifd_offset as usize + ifd_size;
1408        let tile_offsets: Vec<u32> = tiles
1409            .iter()
1410            .map(|tile| {
1411                let offset = tile_data_offset as u32;
1412                tile_data_offset += tile.len();
1413                offset
1414            })
1415            .collect();
1416        entries.insert(
1417            324,
1418            (
1419                4,
1420                tile_offsets.len() as u32,
1421                tile_offsets
1422                    .iter()
1423                    .flat_map(|offset| le_u32(*offset))
1424                    .collect(),
1425            ),
1426        );
1427
1428        let mut next_data_offset = tile_data_offset;
1429        let mut data = Vec::with_capacity(next_data_offset);
1430        data.extend_from_slice(b"II");
1431        data.extend_from_slice(&le_u16(42));
1432        data.extend_from_slice(&le_u32(ifd_offset));
1433        data.extend_from_slice(&le_u16(entries.len() as u16));
1434
1435        let mut deferred = Vec::new();
1436        for (tag, (ty, count, value)) in entries {
1437            data.extend_from_slice(&le_u16(tag));
1438            data.extend_from_slice(&le_u16(ty));
1439            data.extend_from_slice(&le_u32(count));
1440            if value.len() <= 4 {
1441                let mut inline = [0u8; 4];
1442                inline[..value.len()].copy_from_slice(&value);
1443                data.extend_from_slice(&inline);
1444            } else {
1445                let offset = next_data_offset as u32;
1446                data.extend_from_slice(&le_u32(offset));
1447                next_data_offset += value.len();
1448                deferred.push(value);
1449            }
1450        }
1451        data.extend_from_slice(&le_u32(0));
1452        for tile in tiles {
1453            data.extend_from_slice(tile);
1454        }
1455        for value in deferred {
1456            data.extend_from_slice(&value);
1457        }
1458        data
1459    }
1460
1461    fn build_multi_strip_tiff(width: u32, rows: &[&[u8]]) -> Vec<u8> {
1462        let mut entries = BTreeMap::new();
1463        entries.insert(256, (4, 1, le_u32(width).to_vec()));
1464        entries.insert(257, (4, 1, le_u32(rows.len() as u32).to_vec()));
1465        entries.insert(258, (3, 1, [8, 0, 0, 0].to_vec()));
1466        entries.insert(259, (3, 1, [1, 0, 0, 0].to_vec()));
1467        entries.insert(277, (3, 1, [1, 0, 0, 0].to_vec()));
1468        entries.insert(278, (4, 1, le_u32(1).to_vec()));
1469        entries.insert(
1470            279,
1471            (
1472                4,
1473                rows.len() as u32,
1474                rows.iter()
1475                    .flat_map(|row| le_u32(row.len() as u32))
1476                    .collect(),
1477            ),
1478        );
1479
1480        let ifd_offset = 8u32;
1481        let ifd_size = 2 + (entries.len() + 1) * 12 + 4;
1482        let mut strip_data_offset = ifd_offset as usize + ifd_size;
1483        let strip_offsets: Vec<u32> = rows
1484            .iter()
1485            .map(|row| {
1486                let offset = strip_data_offset as u32;
1487                strip_data_offset += row.len();
1488                offset
1489            })
1490            .collect();
1491        entries.insert(
1492            273,
1493            (
1494                4,
1495                strip_offsets.len() as u32,
1496                strip_offsets
1497                    .iter()
1498                    .flat_map(|offset| le_u32(*offset))
1499                    .collect(),
1500            ),
1501        );
1502
1503        let mut next_data_offset = strip_data_offset;
1504        let mut data = Vec::with_capacity(next_data_offset);
1505        data.extend_from_slice(b"II");
1506        data.extend_from_slice(&le_u16(42));
1507        data.extend_from_slice(&le_u32(ifd_offset));
1508        data.extend_from_slice(&le_u16(entries.len() as u16));
1509
1510        let mut deferred = Vec::new();
1511        for (tag, (ty, count, value)) in entries {
1512            data.extend_from_slice(&le_u16(tag));
1513            data.extend_from_slice(&le_u16(ty));
1514            data.extend_from_slice(&le_u32(count));
1515            if value.len() <= 4 {
1516                let mut inline = [0u8; 4];
1517                inline[..value.len()].copy_from_slice(&value);
1518                data.extend_from_slice(&inline);
1519            } else {
1520                let offset = next_data_offset as u32;
1521                data.extend_from_slice(&le_u32(offset));
1522                next_data_offset += value.len();
1523                deferred.push(value);
1524            }
1525        }
1526        data.extend_from_slice(&le_u32(0));
1527        for row in rows {
1528            data.extend_from_slice(row);
1529        }
1530        for value in deferred {
1531            data.extend_from_slice(&value);
1532        }
1533        data
1534    }
1535
1536    fn build_planar_stripped_tiff(width: u32, height: u32, planes: &[&[u8]]) -> Vec<u8> {
1537        let mut entries = BTreeMap::new();
1538        entries.insert(256, (4, 1, le_u32(width).to_vec()));
1539        entries.insert(257, (4, 1, le_u32(height).to_vec()));
1540        entries.insert(258, (3, 1, [8, 0, 0, 0].to_vec()));
1541        entries.insert(259, (3, 1, [1, 0, 0, 0].to_vec()));
1542        entries.insert(262, (3, 1, [2, 0, 0, 0].to_vec()));
1543        entries.insert(277, (3, 1, inline_short(planes.len() as u16)));
1544        entries.insert(278, (4, 1, le_u32(height).to_vec()));
1545        entries.insert(284, (3, 1, [2, 0, 0, 0].to_vec()));
1546        entries.insert(
1547            279,
1548            (
1549                4,
1550                planes.len() as u32,
1551                planes
1552                    .iter()
1553                    .flat_map(|plane| le_u32(plane.len() as u32))
1554                    .collect(),
1555            ),
1556        );
1557
1558        let ifd_offset = 8u32;
1559        let ifd_size = 2 + (entries.len() + 1) * 12 + 4;
1560        let mut strip_data_offset = ifd_offset as usize + ifd_size;
1561        let strip_offsets: Vec<u32> = planes
1562            .iter()
1563            .map(|plane| {
1564                let offset = strip_data_offset as u32;
1565                strip_data_offset += plane.len();
1566                offset
1567            })
1568            .collect();
1569        entries.insert(
1570            273,
1571            (
1572                4,
1573                strip_offsets.len() as u32,
1574                strip_offsets
1575                    .iter()
1576                    .flat_map(|offset| le_u32(*offset))
1577                    .collect(),
1578            ),
1579        );
1580
1581        let mut next_data_offset = strip_data_offset;
1582        let mut data = Vec::with_capacity(next_data_offset);
1583        data.extend_from_slice(b"II");
1584        data.extend_from_slice(&le_u16(42));
1585        data.extend_from_slice(&le_u32(ifd_offset));
1586        data.extend_from_slice(&le_u16(entries.len() as u16));
1587
1588        let mut deferred = Vec::new();
1589        for (tag, (ty, count, value)) in entries {
1590            data.extend_from_slice(&le_u16(tag));
1591            data.extend_from_slice(&le_u16(ty));
1592            data.extend_from_slice(&le_u32(count));
1593            if value.len() <= 4 {
1594                let mut inline = [0u8; 4];
1595                inline[..value.len()].copy_from_slice(&value);
1596                data.extend_from_slice(&inline);
1597            } else {
1598                let offset = next_data_offset as u32;
1599                data.extend_from_slice(&le_u32(offset));
1600                next_data_offset += value.len();
1601                deferred.push(value);
1602            }
1603        }
1604        data.extend_from_slice(&le_u32(0));
1605        for plane in planes {
1606            data.extend_from_slice(plane);
1607        }
1608        for value in deferred {
1609            data.extend_from_slice(&value);
1610        }
1611        data
1612    }
1613
1614    struct CountingSource {
1615        bytes: Vec<u8>,
1616        reads: AtomicUsize,
1617    }
1618
1619    impl CountingSource {
1620        fn new(bytes: Vec<u8>) -> Self {
1621            Self {
1622                bytes,
1623                reads: AtomicUsize::new(0),
1624            }
1625        }
1626
1627        fn reset_reads(&self) {
1628            self.reads.store(0, Ordering::SeqCst);
1629        }
1630
1631        fn reads(&self) -> usize {
1632            self.reads.load(Ordering::SeqCst)
1633        }
1634    }
1635
1636    impl TiffSource for CountingSource {
1637        fn len(&self) -> u64 {
1638            self.bytes.len() as u64
1639        }
1640
1641        fn read_exact_at(&self, offset: u64, len: usize) -> crate::error::Result<Vec<u8>> {
1642            self.reads.fetch_add(1, Ordering::SeqCst);
1643            let start =
1644                usize::try_from(offset).map_err(|_| crate::error::Error::OffsetOutOfBounds {
1645                    offset,
1646                    length: len as u64,
1647                    data_len: self.len(),
1648                })?;
1649            let end = start
1650                .checked_add(len)
1651                .ok_or(crate::error::Error::OffsetOutOfBounds {
1652                    offset,
1653                    length: len as u64,
1654                    data_len: self.len(),
1655                })?;
1656            if end > self.bytes.len() {
1657                return Err(crate::error::Error::OffsetOutOfBounds {
1658                    offset,
1659                    length: len as u64,
1660                    data_len: self.len(),
1661                });
1662            }
1663            Ok(self.bytes[start..end].to_vec())
1664        }
1665    }
1666
1667    fn overwrite_classic_inline_long_tag(data: &mut [u8], tag: u16, value: u32) {
1668        let ifd_offset = u32::from_le_bytes(data[4..8].try_into().unwrap()) as usize;
1669        let entry_count = u16::from_le_bytes(data[ifd_offset..ifd_offset + 2].try_into().unwrap());
1670        for entry_index in 0..usize::from(entry_count) {
1671            let entry = ifd_offset + 2 + entry_index * 12;
1672            let entry_tag = u16::from_le_bytes(data[entry..entry + 2].try_into().unwrap());
1673            if entry_tag == tag {
1674                data[entry + 8..entry + 12].copy_from_slice(&le_u32(value));
1675                return;
1676            }
1677        }
1678        panic!("tag {tag} not found");
1679    }
1680
1681    /// Zero one element of a classic LONG-array tag (inline or deferred).
1682    fn zero_classic_long_array_element(data: &mut [u8], tag: u16, element_index: usize) {
1683        let ifd_offset = u32::from_le_bytes(data[4..8].try_into().unwrap()) as usize;
1684        let entry_count = u16::from_le_bytes(data[ifd_offset..ifd_offset + 2].try_into().unwrap());
1685        for entry_index in 0..usize::from(entry_count) {
1686            let entry = ifd_offset + 2 + entry_index * 12;
1687            let entry_tag = u16::from_le_bytes(data[entry..entry + 2].try_into().unwrap());
1688            if entry_tag != tag {
1689                continue;
1690            }
1691            let count = u32::from_le_bytes(data[entry + 4..entry + 8].try_into().unwrap()) as usize;
1692            assert!(element_index < count, "element index out of range");
1693            if count == 1 {
1694                data[entry + 8..entry + 12].copy_from_slice(&[0; 4]);
1695            } else {
1696                let value_offset =
1697                    u32::from_le_bytes(data[entry + 8..entry + 12].try_into().unwrap()) as usize;
1698                let element = value_offset + element_index * 4;
1699                data[element..element + 4].copy_from_slice(&[0; 4]);
1700            }
1701            return;
1702        }
1703        panic!("tag {tag} not found");
1704    }
1705
1706    fn ghost_metadata(trailer: bool) -> GdalStructuralMetadata {
1707        GdalStructuralMetadata {
1708            block_leader_size_as_u32: true,
1709            block_trailer_repeats_last_4_bytes: trailer,
1710        }
1711    }
1712
1713    fn read_ghost_payload(
1714        bytes: Vec<u8>,
1715        metadata: &GdalStructuralMetadata,
1716        offset: u64,
1717        byte_count: u64,
1718    ) -> crate::error::Result<Vec<u8>> {
1719        let source = BytesSource::new(bytes);
1720        super::read_gdal_block_payload(
1721            &source,
1722            metadata,
1723            crate::ByteOrder::LittleEndian,
1724            offset,
1725            byte_count,
1726            1024,
1727            0,
1728        )
1729    }
1730
1731    #[test]
1732    fn ghost_block_prefers_wrapped_copy_with_matching_length() {
1733        // leader(4) | payload(4) | trailer(4, repeats last 4 payload bytes)
1734        let payload = [10u8, 20, 30, 40];
1735        let mut bytes = 4u32.to_le_bytes().to_vec();
1736        bytes.extend_from_slice(&payload);
1737        bytes.extend_from_slice(&payload);
1738
1739        let result = read_ghost_payload(bytes, &ghost_metadata(true), 4, 4).unwrap();
1740        assert_eq!(result, payload);
1741    }
1742
1743    #[test]
1744    fn ghost_block_falls_back_to_direct_read_without_leader() {
1745        // No leader bytes in the file even though the metadata declares one:
1746        // the direct payload-range read must win.
1747        let bytes = vec![9u8, 9, 9, 9, 1, 2, 3, 4];
1748        let result = read_ghost_payload(bytes, &ghost_metadata(false), 4, 4).unwrap();
1749        assert_eq!(result, vec![1, 2, 3, 4]);
1750    }
1751
1752    #[test]
1753    fn ghost_block_falls_back_when_wrapped_read_is_out_of_bounds() {
1754        // Wrapped read (offset-4 .. +leader+trailer) would run past EOF; the
1755        // direct read still succeeds.
1756        let mut bytes = vec![0u8; 4];
1757        bytes.extend_from_slice(&[5, 6, 7, 8]);
1758        let result = read_ghost_payload(bytes, &ghost_metadata(true), 4, 4).unwrap();
1759        assert_eq!(result, vec![5, 6, 7, 8]);
1760    }
1761
1762    #[test]
1763    fn ghost_block_skips_wrapped_read_for_small_offsets() {
1764        let bytes = vec![11u8, 12, 13, 14];
1765        let result = read_ghost_payload(bytes, &ghost_metadata(true), 0, 4).unwrap();
1766        assert_eq!(result, vec![11, 12, 13, 14]);
1767    }
1768
1769    #[test]
1770    fn ghost_block_errors_when_both_reads_fail() {
1771        let bytes = vec![0u8; 4];
1772        let error = read_ghost_payload(bytes, &ghost_metadata(false), 8, 4).unwrap_err();
1773        assert!(matches!(error, Error::OffsetOutOfBounds { .. }), "{error}");
1774    }
1775
1776    #[test]
1777    fn sparse_strip_reads_as_zero_fill() {
1778        let rows: [&[u8]; 2] = [&[1, 2, 3, 4], &[5, 6, 7, 8]];
1779        let mut data = build_multi_strip_tiff(4, &rows);
1780        zero_classic_long_array_element(&mut data, 273, 1); // StripOffsets[1]
1781        zero_classic_long_array_element(&mut data, 279, 1); // StripByteCounts[1]
1782
1783        let file = TiffFile::from_bytes(data).unwrap();
1784        assert_eq!(
1785            file.read_image_bytes(0).unwrap(),
1786            vec![1, 2, 3, 4, 0, 0, 0, 0]
1787        );
1788    }
1789
1790    #[test]
1791    fn zero_byte_count_block_reads_as_zero_fill() {
1792        let rows: [&[u8]; 2] = [&[1, 2], &[3, 4]];
1793        let mut data = build_multi_strip_tiff(2, &rows);
1794        zero_classic_long_array_element(&mut data, 279, 0); // StripByteCounts[0]
1795
1796        let file = TiffFile::from_bytes(data).unwrap();
1797        assert_eq!(file.read_image_bytes(0).unwrap(), vec![0, 0, 3, 4]);
1798    }
1799
1800    #[test]
1801    fn sparse_tile_reads_as_zero_fill() {
1802        let tile0 = vec![9u8; 256];
1803        let tile1 = vec![7u8; 256];
1804        let mut data = build_tiled_tiff(32, 16, 16, 16, &[&tile0, &tile1]);
1805        zero_classic_long_array_element(&mut data, 324, 1); // TileOffsets[1]
1806        zero_classic_long_array_element(&mut data, 325, 1); // TileByteCounts[1]
1807
1808        let file = TiffFile::from_bytes(data).unwrap();
1809        let image = file.read_image_bytes(0).unwrap();
1810        assert_eq!(image.len(), 32 * 16);
1811        assert!(image.chunks(32).all(|row| {
1812            row[..16].iter().all(|&value| value == 9) && row[16..].iter().all(|&value| value == 0)
1813        }));
1814    }
1815
1816    #[test]
1817    fn open_uses_safe_file_source_without_raw_slice() {
1818        let path = temp_tiff_path("open_uses_safe_file_source_without_raw_slice");
1819        fs::write(&path, build_stripped_tiff(1, 1, &[7], &[])).unwrap();
1820
1821        let file = TiffFile::open(&path).unwrap();
1822        assert!(file.raw_bytes().is_none());
1823        assert_eq!(file.read_image_bytes(0).unwrap(), vec![7]);
1824
1825        drop(file);
1826        let _ = fs::remove_file(path);
1827    }
1828
1829    #[test]
1830    fn open_mmap_exposes_raw_slice() {
1831        let bytes = build_stripped_tiff(1, 1, &[7], &[]);
1832        let path = temp_tiff_path("open_mmap_exposes_raw_slice");
1833        fs::write(&path, &bytes).unwrap();
1834
1835        let file = unsafe { TiffFile::open_mmap(&path).unwrap() };
1836        assert_eq!(file.raw_bytes(), Some(bytes.as_slice()));
1837        assert_eq!(file.read_image_bytes(0).unwrap(), vec![7]);
1838
1839        drop(file);
1840        let _ = fs::remove_file(path);
1841    }
1842
1843    #[test]
1844    fn decode_output_budget_rejects_large_storage_window_before_allocation() {
1845        let file = TiffFile::from_bytes_with_options(
1846            build_stripped_tiff(4, 4, &[0], &[]),
1847            OpenOptions {
1848                decode_output_bytes: 8,
1849                ..OpenOptions::default()
1850            },
1851        )
1852        .unwrap();
1853
1854        let err = file.read_image_bytes(0).unwrap_err();
1855        assert!(matches!(
1856            err,
1857            Error::DecodeOutputTooLarge {
1858                requested: 16,
1859                limit: 8
1860            }
1861        ));
1862    }
1863
1864    #[test]
1865    fn decode_output_budget_also_bounds_intersecting_storage_blocks() {
1866        let tile = vec![7u8; 16 * 16];
1867        let file = TiffFile::from_bytes_with_options(
1868            build_tiled_tiff(16, 16, 16, 16, &[&tile]),
1869            OpenOptions {
1870                decode_output_bytes: 16,
1871                ..OpenOptions::default()
1872            },
1873        )
1874        .unwrap();
1875
1876        // The requested window is one byte, but servicing it requires
1877        // decoding the intersecting 256-byte tile.
1878        let err = file.read_window_bytes(0, 0, 0, 1, 1).unwrap_err();
1879        assert!(matches!(
1880            err,
1881            Error::DecodeOutputTooLarge {
1882                requested: 256,
1883                limit: 16
1884            }
1885        ));
1886    }
1887
1888    #[test]
1889    fn decode_output_budget_rejects_large_color_decoded_output() {
1890        let mut color_map = Vec::new();
1891        color_map.extend((0u16..16).map(|value| value * 17 * 257));
1892        color_map.extend((0u16..16).map(|value| (15 - value) * 17 * 257));
1893        color_map.extend((0u16..16).map(|value| value * 8 * 257));
1894        let file = TiffFile::from_bytes_with_options(
1895            build_stripped_tiff(
1896                1,
1897                1,
1898                &[0x00],
1899                &[
1900                    (258, 3, 1, inline_short(4)),
1901                    (262, 3, 1, inline_short(3)),
1902                    (
1903                        320,
1904                        3,
1905                        color_map.len() as u32,
1906                        color_map.iter().flat_map(|value| le_u16(*value)).collect(),
1907                    ),
1908                ],
1909            ),
1910            OpenOptions {
1911                decode_output_bytes: 2,
1912                ..OpenOptions::default()
1913            },
1914        )
1915        .unwrap();
1916
1917        let err = file.read_decoded_image_bytes(0).unwrap_err();
1918        assert!(matches!(
1919            err,
1920            Error::DecodeOutputTooLarge {
1921                requested: 3,
1922                limit: 2
1923            }
1924        ));
1925    }
1926
1927    #[test]
1928    fn bigtiff_ifd_entry_count_respects_parse_budget_before_body_read() {
1929        let mut data = bigtiff_header(16);
1930        data.extend_from_slice(&le_u64(2));
1931
1932        let err = match TiffFile::from_bytes_with_options(
1933            data,
1934            OpenOptions {
1935                parse_budgets: ParseBudgets {
1936                    max_ifd_entries: 1,
1937                    ..ParseBudgets::default()
1938                },
1939                ..OpenOptions::default()
1940            },
1941        ) {
1942            Ok(_) => panic!("expected parse budget error"),
1943            Err(err) => err,
1944        };
1945        assert!(
1946            matches!(err, Error::InvalidImageLayout(message) if message.contains("entry count"))
1947        );
1948    }
1949
1950    #[test]
1951    fn bigtiff_tag_value_bytes_respect_parse_budget_before_value_read() {
1952        let mut data = bigtiff_header(16);
1953        data.extend_from_slice(&le_u64(1));
1954        data.extend_from_slice(&le_u16(256));
1955        data.extend_from_slice(&le_u16(1));
1956        data.extend_from_slice(&le_u64(9));
1957        data.extend_from_slice(&le_u64(1024));
1958        data.extend_from_slice(&le_u64(0));
1959
1960        let err = match TiffFile::from_bytes_with_options(
1961            data,
1962            OpenOptions {
1963                parse_budgets: ParseBudgets {
1964                    max_tag_value_bytes: 8,
1965                    ..ParseBudgets::default()
1966                },
1967                ..OpenOptions::default()
1968            },
1969        ) {
1970            Ok(_) => panic!("expected parse budget error"),
1971            Err(err) => err,
1972        };
1973        assert!(
1974            matches!(err, Error::InvalidTagValue { tag: 256, reason } if reason.contains("parse budget"))
1975        );
1976    }
1977
1978    #[test]
1979    fn bigtiff_tag_value_bytes_respect_aggregate_parse_budget() {
1980        let mut data = bigtiff_header(16);
1981        data.extend_from_slice(&le_u64(2));
1982        data.extend_from_slice(&le_u16(65000));
1983        data.extend_from_slice(&le_u16(1));
1984        data.extend_from_slice(&le_u64(8));
1985        data.extend_from_slice(&[0x11; 8]);
1986        data.extend_from_slice(&le_u16(65001));
1987        data.extend_from_slice(&le_u16(1));
1988        data.extend_from_slice(&le_u64(8));
1989        data.extend_from_slice(&[0x22; 8]);
1990        data.extend_from_slice(&le_u64(0));
1991
1992        let err = match TiffFile::from_bytes_with_options(
1993            data,
1994            OpenOptions {
1995                parse_budgets: ParseBudgets {
1996                    max_tag_value_bytes: 8,
1997                    max_metadata_value_bytes: 8,
1998                    ..ParseBudgets::default()
1999                },
2000                ..OpenOptions::default()
2001            },
2002        ) {
2003            Ok(_) => panic!("expected aggregate parse budget error"),
2004            Err(err) => err,
2005        };
2006        assert!(
2007            matches!(err, Error::InvalidTagValue { tag: 65001, reason } if reason.contains("aggregate metadata"))
2008        );
2009    }
2010
2011    #[test]
2012    fn bigtiff_ifd_chain_respects_parse_budget() {
2013        let mut data = bigtiff_header(16);
2014        data.extend_from_slice(&le_u64(0));
2015        data.extend_from_slice(&le_u64(32));
2016        data.extend_from_slice(&le_u64(0));
2017        data.extend_from_slice(&le_u64(0));
2018
2019        let err = match TiffFile::from_bytes_with_options(
2020            data,
2021            OpenOptions {
2022                parse_budgets: ParseBudgets {
2023                    max_ifds: 1,
2024                    ..ParseBudgets::default()
2025                },
2026                ..OpenOptions::default()
2027            },
2028        ) {
2029            Ok(_) => panic!("expected parse budget error"),
2030            Err(err) => err,
2031        };
2032        assert!(matches!(err, Error::Other(message) if message.contains("parse budget")));
2033    }
2034
2035    #[test]
2036    fn rejects_bigtiff_long8_dimension_that_exceeds_u32() {
2037        let mut data = bigtiff_header(16);
2038        data.extend_from_slice(&le_u64(2));
2039        data.extend_from_slice(&le_u16(256));
2040        data.extend_from_slice(&le_u16(16));
2041        data.extend_from_slice(&le_u64(1));
2042        data.extend_from_slice(&le_u64(u64::from(u32::MAX) + 2));
2043        data.extend_from_slice(&le_u16(257));
2044        data.extend_from_slice(&le_u16(16));
2045        data.extend_from_slice(&le_u64(1));
2046        data.extend_from_slice(&le_u64(1));
2047        data.extend_from_slice(&le_u64(0));
2048
2049        let file = TiffFile::from_bytes(data).unwrap();
2050        let err = file.ifd(0).unwrap().raster_layout().unwrap_err();
2051        assert!(
2052            matches!(err, Error::InvalidImageLayout(message) if message.contains("dimensions"))
2053        );
2054    }
2055
2056    #[test]
2057    fn oversized_strip_byte_count_is_rejected_before_payload_read() {
2058        let data = build_stripped_tiff(
2059            2,
2060            2,
2061            &[1, 2, 3, 4],
2062            &[(279, 4, 1, le_u32(u32::MAX).to_vec())],
2063        );
2064        let source = Arc::new(CountingSource::new(data));
2065        let file = TiffFile::from_source(source.clone()).unwrap();
2066        source.reset_reads();
2067
2068        let err = file.read_image_bytes(0).unwrap_err();
2069        assert!(err.to_string().contains("block read budget"));
2070        assert_eq!(source.reads(), 0);
2071    }
2072
2073    #[test]
2074    fn oversized_tile_byte_count_is_rejected_before_payload_read() {
2075        let mut data = build_tiled_tiff(2, 2, 2, 2, &[&[1, 2, 3, 4]]);
2076        overwrite_classic_inline_long_tag(&mut data, 325, u32::MAX);
2077        let source = Arc::new(CountingSource::new(data));
2078        let file = TiffFile::from_source(source.clone()).unwrap();
2079        source.reset_reads();
2080
2081        let err = file.read_image_bytes(0).unwrap_err();
2082        assert!(err.to_string().contains("block read budget"));
2083        assert_eq!(source.reads(), 0);
2084    }
2085
2086    #[test]
2087    fn huge_planar_tile_count_overflow_is_rejected_without_panicking() {
2088        let data = build_tiled_tiff_with_overrides(
2089            u32::MAX,
2090            u32::MAX,
2091            1,
2092            1,
2093            &[&[0]],
2094            &[(277, 3, 1, inline_short(2)), (284, 3, 1, inline_short(2))],
2095        );
2096        let file = TiffFile::from_bytes(data).unwrap();
2097
2098        let err = file.read_window_bytes(0, 0, 0, 1, 1).unwrap_err();
2099        assert!(
2100            matches!(err, Error::InvalidImageLayout(message) if message.contains("tile count"))
2101        );
2102    }
2103
2104    #[test]
2105    fn reads_stripped_u8_image() {
2106        let data = build_stripped_tiff(2, 2, &[1, 2, 3, 4], &[]);
2107        let file = TiffFile::from_bytes(data).unwrap();
2108        let image = file.read_image::<u8>(0).unwrap();
2109        assert_eq!(image.shape(), &[2, 2]);
2110        let (values, offset) = image.into_raw_vec_and_offset();
2111        assert_eq!(offset, Some(0));
2112        assert_eq!(values, vec![1, 2, 3, 4]);
2113    }
2114
2115    #[test]
2116    fn reads_single_chunky_band_and_window() {
2117        let data = build_stripped_tiff(
2118            2,
2119            2,
2120            &[
2121                1, 10, 100, //
2122                2, 20, 110, //
2123                3, 30, 120, //
2124                4, 40, 130,
2125            ],
2126            &[
2127                (262, 3, 1, inline_short(2)),
2128                (277, 3, 1, inline_short(3)),
2129                (279, 4, 1, le_u32(12).to_vec()),
2130            ],
2131        );
2132        let file = TiffFile::from_bytes(data).unwrap();
2133
2134        let green = file.read_band::<u8>(0, 1).unwrap();
2135        assert_eq!(green.shape(), &[2, 2]);
2136        let (green_values, offset) = green.into_raw_vec_and_offset();
2137        assert_eq!(offset, Some(0));
2138        assert_eq!(green_values, vec![10, 20, 30, 40]);
2139
2140        let blue_window = file.read_band_window::<u8>(0, 2, 0, 1, 2, 1).unwrap();
2141        assert_eq!(blue_window.shape(), &[2, 1]);
2142        let (blue_values, offset) = blue_window.into_raw_vec_and_offset();
2143        assert_eq!(offset, Some(0));
2144        assert_eq!(blue_values, vec![110, 130]);
2145
2146        let err = file.read_band::<u8>(0, 3).unwrap_err();
2147        assert!(matches!(
2148            err,
2149            Error::BandIndexOutOfBounds {
2150                index: 3,
2151                band_count: 3
2152            }
2153        ));
2154    }
2155
2156    #[test]
2157    fn planar_band_reads_only_requested_plane() {
2158        let data = build_planar_stripped_tiff(
2159            2,
2160            2,
2161            &[&[1, 2, 3, 4], &[10, 20, 30, 40], &[100, 110, 120, 130]],
2162        );
2163        let source = Arc::new(CountingSource::new(data));
2164        let file = TiffFile::from_source(source.clone()).unwrap();
2165        source.reset_reads();
2166
2167        let blue = file.read_band::<u8>(0, 2).unwrap();
2168        assert_eq!(blue.shape(), &[2, 2]);
2169        let (values, offset) = blue.into_raw_vec_and_offset();
2170        assert_eq!(offset, Some(0));
2171        assert_eq!(values, vec![100, 110, 120, 130]);
2172        assert_eq!(source.reads(), 1);
2173    }
2174
2175    #[test]
2176    fn keeps_subbyte_palette_reads_raw_and_offers_explicit_decoded_pixels() {
2177        let mut color_map = Vec::new();
2178        color_map.extend((0u16..16).map(|value| value * 17 * 257));
2179        color_map.extend((0u16..16).map(|value| (15 - value) * 17 * 257));
2180        color_map.extend((0u16..16).map(|value| value * 8 * 257));
2181        let data = build_stripped_tiff(
2182            4,
2183            1,
2184            &[0x01, 0x23],
2185            &[
2186                (258, 3, 1, inline_short(4)),
2187                (262, 3, 1, inline_short(3)),
2188                (
2189                    320,
2190                    3,
2191                    color_map.len() as u32,
2192                    color_map.iter().flat_map(|value| le_u16(*value)).collect(),
2193                ),
2194            ],
2195        );
2196        let file = TiffFile::from_bytes(data).unwrap();
2197
2198        let image = file.read_image::<u8>(0).unwrap();
2199        assert_eq!(image.shape(), &[1, 4]);
2200        let (values, offset) = image.into_raw_vec_and_offset();
2201        assert_eq!(offset, Some(0));
2202        assert_eq!(values, vec![0, 1, 2, 3]);
2203
2204        let image = file.read_decoded_image::<u8>(0).unwrap();
2205        assert_eq!(image.shape(), &[1, 4, 3]);
2206        let (values, offset) = image.into_raw_vec_and_offset();
2207        assert_eq!(offset, Some(0));
2208        assert_eq!(
2209            values,
2210            vec![
2211                0, 255, 0, //
2212                17, 238, 8, //
2213                34, 221, 16, //
2214                51, 204, 24
2215            ]
2216        );
2217
2218        let sample_bytes = file.read_image_bytes(0).unwrap();
2219        assert_eq!(sample_bytes, vec![0, 1, 2, 3]);
2220    }
2221
2222    #[test]
2223    fn keeps_subsampled_ycbcr_reads_raw_and_offers_explicit_decoded_pixels() {
2224        let data = build_stripped_tiff(
2225            2,
2226            2,
2227            &[10u8, 20, 30, 40, 128, 128],
2228            &[
2229                (
2230                    258,
2231                    3,
2232                    3,
2233                    [8u16, 8, 8].into_iter().flat_map(le_u16).collect(),
2234                ),
2235                (262, 3, 1, inline_short(6)),
2236                (277, 3, 1, inline_short(3)),
2237                (530, 3, 2, [2u16, 2].into_iter().flat_map(le_u16).collect()),
2238            ],
2239        );
2240        let file = TiffFile::from_bytes(data).unwrap();
2241
2242        let image = file.read_image::<u8>(0).unwrap();
2243        assert_eq!(image.shape(), &[2, 2, 3]);
2244        let (values, offset) = image.into_raw_vec_and_offset();
2245        assert_eq!(offset, Some(0));
2246        assert_eq!(
2247            values,
2248            vec![
2249                10, 128, 128, //
2250                20, 128, 128, //
2251                30, 128, 128, //
2252                40, 128, 128
2253            ]
2254        );
2255
2256        let image = file.read_decoded_image::<u8>(0).unwrap();
2257        assert_eq!(image.shape(), &[2, 2, 3]);
2258        let (rgb, offset) = image.into_raw_vec_and_offset();
2259        assert_eq!(offset, Some(0));
2260        assert_eq!(
2261            rgb,
2262            vec![
2263                10, 10, 10, //
2264                20, 20, 20, //
2265                30, 30, 30, //
2266                40, 40, 40
2267            ]
2268        );
2269
2270        let samples = file.read_image::<u8>(0).unwrap();
2271        let (values, offset) = samples.into_raw_vec_and_offset();
2272        assert_eq!(offset, Some(0));
2273        assert_eq!(
2274            values,
2275            vec![
2276                10, 128, 128, //
2277                20, 128, 128, //
2278                30, 128, 128, //
2279                40, 128, 128
2280            ]
2281        );
2282    }
2283
2284    #[test]
2285    fn accepts_long_typed_bits_per_sample_and_sample_format() {
2286        // Nonconforming writers store BitsPerSample/SampleFormat as LONG.
2287        let data = build_stripped_tiff(
2288            1,
2289            1,
2290            &[0x34, 0x12],
2291            &[
2292                (258, 4, 1, le_u32(16).to_vec()),
2293                (339, 4, 1, le_u32(1).to_vec()),
2294            ],
2295        );
2296        let file = TiffFile::from_bytes(data).unwrap();
2297        let image = file.read_image::<u16>(0).unwrap();
2298        assert_eq!(image[[0, 0]], 0x1234);
2299    }
2300
2301    #[test]
2302    fn rejects_unexpected_bits_per_sample_tag_type() {
2303        let data = build_stripped_tiff(1, 1, &[7], &[(258, 11, 1, 16f32.to_le_bytes().to_vec())]);
2304        let file = TiffFile::from_bytes(data).unwrap();
2305        let error = file.read_image_bytes(0).unwrap_err();
2306        assert!(
2307            matches!(error, Error::UnexpectedTagType { tag: 258, .. }),
2308            "{error}"
2309        );
2310    }
2311
2312    #[cfg(feature = "webp")]
2313    #[test]
2314    fn decodes_webp_compressed_rgb_tiles() {
2315        let (tile_w, tile_h) = (16usize, 16usize);
2316        let mut rgb = vec![0u8; tile_w * tile_h * 3];
2317        for row in 0..tile_h {
2318            for col in 0..tile_w {
2319                let base = (row * tile_w + col) * 3;
2320                rgb[base] = (row * 16) as u8;
2321                rgb[base + 1] = (col * 16) as u8;
2322                rgb[base + 2] = ((row + col) * 8) as u8;
2323            }
2324        }
2325
2326        // Lossless WebP payload for the tile.
2327        let mut webp = Vec::new();
2328        image_webp::WebPEncoder::new(&mut webp)
2329            .encode(
2330                &rgb,
2331                tile_w as u32,
2332                tile_h as u32,
2333                image_webp::ColorType::Rgb8,
2334            )
2335            .unwrap();
2336
2337        let data = build_tiled_tiff_with_overrides(
2338            16,
2339            16,
2340            16,
2341            16,
2342            &[&webp],
2343            &[
2344                (
2345                    258,
2346                    3,
2347                    3,
2348                    [8u16, 8, 8].into_iter().flat_map(le_u16).collect(),
2349                ),
2350                (259, 3, 1, inline_short(50001)),
2351                (262, 3, 1, inline_short(2)),
2352                (277, 3, 1, inline_short(3)),
2353            ],
2354        );
2355        let file = TiffFile::from_bytes(data).unwrap();
2356        let image = file.read_image::<u8>(0).unwrap();
2357        assert_eq!(image.shape(), &[16, 16, 3]);
2358        let (values, offset) = image.into_raw_vec_and_offset();
2359        assert_eq!(offset, Some(0));
2360        assert_eq!(values, rgb);
2361    }
2362
2363    #[cfg(feature = "webp")]
2364    #[test]
2365    fn rejects_webp_payload_dimensions_that_do_not_match_tile() {
2366        let rgb = vec![17u8; 8 * 32 * 3];
2367        let mut webp = Vec::new();
2368        image_webp::WebPEncoder::new(&mut webp)
2369            .encode(&rgb, 8, 32, image_webp::ColorType::Rgb8)
2370            .unwrap();
2371
2372        let data = build_tiled_tiff_with_overrides(
2373            16,
2374            16,
2375            16,
2376            16,
2377            &[&webp],
2378            &[
2379                (
2380                    258,
2381                    3,
2382                    3,
2383                    [8u16, 8, 8].into_iter().flat_map(le_u16).collect(),
2384                ),
2385                (259, 3, 1, inline_short(50001)),
2386                (262, 3, 1, inline_short(2)),
2387                (277, 3, 1, inline_short(3)),
2388            ],
2389        );
2390        let file = TiffFile::from_bytes(data).unwrap();
2391        let error = file.read_image::<u8>(0).unwrap_err();
2392        assert!(error.to_string().contains("dimensions 8x32"), "{error}");
2393    }
2394
2395    #[cfg(feature = "jpeg")]
2396    #[test]
2397    fn rejects_jpeg_payload_dimensions_that_do_not_match_tile() {
2398        let pixels = vec![17u8; 8 * 32];
2399        let mut jpeg = Vec::new();
2400        jpeg_encoder::Encoder::new(&mut jpeg, 80)
2401            .encode(&pixels, 8, 32, jpeg_encoder::ColorType::Luma)
2402            .unwrap();
2403
2404        // The pixel count matches 16x16, so a byte-length-only validation
2405        // would accept the block and silently reinterpret its row geometry.
2406        let data = build_tiled_tiff_with_overrides(
2407            16,
2408            16,
2409            16,
2410            16,
2411            &[&jpeg],
2412            &[(259, 3, 1, inline_short(7))],
2413        );
2414        let file = TiffFile::from_bytes(data).unwrap();
2415        let error = file.read_image::<u8>(0).unwrap_err();
2416        assert!(error.to_string().contains("dimensions 8x32"), "{error}");
2417    }
2418
2419    #[test]
2420    fn ycbcr_decode_honors_reference_black_white_ranges() {
2421        // BT.601 video-range references: luma 16..235, chroma 128 +/- 112.
2422        let reference: [u32; 12] = [16, 1, 235, 1, 128, 1, 240, 1, 128, 1, 240, 1];
2423        let data = build_stripped_tiff(
2424            1,
2425            1,
2426            &[126u8, 201, 190],
2427            &[
2428                (
2429                    258,
2430                    3,
2431                    3,
2432                    [8u16, 8, 8].into_iter().flat_map(le_u16).collect(),
2433                ),
2434                (262, 3, 1, inline_short(6)),
2435                (277, 3, 1, inline_short(3)),
2436                (532, 5, 6, reference.into_iter().flat_map(le_u32).collect()),
2437            ],
2438        );
2439        let file = TiffFile::from_bytes(data).unwrap();
2440
2441        let image = file.read_decoded_image::<u8>(0).unwrap();
2442        let (rgb, offset) = image.into_raw_vec_and_offset();
2443        assert_eq!(offset, Some(0));
2444        // Expected values follow the TIFF 6.0 / libtiff formula, which scales
2445        // each chroma delta by 127/(ReferenceMax - ReferenceZero) for 8-bit
2446        // samples rather than by the full-scale denominator.
2447        assert_eq!(rgb, vec![227, 49, 255]);
2448    }
2449
2450    #[test]
2451    fn reads_horizontal_predictor_u16_strip() {
2452        let encoded = [1, 0, 1, 0, 2, 0];
2453        let data = build_stripped_tiff(
2454            3,
2455            1,
2456            &encoded,
2457            &[
2458                (258, 3, 1, [16, 0, 0, 0].to_vec()),
2459                (317, 3, 1, [2, 0, 0, 0].to_vec()),
2460            ],
2461        );
2462        let file = TiffFile::from_bytes(data).unwrap();
2463        let image = file.read_image::<u16>(0).unwrap();
2464        assert_eq!(image.shape(), &[1, 3]);
2465        let (values, offset) = image.into_raw_vec_and_offset();
2466        assert_eq!(offset, Some(0));
2467        assert_eq!(values, vec![1, 2, 4]);
2468    }
2469
2470    #[test]
2471    fn reads_lerc_f32_strip() {
2472        let mut blob = build_lerc2_header_v2(2, 2, 4, 6, 0.0, 1.0, 4.0, 1 + 16);
2473        blob.extend_from_slice(&0u32.to_le_bytes());
2474        blob.push(1);
2475        for value in [1.0f32, 2.0, 3.0, 4.0] {
2476            blob.extend_from_slice(&value.to_le_bytes());
2477        }
2478
2479        let data = build_lerc_tiff(2, 2, &blob, 32, 3, 1, None);
2480        let file = TiffFile::from_bytes(data).unwrap();
2481        let image = file.read_image::<f32>(0).unwrap();
2482        let (values, offset) = image.into_raw_vec_and_offset();
2483        assert_eq!(offset, Some(0));
2484        assert_eq!(values, vec![1.0, 2.0, 3.0, 4.0]);
2485    }
2486
2487    #[test]
2488    fn non_lerc_read_ignores_irrelevant_lerc_parameters() {
2489        let data = build_stripped_tiff(1, 1, &[9], &[(50674, 3, 1, inline_short(1))]);
2490        let file = TiffFile::from_bytes(data).unwrap();
2491        let image = file.read_image::<u8>(0).unwrap();
2492        assert_eq!(image.into_raw_vec_and_offset().0, vec![9]);
2493    }
2494
2495    #[test]
2496    fn reads_lerc_masked_f32_strip_as_nan() {
2497        let mask = [1u8, 0, 1, 1];
2498        let encoded_mask = encode_mask_rle(&mask);
2499        let mut blob =
2500            build_lerc2_header_v2(2, 2, 3, 6, 0.0, 1.0, 4.0, encoded_mask.len() + 1 + 12);
2501        blob.extend_from_slice(&(encoded_mask.len() as u32).to_le_bytes());
2502        blob.extend_from_slice(&encoded_mask);
2503        blob.push(1);
2504        for value in [1.0f32, 3.0, 4.0] {
2505            blob.extend_from_slice(&value.to_le_bytes());
2506        }
2507
2508        let data = build_lerc_tiff(2, 2, &blob, 32, 3, 1, None);
2509        let file = TiffFile::from_bytes(data).unwrap();
2510        let image = file.read_image::<f32>(0).unwrap();
2511        let (values, offset) = image.into_raw_vec_and_offset();
2512        assert_eq!(offset, Some(0));
2513        assert_eq!(values[0], 1.0);
2514        assert!(values[1].is_nan());
2515        assert_eq!(values[2], 3.0);
2516        assert_eq!(values[3], 4.0);
2517    }
2518
2519    #[test]
2520    fn reads_lerc_chunky_rgb_band_set_strip() {
2521        let mut red = build_lerc2_header_v2(2, 1, 2, 1, 0.0, 1.0, 1.0, 0);
2522        red.extend_from_slice(&0u32.to_le_bytes());
2523        let mut green = build_lerc2_header_v2(2, 1, 2, 1, 0.0, 2.0, 2.0, 0);
2524        green.extend_from_slice(&0u32.to_le_bytes());
2525        let mut blue = build_lerc2_header_v2(2, 1, 2, 1, 0.0, 3.0, 3.0, 0);
2526        blue.extend_from_slice(&0u32.to_le_bytes());
2527
2528        let mut blob = red;
2529        blob.extend_from_slice(&green);
2530        blob.extend_from_slice(&blue);
2531
2532        let data = build_lerc_tiff(2, 1, &blob, 8, 1, 3, None);
2533        let file = TiffFile::from_bytes(data).unwrap();
2534        let image = file.read_image::<u8>(0).unwrap();
2535        assert_eq!(image.shape(), &[1, 2, 3]);
2536        let (values, offset) = image.into_raw_vec_and_offset();
2537        assert_eq!(offset, Some(0));
2538        assert_eq!(values, vec![1, 2, 3, 1, 2, 3]);
2539    }
2540
2541    #[test]
2542    fn reads_lerc_chunky_rgb_depth_blob_strip() {
2543        let mut blob = build_lerc2_header_v4(2, 1, 3, 2, 1, 0.0, 1.0, 6.0, 6 + 6 + 1 + 6);
2544        blob.extend_from_slice(&0u32.to_le_bytes());
2545        for value in [1u8, 2, 3] {
2546            blob.extend_from_slice(&value.to_le_bytes());
2547        }
2548        for value in [4u8, 5, 6] {
2549            blob.extend_from_slice(&value.to_le_bytes());
2550        }
2551        blob.push(1);
2552        blob.extend_from_slice(&[1, 2, 3, 4, 5, 6]);
2553        let blob = finalize_lerc2_v4_with_checksum(blob);
2554
2555        let data = build_lerc_tiff(2, 1, &blob, 8, 1, 3, Some([4, 0]));
2556        let file = TiffFile::from_bytes(data).unwrap();
2557        let image = file.read_image::<u8>(0).unwrap();
2558        assert_eq!(image.shape(), &[1, 2, 3]);
2559        let (values, offset) = image.into_raw_vec_and_offset();
2560        assert_eq!(offset, Some(0));
2561        assert_eq!(values, vec![1, 2, 3, 4, 5, 6]);
2562    }
2563
2564    #[test]
2565    fn rejects_lerc2_blob_size_before_checksum_range_without_panicking() {
2566        let mut blob = build_lerc2_header_v4(1, 1, 1, 1, 1, 0.0, 1.0, 1.0, 0);
2567        blob[34..38].copy_from_slice(&8i32.to_le_bytes());
2568
2569        let data = build_lerc_tiff(1, 1, &blob, 8, 1, 1, Some([4, 0]));
2570        let file = TiffFile::from_bytes(data).unwrap();
2571        let error = file.read_image_bytes(0).unwrap_err();
2572        assert!(error.to_string().contains("invalid Lerc2 v4 blob size 8"));
2573    }
2574
2575    #[test]
2576    fn rejects_lerc2_header_dimensions_before_allocating_mask() {
2577        let mut blob = build_lerc2_header_v2(u32::MAX, u32::MAX, 1, 1, 0.0, 0.0, 1.0, 4);
2578        blob.extend_from_slice(&4u32.to_le_bytes());
2579        blob.extend_from_slice(&[0, 0, 0, 0]);
2580
2581        let data = build_lerc_tiff(1, 1, &blob, 8, 1, 1, None);
2582        let file = TiffFile::from_bytes(data).unwrap();
2583        let error = file.read_image_bytes(0).unwrap_err();
2584        assert!(error.to_string().contains("LERC raster dimensions"));
2585    }
2586
2587    #[test]
2588    fn rejects_truncated_lerc2_header_dimensions_before_decoder() {
2589        let blob = build_lerc2_header_v2(u32::MAX, u32::MAX, 1, 1, 0.0, 0.0, 1.0, 64);
2590
2591        let data = build_lerc_tiff(1, 1, &blob, 8, 1, 1, None);
2592        let file = TiffFile::from_bytes(data).unwrap();
2593        let error = file.read_image_bytes(0).unwrap_err();
2594        assert!(error.to_string().contains("LERC raster dimensions"));
2595    }
2596
2597    #[test]
2598    fn reads_lerc_deflate_f32_strip() {
2599        let mut blob = build_lerc2_header_v2(2, 2, 4, 6, 0.0, 1.0, 4.0, 1 + 16);
2600        blob.extend_from_slice(&0u32.to_le_bytes());
2601        blob.push(1);
2602        for value in [1.0f32, 2.0, 3.0, 4.0] {
2603            blob.extend_from_slice(&value.to_le_bytes());
2604        }
2605
2606        let mut encoder = ZlibEncoder::new(Vec::new(), FlateCompression::default());
2607        std::io::Write::write_all(&mut encoder, &blob).unwrap();
2608        let compressed = encoder.finish().unwrap();
2609
2610        let data = build_lerc_tiff(2, 2, &compressed, 32, 3, 1, Some([2, 1]));
2611        let file = TiffFile::from_bytes(data).unwrap();
2612        let image = file.read_image::<f32>(0).unwrap();
2613        let (values, offset) = image.into_raw_vec_and_offset();
2614        assert_eq!(offset, Some(0));
2615        assert_eq!(values, vec![1.0, 2.0, 3.0, 4.0]);
2616    }
2617
2618    #[cfg(feature = "zstd")]
2619    #[test]
2620    fn reads_lerc_zstd_f32_strip() {
2621        let mut blob = build_lerc2_header_v2(2, 2, 4, 6, 0.0, 1.0, 4.0, 1 + 16);
2622        blob.extend_from_slice(&0u32.to_le_bytes());
2623        blob.push(1);
2624        for value in [1.0f32, 2.0, 3.0, 4.0] {
2625            blob.extend_from_slice(&value.to_le_bytes());
2626        }
2627
2628        let compressed = ruzstd::encoding::compress_to_vec(
2629            &blob[..],
2630            ruzstd::encoding::CompressionLevel::Fastest,
2631        );
2632        let data = build_lerc_tiff(2, 2, &compressed, 32, 3, 1, Some([2, 2]));
2633        let file = TiffFile::from_bytes(data).unwrap();
2634        let image = file.read_image::<f32>(0).unwrap();
2635        let (values, offset) = image.into_raw_vec_and_offset();
2636        assert_eq!(offset, Some(0));
2637        assert_eq!(values, vec![1.0, 2.0, 3.0, 4.0]);
2638    }
2639
2640    #[test]
2641    fn reads_stripped_u8_window() {
2642        let data = build_multi_strip_tiff(
2643            4,
2644            &[
2645                &[1, 2, 3, 4],
2646                &[5, 6, 7, 8],
2647                &[9, 10, 11, 12],
2648                &[13, 14, 15, 16],
2649            ],
2650        );
2651        let file = TiffFile::from_bytes(data).unwrap();
2652        let window = file.read_window::<u8>(0, 1, 1, 2, 2).unwrap();
2653        assert_eq!(window.shape(), &[2, 2]);
2654        let (values, offset) = window.into_raw_vec_and_offset();
2655        assert_eq!(offset, Some(0));
2656        assert_eq!(values, vec![6, 7, 10, 11]);
2657    }
2658
2659    #[test]
2660    fn reads_tiled_u8_window() {
2661        let data = build_tiled_tiff(
2662            4,
2663            4,
2664            2,
2665            2,
2666            &[
2667                &[1, 2, 5, 6],
2668                &[3, 4, 7, 8],
2669                &[9, 10, 13, 14],
2670                &[11, 12, 15, 16],
2671            ],
2672        );
2673        let file = TiffFile::from_bytes(data).unwrap();
2674        let window = file.read_window::<u8>(0, 1, 1, 2, 2).unwrap();
2675        assert_eq!(window.shape(), &[2, 2]);
2676        let (values, offset) = window.into_raw_vec_and_offset();
2677        assert_eq!(offset, Some(0));
2678        assert_eq!(values, vec![6, 7, 10, 11]);
2679    }
2680
2681    #[test]
2682    fn windowed_tiled_reads_only_intersecting_blocks() {
2683        let data = build_tiled_tiff(
2684            4,
2685            4,
2686            2,
2687            2,
2688            &[
2689                &[1, 2, 5, 6],
2690                &[3, 4, 7, 8],
2691                &[9, 10, 13, 14],
2692                &[11, 12, 15, 16],
2693            ],
2694        );
2695        let source = Arc::new(CountingSource::new(data));
2696        let file = TiffFile::from_source(source.clone()).unwrap();
2697        source.reset_reads();
2698
2699        let window = file.read_window::<u8>(0, 0, 0, 2, 2).unwrap();
2700        let (values, offset) = window.into_raw_vec_and_offset();
2701        assert_eq!(offset, Some(0));
2702        assert_eq!(values, vec![1, 2, 5, 6]);
2703        assert_eq!(source.reads(), 1);
2704    }
2705
2706    #[test]
2707    fn unwraps_gdal_structural_metadata_block() {
2708        let metadata = GdalStructuralMetadata::from_prefix(
2709            b"GDAL_STRUCTURAL_METADATA_SIZE=000174 bytes\nBLOCK_LEADER=SIZE_AS_UINT4\nBLOCK_TRAILER=LAST_4_BYTES_REPEATED\n",
2710        )
2711        .unwrap();
2712
2713        let payload = [1u8, 2, 3, 4];
2714        let mut block = Vec::new();
2715        block.extend_from_slice(&(payload.len() as u32).to_le_bytes());
2716        block.extend_from_slice(&payload);
2717        block.extend_from_slice(&payload[payload.len() - 4..]);
2718
2719        let unwrapped = metadata
2720            .unwrap_block(&block, crate::ByteOrder::LittleEndian, 256)
2721            .unwrap();
2722        assert_eq!(unwrapped, payload);
2723    }
2724
2725    #[test]
2726    fn rejects_gdal_structural_metadata_trailer_mismatch() {
2727        let metadata = GdalStructuralMetadata::from_prefix(
2728            b"GDAL_STRUCTURAL_METADATA_SIZE=000174 bytes\nBLOCK_LEADER=SIZE_AS_UINT4\nBLOCK_TRAILER=LAST_4_BYTES_REPEATED\n",
2729        )
2730        .unwrap();
2731
2732        let block = [
2733            4u8, 0, 0, 0, //
2734            1, 2, 3, 4, //
2735            4, 3, 2, 1,
2736        ];
2737
2738        let error = metadata
2739            .unwrap_block(&block, crate::ByteOrder::LittleEndian, 512)
2740            .unwrap_err();
2741        assert!(error.to_string().contains("GDAL block trailer mismatch"));
2742    }
2743
2744    #[test]
2745    fn parses_gdal_structural_metadata_before_binary_prefix_data() {
2746        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";
2747        let prefix = format!(
2748            "{GDAL_STRUCTURAL_METADATA_PREFIX}{:06} bytes\n{rest}",
2749            rest.len()
2750        );
2751
2752        let mut bytes = vec![0u8; 8];
2753        bytes.extend_from_slice(prefix.as_bytes());
2754        bytes.extend_from_slice(&[0xff, 0x00, 0x80, 0x7f]);
2755
2756        let source = BytesSource::new(bytes);
2757        let metadata = parse_gdal_structural_metadata(&source).unwrap();
2758        assert!(metadata.block_leader_size_as_u32);
2759        assert!(metadata.block_trailer_repeats_last_4_bytes);
2760    }
2761
2762    #[test]
2763    fn parses_gdal_structural_metadata_declared_length_as_header_plus_payload() {
2764        let rest = "LAYOUT=IFDS_BEFORE_DATA\nBLOCK_ORDER=ROW_MAJOR\n";
2765        let prefix = format!(
2766            "{GDAL_STRUCTURAL_METADATA_PREFIX}{:06} bytes\n{rest}",
2767            rest.len()
2768        );
2769        assert_eq!(
2770            parse_gdal_structural_metadata_len(prefix.as_bytes()),
2771            Some(prefix.len())
2772        );
2773    }
2774
2775    #[test]
2776    fn leaves_payload_only_gdal_block_unchanged() {
2777        let metadata = GdalStructuralMetadata {
2778            block_leader_size_as_u32: true,
2779            block_trailer_repeats_last_4_bytes: true,
2780        };
2781        let payload = [0x80u8, 0x1a, 0xcf, 0x68, 0x43, 0x9a, 0x11, 0x08];
2782        let unwrapped = metadata
2783            .unwrap_block(&payload, crate::ByteOrder::LittleEndian, 570)
2784            .unwrap();
2785        assert_eq!(unwrapped, payload);
2786    }
2787
2788    #[test]
2789    fn rejects_zero_rows_per_strip_without_panicking() {
2790        let data = build_stripped_tiff(2, 2, &[1, 2, 3, 4], &[(278, 4, 1, le_u32(0).to_vec())]);
2791        let file = TiffFile::from_bytes(data).unwrap();
2792        let error = file.read_image_bytes(0).unwrap_err();
2793        assert!(error.to_string().contains("RowsPerStrip"));
2794    }
2795}