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