1use std::collections::HashSet;
2
3use crate::error::{Error, Result};
4use crate::header::{ByteOrder, TiffHeader};
5use crate::io::Cursor;
6use crate::source::TiffSource;
7use crate::tag::{parse_tag_bigtiff, parse_tag_classic, Tag};
8
9pub use tiff_core::constants::{
10 TAG_BITS_PER_SAMPLE, TAG_COMPRESSION, TAG_IMAGE_LENGTH, TAG_IMAGE_WIDTH, TAG_LERC_PARAMETERS,
11 TAG_PHOTOMETRIC_INTERPRETATION, TAG_PLANAR_CONFIGURATION, TAG_PREDICTOR, TAG_ROWS_PER_STRIP,
12 TAG_SAMPLES_PER_PIXEL, TAG_SAMPLE_FORMAT, TAG_STRIP_BYTE_COUNTS, TAG_STRIP_OFFSETS,
13 TAG_TILE_BYTE_COUNTS, TAG_TILE_LENGTH, TAG_TILE_OFFSETS, TAG_TILE_WIDTH,
14};
15pub use tiff_core::RasterLayout;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum LercAdditionalCompression {
20 None,
21 Deflate,
22 Zstd,
23}
24
25impl LercAdditionalCompression {
26 fn from_code(code: u32) -> Option<Self> {
27 match code {
28 0 => Some(Self::None),
29 1 => Some(Self::Deflate),
30 2 => Some(Self::Zstd),
31 _ => None,
32 }
33 }
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub struct LercParameters {
39 pub version: u32,
40 pub additional_compression: LercAdditionalCompression,
41}
42
43#[derive(Debug, Clone)]
45pub struct Ifd {
46 tags: Vec<Tag>,
48 pub index: usize,
50}
51
52impl Ifd {
53 pub fn tag(&self, code: u16) -> Option<&Tag> {
55 self.tags
56 .binary_search_by_key(&code, |tag| tag.code)
57 .ok()
58 .map(|index| &self.tags[index])
59 }
60
61 pub fn tags(&self) -> &[Tag] {
63 &self.tags
64 }
65
66 pub fn width(&self) -> u32 {
68 self.tag_u32(TAG_IMAGE_WIDTH).unwrap_or(0)
69 }
70
71 pub fn height(&self) -> u32 {
73 self.tag_u32(TAG_IMAGE_LENGTH).unwrap_or(0)
74 }
75
76 pub fn bits_per_sample(&self) -> Vec<u16> {
78 self.tag(TAG_BITS_PER_SAMPLE)
79 .and_then(|tag| tag.value.as_u16_slice().map(|values| values.to_vec()))
80 .unwrap_or_else(|| vec![1])
81 }
82
83 pub fn compression(&self) -> u16 {
85 self.tag_u16(TAG_COMPRESSION).unwrap_or(1)
86 }
87
88 pub fn photometric_interpretation(&self) -> Option<u16> {
90 self.tag_u16(TAG_PHOTOMETRIC_INTERPRETATION)
91 }
92
93 pub fn samples_per_pixel(&self) -> u16 {
95 self.tag_u16(TAG_SAMPLES_PER_PIXEL).unwrap_or(1)
96 }
97
98 pub fn is_tiled(&self) -> bool {
100 self.tag(TAG_TILE_WIDTH).is_some() && self.tag(TAG_TILE_LENGTH).is_some()
101 }
102
103 pub fn tile_width(&self) -> Option<u32> {
105 self.tag_u32(TAG_TILE_WIDTH)
106 }
107
108 pub fn tile_height(&self) -> Option<u32> {
110 self.tag_u32(TAG_TILE_LENGTH)
111 }
112
113 pub fn rows_per_strip(&self) -> Option<u32> {
115 Some(
116 self.tag_u32(TAG_ROWS_PER_STRIP)
117 .unwrap_or_else(|| self.height()),
118 )
119 }
120
121 pub fn sample_format(&self) -> Vec<u16> {
123 self.tag(TAG_SAMPLE_FORMAT)
124 .and_then(|tag| tag.value.as_u16_slice().map(|values| values.to_vec()))
125 .unwrap_or_else(|| vec![1])
126 }
127
128 pub fn planar_configuration(&self) -> u16 {
130 self.tag_u16(TAG_PLANAR_CONFIGURATION).unwrap_or(1)
131 }
132
133 pub fn predictor(&self) -> u16 {
135 self.tag_u16(TAG_PREDICTOR).unwrap_or(1)
136 }
137
138 pub fn lerc_parameters(&self) -> Result<Option<LercParameters>> {
140 let Some(tag) = self.tag(TAG_LERC_PARAMETERS) else {
141 return Ok(None);
142 };
143 let values = tag.value.as_u32_slice().ok_or(Error::UnexpectedTagType {
144 tag: TAG_LERC_PARAMETERS,
145 expected: "LONG",
146 actual: tag.tag_type.to_code(),
147 })?;
148 if values.len() < 2 {
149 return Err(Error::InvalidTagValue {
150 tag: TAG_LERC_PARAMETERS,
151 reason: "LercParameters must contain at least version and additional compression"
152 .into(),
153 });
154 }
155 let additional_compression =
156 LercAdditionalCompression::from_code(values[1]).ok_or(Error::InvalidTagValue {
157 tag: TAG_LERC_PARAMETERS,
158 reason: format!("unsupported LERC additional compression code {}", values[1]),
159 })?;
160 Ok(Some(LercParameters {
161 version: values[0],
162 additional_compression,
163 }))
164 }
165
166 pub fn strip_offsets(&self) -> Option<Vec<u64>> {
168 self.tag_u64_list(TAG_STRIP_OFFSETS)
169 }
170
171 pub fn strip_byte_counts(&self) -> Option<Vec<u64>> {
173 self.tag_u64_list(TAG_STRIP_BYTE_COUNTS)
174 }
175
176 pub fn tile_offsets(&self) -> Option<Vec<u64>> {
178 self.tag_u64_list(TAG_TILE_OFFSETS)
179 }
180
181 pub fn tile_byte_counts(&self) -> Option<Vec<u64>> {
183 self.tag_u64_list(TAG_TILE_BYTE_COUNTS)
184 }
185
186 pub fn raster_layout(&self) -> Result<RasterLayout> {
188 let width = self.width();
189 let height = self.height();
190 if width == 0 || height == 0 {
191 return Err(Error::InvalidImageLayout(format!(
192 "image dimensions must be positive, got {}x{}",
193 width, height
194 )));
195 }
196
197 let samples_per_pixel = self.samples_per_pixel();
198 if samples_per_pixel == 0 {
199 return Err(Error::InvalidImageLayout(
200 "SamplesPerPixel must be greater than zero".into(),
201 ));
202 }
203 let samples_per_pixel = samples_per_pixel as usize;
204
205 let bits = normalize_u16_values(
206 TAG_BITS_PER_SAMPLE,
207 self.bits_per_sample(),
208 samples_per_pixel,
209 1,
210 )?;
211 let formats = normalize_u16_values(
212 TAG_SAMPLE_FORMAT,
213 self.sample_format(),
214 samples_per_pixel,
215 1,
216 )?;
217
218 let first_bits = bits[0];
219 let first_format = formats[0];
220 if !bits.iter().all(|&value| value == first_bits) {
221 return Err(Error::InvalidImageLayout(
222 "mixed BitsPerSample values are not supported".into(),
223 ));
224 }
225 if !formats.iter().all(|&value| value == first_format) {
226 return Err(Error::InvalidImageLayout(
227 "mixed SampleFormat values are not supported".into(),
228 ));
229 }
230 if !matches!(first_bits, 8 | 16 | 32 | 64) {
231 return Err(Error::UnsupportedBitsPerSample(first_bits));
232 }
233 if !matches!(first_format, 1..=3) {
234 return Err(Error::UnsupportedSampleFormat(first_format));
235 }
236
237 let planar_configuration = self.planar_configuration();
238 if !matches!(planar_configuration, 1 | 2) {
239 return Err(Error::UnsupportedPlanarConfiguration(planar_configuration));
240 }
241
242 let predictor = self.predictor();
243 if !matches!(predictor, 1..=3) {
244 return Err(Error::UnsupportedPredictor(predictor));
245 }
246
247 Ok(RasterLayout {
248 width: width as usize,
249 height: height as usize,
250 samples_per_pixel,
251 bits_per_sample: first_bits,
252 bytes_per_sample: (first_bits / 8) as usize,
253 sample_format: first_format,
254 planar_configuration,
255 predictor,
256 })
257 }
258
259 fn tag_u16(&self, code: u16) -> Option<u16> {
260 self.tag(code).and_then(|tag| tag.value.as_u16())
261 }
262
263 fn tag_u32(&self, code: u16) -> Option<u32> {
264 self.tag(code).and_then(|tag| tag.value.as_u32())
265 }
266
267 fn tag_u64_list(&self, code: u16) -> Option<Vec<u64>> {
268 self.tag(code).and_then(|tag| tag.value.as_u64_vec())
269 }
270}
271
272pub fn parse_ifd_chain(source: &dyn TiffSource, header: &TiffHeader) -> Result<Vec<Ifd>> {
274 let mut ifds = Vec::new();
275 let mut offset = header.first_ifd_offset;
276 let mut index = 0usize;
277 let mut seen_offsets = HashSet::new();
278
279 while offset != 0 {
280 if !seen_offsets.insert(offset) {
281 return Err(Error::InvalidImageLayout(format!(
282 "IFD chain contains a loop at offset {offset}"
283 )));
284 }
285 if offset >= source.len() {
286 return Err(Error::Truncated {
287 offset,
288 needed: 2,
289 available: source.len().saturating_sub(offset),
290 });
291 }
292
293 let (tags, next_offset) = read_ifd(source, header, offset)?;
294
295 ifds.push(Ifd { tags, index });
296 offset = next_offset;
297 index += 1;
298
299 if index > 10_000 {
300 return Err(Error::Other("IFD chain exceeds 10,000 entries".into()));
301 }
302 }
303
304 Ok(ifds)
305}
306
307fn read_ifd(source: &dyn TiffSource, header: &TiffHeader, offset: u64) -> Result<(Vec<Tag>, u64)> {
308 let entry_count_size = if header.is_bigtiff() { 8usize } else { 2usize };
309 let entry_size = if header.is_bigtiff() {
310 20usize
311 } else {
312 12usize
313 };
314 let next_offset_size = if header.is_bigtiff() { 8usize } else { 4usize };
315
316 let count_bytes = source.read_exact_at(offset, entry_count_size)?;
317 let mut count_cursor = Cursor::new(&count_bytes, header.byte_order);
318 let count = if header.is_bigtiff() {
319 usize::try_from(count_cursor.read_u64()?).map_err(|_| {
320 Error::InvalidImageLayout("BigTIFF entry count does not fit in usize".into())
321 })?
322 } else {
323 count_cursor.read_u16()? as usize
324 };
325
326 let entries_len = count
327 .checked_mul(entry_size)
328 .and_then(|v| v.checked_add(next_offset_size))
329 .ok_or_else(|| Error::InvalidImageLayout("IFD byte length overflows usize".into()))?;
330 let body = source.read_exact_at(offset + entry_count_size as u64, entries_len)?;
331 let mut cursor = Cursor::new(&body, header.byte_order);
332
333 if header.is_bigtiff() {
334 let tags = parse_tags_bigtiff(&mut cursor, count, source, header.byte_order)?;
335 let next = cursor.read_u64()?;
336 Ok((tags, next))
337 } else {
338 let tags = parse_tags_classic(&mut cursor, count, source, header.byte_order)?;
339 let next = cursor.read_u32()? as u64;
340 Ok((tags, next))
341 }
342}
343
344fn normalize_u16_values(
345 tag: u16,
346 values: Vec<u16>,
347 expected_len: usize,
348 default_value: u16,
349) -> Result<Vec<u16>> {
350 match values.len() {
351 0 => Ok(vec![default_value; expected_len]),
352 1 if expected_len > 1 => Ok(vec![values[0]; expected_len]),
353 len if len == expected_len => Ok(values),
354 len => Err(Error::InvalidTagValue {
355 tag,
356 reason: format!("expected 1 or {expected_len} values, found {len}"),
357 }),
358 }
359}
360
361fn parse_tags_classic(
363 cursor: &mut Cursor<'_>,
364 count: usize,
365 source: &dyn TiffSource,
366 byte_order: ByteOrder,
367) -> Result<Vec<Tag>> {
368 let mut tags = Vec::with_capacity(count);
369 for _ in 0..count {
370 let code = cursor.read_u16()?;
371 let type_code = cursor.read_u16()?;
372 let value_count = cursor.read_u32()? as u64;
373 let value_offset_bytes = cursor.read_bytes(4)?;
374 let tag = parse_tag_classic(
375 code,
376 type_code,
377 value_count,
378 value_offset_bytes,
379 source,
380 byte_order,
381 )?;
382 tags.push(tag);
383 }
384 tags.sort_by_key(|tag| tag.code);
385 Ok(tags)
386}
387
388fn parse_tags_bigtiff(
390 cursor: &mut Cursor<'_>,
391 count: usize,
392 source: &dyn TiffSource,
393 byte_order: ByteOrder,
394) -> Result<Vec<Tag>> {
395 let mut tags = Vec::with_capacity(count);
396 for _ in 0..count {
397 let code = cursor.read_u16()?;
398 let type_code = cursor.read_u16()?;
399 let value_count = cursor.read_u64()?;
400 let value_offset_bytes = cursor.read_bytes(8)?;
401 let tag = parse_tag_bigtiff(
402 code,
403 type_code,
404 value_count,
405 value_offset_bytes,
406 source,
407 byte_order,
408 )?;
409 tags.push(tag);
410 }
411 tags.sort_by_key(|tag| tag.code);
412 Ok(tags)
413}
414
415#[cfg(test)]
416mod tests {
417 use super::{
418 Ifd, LercAdditionalCompression, RasterLayout, TAG_BITS_PER_SAMPLE, TAG_IMAGE_LENGTH,
419 TAG_IMAGE_WIDTH, TAG_LERC_PARAMETERS, TAG_SAMPLES_PER_PIXEL, TAG_SAMPLE_FORMAT,
420 };
421 use crate::tag::{Tag, TagType, TagValue};
422
423 fn make_ifd(tags: Vec<Tag>) -> Ifd {
424 let mut tags = tags;
425 tags.sort_by_key(|tag| tag.code);
426 Ifd { tags, index: 0 }
427 }
428
429 #[test]
430 fn normalizes_single_value_sample_tags() {
431 let ifd = make_ifd(vec![
432 Tag {
433 code: TAG_IMAGE_WIDTH,
434 tag_type: TagType::Long,
435 count: 1,
436 value: TagValue::Long(vec![10]),
437 },
438 Tag {
439 code: TAG_IMAGE_LENGTH,
440 tag_type: TagType::Long,
441 count: 1,
442 value: TagValue::Long(vec![5]),
443 },
444 Tag {
445 code: TAG_SAMPLES_PER_PIXEL,
446 tag_type: TagType::Short,
447 count: 1,
448 value: TagValue::Short(vec![3]),
449 },
450 Tag {
451 code: TAG_BITS_PER_SAMPLE,
452 tag_type: TagType::Short,
453 count: 1,
454 value: TagValue::Short(vec![16]),
455 },
456 Tag {
457 code: TAG_SAMPLE_FORMAT,
458 tag_type: TagType::Short,
459 count: 1,
460 value: TagValue::Short(vec![1]),
461 },
462 ]);
463
464 let layout = ifd.raster_layout().unwrap();
465 assert_eq!(layout.width, 10);
466 assert_eq!(layout.height, 5);
467 assert_eq!(layout.samples_per_pixel, 3);
468 assert_eq!(layout.bytes_per_sample, 2);
469 }
470
471 #[test]
472 fn rejects_mixed_sample_formats() {
473 let ifd = make_ifd(vec![
474 Tag {
475 code: TAG_IMAGE_WIDTH,
476 tag_type: TagType::Long,
477 count: 1,
478 value: TagValue::Long(vec![1]),
479 },
480 Tag {
481 code: TAG_IMAGE_LENGTH,
482 tag_type: TagType::Long,
483 count: 1,
484 value: TagValue::Long(vec![1]),
485 },
486 Tag {
487 code: TAG_SAMPLES_PER_PIXEL,
488 tag_type: TagType::Short,
489 count: 1,
490 value: TagValue::Short(vec![2]),
491 },
492 Tag {
493 code: TAG_BITS_PER_SAMPLE,
494 tag_type: TagType::Short,
495 count: 2,
496 value: TagValue::Short(vec![16, 16]),
497 },
498 Tag {
499 code: TAG_SAMPLE_FORMAT,
500 tag_type: TagType::Short,
501 count: 2,
502 value: TagValue::Short(vec![1, 3]),
503 },
504 ]);
505
506 assert!(ifd.raster_layout().is_err());
507 }
508
509 #[test]
510 fn raster_layout_helpers_match_expected_strides() {
511 let layout = RasterLayout {
512 width: 4,
513 height: 3,
514 samples_per_pixel: 2,
515 bits_per_sample: 16,
516 bytes_per_sample: 2,
517 sample_format: 1,
518 planar_configuration: 1,
519 predictor: 1,
520 };
521 assert_eq!(layout.pixel_stride_bytes(), 4);
522 assert_eq!(layout.row_bytes(), 16);
523 assert_eq!(layout.sample_plane_row_bytes(), 8);
524 }
525
526 #[test]
527 fn parses_lerc_parameters() {
528 let ifd = make_ifd(vec![Tag {
529 code: TAG_LERC_PARAMETERS,
530 tag_type: TagType::Long,
531 count: 2,
532 value: TagValue::Long(vec![4, 2]),
533 }]);
534
535 let params = ifd.lerc_parameters().unwrap().unwrap();
536 assert_eq!(params.version, 4);
537 assert_eq!(
538 params.additional_compression,
539 LercAdditionalCompression::Zstd
540 );
541 }
542}