1mod block_decode;
31pub mod cache;
32pub mod error;
33pub mod filters;
34pub mod header;
35pub mod ifd;
36pub mod io;
37mod pixel;
38pub mod source;
39pub mod strip;
40pub mod tag;
41pub mod tile;
42
43use std::path::Path;
44use std::sync::Arc;
45
46use cache::BlockCache;
47use error::{Error, Result};
48use ndarray::{ArrayD, IxDyn};
49use source::{BytesSource, MmapSource, SharedSource, TiffSource};
50
51pub use error::Error as TiffError;
52pub use header::ByteOrder;
53pub use ifd::{Ifd, ParseBudgets, RasterLayout};
54pub use tag::{Tag, TagValue};
55pub use tiff_core::constants;
56pub use tiff_core::sample::TiffSample;
57pub use tiff_core::TagType;
58pub use tiff_core::{
59 ColorMap, ColorModel, ExtraSample, InkSet, PhotometricInterpretation, YCbCrPositioning,
60};
61
62#[derive(Debug, Clone, Copy)]
64pub struct OpenOptions {
65 pub block_cache_bytes: usize,
67 pub block_cache_slots: usize,
69 pub parse_budgets: ParseBudgets,
71}
72
73impl Default for OpenOptions {
74 fn default() -> Self {
75 Self {
76 block_cache_bytes: 64 * 1024 * 1024,
77 block_cache_slots: 257,
78 parse_budgets: ParseBudgets::default(),
79 }
80 }
81}
82
83pub struct TiffFile {
85 source: SharedSource,
86 header: header::TiffHeader,
87 ifds: Vec<ifd::Ifd>,
88 parse_budgets: ParseBudgets,
89 block_cache: Arc<BlockCache>,
90 gdal_structural_metadata: Option<GdalStructuralMetadata>,
91}
92
93#[derive(Debug, Clone, Copy)]
94pub(crate) struct GdalStructuralMetadata {
95 block_leader_size_as_u32: bool,
96 block_trailer_repeats_last_4_bytes: bool,
97}
98
99#[derive(Debug, Clone, Copy, PartialEq, Eq)]
100pub(crate) struct Window {
101 pub row_off: usize,
102 pub col_off: usize,
103 pub rows: usize,
104 pub cols: usize,
105}
106
107impl Window {
108 pub(crate) fn is_empty(self) -> bool {
109 self.rows == 0 || self.cols == 0
110 }
111
112 pub(crate) fn row_end(self) -> usize {
113 self.row_off + self.rows
114 }
115
116 pub(crate) fn col_end(self) -> usize {
117 self.col_off + self.cols
118 }
119
120 pub(crate) fn output_len(self, layout: &RasterLayout) -> Result<usize> {
121 self.cols
122 .checked_mul(self.rows)
123 .and_then(|pixels| pixels.checked_mul(layout.pixel_stride_bytes()))
124 .ok_or_else(|| Error::InvalidImageLayout("window size overflows usize".into()))
125 }
126
127 pub(crate) fn band_output_len(self, layout: &RasterLayout) -> Result<usize> {
128 self.cols
129 .checked_mul(self.rows)
130 .and_then(|pixels| pixels.checked_mul(layout.bytes_per_sample))
131 .ok_or_else(|| Error::InvalidImageLayout("window band size overflows usize".into()))
132 }
133}
134
135impl GdalStructuralMetadata {
136 fn from_prefix(bytes: &[u8]) -> Option<Self> {
137 let text = std::str::from_utf8(bytes).ok()?;
138 if !text.contains("GDAL_STRUCTURAL_METADATA_SIZE=") {
139 return None;
140 }
141
142 Some(Self {
143 block_leader_size_as_u32: text.contains("BLOCK_LEADER=SIZE_AS_UINT4"),
144 block_trailer_repeats_last_4_bytes: text
145 .contains("BLOCK_TRAILER=LAST_4_BYTES_REPEATED"),
146 })
147 }
148
149 pub(crate) fn unwrap_block<'a>(
150 &self,
151 raw: &'a [u8],
152 byte_order: ByteOrder,
153 offset: u64,
154 ) -> Result<&'a [u8]> {
155 if self.block_leader_size_as_u32 {
156 if raw.len() < 4 {
157 return Ok(raw);
158 }
159 let declared_len = match byte_order {
160 ByteOrder::LittleEndian => u32::from_le_bytes(raw[..4].try_into().unwrap()),
161 ByteOrder::BigEndian => u32::from_be_bytes(raw[..4].try_into().unwrap()),
162 } as usize;
163 if let Some(payload_end) = 4usize.checked_add(declared_len) {
164 if payload_end <= raw.len() {
165 if self.block_trailer_repeats_last_4_bytes {
166 let trailer_end = payload_end.checked_add(4).ok_or_else(|| {
167 Error::InvalidImageLayout("GDAL block trailer overflows usize".into())
168 })?;
169 if trailer_end <= raw.len() {
170 let expected = &raw[payload_end - 4..payload_end];
171 let trailer = &raw[payload_end..trailer_end];
172 if expected != trailer {
173 return Err(Error::InvalidImageLayout(format!(
174 "GDAL block trailer mismatch at offset {offset}"
175 )));
176 }
177 }
178 }
179 return Ok(&raw[4..payload_end]);
180 }
181 }
182 }
183
184 if self.block_trailer_repeats_last_4_bytes && raw.len() >= 8 {
185 let split = raw.len() - 4;
186 if raw[split - 4..split] == raw[split..] {
187 return Ok(&raw[..split]);
188 }
189 }
190
191 Ok(raw)
192 }
193}
194
195pub(crate) fn read_block_payload(
196 source: &dyn TiffSource,
197 offset: u64,
198 byte_count: u64,
199 byte_count_limit: usize,
200 index: usize,
201) -> Result<Vec<u8>> {
202 let len = validate_block_byte_count(index, byte_count, byte_count_limit)?;
203 if let Some(bytes) = source.as_slice() {
204 let start = usize::try_from(offset).map_err(|_| Error::OffsetOutOfBounds {
205 offset,
206 length: byte_count,
207 data_len: bytes.len() as u64,
208 })?;
209 let end = start.checked_add(len).ok_or(Error::OffsetOutOfBounds {
210 offset,
211 length: byte_count,
212 data_len: bytes.len() as u64,
213 })?;
214 if end > bytes.len() {
215 return Err(Error::OffsetOutOfBounds {
216 offset,
217 length: byte_count,
218 data_len: bytes.len() as u64,
219 });
220 }
221 Ok(bytes[start..end].to_vec())
222 } else {
223 source.read_exact_at(offset, len)
224 }
225}
226
227pub(crate) fn read_gdal_block_payload(
228 source: &dyn TiffSource,
229 metadata: &GdalStructuralMetadata,
230 byte_order: ByteOrder,
231 offset: u64,
232 byte_count: u64,
233 byte_count_limit: usize,
234 index: usize,
235) -> Result<Vec<u8>> {
236 let payload_len = validate_block_byte_count(index, byte_count, byte_count_limit)?;
237 let wrapped_extra = 4u64
238 .checked_add(if metadata.block_trailer_repeats_last_4_bytes {
239 4
240 } else {
241 0
242 })
243 .ok_or_else(|| Error::InvalidImageLayout("GDAL block wrapper overflows u64".into()))?;
244
245 let mut candidates = Vec::with_capacity(2);
246 if metadata.block_leader_size_as_u32 && offset >= 4 {
247 candidates.push((
248 offset - 4,
249 byte_count.checked_add(wrapped_extra).ok_or_else(|| {
250 Error::InvalidImageLayout("GDAL wrapped block length overflows u64".into())
251 })?,
252 ));
253 }
254 candidates.push((offset, byte_count));
255
256 let mut fallback: Option<Result<Vec<u8>>> = None;
257 for (candidate_offset, candidate_len) in candidates {
258 let len = usize::try_from(candidate_len).map_err(|_| Error::OffsetOutOfBounds {
259 offset: candidate_offset,
260 length: candidate_len,
261 data_len: source.len(),
262 })?;
263 let raw = match source.read_exact_at(candidate_offset, len) {
264 Ok(raw) => raw,
265 Err(err) => {
266 if fallback.is_none() {
267 fallback = Some(Err(err));
268 }
269 continue;
270 }
271 };
272 match metadata.unwrap_block(&raw, byte_order, candidate_offset) {
273 Ok(payload) => {
274 if payload.len() > byte_count_limit {
275 let err =
276 block_byte_count_too_large(index, payload.len() as u64, byte_count_limit);
277 if candidate_offset == offset {
278 return Err(err);
279 }
280 if fallback.is_none() {
281 fallback = Some(Err(err));
282 }
283 continue;
284 }
285 if candidate_offset != offset && payload.len() == payload_len {
286 return Ok(payload.to_vec());
287 }
288 fallback = Some(Ok(payload.to_vec()));
289 }
290 Err(err) => {
291 if fallback.is_none() {
292 fallback = Some(Err(err));
293 }
294 }
295 }
296 }
297
298 match fallback {
299 Some(result) => result,
300 None => Ok(Vec::new()),
301 }
302}
303
304fn validate_block_byte_count(
305 index: usize,
306 byte_count: u64,
307 byte_count_limit: usize,
308) -> Result<usize> {
309 let len = usize::try_from(byte_count)
310 .map_err(|_| block_byte_count_too_large(index, byte_count, byte_count_limit))?;
311 if len > byte_count_limit {
312 return Err(block_byte_count_too_large(
313 index,
314 byte_count,
315 byte_count_limit,
316 ));
317 }
318 Ok(len)
319}
320
321fn block_byte_count_too_large(index: usize, byte_count: u64, byte_count_limit: usize) -> Error {
322 Error::DecompressionFailed {
323 index,
324 reason: format!(
325 "encoded block byte count {byte_count} exceeds TIFF block read budget {byte_count_limit}"
326 ),
327 }
328}
329
330const GDAL_STRUCTURAL_METADATA_PREFIX: &str = "GDAL_STRUCTURAL_METADATA_SIZE=";
331
332impl TiffFile {
335 pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
337 Self::open_with_options(path, OpenOptions::default())
338 }
339
340 pub fn open_with_options<P: AsRef<Path>>(path: P, options: OpenOptions) -> Result<Self> {
342 let source: SharedSource = Arc::new(MmapSource::open(path.as_ref())?);
343 Self::from_source_with_options(source, options)
344 }
345
346 pub fn from_bytes(data: Vec<u8>) -> Result<Self> {
348 Self::from_bytes_with_options(data, OpenOptions::default())
349 }
350
351 pub fn from_bytes_with_options(data: Vec<u8>, options: OpenOptions) -> Result<Self> {
353 let source: SharedSource = Arc::new(BytesSource::new(data));
354 Self::from_source_with_options(source, options)
355 }
356
357 pub fn from_source(source: SharedSource) -> Result<Self> {
359 Self::from_source_with_options(source, OpenOptions::default())
360 }
361
362 pub fn from_source_with_options(source: SharedSource, options: OpenOptions) -> Result<Self> {
364 let header_len = usize::try_from(source.len().min(16)).unwrap_or(16);
365 let header_bytes = source.read_exact_at(0, header_len)?;
366 let header = header::TiffHeader::parse(&header_bytes)?;
367 let gdal_structural_metadata = parse_gdal_structural_metadata(source.as_ref());
368 let ifds =
369 ifd::parse_ifd_chain_with_budgets(source.as_ref(), &header, options.parse_budgets)?;
370 Ok(Self {
371 source,
372 header,
373 ifds,
374 parse_budgets: options.parse_budgets,
375 block_cache: Arc::new(BlockCache::new(
376 options.block_cache_bytes,
377 options.block_cache_slots,
378 )),
379 gdal_structural_metadata,
380 })
381 }
382
383 pub fn byte_order(&self) -> ByteOrder {
385 self.header.byte_order
386 }
387
388 pub fn is_bigtiff(&self) -> bool {
390 self.header.is_bigtiff()
391 }
392
393 pub fn ifd_count(&self) -> usize {
395 self.ifds.len()
396 }
397
398 pub fn ifd(&self, index: usize) -> Result<&Ifd> {
400 self.ifds.get(index).ok_or(Error::IfdNotFound(index))
401 }
402
403 pub fn ifds(&self) -> &[Ifd] {
405 &self.ifds
406 }
407
408 pub fn raw_bytes(&self) -> Option<&[u8]> {
410 self.source.as_slice()
411 }
412
413 pub fn source(&self) -> &dyn TiffSource {
415 self.source.as_ref()
416 }
417
418 pub fn read_ifd_at_offset(&self, offset: u64) -> Result<Ifd> {
420 ifd::parse_ifd_at_with_budgets(
421 self.source.as_ref(),
422 &self.header,
423 offset,
424 self.parse_budgets,
425 )
426 }
427
428 pub fn read_image_bytes(&self, ifd_index: usize) -> Result<Vec<u8>> {
430 let ifd = self.ifd(ifd_index)?;
431 self.read_image_bytes_from_ifd(ifd)
432 }
433
434 pub fn read_image_bytes_from_ifd(&self, ifd: &Ifd) -> Result<Vec<u8>> {
436 self.read_image_sample_bytes_from_ifd(ifd)
437 }
438
439 pub fn read_decoded_image_bytes(&self, ifd_index: usize) -> Result<Vec<u8>> {
441 let ifd = self.ifd(ifd_index)?;
442 self.read_decoded_image_bytes_from_ifd(ifd)
443 }
444
445 pub fn read_decoded_image_bytes_from_ifd(&self, ifd: &Ifd) -> Result<Vec<u8>> {
448 let layout = ifd.decoded_raster_layout()?;
449 self.decode_window_pixel_bytes(
450 ifd,
451 Window {
452 row_off: 0,
453 col_off: 0,
454 rows: layout.height,
455 cols: layout.width,
456 },
457 )
458 }
459
460 pub fn read_image_sample_bytes(&self, ifd_index: usize) -> Result<Vec<u8>> {
464 let ifd = self.ifd(ifd_index)?;
465 self.read_image_sample_bytes_from_ifd(ifd)
466 }
467
468 pub fn read_image_sample_bytes_from_ifd(&self, ifd: &Ifd) -> Result<Vec<u8>> {
473 let layout = ifd.raster_layout()?;
474 self.decode_window_sample_bytes(
475 ifd,
476 Window {
477 row_off: 0,
478 col_off: 0,
479 rows: layout.height,
480 cols: layout.width,
481 },
482 )
483 }
484
485 pub fn read_window_bytes(
488 &self,
489 ifd_index: usize,
490 row_off: usize,
491 col_off: usize,
492 rows: usize,
493 cols: usize,
494 ) -> Result<Vec<u8>> {
495 let ifd = self.ifd(ifd_index)?;
496 self.read_window_bytes_from_ifd(ifd, row_off, col_off, rows, cols)
497 }
498
499 pub fn read_decoded_window_bytes(
502 &self,
503 ifd_index: usize,
504 row_off: usize,
505 col_off: usize,
506 rows: usize,
507 cols: usize,
508 ) -> Result<Vec<u8>> {
509 let ifd = self.ifd(ifd_index)?;
510 self.read_decoded_window_bytes_from_ifd(ifd, row_off, col_off, rows, cols)
511 }
512
513 pub fn read_window_sample_bytes(
518 &self,
519 ifd_index: usize,
520 row_off: usize,
521 col_off: usize,
522 rows: usize,
523 cols: usize,
524 ) -> Result<Vec<u8>> {
525 let ifd = self.ifd(ifd_index)?;
526 self.read_window_sample_bytes_from_ifd(ifd, row_off, col_off, rows, cols)
527 }
528
529 pub fn read_window_bytes_from_ifd(
532 &self,
533 ifd: &Ifd,
534 row_off: usize,
535 col_off: usize,
536 rows: usize,
537 cols: usize,
538 ) -> Result<Vec<u8>> {
539 self.read_window_sample_bytes_from_ifd(ifd, row_off, col_off, rows, cols)
540 }
541
542 pub fn read_decoded_window_bytes_from_ifd(
545 &self,
546 ifd: &Ifd,
547 row_off: usize,
548 col_off: usize,
549 rows: usize,
550 cols: usize,
551 ) -> Result<Vec<u8>> {
552 let layout = ifd.decoded_raster_layout()?;
553 let window = validate_window(&layout, row_off, col_off, rows, cols)?;
554 self.decode_window_pixel_bytes(ifd, window)
555 }
556
557 pub fn read_window_sample_bytes_from_ifd(
562 &self,
563 ifd: &Ifd,
564 row_off: usize,
565 col_off: usize,
566 rows: usize,
567 cols: usize,
568 ) -> Result<Vec<u8>> {
569 let layout = ifd.raster_layout()?;
570 let window = validate_window(&layout, row_off, col_off, rows, cols)?;
571 self.decode_window_sample_bytes(ifd, window)
572 }
573
574 pub fn read_band_bytes(&self, ifd_index: usize, band_index: usize) -> Result<Vec<u8>> {
576 let ifd = self.ifd(ifd_index)?;
577 self.read_band_bytes_from_ifd(ifd, band_index)
578 }
579
580 pub fn read_band_bytes_from_ifd(&self, ifd: &Ifd, band_index: usize) -> Result<Vec<u8>> {
583 let layout = ifd.raster_layout()?;
584 self.read_band_window_bytes_from_ifd(ifd, band_index, 0, 0, layout.height, layout.width)
585 }
586
587 pub fn read_band_window_bytes(
590 &self,
591 ifd_index: usize,
592 band_index: usize,
593 row_off: usize,
594 col_off: usize,
595 rows: usize,
596 cols: usize,
597 ) -> Result<Vec<u8>> {
598 let ifd = self.ifd(ifd_index)?;
599 self.read_band_window_bytes_from_ifd(ifd, band_index, row_off, col_off, rows, cols)
600 }
601
602 pub fn read_band_window_bytes_from_ifd(
605 &self,
606 ifd: &Ifd,
607 band_index: usize,
608 row_off: usize,
609 col_off: usize,
610 rows: usize,
611 cols: usize,
612 ) -> Result<Vec<u8>> {
613 let layout = ifd.raster_layout()?;
614 validate_band_index(&layout, band_index)?;
615 let window = validate_window(&layout, row_off, col_off, rows, cols)?;
616 self.decode_window_sample_band_bytes(ifd, window, band_index)
617 }
618
619 fn decode_window_sample_bytes(&self, ifd: &Ifd, window: Window) -> Result<Vec<u8>> {
620 if window.is_empty() {
621 return Ok(Vec::new());
622 }
623
624 if ifd.is_tiled() {
625 tile::read_window(
626 self.source.as_ref(),
627 ifd,
628 self.byte_order(),
629 &self.block_cache,
630 window,
631 self.gdal_structural_metadata.as_ref(),
632 )
633 } else {
634 strip::read_window(
635 self.source.as_ref(),
636 ifd,
637 self.byte_order(),
638 &self.block_cache,
639 window,
640 self.gdal_structural_metadata.as_ref(),
641 )
642 }
643 }
644
645 fn decode_window_sample_band_bytes(
646 &self,
647 ifd: &Ifd,
648 window: Window,
649 band_index: usize,
650 ) -> Result<Vec<u8>> {
651 if window.is_empty() {
652 return Ok(Vec::new());
653 }
654
655 let layout = ifd.raster_layout()?;
656 validate_band_index(&layout, band_index)?;
657 if ifd.is_tiled() {
658 tile::read_window_band(
659 self.source.as_ref(),
660 ifd,
661 self.byte_order(),
662 &self.block_cache,
663 window,
664 band_index,
665 self.gdal_structural_metadata.as_ref(),
666 )
667 } else {
668 strip::read_window_band(
669 self.source.as_ref(),
670 ifd,
671 self.byte_order(),
672 &self.block_cache,
673 window,
674 band_index,
675 self.gdal_structural_metadata.as_ref(),
676 )
677 }
678 }
679
680 fn decode_window_pixel_bytes(&self, ifd: &Ifd, window: Window) -> Result<Vec<u8>> {
681 let storage_layout = ifd.raster_layout()?;
682 let sample_bytes = self.decode_window_sample_bytes(ifd, window)?;
683 let (_, pixels) = pixel::decode_pixels(
684 ifd,
685 &storage_layout,
686 window.cols,
687 window.rows,
688 &sample_bytes,
689 )?;
690 Ok(pixels)
691 }
692
693 pub fn read_window<T: TiffSample>(
698 &self,
699 ifd_index: usize,
700 row_off: usize,
701 col_off: usize,
702 rows: usize,
703 cols: usize,
704 ) -> Result<ArrayD<T>> {
705 let ifd = self.ifd(ifd_index)?;
706 self.read_window_from_ifd(ifd, row_off, col_off, rows, cols)
707 }
708
709 pub fn read_window_from_ifd<T: TiffSample>(
712 &self,
713 ifd: &Ifd,
714 row_off: usize,
715 col_off: usize,
716 rows: usize,
717 cols: usize,
718 ) -> Result<ArrayD<T>> {
719 self.read_window_samples_from_ifd(ifd, row_off, col_off, rows, cols)
720 }
721
722 pub fn read_decoded_window<T: TiffSample>(
727 &self,
728 ifd_index: usize,
729 row_off: usize,
730 col_off: usize,
731 rows: usize,
732 cols: usize,
733 ) -> Result<ArrayD<T>> {
734 let ifd = self.ifd(ifd_index)?;
735 self.read_decoded_window_from_ifd(ifd, row_off, col_off, rows, cols)
736 }
737
738 pub fn read_decoded_window_from_ifd<T: TiffSample>(
741 &self,
742 ifd: &Ifd,
743 row_off: usize,
744 col_off: usize,
745 rows: usize,
746 cols: usize,
747 ) -> Result<ArrayD<T>> {
748 let layout = ifd.decoded_raster_layout()?;
749 let window = validate_window(&layout, row_off, col_off, rows, cols)?;
750 if !T::matches_layout(&layout) {
751 return Err(Error::TypeMismatch {
752 expected: T::type_name(),
753 actual: format!(
754 "sample_format={} bits_per_sample={}",
755 layout.sample_format, layout.bits_per_sample
756 ),
757 });
758 }
759
760 let decoded = self.decode_window_pixel_bytes(ifd, window)?;
761 let values = T::decode_many(&decoded);
762 let shape = if layout.samples_per_pixel == 1 {
763 vec![window.rows, window.cols]
764 } else {
765 vec![window.rows, window.cols, layout.samples_per_pixel]
766 };
767 ArrayD::from_shape_vec(IxDyn(&shape), values).map_err(|e| {
768 Error::InvalidImageLayout(format!("failed to build ndarray from decoded raster: {e}"))
769 })
770 }
771
772 pub fn read_window_samples<T: TiffSample>(
777 &self,
778 ifd_index: usize,
779 row_off: usize,
780 col_off: usize,
781 rows: usize,
782 cols: usize,
783 ) -> Result<ArrayD<T>> {
784 let ifd = self.ifd(ifd_index)?;
785 self.read_window_samples_from_ifd(ifd, row_off, col_off, rows, cols)
786 }
787
788 pub fn read_window_samples_from_ifd<T: TiffSample>(
791 &self,
792 ifd: &Ifd,
793 row_off: usize,
794 col_off: usize,
795 rows: usize,
796 cols: usize,
797 ) -> Result<ArrayD<T>> {
798 let layout = ifd.raster_layout()?;
799 let window = validate_window(&layout, row_off, col_off, rows, cols)?;
800 if !T::matches_layout(&layout) {
801 return Err(Error::TypeMismatch {
802 expected: T::type_name(),
803 actual: format!(
804 "sample_format={} bits_per_sample={}",
805 layout.sample_format, layout.bits_per_sample
806 ),
807 });
808 }
809
810 let decoded = self.decode_window_sample_bytes(ifd, window)?;
811 let values = T::decode_many(&decoded);
812 let shape = if layout.samples_per_pixel == 1 {
813 vec![window.rows, window.cols]
814 } else {
815 vec![window.rows, window.cols, layout.samples_per_pixel]
816 };
817 ArrayD::from_shape_vec(IxDyn(&shape), values).map_err(|e| {
818 Error::InvalidImageLayout(format!("failed to build ndarray from storage raster: {e}"))
819 })
820 }
821
822 pub fn read_band<T: TiffSample>(
824 &self,
825 ifd_index: usize,
826 band_index: usize,
827 ) -> Result<ArrayD<T>> {
828 let ifd = self.ifd(ifd_index)?;
829 self.read_band_from_ifd(ifd, band_index)
830 }
831
832 pub fn read_band_from_ifd<T: TiffSample>(
835 &self,
836 ifd: &Ifd,
837 band_index: usize,
838 ) -> Result<ArrayD<T>> {
839 let layout = ifd.raster_layout()?;
840 self.read_band_window_from_ifd(ifd, band_index, 0, 0, layout.height, layout.width)
841 }
842
843 pub fn read_band_window<T: TiffSample>(
846 &self,
847 ifd_index: usize,
848 band_index: usize,
849 row_off: usize,
850 col_off: usize,
851 rows: usize,
852 cols: usize,
853 ) -> Result<ArrayD<T>> {
854 let ifd = self.ifd(ifd_index)?;
855 self.read_band_window_from_ifd(ifd, band_index, row_off, col_off, rows, cols)
856 }
857
858 pub fn read_band_window_from_ifd<T: TiffSample>(
861 &self,
862 ifd: &Ifd,
863 band_index: usize,
864 row_off: usize,
865 col_off: usize,
866 rows: usize,
867 cols: usize,
868 ) -> Result<ArrayD<T>> {
869 let layout = ifd.raster_layout()?;
870 validate_band_index(&layout, band_index)?;
871 let window = validate_window(&layout, row_off, col_off, rows, cols)?;
872 if !T::matches_layout(&layout) {
873 return Err(Error::TypeMismatch {
874 expected: T::type_name(),
875 actual: format!(
876 "sample_format={} bits_per_sample={}",
877 layout.sample_format, layout.bits_per_sample
878 ),
879 });
880 }
881
882 let decoded = self.decode_window_sample_band_bytes(ifd, window, band_index)?;
883 let values = T::decode_many(&decoded);
884 ArrayD::from_shape_vec(IxDyn(&[window.rows, window.cols]), values).map_err(|e| {
885 Error::InvalidImageLayout(format!("failed to build ndarray from band raster: {e}"))
886 })
887 }
888
889 pub fn read_image<T: TiffSample>(&self, ifd_index: usize) -> Result<ArrayD<T>> {
894 let ifd = self.ifd(ifd_index)?;
895 self.read_image_from_ifd(ifd)
896 }
897
898 pub fn read_image_from_ifd<T: TiffSample>(&self, ifd: &Ifd) -> Result<ArrayD<T>> {
900 self.read_image_samples_from_ifd(ifd)
901 }
902
903 pub fn read_decoded_image<T: TiffSample>(&self, ifd_index: usize) -> Result<ArrayD<T>> {
909 let ifd = self.ifd(ifd_index)?;
910 self.read_decoded_image_from_ifd(ifd)
911 }
912
913 pub fn read_decoded_image_from_ifd<T: TiffSample>(&self, ifd: &Ifd) -> Result<ArrayD<T>> {
915 let layout = ifd.decoded_raster_layout()?;
916 if !T::matches_layout(&layout) {
917 return Err(Error::TypeMismatch {
918 expected: T::type_name(),
919 actual: format!(
920 "sample_format={} bits_per_sample={}",
921 layout.sample_format, layout.bits_per_sample
922 ),
923 });
924 }
925
926 self.read_decoded_window_from_ifd(ifd, 0, 0, layout.height, layout.width)
927 }
928
929 pub fn read_image_samples<T: TiffSample>(&self, ifd_index: usize) -> Result<ArrayD<T>> {
933 let ifd = self.ifd(ifd_index)?;
934 self.read_image_samples_from_ifd(ifd)
935 }
936
937 pub fn read_image_samples_from_ifd<T: TiffSample>(&self, ifd: &Ifd) -> Result<ArrayD<T>> {
941 let layout = ifd.raster_layout()?;
942 if !T::matches_layout(&layout) {
943 return Err(Error::TypeMismatch {
944 expected: T::type_name(),
945 actual: format!(
946 "sample_format={} bits_per_sample={}",
947 layout.sample_format, layout.bits_per_sample
948 ),
949 });
950 }
951
952 self.read_window_samples_from_ifd(ifd, 0, 0, layout.height, layout.width)
953 }
954}
955
956fn validate_window(
957 layout: &RasterLayout,
958 row_off: usize,
959 col_off: usize,
960 rows: usize,
961 cols: usize,
962) -> Result<Window> {
963 let row_end = row_off
964 .checked_add(rows)
965 .ok_or_else(|| Error::InvalidImageLayout("window row range overflows usize".into()))?;
966 let col_end = col_off
967 .checked_add(cols)
968 .ok_or_else(|| Error::InvalidImageLayout("window column range overflows usize".into()))?;
969 if row_end > layout.height || col_end > layout.width {
970 return Err(Error::InvalidImageLayout(format!(
971 "window [{row_off}..{row_end}, {col_off}..{col_end}) exceeds raster bounds {}x{}",
972 layout.height, layout.width
973 )));
974 }
975 Ok(Window {
976 row_off,
977 col_off,
978 rows,
979 cols,
980 })
981}
982
983fn validate_band_index(layout: &RasterLayout, band_index: usize) -> Result<()> {
984 if band_index >= layout.samples_per_pixel {
985 return Err(Error::BandIndexOutOfBounds {
986 index: band_index,
987 band_count: layout.samples_per_pixel,
988 });
989 }
990 Ok(())
991}
992
993fn parse_gdal_structural_metadata(source: &dyn TiffSource) -> Option<GdalStructuralMetadata> {
994 let available_len = usize::try_from(source.len().checked_sub(8)?).ok()?;
995 if available_len == 0 {
996 return None;
997 }
998
999 let probe_len = available_len.min(64);
1000 let probe = source.read_exact_at(8, probe_len).ok()?;
1001 let total_len = parse_gdal_structural_metadata_len(&probe)?;
1002 if total_len == 0 || total_len > available_len {
1003 return None;
1004 }
1005
1006 let bytes = source.read_exact_at(8, total_len).ok()?;
1007 GdalStructuralMetadata::from_prefix(&bytes)
1008}
1009
1010fn parse_gdal_structural_metadata_len(bytes: &[u8]) -> Option<usize> {
1011 let text = std::str::from_utf8(bytes).ok()?;
1012 let newline_index = text.find('\n')?;
1013 let header = &text[..newline_index];
1014 let value = header.strip_prefix(GDAL_STRUCTURAL_METADATA_PREFIX)?;
1015 let digits: String = value.chars().take_while(|ch| ch.is_ascii_digit()).collect();
1016 if digits.is_empty() {
1017 return None;
1018 }
1019 let payload_len: usize = digits.parse().ok()?;
1020 newline_index.checked_add(1)?.checked_add(payload_len)
1021}
1022
1023#[cfg(test)]
1024mod tests {
1025 use std::collections::BTreeMap;
1026 use std::sync::atomic::{AtomicUsize, Ordering};
1027 use std::sync::Arc;
1028
1029 use super::{
1030 parse_gdal_structural_metadata, parse_gdal_structural_metadata_len, Error,
1031 GdalStructuralMetadata, OpenOptions, ParseBudgets, TiffFile,
1032 GDAL_STRUCTURAL_METADATA_PREFIX,
1033 };
1034 use crate::source::{BytesSource, TiffSource};
1035 use flate2::{write::ZlibEncoder, Compression as FlateCompression};
1036
1037 fn le_u16(value: u16) -> [u8; 2] {
1038 value.to_le_bytes()
1039 }
1040
1041 fn le_u32(value: u32) -> [u8; 4] {
1042 value.to_le_bytes()
1043 }
1044
1045 fn le_u64(value: u64) -> [u8; 8] {
1046 value.to_le_bytes()
1047 }
1048
1049 fn bigtiff_header(first_ifd_offset: u64) -> Vec<u8> {
1050 let mut bytes = Vec::new();
1051 bytes.extend_from_slice(b"II");
1052 bytes.extend_from_slice(&le_u16(43));
1053 bytes.extend_from_slice(&le_u16(8));
1054 bytes.extend_from_slice(&le_u16(0));
1055 bytes.extend_from_slice(&le_u64(first_ifd_offset));
1056 bytes
1057 }
1058
1059 fn inline_short(value: u16) -> Vec<u8> {
1060 let mut bytes = [0u8; 4];
1061 bytes[..2].copy_from_slice(&le_u16(value));
1062 bytes.to_vec()
1063 }
1064
1065 fn build_stripped_tiff(
1066 width: u32,
1067 height: u32,
1068 image_data: &[u8],
1069 overrides: &[(u16, u16, u32, Vec<u8>)],
1070 ) -> Vec<u8> {
1071 let mut entries = BTreeMap::new();
1072 entries.insert(256, (4, 1, le_u32(width).to_vec()));
1073 entries.insert(257, (4, 1, le_u32(height).to_vec()));
1074 entries.insert(258, (3, 1, [8, 0, 0, 0].to_vec()));
1075 entries.insert(259, (3, 1, [1, 0, 0, 0].to_vec()));
1076 entries.insert(273, (4, 1, Vec::new()));
1077 entries.insert(277, (3, 1, [1, 0, 0, 0].to_vec()));
1078 entries.insert(278, (4, 1, le_u32(height).to_vec()));
1079 entries.insert(279, (4, 1, le_u32(image_data.len() as u32).to_vec()));
1080 for &(tag, ty, count, ref value) in overrides {
1081 entries.insert(tag, (ty, count, value.clone()));
1082 }
1083
1084 let ifd_offset = 8u32;
1085 let ifd_size = 2 + entries.len() * 12 + 4;
1086 let mut next_data_offset = ifd_offset as usize + ifd_size;
1087 let image_offset = next_data_offset as u32;
1088 next_data_offset += image_data.len();
1089
1090 let mut data = Vec::with_capacity(next_data_offset);
1091 data.extend_from_slice(b"II");
1092 data.extend_from_slice(&le_u16(42));
1093 data.extend_from_slice(&le_u32(ifd_offset));
1094 data.extend_from_slice(&le_u16(entries.len() as u16));
1095
1096 let mut deferred = Vec::new();
1097 for (tag, (ty, count, value)) in entries {
1098 data.extend_from_slice(&le_u16(tag));
1099 data.extend_from_slice(&le_u16(ty));
1100 data.extend_from_slice(&le_u32(count));
1101 if tag == 273 {
1102 data.extend_from_slice(&le_u32(image_offset));
1103 } else if value.len() <= 4 {
1104 let mut inline = [0u8; 4];
1105 inline[..value.len()].copy_from_slice(&value);
1106 data.extend_from_slice(&inline);
1107 } else {
1108 let offset = next_data_offset as u32;
1109 data.extend_from_slice(&le_u32(offset));
1110 next_data_offset += value.len();
1111 deferred.push(value);
1112 }
1113 }
1114 data.extend_from_slice(&le_u32(0));
1115 data.extend_from_slice(image_data);
1116 for value in deferred {
1117 data.extend_from_slice(&value);
1118 }
1119 data
1120 }
1121
1122 #[allow(clippy::too_many_arguments)]
1123 fn build_lerc2_header_v2(
1124 width: u32,
1125 height: u32,
1126 valid_pixel_count: u32,
1127 image_type: i32,
1128 max_z_error: f64,
1129 z_min: f64,
1130 z_max: f64,
1131 payload_len: usize,
1132 ) -> Vec<u8> {
1133 let blob_size = 58 + 4 + payload_len;
1134 let mut bytes = Vec::with_capacity(blob_size);
1135 bytes.extend_from_slice(b"Lerc2 ");
1136 bytes.extend_from_slice(&2i32.to_le_bytes());
1137 bytes.extend_from_slice(&height.to_le_bytes());
1138 bytes.extend_from_slice(&width.to_le_bytes());
1139 bytes.extend_from_slice(&valid_pixel_count.to_le_bytes());
1140 bytes.extend_from_slice(&8i32.to_le_bytes());
1141 bytes.extend_from_slice(&(blob_size as i32).to_le_bytes());
1142 bytes.extend_from_slice(&image_type.to_le_bytes());
1143 bytes.extend_from_slice(&max_z_error.to_le_bytes());
1144 bytes.extend_from_slice(&z_min.to_le_bytes());
1145 bytes.extend_from_slice(&z_max.to_le_bytes());
1146 bytes
1147 }
1148
1149 #[allow(clippy::too_many_arguments)]
1150 fn build_lerc2_header_v4(
1151 width: u32,
1152 height: u32,
1153 depth: u32,
1154 valid_pixel_count: u32,
1155 image_type: i32,
1156 max_z_error: f64,
1157 z_min: f64,
1158 z_max: f64,
1159 payload_len: usize,
1160 ) -> Vec<u8> {
1161 let blob_size = 66 + 4 + payload_len;
1162 let mut bytes = Vec::with_capacity(blob_size);
1163 bytes.extend_from_slice(b"Lerc2 ");
1164 bytes.extend_from_slice(&4i32.to_le_bytes());
1165 bytes.extend_from_slice(&0u32.to_le_bytes());
1166 bytes.extend_from_slice(&height.to_le_bytes());
1167 bytes.extend_from_slice(&width.to_le_bytes());
1168 bytes.extend_from_slice(&depth.to_le_bytes());
1169 bytes.extend_from_slice(&valid_pixel_count.to_le_bytes());
1170 bytes.extend_from_slice(&8i32.to_le_bytes());
1171 bytes.extend_from_slice(&(blob_size as i32).to_le_bytes());
1172 bytes.extend_from_slice(&image_type.to_le_bytes());
1173 bytes.extend_from_slice(&max_z_error.to_le_bytes());
1174 bytes.extend_from_slice(&z_min.to_le_bytes());
1175 bytes.extend_from_slice(&z_max.to_le_bytes());
1176 bytes
1177 }
1178
1179 fn finalize_lerc2_v4_with_checksum(mut bytes: Vec<u8>) -> Vec<u8> {
1180 let blob_size = bytes.len() as i32;
1181 bytes[34..38].copy_from_slice(&blob_size.to_le_bytes());
1182 let checksum = fletcher32(&bytes[14..blob_size as usize]);
1183 bytes[10..14].copy_from_slice(&checksum.to_le_bytes());
1184 bytes
1185 }
1186
1187 fn fletcher32(bytes: &[u8]) -> u32 {
1188 let mut sum1 = 0xffffu32;
1189 let mut sum2 = 0xffffu32;
1190 let mut words = bytes.len() / 2;
1191 let mut index = 0usize;
1192
1193 while words > 0 {
1194 let chunk = words.min(359);
1195 words -= chunk;
1196 for _ in 0..chunk {
1197 sum1 += (bytes[index] as u32) << 8;
1198 index += 1;
1199 sum2 += sum1 + bytes[index] as u32;
1200 sum1 += bytes[index] as u32;
1201 index += 1;
1202 }
1203 sum1 = (sum1 & 0xffff) + (sum1 >> 16);
1204 sum2 = (sum2 & 0xffff) + (sum2 >> 16);
1205 }
1206
1207 if bytes.len() & 1 != 0 {
1208 sum1 += (bytes[index] as u32) << 8;
1209 sum2 += sum1;
1210 }
1211
1212 sum1 = (sum1 & 0xffff) + (sum1 >> 16);
1213 sum2 = (sum2 & 0xffff) + (sum2 >> 16);
1214 (sum2 << 16) | (sum1 & 0xffff)
1215 }
1216
1217 fn encode_mask_rle(mask: &[u8]) -> Vec<u8> {
1218 let bitset_len = mask.len().div_ceil(8);
1219 let mut bitset = vec![0u8; bitset_len];
1220 for (index, &value) in mask.iter().enumerate() {
1221 if value != 0 {
1222 bitset[index >> 3] |= 1 << (7 - (index & 7));
1223 }
1224 }
1225
1226 let mut encoded = Vec::with_capacity(bitset_len + 4);
1227 encoded.extend_from_slice(&(bitset_len as i16).to_le_bytes());
1228 encoded.extend_from_slice(&bitset);
1229 encoded.extend_from_slice(&i16::MIN.to_le_bytes());
1230 encoded
1231 }
1232
1233 fn build_lerc_tiff(
1234 width: u32,
1235 height: u32,
1236 image_data: &[u8],
1237 bits_per_sample: u16,
1238 sample_format: u16,
1239 samples_per_pixel: u16,
1240 lerc_parameters: Option<[u32; 2]>,
1241 ) -> Vec<u8> {
1242 let mut overrides = vec![
1243 (258u16, 3u16, 1u32, inline_short(bits_per_sample)),
1244 (259u16, 3u16, 1u32, inline_short(34887)),
1245 (277u16, 3u16, 1u32, inline_short(samples_per_pixel)),
1246 (279u16, 4u16, 1u32, le_u32(image_data.len() as u32).to_vec()),
1247 ];
1248 if sample_format != 1 {
1249 overrides.push((339u16, 3u16, 1u32, inline_short(sample_format)));
1250 }
1251 if let Some([version, additional_compression]) = lerc_parameters {
1252 overrides.push((
1253 50674u16,
1254 4u16,
1255 2u32,
1256 [version, additional_compression]
1257 .into_iter()
1258 .flat_map(le_u32)
1259 .collect(),
1260 ));
1261 }
1262 build_stripped_tiff(width, height, image_data, &overrides)
1263 }
1264
1265 fn build_tiled_tiff(
1266 width: u32,
1267 height: u32,
1268 tile_width: u32,
1269 tile_height: u32,
1270 tiles: &[&[u8]],
1271 ) -> Vec<u8> {
1272 let mut entries = BTreeMap::new();
1273 entries.insert(256, (4, 1, le_u32(width).to_vec()));
1274 entries.insert(257, (4, 1, le_u32(height).to_vec()));
1275 entries.insert(258, (3, 1, [8, 0, 0, 0].to_vec()));
1276 entries.insert(259, (3, 1, [1, 0, 0, 0].to_vec()));
1277 entries.insert(277, (3, 1, [1, 0, 0, 0].to_vec()));
1278 entries.insert(322, (4, 1, le_u32(tile_width).to_vec()));
1279 entries.insert(323, (4, 1, le_u32(tile_height).to_vec()));
1280 entries.insert(
1281 325,
1282 (
1283 4,
1284 tiles.len() as u32,
1285 tiles
1286 .iter()
1287 .flat_map(|tile| le_u32(tile.len() as u32))
1288 .collect(),
1289 ),
1290 );
1291
1292 let ifd_offset = 8u32;
1293 let ifd_size = 2 + (entries.len() + 1) * 12 + 4;
1294 let mut tile_data_offset = ifd_offset as usize + ifd_size;
1295 let tile_offsets: Vec<u32> = tiles
1296 .iter()
1297 .map(|tile| {
1298 let offset = tile_data_offset as u32;
1299 tile_data_offset += tile.len();
1300 offset
1301 })
1302 .collect();
1303 entries.insert(
1304 324,
1305 (
1306 4,
1307 tile_offsets.len() as u32,
1308 tile_offsets
1309 .iter()
1310 .flat_map(|offset| le_u32(*offset))
1311 .collect(),
1312 ),
1313 );
1314
1315 let mut next_data_offset = tile_data_offset;
1316 let mut data = Vec::with_capacity(next_data_offset);
1317 data.extend_from_slice(b"II");
1318 data.extend_from_slice(&le_u16(42));
1319 data.extend_from_slice(&le_u32(ifd_offset));
1320 data.extend_from_slice(&le_u16(entries.len() as u16));
1321
1322 let mut deferred = Vec::new();
1323 for (tag, (ty, count, value)) in entries {
1324 data.extend_from_slice(&le_u16(tag));
1325 data.extend_from_slice(&le_u16(ty));
1326 data.extend_from_slice(&le_u32(count));
1327 if value.len() <= 4 {
1328 let mut inline = [0u8; 4];
1329 inline[..value.len()].copy_from_slice(&value);
1330 data.extend_from_slice(&inline);
1331 } else {
1332 let offset = next_data_offset as u32;
1333 data.extend_from_slice(&le_u32(offset));
1334 next_data_offset += value.len();
1335 deferred.push(value);
1336 }
1337 }
1338 data.extend_from_slice(&le_u32(0));
1339 for tile in tiles {
1340 data.extend_from_slice(tile);
1341 }
1342 for value in deferred {
1343 data.extend_from_slice(&value);
1344 }
1345 data
1346 }
1347
1348 fn build_multi_strip_tiff(width: u32, rows: &[&[u8]]) -> Vec<u8> {
1349 let mut entries = BTreeMap::new();
1350 entries.insert(256, (4, 1, le_u32(width).to_vec()));
1351 entries.insert(257, (4, 1, le_u32(rows.len() as u32).to_vec()));
1352 entries.insert(258, (3, 1, [8, 0, 0, 0].to_vec()));
1353 entries.insert(259, (3, 1, [1, 0, 0, 0].to_vec()));
1354 entries.insert(277, (3, 1, [1, 0, 0, 0].to_vec()));
1355 entries.insert(278, (4, 1, le_u32(1).to_vec()));
1356 entries.insert(
1357 279,
1358 (
1359 4,
1360 rows.len() as u32,
1361 rows.iter()
1362 .flat_map(|row| le_u32(row.len() as u32))
1363 .collect(),
1364 ),
1365 );
1366
1367 let ifd_offset = 8u32;
1368 let ifd_size = 2 + (entries.len() + 1) * 12 + 4;
1369 let mut strip_data_offset = ifd_offset as usize + ifd_size;
1370 let strip_offsets: Vec<u32> = rows
1371 .iter()
1372 .map(|row| {
1373 let offset = strip_data_offset as u32;
1374 strip_data_offset += row.len();
1375 offset
1376 })
1377 .collect();
1378 entries.insert(
1379 273,
1380 (
1381 4,
1382 strip_offsets.len() as u32,
1383 strip_offsets
1384 .iter()
1385 .flat_map(|offset| le_u32(*offset))
1386 .collect(),
1387 ),
1388 );
1389
1390 let mut next_data_offset = strip_data_offset;
1391 let mut data = Vec::with_capacity(next_data_offset);
1392 data.extend_from_slice(b"II");
1393 data.extend_from_slice(&le_u16(42));
1394 data.extend_from_slice(&le_u32(ifd_offset));
1395 data.extend_from_slice(&le_u16(entries.len() as u16));
1396
1397 let mut deferred = Vec::new();
1398 for (tag, (ty, count, value)) in entries {
1399 data.extend_from_slice(&le_u16(tag));
1400 data.extend_from_slice(&le_u16(ty));
1401 data.extend_from_slice(&le_u32(count));
1402 if value.len() <= 4 {
1403 let mut inline = [0u8; 4];
1404 inline[..value.len()].copy_from_slice(&value);
1405 data.extend_from_slice(&inline);
1406 } else {
1407 let offset = next_data_offset as u32;
1408 data.extend_from_slice(&le_u32(offset));
1409 next_data_offset += value.len();
1410 deferred.push(value);
1411 }
1412 }
1413 data.extend_from_slice(&le_u32(0));
1414 for row in rows {
1415 data.extend_from_slice(row);
1416 }
1417 for value in deferred {
1418 data.extend_from_slice(&value);
1419 }
1420 data
1421 }
1422
1423 fn build_planar_stripped_tiff(width: u32, height: u32, planes: &[&[u8]]) -> Vec<u8> {
1424 let mut entries = BTreeMap::new();
1425 entries.insert(256, (4, 1, le_u32(width).to_vec()));
1426 entries.insert(257, (4, 1, le_u32(height).to_vec()));
1427 entries.insert(258, (3, 1, [8, 0, 0, 0].to_vec()));
1428 entries.insert(259, (3, 1, [1, 0, 0, 0].to_vec()));
1429 entries.insert(262, (3, 1, [2, 0, 0, 0].to_vec()));
1430 entries.insert(277, (3, 1, inline_short(planes.len() as u16)));
1431 entries.insert(278, (4, 1, le_u32(height).to_vec()));
1432 entries.insert(284, (3, 1, [2, 0, 0, 0].to_vec()));
1433 entries.insert(
1434 279,
1435 (
1436 4,
1437 planes.len() as u32,
1438 planes
1439 .iter()
1440 .flat_map(|plane| le_u32(plane.len() as u32))
1441 .collect(),
1442 ),
1443 );
1444
1445 let ifd_offset = 8u32;
1446 let ifd_size = 2 + (entries.len() + 1) * 12 + 4;
1447 let mut strip_data_offset = ifd_offset as usize + ifd_size;
1448 let strip_offsets: Vec<u32> = planes
1449 .iter()
1450 .map(|plane| {
1451 let offset = strip_data_offset as u32;
1452 strip_data_offset += plane.len();
1453 offset
1454 })
1455 .collect();
1456 entries.insert(
1457 273,
1458 (
1459 4,
1460 strip_offsets.len() as u32,
1461 strip_offsets
1462 .iter()
1463 .flat_map(|offset| le_u32(*offset))
1464 .collect(),
1465 ),
1466 );
1467
1468 let mut next_data_offset = strip_data_offset;
1469 let mut data = Vec::with_capacity(next_data_offset);
1470 data.extend_from_slice(b"II");
1471 data.extend_from_slice(&le_u16(42));
1472 data.extend_from_slice(&le_u32(ifd_offset));
1473 data.extend_from_slice(&le_u16(entries.len() as u16));
1474
1475 let mut deferred = Vec::new();
1476 for (tag, (ty, count, value)) in entries {
1477 data.extend_from_slice(&le_u16(tag));
1478 data.extend_from_slice(&le_u16(ty));
1479 data.extend_from_slice(&le_u32(count));
1480 if value.len() <= 4 {
1481 let mut inline = [0u8; 4];
1482 inline[..value.len()].copy_from_slice(&value);
1483 data.extend_from_slice(&inline);
1484 } else {
1485 let offset = next_data_offset as u32;
1486 data.extend_from_slice(&le_u32(offset));
1487 next_data_offset += value.len();
1488 deferred.push(value);
1489 }
1490 }
1491 data.extend_from_slice(&le_u32(0));
1492 for plane in planes {
1493 data.extend_from_slice(plane);
1494 }
1495 for value in deferred {
1496 data.extend_from_slice(&value);
1497 }
1498 data
1499 }
1500
1501 struct CountingSource {
1502 bytes: Vec<u8>,
1503 reads: AtomicUsize,
1504 }
1505
1506 impl CountingSource {
1507 fn new(bytes: Vec<u8>) -> Self {
1508 Self {
1509 bytes,
1510 reads: AtomicUsize::new(0),
1511 }
1512 }
1513
1514 fn reset_reads(&self) {
1515 self.reads.store(0, Ordering::SeqCst);
1516 }
1517
1518 fn reads(&self) -> usize {
1519 self.reads.load(Ordering::SeqCst)
1520 }
1521 }
1522
1523 impl TiffSource for CountingSource {
1524 fn len(&self) -> u64 {
1525 self.bytes.len() as u64
1526 }
1527
1528 fn read_exact_at(&self, offset: u64, len: usize) -> crate::error::Result<Vec<u8>> {
1529 self.reads.fetch_add(1, Ordering::SeqCst);
1530 let start =
1531 usize::try_from(offset).map_err(|_| crate::error::Error::OffsetOutOfBounds {
1532 offset,
1533 length: len as u64,
1534 data_len: self.len(),
1535 })?;
1536 let end = start
1537 .checked_add(len)
1538 .ok_or(crate::error::Error::OffsetOutOfBounds {
1539 offset,
1540 length: len as u64,
1541 data_len: self.len(),
1542 })?;
1543 if end > self.bytes.len() {
1544 return Err(crate::error::Error::OffsetOutOfBounds {
1545 offset,
1546 length: len as u64,
1547 data_len: self.len(),
1548 });
1549 }
1550 Ok(self.bytes[start..end].to_vec())
1551 }
1552 }
1553
1554 fn overwrite_classic_inline_long_tag(data: &mut [u8], tag: u16, value: u32) {
1555 let ifd_offset = u32::from_le_bytes(data[4..8].try_into().unwrap()) as usize;
1556 let entry_count = u16::from_le_bytes(data[ifd_offset..ifd_offset + 2].try_into().unwrap());
1557 for entry_index in 0..usize::from(entry_count) {
1558 let entry = ifd_offset + 2 + entry_index * 12;
1559 let entry_tag = u16::from_le_bytes(data[entry..entry + 2].try_into().unwrap());
1560 if entry_tag == tag {
1561 data[entry + 8..entry + 12].copy_from_slice(&le_u32(value));
1562 return;
1563 }
1564 }
1565 panic!("tag {tag} not found");
1566 }
1567
1568 #[test]
1569 fn bigtiff_ifd_entry_count_respects_parse_budget_before_body_read() {
1570 let mut data = bigtiff_header(16);
1571 data.extend_from_slice(&le_u64(2));
1572
1573 let err = match TiffFile::from_bytes_with_options(
1574 data,
1575 OpenOptions {
1576 parse_budgets: ParseBudgets {
1577 max_ifd_entries: 1,
1578 ..ParseBudgets::default()
1579 },
1580 ..OpenOptions::default()
1581 },
1582 ) {
1583 Ok(_) => panic!("expected parse budget error"),
1584 Err(err) => err,
1585 };
1586 assert!(
1587 matches!(err, Error::InvalidImageLayout(message) if message.contains("entry count"))
1588 );
1589 }
1590
1591 #[test]
1592 fn bigtiff_tag_value_bytes_respect_parse_budget_before_value_read() {
1593 let mut data = bigtiff_header(16);
1594 data.extend_from_slice(&le_u64(1));
1595 data.extend_from_slice(&le_u16(256));
1596 data.extend_from_slice(&le_u16(1));
1597 data.extend_from_slice(&le_u64(9));
1598 data.extend_from_slice(&le_u64(1024));
1599 data.extend_from_slice(&le_u64(0));
1600
1601 let err = match TiffFile::from_bytes_with_options(
1602 data,
1603 OpenOptions {
1604 parse_budgets: ParseBudgets {
1605 max_tag_value_bytes: 8,
1606 ..ParseBudgets::default()
1607 },
1608 ..OpenOptions::default()
1609 },
1610 ) {
1611 Ok(_) => panic!("expected parse budget error"),
1612 Err(err) => err,
1613 };
1614 assert!(
1615 matches!(err, Error::InvalidTagValue { tag: 256, reason } if reason.contains("parse budget"))
1616 );
1617 }
1618
1619 #[test]
1620 fn bigtiff_tag_value_bytes_respect_aggregate_parse_budget() {
1621 let mut data = bigtiff_header(16);
1622 data.extend_from_slice(&le_u64(2));
1623 data.extend_from_slice(&le_u16(65000));
1624 data.extend_from_slice(&le_u16(1));
1625 data.extend_from_slice(&le_u64(8));
1626 data.extend_from_slice(&[0x11; 8]);
1627 data.extend_from_slice(&le_u16(65001));
1628 data.extend_from_slice(&le_u16(1));
1629 data.extend_from_slice(&le_u64(8));
1630 data.extend_from_slice(&[0x22; 8]);
1631 data.extend_from_slice(&le_u64(0));
1632
1633 let err = match TiffFile::from_bytes_with_options(
1634 data,
1635 OpenOptions {
1636 parse_budgets: ParseBudgets {
1637 max_tag_value_bytes: 8,
1638 max_metadata_value_bytes: 8,
1639 ..ParseBudgets::default()
1640 },
1641 ..OpenOptions::default()
1642 },
1643 ) {
1644 Ok(_) => panic!("expected aggregate parse budget error"),
1645 Err(err) => err,
1646 };
1647 assert!(
1648 matches!(err, Error::InvalidTagValue { tag: 65001, reason } if reason.contains("aggregate metadata"))
1649 );
1650 }
1651
1652 #[test]
1653 fn bigtiff_ifd_chain_respects_parse_budget() {
1654 let mut data = bigtiff_header(16);
1655 data.extend_from_slice(&le_u64(0));
1656 data.extend_from_slice(&le_u64(32));
1657 data.extend_from_slice(&le_u64(0));
1658 data.extend_from_slice(&le_u64(0));
1659
1660 let err = match TiffFile::from_bytes_with_options(
1661 data,
1662 OpenOptions {
1663 parse_budgets: ParseBudgets {
1664 max_ifds: 1,
1665 ..ParseBudgets::default()
1666 },
1667 ..OpenOptions::default()
1668 },
1669 ) {
1670 Ok(_) => panic!("expected parse budget error"),
1671 Err(err) => err,
1672 };
1673 assert!(matches!(err, Error::Other(message) if message.contains("parse budget")));
1674 }
1675
1676 #[test]
1677 fn rejects_bigtiff_long8_dimension_that_exceeds_u32() {
1678 let mut data = bigtiff_header(16);
1679 data.extend_from_slice(&le_u64(2));
1680 data.extend_from_slice(&le_u16(256));
1681 data.extend_from_slice(&le_u16(16));
1682 data.extend_from_slice(&le_u64(1));
1683 data.extend_from_slice(&le_u64(u64::from(u32::MAX) + 2));
1684 data.extend_from_slice(&le_u16(257));
1685 data.extend_from_slice(&le_u16(16));
1686 data.extend_from_slice(&le_u64(1));
1687 data.extend_from_slice(&le_u64(1));
1688 data.extend_from_slice(&le_u64(0));
1689
1690 let file = TiffFile::from_bytes(data).unwrap();
1691 let err = file.ifd(0).unwrap().raster_layout().unwrap_err();
1692 assert!(
1693 matches!(err, Error::InvalidImageLayout(message) if message.contains("dimensions"))
1694 );
1695 }
1696
1697 #[test]
1698 fn oversized_strip_byte_count_is_rejected_before_payload_read() {
1699 let data = build_stripped_tiff(
1700 2,
1701 2,
1702 &[1, 2, 3, 4],
1703 &[(279, 4, 1, le_u32(u32::MAX).to_vec())],
1704 );
1705 let source = Arc::new(CountingSource::new(data));
1706 let file = TiffFile::from_source(source.clone()).unwrap();
1707 source.reset_reads();
1708
1709 let err = file.read_image_bytes(0).unwrap_err();
1710 assert!(err.to_string().contains("block read budget"));
1711 assert_eq!(source.reads(), 0);
1712 }
1713
1714 #[test]
1715 fn oversized_tile_byte_count_is_rejected_before_payload_read() {
1716 let mut data = build_tiled_tiff(2, 2, 2, 2, &[&[1, 2, 3, 4]]);
1717 overwrite_classic_inline_long_tag(&mut data, 325, u32::MAX);
1718 let source = Arc::new(CountingSource::new(data));
1719 let file = TiffFile::from_source(source.clone()).unwrap();
1720 source.reset_reads();
1721
1722 let err = file.read_image_bytes(0).unwrap_err();
1723 assert!(err.to_string().contains("block read budget"));
1724 assert_eq!(source.reads(), 0);
1725 }
1726
1727 #[test]
1728 fn reads_stripped_u8_image() {
1729 let data = build_stripped_tiff(2, 2, &[1, 2, 3, 4], &[]);
1730 let file = TiffFile::from_bytes(data).unwrap();
1731 let image = file.read_image::<u8>(0).unwrap();
1732 assert_eq!(image.shape(), &[2, 2]);
1733 let (values, offset) = image.into_raw_vec_and_offset();
1734 assert_eq!(offset, Some(0));
1735 assert_eq!(values, vec![1, 2, 3, 4]);
1736 }
1737
1738 #[test]
1739 fn reads_single_chunky_band_and_window() {
1740 let data = build_stripped_tiff(
1741 2,
1742 2,
1743 &[
1744 1, 10, 100, 2, 20, 110, 3, 30, 120, 4, 40, 130,
1748 ],
1749 &[
1750 (262, 3, 1, inline_short(2)),
1751 (277, 3, 1, inline_short(3)),
1752 (279, 4, 1, le_u32(12).to_vec()),
1753 ],
1754 );
1755 let file = TiffFile::from_bytes(data).unwrap();
1756
1757 let green = file.read_band::<u8>(0, 1).unwrap();
1758 assert_eq!(green.shape(), &[2, 2]);
1759 let (green_values, offset) = green.into_raw_vec_and_offset();
1760 assert_eq!(offset, Some(0));
1761 assert_eq!(green_values, vec![10, 20, 30, 40]);
1762
1763 let blue_window = file.read_band_window::<u8>(0, 2, 0, 1, 2, 1).unwrap();
1764 assert_eq!(blue_window.shape(), &[2, 1]);
1765 let (blue_values, offset) = blue_window.into_raw_vec_and_offset();
1766 assert_eq!(offset, Some(0));
1767 assert_eq!(blue_values, vec![110, 130]);
1768
1769 let err = file.read_band::<u8>(0, 3).unwrap_err();
1770 assert!(matches!(
1771 err,
1772 Error::BandIndexOutOfBounds {
1773 index: 3,
1774 band_count: 3
1775 }
1776 ));
1777 }
1778
1779 #[test]
1780 fn planar_band_reads_only_requested_plane() {
1781 let data = build_planar_stripped_tiff(
1782 2,
1783 2,
1784 &[&[1, 2, 3, 4], &[10, 20, 30, 40], &[100, 110, 120, 130]],
1785 );
1786 let source = Arc::new(CountingSource::new(data));
1787 let file = TiffFile::from_source(source.clone()).unwrap();
1788 source.reset_reads();
1789
1790 let blue = file.read_band::<u8>(0, 2).unwrap();
1791 assert_eq!(blue.shape(), &[2, 2]);
1792 let (values, offset) = blue.into_raw_vec_and_offset();
1793 assert_eq!(offset, Some(0));
1794 assert_eq!(values, vec![100, 110, 120, 130]);
1795 assert_eq!(source.reads(), 1);
1796 }
1797
1798 #[test]
1799 fn keeps_subbyte_palette_reads_raw_and_offers_explicit_decoded_pixels() {
1800 let mut color_map = Vec::new();
1801 color_map.extend((0u16..16).map(|value| value * 17 * 257));
1802 color_map.extend((0u16..16).map(|value| (15 - value) * 17 * 257));
1803 color_map.extend((0u16..16).map(|value| value * 8 * 257));
1804 let data = build_stripped_tiff(
1805 4,
1806 1,
1807 &[0x01, 0x23],
1808 &[
1809 (258, 3, 1, inline_short(4)),
1810 (262, 3, 1, inline_short(3)),
1811 (
1812 320,
1813 3,
1814 color_map.len() as u32,
1815 color_map.iter().flat_map(|value| le_u16(*value)).collect(),
1816 ),
1817 ],
1818 );
1819 let file = TiffFile::from_bytes(data).unwrap();
1820
1821 let image = file.read_image::<u8>(0).unwrap();
1822 assert_eq!(image.shape(), &[1, 4]);
1823 let (values, offset) = image.into_raw_vec_and_offset();
1824 assert_eq!(offset, Some(0));
1825 assert_eq!(values, vec![0, 1, 2, 3]);
1826
1827 let image = file.read_decoded_image::<u8>(0).unwrap();
1828 assert_eq!(image.shape(), &[1, 4, 3]);
1829 let (values, offset) = image.into_raw_vec_and_offset();
1830 assert_eq!(offset, Some(0));
1831 assert_eq!(
1832 values,
1833 vec![
1834 0, 255, 0, 17, 238, 8, 34, 221, 16, 51, 204, 24
1838 ]
1839 );
1840
1841 let sample_bytes = file.read_image_sample_bytes(0).unwrap();
1842 assert_eq!(sample_bytes, vec![0, 1, 2, 3]);
1843 }
1844
1845 #[test]
1846 fn keeps_subsampled_ycbcr_reads_raw_and_offers_explicit_decoded_pixels() {
1847 let data = build_stripped_tiff(
1848 2,
1849 2,
1850 &[10u8, 20, 30, 40, 128, 128],
1851 &[
1852 (
1853 258,
1854 3,
1855 3,
1856 [8u16, 8, 8].into_iter().flat_map(le_u16).collect(),
1857 ),
1858 (262, 3, 1, inline_short(6)),
1859 (277, 3, 1, inline_short(3)),
1860 (530, 3, 2, [2u16, 2].into_iter().flat_map(le_u16).collect()),
1861 ],
1862 );
1863 let file = TiffFile::from_bytes(data).unwrap();
1864
1865 let image = file.read_image::<u8>(0).unwrap();
1866 assert_eq!(image.shape(), &[2, 2, 3]);
1867 let (values, offset) = image.into_raw_vec_and_offset();
1868 assert_eq!(offset, Some(0));
1869 assert_eq!(
1870 values,
1871 vec![
1872 10, 128, 128, 20, 128, 128, 30, 128, 128, 40, 128, 128
1876 ]
1877 );
1878
1879 let image = file.read_decoded_image::<u8>(0).unwrap();
1880 assert_eq!(image.shape(), &[2, 2, 3]);
1881 let (rgb, offset) = image.into_raw_vec_and_offset();
1882 assert_eq!(offset, Some(0));
1883 assert_eq!(
1884 rgb,
1885 vec![
1886 10, 10, 10, 20, 20, 20, 30, 30, 30, 40, 40, 40
1890 ]
1891 );
1892
1893 let samples = file.read_image_samples::<u8>(0).unwrap();
1894 let (values, offset) = samples.into_raw_vec_and_offset();
1895 assert_eq!(offset, Some(0));
1896 assert_eq!(
1897 values,
1898 vec![
1899 10, 128, 128, 20, 128, 128, 30, 128, 128, 40, 128, 128
1903 ]
1904 );
1905 }
1906
1907 #[test]
1908 fn reads_horizontal_predictor_u16_strip() {
1909 let encoded = [1, 0, 1, 0, 2, 0];
1910 let data = build_stripped_tiff(
1911 3,
1912 1,
1913 &encoded,
1914 &[
1915 (258, 3, 1, [16, 0, 0, 0].to_vec()),
1916 (317, 3, 1, [2, 0, 0, 0].to_vec()),
1917 ],
1918 );
1919 let file = TiffFile::from_bytes(data).unwrap();
1920 let image = file.read_image::<u16>(0).unwrap();
1921 assert_eq!(image.shape(), &[1, 3]);
1922 let (values, offset) = image.into_raw_vec_and_offset();
1923 assert_eq!(offset, Some(0));
1924 assert_eq!(values, vec![1, 2, 4]);
1925 }
1926
1927 #[test]
1928 fn reads_lerc_f32_strip() {
1929 let mut blob = build_lerc2_header_v2(2, 2, 4, 6, 0.0, 1.0, 4.0, 1 + 16);
1930 blob.extend_from_slice(&0u32.to_le_bytes());
1931 blob.push(1);
1932 for value in [1.0f32, 2.0, 3.0, 4.0] {
1933 blob.extend_from_slice(&value.to_le_bytes());
1934 }
1935
1936 let data = build_lerc_tiff(2, 2, &blob, 32, 3, 1, None);
1937 let file = TiffFile::from_bytes(data).unwrap();
1938 let image = file.read_image::<f32>(0).unwrap();
1939 let (values, offset) = image.into_raw_vec_and_offset();
1940 assert_eq!(offset, Some(0));
1941 assert_eq!(values, vec![1.0, 2.0, 3.0, 4.0]);
1942 }
1943
1944 #[test]
1945 fn reads_lerc_masked_f32_strip_as_nan() {
1946 let mask = [1u8, 0, 1, 1];
1947 let encoded_mask = encode_mask_rle(&mask);
1948 let mut blob =
1949 build_lerc2_header_v2(2, 2, 3, 6, 0.0, 1.0, 4.0, encoded_mask.len() + 1 + 12);
1950 blob.extend_from_slice(&(encoded_mask.len() as u32).to_le_bytes());
1951 blob.extend_from_slice(&encoded_mask);
1952 blob.push(1);
1953 for value in [1.0f32, 3.0, 4.0] {
1954 blob.extend_from_slice(&value.to_le_bytes());
1955 }
1956
1957 let data = build_lerc_tiff(2, 2, &blob, 32, 3, 1, None);
1958 let file = TiffFile::from_bytes(data).unwrap();
1959 let image = file.read_image::<f32>(0).unwrap();
1960 let (values, offset) = image.into_raw_vec_and_offset();
1961 assert_eq!(offset, Some(0));
1962 assert_eq!(values[0], 1.0);
1963 assert!(values[1].is_nan());
1964 assert_eq!(values[2], 3.0);
1965 assert_eq!(values[3], 4.0);
1966 }
1967
1968 #[test]
1969 fn reads_lerc_chunky_rgb_band_set_strip() {
1970 let mut red = build_lerc2_header_v2(2, 1, 2, 1, 0.0, 1.0, 1.0, 0);
1971 red.extend_from_slice(&0u32.to_le_bytes());
1972 let mut green = build_lerc2_header_v2(2, 1, 2, 1, 0.0, 2.0, 2.0, 0);
1973 green.extend_from_slice(&0u32.to_le_bytes());
1974 let mut blue = build_lerc2_header_v2(2, 1, 2, 1, 0.0, 3.0, 3.0, 0);
1975 blue.extend_from_slice(&0u32.to_le_bytes());
1976
1977 let mut blob = red;
1978 blob.extend_from_slice(&green);
1979 blob.extend_from_slice(&blue);
1980
1981 let data = build_lerc_tiff(2, 1, &blob, 8, 1, 3, None);
1982 let file = TiffFile::from_bytes(data).unwrap();
1983 let image = file.read_image::<u8>(0).unwrap();
1984 assert_eq!(image.shape(), &[1, 2, 3]);
1985 let (values, offset) = image.into_raw_vec_and_offset();
1986 assert_eq!(offset, Some(0));
1987 assert_eq!(values, vec![1, 2, 3, 1, 2, 3]);
1988 }
1989
1990 #[test]
1991 fn reads_lerc_chunky_rgb_depth_blob_strip() {
1992 let mut blob = build_lerc2_header_v4(2, 1, 3, 2, 1, 0.0, 1.0, 6.0, 6 + 6 + 1 + 6);
1993 blob.extend_from_slice(&0u32.to_le_bytes());
1994 for value in [1u8, 2, 3] {
1995 blob.extend_from_slice(&value.to_le_bytes());
1996 }
1997 for value in [4u8, 5, 6] {
1998 blob.extend_from_slice(&value.to_le_bytes());
1999 }
2000 blob.push(1);
2001 blob.extend_from_slice(&[1, 2, 3, 4, 5, 6]);
2002 let blob = finalize_lerc2_v4_with_checksum(blob);
2003
2004 let data = build_lerc_tiff(2, 1, &blob, 8, 1, 3, Some([4, 0]));
2005 let file = TiffFile::from_bytes(data).unwrap();
2006 let image = file.read_image::<u8>(0).unwrap();
2007 assert_eq!(image.shape(), &[1, 2, 3]);
2008 let (values, offset) = image.into_raw_vec_and_offset();
2009 assert_eq!(offset, Some(0));
2010 assert_eq!(values, vec![1, 2, 3, 4, 5, 6]);
2011 }
2012
2013 #[test]
2014 fn rejects_lerc2_blob_size_before_checksum_range_without_panicking() {
2015 let mut blob = build_lerc2_header_v4(1, 1, 1, 1, 1, 0.0, 1.0, 1.0, 0);
2016 blob[34..38].copy_from_slice(&8i32.to_le_bytes());
2017
2018 let data = build_lerc_tiff(1, 1, &blob, 8, 1, 1, Some([4, 0]));
2019 let file = TiffFile::from_bytes(data).unwrap();
2020 let error = file.read_image_bytes(0).unwrap_err();
2021 assert!(error.to_string().contains("invalid Lerc2 v4 blob size 8"));
2022 }
2023
2024 #[test]
2025 fn rejects_lerc2_header_dimensions_before_allocating_mask() {
2026 let mut blob = build_lerc2_header_v2(u32::MAX, u32::MAX, 1, 1, 0.0, 0.0, 1.0, 4);
2027 blob.extend_from_slice(&4u32.to_le_bytes());
2028 blob.extend_from_slice(&[0, 0, 0, 0]);
2029
2030 let data = build_lerc_tiff(1, 1, &blob, 8, 1, 1, None);
2031 let file = TiffFile::from_bytes(data).unwrap();
2032 let error = file.read_image_bytes(0).unwrap_err();
2033 assert!(error.to_string().contains("LERC raster dimensions"));
2034 }
2035
2036 #[test]
2037 fn rejects_truncated_lerc2_header_dimensions_before_decoder() {
2038 let blob = build_lerc2_header_v2(u32::MAX, u32::MAX, 1, 1, 0.0, 0.0, 1.0, 64);
2039
2040 let data = build_lerc_tiff(1, 1, &blob, 8, 1, 1, None);
2041 let file = TiffFile::from_bytes(data).unwrap();
2042 let error = file.read_image_bytes(0).unwrap_err();
2043 assert!(error.to_string().contains("LERC raster dimensions"));
2044 }
2045
2046 #[test]
2047 fn reads_lerc_deflate_f32_strip() {
2048 let mut blob = build_lerc2_header_v2(2, 2, 4, 6, 0.0, 1.0, 4.0, 1 + 16);
2049 blob.extend_from_slice(&0u32.to_le_bytes());
2050 blob.push(1);
2051 for value in [1.0f32, 2.0, 3.0, 4.0] {
2052 blob.extend_from_slice(&value.to_le_bytes());
2053 }
2054
2055 let mut encoder = ZlibEncoder::new(Vec::new(), FlateCompression::default());
2056 std::io::Write::write_all(&mut encoder, &blob).unwrap();
2057 let compressed = encoder.finish().unwrap();
2058
2059 let data = build_lerc_tiff(2, 2, &compressed, 32, 3, 1, Some([2, 1]));
2060 let file = TiffFile::from_bytes(data).unwrap();
2061 let image = file.read_image::<f32>(0).unwrap();
2062 let (values, offset) = image.into_raw_vec_and_offset();
2063 assert_eq!(offset, Some(0));
2064 assert_eq!(values, vec![1.0, 2.0, 3.0, 4.0]);
2065 }
2066
2067 #[cfg(feature = "zstd")]
2068 #[test]
2069 fn reads_lerc_zstd_f32_strip() {
2070 let mut blob = build_lerc2_header_v2(2, 2, 4, 6, 0.0, 1.0, 4.0, 1 + 16);
2071 blob.extend_from_slice(&0u32.to_le_bytes());
2072 blob.push(1);
2073 for value in [1.0f32, 2.0, 3.0, 4.0] {
2074 blob.extend_from_slice(&value.to_le_bytes());
2075 }
2076
2077 let compressed = zstd::stream::encode_all(std::io::Cursor::new(blob), 0).unwrap();
2078 let data = build_lerc_tiff(2, 2, &compressed, 32, 3, 1, Some([2, 2]));
2079 let file = TiffFile::from_bytes(data).unwrap();
2080 let image = file.read_image::<f32>(0).unwrap();
2081 let (values, offset) = image.into_raw_vec_and_offset();
2082 assert_eq!(offset, Some(0));
2083 assert_eq!(values, vec![1.0, 2.0, 3.0, 4.0]);
2084 }
2085
2086 #[test]
2087 fn reads_stripped_u8_window() {
2088 let data = build_multi_strip_tiff(
2089 4,
2090 &[
2091 &[1, 2, 3, 4],
2092 &[5, 6, 7, 8],
2093 &[9, 10, 11, 12],
2094 &[13, 14, 15, 16],
2095 ],
2096 );
2097 let file = TiffFile::from_bytes(data).unwrap();
2098 let window = file.read_window::<u8>(0, 1, 1, 2, 2).unwrap();
2099 assert_eq!(window.shape(), &[2, 2]);
2100 let (values, offset) = window.into_raw_vec_and_offset();
2101 assert_eq!(offset, Some(0));
2102 assert_eq!(values, vec![6, 7, 10, 11]);
2103 }
2104
2105 #[test]
2106 fn reads_tiled_u8_window() {
2107 let data = build_tiled_tiff(
2108 4,
2109 4,
2110 2,
2111 2,
2112 &[
2113 &[1, 2, 5, 6],
2114 &[3, 4, 7, 8],
2115 &[9, 10, 13, 14],
2116 &[11, 12, 15, 16],
2117 ],
2118 );
2119 let file = TiffFile::from_bytes(data).unwrap();
2120 let window = file.read_window::<u8>(0, 1, 1, 2, 2).unwrap();
2121 assert_eq!(window.shape(), &[2, 2]);
2122 let (values, offset) = window.into_raw_vec_and_offset();
2123 assert_eq!(offset, Some(0));
2124 assert_eq!(values, vec![6, 7, 10, 11]);
2125 }
2126
2127 #[test]
2128 fn windowed_tiled_reads_only_intersecting_blocks() {
2129 let data = build_tiled_tiff(
2130 4,
2131 4,
2132 2,
2133 2,
2134 &[
2135 &[1, 2, 5, 6],
2136 &[3, 4, 7, 8],
2137 &[9, 10, 13, 14],
2138 &[11, 12, 15, 16],
2139 ],
2140 );
2141 let source = Arc::new(CountingSource::new(data));
2142 let file = TiffFile::from_source(source.clone()).unwrap();
2143 source.reset_reads();
2144
2145 let window = file.read_window::<u8>(0, 0, 0, 2, 2).unwrap();
2146 let (values, offset) = window.into_raw_vec_and_offset();
2147 assert_eq!(offset, Some(0));
2148 assert_eq!(values, vec![1, 2, 5, 6]);
2149 assert_eq!(source.reads(), 1);
2150 }
2151
2152 #[test]
2153 fn unwraps_gdal_structural_metadata_block() {
2154 let metadata = GdalStructuralMetadata::from_prefix(
2155 b"GDAL_STRUCTURAL_METADATA_SIZE=000174 bytes\nBLOCK_LEADER=SIZE_AS_UINT4\nBLOCK_TRAILER=LAST_4_BYTES_REPEATED\n",
2156 )
2157 .unwrap();
2158
2159 let payload = [1u8, 2, 3, 4];
2160 let mut block = Vec::new();
2161 block.extend_from_slice(&(payload.len() as u32).to_le_bytes());
2162 block.extend_from_slice(&payload);
2163 block.extend_from_slice(&payload[payload.len() - 4..]);
2164
2165 let unwrapped = metadata
2166 .unwrap_block(&block, crate::ByteOrder::LittleEndian, 256)
2167 .unwrap();
2168 assert_eq!(unwrapped, payload);
2169 }
2170
2171 #[test]
2172 fn rejects_gdal_structural_metadata_trailer_mismatch() {
2173 let metadata = GdalStructuralMetadata::from_prefix(
2174 b"GDAL_STRUCTURAL_METADATA_SIZE=000174 bytes\nBLOCK_LEADER=SIZE_AS_UINT4\nBLOCK_TRAILER=LAST_4_BYTES_REPEATED\n",
2175 )
2176 .unwrap();
2177
2178 let block = [
2179 4u8, 0, 0, 0, 1, 2, 3, 4, 4, 3, 2, 1,
2182 ];
2183
2184 let error = metadata
2185 .unwrap_block(&block, crate::ByteOrder::LittleEndian, 512)
2186 .unwrap_err();
2187 assert!(error.to_string().contains("GDAL block trailer mismatch"));
2188 }
2189
2190 #[test]
2191 fn parses_gdal_structural_metadata_before_binary_prefix_data() {
2192 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";
2193 let prefix = format!(
2194 "{GDAL_STRUCTURAL_METADATA_PREFIX}{:06} bytes\n{rest}",
2195 rest.len()
2196 );
2197
2198 let mut bytes = vec![0u8; 8];
2199 bytes.extend_from_slice(prefix.as_bytes());
2200 bytes.extend_from_slice(&[0xff, 0x00, 0x80, 0x7f]);
2201
2202 let source = BytesSource::new(bytes);
2203 let metadata = parse_gdal_structural_metadata(&source).unwrap();
2204 assert!(metadata.block_leader_size_as_u32);
2205 assert!(metadata.block_trailer_repeats_last_4_bytes);
2206 }
2207
2208 #[test]
2209 fn parses_gdal_structural_metadata_declared_length_as_header_plus_payload() {
2210 let rest = "LAYOUT=IFDS_BEFORE_DATA\nBLOCK_ORDER=ROW_MAJOR\n";
2211 let prefix = format!(
2212 "{GDAL_STRUCTURAL_METADATA_PREFIX}{:06} bytes\n{rest}",
2213 rest.len()
2214 );
2215 assert_eq!(
2216 parse_gdal_structural_metadata_len(prefix.as_bytes()),
2217 Some(prefix.len())
2218 );
2219 }
2220
2221 #[test]
2222 fn leaves_payload_only_gdal_block_unchanged() {
2223 let metadata = GdalStructuralMetadata {
2224 block_leader_size_as_u32: true,
2225 block_trailer_repeats_last_4_bytes: true,
2226 };
2227 let payload = [0x80u8, 0x1a, 0xcf, 0x68, 0x43, 0x9a, 0x11, 0x08];
2228 let unwrapped = metadata
2229 .unwrap_block(&payload, crate::ByteOrder::LittleEndian, 570)
2230 .unwrap();
2231 assert_eq!(unwrapped, payload);
2232 }
2233
2234 #[test]
2235 fn rejects_zero_rows_per_strip_without_panicking() {
2236 let data = build_stripped_tiff(2, 2, &[1, 2, 3, 4], &[(278, 4, 1, le_u32(0).to_vec())]);
2237 let file = TiffFile::from_bytes(data).unwrap();
2238 let error = file.read_image_bytes(0).unwrap_err();
2239 assert!(error.to_string().contains("RowsPerStrip"));
2240 }
2241}