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