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