Skip to main content

async_tiff/
ifd.rs

1use std::collections::HashMap;
2use std::ops::Range;
3use std::sync::Arc;
4
5use bytes::Bytes;
6use num_enum::TryFromPrimitive;
7
8use crate::error::{AsyncTiffError, AsyncTiffResult, TiffError, TiffFormatError};
9use crate::geo::{GeoKeyDirectory, GeoKeyTag};
10use crate::reader::{AsyncFileReader, Endianness};
11use crate::tag_value::TagValue;
12use crate::tags::{
13    Compression, ExtraSamples, PhotometricInterpretation, PlanarConfiguration, Predictor,
14    ResolutionUnit, SampleFormat, Tag,
15};
16use crate::{DataType, Tile};
17
18const DOCUMENT_NAME: u16 = 269;
19
20/// An ImageFileDirectory representing Image content
21// The ordering of these tags matches the sorted order in TIFF spec Appendix A
22#[allow(dead_code)]
23#[derive(Debug, Clone, PartialEq)]
24pub struct ImageFileDirectory {
25    pub(crate) endianness: Endianness,
26
27    pub(crate) new_subfile_type: Option<u32>,
28
29    /// The number of columns in the image, i.e., the number of pixels per row.
30    pub(crate) image_width: u32,
31
32    /// The number of rows of pixels in the image.
33    pub(crate) image_height: u32,
34
35    pub(crate) bits_per_sample: Vec<u16>,
36
37    pub(crate) compression: Compression,
38
39    pub(crate) photometric_interpretation: PhotometricInterpretation,
40
41    pub(crate) document_name: Option<String>,
42
43    pub(crate) image_description: Option<String>,
44
45    pub(crate) strip_offsets: Option<Vec<u64>>,
46
47    pub(crate) orientation: Option<u16>,
48
49    /// The number of components per pixel.
50    ///
51    /// SamplesPerPixel is usually 1 for bilevel, grayscale, and palette-color images.
52    /// SamplesPerPixel is usually 3 for RGB images. If this value is higher, ExtraSamples should
53    /// give an indication of the meaning of the additional channels.
54    pub(crate) samples_per_pixel: u16,
55
56    pub(crate) rows_per_strip: Option<u32>,
57
58    pub(crate) strip_byte_counts: Option<Vec<u64>>,
59
60    pub(crate) min_sample_value: Option<Vec<u16>>,
61    pub(crate) max_sample_value: Option<Vec<u16>>,
62
63    /// The number of pixels per ResolutionUnit in the ImageWidth direction.
64    pub(crate) x_resolution: Option<f64>,
65
66    /// The number of pixels per ResolutionUnit in the ImageLength direction.
67    pub(crate) y_resolution: Option<f64>,
68
69    /// How the components of each pixel are stored.
70    ///
71    /// The specification defines these values:
72    ///
73    /// - Chunky format. The component values for each pixel are stored contiguously. For example,
74    ///   for RGB data, the data is stored as RGBRGBRGB
75    /// - Planar format. The components are stored in separate component planes. For example, RGB
76    ///   data is stored with the Red components in one component plane, the Green in another, and
77    ///   the Blue in another.
78    ///
79    /// The specification adds a warning that PlanarConfiguration=2 is not in widespread use and
80    /// that Baseline TIFF readers are not required to support it.
81    ///
82    /// If SamplesPerPixel is 1, PlanarConfiguration is irrelevant, and need not be included.
83    pub(crate) planar_configuration: PlanarConfiguration,
84
85    pub(crate) resolution_unit: Option<ResolutionUnit>,
86
87    /// Name and version number of the software package(s) used to create the image.
88    pub(crate) software: Option<String>,
89
90    /// Date and time of image creation.
91    ///
92    /// The format is: "YYYY:MM:DD HH:MM:SS", with hours like those on a 24-hour clock, and one
93    /// space character between the date and the time. The length of the string, including the
94    /// terminating NUL, is 20 bytes.
95    pub(crate) date_time: Option<String>,
96    pub(crate) artist: Option<String>,
97    pub(crate) host_computer: Option<String>,
98
99    pub(crate) predictor: Option<Predictor>,
100
101    /// A color map for palette color images.
102    ///
103    /// This field defines a Red-Green-Blue color map (often called a lookup table) for
104    /// palette-color images. In a palette-color image, a pixel value is used to index into an RGB
105    /// lookup table. For example, a palette-color pixel having a value of 0 would be displayed
106    /// according to the 0th Red, Green, Blue triplet.
107    ///
108    /// In a TIFF ColorMap, all the Red values come first, followed by the Green values, then the
109    /// Blue values. The number of values for each color is 2**BitsPerSample. Therefore, the
110    /// ColorMap field for an 8-bit palette-color image would have 3 * 256 values. The width of
111    /// each value is 16 bits, as implied by the type of SHORT. 0 represents the minimum intensity,
112    /// and 65535 represents the maximum intensity. Black is represented by 0,0,0, and white by
113    /// 65535, 65535, 65535.
114    ///
115    /// ColorMap must be included in all palette-color images.
116    ///
117    /// In Specification Supplement 1, support was added for ColorMaps containing other then RGB
118    /// values. This scheme includes the Indexed tag, with value 1, and a PhotometricInterpretation
119    /// different from PaletteColor then next denotes the colorspace of the ColorMap entries.
120    ///
121    /// <https://web.archive.org/web/20240329145324/https://www.awaresystems.be/imaging/tiff/tifftags/colormap.html>
122    pub(crate) color_map: Option<Arc<[u16]>>,
123
124    pub(crate) tile_width: Option<u32>,
125    pub(crate) tile_height: Option<u32>,
126
127    pub(crate) tile_offsets: Option<Vec<u64>>,
128    pub(crate) tile_byte_counts: Option<Vec<u64>>,
129
130    pub(crate) extra_samples: Option<Vec<ExtraSamples>>,
131
132    pub(crate) sample_format: Vec<SampleFormat>,
133
134    pub(crate) jpeg_tables: Option<Bytes>,
135
136    pub(crate) copyright: Option<String>,
137
138    // Geospatial tags
139    pub(crate) geo_key_directory: Option<GeoKeyDirectory>,
140    pub(crate) model_pixel_scale: Option<Vec<f64>>,
141    pub(crate) model_tiepoint: Option<Vec<f64>>,
142    pub(crate) model_transformation: Option<Vec<f64>>,
143
144    // GDAL tags
145    pub(crate) gdal_nodata: Option<String>,
146    pub(crate) gdal_metadata: Option<String>,
147    pub(crate) other_tags: HashMap<Tag, TagValue>,
148
149    // Other
150    pub(crate) lerc_parameters: Option<Vec<u32>>,
151}
152
153impl ImageFileDirectory {
154    /// Create a new ImageFileDirectory from tag data
155    pub fn from_tags(
156        tag_data: HashMap<Tag, TagValue>,
157        endianness: Endianness,
158    ) -> AsyncTiffResult<Self> {
159        let mut new_subfile_type = None;
160        let mut image_width = None;
161        let mut image_height = None;
162        let mut bits_per_sample = None;
163        let mut compression = None;
164        let mut photometric_interpretation = None;
165        let mut document_name = None;
166        let mut image_description = None;
167        let mut strip_offsets = None;
168        let mut orientation = None;
169        let mut samples_per_pixel = None;
170        let mut rows_per_strip = None;
171        let mut strip_byte_counts = None;
172        let mut min_sample_value = None;
173        let mut max_sample_value = None;
174        let mut x_resolution = None;
175        let mut y_resolution = None;
176        let mut planar_configuration = None;
177        let mut resolution_unit = None;
178        let mut software = None;
179        let mut date_time = None;
180        let mut artist = None;
181        let mut host_computer = None;
182        let mut predictor = None;
183        let mut color_map = None;
184        let mut tile_width = None;
185        let mut tile_height = None;
186        let mut tile_offsets = None;
187        let mut tile_byte_counts = None;
188        let mut extra_samples = None;
189        let mut sample_format = None;
190        let mut jpeg_tables = None;
191        let mut copyright = None;
192        let mut geo_key_directory_data = None;
193        let mut model_pixel_scale = None;
194        let mut model_tiepoint = None;
195        let mut model_transformation = None;
196        let mut geo_ascii_params: Option<String> = None;
197        let mut geo_double_params: Option<Vec<f64>> = None;
198        let mut gdal_nodata = None;
199        let mut gdal_metadata = None;
200        let mut lerc_parameters = None;
201
202        let mut other_tags = HashMap::new();
203
204        tag_data.into_iter().try_for_each(|(tag, value)| {
205            match tag {
206                Tag::NewSubfileType => new_subfile_type = Some(value.into_u32()?),
207                Tag::ImageWidth => image_width = Some(value.into_u32()?),
208                Tag::ImageLength => image_height = Some(value.into_u32()?),
209                Tag::BitsPerSample => bits_per_sample = Some(value.into_u16_vec()?),
210                Tag::Compression => {
211                    compression = Some(Compression::from_u16_exhaustive(value.into_u16()?))
212                }
213                Tag::PhotometricInterpretation => {
214                    photometric_interpretation =
215                        PhotometricInterpretation::from_u16(value.into_u16()?)
216                }
217                Tag::ImageDescription => image_description = Some(value.into_string()?),
218                Tag::StripOffsets => strip_offsets = Some(value.into_u64_vec()?),
219                Tag::Orientation => orientation = Some(value.into_u16()?),
220                Tag::SamplesPerPixel => samples_per_pixel = Some(value.into_u16()?),
221                Tag::RowsPerStrip => rows_per_strip = Some(value.into_u32()?),
222                Tag::StripByteCounts => strip_byte_counts = Some(value.into_u64_vec()?),
223                Tag::MinSampleValue => min_sample_value = Some(value.into_u16_vec()?),
224                Tag::MaxSampleValue => max_sample_value = Some(value.into_u16_vec()?),
225                Tag::XResolution => match value {
226                    TagValue::Rational(n, d) => x_resolution = Some(n as f64 / d as f64),
227                    _ => unreachable!("Expected rational type for XResolution."),
228                },
229                Tag::YResolution => match value {
230                    TagValue::Rational(n, d) => y_resolution = Some(n as f64 / d as f64),
231                    _ => unreachable!("Expected rational type for YResolution."),
232                },
233                Tag::PlanarConfiguration => {
234                    planar_configuration = PlanarConfiguration::from_u16(value.into_u16()?)
235                }
236                Tag::ResolutionUnit => {
237                    resolution_unit = ResolutionUnit::from_u16(value.into_u16()?)
238                }
239                Tag::Software => software = Some(value.into_string()?),
240                Tag::DateTime => date_time = Some(value.into_string()?),
241                Tag::Artist => artist = Some(value.into_string()?),
242                Tag::HostComputer => host_computer = Some(value.into_string()?),
243                Tag::Predictor => predictor = Predictor::from_u16(value.into_u16()?),
244                Tag::ColorMap => color_map = Some(Arc::from(value.into_u16_vec()?)),
245                Tag::TileWidth => tile_width = Some(value.into_u32()?),
246                Tag::TileLength => tile_height = Some(value.into_u32()?),
247                Tag::TileOffsets => tile_offsets = Some(value.into_u64_vec()?),
248                Tag::TileByteCounts => tile_byte_counts = Some(value.into_u64_vec()?),
249                Tag::ExtraSamples => {
250                    let values = value.into_u16_vec()?;
251                    extra_samples = Some(
252                        values
253                            .into_iter()
254                            .map(|val| {
255                                ExtraSamples::from_u16(val)
256                                    .ok_or(TiffError::FormatError(TiffFormatError::InvalidTag))
257                            })
258                            .collect::<Result<Vec<_>, _>>()?,
259                    );
260                }
261                Tag::SampleFormat => {
262                    let values = value.into_u16_vec()?;
263                    sample_format = Some(
264                        values
265                            .into_iter()
266                            .map(SampleFormat::from_u16_exhaustive)
267                            .collect(),
268                    );
269                }
270                Tag::JPEGTables => jpeg_tables = Some(value.into_u8_vec()?.into()),
271                Tag::Copyright => copyright = Some(value.into_string()?),
272
273                // Geospatial tags
274                // http://geotiff.maptools.org/spec/geotiff2.4.html
275                Tag::GeoKeyDirectory => geo_key_directory_data = Some(value.into_u16_vec()?),
276                Tag::ModelPixelScale => model_pixel_scale = Some(value.into_f64_vec()?),
277                Tag::ModelTiepoint => model_tiepoint = Some(value.into_f64_vec()?),
278                Tag::ModelTransformation => model_transformation = Some(value.into_f64_vec()?),
279                Tag::GeoAsciiParams => geo_ascii_params = Some(value.into_string()?),
280                Tag::GeoDoubleParams => geo_double_params = Some(value.into_f64_vec()?),
281                Tag::GdalNodata => gdal_nodata = Some(value.into_string()?),
282                Tag::GdalMetadata => gdal_metadata = Some(value.into_string()?),
283                Tag::LercParameters => lerc_parameters = Some(value.into_u32_vec()?),
284                // Tags for which the tiff crate doesn't have a hard-coded enum variant
285                Tag::Unknown(DOCUMENT_NAME) => document_name = Some(value.into_string()?),
286                _ => {
287                    other_tags.insert(tag, value);
288                }
289            };
290            Ok::<_, TiffError>(())
291        })?;
292
293        let mut geo_key_directory = None;
294
295        // We need to actually parse the GeoKeyDirectory after parsing all other tags because the
296        // GeoKeyDirectory relies on `GeoAsciiParamsTag` having been parsed.
297        if let Some(data) = geo_key_directory_data {
298            let mut chunks = data.chunks(4);
299
300            let header = chunks
301                .next()
302                .expect("If the geo key directory exists, a header should exist.");
303            let key_directory_version = header[0];
304            assert_eq!(key_directory_version, 1);
305
306            let key_revision = header[1];
307            assert_eq!(key_revision, 1);
308
309            let _key_minor_revision = header[2];
310            let number_of_keys = header[3];
311
312            let mut tags = HashMap::with_capacity(number_of_keys as usize);
313            for _ in 0..number_of_keys {
314                let chunk = chunks
315                    .next()
316                    .expect("There should be a chunk for each key.");
317
318                let key_id = chunk[0];
319                let tag_name = if let Ok(tag_name) = GeoKeyTag::try_from_primitive(key_id) {
320                    tag_name
321                } else {
322                    // Skip unknown GeoKeyTag ids. Some GeoTIFFs include keys that were proposed
323                    // but not included in the GeoTIFF spec. See
324                    // https://github.com/developmentseed/async-tiff/pull/131 and
325                    // https://github.com/virtual-zarr/virtual-tiff/issues/52
326                    continue;
327                };
328
329                let tag_location = chunk[1];
330                let count = chunk[2];
331                let value_offset = chunk[3];
332
333                if tag_location == 0 {
334                    tags.insert(tag_name, TagValue::Short(value_offset));
335                } else if Tag::from_u16_exhaustive(tag_location) == Tag::GeoAsciiParams {
336                    // If the tag_location points to the value of Tag::GeoAsciiParams, then we
337                    // need to extract a subslice from GeoAsciiParams
338
339                    let geo_ascii_params = geo_ascii_params
340                        .as_ref()
341                        .expect("GeoAsciiParamsTag exists but geo_ascii_params does not.");
342                    let value_offset = value_offset as usize;
343                    let mut s = &geo_ascii_params[value_offset..value_offset + count as usize];
344
345                    // It seems that this string subslice might always include the final |
346                    // character?
347                    if s.ends_with('|') {
348                        s = &s[0..s.len() - 1];
349                    }
350
351                    tags.insert(tag_name, TagValue::Ascii(s.to_string()));
352                } else if Tag::from_u16_exhaustive(tag_location) == Tag::GeoDoubleParams {
353                    // If the tag_location points to the value of Tag::GeoDoubleParams, then we
354                    // need to extract a subslice from GeoDoubleParams
355
356                    let geo_double_params = geo_double_params
357                        .as_ref()
358                        .expect("GeoDoubleParamsTag exists but geo_double_params does not.");
359                    let value_offset = value_offset as usize;
360                    let value = if count == 1 {
361                        TagValue::Double(geo_double_params[value_offset])
362                    } else {
363                        let x = geo_double_params[value_offset..value_offset + count as usize]
364                            .iter()
365                            .map(|val| TagValue::Double(*val))
366                            .collect();
367                        TagValue::List(x)
368                    };
369                    tags.insert(tag_name, value);
370                }
371            }
372            geo_key_directory = Some(GeoKeyDirectory::from_tags(tags)?);
373        }
374
375        let samples_per_pixel = samples_per_pixel.expect("samples_per_pixel not found");
376        let planar_configuration = if let Some(planar_configuration) = planar_configuration {
377            planar_configuration
378        } else if samples_per_pixel == 1 {
379            // If SamplesPerPixel is 1, PlanarConfiguration is irrelevant, and need not be included.
380            // https://web.archive.org/web/20240329145253/https://www.awaresystems.be/imaging/tiff/tifftags/planarconfiguration.html
381            PlanarConfiguration::Chunky
382        } else {
383            PlanarConfiguration::Chunky
384        };
385        Ok(Self {
386            endianness,
387            new_subfile_type,
388            image_width: image_width.expect("image_width not found"),
389            image_height: image_height.expect("image_height not found"),
390            bits_per_sample: bits_per_sample.expect("bits per sample not found"),
391            // Defaults to no compression
392            // https://web.archive.org/web/20240329145331/https://www.awaresystems.be/imaging/tiff/tifftags/compression.html
393            compression: compression.unwrap_or(Compression::None),
394            photometric_interpretation: photometric_interpretation
395                .expect("photometric interpretation not found"),
396            document_name,
397            image_description,
398            strip_offsets,
399            orientation,
400            samples_per_pixel,
401            rows_per_strip,
402            strip_byte_counts,
403            min_sample_value,
404            max_sample_value,
405            x_resolution,
406            y_resolution,
407            planar_configuration,
408            resolution_unit,
409            software,
410            date_time,
411            artist,
412            host_computer,
413            predictor,
414            color_map,
415            tile_width,
416            tile_height,
417            tile_offsets,
418            tile_byte_counts,
419            extra_samples,
420            // Uint8 is the default for SampleFormat
421            // https://web.archive.org/web/20240329145340/https://www.awaresystems.be/imaging/tiff/tifftags/sampleformat.html
422            sample_format: sample_format
423                .unwrap_or(vec![SampleFormat::Uint; samples_per_pixel as _]),
424            copyright,
425            jpeg_tables,
426            geo_key_directory,
427            model_pixel_scale,
428            model_tiepoint,
429            model_transformation,
430            gdal_nodata,
431            gdal_metadata,
432            lerc_parameters,
433            other_tags,
434        })
435    }
436
437    /// A general indication of the kind of data contained in this subfile.
438    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/newsubfiletype.html>
439    pub fn new_subfile_type(&self) -> Option<u32> {
440        self.new_subfile_type
441    }
442
443    /// The number of columns in the image, i.e., the number of pixels per row.
444    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/imagewidth.html>
445    pub fn image_width(&self) -> u32 {
446        self.image_width
447    }
448
449    /// The number of rows of pixels in the image.
450    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/imagelength.html>
451    pub fn image_height(&self) -> u32 {
452        self.image_height
453    }
454
455    /// Number of bits per component.
456    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/bitspersample.html>
457    pub fn bits_per_sample(&self) -> &[u16] {
458        &self.bits_per_sample
459    }
460
461    /// Compression scheme used on the image data.
462    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/compression.html>
463    pub fn compression(&self) -> Compression {
464        self.compression
465    }
466
467    /// The color space of the image data.
468    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/photometricinterpretation.html>
469    pub fn photometric_interpretation(&self) -> PhotometricInterpretation {
470        self.photometric_interpretation
471    }
472
473    /// Document name.
474    pub fn document_name(&self) -> Option<&str> {
475        self.document_name.as_deref()
476    }
477
478    /// A string that describes the subject of the image.
479    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/imagedescription.html>
480    pub fn image_description(&self) -> Option<&str> {
481        self.image_description.as_deref()
482    }
483
484    /// For each strip, the byte offset of that strip.
485    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/stripoffsets.html>
486    pub fn strip_offsets(&self) -> Option<&[u64]> {
487        self.strip_offsets.as_deref()
488    }
489
490    /// The orientation of the image with respect to the rows and columns.
491    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/orientation.html>
492    pub fn orientation(&self) -> Option<u16> {
493        self.orientation
494    }
495
496    /// The number of components per pixel.
497    ///
498    /// SamplesPerPixel is usually 1 for bilevel, grayscale, and palette-color images.
499    /// SamplesPerPixel is usually 3 for RGB images. If this value is higher, ExtraSamples should
500    /// give an indication of the meaning of the additional channels.
501    pub fn samples_per_pixel(&self) -> u16 {
502        self.samples_per_pixel
503    }
504
505    /// The number of rows per strip.
506    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/rowsperstrip.html>
507    pub fn rows_per_strip(&self) -> Option<u32> {
508        self.rows_per_strip
509    }
510
511    /// For each strip, the number of bytes in the strip after compression.
512    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/stripbytecounts.html>
513    pub fn strip_byte_counts(&self) -> Option<&[u64]> {
514        self.strip_byte_counts.as_deref()
515    }
516
517    /// The minimum component value used.
518    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/minsamplevalue.html>
519    pub fn min_sample_value(&self) -> Option<&[u16]> {
520        self.min_sample_value.as_deref()
521    }
522
523    /// The maximum component value used.
524    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/maxsamplevalue.html>
525    pub fn max_sample_value(&self) -> Option<&[u16]> {
526        self.max_sample_value.as_deref()
527    }
528
529    /// The number of pixels per ResolutionUnit in the ImageWidth direction.
530    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/xresolution.html>
531    pub fn x_resolution(&self) -> Option<f64> {
532        self.x_resolution
533    }
534
535    /// The number of pixels per ResolutionUnit in the ImageLength direction.
536    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/yresolution.html>
537    pub fn y_resolution(&self) -> Option<f64> {
538        self.y_resolution
539    }
540
541    /// How the components of each pixel are stored.
542    ///
543    /// The specification defines these values:
544    ///
545    /// - Chunky format. The component values for each pixel are stored contiguously. For example,
546    ///   for RGB data, the data is stored as RGBRGBRGB
547    /// - Planar format. The components are stored in separate component planes. For example, RGB
548    ///   data is stored with the Red components in one component plane, the Green in another, and
549    ///   the Blue in another.
550    ///
551    /// The specification adds a warning that PlanarConfiguration=2 is not in widespread use and
552    /// that Baseline TIFF readers are not required to support it.
553    ///
554    /// If SamplesPerPixel is 1, PlanarConfiguration is irrelevant, and need not be included.
555    ///
556    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/planarconfiguration.html>
557    pub fn planar_configuration(&self) -> PlanarConfiguration {
558        self.planar_configuration
559    }
560
561    /// The unit of measurement for XResolution and YResolution.
562    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/resolutionunit.html>
563    pub fn resolution_unit(&self) -> Option<ResolutionUnit> {
564        self.resolution_unit
565    }
566
567    /// Name and version number of the software package(s) used to create the image.
568    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/software.html>
569    pub fn software(&self) -> Option<&str> {
570        self.software.as_deref()
571    }
572
573    /// Date and time of image creation.
574    ///
575    /// The format is: "YYYY:MM:DD HH:MM:SS", with hours like those on a 24-hour clock, and one
576    /// space character between the date and the time. The length of the string, including the
577    /// terminating NUL, is 20 bytes.
578    ///
579    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/datetime.html>
580    pub fn date_time(&self) -> Option<&str> {
581        self.date_time.as_deref()
582    }
583
584    /// Person who created the image.
585    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/artist.html>
586    pub fn artist(&self) -> Option<&str> {
587        self.artist.as_deref()
588    }
589
590    /// The computer and/or operating system in use at the time of image creation.
591    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/hostcomputer.html>
592    pub fn host_computer(&self) -> Option<&str> {
593        self.host_computer.as_deref()
594    }
595
596    /// A mathematical operator that is applied to the image data before an encoding scheme is
597    /// applied.
598    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/predictor.html>
599    pub fn predictor(&self) -> Option<Predictor> {
600        self.predictor
601    }
602
603    /// The tile width in pixels. This is the number of columns in each tile.
604    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/tilewidth.html>
605    pub fn tile_width(&self) -> Option<u32> {
606        self.tile_width
607    }
608
609    /// The tile length (height) in pixels. This is the number of rows in each tile.
610    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/tilelength.html>
611    pub fn tile_height(&self) -> Option<u32> {
612        self.tile_height
613    }
614
615    /// For each tile, the byte offset of that tile, as compressed and stored on disk.
616    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/tileoffsets.html>
617    pub fn tile_offsets(&self) -> Option<&[u64]> {
618        self.tile_offsets.as_deref()
619    }
620
621    /// For each tile, the number of (compressed) bytes in that tile.
622    /// <https://web.archive.org/web/20240329145339/https://www.awaresystems.be/imaging/tiff/tifftags/tilebytecounts.html>
623    pub fn tile_byte_counts(&self) -> Option<&[u64]> {
624        self.tile_byte_counts.as_deref()
625    }
626
627    /// Description of extra components.
628    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/extrasamples.html>
629    pub fn extra_samples(&self) -> Option<&[ExtraSamples]> {
630        self.extra_samples.as_deref()
631    }
632
633    /// Specifies how to interpret each data sample in a pixel.
634    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/sampleformat.html>
635    pub fn sample_format(&self) -> &[SampleFormat] {
636        &self.sample_format
637    }
638
639    /// JPEG quantization and/or Huffman tables.
640    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/jpegtables.html>
641    pub fn jpeg_tables(&self) -> Option<&[u8]> {
642        self.jpeg_tables.as_deref()
643    }
644
645    /// Copyright notice.
646    /// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/copyright.html>
647    pub fn copyright(&self) -> Option<&str> {
648        self.copyright.as_deref()
649    }
650
651    /// Geospatial tags
652    /// <https://web.archive.org/web/20240329145313/https://www.awaresystems.be/imaging/tiff/tifftags/geokeydirectorytag.html>
653    pub fn geo_key_directory(&self) -> Option<&GeoKeyDirectory> {
654        self.geo_key_directory.as_ref()
655    }
656
657    /// Used in interchangeable GeoTIFF files.
658    /// <https://web.archive.org/web/20240329145238/https://www.awaresystems.be/imaging/tiff/tifftags/modelpixelscaletag.html>
659    pub fn model_pixel_scale(&self) -> Option<&[f64]> {
660        self.model_pixel_scale.as_deref()
661    }
662
663    /// Used in interchangeable GeoTIFF files.
664    /// <https://web.archive.org/web/20240329145303/https://www.awaresystems.be/imaging/tiff/tifftags/modeltiepointtag.html>
665    pub fn model_tiepoint(&self) -> Option<&[f64]> {
666        self.model_tiepoint.as_deref()
667    }
668
669    /// Stores a full 4×4 affine transformation matrix that maps pixel/line coordinates directly
670    /// into model (map) coordinates.
671    pub fn model_transformation(&self) -> Option<&[f64]> {
672        self.model_transformation.as_deref()
673    }
674
675    /// GDAL NoData value
676    /// <https://gdal.org/en/stable/drivers/raster/gtiff.html#nodata-value>
677    pub fn gdal_nodata(&self) -> Option<&str> {
678        self.gdal_nodata.as_deref()
679    }
680
681    /// GDAL Metadata XML information
682    ///
683    /// Non standard metadata items are grouped together into a XML string stored in the non
684    /// standard `TIFFTAG_GDAL_METADATA` ASCII tag (code `42112`).
685    pub fn gdal_metadata(&self) -> Option<&str> {
686        self.gdal_metadata.as_deref()
687    }
688
689    /// Tags for which this crate doesn't have a hard-coded enum variant.
690    pub fn other_tags(&self) -> &HashMap<Tag, TagValue> {
691        &self.other_tags
692    }
693
694    /// LERC parameters, used in [LERC]-compressed TIFFs.
695    ///
696    /// [LERC]: https://esri.github.io/lerc/
697    pub fn lerc_parameters(&self) -> Option<&[u32]> {
698        self.lerc_parameters.as_deref()
699    }
700
701    /// A color map for palette color images.
702    ///
703    /// This field defines a Red-Green-Blue color map (often called a lookup table) for
704    /// palette-color images. In a palette-color image, a pixel value is used to index into an RGB
705    /// lookup table. For example, a palette-color pixel having a value of 0 would be displayed
706    /// according to the 0th Red, Green, Blue triplet.
707    ///
708    /// In a TIFF ColorMap, all the Red values come first, followed by the Green values, then the
709    /// Blue values. The number of values for each color is `2**BitsPerSample`. Therefore, the
710    /// ColorMap field for an 8-bit palette-color image would have `3 * 256` values. The width of
711    /// each value is 16 bits, as implied by the type of SHORT. 0 represents the minimum intensity,
712    /// and 65535 represents the maximum intensity. Black is represented by 0,0,0, and white by
713    /// 65535, 65535, 65535.
714    ///
715    /// ColorMap must be included in all palette-color images.
716    ///
717    /// <https://web.archive.org/web/20240329145324/https://www.awaresystems.be/imaging/tiff/tifftags/colormap.html>
718    pub fn colormap(&self) -> Option<&Arc<[u16]>> {
719        self.color_map.as_ref()
720    }
721
722    /// Find the byte range(s) for the tile located at `x` column and `y` row.
723    pub fn tile_byte_range(&self, x: usize, y: usize) -> Option<TileByteRange> {
724        TileByteRange::from_ifd_tile(self, x, y)
725    }
726
727    /// Find the byte ranges for the tiles located at `x` column and `y` row.
728    pub fn tiles_byte_ranges(&self, xy: &[(usize, usize)]) -> Option<TilesByteRanges> {
729        TilesByteRanges::from_ifd_tiles(self, xy)
730    }
731
732    /// Fetch the tile located at `x` column and `y` row using the provided reader.
733    ///
734    /// For planar configuration TIFFs, this automatically fetches all bands for the tile
735    /// at position (x, y) and combines them into a single Tile.
736    pub async fn fetch_tile(
737        &self,
738        x: usize,
739        y: usize,
740        reader: &dyn AsyncFileReader,
741    ) -> AsyncTiffResult<Tile> {
742        let byte_ranges = self
743            .tile_byte_range(x, y)
744            .ok_or(AsyncTiffError::General("Not a tiled TIFF".to_string()))?;
745        let compressed_bytes = byte_ranges.into_fetch(reader).await?;
746        Ok(compressed_bytes.into_tile(x, y, self))
747    }
748
749    /// Fetch the tiles located at `x` column and `y` row using the provided reader.
750    pub async fn fetch_tiles(
751        &self,
752        xy: &[(usize, usize)],
753        reader: &dyn AsyncFileReader,
754    ) -> AsyncTiffResult<Vec<Tile>> {
755        let byte_ranges = self
756            .tiles_byte_ranges(xy)
757            .ok_or(AsyncTiffError::General("Not a tiled TIFF".to_string()))?;
758        let compressed_bytes = byte_ranges.into_fetch(reader).await?;
759        Ok(compressed_bytes
760            .into_iter()
761            .zip(xy)
762            .map(|(buffer, (x, y))| buffer.into_tile(*x, *y, self))
763            .collect())
764    }
765
766    /// Return the number of x/y tiles in the IFD
767    /// Returns `None` if this is not a tiled TIFF
768    pub fn tile_count(&self) -> Option<(usize, usize)> {
769        let x_count = (self.image_width as f64 / self.tile_width? as f64).ceil();
770        let y_count = (self.image_height as f64 / self.tile_height? as f64).ceil();
771        Some((x_count as usize, y_count as usize))
772    }
773}
774
775/// A description of the byte ranges for a tile, which may differ based on whether the TIFF is in
776/// chunky or planar format.
777pub enum TileByteRange {
778    /// For chunky TIFFs, a single byte range for the tile that includes all bands.
779    Chunky(Range<u64>),
780
781    /// For planar TIFFs, separate byte ranges for each band of the tile.
782    Planar(Vec<Range<u64>>),
783}
784
785impl TileByteRange {
786    async fn into_fetch(self, reader: &dyn AsyncFileReader) -> AsyncTiffResult<CompressedBytes> {
787        match self {
788            Self::Chunky(range) => Ok(CompressedBytes::Chunky(reader.get_bytes(range).await?)),
789            Self::Planar(ranges) => Ok(CompressedBytes::Planar(
790                reader.get_byte_ranges(ranges).await?,
791            )),
792        }
793    }
794
795    fn from_ifd_tile(ifd: &ImageFileDirectory, x: usize, y: usize) -> Option<Self> {
796        let tile_offsets = ifd.tile_offsets.as_deref()?;
797        let tile_byte_counts = ifd.tile_byte_counts.as_deref()?;
798        let (tiles_per_row, tiles_per_col) = ifd.tile_count()?;
799        match ifd.planar_configuration {
800            PlanarConfiguration::Chunky => {
801                let idx = (y * tiles_per_row) + x;
802                let offset = tile_offsets[idx];
803                let byte_count = tile_byte_counts[idx];
804                Some(TileByteRange::Chunky(offset..(offset + byte_count)))
805            }
806            PlanarConfiguration::Planar => {
807                let tiles_per_band = tiles_per_row * tiles_per_col;
808                let num_bands = ifd.samples_per_pixel as usize;
809                let band_ranges = (0..num_bands)
810                    .map(|band| {
811                        let band_idx = (band * tiles_per_band) + (y * tiles_per_row) + x;
812                        let offset = tile_offsets[band_idx];
813                        let byte_count = tile_byte_counts[band_idx];
814                        offset..(offset + byte_count)
815                    })
816                    .collect::<Vec<_>>();
817                Some(TileByteRange::Planar(band_ranges))
818            }
819        }
820    }
821}
822
823/// A description of the byte ranges for multiple tiles
824pub enum TilesByteRanges {
825    /// For chunky TIFFs, a byte range for each tile that includes all bands.
826    Chunky(Vec<Range<u64>>),
827
828    /// For planar TIFFs, separate byte ranges for each band of each tile.
829    Planar(Vec<Vec<Range<u64>>>),
830}
831
832impl TilesByteRanges {
833    async fn into_fetch(
834        self,
835        reader: &dyn AsyncFileReader,
836    ) -> AsyncTiffResult<Vec<CompressedBytes>> {
837        match self {
838            Self::Chunky(ranges) => {
839                let buffers = reader.get_byte_ranges(ranges).await?;
840                Ok(buffers.into_iter().map(CompressedBytes::Chunky).collect())
841            }
842            Self::Planar(ranges) => {
843                // Record how many bands each tile has, then flatten into a single fetch
844                let band_counts: Vec<usize> = ranges.iter().map(|r| r.len()).collect();
845                let flat_ranges: Vec<Range<u64>> = ranges.into_iter().flatten().collect();
846                let flat_buffers = reader.get_byte_ranges(flat_ranges).await?;
847                // Re-chunk the flat results back into per-tile band vecs
848                let mut flat_iter = flat_buffers.into_iter();
849                band_counts
850                    .into_iter()
851                    .map(|n| {
852                        let band_bytes: Vec<Bytes> = flat_iter.by_ref().take(n).collect();
853                        Ok(CompressedBytes::Planar(band_bytes))
854                    })
855                    .collect()
856            }
857        }
858    }
859
860    fn from_ifd_tiles(ifd: &ImageFileDirectory, xy: &[(usize, usize)]) -> Option<Self> {
861        if xy.is_empty() {
862            return match ifd.planar_configuration {
863                PlanarConfiguration::Chunky => Some(TilesByteRanges::Chunky(vec![])),
864                PlanarConfiguration::Planar => Some(TilesByteRanges::Planar(vec![])),
865            };
866        }
867
868        let ranges = xy
869            .iter()
870            .map(|(x, y)| TileByteRange::from_ifd_tile(ifd, *x, *y))
871            .collect::<Option<Vec<_>>>()?;
872
873        match ifd.planar_configuration {
874            PlanarConfiguration::Chunky => {
875                let chunky_ranges = ranges
876                    .into_iter()
877                    .map(|r| match r {
878                        TileByteRange::Chunky(range) => range,
879                        _ => unreachable!(),
880                    })
881                    .collect();
882                Some(TilesByteRanges::Chunky(chunky_ranges))
883            }
884            PlanarConfiguration::Planar => {
885                let planar_ranges = ranges
886                    .into_iter()
887                    .map(|r| match r {
888                        TileByteRange::Planar(band_ranges) => band_ranges,
889                        _ => unreachable!(),
890                    })
891                    .collect();
892                Some(TilesByteRanges::Planar(planar_ranges))
893            }
894        }
895    }
896}
897
898/// Compressed tile data, either as a single chunk (chunky) or multiple chunks (planar).
899#[derive(Debug, Clone)]
900pub enum CompressedBytes {
901    /// Single compressed chunk for chunky (pixel-interleaved) format.
902    Chunky(Bytes),
903
904    /// Multiple compressed chunks, one per band, for planar (band-interleaved) format.
905    Planar(Vec<Bytes>),
906}
907
908impl CompressedBytes {
909    fn into_tile(self, x: usize, y: usize, ifd: &ImageFileDirectory) -> Tile {
910        let data_type = DataType::from_tags(&ifd.sample_format, &ifd.bits_per_sample);
911        Tile {
912            x,
913            y,
914            data_type,
915            width: ifd.tile_width.unwrap_or(ifd.image_width),
916            height: ifd.tile_height.unwrap_or(ifd.image_height),
917            planar_configuration: ifd.planar_configuration,
918            samples_per_pixel: ifd.samples_per_pixel,
919            bits_per_sample: ifd.bits_per_sample[0],
920            endianness: ifd.endianness,
921            predictor: ifd.predictor.unwrap_or(Predictor::None),
922            compressed_bytes: self,
923            compression_method: ifd.compression,
924            photometric_interpretation: ifd.photometric_interpretation,
925            jpeg_tables: ifd.jpeg_tables.clone(),
926            lerc_parameters: ifd.lerc_parameters.clone(),
927        }
928    }
929}