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,
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)]
19pub struct Ifd {
20 tags: Vec<Tag>,
22 pub index: usize,
24}
25
26impl Ifd {
27 pub fn tag(&self, code: u16) -> Option<&Tag> {
29 self.tags
30 .binary_search_by_key(&code, |tag| tag.code)
31 .ok()
32 .map(|index| &self.tags[index])
33 }
34
35 pub fn tags(&self) -> &[Tag] {
37 &self.tags
38 }
39
40 pub fn width(&self) -> u32 {
42 self.tag_u32(TAG_IMAGE_WIDTH).unwrap_or(0)
43 }
44
45 pub fn height(&self) -> u32 {
47 self.tag_u32(TAG_IMAGE_LENGTH).unwrap_or(0)
48 }
49
50 pub fn bits_per_sample(&self) -> Vec<u16> {
52 self.tag(TAG_BITS_PER_SAMPLE)
53 .and_then(|tag| tag.value.as_u16_slice().map(|values| values.to_vec()))
54 .unwrap_or_else(|| vec![1])
55 }
56
57 pub fn compression(&self) -> u16 {
59 self.tag_u16(TAG_COMPRESSION).unwrap_or(1)
60 }
61
62 pub fn photometric_interpretation(&self) -> Option<u16> {
64 self.tag_u16(TAG_PHOTOMETRIC_INTERPRETATION)
65 }
66
67 pub fn samples_per_pixel(&self) -> u16 {
69 self.tag_u16(TAG_SAMPLES_PER_PIXEL).unwrap_or(1)
70 }
71
72 pub fn is_tiled(&self) -> bool {
74 self.tag(TAG_TILE_WIDTH).is_some() && self.tag(TAG_TILE_LENGTH).is_some()
75 }
76
77 pub fn tile_width(&self) -> Option<u32> {
79 self.tag_u32(TAG_TILE_WIDTH)
80 }
81
82 pub fn tile_height(&self) -> Option<u32> {
84 self.tag_u32(TAG_TILE_LENGTH)
85 }
86
87 pub fn rows_per_strip(&self) -> Option<u32> {
89 Some(
90 self.tag_u32(TAG_ROWS_PER_STRIP)
91 .unwrap_or_else(|| self.height()),
92 )
93 }
94
95 pub fn sample_format(&self) -> Vec<u16> {
97 self.tag(TAG_SAMPLE_FORMAT)
98 .and_then(|tag| tag.value.as_u16_slice().map(|values| values.to_vec()))
99 .unwrap_or_else(|| vec![1])
100 }
101
102 pub fn planar_configuration(&self) -> u16 {
104 self.tag_u16(TAG_PLANAR_CONFIGURATION).unwrap_or(1)
105 }
106
107 pub fn predictor(&self) -> u16 {
109 self.tag_u16(TAG_PREDICTOR).unwrap_or(1)
110 }
111
112 pub fn strip_offsets(&self) -> Option<Vec<u64>> {
114 self.tag_u64_list(TAG_STRIP_OFFSETS)
115 }
116
117 pub fn strip_byte_counts(&self) -> Option<Vec<u64>> {
119 self.tag_u64_list(TAG_STRIP_BYTE_COUNTS)
120 }
121
122 pub fn tile_offsets(&self) -> Option<Vec<u64>> {
124 self.tag_u64_list(TAG_TILE_OFFSETS)
125 }
126
127 pub fn tile_byte_counts(&self) -> Option<Vec<u64>> {
129 self.tag_u64_list(TAG_TILE_BYTE_COUNTS)
130 }
131
132 pub fn raster_layout(&self) -> Result<RasterLayout> {
134 let width = self.width();
135 let height = self.height();
136 if width == 0 || height == 0 {
137 return Err(Error::InvalidImageLayout(format!(
138 "image dimensions must be positive, got {}x{}",
139 width, height
140 )));
141 }
142
143 let samples_per_pixel = self.samples_per_pixel();
144 if samples_per_pixel == 0 {
145 return Err(Error::InvalidImageLayout(
146 "SamplesPerPixel must be greater than zero".into(),
147 ));
148 }
149 let samples_per_pixel = samples_per_pixel as usize;
150
151 let bits = normalize_u16_values(
152 TAG_BITS_PER_SAMPLE,
153 self.bits_per_sample(),
154 samples_per_pixel,
155 1,
156 )?;
157 let formats = normalize_u16_values(
158 TAG_SAMPLE_FORMAT,
159 self.sample_format(),
160 samples_per_pixel,
161 1,
162 )?;
163
164 let first_bits = bits[0];
165 let first_format = formats[0];
166 if !bits.iter().all(|&value| value == first_bits) {
167 return Err(Error::InvalidImageLayout(
168 "mixed BitsPerSample values are not supported".into(),
169 ));
170 }
171 if !formats.iter().all(|&value| value == first_format) {
172 return Err(Error::InvalidImageLayout(
173 "mixed SampleFormat values are not supported".into(),
174 ));
175 }
176 if !matches!(first_bits, 8 | 16 | 32 | 64) {
177 return Err(Error::UnsupportedBitsPerSample(first_bits));
178 }
179 if !matches!(first_format, 1..=3) {
180 return Err(Error::UnsupportedSampleFormat(first_format));
181 }
182
183 let planar_configuration = self.planar_configuration();
184 if !matches!(planar_configuration, 1 | 2) {
185 return Err(Error::UnsupportedPlanarConfiguration(planar_configuration));
186 }
187
188 let predictor = self.predictor();
189 if !matches!(predictor, 1..=3) {
190 return Err(Error::UnsupportedPredictor(predictor));
191 }
192
193 Ok(RasterLayout {
194 width: width as usize,
195 height: height as usize,
196 samples_per_pixel,
197 bits_per_sample: first_bits,
198 bytes_per_sample: (first_bits / 8) as usize,
199 sample_format: first_format,
200 planar_configuration,
201 predictor,
202 })
203 }
204
205 fn tag_u16(&self, code: u16) -> Option<u16> {
206 self.tag(code).and_then(|tag| tag.value.as_u16())
207 }
208
209 fn tag_u32(&self, code: u16) -> Option<u32> {
210 self.tag(code).and_then(|tag| tag.value.as_u32())
211 }
212
213 fn tag_u64_list(&self, code: u16) -> Option<Vec<u64>> {
214 self.tag(code).and_then(|tag| tag.value.as_u64_vec())
215 }
216}
217
218pub fn parse_ifd_chain(source: &dyn TiffSource, header: &TiffHeader) -> Result<Vec<Ifd>> {
220 let mut ifds = Vec::new();
221 let mut offset = header.first_ifd_offset;
222 let mut index = 0usize;
223 let mut seen_offsets = HashSet::new();
224
225 while offset != 0 {
226 if !seen_offsets.insert(offset) {
227 return Err(Error::InvalidImageLayout(format!(
228 "IFD chain contains a loop at offset {offset}"
229 )));
230 }
231 if offset >= source.len() {
232 return Err(Error::Truncated {
233 offset,
234 needed: 2,
235 available: source.len().saturating_sub(offset),
236 });
237 }
238
239 let (tags, next_offset) = read_ifd(source, header, offset)?;
240
241 ifds.push(Ifd { tags, index });
242 offset = next_offset;
243 index += 1;
244
245 if index > 10_000 {
246 return Err(Error::Other("IFD chain exceeds 10,000 entries".into()));
247 }
248 }
249
250 Ok(ifds)
251}
252
253fn read_ifd(source: &dyn TiffSource, header: &TiffHeader, offset: u64) -> Result<(Vec<Tag>, u64)> {
254 let entry_count_size = if header.is_bigtiff() { 8usize } else { 2usize };
255 let entry_size = if header.is_bigtiff() {
256 20usize
257 } else {
258 12usize
259 };
260 let next_offset_size = if header.is_bigtiff() { 8usize } else { 4usize };
261
262 let count_bytes = source.read_exact_at(offset, entry_count_size)?;
263 let mut count_cursor = Cursor::new(&count_bytes, header.byte_order);
264 let count = if header.is_bigtiff() {
265 usize::try_from(count_cursor.read_u64()?).map_err(|_| {
266 Error::InvalidImageLayout("BigTIFF entry count does not fit in usize".into())
267 })?
268 } else {
269 count_cursor.read_u16()? as usize
270 };
271
272 let entries_len = count
273 .checked_mul(entry_size)
274 .and_then(|v| v.checked_add(next_offset_size))
275 .ok_or_else(|| Error::InvalidImageLayout("IFD byte length overflows usize".into()))?;
276 let body = source.read_exact_at(offset + entry_count_size as u64, entries_len)?;
277 let mut cursor = Cursor::new(&body, header.byte_order);
278
279 if header.is_bigtiff() {
280 let tags = parse_tags_bigtiff(&mut cursor, count, source, header.byte_order)?;
281 let next = cursor.read_u64()?;
282 Ok((tags, next))
283 } else {
284 let tags = parse_tags_classic(&mut cursor, count, source, header.byte_order)?;
285 let next = cursor.read_u32()? as u64;
286 Ok((tags, next))
287 }
288}
289
290fn normalize_u16_values(
291 tag: u16,
292 values: Vec<u16>,
293 expected_len: usize,
294 default_value: u16,
295) -> Result<Vec<u16>> {
296 match values.len() {
297 0 => Ok(vec![default_value; expected_len]),
298 1 if expected_len > 1 => Ok(vec![values[0]; expected_len]),
299 len if len == expected_len => Ok(values),
300 len => Err(Error::InvalidTagValue {
301 tag,
302 reason: format!("expected 1 or {expected_len} values, found {len}"),
303 }),
304 }
305}
306
307fn parse_tags_classic(
309 cursor: &mut Cursor<'_>,
310 count: usize,
311 source: &dyn TiffSource,
312 byte_order: ByteOrder,
313) -> Result<Vec<Tag>> {
314 let mut tags = Vec::with_capacity(count);
315 for _ in 0..count {
316 let code = cursor.read_u16()?;
317 let type_code = cursor.read_u16()?;
318 let value_count = cursor.read_u32()? as u64;
319 let value_offset_bytes = cursor.read_bytes(4)?;
320 let tag = parse_tag_classic(
321 code,
322 type_code,
323 value_count,
324 value_offset_bytes,
325 source,
326 byte_order,
327 )?;
328 tags.push(tag);
329 }
330 tags.sort_by_key(|tag| tag.code);
331 Ok(tags)
332}
333
334fn parse_tags_bigtiff(
336 cursor: &mut Cursor<'_>,
337 count: usize,
338 source: &dyn TiffSource,
339 byte_order: ByteOrder,
340) -> Result<Vec<Tag>> {
341 let mut tags = Vec::with_capacity(count);
342 for _ in 0..count {
343 let code = cursor.read_u16()?;
344 let type_code = cursor.read_u16()?;
345 let value_count = cursor.read_u64()?;
346 let value_offset_bytes = cursor.read_bytes(8)?;
347 let tag = parse_tag_bigtiff(
348 code,
349 type_code,
350 value_count,
351 value_offset_bytes,
352 source,
353 byte_order,
354 )?;
355 tags.push(tag);
356 }
357 tags.sort_by_key(|tag| tag.code);
358 Ok(tags)
359}
360
361#[cfg(test)]
362mod tests {
363 use super::{
364 Ifd, RasterLayout, TAG_BITS_PER_SAMPLE, TAG_IMAGE_LENGTH, TAG_IMAGE_WIDTH,
365 TAG_SAMPLES_PER_PIXEL, TAG_SAMPLE_FORMAT,
366 };
367 use crate::tag::{Tag, TagType, TagValue};
368
369 fn make_ifd(tags: Vec<Tag>) -> Ifd {
370 let mut tags = tags;
371 tags.sort_by_key(|tag| tag.code);
372 Ifd { tags, index: 0 }
373 }
374
375 #[test]
376 fn normalizes_single_value_sample_tags() {
377 let ifd = make_ifd(vec![
378 Tag {
379 code: TAG_IMAGE_WIDTH,
380 tag_type: TagType::Long,
381 count: 1,
382 value: TagValue::Long(vec![10]),
383 },
384 Tag {
385 code: TAG_IMAGE_LENGTH,
386 tag_type: TagType::Long,
387 count: 1,
388 value: TagValue::Long(vec![5]),
389 },
390 Tag {
391 code: TAG_SAMPLES_PER_PIXEL,
392 tag_type: TagType::Short,
393 count: 1,
394 value: TagValue::Short(vec![3]),
395 },
396 Tag {
397 code: TAG_BITS_PER_SAMPLE,
398 tag_type: TagType::Short,
399 count: 1,
400 value: TagValue::Short(vec![16]),
401 },
402 Tag {
403 code: TAG_SAMPLE_FORMAT,
404 tag_type: TagType::Short,
405 count: 1,
406 value: TagValue::Short(vec![1]),
407 },
408 ]);
409
410 let layout = ifd.raster_layout().unwrap();
411 assert_eq!(layout.width, 10);
412 assert_eq!(layout.height, 5);
413 assert_eq!(layout.samples_per_pixel, 3);
414 assert_eq!(layout.bytes_per_sample, 2);
415 }
416
417 #[test]
418 fn rejects_mixed_sample_formats() {
419 let ifd = make_ifd(vec![
420 Tag {
421 code: TAG_IMAGE_WIDTH,
422 tag_type: TagType::Long,
423 count: 1,
424 value: TagValue::Long(vec![1]),
425 },
426 Tag {
427 code: TAG_IMAGE_LENGTH,
428 tag_type: TagType::Long,
429 count: 1,
430 value: TagValue::Long(vec![1]),
431 },
432 Tag {
433 code: TAG_SAMPLES_PER_PIXEL,
434 tag_type: TagType::Short,
435 count: 1,
436 value: TagValue::Short(vec![2]),
437 },
438 Tag {
439 code: TAG_BITS_PER_SAMPLE,
440 tag_type: TagType::Short,
441 count: 2,
442 value: TagValue::Short(vec![16, 16]),
443 },
444 Tag {
445 code: TAG_SAMPLE_FORMAT,
446 tag_type: TagType::Short,
447 count: 2,
448 value: TagValue::Short(vec![1, 3]),
449 },
450 ]);
451
452 assert!(ifd.raster_layout().is_err());
453 }
454
455 #[test]
456 fn raster_layout_helpers_match_expected_strides() {
457 let layout = RasterLayout {
458 width: 4,
459 height: 3,
460 samples_per_pixel: 2,
461 bits_per_sample: 16,
462 bytes_per_sample: 2,
463 sample_format: 1,
464 planar_configuration: 1,
465 predictor: 1,
466 };
467 assert_eq!(layout.pixel_stride_bytes(), 4);
468 assert_eq!(layout.row_bytes(), 16);
469 assert_eq!(layout.sample_plane_row_bytes(), 8);
470 }
471}