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