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_COLOR_MAP, TAG_COMPRESSION, TAG_EXTRA_SAMPLES, TAG_IMAGE_LENGTH,
11 TAG_IMAGE_WIDTH, TAG_INK_SET, TAG_LERC_PARAMETERS, TAG_PHOTOMETRIC_INTERPRETATION,
12 TAG_PLANAR_CONFIGURATION, TAG_PREDICTOR, TAG_REFERENCE_BLACK_WHITE, TAG_ROWS_PER_STRIP,
13 TAG_SAMPLES_PER_PIXEL, TAG_SAMPLE_FORMAT, TAG_STRIP_BYTE_COUNTS, TAG_STRIP_OFFSETS,
14 TAG_SUB_IFDS, TAG_TILE_BYTE_COUNTS, TAG_TILE_LENGTH, TAG_TILE_OFFSETS, TAG_TILE_WIDTH,
15 TAG_YCBCR_POSITIONING, TAG_YCBCR_SUBSAMPLING,
16};
17pub use tiff_core::RasterLayout;
18
19pub use tiff_core::{
20 ColorMap, ColorModel, ExtraSample, InkSet, LercAdditionalCompression,
21 PhotometricInterpretation, YCbCrPositioning,
22};
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub struct LercParameters {
27 pub version: u32,
28 pub additional_compression: LercAdditionalCompression,
29}
30
31#[derive(Debug, Clone)]
33pub struct Ifd {
34 tags: Vec<Tag>,
36 pub index: usize,
38}
39
40impl Ifd {
41 pub fn tag(&self, code: u16) -> Option<&Tag> {
43 self.tags
44 .binary_search_by_key(&code, |tag| tag.code)
45 .ok()
46 .map(|index| &self.tags[index])
47 }
48
49 pub fn tags(&self) -> &[Tag] {
51 &self.tags
52 }
53
54 pub fn width(&self) -> u32 {
56 self.tag_u32(TAG_IMAGE_WIDTH).unwrap_or(0)
57 }
58
59 pub fn height(&self) -> u32 {
61 self.tag_u32(TAG_IMAGE_LENGTH).unwrap_or(0)
62 }
63
64 pub fn bits_per_sample(&self) -> Vec<u16> {
66 self.tag(TAG_BITS_PER_SAMPLE)
67 .and_then(|tag| tag.value.as_u16_slice().map(|values| values.to_vec()))
68 .unwrap_or_else(|| vec![1])
69 }
70
71 pub fn compression(&self) -> u16 {
73 self.tag_u16(TAG_COMPRESSION).unwrap_or(1)
74 }
75
76 pub fn photometric_interpretation(&self) -> Option<u16> {
78 self.tag_u16(TAG_PHOTOMETRIC_INTERPRETATION)
79 }
80
81 pub fn photometric_interpretation_enum(&self) -> Option<PhotometricInterpretation> {
84 PhotometricInterpretation::from_code(self.photometric_interpretation().unwrap_or(1))
85 }
86
87 pub fn samples_per_pixel(&self) -> u16 {
89 self.tag_u16(TAG_SAMPLES_PER_PIXEL).unwrap_or(1)
90 }
91
92 pub fn is_tiled(&self) -> bool {
94 self.tag(TAG_TILE_WIDTH).is_some() && self.tag(TAG_TILE_LENGTH).is_some()
95 }
96
97 pub fn tile_width(&self) -> Option<u32> {
99 self.tag_u32(TAG_TILE_WIDTH)
100 }
101
102 pub fn tile_height(&self) -> Option<u32> {
104 self.tag_u32(TAG_TILE_LENGTH)
105 }
106
107 pub fn rows_per_strip(&self) -> Option<u32> {
109 Some(
110 self.tag_u32(TAG_ROWS_PER_STRIP)
111 .unwrap_or_else(|| self.height()),
112 )
113 }
114
115 pub fn sample_format(&self) -> Vec<u16> {
117 self.tag(TAG_SAMPLE_FORMAT)
118 .and_then(|tag| tag.value.as_u16_slice().map(|values| values.to_vec()))
119 .unwrap_or_else(|| vec![1])
120 }
121
122 pub fn planar_configuration(&self) -> u16 {
124 self.tag_u16(TAG_PLANAR_CONFIGURATION).unwrap_or(1)
125 }
126
127 pub fn predictor(&self) -> u16 {
129 self.tag_u16(TAG_PREDICTOR).unwrap_or(1)
130 }
131
132 pub fn lerc_parameters(&self) -> Result<Option<LercParameters>> {
134 let Some(tag) = self.tag(TAG_LERC_PARAMETERS) else {
135 return Ok(None);
136 };
137 let values = tag.value.as_u32_slice().ok_or(Error::UnexpectedTagType {
138 tag: TAG_LERC_PARAMETERS,
139 expected: "LONG",
140 actual: tag.tag_type.to_code(),
141 })?;
142 if values.len() < 2 {
143 return Err(Error::InvalidTagValue {
144 tag: TAG_LERC_PARAMETERS,
145 reason: "LercParameters must contain at least version and additional compression"
146 .into(),
147 });
148 }
149 let additional_compression =
150 LercAdditionalCompression::from_code(values[1]).ok_or(Error::InvalidTagValue {
151 tag: TAG_LERC_PARAMETERS,
152 reason: format!("unsupported LERC additional compression code {}", values[1]),
153 })?;
154 Ok(Some(LercParameters {
155 version: values[0],
156 additional_compression,
157 }))
158 }
159
160 pub fn extra_samples(&self) -> Result<Vec<ExtraSample>> {
162 let Some(tag) = self.tag(TAG_EXTRA_SAMPLES) else {
163 return Ok(Vec::new());
164 };
165 let values = tag.value.as_u16_slice().ok_or(Error::UnexpectedTagType {
166 tag: TAG_EXTRA_SAMPLES,
167 expected: "SHORT",
168 actual: tag.tag_type.to_code(),
169 })?;
170 Ok(values.iter().copied().map(ExtraSample::from_code).collect())
171 }
172
173 pub fn color_map(&self) -> Result<Option<ColorMap>> {
175 let Some(tag) = self.tag(TAG_COLOR_MAP) else {
176 return Ok(None);
177 };
178 let values = tag.value.as_u16_slice().ok_or(Error::UnexpectedTagType {
179 tag: TAG_COLOR_MAP,
180 expected: "SHORT",
181 actual: tag.tag_type.to_code(),
182 })?;
183 ColorMap::from_tag_values(values)
184 .map(Some)
185 .map_err(|reason| Error::InvalidTagValue {
186 tag: TAG_COLOR_MAP,
187 reason,
188 })
189 }
190
191 pub fn ink_set(&self) -> Result<Option<InkSet>> {
193 let Some(tag) = self.tag(TAG_INK_SET) else {
194 return Ok(None);
195 };
196 let value = tag.value.as_u16().ok_or(Error::UnexpectedTagType {
197 tag: TAG_INK_SET,
198 expected: "SHORT",
199 actual: tag.tag_type.to_code(),
200 })?;
201 Ok(Some(InkSet::from_code(value)))
202 }
203
204 pub fn ycbcr_subsampling(&self) -> Result<Option<[u16; 2]>> {
206 let Some(tag) = self.tag(TAG_YCBCR_SUBSAMPLING) else {
207 return Ok(None);
208 };
209 let values = tag.value.as_u16_slice().ok_or(Error::UnexpectedTagType {
210 tag: TAG_YCBCR_SUBSAMPLING,
211 expected: "SHORT",
212 actual: tag.tag_type.to_code(),
213 })?;
214 match values {
215 [h, v] => Ok(Some([*h, *v])),
216 _ => Err(Error::InvalidTagValue {
217 tag: TAG_YCBCR_SUBSAMPLING,
218 reason: format!("expected 2 SHORT values, found {}", values.len()),
219 }),
220 }
221 }
222
223 pub fn ycbcr_positioning(&self) -> Result<Option<YCbCrPositioning>> {
225 let Some(tag) = self.tag(TAG_YCBCR_POSITIONING) else {
226 return Ok(None);
227 };
228 let value = tag.value.as_u16().ok_or(Error::UnexpectedTagType {
229 tag: TAG_YCBCR_POSITIONING,
230 expected: "SHORT",
231 actual: tag.tag_type.to_code(),
232 })?;
233 Ok(Some(YCbCrPositioning::from_code(value)))
234 }
235
236 pub fn reference_black_white(&self) -> Result<Option<[f64; 6]>> {
238 let Some(tag) = self.tag(TAG_REFERENCE_BLACK_WHITE) else {
239 return Ok(None);
240 };
241 let values = tag.value.as_f64_vec().ok_or(Error::UnexpectedTagType {
242 tag: TAG_REFERENCE_BLACK_WHITE,
243 expected: "RATIONAL or DOUBLE",
244 actual: tag.tag_type.to_code(),
245 })?;
246 match values.as_slice() {
247 [a, b, c, d, e, f] => Ok(Some([*a, *b, *c, *d, *e, *f])),
248 _ => Err(Error::InvalidTagValue {
249 tag: TAG_REFERENCE_BLACK_WHITE,
250 reason: format!("expected 6 values, found {}", values.len()),
251 }),
252 }
253 }
254
255 pub fn color_model(&self) -> Result<ColorModel> {
258 let photometric = self
259 .photometric_interpretation_enum()
260 .ok_or(Error::InvalidTagValue {
261 tag: TAG_PHOTOMETRIC_INTERPRETATION,
262 reason: format!(
263 "unsupported photometric interpretation {}",
264 self.photometric_interpretation().unwrap_or(1)
265 ),
266 })?;
267 let samples_per_pixel = self.samples_per_pixel();
268 let extra_samples = self.extra_samples()?;
269
270 match photometric {
271 PhotometricInterpretation::MinIsWhite => Ok(ColorModel::Grayscale {
272 white_is_zero: true,
273 extra_samples: resolve_fixed_model_extra_samples(
274 photometric,
275 samples_per_pixel,
276 1,
277 extra_samples,
278 )?,
279 }),
280 PhotometricInterpretation::MinIsBlack => Ok(ColorModel::Grayscale {
281 white_is_zero: false,
282 extra_samples: resolve_fixed_model_extra_samples(
283 photometric,
284 samples_per_pixel,
285 1,
286 extra_samples,
287 )?,
288 }),
289 PhotometricInterpretation::Rgb => Ok(ColorModel::Rgb {
290 extra_samples: resolve_fixed_model_extra_samples(
291 photometric,
292 samples_per_pixel,
293 3,
294 extra_samples,
295 )?,
296 }),
297 PhotometricInterpretation::Palette => {
298 let color_map = self.color_map()?.ok_or(Error::InvalidImageLayout(
299 "palette TIFF is missing ColorMap".into(),
300 ))?;
301 Ok(ColorModel::Palette {
302 color_map,
303 extra_samples: resolve_fixed_model_extra_samples(
304 photometric,
305 samples_per_pixel,
306 1,
307 extra_samples,
308 )?,
309 })
310 }
311 PhotometricInterpretation::Mask => Ok(ColorModel::TransparencyMask),
312 PhotometricInterpretation::Separated => {
313 let ink_set = self.ink_set()?.unwrap_or(InkSet::Cmyk);
314 if ink_set == InkSet::Cmyk {
315 let extra_samples = resolve_fixed_model_extra_samples(
316 photometric,
317 samples_per_pixel,
318 4,
319 extra_samples,
320 )?;
321 Ok(ColorModel::Cmyk { extra_samples })
322 } else {
323 let color_channels = samples_per_pixel
324 .checked_sub(extra_samples.len() as u16)
325 .ok_or_else(|| {
326 Error::InvalidImageLayout(format!(
327 "{} photometric interpretation defines more ExtraSamples than total channels",
328 photometric_name(photometric)
329 ))
330 })?;
331 Ok(ColorModel::Separated {
332 ink_set,
333 color_channels,
334 extra_samples,
335 })
336 }
337 }
338 PhotometricInterpretation::YCbCr => Ok(ColorModel::YCbCr {
339 subsampling: self.ycbcr_subsampling()?.unwrap_or([1, 1]),
340 positioning: self
341 .ycbcr_positioning()?
342 .unwrap_or(YCbCrPositioning::Centered),
343 extra_samples: resolve_fixed_model_extra_samples(
344 photometric,
345 samples_per_pixel,
346 3,
347 extra_samples,
348 )?,
349 }),
350 PhotometricInterpretation::CieLab => Ok(ColorModel::CieLab {
351 extra_samples: resolve_fixed_model_extra_samples(
352 photometric,
353 samples_per_pixel,
354 3,
355 extra_samples,
356 )?,
357 }),
358 }
359 }
360
361 pub fn strip_offsets(&self) -> Option<Vec<u64>> {
363 self.tag_u64_list(TAG_STRIP_OFFSETS)
364 }
365
366 pub fn strip_byte_counts(&self) -> Option<Vec<u64>> {
368 self.tag_u64_list(TAG_STRIP_BYTE_COUNTS)
369 }
370
371 pub fn tile_offsets(&self) -> Option<Vec<u64>> {
373 self.tag_u64_list(TAG_TILE_OFFSETS)
374 }
375
376 pub fn tile_byte_counts(&self) -> Option<Vec<u64>> {
378 self.tag_u64_list(TAG_TILE_BYTE_COUNTS)
379 }
380
381 pub fn sub_ifd_offsets(&self) -> Option<Vec<u64>> {
383 self.tag_u64_list(TAG_SUB_IFDS)
384 }
385
386 pub fn raster_layout(&self) -> Result<RasterLayout> {
388 let width = self.width();
389 let height = self.height();
390 if width == 0 || height == 0 {
391 return Err(Error::InvalidImageLayout(format!(
392 "image dimensions must be positive, got {}x{}",
393 width, height
394 )));
395 }
396
397 let samples_per_pixel = self.samples_per_pixel();
398 if samples_per_pixel == 0 {
399 return Err(Error::InvalidImageLayout(
400 "SamplesPerPixel must be greater than zero".into(),
401 ));
402 }
403 let samples_per_pixel = samples_per_pixel as usize;
404
405 let bits = normalize_u16_values(
406 TAG_BITS_PER_SAMPLE,
407 self.bits_per_sample(),
408 samples_per_pixel,
409 1,
410 )?;
411 let formats = normalize_u16_values(
412 TAG_SAMPLE_FORMAT,
413 self.sample_format(),
414 samples_per_pixel,
415 1,
416 )?;
417
418 let first_bits = bits[0];
419 let first_format = formats[0];
420 if !bits.iter().all(|&value| value == first_bits) {
421 return Err(Error::InvalidImageLayout(
422 "mixed BitsPerSample values are not supported".into(),
423 ));
424 }
425 if !formats.iter().all(|&value| value == first_format) {
426 return Err(Error::InvalidImageLayout(
427 "mixed SampleFormat values are not supported".into(),
428 ));
429 }
430 if !matches!(first_format, 1..=3) {
431 return Err(Error::UnsupportedSampleFormat(first_format));
432 }
433 validate_sample_encoding(first_format, first_bits)?;
434
435 let planar_configuration = self.planar_configuration();
436 if !matches!(planar_configuration, 1 | 2) {
437 return Err(Error::UnsupportedPlanarConfiguration(planar_configuration));
438 }
439
440 let predictor = self.predictor();
441 if !matches!(predictor, 1..=3) {
442 return Err(Error::UnsupportedPredictor(predictor));
443 }
444 if first_bits < 8 && predictor != 1 {
445 return Err(Error::InvalidImageLayout(
446 "predictors are not supported for sub-byte sample encodings".into(),
447 ));
448 }
449
450 validate_color_model(self, samples_per_pixel as u16, first_bits)?;
451
452 Ok(RasterLayout {
453 width: width as usize,
454 height: height as usize,
455 samples_per_pixel,
456 bits_per_sample: first_bits,
457 bytes_per_sample: usize::from(first_bits.div_ceil(8)),
458 sample_format: first_format,
459 planar_configuration,
460 predictor,
461 })
462 }
463
464 pub fn decoded_raster_layout(&self) -> Result<RasterLayout> {
470 let storage = self.raster_layout()?;
471 let color_model = self.color_model()?;
472 let decoded_samples = match &color_model {
473 ColorModel::Palette { extra_samples, .. } => 3 + extra_samples.len(),
474 ColorModel::Cmyk { extra_samples } => 3 + extra_samples.len(),
475 ColorModel::YCbCr { extra_samples, .. } => 3 + extra_samples.len(),
476 ColorModel::Grayscale { extra_samples, .. } => 1 + extra_samples.len(),
477 ColorModel::Rgb { extra_samples } => 3 + extra_samples.len(),
478 ColorModel::Separated {
479 color_channels,
480 extra_samples,
481 ..
482 } => *color_channels as usize + extra_samples.len(),
483 ColorModel::CieLab { extra_samples } => 3 + extra_samples.len(),
484 ColorModel::TransparencyMask => 1,
485 };
486 let (sample_format, bits_per_sample) = match &color_model {
487 ColorModel::Palette { color_map, .. } => {
488 if color_map_is_u8_equivalent(color_map) {
489 (1, 8)
490 } else {
491 (1, 16)
492 }
493 }
494 ColorModel::YCbCr { .. } | ColorModel::Cmyk { .. } => {
495 if storage.sample_format != 1 {
496 return Err(Error::InvalidImageLayout(
497 "decoded YCbCr/CMYK reads require unsigned integer source samples".into(),
498 ));
499 }
500 (1, decoded_uint_bits(storage.bits_per_sample))
501 }
502 _ => (
503 storage.sample_format,
504 decoded_bits(storage.sample_format, storage.bits_per_sample)?,
505 ),
506 };
507
508 Ok(RasterLayout {
509 width: storage.width,
510 height: storage.height,
511 samples_per_pixel: decoded_samples,
512 bits_per_sample,
513 bytes_per_sample: usize::from(bits_per_sample.div_ceil(8)),
514 sample_format,
515 planar_configuration: 1,
516 predictor: 1,
517 })
518 }
519
520 fn tag_u16(&self, code: u16) -> Option<u16> {
521 self.tag(code).and_then(|tag| tag.value.as_u16())
522 }
523
524 fn tag_u32(&self, code: u16) -> Option<u32> {
525 self.tag(code).and_then(|tag| tag.value.as_u32())
526 }
527
528 fn tag_u64_list(&self, code: u16) -> Option<Vec<u64>> {
529 self.tag(code).and_then(|tag| tag.value.as_u64_vec())
530 }
531}
532
533pub fn parse_ifd_chain(source: &dyn TiffSource, header: &TiffHeader) -> Result<Vec<Ifd>> {
535 let mut ifds = Vec::new();
536 let mut offset = header.first_ifd_offset;
537 let mut index = 0usize;
538 let mut seen_offsets = HashSet::new();
539
540 while offset != 0 {
541 if !seen_offsets.insert(offset) {
542 return Err(Error::InvalidImageLayout(format!(
543 "IFD chain contains a loop at offset {offset}"
544 )));
545 }
546 if offset >= source.len() {
547 return Err(Error::Truncated {
548 offset,
549 needed: 2,
550 available: source.len().saturating_sub(offset),
551 });
552 }
553
554 let (tags, next_offset) = read_ifd(source, header, offset)?;
555
556 ifds.push(Ifd { tags, index });
557 offset = next_offset;
558 index += 1;
559
560 if index > 10_000 {
561 return Err(Error::Other("IFD chain exceeds 10,000 entries".into()));
562 }
563 }
564
565 Ok(ifds)
566}
567
568pub fn parse_ifd_at(source: &dyn TiffSource, header: &TiffHeader, offset: u64) -> Result<Ifd> {
570 let (tags, _) = read_ifd(source, header, offset)?;
571 Ok(Ifd {
572 tags,
573 index: usize::try_from(offset).unwrap_or(usize::MAX),
574 })
575}
576
577fn read_ifd(source: &dyn TiffSource, header: &TiffHeader, offset: u64) -> Result<(Vec<Tag>, u64)> {
578 let entry_count_size = if header.is_bigtiff() { 8usize } else { 2usize };
579 let entry_size = if header.is_bigtiff() {
580 20usize
581 } else {
582 12usize
583 };
584 let next_offset_size = if header.is_bigtiff() { 8usize } else { 4usize };
585
586 let count_bytes = source.read_exact_at(offset, entry_count_size)?;
587 let mut count_cursor = Cursor::new(&count_bytes, header.byte_order);
588 let count = if header.is_bigtiff() {
589 usize::try_from(count_cursor.read_u64()?).map_err(|_| {
590 Error::InvalidImageLayout("BigTIFF entry count does not fit in usize".into())
591 })?
592 } else {
593 count_cursor.read_u16()? as usize
594 };
595
596 let entries_len = count
597 .checked_mul(entry_size)
598 .and_then(|v| v.checked_add(next_offset_size))
599 .ok_or_else(|| Error::InvalidImageLayout("IFD byte length overflows usize".into()))?;
600 let body = source.read_exact_at(offset + entry_count_size as u64, entries_len)?;
601 let mut cursor = Cursor::new(&body, header.byte_order);
602
603 if header.is_bigtiff() {
604 let tags = parse_tags_bigtiff(&mut cursor, count, source, header.byte_order)?;
605 let next = cursor.read_u64()?;
606 Ok((tags, next))
607 } else {
608 let tags = parse_tags_classic(&mut cursor, count, source, header.byte_order)?;
609 let next = cursor.read_u32()? as u64;
610 Ok((tags, next))
611 }
612}
613
614fn normalize_u16_values(
615 tag: u16,
616 values: Vec<u16>,
617 expected_len: usize,
618 default_value: u16,
619) -> Result<Vec<u16>> {
620 match values.len() {
621 0 => Ok(vec![default_value; expected_len]),
622 1 if expected_len > 1 => Ok(vec![values[0]; expected_len]),
623 len if len == expected_len => Ok(values),
624 len => Err(Error::InvalidTagValue {
625 tag,
626 reason: format!("expected 1 or {expected_len} values, found {len}"),
627 }),
628 }
629}
630
631fn resolve_fixed_model_extra_samples(
632 photometric: PhotometricInterpretation,
633 samples_per_pixel: u16,
634 base_samples: u16,
635 mut extra_samples: Vec<ExtraSample>,
636) -> Result<Vec<ExtraSample>> {
637 let implied_extra_samples = samples_per_pixel.checked_sub(base_samples).ok_or_else(|| {
638 Error::InvalidImageLayout(format!(
639 "{} photometric interpretation requires at least {base_samples} samples, got {samples_per_pixel}",
640 photometric_name(photometric)
641 ))
642 })?;
643 if extra_samples.len() > implied_extra_samples as usize {
644 return Err(Error::InvalidImageLayout(format!(
645 "{} photometric interpretation has {} total channels but {} ExtraSamples",
646 photometric_name(photometric),
647 samples_per_pixel,
648 extra_samples.len()
649 )));
650 }
651 extra_samples.resize(implied_extra_samples as usize, ExtraSample::Unspecified);
652 Ok(extra_samples)
653}
654
655fn photometric_name(photometric: PhotometricInterpretation) -> &'static str {
656 match photometric {
657 PhotometricInterpretation::MinIsWhite => "MinIsWhite",
658 PhotometricInterpretation::MinIsBlack => "MinIsBlack",
659 PhotometricInterpretation::Rgb => "RGB",
660 PhotometricInterpretation::Palette => "Palette",
661 PhotometricInterpretation::Mask => "TransparencyMask",
662 PhotometricInterpretation::Separated => "Separated",
663 PhotometricInterpretation::YCbCr => "YCbCr",
664 PhotometricInterpretation::CieLab => "CIELab",
665 }
666}
667
668fn validate_sample_encoding(sample_format: u16, bits_per_sample: u16) -> Result<()> {
669 let supported = match sample_format {
670 1 => matches!(bits_per_sample, 1 | 2 | 4 | 8 | 16 | 32 | 64),
671 2 => matches!(bits_per_sample, 8 | 16 | 32 | 64),
672 3 => matches!(bits_per_sample, 32 | 64),
673 _ => false,
674 };
675 if !supported {
676 return Err(Error::UnsupportedBitsPerSample(bits_per_sample));
677 }
678 Ok(())
679}
680
681fn decoded_uint_bits(bits_per_sample: u16) -> u16 {
682 bits_per_sample.max(8)
683}
684
685fn decoded_bits(sample_format: u16, bits_per_sample: u16) -> Result<u16> {
686 if sample_format == 1 {
687 Ok(decoded_uint_bits(bits_per_sample))
688 } else {
689 validate_sample_encoding(sample_format, bits_per_sample)?;
690 Ok(bits_per_sample)
691 }
692}
693
694fn color_map_is_u8_equivalent(color_map: &ColorMap) -> bool {
695 color_map
696 .red()
697 .iter()
698 .chain(color_map.green().iter())
699 .chain(color_map.blue().iter())
700 .all(|&value| value % 257 == 0)
701}
702
703fn validate_color_model(ifd: &Ifd, samples_per_pixel: u16, bits_per_sample: u16) -> Result<()> {
704 let color_model = ifd.color_model()?;
705
706 match &color_model {
707 ColorModel::Grayscale { extra_samples, .. } => {
708 validate_expected_samples(samples_per_pixel, 1, extra_samples.len())?;
709 }
710 ColorModel::Palette {
711 color_map,
712 extra_samples,
713 } => {
714 let expected_entries = 1usize.checked_shl(bits_per_sample as u32).ok_or_else(|| {
715 Error::InvalidImageLayout(format!(
716 "palette BitsPerSample {bits_per_sample} exceeds usize shift width"
717 ))
718 })?;
719 if color_map.len() != expected_entries {
720 return Err(Error::InvalidImageLayout(format!(
721 "palette ColorMap has {} entries but BitsPerSample={} requires {}",
722 color_map.len(),
723 bits_per_sample,
724 expected_entries
725 )));
726 }
727 validate_expected_samples(samples_per_pixel, 1, extra_samples.len())?;
728 }
729 ColorModel::Rgb { extra_samples } => {
730 validate_expected_samples(samples_per_pixel, 3, extra_samples.len())?;
731 }
732 ColorModel::TransparencyMask => {
733 validate_expected_samples(samples_per_pixel, 1, 0)?;
734 }
735 ColorModel::Cmyk { extra_samples } => {
736 validate_expected_samples(samples_per_pixel, 4, extra_samples.len())?;
737 }
738 ColorModel::Separated {
739 color_channels,
740 extra_samples,
741 ..
742 } => {
743 if *color_channels == 0 {
744 return Err(Error::InvalidImageLayout(
745 "separated photometric interpretation must have at least one base ink channel"
746 .into(),
747 ));
748 }
749 validate_expected_samples(samples_per_pixel, *color_channels, extra_samples.len())?;
750 }
751 ColorModel::YCbCr {
752 subsampling,
753 extra_samples,
754 ..
755 } => {
756 if subsampling.contains(&0) {
757 return Err(Error::InvalidImageLayout(format!(
758 "YCbCr subsampling {:?} must be positive",
759 subsampling
760 )));
761 }
762 if *subsampling != [1, 1] && !extra_samples.is_empty() {
763 return Err(Error::InvalidImageLayout(
764 "subsampled YCbCr with ExtraSamples is not supported".into(),
765 ));
766 }
767 if *subsampling != [1, 1] && ifd.predictor() != 1 {
768 return Err(Error::InvalidImageLayout(
769 "subsampled YCbCr does not support TIFF predictors".into(),
770 ));
771 }
772 validate_expected_samples(samples_per_pixel, 3, extra_samples.len())?;
773 }
774 ColorModel::CieLab { extra_samples } => {
775 validate_expected_samples(samples_per_pixel, 3, extra_samples.len())?;
776 }
777 }
778
779 Ok(())
780}
781
782fn validate_expected_samples(
783 samples_per_pixel: u16,
784 base_samples: u16,
785 extra_sample_count: usize,
786) -> Result<()> {
787 let expected_samples = base_samples
788 .checked_add(extra_sample_count as u16)
789 .ok_or_else(|| Error::InvalidImageLayout("samples per pixel overflow".into()))?;
790 if samples_per_pixel != expected_samples {
791 return Err(Error::InvalidImageLayout(format!(
792 "SamplesPerPixel={samples_per_pixel} does not match color model base channels {base_samples} plus {extra_sample_count} ExtraSamples"
793 )));
794 }
795 Ok(())
796}
797
798fn parse_tags_classic(
800 cursor: &mut Cursor<'_>,
801 count: usize,
802 source: &dyn TiffSource,
803 byte_order: ByteOrder,
804) -> Result<Vec<Tag>> {
805 let mut tags = Vec::with_capacity(count);
806 for _ in 0..count {
807 let code = cursor.read_u16()?;
808 let type_code = cursor.read_u16()?;
809 let value_count = cursor.read_u32()? as u64;
810 let value_offset_bytes = cursor.read_bytes(4)?;
811 let tag = parse_tag_classic(
812 code,
813 type_code,
814 value_count,
815 value_offset_bytes,
816 source,
817 byte_order,
818 )?;
819 tags.push(tag);
820 }
821 tags.sort_by_key(|tag| tag.code);
822 Ok(tags)
823}
824
825fn parse_tags_bigtiff(
827 cursor: &mut Cursor<'_>,
828 count: usize,
829 source: &dyn TiffSource,
830 byte_order: ByteOrder,
831) -> Result<Vec<Tag>> {
832 let mut tags = Vec::with_capacity(count);
833 for _ in 0..count {
834 let code = cursor.read_u16()?;
835 let type_code = cursor.read_u16()?;
836 let value_count = cursor.read_u64()?;
837 let value_offset_bytes = cursor.read_bytes(8)?;
838 let tag = parse_tag_bigtiff(
839 code,
840 type_code,
841 value_count,
842 value_offset_bytes,
843 source,
844 byte_order,
845 )?;
846 tags.push(tag);
847 }
848 tags.sort_by_key(|tag| tag.code);
849 Ok(tags)
850}
851
852#[cfg(test)]
853mod tests {
854 use super::{
855 ColorModel, ExtraSample, Ifd, InkSet, LercAdditionalCompression, RasterLayout,
856 TAG_BITS_PER_SAMPLE, TAG_COLOR_MAP, TAG_EXTRA_SAMPLES, TAG_IMAGE_LENGTH, TAG_IMAGE_WIDTH,
857 TAG_INK_SET, TAG_LERC_PARAMETERS, TAG_PHOTOMETRIC_INTERPRETATION, TAG_SAMPLES_PER_PIXEL,
858 TAG_SAMPLE_FORMAT, TAG_YCBCR_SUBSAMPLING,
859 };
860 use crate::tag::{Tag, TagType, TagValue};
861
862 fn make_ifd(tags: Vec<Tag>) -> Ifd {
863 let mut tags = tags;
864 tags.sort_by_key(|tag| tag.code);
865 Ifd { tags, index: 0 }
866 }
867
868 #[test]
869 fn normalizes_single_value_sample_tags() {
870 let ifd = make_ifd(vec![
871 Tag {
872 code: TAG_IMAGE_WIDTH,
873 tag_type: TagType::Long,
874 count: 1,
875 value: TagValue::Long(vec![10]),
876 },
877 Tag {
878 code: TAG_IMAGE_LENGTH,
879 tag_type: TagType::Long,
880 count: 1,
881 value: TagValue::Long(vec![5]),
882 },
883 Tag {
884 code: TAG_SAMPLES_PER_PIXEL,
885 tag_type: TagType::Short,
886 count: 1,
887 value: TagValue::Short(vec![3]),
888 },
889 Tag {
890 code: TAG_BITS_PER_SAMPLE,
891 tag_type: TagType::Short,
892 count: 1,
893 value: TagValue::Short(vec![16]),
894 },
895 Tag {
896 code: TAG_SAMPLE_FORMAT,
897 tag_type: TagType::Short,
898 count: 1,
899 value: TagValue::Short(vec![1]),
900 },
901 ]);
902
903 let layout = ifd.raster_layout().unwrap();
904 assert_eq!(layout.width, 10);
905 assert_eq!(layout.height, 5);
906 assert_eq!(layout.samples_per_pixel, 3);
907 assert_eq!(layout.bytes_per_sample, 2);
908 }
909
910 #[test]
911 fn rejects_mixed_sample_formats() {
912 let ifd = make_ifd(vec![
913 Tag {
914 code: TAG_IMAGE_WIDTH,
915 tag_type: TagType::Long,
916 count: 1,
917 value: TagValue::Long(vec![1]),
918 },
919 Tag {
920 code: TAG_IMAGE_LENGTH,
921 tag_type: TagType::Long,
922 count: 1,
923 value: TagValue::Long(vec![1]),
924 },
925 Tag {
926 code: TAG_SAMPLES_PER_PIXEL,
927 tag_type: TagType::Short,
928 count: 1,
929 value: TagValue::Short(vec![2]),
930 },
931 Tag {
932 code: TAG_BITS_PER_SAMPLE,
933 tag_type: TagType::Short,
934 count: 2,
935 value: TagValue::Short(vec![16, 16]),
936 },
937 Tag {
938 code: TAG_SAMPLE_FORMAT,
939 tag_type: TagType::Short,
940 count: 2,
941 value: TagValue::Short(vec![1, 3]),
942 },
943 ]);
944
945 assert!(ifd.raster_layout().is_err());
946 }
947
948 #[test]
949 fn raster_layout_helpers_match_expected_strides() {
950 let layout = RasterLayout {
951 width: 4,
952 height: 3,
953 samples_per_pixel: 2,
954 bits_per_sample: 16,
955 bytes_per_sample: 2,
956 sample_format: 1,
957 planar_configuration: 1,
958 predictor: 1,
959 };
960 assert_eq!(layout.pixel_stride_bytes(), 4);
961 assert_eq!(layout.row_bytes(), 16);
962 assert_eq!(layout.sample_plane_row_bytes(), 8);
963 }
964
965 #[test]
966 fn parses_lerc_parameters() {
967 let ifd = make_ifd(vec![Tag {
968 code: TAG_LERC_PARAMETERS,
969 tag_type: TagType::Long,
970 count: 2,
971 value: TagValue::Long(vec![4, 2]),
972 }]);
973
974 let params = ifd.lerc_parameters().unwrap().unwrap();
975 assert_eq!(params.version, 4);
976 assert_eq!(
977 params.additional_compression,
978 LercAdditionalCompression::Zstd
979 );
980 }
981
982 #[test]
983 fn parses_palette_color_model_and_extra_alpha() {
984 let ifd = make_ifd(vec![
985 Tag::new(TAG_IMAGE_WIDTH, TagValue::Long(vec![2])),
986 Tag::new(TAG_IMAGE_LENGTH, TagValue::Long(vec![2])),
987 Tag::new(TAG_SAMPLES_PER_PIXEL, TagValue::Short(vec![2])),
988 Tag::new(TAG_BITS_PER_SAMPLE, TagValue::Short(vec![8, 8])),
989 Tag::new(TAG_SAMPLE_FORMAT, TagValue::Short(vec![1, 1])),
990 Tag::new(TAG_PHOTOMETRIC_INTERPRETATION, TagValue::Short(vec![3])),
991 Tag::new(TAG_EXTRA_SAMPLES, TagValue::Short(vec![2])),
992 Tag::new(
993 TAG_COLOR_MAP,
994 TagValue::Short(
995 (0u16..256)
996 .chain((0u16..256).map(|value| value.saturating_mul(2)))
997 .chain((0u16..256).map(|value| value.saturating_mul(3)))
998 .collect(),
999 ),
1000 ),
1001 ]);
1002
1003 let model = ifd.color_model().unwrap();
1004 match model {
1005 ColorModel::Palette {
1006 color_map,
1007 extra_samples,
1008 } => {
1009 assert_eq!(color_map.len(), 256);
1010 assert_eq!(extra_samples, vec![ExtraSample::UnassociatedAlpha]);
1011 }
1012 other => panic!("unexpected color model: {other:?}"),
1013 }
1014
1015 let layout = ifd.raster_layout().unwrap();
1016 assert_eq!(layout.samples_per_pixel, 2);
1017 }
1018
1019 #[test]
1020 fn parses_cmyk_color_model() {
1021 let ifd = make_ifd(vec![
1022 Tag::new(TAG_IMAGE_WIDTH, TagValue::Long(vec![1])),
1023 Tag::new(TAG_IMAGE_LENGTH, TagValue::Long(vec![1])),
1024 Tag::new(TAG_SAMPLES_PER_PIXEL, TagValue::Short(vec![4])),
1025 Tag::new(TAG_BITS_PER_SAMPLE, TagValue::Short(vec![8, 8, 8, 8])),
1026 Tag::new(TAG_SAMPLE_FORMAT, TagValue::Short(vec![1, 1, 1, 1])),
1027 Tag::new(TAG_PHOTOMETRIC_INTERPRETATION, TagValue::Short(vec![5])),
1028 Tag::new(TAG_INK_SET, TagValue::Short(vec![1])),
1029 ]);
1030
1031 assert!(matches!(
1032 ifd.color_model().unwrap(),
1033 ColorModel::Cmyk { .. }
1034 ));
1035 assert_eq!(ifd.ink_set().unwrap(), Some(InkSet::Cmyk));
1036 assert_eq!(ifd.raster_layout().unwrap().samples_per_pixel, 4);
1037 }
1038
1039 #[test]
1040 fn rejects_palette_without_colormap() {
1041 let ifd = make_ifd(vec![
1042 Tag::new(TAG_IMAGE_WIDTH, TagValue::Long(vec![1])),
1043 Tag::new(TAG_IMAGE_LENGTH, TagValue::Long(vec![1])),
1044 Tag::new(TAG_SAMPLES_PER_PIXEL, TagValue::Short(vec![1])),
1045 Tag::new(TAG_BITS_PER_SAMPLE, TagValue::Short(vec![8])),
1046 Tag::new(TAG_SAMPLE_FORMAT, TagValue::Short(vec![1])),
1047 Tag::new(TAG_PHOTOMETRIC_INTERPRETATION, TagValue::Short(vec![3])),
1048 ]);
1049
1050 let error = ifd.raster_layout().unwrap_err();
1051 assert!(
1052 matches!(error, crate::error::Error::InvalidImageLayout(message) if message.contains("ColorMap"))
1053 );
1054 }
1055
1056 #[test]
1057 fn accepts_subsampled_ycbcr_storage_layouts() {
1058 let ifd = make_ifd(vec![
1059 Tag::new(TAG_IMAGE_WIDTH, TagValue::Long(vec![2])),
1060 Tag::new(TAG_IMAGE_LENGTH, TagValue::Long(vec![2])),
1061 Tag::new(TAG_SAMPLES_PER_PIXEL, TagValue::Short(vec![3])),
1062 Tag::new(TAG_BITS_PER_SAMPLE, TagValue::Short(vec![8, 8, 8])),
1063 Tag::new(TAG_SAMPLE_FORMAT, TagValue::Short(vec![1, 1, 1])),
1064 Tag::new(TAG_PHOTOMETRIC_INTERPRETATION, TagValue::Short(vec![6])),
1065 Tag::new(TAG_YCBCR_SUBSAMPLING, TagValue::Short(vec![2, 2])),
1066 ]);
1067
1068 let layout = ifd.raster_layout().unwrap();
1069 assert_eq!(layout.samples_per_pixel, 3);
1070 assert_eq!(ifd.decoded_raster_layout().unwrap().samples_per_pixel, 3);
1071 }
1072}