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