1use super::ifd::Value;
2use super::stream::PackBitsReader;
3use super::tag_reader::TagReader;
4use super::ChunkType;
5use super::{predict_f16, predict_f32, predict_f64, ValueReader};
6use crate::tags::{
7 CompressionMethod, PhotometricInterpretation, PlanarConfiguration, Predictor, SampleFormat, Tag,
8};
9use crate::{
10 ColorType, Directory, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError,
11};
12
13use std::io::{self, Cursor, Read, Seek};
14use std::num::NonZeroUsize;
15use std::sync::Arc;
16
17#[derive(Debug)]
18pub(crate) struct StripDecodeState {
19 pub rows_per_strip: u32,
20}
21
22#[derive(Debug)]
23pub(crate) struct TileAttributes {
25 pub image_width: usize,
26 pub image_height: usize,
27
28 pub tile_width: usize,
29 pub tile_length: usize,
30}
31
32impl TileAttributes {
33 pub fn tiles_across(&self) -> usize {
34 self.image_width.div_ceil(self.tile_width)
35 }
36 pub fn tiles_down(&self) -> usize {
37 self.image_height.div_ceil(self.tile_length)
38 }
39 fn padding_right(&self) -> usize {
40 (self.tile_width - self.image_width % self.tile_width) % self.tile_width
41 }
42 fn padding_down(&self) -> usize {
43 (self.tile_length - self.image_height % self.tile_length) % self.tile_length
44 }
45 pub fn get_padding(&self, tile: usize) -> (usize, usize) {
46 let row = tile / self.tiles_across();
47 let column = tile % self.tiles_across();
48
49 let padding_right = if column == self.tiles_across() - 1 {
50 self.padding_right()
51 } else {
52 0
53 };
54
55 let padding_down = if row == self.tiles_down() - 1 {
56 self.padding_down()
57 } else {
58 0
59 };
60
61 (padding_right, padding_down)
62 }
63}
64
65#[derive(Debug)]
66pub(crate) struct Image {
67 pub ifd: Option<Directory>,
68 pub width: u32,
69 pub height: u32,
70 pub bits_per_sample: u8,
71 pub samples: u16,
72 pub sample_format: SampleFormat,
73 pub photometric_interpretation: PhotometricInterpretation,
74 pub compression_method: CompressionMethod,
75 pub predictor: Predictor,
76 pub jpeg_tables: Option<Arc<Vec<u8>>>,
77 pub chunk_type: ChunkType,
78 pub planar_config: PlanarConfiguration,
79 pub strip_decoder: Option<StripDecodeState>,
80 pub tile_attributes: Option<TileAttributes>,
81 pub chunk_offsets: Vec<u64>,
82 pub chunk_bytes: Vec<u64>,
83}
84
85impl Image {
86 pub fn from_reader<R: Read + Seek>(
87 decoder: &mut ValueReader<R>,
88 ifd: Directory,
89 ) -> TiffResult<Image> {
90 let mut tag_reader = TagReader { decoder, ifd: &ifd };
91
92 let width = tag_reader.require_tag(Tag::ImageWidth)?.into_u32()?;
93 let height = tag_reader.require_tag(Tag::ImageLength)?.into_u32()?;
94 if width == 0 || height == 0 {
95 return Err(TiffError::FormatError(TiffFormatError::InvalidDimensions(
96 width, height,
97 )));
98 }
99
100 let photometric_interpretation = tag_reader
101 .find_tag(Tag::PhotometricInterpretation)?
102 .map(Value::into_u16)
103 .transpose()?
104 .and_then(PhotometricInterpretation::from_u16)
105 .ok_or(TiffUnsupportedError::UnknownInterpretation)?;
106
107 let compression_method = match tag_reader.find_tag(Tag::Compression)? {
110 Some(val) => CompressionMethod::from_u16_exhaustive(val.into_u16()?),
111 None => CompressionMethod::None,
112 };
113
114 let jpeg_tables = if compression_method == CompressionMethod::ModernJPEG
115 && ifd.contains(Tag::JPEGTables)
116 {
117 let vec = tag_reader
118 .find_tag(Tag::JPEGTables)?
119 .unwrap()
120 .into_u8_vec()?;
121 if vec.len() < 2 {
122 return Err(TiffError::FormatError(
123 TiffFormatError::InvalidTagValueType(Tag::JPEGTables),
124 ));
125 }
126
127 Some(Arc::new(vec))
128 } else {
129 None
130 };
131
132 let samples: u16 = tag_reader
133 .find_tag(Tag::SamplesPerPixel)?
134 .map(Value::into_u16)
135 .transpose()?
136 .unwrap_or(1);
137 if samples == 0 {
138 return Err(TiffFormatError::SamplesPerPixelIsZero.into());
139 }
140
141 let sample_format = match tag_reader.find_tag_uint_vec(Tag::SampleFormat)? {
142 Some(vals) => {
143 let sample_format: Vec<_> = vals
144 .into_iter()
145 .map(SampleFormat::from_u16_exhaustive)
146 .collect();
147
148 if !sample_format.windows(2).all(|s| s[0] == s[1]) {
150 return Err(TiffUnsupportedError::UnsupportedSampleFormat(sample_format).into());
151 }
152
153 sample_format[0]
154 }
155 None => SampleFormat::Uint,
156 };
157
158 let bits_per_sample: Vec<u8> = tag_reader
159 .find_tag_uint_vec(Tag::BitsPerSample)?
160 .unwrap_or_else(|| vec![1]);
161
162 if bits_per_sample.len() != samples.into() && bits_per_sample.len() != 1 {
165 return Err(TiffError::FormatError(
166 TiffFormatError::InconsistentSizesEncountered,
167 ));
168 }
169
170 if bits_per_sample.iter().any(|&b| b != bits_per_sample[0]) || bits_per_sample[0] == 0 {
173 return Err(TiffUnsupportedError::InconsistentBitsPerSample(bits_per_sample).into());
174 }
175
176 let predictor = tag_reader
177 .find_tag(Tag::Predictor)?
178 .map(Value::into_u16)
179 .transpose()?
180 .map(|p| {
181 Predictor::from_u16(p)
182 .ok_or(TiffError::FormatError(TiffFormatError::UnknownPredictor(p)))
183 })
184 .transpose()?
185 .unwrap_or(Predictor::None);
186
187 let planar_config = tag_reader
188 .find_tag(Tag::PlanarConfiguration)?
189 .map(Value::into_u16)
190 .transpose()?
191 .map(|p| {
192 PlanarConfiguration::from_u16(p).ok_or(TiffError::FormatError(
193 TiffFormatError::UnknownPlanarConfiguration(p),
194 ))
195 })
196 .transpose()?
197 .unwrap_or(PlanarConfiguration::Chunky);
198
199 let planes = match planar_config {
200 PlanarConfiguration::Chunky => 1,
201 PlanarConfiguration::Planar => samples,
202 };
203
204 let chunk_type;
205 let chunk_offsets;
206 let chunk_bytes;
207 let strip_decoder;
208 let tile_attributes;
209 match (
210 ifd.contains(Tag::StripByteCounts),
211 ifd.contains(Tag::StripOffsets),
212 ifd.contains(Tag::TileByteCounts),
213 ifd.contains(Tag::TileOffsets),
214 ) {
215 (true, true, false, false) => {
216 chunk_type = ChunkType::Strip;
217
218 chunk_offsets = tag_reader
219 .find_tag(Tag::StripOffsets)?
220 .unwrap()
221 .into_u64_vec()?;
222 chunk_bytes = tag_reader
223 .find_tag(Tag::StripByteCounts)?
224 .unwrap()
225 .into_u64_vec()?;
226 let rows_per_strip = tag_reader
227 .find_tag(Tag::RowsPerStrip)?
228 .map(Value::into_u32)
229 .transpose()?
230 .unwrap_or(height);
231 strip_decoder = Some(StripDecodeState { rows_per_strip });
232 tile_attributes = None;
233
234 if chunk_offsets.len() != chunk_bytes.len()
235 || rows_per_strip == 0
236 || u32::try_from(chunk_offsets.len())?
237 != (height.saturating_sub(1) / rows_per_strip + 1) * planes as u32
238 {
239 return Err(TiffError::FormatError(
240 TiffFormatError::InconsistentSizesEncountered,
241 ));
242 }
243 }
244 (false, false, true, true) => {
245 chunk_type = ChunkType::Tile;
246
247 let tile_width =
248 usize::try_from(tag_reader.require_tag(Tag::TileWidth)?.into_u32()?)?;
249 let tile_length =
250 usize::try_from(tag_reader.require_tag(Tag::TileLength)?.into_u32()?)?;
251
252 if tile_width == 0 {
253 return Err(TiffFormatError::InvalidTagValueType(Tag::TileWidth).into());
254 } else if tile_length == 0 {
255 return Err(TiffFormatError::InvalidTagValueType(Tag::TileLength).into());
256 }
257
258 strip_decoder = None;
259 tile_attributes = Some(TileAttributes {
260 image_width: usize::try_from(width)?,
261 image_height: usize::try_from(height)?,
262 tile_width,
263 tile_length,
264 });
265 chunk_offsets = tag_reader
266 .find_tag(Tag::TileOffsets)?
267 .unwrap()
268 .into_u64_vec()?;
269 chunk_bytes = tag_reader
270 .find_tag(Tag::TileByteCounts)?
271 .unwrap()
272 .into_u64_vec()?;
273
274 let tile = tile_attributes.as_ref().unwrap();
275 if chunk_offsets.len() != chunk_bytes.len()
276 || chunk_offsets.len()
277 != tile.tiles_down() * tile.tiles_across() * planes as usize
278 {
279 return Err(TiffError::FormatError(
280 TiffFormatError::InconsistentSizesEncountered,
281 ));
282 }
283 }
284 (_, _, _, _) => {
285 return Err(TiffError::FormatError(
286 TiffFormatError::StripTileTagConflict,
287 ))
288 }
289 };
290
291 Ok(Image {
292 ifd: Some(ifd),
293 width,
294 height,
295 bits_per_sample: bits_per_sample[0],
296 samples,
297 sample_format,
298 photometric_interpretation,
299 compression_method,
300 jpeg_tables,
301 predictor,
302 chunk_type,
303 planar_config,
304 strip_decoder,
305 tile_attributes,
306 chunk_offsets,
307 chunk_bytes,
308 })
309 }
310
311 pub(crate) fn colortype(&self) -> TiffResult<ColorType> {
312 match self.photometric_interpretation {
313 PhotometricInterpretation::RGB => match self.samples {
314 3 => Ok(ColorType::RGB(self.bits_per_sample)),
315 4 => Ok(ColorType::RGBA(self.bits_per_sample)),
316 _ => Err(TiffError::UnsupportedError(
322 TiffUnsupportedError::InterpretationWithBits(
323 self.photometric_interpretation,
324 vec![self.bits_per_sample; self.samples as usize],
325 ),
326 )),
327 },
328 PhotometricInterpretation::CMYK => match self.samples {
329 4 => Ok(ColorType::CMYK(self.bits_per_sample)),
330 5 => Ok(ColorType::CMYKA(self.bits_per_sample)),
331 _ => Err(TiffError::UnsupportedError(
332 TiffUnsupportedError::InterpretationWithBits(
333 self.photometric_interpretation,
334 vec![self.bits_per_sample; self.samples as usize],
335 ),
336 )),
337 },
338 PhotometricInterpretation::YCbCr => match self.samples {
339 3 => Ok(ColorType::YCbCr(self.bits_per_sample)),
340 _ => Err(TiffError::UnsupportedError(
341 TiffUnsupportedError::InterpretationWithBits(
342 self.photometric_interpretation,
343 vec![self.bits_per_sample; self.samples as usize],
344 ),
345 )),
346 },
347 PhotometricInterpretation::BlackIsZero | PhotometricInterpretation::WhiteIsZero => {
351 match self.samples {
352 1 => Ok(ColorType::Gray(self.bits_per_sample)),
353 _ => Ok(ColorType::Multiband {
354 bit_depth: self.bits_per_sample,
355 num_samples: self.samples,
356 }),
357 }
358 }
359 PhotometricInterpretation::RGBPalette
361 | PhotometricInterpretation::TransparencyMask
362 | PhotometricInterpretation::CIELab => Err(TiffError::UnsupportedError(
363 TiffUnsupportedError::InterpretationWithBits(
364 self.photometric_interpretation,
365 vec![self.bits_per_sample; self.samples as usize],
366 ),
367 )),
368 }
369 }
370
371 pub(crate) fn minimum_row_stride(&self, dims: (u32, u32)) -> Option<NonZeroUsize> {
372 let (width, height) = dims;
373
374 let row_stride = u64::from(width)
375 .saturating_mul(self.samples_per_pixel() as u64)
376 .saturating_mul(self.bits_per_sample as u64)
377 .div_ceil(8);
378
379 (height > 0)
383 .then_some(row_stride as usize)
384 .and_then(NonZeroUsize::new)
385 }
386
387 fn create_reader<'r, R: 'r + Read>(
388 reader: R,
389 compression_method: CompressionMethod,
390 compressed_length: u64,
391 #[cfg_attr(not(feature = "jpeg"), allow(unused_variables))] jpeg_tables: Option<&[u8]>,
393 #[cfg_attr(not(feature = "fax"), allow(unused_variables))] dimensions: (u32, u32),
394 ) -> TiffResult<Box<dyn Read + 'r>> {
395 Ok(match compression_method {
396 CompressionMethod::None => Box::new(reader),
397 #[cfg(feature = "lzw")]
398 CompressionMethod::LZW => Box::new(super::stream::LZWReader::new(
399 reader,
400 usize::try_from(compressed_length)?,
401 )),
402 #[cfg(feature = "zstd")]
403 CompressionMethod::ZSTD => Box::new(zstd::Decoder::new(reader)?),
404 CompressionMethod::PackBits => Box::new(PackBitsReader::new(reader, compressed_length)),
405 #[cfg(feature = "deflate")]
406 CompressionMethod::Deflate | CompressionMethod::OldDeflate => {
407 Box::new(super::stream::DeflateReader::new(reader))
408 }
409 #[cfg(feature = "jpeg")]
410 CompressionMethod::ModernJPEG => {
411 use zune_jpeg::zune_core;
412
413 if jpeg_tables.is_some() && compressed_length < 2 {
414 return Err(TiffError::FormatError(
415 TiffFormatError::InvalidTagValueType(Tag::JPEGTables),
416 ));
417 }
418
419 let mut jpeg_reader = match jpeg_tables {
430 Some(jpeg_tables) => {
431 let mut reader = reader.take(compressed_length);
432 reader.read_exact(&mut [0; 2])?;
433
434 Box::new(
435 Cursor::new(&jpeg_tables[..jpeg_tables.len() - 2])
436 .chain(reader.take(compressed_length)),
437 ) as Box<dyn Read>
438 }
439 None => Box::new(reader.take(compressed_length)),
440 };
441
442 let mut jpeg_data = Vec::new();
443 jpeg_reader.read_to_end(&mut jpeg_data)?;
444
445 let mut decoder = zune_jpeg::JpegDecoder::new(jpeg_data);
446 let mut options: zune_core::options::DecoderOptions = Default::default();
447
448 decoder.decode_headers()?;
451 if let Some(colorspace) = decoder.get_input_colorspace() {
452 options = options.jpeg_set_out_colorspace(colorspace);
453 }
454
455 decoder.set_options(options);
456
457 let data = decoder.decode()?;
458
459 Box::new(Cursor::new(data))
460 }
461 #[cfg(feature = "fax")]
462 CompressionMethod::Fax4 => Box::new(super::stream::Group4Reader::new(
463 dimensions,
464 reader,
465 compressed_length,
466 )?),
467 method => {
468 return Err(TiffError::UnsupportedError(
469 TiffUnsupportedError::UnsupportedCompressionMethod(method),
470 ))
471 }
472 })
473 }
474
475 pub(crate) fn samples_per_pixel(&self) -> usize {
483 match self.planar_config {
484 PlanarConfiguration::Chunky => self.samples.into(),
485 PlanarConfiguration::Planar => 1,
486 }
487 }
488
489 pub(crate) fn strips_per_pixel(&self) -> usize {
491 match self.planar_config {
492 PlanarConfiguration::Chunky => 1,
493 PlanarConfiguration::Planar => self.samples.into(),
494 }
495 }
496
497 pub(crate) fn chunk_file_range(&self, chunk: u32) -> TiffResult<(u64, u64)> {
498 let file_offset = self
499 .chunk_offsets
500 .get(chunk as usize)
501 .ok_or(TiffError::FormatError(
502 TiffFormatError::InconsistentSizesEncountered,
503 ))?;
504
505 let compressed_bytes =
506 self.chunk_bytes
507 .get(chunk as usize)
508 .ok_or(TiffError::FormatError(
509 TiffFormatError::InconsistentSizesEncountered,
510 ))?;
511
512 Ok((*file_offset, *compressed_bytes))
513 }
514
515 pub(crate) fn chunk_dimensions(&self) -> TiffResult<(u32, u32)> {
516 match self.chunk_type {
517 ChunkType::Strip => {
518 let strip_attrs = self.strip_decoder.as_ref().unwrap();
519 Ok((self.width, strip_attrs.rows_per_strip))
520 }
521 ChunkType::Tile => {
522 let tile_attrs = self.tile_attributes.as_ref().unwrap();
523 Ok((
524 u32::try_from(tile_attrs.tile_width)?,
525 u32::try_from(tile_attrs.tile_length)?,
526 ))
527 }
528 }
529 }
530
531 pub(crate) fn chunk_data_dimensions(&self, chunk_index: u32) -> TiffResult<(u32, u32)> {
532 let dims = self.chunk_dimensions()?;
533
534 match self.chunk_type {
535 ChunkType::Strip => {
536 let strip_attrs = self.strip_decoder.as_ref().unwrap();
537 let strips_per_band =
538 self.height.saturating_sub(1) / strip_attrs.rows_per_strip + 1;
539 let strip_height_without_padding = (chunk_index % strips_per_band)
540 .checked_mul(dims.1)
541 .and_then(|x| self.height.checked_sub(x))
542 .ok_or(TiffError::UsageError(UsageError::InvalidChunkIndex(
543 chunk_index,
544 )))?;
545
546 let strip_height = dims.1.min(strip_height_without_padding);
548
549 Ok((dims.0, strip_height))
550 }
551 ChunkType::Tile => {
552 let tile_attrs = self.tile_attributes.as_ref().unwrap();
553 let (padding_right, padding_down) = tile_attrs.get_padding(chunk_index as usize);
554
555 let tile_width = tile_attrs.tile_width - padding_right;
556 let tile_length = tile_attrs.tile_length - padding_down;
557
558 Ok((u32::try_from(tile_width)?, u32::try_from(tile_length)?))
559 }
560 }
561 }
562
563 pub(crate) fn expand_chunk(
564 &self,
565 reader: &mut ValueReader<impl Read>,
566 buf: &mut [u8],
567 output_row_stride: usize,
568 chunk_index: u32,
569 ) -> TiffResult<()> {
570 let ValueReader {
571 reader,
572 bigtiff: _,
573 limits,
574 } = reader;
575
576 let byte_order = reader.byte_order;
577 let reader = reader.inner();
578
579 let color_type = self.colortype()?;
581 match color_type {
582 ColorType::RGB(n)
583 | ColorType::RGBA(n)
584 | ColorType::CMYK(n)
585 | ColorType::CMYKA(n)
586 | ColorType::YCbCr(n)
587 | ColorType::Gray(n)
588 | ColorType::Multiband {
589 bit_depth: n,
590 num_samples: _,
591 } if n == 8 || n == 16 || n == 32 || n == 64 => {}
592 ColorType::Gray(n)
593 | ColorType::Multiband {
594 bit_depth: n,
595 num_samples: _,
596 } if n < 8 => match self.predictor {
597 Predictor::None => {}
598 Predictor::Horizontal => {
599 return Err(TiffError::UnsupportedError(
600 TiffUnsupportedError::HorizontalPredictor(color_type),
601 ));
602 }
603 Predictor::FloatingPoint => {
604 return Err(TiffError::UnsupportedError(
605 TiffUnsupportedError::FloatingPointPredictor(color_type),
606 ));
607 }
608 },
609 type_ => {
610 return Err(TiffError::UnsupportedError(
611 TiffUnsupportedError::UnsupportedColorType(type_),
612 ));
613 }
614 }
615
616 match (self.predictor, self.sample_format) {
618 (
619 Predictor::Horizontal,
620 SampleFormat::Int | SampleFormat::Uint | SampleFormat::IEEEFP,
621 ) => {}
622 (Predictor::Horizontal, _) => {
623 return Err(TiffError::UnsupportedError(
624 TiffUnsupportedError::HorizontalPredictor(color_type),
625 ));
626 }
627 (Predictor::FloatingPoint, SampleFormat::IEEEFP) => {}
628 (Predictor::FloatingPoint, _) => {
629 return Err(TiffError::UnsupportedError(
630 TiffUnsupportedError::FloatingPointPredictor(color_type),
631 ));
632 }
633 _ => {}
634 }
635
636 let compressed_bytes =
637 self.chunk_bytes
638 .get(chunk_index as usize)
639 .ok_or(TiffError::FormatError(
640 TiffFormatError::InconsistentSizesEncountered,
641 ))?;
642 if *compressed_bytes > limits.intermediate_buffer_size as u64 {
643 return Err(TiffError::LimitsExceeded);
644 }
645
646 let compression_method = self.compression_method;
647 let photometric_interpretation = self.photometric_interpretation;
648 let predictor = self.predictor;
649 let samples = self.samples_per_pixel();
650
651 let chunk_dims = self.chunk_dimensions()?;
652 let data_dims = self.chunk_data_dimensions(chunk_index)?;
653
654 let chunk_row_bits = (u64::from(chunk_dims.0) * u64::from(self.bits_per_sample))
655 .checked_mul(samples as u64)
656 .ok_or(TiffError::LimitsExceeded)?;
657 let chunk_row_bytes: usize = chunk_row_bits.div_ceil(8).try_into()?;
658
659 let data_row_bits = (u64::from(data_dims.0) * u64::from(self.bits_per_sample))
660 .checked_mul(samples as u64)
661 .ok_or(TiffError::LimitsExceeded)?;
662 let data_row_bytes: usize = data_row_bits.div_ceil(8).try_into()?;
663
664 assert!(output_row_stride >= data_row_bytes);
666 assert!(buf.len() >= output_row_stride * (data_dims.1 as usize - 1) + data_row_bytes);
667
668 let mut reader = Self::create_reader(
669 reader,
670 compression_method,
671 *compressed_bytes,
672 self.jpeg_tables.as_deref().map(|a| &**a),
673 chunk_dims,
674 )?;
675
676 if output_row_stride == chunk_row_bytes {
677 let tile = &mut buf[..chunk_row_bytes * data_dims.1 as usize];
678 reader.read_exact(tile)?;
679
680 for row in tile.chunks_mut(chunk_row_bytes) {
681 super::fix_endianness_and_predict(
682 row,
683 color_type.bit_depth(),
684 samples,
685 byte_order,
686 predictor,
687 );
688 }
689 if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
690 super::invert_colors(tile, color_type, self.sample_format)?;
691 }
692 } else if chunk_row_bytes > data_row_bytes && self.predictor == Predictor::FloatingPoint {
693 let mut encoded = vec![0u8; chunk_row_bytes];
696 for row in buf.chunks_mut(output_row_stride).take(data_dims.1 as usize) {
697 reader.read_exact(&mut encoded)?;
698
699 let row = &mut row[..data_row_bytes];
700 match color_type.bit_depth() {
701 16 => predict_f16(&mut encoded, row, samples),
702 32 => predict_f32(&mut encoded, row, samples),
703 64 => predict_f64(&mut encoded, row, samples),
704 _ => unreachable!(),
705 }
706 if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
707 super::invert_colors(row, color_type, self.sample_format)?;
708 }
709 }
710 } else {
711 for row in buf.chunks_mut(output_row_stride).take(data_dims.1 as usize) {
712 let row = &mut row[..data_row_bytes];
713 reader.read_exact(row)?;
714
715 if chunk_row_bytes > data_row_bytes {
717 let len = u64::try_from(chunk_row_bytes - data_row_bytes)?;
718 io::copy(&mut reader.by_ref().take(len), &mut io::sink())?;
719 }
720
721 super::fix_endianness_and_predict(
722 row,
723 color_type.bit_depth(),
724 samples,
725 byte_order,
726 predictor,
727 );
728 if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
729 super::invert_colors(row, color_type, self.sample_format)?;
730 }
731 }
732 }
733
734 Ok(())
735 }
736}