Skip to main content

tiff_reader/
lib.rs

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