async_tiff/
ifd.rs

1use std::collections::HashMap;
2use std::io::Read;
3use std::ops::Range;
4
5use bytes::Bytes;
6use num_enum::TryFromPrimitive;
7
8use crate::error::{AsyncTiffError, AsyncTiffResult};
9use crate::geo::{GeoKeyDirectory, GeoKeyTag};
10use crate::reader::{AsyncCursor, AsyncFileReader};
11use crate::tiff::tags::{
12    CompressionMethod, PhotometricInterpretation, PlanarConfiguration, Predictor, ResolutionUnit,
13    SampleFormat, Tag, Type,
14};
15use crate::tiff::{TiffError, Value};
16use crate::tile::Tile;
17
18const DOCUMENT_NAME: u16 = 269;
19
20/// A collection of all the IFD
21// TODO: maybe separate out the primary/first image IFD out of the vec, as that one should have
22// geospatial metadata?
23#[derive(Debug, Clone)]
24pub struct ImageFileDirectories {
25    /// There's always at least one IFD in a TIFF. We store this separately
26    ifds: Vec<ImageFileDirectory>,
27    // Is it guaranteed that if masks exist that there will be one per image IFD? Or could there be
28    // different numbers of image ifds and mask ifds?
29    // mask_ifds: Option<Vec<IFD>>,
30}
31
32impl AsRef<[ImageFileDirectory]> for ImageFileDirectories {
33    fn as_ref(&self) -> &[ImageFileDirectory] {
34        &self.ifds
35    }
36}
37
38impl ImageFileDirectories {
39    pub(crate) async fn open(
40        cursor: &mut AsyncCursor,
41        ifd_offset: u64,
42        bigtiff: bool,
43    ) -> AsyncTiffResult<Self> {
44        let mut next_ifd_offset = Some(ifd_offset);
45
46        let mut ifds = vec![];
47        while let Some(offset) = next_ifd_offset {
48            let ifd = ImageFileDirectory::read(cursor, offset, bigtiff).await?;
49            next_ifd_offset = ifd.next_ifd_offset();
50            ifds.push(ifd);
51        }
52
53        Ok(Self { ifds })
54    }
55}
56
57/// An ImageFileDirectory representing Image content
58// The ordering of these tags matches the sorted order in TIFF spec Appendix A
59#[allow(dead_code)]
60#[derive(Debug, Clone)]
61pub struct ImageFileDirectory {
62    pub(crate) new_subfile_type: Option<u32>,
63
64    /// The number of columns in the image, i.e., the number of pixels per row.
65    pub(crate) image_width: u32,
66
67    /// The number of rows of pixels in the image.
68    pub(crate) image_height: u32,
69
70    pub(crate) bits_per_sample: Vec<u16>,
71
72    pub(crate) compression: CompressionMethod,
73
74    pub(crate) photometric_interpretation: PhotometricInterpretation,
75
76    pub(crate) document_name: Option<String>,
77
78    pub(crate) image_description: Option<String>,
79
80    pub(crate) strip_offsets: Option<Vec<u64>>,
81
82    pub(crate) orientation: Option<u16>,
83
84    /// The number of components per pixel.
85    ///
86    /// SamplesPerPixel is usually 1 for bilevel, grayscale, and palette-color images.
87    /// SamplesPerPixel is usually 3 for RGB images. If this value is higher, ExtraSamples should
88    /// give an indication of the meaning of the additional channels.
89    pub(crate) samples_per_pixel: u16,
90
91    pub(crate) rows_per_strip: Option<u32>,
92
93    pub(crate) strip_byte_counts: Option<Vec<u64>>,
94
95    pub(crate) min_sample_value: Option<Vec<u16>>,
96    pub(crate) max_sample_value: Option<Vec<u16>>,
97
98    /// The number of pixels per ResolutionUnit in the ImageWidth direction.
99    pub(crate) x_resolution: Option<f64>,
100
101    /// The number of pixels per ResolutionUnit in the ImageLength direction.
102    pub(crate) y_resolution: Option<f64>,
103
104    /// How the components of each pixel are stored.
105    ///
106    /// The specification defines these values:
107    ///
108    /// - Chunky format. The component values for each pixel are stored contiguously. For example,
109    ///   for RGB data, the data is stored as RGBRGBRGB
110    /// - Planar format. The components are stored in separate component planes. For example, RGB
111    ///   data is stored with the Red components in one component plane, the Green in another, and
112    ///   the Blue in another.
113    ///
114    /// The specification adds a warning that PlanarConfiguration=2 is not in widespread use and
115    /// that Baseline TIFF readers are not required to support it.
116    ///
117    /// If SamplesPerPixel is 1, PlanarConfiguration is irrelevant, and need not be included.
118    pub(crate) planar_configuration: PlanarConfiguration,
119
120    pub(crate) resolution_unit: Option<ResolutionUnit>,
121
122    /// Name and version number of the software package(s) used to create the image.
123    pub(crate) software: Option<String>,
124
125    /// Date and time of image creation.
126    ///
127    /// The format is: "YYYY:MM:DD HH:MM:SS", with hours like those on a 24-hour clock, and one
128    /// space character between the date and the time. The length of the string, including the
129    /// terminating NUL, is 20 bytes.
130    pub(crate) date_time: Option<String>,
131    pub(crate) artist: Option<String>,
132    pub(crate) host_computer: Option<String>,
133
134    pub(crate) predictor: Option<Predictor>,
135
136    /// A color map for palette color images.
137    ///
138    /// This field defines a Red-Green-Blue color map (often called a lookup table) for
139    /// palette-color images. In a palette-color image, a pixel value is used to index into an RGB
140    /// lookup table. For example, a palette-color pixel having a value of 0 would be displayed
141    /// according to the 0th Red, Green, Blue triplet.
142    ///
143    /// In a TIFF ColorMap, all the Red values come first, followed by the Green values, then the
144    /// Blue values. The number of values for each color is 2**BitsPerSample. Therefore, the
145    /// ColorMap field for an 8-bit palette-color image would have 3 * 256 values. The width of
146    /// each value is 16 bits, as implied by the type of SHORT. 0 represents the minimum intensity,
147    /// and 65535 represents the maximum intensity. Black is represented by 0,0,0, and white by
148    /// 65535, 65535, 65535.
149    ///
150    /// ColorMap must be included in all palette-color images.
151    ///
152    /// In Specification Supplement 1, support was added for ColorMaps containing other then RGB
153    /// values. This scheme includes the Indexed tag, with value 1, and a PhotometricInterpretation
154    /// different from PaletteColor then next denotes the colorspace of the ColorMap entries.
155    pub(crate) color_map: Option<Vec<u16>>,
156
157    pub(crate) tile_width: Option<u32>,
158    pub(crate) tile_height: Option<u32>,
159
160    pub(crate) tile_offsets: Option<Vec<u64>>,
161    pub(crate) tile_byte_counts: Option<Vec<u64>>,
162
163    pub(crate) extra_samples: Option<Vec<u16>>,
164
165    pub(crate) sample_format: Vec<SampleFormat>,
166
167    pub(crate) jpeg_tables: Option<Bytes>,
168
169    pub(crate) copyright: Option<String>,
170
171    // Geospatial tags
172    pub(crate) geo_key_directory: Option<GeoKeyDirectory>,
173    pub(crate) model_pixel_scale: Option<Vec<f64>>,
174    pub(crate) model_tiepoint: Option<Vec<f64>>,
175
176    // GDAL tags
177    // no_data
178    // gdal_metadata
179    pub(crate) other_tags: HashMap<Tag, Value>,
180
181    pub(crate) next_ifd_offset: Option<u64>,
182}
183
184impl ImageFileDirectory {
185    /// Read and parse the IFD starting at the given file offset
186    async fn read(
187        cursor: &mut AsyncCursor,
188        ifd_start: u64,
189        bigtiff: bool,
190    ) -> AsyncTiffResult<Self> {
191        cursor.seek(ifd_start);
192
193        let tag_count = if bigtiff {
194            cursor.read_u64().await?
195        } else {
196            cursor.read_u16().await?.into()
197        };
198        let mut tags = HashMap::with_capacity(tag_count as usize);
199        for _ in 0..tag_count {
200            let (tag_name, tag_value) = read_tag(cursor, bigtiff).await?;
201            tags.insert(tag_name, tag_value);
202        }
203
204        // Tag   2 bytes
205        // Type  2 bytes
206        // Count:
207        //  - bigtiff: 8 bytes
208        //  - else: 4 bytes
209        // Value:
210        //  - bigtiff: 8 bytes either a pointer the value itself
211        //  - else: 4 bytes either a pointer the value itself
212        let ifd_entry_byte_size = if bigtiff { 20 } else { 12 };
213        // The size of `tag_count` that we read above
214        let tag_count_byte_size = if bigtiff { 8 } else { 2 };
215
216        // Reset the cursor position before reading the next ifd offset
217        cursor.seek(ifd_start + (ifd_entry_byte_size * tag_count) + tag_count_byte_size);
218
219        let next_ifd_offset = if bigtiff {
220            cursor.read_u64().await?
221        } else {
222            cursor.read_u32().await?.into()
223        };
224
225        // If the ifd_offset is 0, stop
226        let next_ifd_offset = if next_ifd_offset == 0 {
227            None
228        } else {
229            Some(next_ifd_offset)
230        };
231
232        Self::from_tags(tags, next_ifd_offset)
233    }
234
235    fn next_ifd_offset(&self) -> Option<u64> {
236        self.next_ifd_offset
237    }
238
239    fn from_tags(
240        mut tag_data: HashMap<Tag, Value>,
241        next_ifd_offset: Option<u64>,
242    ) -> AsyncTiffResult<Self> {
243        let mut new_subfile_type = None;
244        let mut image_width = None;
245        let mut image_height = None;
246        let mut bits_per_sample = None;
247        let mut compression = None;
248        let mut photometric_interpretation = None;
249        let mut document_name = None;
250        let mut image_description = None;
251        let mut strip_offsets = None;
252        let mut orientation = None;
253        let mut samples_per_pixel = None;
254        let mut rows_per_strip = None;
255        let mut strip_byte_counts = None;
256        let mut min_sample_value = None;
257        let mut max_sample_value = None;
258        let mut x_resolution = None;
259        let mut y_resolution = None;
260        let mut planar_configuration = None;
261        let mut resolution_unit = None;
262        let mut software = None;
263        let mut date_time = None;
264        let mut artist = None;
265        let mut host_computer = None;
266        let mut predictor = None;
267        let mut color_map = None;
268        let mut tile_width = None;
269        let mut tile_height = None;
270        let mut tile_offsets = None;
271        let mut tile_byte_counts = None;
272        let mut extra_samples = None;
273        let mut sample_format = None;
274        let mut jpeg_tables = None;
275        let mut copyright = None;
276        let mut geo_key_directory_data = None;
277        let mut model_pixel_scale = None;
278        let mut model_tiepoint = None;
279        let mut geo_ascii_params: Option<String> = None;
280        let mut geo_double_params: Option<Vec<f64>> = None;
281
282        let mut other_tags = HashMap::new();
283
284        tag_data.drain().try_for_each(|(tag, value)| {
285            match tag {
286                Tag::NewSubfileType => new_subfile_type = Some(value.into_u32()?),
287                Tag::ImageWidth => image_width = Some(value.into_u32()?),
288                Tag::ImageLength => image_height = Some(value.into_u32()?),
289                Tag::BitsPerSample => bits_per_sample = Some(value.into_u16_vec()?),
290                Tag::Compression => {
291                    compression = Some(CompressionMethod::from_u16_exhaustive(value.into_u16()?))
292                }
293                Tag::PhotometricInterpretation => {
294                    photometric_interpretation =
295                        PhotometricInterpretation::from_u16(value.into_u16()?)
296                }
297                Tag::ImageDescription => image_description = Some(value.into_string()?),
298                Tag::StripOffsets => strip_offsets = Some(value.into_u64_vec()?),
299                Tag::Orientation => orientation = Some(value.into_u16()?),
300                Tag::SamplesPerPixel => samples_per_pixel = Some(value.into_u16()?),
301                Tag::RowsPerStrip => rows_per_strip = Some(value.into_u32()?),
302                Tag::StripByteCounts => strip_byte_counts = Some(value.into_u64_vec()?),
303                Tag::MinSampleValue => min_sample_value = Some(value.into_u16_vec()?),
304                Tag::MaxSampleValue => max_sample_value = Some(value.into_u16_vec()?),
305                Tag::XResolution => match value {
306                    Value::Rational(n, d) => x_resolution = Some(n as f64 / d as f64),
307                    _ => unreachable!("Expected rational type for XResolution."),
308                },
309                Tag::YResolution => match value {
310                    Value::Rational(n, d) => y_resolution = Some(n as f64 / d as f64),
311                    _ => unreachable!("Expected rational type for YResolution."),
312                },
313                Tag::PlanarConfiguration => {
314                    planar_configuration = PlanarConfiguration::from_u16(value.into_u16()?)
315                }
316                Tag::ResolutionUnit => {
317                    resolution_unit = ResolutionUnit::from_u16(value.into_u16()?)
318                }
319                Tag::Software => software = Some(value.into_string()?),
320                Tag::DateTime => date_time = Some(value.into_string()?),
321                Tag::Artist => artist = Some(value.into_string()?),
322                Tag::HostComputer => host_computer = Some(value.into_string()?),
323                Tag::Predictor => predictor = Predictor::from_u16(value.into_u16()?),
324                Tag::ColorMap => color_map = Some(value.into_u16_vec()?),
325                Tag::TileWidth => tile_width = Some(value.into_u32()?),
326                Tag::TileLength => tile_height = Some(value.into_u32()?),
327                Tag::TileOffsets => tile_offsets = Some(value.into_u64_vec()?),
328                Tag::TileByteCounts => tile_byte_counts = Some(value.into_u64_vec()?),
329                Tag::ExtraSamples => extra_samples = Some(value.into_u16_vec()?),
330                Tag::SampleFormat => {
331                    let values = value.into_u16_vec()?;
332                    sample_format = Some(
333                        values
334                            .into_iter()
335                            .map(SampleFormat::from_u16_exhaustive)
336                            .collect(),
337                    );
338                }
339                Tag::JPEGTables => jpeg_tables = Some(value.into_u8_vec()?.into()),
340                Tag::Copyright => copyright = Some(value.into_string()?),
341
342                // Geospatial tags
343                // http://geotiff.maptools.org/spec/geotiff2.4.html
344                Tag::GeoKeyDirectoryTag => geo_key_directory_data = Some(value.into_u16_vec()?),
345                Tag::ModelPixelScaleTag => model_pixel_scale = Some(value.into_f64_vec()?),
346                Tag::ModelTiepointTag => model_tiepoint = Some(value.into_f64_vec()?),
347                Tag::GeoAsciiParamsTag => geo_ascii_params = Some(value.into_string()?),
348                Tag::GeoDoubleParamsTag => geo_double_params = Some(value.into_f64_vec()?),
349                // Tag::GdalNodata
350                // Tags for which the tiff crate doesn't have a hard-coded enum variant
351                Tag::Unknown(DOCUMENT_NAME) => document_name = Some(value.into_string()?),
352                _ => {
353                    other_tags.insert(tag, value);
354                }
355            };
356            Ok::<_, TiffError>(())
357        })?;
358
359        let mut geo_key_directory = None;
360
361        // We need to actually parse the GeoKeyDirectory after parsing all other tags because the
362        // GeoKeyDirectory relies on `GeoAsciiParamsTag` having been parsed.
363        if let Some(data) = geo_key_directory_data {
364            let mut chunks = data.chunks(4);
365
366            let header = chunks
367                .next()
368                .expect("If the geo key directory exists, a header should exist.");
369            let key_directory_version = header[0];
370            assert_eq!(key_directory_version, 1);
371
372            let key_revision = header[1];
373            assert_eq!(key_revision, 1);
374
375            let _key_minor_revision = header[2];
376            let number_of_keys = header[3];
377
378            let mut tags = HashMap::with_capacity(number_of_keys as usize);
379            for _ in 0..number_of_keys {
380                let chunk = chunks
381                    .next()
382                    .expect("There should be a chunk for each key.");
383
384                let key_id = chunk[0];
385                let tag_name =
386                    GeoKeyTag::try_from_primitive(key_id).expect("Unknown GeoKeyTag id: {key_id}");
387
388                let tag_location = chunk[1];
389                let count = chunk[2];
390                let value_offset = chunk[3];
391
392                if tag_location == 0 {
393                    tags.insert(tag_name, Value::Short(value_offset));
394                } else if Tag::from_u16_exhaustive(tag_location) == Tag::GeoAsciiParamsTag {
395                    // If the tag_location points to the value of Tag::GeoAsciiParamsTag, then we
396                    // need to extract a subslice from GeoAsciiParamsTag
397
398                    let geo_ascii_params = geo_ascii_params
399                        .as_ref()
400                        .expect("GeoAsciiParamsTag exists but geo_ascii_params does not.");
401                    let value_offset = value_offset as usize;
402                    let mut s = &geo_ascii_params[value_offset..value_offset + count as usize];
403
404                    // It seems that this string subslice might always include the final |
405                    // character?
406                    if s.ends_with('|') {
407                        s = &s[0..s.len() - 1];
408                    }
409
410                    tags.insert(tag_name, Value::Ascii(s.to_string()));
411                } else if Tag::from_u16_exhaustive(tag_location) == Tag::GeoDoubleParamsTag {
412                    // If the tag_location points to the value of Tag::GeoDoubleParamsTag, then we
413                    // need to extract a subslice from GeoDoubleParamsTag
414
415                    let geo_double_params = geo_double_params
416                        .as_ref()
417                        .expect("GeoDoubleParamsTag exists but geo_double_params does not.");
418                    let value_offset = value_offset as usize;
419                    let value = if count == 1 {
420                        Value::Double(geo_double_params[value_offset])
421                    } else {
422                        let x = geo_double_params[value_offset..value_offset + count as usize]
423                            .iter()
424                            .map(|val| Value::Double(*val))
425                            .collect();
426                        Value::List(x)
427                    };
428                    tags.insert(tag_name, value);
429                }
430            }
431            geo_key_directory = Some(GeoKeyDirectory::from_tags(tags)?);
432        }
433
434        let samples_per_pixel = samples_per_pixel.expect("samples_per_pixel not found");
435        let planar_configuration = if let Some(planar_configuration) = planar_configuration {
436            planar_configuration
437        } else if samples_per_pixel == 1 {
438            // If SamplesPerPixel is 1, PlanarConfiguration is irrelevant, and need not be included.
439            // https://web.archive.org/web/20240329145253/https://www.awaresystems.be/imaging/tiff/tifftags/planarconfiguration.html
440            PlanarConfiguration::Chunky
441        } else {
442            PlanarConfiguration::Chunky
443        };
444        Ok(Self {
445            new_subfile_type,
446            image_width: image_width.expect("image_width not found"),
447            image_height: image_height.expect("image_height not found"),
448            bits_per_sample: bits_per_sample.expect("bits per sample not found"),
449            // Defaults to no compression
450            // https://web.archive.org/web/20240329145331/https://www.awaresystems.be/imaging/tiff/tifftags/compression.html
451            compression: compression.unwrap_or(CompressionMethod::None),
452            photometric_interpretation: photometric_interpretation
453                .expect("photometric interpretation not found"),
454            document_name,
455            image_description,
456            strip_offsets,
457            orientation,
458            samples_per_pixel,
459            rows_per_strip,
460            strip_byte_counts,
461            min_sample_value,
462            max_sample_value,
463            x_resolution,
464            y_resolution,
465            planar_configuration,
466            resolution_unit,
467            software,
468            date_time,
469            artist,
470            host_computer,
471            predictor,
472            color_map,
473            tile_width,
474            tile_height,
475            tile_offsets,
476            tile_byte_counts,
477            extra_samples,
478            // Uint8 is the default for SampleFormat
479            // https://web.archive.org/web/20240329145340/https://www.awaresystems.be/imaging/tiff/tifftags/sampleformat.html
480            sample_format: sample_format
481                .unwrap_or(vec![SampleFormat::Uint; samples_per_pixel as _]),
482            copyright,
483            jpeg_tables,
484            geo_key_directory,
485            model_pixel_scale,
486            model_tiepoint,
487            other_tags,
488            next_ifd_offset,
489        })
490    }
491
492    /// A general indication of the kind of data contained in this subfile.
493    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/newsubfiletype.html>
494    pub fn new_subfile_type(&self) -> Option<u32> {
495        self.new_subfile_type
496    }
497
498    /// The number of columns in the image, i.e., the number of pixels per row.
499    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/imagewidth.html>
500    pub fn image_width(&self) -> u32 {
501        self.image_width
502    }
503
504    /// The number of rows of pixels in the image.
505    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/imagelength.html>
506    pub fn image_height(&self) -> u32 {
507        self.image_height
508    }
509
510    /// Number of bits per component.
511    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/bitspersample.html>
512    pub fn bits_per_sample(&self) -> &[u16] {
513        &self.bits_per_sample
514    }
515
516    /// Compression scheme used on the image data.
517    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/compression.html>
518    pub fn compression(&self) -> CompressionMethod {
519        self.compression
520    }
521
522    /// The color space of the image data.
523    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/photometricinterpretation.html>
524    pub fn photometric_interpretation(&self) -> PhotometricInterpretation {
525        self.photometric_interpretation
526    }
527
528    /// Document name.
529    pub fn document_name(&self) -> Option<&str> {
530        self.document_name.as_deref()
531    }
532
533    /// A string that describes the subject of the image.
534    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/imagedescription.html>
535    pub fn image_description(&self) -> Option<&str> {
536        self.image_description.as_deref()
537    }
538
539    /// For each strip, the byte offset of that strip.
540    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/stripoffsets.html>
541    pub fn strip_offsets(&self) -> Option<&[u64]> {
542        self.strip_offsets.as_deref()
543    }
544
545    /// The orientation of the image with respect to the rows and columns.
546    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/orientation.html>
547    pub fn orientation(&self) -> Option<u16> {
548        self.orientation
549    }
550
551    /// The number of components per pixel.
552    ///
553    /// SamplesPerPixel is usually 1 for bilevel, grayscale, and palette-color images.
554    /// SamplesPerPixel is usually 3 for RGB images. If this value is higher, ExtraSamples should
555    /// give an indication of the meaning of the additional channels.
556    pub fn samples_per_pixel(&self) -> u16 {
557        self.samples_per_pixel
558    }
559
560    /// The number of rows per strip.
561    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/rowsperstrip.html>
562    pub fn rows_per_strip(&self) -> Option<u32> {
563        self.rows_per_strip
564    }
565
566    /// For each strip, the number of bytes in the strip after compression.
567    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/stripbytecounts.html>
568    pub fn strip_byte_counts(&self) -> Option<&[u64]> {
569        self.strip_byte_counts.as_deref()
570    }
571
572    /// The minimum component value used.
573    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/minsamplevalue.html>
574    pub fn min_sample_value(&self) -> Option<&[u16]> {
575        self.min_sample_value.as_deref()
576    }
577
578    /// The maximum component value used.
579    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/maxsamplevalue.html>
580    pub fn max_sample_value(&self) -> Option<&[u16]> {
581        self.max_sample_value.as_deref()
582    }
583
584    /// The number of pixels per ResolutionUnit in the ImageWidth direction.
585    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/xresolution.html>
586    pub fn x_resolution(&self) -> Option<f64> {
587        self.x_resolution
588    }
589
590    /// The number of pixels per ResolutionUnit in the ImageLength direction.
591    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/yresolution.html>
592    pub fn y_resolution(&self) -> Option<f64> {
593        self.y_resolution
594    }
595
596    /// How the components of each pixel are stored.
597    ///
598    /// The specification defines these values:
599    ///
600    /// - Chunky format. The component values for each pixel are stored contiguously. For example,
601    ///   for RGB data, the data is stored as RGBRGBRGB
602    /// - Planar format. The components are stored in separate component planes. For example, RGB
603    ///   data is stored with the Red components in one component plane, the Green in another, and
604    ///   the Blue in another.
605    ///
606    /// The specification adds a warning that PlanarConfiguration=2 is not in widespread use and
607    /// that Baseline TIFF readers are not required to support it.
608    ///
609    /// If SamplesPerPixel is 1, PlanarConfiguration is irrelevant, and need not be included.
610    ///
611    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/planarconfiguration.html>
612    pub fn planar_configuration(&self) -> PlanarConfiguration {
613        self.planar_configuration
614    }
615
616    /// The unit of measurement for XResolution and YResolution.
617    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/resolutionunit.html>
618    pub fn resolution_unit(&self) -> Option<ResolutionUnit> {
619        self.resolution_unit
620    }
621
622    /// Name and version number of the software package(s) used to create the image.
623    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/software.html>
624    pub fn software(&self) -> Option<&str> {
625        self.software.as_deref()
626    }
627
628    /// Date and time of image creation.
629    ///
630    /// The format is: "YYYY:MM:DD HH:MM:SS", with hours like those on a 24-hour clock, and one
631    /// space character between the date and the time. The length of the string, including the
632    /// terminating NUL, is 20 bytes.
633    ///
634    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/datetime.html>
635    pub fn date_time(&self) -> Option<&str> {
636        self.date_time.as_deref()
637    }
638
639    /// Person who created the image.
640    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/artist.html>
641    pub fn artist(&self) -> Option<&str> {
642        self.artist.as_deref()
643    }
644
645    /// The computer and/or operating system in use at the time of image creation.
646    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/hostcomputer.html>
647    pub fn host_computer(&self) -> Option<&str> {
648        self.host_computer.as_deref()
649    }
650
651    /// A mathematical operator that is applied to the image data before an encoding scheme is
652    /// applied.
653    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/predictor.html>
654    pub fn predictor(&self) -> Option<Predictor> {
655        self.predictor
656    }
657
658    /// The tile width in pixels. This is the number of columns in each tile.
659    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/tilewidth.html>
660    pub fn tile_width(&self) -> Option<u32> {
661        self.tile_width
662    }
663
664    /// The tile length (height) in pixels. This is the number of rows in each tile.
665    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/tilelength.html>
666    pub fn tile_height(&self) -> Option<u32> {
667        self.tile_height
668    }
669
670    /// For each tile, the byte offset of that tile, as compressed and stored on disk.
671    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/tileoffsets.html>
672    pub fn tile_offsets(&self) -> Option<&[u64]> {
673        self.tile_offsets.as_deref()
674    }
675
676    /// For each tile, the number of (compressed) bytes in that tile.
677    /// <https://web.archive.org/web/20240329145339/https://www.awaresystems.be/imaging/tiff/tifftags/tilebytecounts.html>
678    pub fn tile_byte_counts(&self) -> Option<&[u64]> {
679        self.tile_byte_counts.as_deref()
680    }
681
682    /// Description of extra components.
683    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/extrasamples.html>
684    pub fn extra_samples(&self) -> Option<&[u16]> {
685        self.extra_samples.as_deref()
686    }
687
688    /// Specifies how to interpret each data sample in a pixel.
689    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/sampleformat.html>
690    pub fn sample_format(&self) -> &[SampleFormat] {
691        &self.sample_format
692    }
693
694    /// JPEG quantization and/or Huffman tables.
695    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/jpegtables.html>
696    pub fn jpeg_tables(&self) -> Option<&[u8]> {
697        self.jpeg_tables.as_deref()
698    }
699
700    /// Copyright notice.
701    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/copyright.html>
702    pub fn copyright(&self) -> Option<&str> {
703        self.copyright.as_deref()
704    }
705
706    /// Geospatial tags
707    /// <https://web.archive.org/web/20240329145313/https://www.awaresystems.be/imaging/tiff/tifftags/geokeydirectorytag.html>
708    pub fn geo_key_directory(&self) -> Option<&GeoKeyDirectory> {
709        self.geo_key_directory.as_ref()
710    }
711
712    /// Used in interchangeable GeoTIFF files.
713    /// <https://web.archive.org/web/20240329145238/https://www.awaresystems.be/imaging/tiff/tifftags/modelpixelscaletag.html>
714    pub fn model_pixel_scale(&self) -> Option<&[f64]> {
715        self.model_pixel_scale.as_deref()
716    }
717
718    /// Used in interchangeable GeoTIFF files.
719    /// <https://web.archive.org/web/20240329145303/https://www.awaresystems.be/imaging/tiff/tifftags/modeltiepointtag.html>
720    pub fn model_tiepoint(&self) -> Option<&[f64]> {
721        self.model_tiepoint.as_deref()
722    }
723
724    /// Construct colormap from colormap tag
725    pub fn colormap(&self) -> Option<HashMap<usize, [u8; 3]>> {
726        fn cmap_transform(val: u16) -> u8 {
727            let val = ((val as f64 / 65535.0) * 255.0).floor();
728            if val >= 255.0 {
729                255
730            } else if val < 0.0 {
731                0
732            } else {
733                val as u8
734            }
735        }
736
737        if let Some(cmap_data) = &self.color_map {
738            let bits_per_sample = self.bits_per_sample[0];
739            let count = 2_usize.pow(bits_per_sample as u32);
740            let mut result = HashMap::new();
741
742            // TODO: support nodata
743            for idx in 0..count {
744                let color: [u8; 3] =
745                    std::array::from_fn(|i| cmap_transform(cmap_data[idx + i * count]));
746                // TODO: Handle nodata value
747
748                result.insert(idx, color);
749            }
750
751            Some(result)
752        } else {
753            None
754        }
755    }
756
757    fn get_tile_byte_range(&self, x: usize, y: usize) -> Option<Range<u64>> {
758        let tile_offsets = self.tile_offsets.as_deref()?;
759        let tile_byte_counts = self.tile_byte_counts.as_deref()?;
760        let idx = (y * self.tile_count()?.0) + x;
761        let offset = tile_offsets[idx] as usize;
762        // TODO: aiocogeo has a -1 here, but I think that was in error
763        let byte_count = tile_byte_counts[idx] as usize;
764        Some(offset as _..(offset + byte_count) as _)
765    }
766
767    /// Fetch the tile located at `x` column and `y` row using the provided reader.
768    pub async fn fetch_tile(
769        &self,
770        x: usize,
771        y: usize,
772        reader: &dyn AsyncFileReader,
773    ) -> AsyncTiffResult<Tile> {
774        let range = self
775            .get_tile_byte_range(x, y)
776            .ok_or(AsyncTiffError::General("Not a tiled TIFF".to_string()))?;
777        let compressed_bytes = reader.get_bytes(range).await?;
778        Ok(Tile {
779            x,
780            y,
781            compressed_bytes,
782            compression_method: self.compression,
783            photometric_interpretation: self.photometric_interpretation,
784            jpeg_tables: self.jpeg_tables.clone(),
785        })
786    }
787
788    /// Fetch the tiles located at `x` column and `y` row using the provided reader.
789    pub async fn fetch_tiles(
790        &self,
791        x: &[usize],
792        y: &[usize],
793        reader: &dyn AsyncFileReader,
794    ) -> AsyncTiffResult<Vec<Tile>> {
795        assert_eq!(x.len(), y.len(), "x and y should have same len");
796
797        // 1: Get all the byte ranges for all tiles
798        let byte_ranges = x
799            .iter()
800            .zip(y)
801            .map(|(x, y)| {
802                self.get_tile_byte_range(*x, *y)
803                    .ok_or(AsyncTiffError::General("Not a tiled TIFF".to_string()))
804            })
805            .collect::<AsyncTiffResult<Vec<_>>>()?;
806
807        // 2: Fetch using `get_ranges
808        let buffers = reader.get_byte_ranges(byte_ranges).await?;
809
810        // 3: Create tile objects
811        let mut tiles = vec![];
812        for ((compressed_bytes, &x), &y) in buffers.into_iter().zip(x).zip(y) {
813            let tile = Tile {
814                x,
815                y,
816                compressed_bytes,
817                compression_method: self.compression,
818                photometric_interpretation: self.photometric_interpretation,
819                jpeg_tables: self.jpeg_tables.clone(),
820            };
821            tiles.push(tile);
822        }
823        Ok(tiles)
824    }
825
826    /// Return the number of x/y tiles in the IFD
827    /// Returns `None` if this is not a tiled TIFF
828    pub fn tile_count(&self) -> Option<(usize, usize)> {
829        let x_count = (self.image_width as f64 / self.tile_width? as f64).ceil();
830        let y_count = (self.image_height as f64 / self.tile_height? as f64).ceil();
831        Some((x_count as usize, y_count as usize))
832    }
833}
834
835/// Read a single tag from the cursor
836async fn read_tag(cursor: &mut AsyncCursor, bigtiff: bool) -> AsyncTiffResult<(Tag, Value)> {
837    let start_cursor_position = cursor.position();
838
839    let tag_name = Tag::from_u16_exhaustive(cursor.read_u16().await?);
840
841    let tag_type_code = cursor.read_u16().await?;
842    let tag_type = Type::from_u16(tag_type_code).expect(
843        "Unknown tag type {tag_type_code}. TODO: we should skip entries with unknown tag types.",
844    );
845    let count = if bigtiff {
846        cursor.read_u64().await?
847    } else {
848        cursor.read_u32().await?.into()
849    };
850
851    let tag_value = read_tag_value(cursor, tag_type, count, bigtiff).await?;
852
853    // TODO: better handle management of cursor state
854    let ifd_entry_size = if bigtiff { 20 } else { 12 };
855    cursor.seek(start_cursor_position + ifd_entry_size);
856
857    Ok((tag_name, tag_value))
858}
859
860/// Read a tag's value from the cursor
861///
862/// NOTE: this does not maintain cursor state
863// This is derived from the upstream tiff crate:
864// https://github.com/image-rs/image-tiff/blob/6dc7a266d30291db1e706c8133357931f9e2a053/src/decoder/ifd.rs#L369-L639
865async fn read_tag_value(
866    cursor: &mut AsyncCursor,
867    tag_type: Type,
868    count: u64,
869    bigtiff: bool,
870) -> AsyncTiffResult<Value> {
871    // Case 1: there are no values so we can return immediately.
872    if count == 0 {
873        return Ok(Value::List(vec![]));
874    }
875
876    let tag_size = match tag_type {
877        Type::BYTE | Type::SBYTE | Type::ASCII | Type::UNDEFINED => 1,
878        Type::SHORT | Type::SSHORT => 2,
879        Type::LONG | Type::SLONG | Type::FLOAT | Type::IFD => 4,
880        Type::LONG8
881        | Type::SLONG8
882        | Type::DOUBLE
883        | Type::RATIONAL
884        | Type::SRATIONAL
885        | Type::IFD8 => 8,
886    };
887
888    let value_byte_length = count.checked_mul(tag_size).unwrap();
889
890    // Case 2: there is one value.
891    if count == 1 {
892        // 2a: the value is 5-8 bytes and we're in BigTiff mode.
893        if bigtiff && value_byte_length > 4 && value_byte_length <= 8 {
894            let mut data = cursor.read(value_byte_length).await?;
895
896            return Ok(match tag_type {
897                Type::LONG8 => Value::UnsignedBig(data.read_u64()?),
898                Type::SLONG8 => Value::SignedBig(data.read_i64()?),
899                Type::DOUBLE => Value::Double(data.read_f64()?),
900                Type::RATIONAL => Value::Rational(data.read_u32()?, data.read_u32()?),
901                Type::SRATIONAL => Value::SRational(data.read_i32()?, data.read_i32()?),
902                Type::IFD8 => Value::IfdBig(data.read_u64()?),
903                Type::BYTE
904                | Type::SBYTE
905                | Type::ASCII
906                | Type::UNDEFINED
907                | Type::SHORT
908                | Type::SSHORT
909                | Type::LONG
910                | Type::SLONG
911                | Type::FLOAT
912                | Type::IFD => unreachable!(),
913            });
914        }
915
916        // NOTE: we should only be reading value_byte_length when it's 4 bytes or fewer. Right now
917        // we're reading even if it's 8 bytes, but then only using the first 4 bytes of this
918        // buffer.
919        let mut data = cursor.read(value_byte_length).await?;
920
921        // 2b: the value is at most 4 bytes or doesn't fit in the offset field.
922        return Ok(match tag_type {
923            Type::BYTE | Type::UNDEFINED => Value::Byte(data.read_u8()?),
924            Type::SBYTE => Value::Signed(data.read_i8()? as i32),
925            Type::SHORT => Value::Short(data.read_u16()?),
926            Type::SSHORT => Value::Signed(data.read_i16()? as i32),
927            Type::LONG => Value::Unsigned(data.read_u32()?),
928            Type::SLONG => Value::Signed(data.read_i32()?),
929            Type::FLOAT => Value::Float(data.read_f32()?),
930            Type::ASCII => {
931                if data.as_ref()[0] == 0 {
932                    Value::Ascii("".to_string())
933                } else {
934                    panic!("Invalid tag");
935                    // return Err(TiffError::FormatError(TiffFormatError::InvalidTag));
936                }
937            }
938            Type::LONG8 => {
939                let offset = data.read_u32()?;
940                cursor.seek(offset as _);
941                Value::UnsignedBig(cursor.read_u64().await?)
942            }
943            Type::SLONG8 => {
944                let offset = data.read_u32()?;
945                cursor.seek(offset as _);
946                Value::SignedBig(cursor.read_i64().await?)
947            }
948            Type::DOUBLE => {
949                let offset = data.read_u32()?;
950                cursor.seek(offset as _);
951                Value::Double(cursor.read_f64().await?)
952            }
953            Type::RATIONAL => {
954                let offset = data.read_u32()?;
955                cursor.seek(offset as _);
956                let numerator = cursor.read_u32().await?;
957                let denominator = cursor.read_u32().await?;
958                Value::Rational(numerator, denominator)
959            }
960            Type::SRATIONAL => {
961                let offset = data.read_u32()?;
962                cursor.seek(offset as _);
963                let numerator = cursor.read_i32().await?;
964                let denominator = cursor.read_i32().await?;
965                Value::SRational(numerator, denominator)
966            }
967            Type::IFD => Value::Ifd(data.read_u32()?),
968            Type::IFD8 => {
969                let offset = data.read_u32()?;
970                cursor.seek(offset as _);
971                Value::IfdBig(cursor.read_u64().await?)
972            }
973        });
974    }
975
976    // Case 3: There is more than one value, but it fits in the offset field.
977    if value_byte_length <= 4 || bigtiff && value_byte_length <= 8 {
978        let mut data = cursor.read(value_byte_length).await?;
979        if bigtiff {
980            cursor.advance(8 - value_byte_length);
981        } else {
982            cursor.advance(4 - value_byte_length);
983        }
984
985        match tag_type {
986            Type::BYTE | Type::UNDEFINED => {
987                return {
988                    Ok(Value::List(
989                        (0..count)
990                            .map(|_| Value::Byte(data.read_u8().unwrap()))
991                            .collect(),
992                    ))
993                };
994            }
995            Type::SBYTE => {
996                return {
997                    Ok(Value::List(
998                        (0..count)
999                            .map(|_| Value::Signed(data.read_i8().unwrap() as i32))
1000                            .collect(),
1001                    ))
1002                }
1003            }
1004            Type::ASCII => {
1005                let mut buf = vec![0; count as usize];
1006                data.read_exact(&mut buf)?;
1007                if buf.is_ascii() && buf.ends_with(&[0]) {
1008                    let v = std::str::from_utf8(&buf)
1009                        .map_err(|err| AsyncTiffError::General(err.to_string()))?;
1010                    let v = v.trim_matches(char::from(0));
1011                    return Ok(Value::Ascii(v.into()));
1012                } else {
1013                    panic!("Invalid tag");
1014                    // return Err(TiffError::FormatError(TiffFormatError::InvalidTag));
1015                }
1016            }
1017            Type::SHORT => {
1018                let mut v = Vec::new();
1019                for _ in 0..count {
1020                    v.push(Value::Short(data.read_u16()?));
1021                }
1022                return Ok(Value::List(v));
1023            }
1024            Type::SSHORT => {
1025                let mut v = Vec::new();
1026                for _ in 0..count {
1027                    v.push(Value::Signed(i32::from(data.read_i16()?)));
1028                }
1029                return Ok(Value::List(v));
1030            }
1031            Type::LONG => {
1032                let mut v = Vec::new();
1033                for _ in 0..count {
1034                    v.push(Value::Unsigned(data.read_u32()?));
1035                }
1036                return Ok(Value::List(v));
1037            }
1038            Type::SLONG => {
1039                let mut v = Vec::new();
1040                for _ in 0..count {
1041                    v.push(Value::Signed(data.read_i32()?));
1042                }
1043                return Ok(Value::List(v));
1044            }
1045            Type::FLOAT => {
1046                let mut v = Vec::new();
1047                for _ in 0..count {
1048                    v.push(Value::Float(data.read_f32()?));
1049                }
1050                return Ok(Value::List(v));
1051            }
1052            Type::IFD => {
1053                let mut v = Vec::new();
1054                for _ in 0..count {
1055                    v.push(Value::Ifd(data.read_u32()?));
1056                }
1057                return Ok(Value::List(v));
1058            }
1059            Type::LONG8
1060            | Type::SLONG8
1061            | Type::RATIONAL
1062            | Type::SRATIONAL
1063            | Type::DOUBLE
1064            | Type::IFD8 => {
1065                unreachable!()
1066            }
1067        }
1068    }
1069
1070    // Seek cursor
1071    let offset = if bigtiff {
1072        cursor.read_u64().await?
1073    } else {
1074        cursor.read_u32().await?.into()
1075    };
1076    cursor.seek(offset);
1077
1078    // Case 4: there is more than one value, and it doesn't fit in the offset field.
1079    match tag_type {
1080        // TODO check if this could give wrong results
1081        // at a different endianess of file/computer.
1082        Type::BYTE | Type::UNDEFINED => {
1083            let mut v = Vec::with_capacity(count as _);
1084            for _ in 0..count {
1085                v.push(Value::Byte(cursor.read_u8().await?))
1086            }
1087            Ok(Value::List(v))
1088        }
1089        Type::SBYTE => {
1090            let mut v = Vec::with_capacity(count as _);
1091            for _ in 0..count {
1092                v.push(Value::Signed(cursor.read_i8().await? as i32))
1093            }
1094            Ok(Value::List(v))
1095        }
1096        Type::SHORT => {
1097            let mut v = Vec::with_capacity(count as _);
1098            for _ in 0..count {
1099                v.push(Value::Short(cursor.read_u16().await?))
1100            }
1101            Ok(Value::List(v))
1102        }
1103        Type::SSHORT => {
1104            let mut v = Vec::with_capacity(count as _);
1105            for _ in 0..count {
1106                v.push(Value::Signed(cursor.read_i16().await? as i32))
1107            }
1108            Ok(Value::List(v))
1109        }
1110        Type::LONG => {
1111            let mut v = Vec::with_capacity(count as _);
1112            for _ in 0..count {
1113                v.push(Value::Unsigned(cursor.read_u32().await?))
1114            }
1115            Ok(Value::List(v))
1116        }
1117        Type::SLONG => {
1118            let mut v = Vec::with_capacity(count as _);
1119            for _ in 0..count {
1120                v.push(Value::Signed(cursor.read_i32().await?))
1121            }
1122            Ok(Value::List(v))
1123        }
1124        Type::FLOAT => {
1125            let mut v = Vec::with_capacity(count as _);
1126            for _ in 0..count {
1127                v.push(Value::Float(cursor.read_f32().await?))
1128            }
1129            Ok(Value::List(v))
1130        }
1131        Type::DOUBLE => {
1132            let mut v = Vec::with_capacity(count as _);
1133            for _ in 0..count {
1134                v.push(Value::Double(cursor.read_f64().await?))
1135            }
1136            Ok(Value::List(v))
1137        }
1138        Type::RATIONAL => {
1139            let mut v = Vec::with_capacity(count as _);
1140            for _ in 0..count {
1141                v.push(Value::Rational(
1142                    cursor.read_u32().await?,
1143                    cursor.read_u32().await?,
1144                ))
1145            }
1146            Ok(Value::List(v))
1147        }
1148        Type::SRATIONAL => {
1149            let mut v = Vec::with_capacity(count as _);
1150            for _ in 0..count {
1151                v.push(Value::SRational(
1152                    cursor.read_i32().await?,
1153                    cursor.read_i32().await?,
1154                ))
1155            }
1156            Ok(Value::List(v))
1157        }
1158        Type::LONG8 => {
1159            let mut v = Vec::with_capacity(count as _);
1160            for _ in 0..count {
1161                v.push(Value::UnsignedBig(cursor.read_u64().await?))
1162            }
1163            Ok(Value::List(v))
1164        }
1165        Type::SLONG8 => {
1166            let mut v = Vec::with_capacity(count as _);
1167            for _ in 0..count {
1168                v.push(Value::SignedBig(cursor.read_i64().await?))
1169            }
1170            Ok(Value::List(v))
1171        }
1172        Type::IFD => {
1173            let mut v = Vec::with_capacity(count as _);
1174            for _ in 0..count {
1175                v.push(Value::Ifd(cursor.read_u32().await?))
1176            }
1177            Ok(Value::List(v))
1178        }
1179        Type::IFD8 => {
1180            let mut v = Vec::with_capacity(count as _);
1181            for _ in 0..count {
1182                v.push(Value::IfdBig(cursor.read_u64().await?))
1183            }
1184            Ok(Value::List(v))
1185        }
1186        Type::ASCII => {
1187            let mut out = vec![0; count as _];
1188            let mut buf = cursor.read(count).await?;
1189            buf.read_exact(&mut out)?;
1190
1191            // Strings may be null-terminated, so we trim anything downstream of the null byte
1192            if let Some(first) = out.iter().position(|&b| b == 0) {
1193                out.truncate(first);
1194            }
1195            Ok(Value::Ascii(
1196                String::from_utf8(out).map_err(|err| AsyncTiffError::General(err.to_string()))?,
1197            ))
1198        }
1199    }
1200}