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//!
8//! # Example
9//!
10//! ```no_run
11//! use tiff_reader::TiffFile;
12//!
13//! let file = TiffFile::open("image.tif").unwrap();
14//! println!("byte order: {:?}", file.byte_order());
15//! println!("IFD count: {}", file.ifd_count());
16//!
17//! let ifd = file.ifd(0).unwrap();
18//! println!("  width: {}", ifd.width());
19//! println!("  height: {}", ifd.height());
20//! println!("  bits per sample: {:?}", ifd.bits_per_sample());
21//!
22//! let pixels: ndarray::ArrayD<u16> = file.read_image(0).unwrap();
23//! ```
24
25pub mod cache;
26pub mod error;
27pub mod filters;
28pub mod header;
29pub mod ifd;
30pub mod io;
31pub mod source;
32pub mod strip;
33pub mod tag;
34pub mod tile;
35
36use std::path::Path;
37use std::sync::Arc;
38
39use cache::BlockCache;
40use error::{Error, Result};
41use ndarray::{ArrayD, IxDyn};
42use source::{BytesSource, MmapSource, SharedSource, TiffSource};
43
44pub use error::Error as TiffError;
45pub use header::ByteOrder;
46pub use ifd::{Ifd, RasterLayout};
47pub use tag::{Tag, TagValue};
48
49/// Configuration for opening a TIFF file.
50#[derive(Debug, Clone, Copy)]
51pub struct OpenOptions {
52    /// Maximum bytes held in the decoded strip/tile cache.
53    pub block_cache_bytes: usize,
54    /// Maximum number of cached strips/tiles.
55    pub block_cache_slots: usize,
56}
57
58impl Default for OpenOptions {
59    fn default() -> Self {
60        Self {
61            block_cache_bytes: 64 * 1024 * 1024,
62            block_cache_slots: 257,
63        }
64    }
65}
66
67/// A memory-mapped TIFF file handle.
68pub struct TiffFile {
69    source: SharedSource,
70    header: header::TiffHeader,
71    ifds: Vec<ifd::Ifd>,
72    block_cache: Arc<BlockCache>,
73    gdal_structural_metadata: Option<GdalStructuralMetadata>,
74}
75
76#[derive(Debug, Clone, Copy)]
77pub(crate) struct GdalStructuralMetadata {
78    block_leader_size_as_u32: bool,
79    block_trailer_repeats_last_4_bytes: bool,
80}
81
82#[derive(Debug, Clone, Copy, PartialEq, Eq)]
83pub(crate) struct Window {
84    pub row_off: usize,
85    pub col_off: usize,
86    pub rows: usize,
87    pub cols: usize,
88}
89
90impl Window {
91    pub(crate) fn is_empty(self) -> bool {
92        self.rows == 0 || self.cols == 0
93    }
94
95    pub(crate) fn row_end(self) -> usize {
96        self.row_off + self.rows
97    }
98
99    pub(crate) fn col_end(self) -> usize {
100        self.col_off + self.cols
101    }
102
103    pub(crate) fn output_len(self, layout: &RasterLayout) -> Result<usize> {
104        self.cols
105            .checked_mul(self.rows)
106            .and_then(|pixels| pixels.checked_mul(layout.pixel_stride_bytes()))
107            .ok_or_else(|| Error::InvalidImageLayout("window size overflows usize".into()))
108    }
109}
110
111impl GdalStructuralMetadata {
112    fn from_prefix(bytes: &[u8]) -> Option<Self> {
113        let text = std::str::from_utf8(bytes).ok()?;
114        if !text.contains("GDAL_STRUCTURAL_METADATA_SIZE=") {
115            return None;
116        }
117
118        Some(Self {
119            block_leader_size_as_u32: text.contains("BLOCK_LEADER=SIZE_AS_UINT4"),
120            block_trailer_repeats_last_4_bytes: text
121                .contains("BLOCK_TRAILER=LAST_4_BYTES_REPEATED"),
122        })
123    }
124
125    pub(crate) fn unwrap_block<'a>(
126        &self,
127        raw: &'a [u8],
128        byte_order: ByteOrder,
129        offset: u64,
130    ) -> Result<&'a [u8]> {
131        if self.block_leader_size_as_u32 {
132            if raw.len() < 4 {
133                return Ok(raw);
134            }
135            let declared_len = match byte_order {
136                ByteOrder::LittleEndian => u32::from_le_bytes(raw[..4].try_into().unwrap()),
137                ByteOrder::BigEndian => u32::from_be_bytes(raw[..4].try_into().unwrap()),
138            } as usize;
139            if let Some(payload_end) = 4usize.checked_add(declared_len) {
140                if payload_end <= raw.len() {
141                    if self.block_trailer_repeats_last_4_bytes {
142                        let trailer_end = payload_end.checked_add(4).ok_or_else(|| {
143                            Error::InvalidImageLayout("GDAL block trailer overflows usize".into())
144                        })?;
145                        if trailer_end <= raw.len() {
146                            let expected = &raw[payload_end - 4..payload_end];
147                            let trailer = &raw[payload_end..trailer_end];
148                            if expected != trailer {
149                                return Err(Error::InvalidImageLayout(format!(
150                                    "GDAL block trailer mismatch at offset {offset}"
151                                )));
152                            }
153                        }
154                    }
155                    return Ok(&raw[4..payload_end]);
156                }
157            }
158        }
159
160        if self.block_trailer_repeats_last_4_bytes && raw.len() >= 8 {
161            let split = raw.len() - 4;
162            if raw[split - 4..split] == raw[split..] {
163                return Ok(&raw[..split]);
164            }
165        }
166
167        Ok(raw)
168    }
169}
170
171const GDAL_STRUCTURAL_METADATA_PREFIX: &str = "GDAL_STRUCTURAL_METADATA_SIZE=";
172
173/// Types that can be read directly from a decoded TIFF raster.
174pub trait TiffSample: Clone + 'static {
175    fn matches_layout(layout: &RasterLayout) -> bool;
176    fn decode_many(bytes: &[u8]) -> Vec<Self>;
177    fn type_name() -> &'static str;
178}
179
180macro_rules! impl_tiff_sample {
181    ($ty:ty, $format:expr, $bits:expr, $chunk:expr, $from_ne:expr) => {
182        impl TiffSample for $ty {
183            fn matches_layout(layout: &RasterLayout) -> bool {
184                layout.sample_format == $format && layout.bits_per_sample == $bits
185            }
186
187            fn decode_many(bytes: &[u8]) -> Vec<Self> {
188                bytes.chunks_exact($chunk).map($from_ne).collect()
189            }
190
191            fn type_name() -> &'static str {
192                stringify!($ty)
193            }
194        }
195    };
196}
197
198impl_tiff_sample!(u8, 1, 8, 1, |chunk: &[u8]| chunk[0]);
199impl_tiff_sample!(i8, 2, 8, 1, |chunk: &[u8]| chunk[0] as i8);
200impl_tiff_sample!(u16, 1, 16, 2, |chunk: &[u8]| u16::from_ne_bytes(
201    chunk.try_into().unwrap()
202));
203impl_tiff_sample!(i16, 2, 16, 2, |chunk: &[u8]| i16::from_ne_bytes(
204    chunk.try_into().unwrap()
205));
206impl_tiff_sample!(u32, 1, 32, 4, |chunk: &[u8]| u32::from_ne_bytes(
207    chunk.try_into().unwrap()
208));
209impl_tiff_sample!(i32, 2, 32, 4, |chunk: &[u8]| i32::from_ne_bytes(
210    chunk.try_into().unwrap()
211));
212impl_tiff_sample!(f32, 3, 32, 4, |chunk: &[u8]| f32::from_ne_bytes(
213    chunk.try_into().unwrap()
214));
215impl_tiff_sample!(u64, 1, 64, 8, |chunk: &[u8]| u64::from_ne_bytes(
216    chunk.try_into().unwrap()
217));
218impl_tiff_sample!(i64, 2, 64, 8, |chunk: &[u8]| i64::from_ne_bytes(
219    chunk.try_into().unwrap()
220));
221impl_tiff_sample!(f64, 3, 64, 8, |chunk: &[u8]| f64::from_ne_bytes(
222    chunk.try_into().unwrap()
223));
224
225impl TiffFile {
226    /// Open a TIFF file from disk using memory-mapped I/O.
227    pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
228        Self::open_with_options(path, OpenOptions::default())
229    }
230
231    /// Open a TIFF file from disk with explicit decoder options.
232    pub fn open_with_options<P: AsRef<Path>>(path: P, options: OpenOptions) -> Result<Self> {
233        let source: SharedSource = Arc::new(MmapSource::open(path.as_ref())?);
234        Self::from_source_with_options(source, options)
235    }
236
237    /// Open a TIFF file from an owned byte buffer (WASM-compatible).
238    pub fn from_bytes(data: Vec<u8>) -> Result<Self> {
239        Self::from_bytes_with_options(data, OpenOptions::default())
240    }
241
242    /// Open a TIFF file from bytes with explicit decoder options.
243    pub fn from_bytes_with_options(data: Vec<u8>, options: OpenOptions) -> Result<Self> {
244        let source: SharedSource = Arc::new(BytesSource::new(data));
245        Self::from_source_with_options(source, options)
246    }
247
248    /// Open a TIFF file from an arbitrary random-access source.
249    pub fn from_source(source: SharedSource) -> Result<Self> {
250        Self::from_source_with_options(source, OpenOptions::default())
251    }
252
253    /// Open a TIFF file from an arbitrary random-access source with options.
254    pub fn from_source_with_options(source: SharedSource, options: OpenOptions) -> Result<Self> {
255        let header_len = usize::try_from(source.len().min(16)).unwrap_or(16);
256        let header_bytes = source.read_exact_at(0, header_len)?;
257        let header = header::TiffHeader::parse(&header_bytes)?;
258        let gdal_structural_metadata = parse_gdal_structural_metadata(source.as_ref());
259        let ifds = ifd::parse_ifd_chain(source.as_ref(), &header)?;
260        Ok(Self {
261            source,
262            header,
263            ifds,
264            block_cache: Arc::new(BlockCache::new(
265                options.block_cache_bytes,
266                options.block_cache_slots,
267            )),
268            gdal_structural_metadata,
269        })
270    }
271
272    /// Returns the byte order of the TIFF file.
273    pub fn byte_order(&self) -> ByteOrder {
274        self.header.byte_order
275    }
276
277    /// Returns `true` if this is a BigTIFF file.
278    pub fn is_bigtiff(&self) -> bool {
279        self.header.is_bigtiff()
280    }
281
282    /// Returns the number of IFDs (images/pages) in the file.
283    pub fn ifd_count(&self) -> usize {
284        self.ifds.len()
285    }
286
287    /// Returns the IFD at the given index.
288    pub fn ifd(&self, index: usize) -> Result<&Ifd> {
289        self.ifds.get(index).ok_or(Error::IfdNotFound(index))
290    }
291
292    /// Returns all parsed IFDs.
293    pub fn ifds(&self) -> &[Ifd] {
294        &self.ifds
295    }
296
297    /// Returns the raw file bytes.
298    pub fn raw_bytes(&self) -> Option<&[u8]> {
299        self.source.as_slice()
300    }
301
302    /// Returns the backing source.
303    pub fn source(&self) -> &dyn TiffSource {
304        self.source.as_ref()
305    }
306
307    /// Decode an image into native-endian interleaved sample bytes.
308    pub fn read_image_bytes(&self, ifd_index: usize) -> Result<Vec<u8>> {
309        let ifd = self.ifd(ifd_index)?;
310        let layout = ifd.raster_layout()?;
311        self.decode_window_bytes(
312            ifd,
313            Window {
314                row_off: 0,
315                col_off: 0,
316                rows: layout.height,
317                cols: layout.width,
318            },
319        )
320    }
321
322    /// Decode a pixel window into native-endian interleaved sample bytes.
323    pub fn read_window_bytes(
324        &self,
325        ifd_index: usize,
326        row_off: usize,
327        col_off: usize,
328        rows: usize,
329        cols: usize,
330    ) -> Result<Vec<u8>> {
331        let ifd = self.ifd(ifd_index)?;
332        let layout = ifd.raster_layout()?;
333        let window = validate_window(&layout, row_off, col_off, rows, cols)?;
334        self.decode_window_bytes(ifd, window)
335    }
336
337    fn decode_window_bytes(&self, ifd: &Ifd, window: Window) -> Result<Vec<u8>> {
338        if window.is_empty() {
339            return Ok(Vec::new());
340        }
341
342        if ifd.is_tiled() {
343            tile::read_window(
344                self.source.as_ref(),
345                ifd,
346                self.byte_order(),
347                &self.block_cache,
348                window,
349                self.gdal_structural_metadata.as_ref(),
350            )
351        } else {
352            strip::read_window(
353                self.source.as_ref(),
354                ifd,
355                self.byte_order(),
356                &self.block_cache,
357                window,
358                self.gdal_structural_metadata.as_ref(),
359            )
360        }
361    }
362
363    /// Decode a window into a typed ndarray.
364    ///
365    /// Single-band rasters are returned as shape `[rows, cols]`.
366    /// Multi-band rasters are returned as shape `[rows, cols, samples_per_pixel]`.
367    pub fn read_window<T: TiffSample>(
368        &self,
369        ifd_index: usize,
370        row_off: usize,
371        col_off: usize,
372        rows: usize,
373        cols: usize,
374    ) -> Result<ArrayD<T>> {
375        let ifd = self.ifd(ifd_index)?;
376        let layout = ifd.raster_layout()?;
377        let window = validate_window(&layout, row_off, col_off, rows, cols)?;
378        if !T::matches_layout(&layout) {
379            return Err(Error::TypeMismatch {
380                expected: T::type_name(),
381                actual: format!(
382                    "sample_format={} bits_per_sample={}",
383                    layout.sample_format, layout.bits_per_sample
384                ),
385            });
386        }
387
388        let decoded = self.decode_window_bytes(ifd, window)?;
389        let values = T::decode_many(&decoded);
390        let shape = if layout.samples_per_pixel == 1 {
391            vec![window.rows, window.cols]
392        } else {
393            vec![window.rows, window.cols, layout.samples_per_pixel]
394        };
395        ArrayD::from_shape_vec(IxDyn(&shape), values).map_err(|e| {
396            Error::InvalidImageLayout(format!("failed to build ndarray from decoded raster: {e}"))
397        })
398    }
399
400    /// Decode an image into a typed ndarray.
401    ///
402    /// Single-band rasters are returned as shape `[height, width]`.
403    /// Multi-band rasters are returned as shape `[height, width, samples_per_pixel]`.
404    pub fn read_image<T: TiffSample>(&self, ifd_index: usize) -> Result<ArrayD<T>> {
405        let ifd = self.ifd(ifd_index)?;
406        let layout = ifd.raster_layout()?;
407        if !T::matches_layout(&layout) {
408            return Err(Error::TypeMismatch {
409                expected: T::type_name(),
410                actual: format!(
411                    "sample_format={} bits_per_sample={}",
412                    layout.sample_format, layout.bits_per_sample
413                ),
414            });
415        }
416
417        self.read_window(ifd_index, 0, 0, layout.height, layout.width)
418    }
419}
420
421fn validate_window(
422    layout: &RasterLayout,
423    row_off: usize,
424    col_off: usize,
425    rows: usize,
426    cols: usize,
427) -> Result<Window> {
428    let row_end = row_off
429        .checked_add(rows)
430        .ok_or_else(|| Error::InvalidImageLayout("window row range overflows usize".into()))?;
431    let col_end = col_off
432        .checked_add(cols)
433        .ok_or_else(|| Error::InvalidImageLayout("window column range overflows usize".into()))?;
434    if row_end > layout.height || col_end > layout.width {
435        return Err(Error::InvalidImageLayout(format!(
436            "window [{row_off}..{row_end}, {col_off}..{col_end}) exceeds raster bounds {}x{}",
437            layout.height, layout.width
438        )));
439    }
440    Ok(Window {
441        row_off,
442        col_off,
443        rows,
444        cols,
445    })
446}
447
448fn parse_gdal_structural_metadata(source: &dyn TiffSource) -> Option<GdalStructuralMetadata> {
449    let available_len = usize::try_from(source.len().checked_sub(8)?).ok()?;
450    if available_len == 0 {
451        return None;
452    }
453
454    let probe_len = available_len.min(64);
455    let probe = source.read_exact_at(8, probe_len).ok()?;
456    let total_len = parse_gdal_structural_metadata_len(&probe)?;
457    if total_len == 0 || total_len > available_len {
458        return None;
459    }
460
461    let bytes = source.read_exact_at(8, total_len).ok()?;
462    GdalStructuralMetadata::from_prefix(&bytes)
463}
464
465fn parse_gdal_structural_metadata_len(bytes: &[u8]) -> Option<usize> {
466    let text = std::str::from_utf8(bytes).ok()?;
467    let newline_index = text.find('\n')?;
468    let header = &text[..newline_index];
469    let value = header.strip_prefix(GDAL_STRUCTURAL_METADATA_PREFIX)?;
470    let digits: String = value.chars().take_while(|ch| ch.is_ascii_digit()).collect();
471    if digits.is_empty() {
472        return None;
473    }
474    let payload_len: usize = digits.parse().ok()?;
475    newline_index.checked_add(1)?.checked_add(payload_len)
476}
477
478#[cfg(test)]
479mod tests {
480    use std::collections::BTreeMap;
481    use std::sync::atomic::{AtomicUsize, Ordering};
482    use std::sync::Arc;
483
484    use super::{
485        parse_gdal_structural_metadata, parse_gdal_structural_metadata_len, GdalStructuralMetadata,
486        TiffFile, GDAL_STRUCTURAL_METADATA_PREFIX,
487    };
488    use crate::source::{BytesSource, TiffSource};
489
490    fn le_u16(value: u16) -> [u8; 2] {
491        value.to_le_bytes()
492    }
493
494    fn le_u32(value: u32) -> [u8; 4] {
495        value.to_le_bytes()
496    }
497
498    fn build_stripped_tiff(
499        width: u32,
500        height: u32,
501        image_data: &[u8],
502        overrides: &[(u16, u16, u32, Vec<u8>)],
503    ) -> Vec<u8> {
504        let mut entries = BTreeMap::new();
505        entries.insert(256, (4, 1, le_u32(width).to_vec()));
506        entries.insert(257, (4, 1, le_u32(height).to_vec()));
507        entries.insert(258, (3, 1, [8, 0, 0, 0].to_vec()));
508        entries.insert(259, (3, 1, [1, 0, 0, 0].to_vec()));
509        entries.insert(273, (4, 1, Vec::new()));
510        entries.insert(277, (3, 1, [1, 0, 0, 0].to_vec()));
511        entries.insert(278, (4, 1, le_u32(height).to_vec()));
512        entries.insert(279, (4, 1, le_u32(image_data.len() as u32).to_vec()));
513        for &(tag, ty, count, ref value) in overrides {
514            entries.insert(tag, (ty, count, value.clone()));
515        }
516
517        let ifd_offset = 8u32;
518        let ifd_size = 2 + entries.len() * 12 + 4;
519        let mut next_data_offset = ifd_offset as usize + ifd_size;
520        let image_offset = next_data_offset as u32;
521        next_data_offset += image_data.len();
522
523        let mut data = Vec::with_capacity(next_data_offset);
524        data.extend_from_slice(b"II");
525        data.extend_from_slice(&le_u16(42));
526        data.extend_from_slice(&le_u32(ifd_offset));
527        data.extend_from_slice(&le_u16(entries.len() as u16));
528
529        let mut deferred = Vec::new();
530        for (tag, (ty, count, value)) in entries {
531            data.extend_from_slice(&le_u16(tag));
532            data.extend_from_slice(&le_u16(ty));
533            data.extend_from_slice(&le_u32(count));
534            if tag == 273 {
535                data.extend_from_slice(&le_u32(image_offset));
536            } else if value.len() <= 4 {
537                let mut inline = [0u8; 4];
538                inline[..value.len()].copy_from_slice(&value);
539                data.extend_from_slice(&inline);
540            } else {
541                let offset = next_data_offset as u32;
542                data.extend_from_slice(&le_u32(offset));
543                next_data_offset += value.len();
544                deferred.push(value);
545            }
546        }
547        data.extend_from_slice(&le_u32(0));
548        data.extend_from_slice(image_data);
549        for value in deferred {
550            data.extend_from_slice(&value);
551        }
552        data
553    }
554
555    fn build_tiled_tiff(
556        width: u32,
557        height: u32,
558        tile_width: u32,
559        tile_height: u32,
560        tiles: &[&[u8]],
561    ) -> Vec<u8> {
562        let mut entries = BTreeMap::new();
563        entries.insert(256, (4, 1, le_u32(width).to_vec()));
564        entries.insert(257, (4, 1, le_u32(height).to_vec()));
565        entries.insert(258, (3, 1, [8, 0, 0, 0].to_vec()));
566        entries.insert(259, (3, 1, [1, 0, 0, 0].to_vec()));
567        entries.insert(277, (3, 1, [1, 0, 0, 0].to_vec()));
568        entries.insert(322, (4, 1, le_u32(tile_width).to_vec()));
569        entries.insert(323, (4, 1, le_u32(tile_height).to_vec()));
570        entries.insert(
571            325,
572            (
573                4,
574                tiles.len() as u32,
575                tiles
576                    .iter()
577                    .flat_map(|tile| le_u32(tile.len() as u32))
578                    .collect(),
579            ),
580        );
581
582        let ifd_offset = 8u32;
583        let ifd_size = 2 + (entries.len() + 1) * 12 + 4;
584        let mut tile_data_offset = ifd_offset as usize + ifd_size;
585        let tile_offsets: Vec<u32> = tiles
586            .iter()
587            .map(|tile| {
588                let offset = tile_data_offset as u32;
589                tile_data_offset += tile.len();
590                offset
591            })
592            .collect();
593        entries.insert(
594            324,
595            (
596                4,
597                tile_offsets.len() as u32,
598                tile_offsets
599                    .iter()
600                    .flat_map(|offset| le_u32(*offset))
601                    .collect(),
602            ),
603        );
604
605        let mut next_data_offset = tile_data_offset;
606        let mut data = Vec::with_capacity(next_data_offset);
607        data.extend_from_slice(b"II");
608        data.extend_from_slice(&le_u16(42));
609        data.extend_from_slice(&le_u32(ifd_offset));
610        data.extend_from_slice(&le_u16(entries.len() as u16));
611
612        let mut deferred = Vec::new();
613        for (tag, (ty, count, value)) in entries {
614            data.extend_from_slice(&le_u16(tag));
615            data.extend_from_slice(&le_u16(ty));
616            data.extend_from_slice(&le_u32(count));
617            if value.len() <= 4 {
618                let mut inline = [0u8; 4];
619                inline[..value.len()].copy_from_slice(&value);
620                data.extend_from_slice(&inline);
621            } else {
622                let offset = next_data_offset as u32;
623                data.extend_from_slice(&le_u32(offset));
624                next_data_offset += value.len();
625                deferred.push(value);
626            }
627        }
628        data.extend_from_slice(&le_u32(0));
629        for tile in tiles {
630            data.extend_from_slice(tile);
631        }
632        for value in deferred {
633            data.extend_from_slice(&value);
634        }
635        data
636    }
637
638    fn build_multi_strip_tiff(width: u32, rows: &[&[u8]]) -> Vec<u8> {
639        let mut entries = BTreeMap::new();
640        entries.insert(256, (4, 1, le_u32(width).to_vec()));
641        entries.insert(257, (4, 1, le_u32(rows.len() as u32).to_vec()));
642        entries.insert(258, (3, 1, [8, 0, 0, 0].to_vec()));
643        entries.insert(259, (3, 1, [1, 0, 0, 0].to_vec()));
644        entries.insert(277, (3, 1, [1, 0, 0, 0].to_vec()));
645        entries.insert(278, (4, 1, le_u32(1).to_vec()));
646        entries.insert(
647            279,
648            (
649                4,
650                rows.len() as u32,
651                rows.iter()
652                    .flat_map(|row| le_u32(row.len() as u32))
653                    .collect(),
654            ),
655        );
656
657        let ifd_offset = 8u32;
658        let ifd_size = 2 + (entries.len() + 1) * 12 + 4;
659        let mut strip_data_offset = ifd_offset as usize + ifd_size;
660        let strip_offsets: Vec<u32> = rows
661            .iter()
662            .map(|row| {
663                let offset = strip_data_offset as u32;
664                strip_data_offset += row.len();
665                offset
666            })
667            .collect();
668        entries.insert(
669            273,
670            (
671                4,
672                strip_offsets.len() as u32,
673                strip_offsets
674                    .iter()
675                    .flat_map(|offset| le_u32(*offset))
676                    .collect(),
677            ),
678        );
679
680        let mut next_data_offset = strip_data_offset;
681        let mut data = Vec::with_capacity(next_data_offset);
682        data.extend_from_slice(b"II");
683        data.extend_from_slice(&le_u16(42));
684        data.extend_from_slice(&le_u32(ifd_offset));
685        data.extend_from_slice(&le_u16(entries.len() as u16));
686
687        let mut deferred = Vec::new();
688        for (tag, (ty, count, value)) in entries {
689            data.extend_from_slice(&le_u16(tag));
690            data.extend_from_slice(&le_u16(ty));
691            data.extend_from_slice(&le_u32(count));
692            if value.len() <= 4 {
693                let mut inline = [0u8; 4];
694                inline[..value.len()].copy_from_slice(&value);
695                data.extend_from_slice(&inline);
696            } else {
697                let offset = next_data_offset as u32;
698                data.extend_from_slice(&le_u32(offset));
699                next_data_offset += value.len();
700                deferred.push(value);
701            }
702        }
703        data.extend_from_slice(&le_u32(0));
704        for row in rows {
705            data.extend_from_slice(row);
706        }
707        for value in deferred {
708            data.extend_from_slice(&value);
709        }
710        data
711    }
712
713    struct CountingSource {
714        bytes: Vec<u8>,
715        reads: AtomicUsize,
716    }
717
718    impl CountingSource {
719        fn new(bytes: Vec<u8>) -> Self {
720            Self {
721                bytes,
722                reads: AtomicUsize::new(0),
723            }
724        }
725
726        fn reset_reads(&self) {
727            self.reads.store(0, Ordering::SeqCst);
728        }
729
730        fn reads(&self) -> usize {
731            self.reads.load(Ordering::SeqCst)
732        }
733    }
734
735    impl TiffSource for CountingSource {
736        fn len(&self) -> u64 {
737            self.bytes.len() as u64
738        }
739
740        fn read_exact_at(&self, offset: u64, len: usize) -> crate::error::Result<Vec<u8>> {
741            self.reads.fetch_add(1, Ordering::SeqCst);
742            let start =
743                usize::try_from(offset).map_err(|_| crate::error::Error::OffsetOutOfBounds {
744                    offset,
745                    length: len as u64,
746                    data_len: self.len(),
747                })?;
748            let end = start
749                .checked_add(len)
750                .ok_or(crate::error::Error::OffsetOutOfBounds {
751                    offset,
752                    length: len as u64,
753                    data_len: self.len(),
754                })?;
755            if end > self.bytes.len() {
756                return Err(crate::error::Error::OffsetOutOfBounds {
757                    offset,
758                    length: len as u64,
759                    data_len: self.len(),
760                });
761            }
762            Ok(self.bytes[start..end].to_vec())
763        }
764    }
765
766    #[test]
767    fn reads_stripped_u8_image() {
768        let data = build_stripped_tiff(2, 2, &[1, 2, 3, 4], &[]);
769        let file = TiffFile::from_bytes(data).unwrap();
770        let image = file.read_image::<u8>(0).unwrap();
771        assert_eq!(image.shape(), &[2, 2]);
772        let (values, offset) = image.into_raw_vec_and_offset();
773        assert_eq!(offset, Some(0));
774        assert_eq!(values, vec![1, 2, 3, 4]);
775    }
776
777    #[test]
778    fn reads_horizontal_predictor_u16_strip() {
779        let encoded = [1, 0, 1, 0, 2, 0];
780        let data = build_stripped_tiff(
781            3,
782            1,
783            &encoded,
784            &[
785                (258, 3, 1, [16, 0, 0, 0].to_vec()),
786                (317, 3, 1, [2, 0, 0, 0].to_vec()),
787            ],
788        );
789        let file = TiffFile::from_bytes(data).unwrap();
790        let image = file.read_image::<u16>(0).unwrap();
791        assert_eq!(image.shape(), &[1, 3]);
792        let (values, offset) = image.into_raw_vec_and_offset();
793        assert_eq!(offset, Some(0));
794        assert_eq!(values, vec![1, 2, 4]);
795    }
796
797    #[test]
798    fn reads_stripped_u8_window() {
799        let data = build_multi_strip_tiff(
800            4,
801            &[
802                &[1, 2, 3, 4],
803                &[5, 6, 7, 8],
804                &[9, 10, 11, 12],
805                &[13, 14, 15, 16],
806            ],
807        );
808        let file = TiffFile::from_bytes(data).unwrap();
809        let window = file.read_window::<u8>(0, 1, 1, 2, 2).unwrap();
810        assert_eq!(window.shape(), &[2, 2]);
811        let (values, offset) = window.into_raw_vec_and_offset();
812        assert_eq!(offset, Some(0));
813        assert_eq!(values, vec![6, 7, 10, 11]);
814    }
815
816    #[test]
817    fn reads_tiled_u8_window() {
818        let data = build_tiled_tiff(
819            4,
820            4,
821            2,
822            2,
823            &[
824                &[1, 2, 5, 6],
825                &[3, 4, 7, 8],
826                &[9, 10, 13, 14],
827                &[11, 12, 15, 16],
828            ],
829        );
830        let file = TiffFile::from_bytes(data).unwrap();
831        let window = file.read_window::<u8>(0, 1, 1, 2, 2).unwrap();
832        assert_eq!(window.shape(), &[2, 2]);
833        let (values, offset) = window.into_raw_vec_and_offset();
834        assert_eq!(offset, Some(0));
835        assert_eq!(values, vec![6, 7, 10, 11]);
836    }
837
838    #[test]
839    fn windowed_tiled_reads_only_intersecting_blocks() {
840        let data = build_tiled_tiff(
841            4,
842            4,
843            2,
844            2,
845            &[
846                &[1, 2, 5, 6],
847                &[3, 4, 7, 8],
848                &[9, 10, 13, 14],
849                &[11, 12, 15, 16],
850            ],
851        );
852        let source = Arc::new(CountingSource::new(data));
853        let file = TiffFile::from_source(source.clone()).unwrap();
854        source.reset_reads();
855
856        let window = file.read_window::<u8>(0, 0, 0, 2, 2).unwrap();
857        let (values, offset) = window.into_raw_vec_and_offset();
858        assert_eq!(offset, Some(0));
859        assert_eq!(values, vec![1, 2, 5, 6]);
860        assert_eq!(source.reads(), 1);
861    }
862
863    #[test]
864    fn unwraps_gdal_structural_metadata_block() {
865        let metadata = GdalStructuralMetadata::from_prefix(
866            b"GDAL_STRUCTURAL_METADATA_SIZE=000174 bytes\nBLOCK_LEADER=SIZE_AS_UINT4\nBLOCK_TRAILER=LAST_4_BYTES_REPEATED\n",
867        )
868        .unwrap();
869
870        let payload = [1u8, 2, 3, 4];
871        let mut block = Vec::new();
872        block.extend_from_slice(&(payload.len() as u32).to_le_bytes());
873        block.extend_from_slice(&payload);
874        block.extend_from_slice(&payload[payload.len() - 4..]);
875
876        let unwrapped = metadata
877            .unwrap_block(&block, crate::ByteOrder::LittleEndian, 256)
878            .unwrap();
879        assert_eq!(unwrapped, payload);
880    }
881
882    #[test]
883    fn rejects_gdal_structural_metadata_trailer_mismatch() {
884        let metadata = GdalStructuralMetadata::from_prefix(
885            b"GDAL_STRUCTURAL_METADATA_SIZE=000174 bytes\nBLOCK_LEADER=SIZE_AS_UINT4\nBLOCK_TRAILER=LAST_4_BYTES_REPEATED\n",
886        )
887        .unwrap();
888
889        let block = [
890            4u8, 0, 0, 0, //
891            1, 2, 3, 4, //
892            4, 3, 2, 1,
893        ];
894
895        let error = metadata
896            .unwrap_block(&block, crate::ByteOrder::LittleEndian, 512)
897            .unwrap_err();
898        assert!(error.to_string().contains("GDAL block trailer mismatch"));
899    }
900
901    #[test]
902    fn parses_gdal_structural_metadata_before_binary_prefix_data() {
903        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";
904        let prefix = format!(
905            "{GDAL_STRUCTURAL_METADATA_PREFIX}{:06} bytes\n{rest}",
906            rest.len()
907        );
908
909        let mut bytes = vec![0u8; 8];
910        bytes.extend_from_slice(prefix.as_bytes());
911        bytes.extend_from_slice(&[0xff, 0x00, 0x80, 0x7f]);
912
913        let source = BytesSource::new(bytes);
914        let metadata = parse_gdal_structural_metadata(&source).unwrap();
915        assert!(metadata.block_leader_size_as_u32);
916        assert!(metadata.block_trailer_repeats_last_4_bytes);
917    }
918
919    #[test]
920    fn parses_gdal_structural_metadata_declared_length_as_header_plus_payload() {
921        let rest = "LAYOUT=IFDS_BEFORE_DATA\nBLOCK_ORDER=ROW_MAJOR\n";
922        let prefix = format!(
923            "{GDAL_STRUCTURAL_METADATA_PREFIX}{:06} bytes\n{rest}",
924            rest.len()
925        );
926        assert_eq!(
927            parse_gdal_structural_metadata_len(prefix.as_bytes()),
928            Some(prefix.len())
929        );
930    }
931
932    #[test]
933    fn leaves_payload_only_gdal_block_unchanged() {
934        let metadata = GdalStructuralMetadata {
935            block_leader_size_as_u32: true,
936            block_trailer_repeats_last_4_bytes: true,
937        };
938        let payload = [0x80u8, 0x1a, 0xcf, 0x68, 0x43, 0x9a, 0x11, 0x08];
939        let unwrapped = metadata
940            .unwrap_block(&payload, crate::ByteOrder::LittleEndian, 570)
941            .unwrap();
942        assert_eq!(unwrapped, payload);
943    }
944}