1use bytes::Bytes;
28
29use crate::error::TiffError;
30use crate::io::RangeReader;
31
32use super::parser::{ByteOrder, Ifd, IfdEntry, TiffHeader, BIGTIFF_HEADER_SIZE};
33use super::tags::TiffTag;
34use super::values::ValueReader;
35
36const MAX_IFDS: usize = 100;
42
43const MIN_PYRAMID_DIMENSION: u32 = 256;
46
47const MAX_LABEL_DIMENSION: u32 = 2000;
49
50#[derive(Debug, Clone)]
60pub struct PyramidLevel {
61 pub level_index: usize,
63
64 pub ifd_index: usize,
66
67 pub width: u32,
69
70 pub height: u32,
72
73 pub tile_width: u32,
75
76 pub tile_height: u32,
78
79 pub tiles_x: u32,
81
82 pub tiles_y: u32,
84
85 pub tile_count: u32,
87
88 pub downsample: f64,
90
91 pub compression: u16,
93
94 pub ifd: Ifd,
96
97 pub tile_offsets_entry: Option<IfdEntry>,
99
100 pub tile_byte_counts_entry: Option<IfdEntry>,
102
103 pub jpeg_tables_entry: Option<IfdEntry>,
105}
106
107impl PyramidLevel {
108 fn from_ifd(ifd: Ifd, ifd_index: usize, byte_order: ByteOrder) -> Option<Self> {
112 let tile_width = ifd.tile_width(byte_order)?;
114 let tile_height = ifd.tile_height(byte_order)?;
115
116 let width = ifd.image_width(byte_order)?;
118 let height = ifd.image_height(byte_order)?;
119
120 let compression = ifd.compression(byte_order).unwrap_or(0);
122
123 let tiles_x = width.div_ceil(tile_width);
125 let tiles_y = height.div_ceil(tile_height);
126 let tile_count = tiles_x * tiles_y;
127
128 let tile_offsets_entry = ifd.get_entry_by_tag(TiffTag::TileOffsets).cloned();
130 let tile_byte_counts_entry = ifd.get_entry_by_tag(TiffTag::TileByteCounts).cloned();
131
132 let jpeg_tables_entry = ifd.get_entry_by_tag(TiffTag::JpegTables).cloned();
134
135 Some(PyramidLevel {
136 level_index: 0, ifd_index,
138 width,
139 height,
140 tile_width,
141 tile_height,
142 tiles_x,
143 tiles_y,
144 tile_count,
145 downsample: 1.0, compression,
147 ifd,
148 tile_offsets_entry,
149 tile_byte_counts_entry,
150 jpeg_tables_entry,
151 })
152 }
153
154 pub fn has_tile_data(&self) -> bool {
156 self.tile_offsets_entry.is_some() && self.tile_byte_counts_entry.is_some()
157 }
158
159 pub fn tile_index(&self, tile_x: u32, tile_y: u32) -> Option<u32> {
163 if tile_x >= self.tiles_x || tile_y >= self.tiles_y {
164 return None;
165 }
166 Some(tile_y * self.tiles_x + tile_x)
167 }
168
169 pub fn tile_dimensions(&self, tile_x: u32, tile_y: u32) -> Option<(u32, u32)> {
173 if tile_x >= self.tiles_x || tile_y >= self.tiles_y {
174 return None;
175 }
176
177 let w = if tile_x == self.tiles_x - 1 {
178 let remainder = self.width % self.tile_width;
180 if remainder == 0 {
181 self.tile_width
182 } else {
183 remainder
184 }
185 } else {
186 self.tile_width
187 };
188
189 let h = if tile_y == self.tiles_y - 1 {
190 let remainder = self.height % self.tile_height;
192 if remainder == 0 {
193 self.tile_height
194 } else {
195 remainder
196 }
197 } else {
198 self.tile_height
199 };
200
201 Some((w, h))
202 }
203}
204
205#[derive(Debug, Clone)]
214pub struct TiffPyramid {
215 pub header: TiffHeader,
217
218 pub levels: Vec<PyramidLevel>,
220
221 pub other_ifds: Vec<(usize, Ifd)>,
223}
224
225impl TiffPyramid {
226 pub async fn parse<R: RangeReader>(reader: &R) -> Result<Self, TiffError> {
231 let header_bytes = reader.read_exact_at(0, BIGTIFF_HEADER_SIZE).await?;
233 let header = TiffHeader::parse(&header_bytes, reader.size())?;
234
235 let ifds = Self::parse_all_ifds(reader, &header).await?;
237
238 Self::build_pyramid(header, ifds)
240 }
241
242 async fn parse_all_ifds<R: RangeReader>(
244 reader: &R,
245 header: &TiffHeader,
246 ) -> Result<Vec<Ifd>, TiffError> {
247 let mut ifds = Vec::new();
248 let mut offset = header.first_ifd_offset;
249
250 while offset != 0 && ifds.len() < MAX_IFDS {
251 let count_size = header.ifd_count_size();
253 let count_bytes = reader.read_exact_at(offset, count_size).await?;
254
255 let entry_count = if header.is_bigtiff {
256 header.byte_order.read_u64(&count_bytes)
257 } else {
258 header.byte_order.read_u16(&count_bytes) as u64
259 };
260
261 let ifd_size = Ifd::calculate_size(entry_count, header);
263 let ifd_bytes = reader.read_exact_at(offset, ifd_size).await?;
264 let ifd = Ifd::parse(&ifd_bytes, header)?;
265
266 let next_offset = ifd.next_ifd_offset;
267 ifds.push(ifd);
268
269 offset = next_offset;
270 }
271
272 Ok(ifds)
273 }
274
275 fn build_pyramid(header: TiffHeader, ifds: Vec<Ifd>) -> Result<Self, TiffError> {
277 let byte_order = header.byte_order;
278
279 let mut pyramid_candidates: Vec<PyramidLevel> = Vec::new();
280 let mut other_ifds: Vec<(usize, Ifd)> = Vec::new();
281
282 for (ifd_index, ifd) in ifds.into_iter().enumerate() {
283 if let Some(level) = PyramidLevel::from_ifd(ifd.clone(), ifd_index, byte_order) {
285 if Self::is_pyramid_candidate(&level) {
287 pyramid_candidates.push(level);
288 } else {
289 other_ifds.push((ifd_index, ifd));
290 }
291 } else {
292 other_ifds.push((ifd_index, ifd));
294 }
295 }
296
297 pyramid_candidates.sort_by(|a, b| {
299 let area_a = (a.width as u64) * (a.height as u64);
300 let area_b = (b.width as u64) * (b.height as u64);
301 area_b.cmp(&area_a)
302 });
303
304 let levels = Self::filter_pyramid_levels(pyramid_candidates);
306
307 Ok(TiffPyramid {
308 header,
309 levels,
310 other_ifds,
311 })
312 }
313
314 fn is_pyramid_candidate(level: &PyramidLevel) -> bool {
316 if level.width < MIN_PYRAMID_DIMENSION || level.height < MIN_PYRAMID_DIMENSION {
318 return false;
319 }
320
321 if !level.has_tile_data() {
323 return false;
324 }
325
326 if level.width <= MAX_LABEL_DIMENSION && level.height <= MAX_LABEL_DIMENSION {
328 let aspect_ratio = level.width as f64 / level.height as f64;
329 if aspect_ratio > 0.5 && aspect_ratio < 2.0 {
331 if level.width <= 1000 && level.height <= 1000 {
333 return false;
334 }
335 }
336 }
337
338 true
339 }
340
341 fn filter_pyramid_levels(candidates: Vec<PyramidLevel>) -> Vec<PyramidLevel> {
343 if candidates.is_empty() {
344 return candidates;
345 }
346
347 let base_width = candidates[0].width as f64;
349 let base_height = candidates[0].height as f64;
350
351 let mut levels = Vec::new();
352
353 for (idx, mut level) in candidates.into_iter().enumerate() {
354 let downsample_x = base_width / level.width as f64;
356 let downsample_y = base_height / level.height as f64;
357
358 let downsample = (downsample_x + downsample_y) / 2.0;
360
361 if Self::is_valid_downsample(downsample, idx) {
364 level.level_index = levels.len();
365 level.downsample = downsample;
366 levels.push(level);
367 }
368 }
369
370 levels
371 }
372
373 fn is_valid_downsample(downsample: f64, level_idx: usize) -> bool {
375 if level_idx == 0 {
376 return (downsample - 1.0).abs() < 0.1;
378 }
379
380 let log2 = downsample.log2();
383 let rounded = log2.round();
384
385 if rounded < 1.0 {
387 return false;
388 }
389
390 let expected = 2.0_f64.powf(rounded);
392 let ratio = downsample / expected;
393
394 ratio > 0.8 && ratio < 1.2
396 }
397
398 pub fn level_count(&self) -> usize {
400 self.levels.len()
401 }
402
403 pub fn get_level(&self, level: usize) -> Option<&PyramidLevel> {
405 self.levels.get(level)
406 }
407
408 pub fn base_level(&self) -> Option<&PyramidLevel> {
410 self.levels.first()
411 }
412
413 pub fn dimensions(&self) -> Option<(u32, u32)> {
415 self.base_level().map(|l| (l.width, l.height))
416 }
417
418 pub fn best_level_for_downsample(&self, downsample: f64) -> Option<&PyramidLevel> {
422 self.levels
424 .iter()
425 .filter(|l| l.downsample >= downsample * 0.99) .min_by(|a, b| a.downsample.partial_cmp(&b.downsample).unwrap())
427 .or_else(|| self.levels.last()) }
429}
430
431#[derive(Debug, Clone)]
437pub struct TileData {
438 pub offsets: Vec<u64>,
440
441 pub byte_counts: Vec<u64>,
443
444 pub jpeg_tables: Option<Bytes>,
446}
447
448impl TileData {
449 pub async fn load<R: RangeReader>(
451 reader: &R,
452 level: &PyramidLevel,
453 header: &TiffHeader,
454 ) -> Result<Self, TiffError> {
455 let value_reader = ValueReader::new(reader, header);
456
457 let offsets = if let Some(ref entry) = level.tile_offsets_entry {
459 value_reader.read_u64_array(entry).await?
460 } else {
461 return Err(TiffError::MissingTag("TileOffsets"));
462 };
463
464 let byte_counts = if let Some(ref entry) = level.tile_byte_counts_entry {
466 value_reader.read_u64_array(entry).await?
467 } else {
468 return Err(TiffError::MissingTag("TileByteCounts"));
469 };
470
471 let jpeg_tables = if let Some(ref entry) = level.jpeg_tables_entry {
473 Some(value_reader.read_raw_bytes(entry).await?)
474 } else {
475 None
476 };
477
478 Ok(TileData {
479 offsets,
480 byte_counts,
481 jpeg_tables,
482 })
483 }
484
485 pub fn get_tile_location(&self, tile_index: u32) -> Option<(u64, u64)> {
487 let idx = tile_index as usize;
488 if idx >= self.offsets.len() || idx >= self.byte_counts.len() {
489 return None;
490 }
491 Some((self.offsets[idx], self.byte_counts[idx]))
492 }
493}
494
495#[cfg(test)]
500mod tests {
501 use super::*;
502
503 fn make_tiff_header() -> TiffHeader {
504 TiffHeader {
505 byte_order: ByteOrder::LittleEndian,
506 is_bigtiff: false,
507 first_ifd_offset: 8,
508 }
509 }
510
511 #[test]
516 fn test_tile_index() {
517 let level = PyramidLevel {
518 level_index: 0,
519 ifd_index: 0,
520 width: 1024,
521 height: 768,
522 tile_width: 256,
523 tile_height: 256,
524 tiles_x: 4,
525 tiles_y: 3,
526 tile_count: 12,
527 downsample: 1.0,
528 compression: 7,
529 ifd: create_mock_ifd(),
530 tile_offsets_entry: None,
531 tile_byte_counts_entry: None,
532 jpeg_tables_entry: None,
533 };
534
535 assert_eq!(level.tile_index(0, 0), Some(0));
537 assert_eq!(level.tile_index(1, 0), Some(1));
538 assert_eq!(level.tile_index(0, 1), Some(4));
539 assert_eq!(level.tile_index(3, 2), Some(11));
540
541 assert_eq!(level.tile_index(4, 0), None);
543 assert_eq!(level.tile_index(0, 3), None);
544 }
545
546 #[test]
547 fn test_tile_dimensions() {
548 let level = PyramidLevel {
549 level_index: 0,
550 ifd_index: 0,
551 width: 1000, height: 700,
553 tile_width: 256,
554 tile_height: 256,
555 tiles_x: 4, tiles_y: 3, tile_count: 12,
558 downsample: 1.0,
559 compression: 7,
560 ifd: create_mock_ifd(),
561 tile_offsets_entry: None,
562 tile_byte_counts_entry: None,
563 jpeg_tables_entry: None,
564 };
565
566 assert_eq!(level.tile_dimensions(0, 0), Some((256, 256)));
568 assert_eq!(level.tile_dimensions(1, 1), Some((256, 256)));
569
570 assert_eq!(level.tile_dimensions(3, 0), Some((232, 256)));
572
573 assert_eq!(level.tile_dimensions(0, 2), Some((256, 188)));
575
576 assert_eq!(level.tile_dimensions(3, 2), Some((232, 188)));
578
579 assert_eq!(level.tile_dimensions(4, 0), None);
581 }
582
583 #[test]
584 fn test_is_valid_downsample() {
585 assert!(TiffPyramid::is_valid_downsample(1.0, 0));
587 assert!(TiffPyramid::is_valid_downsample(1.05, 0));
588 assert!(!TiffPyramid::is_valid_downsample(2.0, 0));
589
590 assert!(TiffPyramid::is_valid_downsample(2.0, 1));
592 assert!(TiffPyramid::is_valid_downsample(4.0, 2));
593 assert!(TiffPyramid::is_valid_downsample(8.0, 3));
594 assert!(TiffPyramid::is_valid_downsample(16.0, 4));
595
596 assert!(TiffPyramid::is_valid_downsample(2.1, 1));
598 assert!(TiffPyramid::is_valid_downsample(3.9, 2));
599
600 assert!(!TiffPyramid::is_valid_downsample(1.5, 1)); assert!(!TiffPyramid::is_valid_downsample(3.0, 2)); }
604
605 #[test]
606 fn test_is_pyramid_candidate() {
607 let good_level = PyramidLevel {
609 level_index: 0,
610 ifd_index: 0,
611 width: 10000,
612 height: 8000,
613 tile_width: 256,
614 tile_height: 256,
615 tiles_x: 40,
616 tiles_y: 32,
617 tile_count: 1280,
618 downsample: 1.0,
619 compression: 7,
620 ifd: create_mock_ifd(),
621 tile_offsets_entry: Some(create_mock_entry()),
622 tile_byte_counts_entry: Some(create_mock_entry()),
623 jpeg_tables_entry: None,
624 };
625 assert!(TiffPyramid::is_pyramid_candidate(&good_level));
626
627 let small_level = PyramidLevel {
629 width: 100,
630 height: 100,
631 ..good_level.clone()
632 };
633 assert!(!TiffPyramid::is_pyramid_candidate(&small_level));
634
635 let no_tiles = PyramidLevel {
637 tile_offsets_entry: None,
638 ..good_level.clone()
639 };
640 assert!(!TiffPyramid::is_pyramid_candidate(&no_tiles));
641
642 let label_like = PyramidLevel {
644 width: 500,
645 height: 500,
646 tiles_x: 2,
647 tiles_y: 2,
648 tile_count: 4,
649 ..good_level.clone()
650 };
651 assert!(!TiffPyramid::is_pyramid_candidate(&label_like));
652 }
653
654 #[test]
655 fn test_best_level_for_downsample() {
656 let header = make_tiff_header();
657 let pyramid = TiffPyramid {
658 header,
659 levels: vec![
660 create_level_with_downsample(0, 1.0, 10000, 8000),
661 create_level_with_downsample(1, 4.0, 2500, 2000),
662 create_level_with_downsample(2, 16.0, 625, 500),
663 ],
664 other_ifds: vec![],
665 };
666
667 assert_eq!(
669 pyramid.best_level_for_downsample(1.0).unwrap().level_index,
670 0
671 );
672 assert_eq!(
673 pyramid.best_level_for_downsample(4.0).unwrap().level_index,
674 1
675 );
676 assert_eq!(
677 pyramid.best_level_for_downsample(16.0).unwrap().level_index,
678 2
679 );
680
681 assert_eq!(
683 pyramid.best_level_for_downsample(2.0).unwrap().level_index,
684 1
685 );
686 assert_eq!(
687 pyramid.best_level_for_downsample(8.0).unwrap().level_index,
688 2
689 );
690
691 assert_eq!(
693 pyramid.best_level_for_downsample(0.5).unwrap().level_index,
694 0
695 );
696
697 assert_eq!(
699 pyramid.best_level_for_downsample(32.0).unwrap().level_index,
700 2
701 );
702 }
703
704 fn create_mock_ifd() -> Ifd {
709 Ifd::empty()
710 }
711
712 fn create_mock_entry() -> IfdEntry {
713 IfdEntry {
714 tag_id: 324,
715 field_type: Some(super::super::tags::FieldType::Long),
716 field_type_raw: 4,
717 count: 1,
718 value_offset_bytes: vec![0, 0, 0, 0],
719 is_inline: true,
720 }
721 }
722
723 fn create_level_with_downsample(
724 level_index: usize,
725 downsample: f64,
726 width: u32,
727 height: u32,
728 ) -> PyramidLevel {
729 PyramidLevel {
730 level_index,
731 ifd_index: level_index,
732 width,
733 height,
734 tile_width: 256,
735 tile_height: 256,
736 tiles_x: width.div_ceil(256),
737 tiles_y: height.div_ceil(256),
738 tile_count: width.div_ceil(256) * height.div_ceil(256),
739 downsample,
740 compression: 7,
741 ifd: create_mock_ifd(),
742 tile_offsets_entry: Some(create_mock_entry()),
743 tile_byte_counts_entry: Some(create_mock_entry()),
744 jpeg_tables_entry: None,
745 }
746 }
747}