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