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 pixels: 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;
36pub mod source;
37pub mod strip;
38pub mod tag;
39pub mod tile;
40
41use std::path::Path;
42use std::sync::Arc;
43
44use cache::BlockCache;
45use error::{Error, Result};
46use ndarray::{ArrayD, IxDyn};
47use source::{BytesSource, MmapSource, SharedSource, TiffSource};
48
49pub use error::Error as TiffError;
50pub use header::ByteOrder;
51pub use ifd::{Ifd, RasterLayout};
52pub use tag::{Tag, TagValue};
53pub use tiff_core::constants;
54pub use tiff_core::sample::TiffSample;
55pub use tiff_core::TagType;
56
57/// Configuration for opening a TIFF file.
58#[derive(Debug, Clone, Copy)]
59pub struct OpenOptions {
60    /// Maximum bytes held in the decoded strip/tile cache.
61    pub block_cache_bytes: usize,
62    /// Maximum number of cached strips/tiles.
63    pub block_cache_slots: usize,
64}
65
66impl Default for OpenOptions {
67    fn default() -> Self {
68        Self {
69            block_cache_bytes: 64 * 1024 * 1024,
70            block_cache_slots: 257,
71        }
72    }
73}
74
75/// A memory-mapped TIFF file handle.
76pub struct TiffFile {
77    source: SharedSource,
78    header: header::TiffHeader,
79    ifds: Vec<ifd::Ifd>,
80    block_cache: Arc<BlockCache>,
81    gdal_structural_metadata: Option<GdalStructuralMetadata>,
82}
83
84#[derive(Debug, Clone, Copy)]
85pub(crate) struct GdalStructuralMetadata {
86    block_leader_size_as_u32: bool,
87    block_trailer_repeats_last_4_bytes: bool,
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq)]
91pub(crate) struct Window {
92    pub row_off: usize,
93    pub col_off: usize,
94    pub rows: usize,
95    pub cols: usize,
96}
97
98impl Window {
99    pub(crate) fn is_empty(self) -> bool {
100        self.rows == 0 || self.cols == 0
101    }
102
103    pub(crate) fn row_end(self) -> usize {
104        self.row_off + self.rows
105    }
106
107    pub(crate) fn col_end(self) -> usize {
108        self.col_off + self.cols
109    }
110
111    pub(crate) fn output_len(self, layout: &RasterLayout) -> Result<usize> {
112        self.cols
113            .checked_mul(self.rows)
114            .and_then(|pixels| pixels.checked_mul(layout.pixel_stride_bytes()))
115            .ok_or_else(|| Error::InvalidImageLayout("window size overflows usize".into()))
116    }
117}
118
119impl GdalStructuralMetadata {
120    fn from_prefix(bytes: &[u8]) -> Option<Self> {
121        let text = std::str::from_utf8(bytes).ok()?;
122        if !text.contains("GDAL_STRUCTURAL_METADATA_SIZE=") {
123            return None;
124        }
125
126        Some(Self {
127            block_leader_size_as_u32: text.contains("BLOCK_LEADER=SIZE_AS_UINT4"),
128            block_trailer_repeats_last_4_bytes: text
129                .contains("BLOCK_TRAILER=LAST_4_BYTES_REPEATED"),
130        })
131    }
132
133    pub(crate) fn unwrap_block<'a>(
134        &self,
135        raw: &'a [u8],
136        byte_order: ByteOrder,
137        offset: u64,
138    ) -> Result<&'a [u8]> {
139        if self.block_leader_size_as_u32 {
140            if raw.len() < 4 {
141                return Ok(raw);
142            }
143            let declared_len = match byte_order {
144                ByteOrder::LittleEndian => u32::from_le_bytes(raw[..4].try_into().unwrap()),
145                ByteOrder::BigEndian => u32::from_be_bytes(raw[..4].try_into().unwrap()),
146            } as usize;
147            if let Some(payload_end) = 4usize.checked_add(declared_len) {
148                if payload_end <= raw.len() {
149                    if self.block_trailer_repeats_last_4_bytes {
150                        let trailer_end = payload_end.checked_add(4).ok_or_else(|| {
151                            Error::InvalidImageLayout("GDAL block trailer overflows usize".into())
152                        })?;
153                        if trailer_end <= raw.len() {
154                            let expected = &raw[payload_end - 4..payload_end];
155                            let trailer = &raw[payload_end..trailer_end];
156                            if expected != trailer {
157                                return Err(Error::InvalidImageLayout(format!(
158                                    "GDAL block trailer mismatch at offset {offset}"
159                                )));
160                            }
161                        }
162                    }
163                    return Ok(&raw[4..payload_end]);
164                }
165            }
166        }
167
168        if self.block_trailer_repeats_last_4_bytes && raw.len() >= 8 {
169            let split = raw.len() - 4;
170            if raw[split - 4..split] == raw[split..] {
171                return Ok(&raw[..split]);
172            }
173        }
174
175        Ok(raw)
176    }
177}
178
179pub(crate) fn read_gdal_block_payload(
180    source: &dyn TiffSource,
181    metadata: &GdalStructuralMetadata,
182    byte_order: ByteOrder,
183    offset: u64,
184    byte_count: u64,
185) -> Result<Vec<u8>> {
186    let wrapped_extra = 4u64
187        .checked_add(if metadata.block_trailer_repeats_last_4_bytes {
188            4
189        } else {
190            0
191        })
192        .ok_or_else(|| Error::InvalidImageLayout("GDAL block wrapper overflows u64".into()))?;
193
194    let mut candidates = Vec::with_capacity(2);
195    if metadata.block_leader_size_as_u32 && offset >= 4 {
196        candidates.push((
197            offset - 4,
198            byte_count.checked_add(wrapped_extra).ok_or_else(|| {
199                Error::InvalidImageLayout("GDAL wrapped block length overflows u64".into())
200            })?,
201        ));
202    }
203    candidates.push((offset, byte_count));
204
205    let mut fallback: Option<Result<Vec<u8>>> = None;
206    for (candidate_offset, candidate_len) in candidates {
207        let len = usize::try_from(candidate_len).map_err(|_| Error::OffsetOutOfBounds {
208            offset: candidate_offset,
209            length: candidate_len,
210            data_len: source.len(),
211        })?;
212        let raw = match source.read_exact_at(candidate_offset, len) {
213            Ok(raw) => raw,
214            Err(err) => {
215                if fallback.is_none() {
216                    fallback = Some(Err(err));
217                }
218                continue;
219            }
220        };
221        match metadata.unwrap_block(&raw, byte_order, candidate_offset) {
222            Ok(payload) => {
223                if candidate_offset != offset
224                    && payload.len() == usize::try_from(byte_count).unwrap_or(usize::MAX)
225                {
226                    return Ok(payload.to_vec());
227                }
228                fallback = Some(Ok(payload.to_vec()));
229            }
230            Err(err) => {
231                if fallback.is_none() {
232                    fallback = Some(Err(err));
233                }
234            }
235        }
236    }
237
238    match fallback {
239        Some(result) => result,
240        None => Ok(Vec::new()),
241    }
242}
243
244const GDAL_STRUCTURAL_METADATA_PREFIX: &str = "GDAL_STRUCTURAL_METADATA_SIZE=";
245
246// TiffSample trait and impls are provided by tiff-core and re-exported above.
247
248impl TiffFile {
249    /// Open a TIFF file from disk using memory-mapped I/O.
250    pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
251        Self::open_with_options(path, OpenOptions::default())
252    }
253
254    /// Open a TIFF file from disk with explicit decoder options.
255    pub fn open_with_options<P: AsRef<Path>>(path: P, options: OpenOptions) -> Result<Self> {
256        let source: SharedSource = Arc::new(MmapSource::open(path.as_ref())?);
257        Self::from_source_with_options(source, options)
258    }
259
260    /// Open a TIFF file from an owned byte buffer (WASM-compatible).
261    pub fn from_bytes(data: Vec<u8>) -> Result<Self> {
262        Self::from_bytes_with_options(data, OpenOptions::default())
263    }
264
265    /// Open a TIFF file from bytes with explicit decoder options.
266    pub fn from_bytes_with_options(data: Vec<u8>, options: OpenOptions) -> Result<Self> {
267        let source: SharedSource = Arc::new(BytesSource::new(data));
268        Self::from_source_with_options(source, options)
269    }
270
271    /// Open a TIFF file from an arbitrary random-access source.
272    pub fn from_source(source: SharedSource) -> Result<Self> {
273        Self::from_source_with_options(source, OpenOptions::default())
274    }
275
276    /// Open a TIFF file from an arbitrary random-access source with options.
277    pub fn from_source_with_options(source: SharedSource, options: OpenOptions) -> Result<Self> {
278        let header_len = usize::try_from(source.len().min(16)).unwrap_or(16);
279        let header_bytes = source.read_exact_at(0, header_len)?;
280        let header = header::TiffHeader::parse(&header_bytes)?;
281        let gdal_structural_metadata = parse_gdal_structural_metadata(source.as_ref());
282        let ifds = ifd::parse_ifd_chain(source.as_ref(), &header)?;
283        Ok(Self {
284            source,
285            header,
286            ifds,
287            block_cache: Arc::new(BlockCache::new(
288                options.block_cache_bytes,
289                options.block_cache_slots,
290            )),
291            gdal_structural_metadata,
292        })
293    }
294
295    /// Returns the byte order of the TIFF file.
296    pub fn byte_order(&self) -> ByteOrder {
297        self.header.byte_order
298    }
299
300    /// Returns `true` if this is a BigTIFF file.
301    pub fn is_bigtiff(&self) -> bool {
302        self.header.is_bigtiff()
303    }
304
305    /// Returns the number of IFDs (images/pages) in the file.
306    pub fn ifd_count(&self) -> usize {
307        self.ifds.len()
308    }
309
310    /// Returns the IFD at the given index.
311    pub fn ifd(&self, index: usize) -> Result<&Ifd> {
312        self.ifds.get(index).ok_or(Error::IfdNotFound(index))
313    }
314
315    /// Returns all parsed IFDs.
316    pub fn ifds(&self) -> &[Ifd] {
317        &self.ifds
318    }
319
320    /// Returns the raw file bytes.
321    pub fn raw_bytes(&self) -> Option<&[u8]> {
322        self.source.as_slice()
323    }
324
325    /// Returns the backing source.
326    pub fn source(&self) -> &dyn TiffSource {
327        self.source.as_ref()
328    }
329
330    /// Decode an image into native-endian interleaved sample bytes.
331    pub fn read_image_bytes(&self, ifd_index: usize) -> Result<Vec<u8>> {
332        let ifd = self.ifd(ifd_index)?;
333        let layout = ifd.raster_layout()?;
334        self.decode_window_bytes(
335            ifd,
336            Window {
337                row_off: 0,
338                col_off: 0,
339                rows: layout.height,
340                cols: layout.width,
341            },
342        )
343    }
344
345    /// Decode a pixel window into native-endian interleaved sample bytes.
346    pub fn read_window_bytes(
347        &self,
348        ifd_index: usize,
349        row_off: usize,
350        col_off: usize,
351        rows: usize,
352        cols: usize,
353    ) -> Result<Vec<u8>> {
354        let ifd = self.ifd(ifd_index)?;
355        let layout = ifd.raster_layout()?;
356        let window = validate_window(&layout, row_off, col_off, rows, cols)?;
357        self.decode_window_bytes(ifd, window)
358    }
359
360    fn decode_window_bytes(&self, ifd: &Ifd, window: Window) -> Result<Vec<u8>> {
361        if window.is_empty() {
362            return Ok(Vec::new());
363        }
364
365        if ifd.is_tiled() {
366            tile::read_window(
367                self.source.as_ref(),
368                ifd,
369                self.byte_order(),
370                &self.block_cache,
371                window,
372                self.gdal_structural_metadata.as_ref(),
373            )
374        } else {
375            strip::read_window(
376                self.source.as_ref(),
377                ifd,
378                self.byte_order(),
379                &self.block_cache,
380                window,
381                self.gdal_structural_metadata.as_ref(),
382            )
383        }
384    }
385
386    /// Decode a window into a typed ndarray.
387    ///
388    /// Single-band rasters are returned as shape `[rows, cols]`.
389    /// Multi-band rasters are returned as shape `[rows, cols, samples_per_pixel]`.
390    pub fn read_window<T: TiffSample>(
391        &self,
392        ifd_index: usize,
393        row_off: usize,
394        col_off: usize,
395        rows: usize,
396        cols: usize,
397    ) -> Result<ArrayD<T>> {
398        let ifd = self.ifd(ifd_index)?;
399        let layout = ifd.raster_layout()?;
400        let window = validate_window(&layout, row_off, col_off, rows, cols)?;
401        if !T::matches_layout(&layout) {
402            return Err(Error::TypeMismatch {
403                expected: T::type_name(),
404                actual: format!(
405                    "sample_format={} bits_per_sample={}",
406                    layout.sample_format, layout.bits_per_sample
407                ),
408            });
409        }
410
411        let decoded = self.decode_window_bytes(ifd, window)?;
412        let values = T::decode_many(&decoded);
413        let shape = if layout.samples_per_pixel == 1 {
414            vec![window.rows, window.cols]
415        } else {
416            vec![window.rows, window.cols, layout.samples_per_pixel]
417        };
418        ArrayD::from_shape_vec(IxDyn(&shape), values).map_err(|e| {
419            Error::InvalidImageLayout(format!("failed to build ndarray from decoded raster: {e}"))
420        })
421    }
422
423    /// Decode an image into a typed ndarray.
424    ///
425    /// Single-band rasters are returned as shape `[height, width]`.
426    /// Multi-band rasters are returned as shape `[height, width, samples_per_pixel]`.
427    pub fn read_image<T: TiffSample>(&self, ifd_index: usize) -> Result<ArrayD<T>> {
428        let ifd = self.ifd(ifd_index)?;
429        let layout = ifd.raster_layout()?;
430        if !T::matches_layout(&layout) {
431            return Err(Error::TypeMismatch {
432                expected: T::type_name(),
433                actual: format!(
434                    "sample_format={} bits_per_sample={}",
435                    layout.sample_format, layout.bits_per_sample
436                ),
437            });
438        }
439
440        self.read_window(ifd_index, 0, 0, layout.height, layout.width)
441    }
442}
443
444fn validate_window(
445    layout: &RasterLayout,
446    row_off: usize,
447    col_off: usize,
448    rows: usize,
449    cols: usize,
450) -> Result<Window> {
451    let row_end = row_off
452        .checked_add(rows)
453        .ok_or_else(|| Error::InvalidImageLayout("window row range overflows usize".into()))?;
454    let col_end = col_off
455        .checked_add(cols)
456        .ok_or_else(|| Error::InvalidImageLayout("window column range overflows usize".into()))?;
457    if row_end > layout.height || col_end > layout.width {
458        return Err(Error::InvalidImageLayout(format!(
459            "window [{row_off}..{row_end}, {col_off}..{col_end}) exceeds raster bounds {}x{}",
460            layout.height, layout.width
461        )));
462    }
463    Ok(Window {
464        row_off,
465        col_off,
466        rows,
467        cols,
468    })
469}
470
471fn parse_gdal_structural_metadata(source: &dyn TiffSource) -> Option<GdalStructuralMetadata> {
472    let available_len = usize::try_from(source.len().checked_sub(8)?).ok()?;
473    if available_len == 0 {
474        return None;
475    }
476
477    let probe_len = available_len.min(64);
478    let probe = source.read_exact_at(8, probe_len).ok()?;
479    let total_len = parse_gdal_structural_metadata_len(&probe)?;
480    if total_len == 0 || total_len > available_len {
481        return None;
482    }
483
484    let bytes = source.read_exact_at(8, total_len).ok()?;
485    GdalStructuralMetadata::from_prefix(&bytes)
486}
487
488fn parse_gdal_structural_metadata_len(bytes: &[u8]) -> Option<usize> {
489    let text = std::str::from_utf8(bytes).ok()?;
490    let newline_index = text.find('\n')?;
491    let header = &text[..newline_index];
492    let value = header.strip_prefix(GDAL_STRUCTURAL_METADATA_PREFIX)?;
493    let digits: String = value.chars().take_while(|ch| ch.is_ascii_digit()).collect();
494    if digits.is_empty() {
495        return None;
496    }
497    let payload_len: usize = digits.parse().ok()?;
498    newline_index.checked_add(1)?.checked_add(payload_len)
499}
500
501#[cfg(test)]
502mod tests {
503    use std::collections::BTreeMap;
504    use std::sync::atomic::{AtomicUsize, Ordering};
505    use std::sync::Arc;
506
507    use super::{
508        parse_gdal_structural_metadata, parse_gdal_structural_metadata_len, GdalStructuralMetadata,
509        TiffFile, GDAL_STRUCTURAL_METADATA_PREFIX,
510    };
511    use crate::source::{BytesSource, TiffSource};
512    use flate2::{write::ZlibEncoder, Compression as FlateCompression};
513
514    fn le_u16(value: u16) -> [u8; 2] {
515        value.to_le_bytes()
516    }
517
518    fn le_u32(value: u32) -> [u8; 4] {
519        value.to_le_bytes()
520    }
521
522    fn inline_short(value: u16) -> Vec<u8> {
523        let mut bytes = [0u8; 4];
524        bytes[..2].copy_from_slice(&le_u16(value));
525        bytes.to_vec()
526    }
527
528    fn build_stripped_tiff(
529        width: u32,
530        height: u32,
531        image_data: &[u8],
532        overrides: &[(u16, u16, u32, Vec<u8>)],
533    ) -> Vec<u8> {
534        let mut entries = BTreeMap::new();
535        entries.insert(256, (4, 1, le_u32(width).to_vec()));
536        entries.insert(257, (4, 1, le_u32(height).to_vec()));
537        entries.insert(258, (3, 1, [8, 0, 0, 0].to_vec()));
538        entries.insert(259, (3, 1, [1, 0, 0, 0].to_vec()));
539        entries.insert(273, (4, 1, Vec::new()));
540        entries.insert(277, (3, 1, [1, 0, 0, 0].to_vec()));
541        entries.insert(278, (4, 1, le_u32(height).to_vec()));
542        entries.insert(279, (4, 1, le_u32(image_data.len() as u32).to_vec()));
543        for &(tag, ty, count, ref value) in overrides {
544            entries.insert(tag, (ty, count, value.clone()));
545        }
546
547        let ifd_offset = 8u32;
548        let ifd_size = 2 + entries.len() * 12 + 4;
549        let mut next_data_offset = ifd_offset as usize + ifd_size;
550        let image_offset = next_data_offset as u32;
551        next_data_offset += image_data.len();
552
553        let mut data = Vec::with_capacity(next_data_offset);
554        data.extend_from_slice(b"II");
555        data.extend_from_slice(&le_u16(42));
556        data.extend_from_slice(&le_u32(ifd_offset));
557        data.extend_from_slice(&le_u16(entries.len() as u16));
558
559        let mut deferred = Vec::new();
560        for (tag, (ty, count, value)) in entries {
561            data.extend_from_slice(&le_u16(tag));
562            data.extend_from_slice(&le_u16(ty));
563            data.extend_from_slice(&le_u32(count));
564            if tag == 273 {
565                data.extend_from_slice(&le_u32(image_offset));
566            } else if value.len() <= 4 {
567                let mut inline = [0u8; 4];
568                inline[..value.len()].copy_from_slice(&value);
569                data.extend_from_slice(&inline);
570            } else {
571                let offset = next_data_offset as u32;
572                data.extend_from_slice(&le_u32(offset));
573                next_data_offset += value.len();
574                deferred.push(value);
575            }
576        }
577        data.extend_from_slice(&le_u32(0));
578        data.extend_from_slice(image_data);
579        for value in deferred {
580            data.extend_from_slice(&value);
581        }
582        data
583    }
584
585    #[allow(clippy::too_many_arguments)]
586    fn build_lerc2_header_v2(
587        width: u32,
588        height: u32,
589        valid_pixel_count: u32,
590        image_type: i32,
591        max_z_error: f64,
592        z_min: f64,
593        z_max: f64,
594        payload_len: usize,
595    ) -> Vec<u8> {
596        let blob_size = 58 + 4 + payload_len;
597        let mut bytes = Vec::with_capacity(blob_size);
598        bytes.extend_from_slice(b"Lerc2 ");
599        bytes.extend_from_slice(&2i32.to_le_bytes());
600        bytes.extend_from_slice(&height.to_le_bytes());
601        bytes.extend_from_slice(&width.to_le_bytes());
602        bytes.extend_from_slice(&valid_pixel_count.to_le_bytes());
603        bytes.extend_from_slice(&8i32.to_le_bytes());
604        bytes.extend_from_slice(&(blob_size as i32).to_le_bytes());
605        bytes.extend_from_slice(&image_type.to_le_bytes());
606        bytes.extend_from_slice(&max_z_error.to_le_bytes());
607        bytes.extend_from_slice(&z_min.to_le_bytes());
608        bytes.extend_from_slice(&z_max.to_le_bytes());
609        bytes
610    }
611
612    #[allow(clippy::too_many_arguments)]
613    fn build_lerc2_header_v4(
614        width: u32,
615        height: u32,
616        depth: u32,
617        valid_pixel_count: u32,
618        image_type: i32,
619        max_z_error: f64,
620        z_min: f64,
621        z_max: f64,
622        payload_len: usize,
623    ) -> Vec<u8> {
624        let blob_size = 66 + 4 + payload_len;
625        let mut bytes = Vec::with_capacity(blob_size);
626        bytes.extend_from_slice(b"Lerc2 ");
627        bytes.extend_from_slice(&4i32.to_le_bytes());
628        bytes.extend_from_slice(&0u32.to_le_bytes());
629        bytes.extend_from_slice(&height.to_le_bytes());
630        bytes.extend_from_slice(&width.to_le_bytes());
631        bytes.extend_from_slice(&depth.to_le_bytes());
632        bytes.extend_from_slice(&valid_pixel_count.to_le_bytes());
633        bytes.extend_from_slice(&8i32.to_le_bytes());
634        bytes.extend_from_slice(&(blob_size as i32).to_le_bytes());
635        bytes.extend_from_slice(&image_type.to_le_bytes());
636        bytes.extend_from_slice(&max_z_error.to_le_bytes());
637        bytes.extend_from_slice(&z_min.to_le_bytes());
638        bytes.extend_from_slice(&z_max.to_le_bytes());
639        bytes
640    }
641
642    fn finalize_lerc2_v4_with_checksum(mut bytes: Vec<u8>) -> Vec<u8> {
643        let blob_size = bytes.len() as i32;
644        bytes[34..38].copy_from_slice(&blob_size.to_le_bytes());
645        let checksum = fletcher32(&bytes[14..blob_size as usize]);
646        bytes[10..14].copy_from_slice(&checksum.to_le_bytes());
647        bytes
648    }
649
650    fn fletcher32(bytes: &[u8]) -> u32 {
651        let mut sum1 = 0xffffu32;
652        let mut sum2 = 0xffffu32;
653        let mut words = bytes.len() / 2;
654        let mut index = 0usize;
655
656        while words > 0 {
657            let chunk = words.min(359);
658            words -= chunk;
659            for _ in 0..chunk {
660                sum1 += (bytes[index] as u32) << 8;
661                index += 1;
662                sum2 += sum1 + bytes[index] as u32;
663                sum1 += bytes[index] as u32;
664                index += 1;
665            }
666            sum1 = (sum1 & 0xffff) + (sum1 >> 16);
667            sum2 = (sum2 & 0xffff) + (sum2 >> 16);
668        }
669
670        if bytes.len() & 1 != 0 {
671            sum1 += (bytes[index] as u32) << 8;
672            sum2 += sum1;
673        }
674
675        sum1 = (sum1 & 0xffff) + (sum1 >> 16);
676        sum2 = (sum2 & 0xffff) + (sum2 >> 16);
677        (sum2 << 16) | (sum1 & 0xffff)
678    }
679
680    fn encode_mask_rle(mask: &[u8]) -> Vec<u8> {
681        let bitset_len = mask.len().div_ceil(8);
682        let mut bitset = vec![0u8; bitset_len];
683        for (index, &value) in mask.iter().enumerate() {
684            if value != 0 {
685                bitset[index >> 3] |= 1 << (7 - (index & 7));
686            }
687        }
688
689        let mut encoded = Vec::with_capacity(bitset_len + 4);
690        encoded.extend_from_slice(&(bitset_len as i16).to_le_bytes());
691        encoded.extend_from_slice(&bitset);
692        encoded.extend_from_slice(&i16::MIN.to_le_bytes());
693        encoded
694    }
695
696    fn build_lerc_tiff(
697        width: u32,
698        height: u32,
699        image_data: &[u8],
700        bits_per_sample: u16,
701        sample_format: u16,
702        samples_per_pixel: u16,
703        lerc_parameters: Option<[u32; 2]>,
704    ) -> Vec<u8> {
705        let mut overrides = vec![
706            (258u16, 3u16, 1u32, inline_short(bits_per_sample)),
707            (259u16, 3u16, 1u32, inline_short(34887)),
708            (277u16, 3u16, 1u32, inline_short(samples_per_pixel)),
709            (279u16, 4u16, 1u32, le_u32(image_data.len() as u32).to_vec()),
710        ];
711        if sample_format != 1 {
712            overrides.push((339u16, 3u16, 1u32, inline_short(sample_format)));
713        }
714        if let Some([version, additional_compression]) = lerc_parameters {
715            overrides.push((
716                50674u16,
717                4u16,
718                2u32,
719                [version, additional_compression]
720                    .into_iter()
721                    .flat_map(le_u32)
722                    .collect(),
723            ));
724        }
725        build_stripped_tiff(width, height, image_data, &overrides)
726    }
727
728    fn build_tiled_tiff(
729        width: u32,
730        height: u32,
731        tile_width: u32,
732        tile_height: u32,
733        tiles: &[&[u8]],
734    ) -> Vec<u8> {
735        let mut entries = BTreeMap::new();
736        entries.insert(256, (4, 1, le_u32(width).to_vec()));
737        entries.insert(257, (4, 1, le_u32(height).to_vec()));
738        entries.insert(258, (3, 1, [8, 0, 0, 0].to_vec()));
739        entries.insert(259, (3, 1, [1, 0, 0, 0].to_vec()));
740        entries.insert(277, (3, 1, [1, 0, 0, 0].to_vec()));
741        entries.insert(322, (4, 1, le_u32(tile_width).to_vec()));
742        entries.insert(323, (4, 1, le_u32(tile_height).to_vec()));
743        entries.insert(
744            325,
745            (
746                4,
747                tiles.len() as u32,
748                tiles
749                    .iter()
750                    .flat_map(|tile| le_u32(tile.len() as u32))
751                    .collect(),
752            ),
753        );
754
755        let ifd_offset = 8u32;
756        let ifd_size = 2 + (entries.len() + 1) * 12 + 4;
757        let mut tile_data_offset = ifd_offset as usize + ifd_size;
758        let tile_offsets: Vec<u32> = tiles
759            .iter()
760            .map(|tile| {
761                let offset = tile_data_offset as u32;
762                tile_data_offset += tile.len();
763                offset
764            })
765            .collect();
766        entries.insert(
767            324,
768            (
769                4,
770                tile_offsets.len() as u32,
771                tile_offsets
772                    .iter()
773                    .flat_map(|offset| le_u32(*offset))
774                    .collect(),
775            ),
776        );
777
778        let mut next_data_offset = tile_data_offset;
779        let mut data = Vec::with_capacity(next_data_offset);
780        data.extend_from_slice(b"II");
781        data.extend_from_slice(&le_u16(42));
782        data.extend_from_slice(&le_u32(ifd_offset));
783        data.extend_from_slice(&le_u16(entries.len() as u16));
784
785        let mut deferred = Vec::new();
786        for (tag, (ty, count, value)) in entries {
787            data.extend_from_slice(&le_u16(tag));
788            data.extend_from_slice(&le_u16(ty));
789            data.extend_from_slice(&le_u32(count));
790            if value.len() <= 4 {
791                let mut inline = [0u8; 4];
792                inline[..value.len()].copy_from_slice(&value);
793                data.extend_from_slice(&inline);
794            } else {
795                let offset = next_data_offset as u32;
796                data.extend_from_slice(&le_u32(offset));
797                next_data_offset += value.len();
798                deferred.push(value);
799            }
800        }
801        data.extend_from_slice(&le_u32(0));
802        for tile in tiles {
803            data.extend_from_slice(tile);
804        }
805        for value in deferred {
806            data.extend_from_slice(&value);
807        }
808        data
809    }
810
811    fn build_multi_strip_tiff(width: u32, rows: &[&[u8]]) -> Vec<u8> {
812        let mut entries = BTreeMap::new();
813        entries.insert(256, (4, 1, le_u32(width).to_vec()));
814        entries.insert(257, (4, 1, le_u32(rows.len() as u32).to_vec()));
815        entries.insert(258, (3, 1, [8, 0, 0, 0].to_vec()));
816        entries.insert(259, (3, 1, [1, 0, 0, 0].to_vec()));
817        entries.insert(277, (3, 1, [1, 0, 0, 0].to_vec()));
818        entries.insert(278, (4, 1, le_u32(1).to_vec()));
819        entries.insert(
820            279,
821            (
822                4,
823                rows.len() as u32,
824                rows.iter()
825                    .flat_map(|row| le_u32(row.len() as u32))
826                    .collect(),
827            ),
828        );
829
830        let ifd_offset = 8u32;
831        let ifd_size = 2 + (entries.len() + 1) * 12 + 4;
832        let mut strip_data_offset = ifd_offset as usize + ifd_size;
833        let strip_offsets: Vec<u32> = rows
834            .iter()
835            .map(|row| {
836                let offset = strip_data_offset as u32;
837                strip_data_offset += row.len();
838                offset
839            })
840            .collect();
841        entries.insert(
842            273,
843            (
844                4,
845                strip_offsets.len() as u32,
846                strip_offsets
847                    .iter()
848                    .flat_map(|offset| le_u32(*offset))
849                    .collect(),
850            ),
851        );
852
853        let mut next_data_offset = strip_data_offset;
854        let mut data = Vec::with_capacity(next_data_offset);
855        data.extend_from_slice(b"II");
856        data.extend_from_slice(&le_u16(42));
857        data.extend_from_slice(&le_u32(ifd_offset));
858        data.extend_from_slice(&le_u16(entries.len() as u16));
859
860        let mut deferred = Vec::new();
861        for (tag, (ty, count, value)) in entries {
862            data.extend_from_slice(&le_u16(tag));
863            data.extend_from_slice(&le_u16(ty));
864            data.extend_from_slice(&le_u32(count));
865            if value.len() <= 4 {
866                let mut inline = [0u8; 4];
867                inline[..value.len()].copy_from_slice(&value);
868                data.extend_from_slice(&inline);
869            } else {
870                let offset = next_data_offset as u32;
871                data.extend_from_slice(&le_u32(offset));
872                next_data_offset += value.len();
873                deferred.push(value);
874            }
875        }
876        data.extend_from_slice(&le_u32(0));
877        for row in rows {
878            data.extend_from_slice(row);
879        }
880        for value in deferred {
881            data.extend_from_slice(&value);
882        }
883        data
884    }
885
886    struct CountingSource {
887        bytes: Vec<u8>,
888        reads: AtomicUsize,
889    }
890
891    impl CountingSource {
892        fn new(bytes: Vec<u8>) -> Self {
893            Self {
894                bytes,
895                reads: AtomicUsize::new(0),
896            }
897        }
898
899        fn reset_reads(&self) {
900            self.reads.store(0, Ordering::SeqCst);
901        }
902
903        fn reads(&self) -> usize {
904            self.reads.load(Ordering::SeqCst)
905        }
906    }
907
908    impl TiffSource for CountingSource {
909        fn len(&self) -> u64 {
910            self.bytes.len() as u64
911        }
912
913        fn read_exact_at(&self, offset: u64, len: usize) -> crate::error::Result<Vec<u8>> {
914            self.reads.fetch_add(1, Ordering::SeqCst);
915            let start =
916                usize::try_from(offset).map_err(|_| crate::error::Error::OffsetOutOfBounds {
917                    offset,
918                    length: len as u64,
919                    data_len: self.len(),
920                })?;
921            let end = start
922                .checked_add(len)
923                .ok_or(crate::error::Error::OffsetOutOfBounds {
924                    offset,
925                    length: len as u64,
926                    data_len: self.len(),
927                })?;
928            if end > self.bytes.len() {
929                return Err(crate::error::Error::OffsetOutOfBounds {
930                    offset,
931                    length: len as u64,
932                    data_len: self.len(),
933                });
934            }
935            Ok(self.bytes[start..end].to_vec())
936        }
937    }
938
939    #[test]
940    fn reads_stripped_u8_image() {
941        let data = build_stripped_tiff(2, 2, &[1, 2, 3, 4], &[]);
942        let file = TiffFile::from_bytes(data).unwrap();
943        let image = file.read_image::<u8>(0).unwrap();
944        assert_eq!(image.shape(), &[2, 2]);
945        let (values, offset) = image.into_raw_vec_and_offset();
946        assert_eq!(offset, Some(0));
947        assert_eq!(values, vec![1, 2, 3, 4]);
948    }
949
950    #[test]
951    fn reads_horizontal_predictor_u16_strip() {
952        let encoded = [1, 0, 1, 0, 2, 0];
953        let data = build_stripped_tiff(
954            3,
955            1,
956            &encoded,
957            &[
958                (258, 3, 1, [16, 0, 0, 0].to_vec()),
959                (317, 3, 1, [2, 0, 0, 0].to_vec()),
960            ],
961        );
962        let file = TiffFile::from_bytes(data).unwrap();
963        let image = file.read_image::<u16>(0).unwrap();
964        assert_eq!(image.shape(), &[1, 3]);
965        let (values, offset) = image.into_raw_vec_and_offset();
966        assert_eq!(offset, Some(0));
967        assert_eq!(values, vec![1, 2, 4]);
968    }
969
970    #[test]
971    fn reads_lerc_f32_strip() {
972        let mut blob = build_lerc2_header_v2(2, 2, 4, 6, 0.0, 1.0, 4.0, 1 + 16);
973        blob.extend_from_slice(&0u32.to_le_bytes());
974        blob.push(1);
975        for value in [1.0f32, 2.0, 3.0, 4.0] {
976            blob.extend_from_slice(&value.to_le_bytes());
977        }
978
979        let data = build_lerc_tiff(2, 2, &blob, 32, 3, 1, None);
980        let file = TiffFile::from_bytes(data).unwrap();
981        let image = file.read_image::<f32>(0).unwrap();
982        let (values, offset) = image.into_raw_vec_and_offset();
983        assert_eq!(offset, Some(0));
984        assert_eq!(values, vec![1.0, 2.0, 3.0, 4.0]);
985    }
986
987    #[test]
988    fn reads_lerc_masked_f32_strip_as_nan() {
989        let mask = [1u8, 0, 1, 1];
990        let encoded_mask = encode_mask_rle(&mask);
991        let mut blob =
992            build_lerc2_header_v2(2, 2, 3, 6, 0.0, 1.0, 4.0, encoded_mask.len() + 1 + 12);
993        blob.extend_from_slice(&(encoded_mask.len() as u32).to_le_bytes());
994        blob.extend_from_slice(&encoded_mask);
995        blob.push(1);
996        for value in [1.0f32, 3.0, 4.0] {
997            blob.extend_from_slice(&value.to_le_bytes());
998        }
999
1000        let data = build_lerc_tiff(2, 2, &blob, 32, 3, 1, None);
1001        let file = TiffFile::from_bytes(data).unwrap();
1002        let image = file.read_image::<f32>(0).unwrap();
1003        let (values, offset) = image.into_raw_vec_and_offset();
1004        assert_eq!(offset, Some(0));
1005        assert_eq!(values[0], 1.0);
1006        assert!(values[1].is_nan());
1007        assert_eq!(values[2], 3.0);
1008        assert_eq!(values[3], 4.0);
1009    }
1010
1011    #[test]
1012    fn reads_lerc_chunky_rgb_band_set_strip() {
1013        let mut red = build_lerc2_header_v2(2, 1, 2, 1, 0.0, 1.0, 1.0, 0);
1014        red.extend_from_slice(&0u32.to_le_bytes());
1015        let mut green = build_lerc2_header_v2(2, 1, 2, 1, 0.0, 2.0, 2.0, 0);
1016        green.extend_from_slice(&0u32.to_le_bytes());
1017        let mut blue = build_lerc2_header_v2(2, 1, 2, 1, 0.0, 3.0, 3.0, 0);
1018        blue.extend_from_slice(&0u32.to_le_bytes());
1019
1020        let mut blob = red;
1021        blob.extend_from_slice(&green);
1022        blob.extend_from_slice(&blue);
1023
1024        let data = build_lerc_tiff(2, 1, &blob, 8, 1, 3, None);
1025        let file = TiffFile::from_bytes(data).unwrap();
1026        let image = file.read_image::<u8>(0).unwrap();
1027        assert_eq!(image.shape(), &[1, 2, 3]);
1028        let (values, offset) = image.into_raw_vec_and_offset();
1029        assert_eq!(offset, Some(0));
1030        assert_eq!(values, vec![1, 2, 3, 1, 2, 3]);
1031    }
1032
1033    #[test]
1034    fn reads_lerc_chunky_rgb_depth_blob_strip() {
1035        let mut blob = build_lerc2_header_v4(2, 1, 3, 2, 1, 0.0, 1.0, 6.0, 6 + 6 + 1 + 6);
1036        blob.extend_from_slice(&0u32.to_le_bytes());
1037        for value in [1u8, 2, 3] {
1038            blob.extend_from_slice(&value.to_le_bytes());
1039        }
1040        for value in [4u8, 5, 6] {
1041            blob.extend_from_slice(&value.to_le_bytes());
1042        }
1043        blob.push(1);
1044        blob.extend_from_slice(&[1, 2, 3, 4, 5, 6]);
1045        let blob = finalize_lerc2_v4_with_checksum(blob);
1046
1047        let data = build_lerc_tiff(2, 1, &blob, 8, 1, 3, Some([4, 0]));
1048        let file = TiffFile::from_bytes(data).unwrap();
1049        let image = file.read_image::<u8>(0).unwrap();
1050        assert_eq!(image.shape(), &[1, 2, 3]);
1051        let (values, offset) = image.into_raw_vec_and_offset();
1052        assert_eq!(offset, Some(0));
1053        assert_eq!(values, vec![1, 2, 3, 4, 5, 6]);
1054    }
1055
1056    #[test]
1057    fn reads_lerc_deflate_f32_strip() {
1058        let mut blob = build_lerc2_header_v2(2, 2, 4, 6, 0.0, 1.0, 4.0, 1 + 16);
1059        blob.extend_from_slice(&0u32.to_le_bytes());
1060        blob.push(1);
1061        for value in [1.0f32, 2.0, 3.0, 4.0] {
1062            blob.extend_from_slice(&value.to_le_bytes());
1063        }
1064
1065        let mut encoder = ZlibEncoder::new(Vec::new(), FlateCompression::default());
1066        std::io::Write::write_all(&mut encoder, &blob).unwrap();
1067        let compressed = encoder.finish().unwrap();
1068
1069        let data = build_lerc_tiff(2, 2, &compressed, 32, 3, 1, Some([2, 1]));
1070        let file = TiffFile::from_bytes(data).unwrap();
1071        let image = file.read_image::<f32>(0).unwrap();
1072        let (values, offset) = image.into_raw_vec_and_offset();
1073        assert_eq!(offset, Some(0));
1074        assert_eq!(values, vec![1.0, 2.0, 3.0, 4.0]);
1075    }
1076
1077    #[cfg(feature = "zstd")]
1078    #[test]
1079    fn reads_lerc_zstd_f32_strip() {
1080        let mut blob = build_lerc2_header_v2(2, 2, 4, 6, 0.0, 1.0, 4.0, 1 + 16);
1081        blob.extend_from_slice(&0u32.to_le_bytes());
1082        blob.push(1);
1083        for value in [1.0f32, 2.0, 3.0, 4.0] {
1084            blob.extend_from_slice(&value.to_le_bytes());
1085        }
1086
1087        let compressed = zstd::stream::encode_all(std::io::Cursor::new(blob), 0).unwrap();
1088        let data = build_lerc_tiff(2, 2, &compressed, 32, 3, 1, Some([2, 2]));
1089        let file = TiffFile::from_bytes(data).unwrap();
1090        let image = file.read_image::<f32>(0).unwrap();
1091        let (values, offset) = image.into_raw_vec_and_offset();
1092        assert_eq!(offset, Some(0));
1093        assert_eq!(values, vec![1.0, 2.0, 3.0, 4.0]);
1094    }
1095
1096    #[test]
1097    fn reads_stripped_u8_window() {
1098        let data = build_multi_strip_tiff(
1099            4,
1100            &[
1101                &[1, 2, 3, 4],
1102                &[5, 6, 7, 8],
1103                &[9, 10, 11, 12],
1104                &[13, 14, 15, 16],
1105            ],
1106        );
1107        let file = TiffFile::from_bytes(data).unwrap();
1108        let window = file.read_window::<u8>(0, 1, 1, 2, 2).unwrap();
1109        assert_eq!(window.shape(), &[2, 2]);
1110        let (values, offset) = window.into_raw_vec_and_offset();
1111        assert_eq!(offset, Some(0));
1112        assert_eq!(values, vec![6, 7, 10, 11]);
1113    }
1114
1115    #[test]
1116    fn reads_tiled_u8_window() {
1117        let data = build_tiled_tiff(
1118            4,
1119            4,
1120            2,
1121            2,
1122            &[
1123                &[1, 2, 5, 6],
1124                &[3, 4, 7, 8],
1125                &[9, 10, 13, 14],
1126                &[11, 12, 15, 16],
1127            ],
1128        );
1129        let file = TiffFile::from_bytes(data).unwrap();
1130        let window = file.read_window::<u8>(0, 1, 1, 2, 2).unwrap();
1131        assert_eq!(window.shape(), &[2, 2]);
1132        let (values, offset) = window.into_raw_vec_and_offset();
1133        assert_eq!(offset, Some(0));
1134        assert_eq!(values, vec![6, 7, 10, 11]);
1135    }
1136
1137    #[test]
1138    fn windowed_tiled_reads_only_intersecting_blocks() {
1139        let data = build_tiled_tiff(
1140            4,
1141            4,
1142            2,
1143            2,
1144            &[
1145                &[1, 2, 5, 6],
1146                &[3, 4, 7, 8],
1147                &[9, 10, 13, 14],
1148                &[11, 12, 15, 16],
1149            ],
1150        );
1151        let source = Arc::new(CountingSource::new(data));
1152        let file = TiffFile::from_source(source.clone()).unwrap();
1153        source.reset_reads();
1154
1155        let window = file.read_window::<u8>(0, 0, 0, 2, 2).unwrap();
1156        let (values, offset) = window.into_raw_vec_and_offset();
1157        assert_eq!(offset, Some(0));
1158        assert_eq!(values, vec![1, 2, 5, 6]);
1159        assert_eq!(source.reads(), 1);
1160    }
1161
1162    #[test]
1163    fn unwraps_gdal_structural_metadata_block() {
1164        let metadata = GdalStructuralMetadata::from_prefix(
1165            b"GDAL_STRUCTURAL_METADATA_SIZE=000174 bytes\nBLOCK_LEADER=SIZE_AS_UINT4\nBLOCK_TRAILER=LAST_4_BYTES_REPEATED\n",
1166        )
1167        .unwrap();
1168
1169        let payload = [1u8, 2, 3, 4];
1170        let mut block = Vec::new();
1171        block.extend_from_slice(&(payload.len() as u32).to_le_bytes());
1172        block.extend_from_slice(&payload);
1173        block.extend_from_slice(&payload[payload.len() - 4..]);
1174
1175        let unwrapped = metadata
1176            .unwrap_block(&block, crate::ByteOrder::LittleEndian, 256)
1177            .unwrap();
1178        assert_eq!(unwrapped, payload);
1179    }
1180
1181    #[test]
1182    fn rejects_gdal_structural_metadata_trailer_mismatch() {
1183        let metadata = GdalStructuralMetadata::from_prefix(
1184            b"GDAL_STRUCTURAL_METADATA_SIZE=000174 bytes\nBLOCK_LEADER=SIZE_AS_UINT4\nBLOCK_TRAILER=LAST_4_BYTES_REPEATED\n",
1185        )
1186        .unwrap();
1187
1188        let block = [
1189            4u8, 0, 0, 0, //
1190            1, 2, 3, 4, //
1191            4, 3, 2, 1,
1192        ];
1193
1194        let error = metadata
1195            .unwrap_block(&block, crate::ByteOrder::LittleEndian, 512)
1196            .unwrap_err();
1197        assert!(error.to_string().contains("GDAL block trailer mismatch"));
1198    }
1199
1200    #[test]
1201    fn parses_gdal_structural_metadata_before_binary_prefix_data() {
1202        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";
1203        let prefix = format!(
1204            "{GDAL_STRUCTURAL_METADATA_PREFIX}{:06} bytes\n{rest}",
1205            rest.len()
1206        );
1207
1208        let mut bytes = vec![0u8; 8];
1209        bytes.extend_from_slice(prefix.as_bytes());
1210        bytes.extend_from_slice(&[0xff, 0x00, 0x80, 0x7f]);
1211
1212        let source = BytesSource::new(bytes);
1213        let metadata = parse_gdal_structural_metadata(&source).unwrap();
1214        assert!(metadata.block_leader_size_as_u32);
1215        assert!(metadata.block_trailer_repeats_last_4_bytes);
1216    }
1217
1218    #[test]
1219    fn parses_gdal_structural_metadata_declared_length_as_header_plus_payload() {
1220        let rest = "LAYOUT=IFDS_BEFORE_DATA\nBLOCK_ORDER=ROW_MAJOR\n";
1221        let prefix = format!(
1222            "{GDAL_STRUCTURAL_METADATA_PREFIX}{:06} bytes\n{rest}",
1223            rest.len()
1224        );
1225        assert_eq!(
1226            parse_gdal_structural_metadata_len(prefix.as_bytes()),
1227            Some(prefix.len())
1228        );
1229    }
1230
1231    #[test]
1232    fn leaves_payload_only_gdal_block_unchanged() {
1233        let metadata = GdalStructuralMetadata {
1234            block_leader_size_as_u32: true,
1235            block_trailer_repeats_last_4_bytes: true,
1236        };
1237        let payload = [0x80u8, 0x1a, 0xcf, 0x68, 0x43, 0x9a, 0x11, 0x08];
1238        let unwrapped = metadata
1239            .unwrap_block(&payload, crate::ByteOrder::LittleEndian, 570)
1240            .unwrap();
1241        assert_eq!(unwrapped, payload);
1242    }
1243
1244    #[test]
1245    fn rejects_zero_rows_per_strip_without_panicking() {
1246        let data = build_stripped_tiff(2, 2, &[1, 2, 3, 4], &[(278, 4, 1, le_u32(0).to_vec())]);
1247        let file = TiffFile::from_bytes(data).unwrap();
1248        let error = file.read_image_bytes(0).unwrap_err();
1249        assert!(error.to_string().contains("RowsPerStrip"));
1250    }
1251}