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}