1mod 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#[derive(Debug, Clone, Copy)]
59pub struct OpenOptions {
60 pub block_cache_bytes: usize,
62 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
75pub 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
246impl TiffFile {
249 pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
251 Self::open_with_options(path, OpenOptions::default())
252 }
253
254 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 pub fn from_bytes(data: Vec<u8>) -> Result<Self> {
262 Self::from_bytes_with_options(data, OpenOptions::default())
263 }
264
265 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 pub fn from_source(source: SharedSource) -> Result<Self> {
273 Self::from_source_with_options(source, OpenOptions::default())
274 }
275
276 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 pub fn byte_order(&self) -> ByteOrder {
297 self.header.byte_order
298 }
299
300 pub fn is_bigtiff(&self) -> bool {
302 self.header.is_bigtiff()
303 }
304
305 pub fn ifd_count(&self) -> usize {
307 self.ifds.len()
308 }
309
310 pub fn ifd(&self, index: usize) -> Result<&Ifd> {
312 self.ifds.get(index).ok_or(Error::IfdNotFound(index))
313 }
314
315 pub fn ifds(&self) -> &[Ifd] {
317 &self.ifds
318 }
319
320 pub fn raw_bytes(&self) -> Option<&[u8]> {
322 self.source.as_slice()
323 }
324
325 pub fn source(&self) -> &dyn TiffSource {
327 self.source.as_ref()
328 }
329
330 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 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 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 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, 1, 2, 3, 4, 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}