image/codecs/
tiff.rs

1//! Decoding and Encoding of TIFF Images
2//!
3//! TIFF (Tagged Image File Format) is a versatile image format that supports
4//! lossless and lossy compression.
5//!
6//! # Related Links
7//! * <http://partners.adobe.com/public/developer/tiff/index.html> - The TIFF specification
8use std::io::{self, BufRead, Cursor, Read, Seek, Write};
9use std::marker::PhantomData;
10use std::mem;
11
12use tiff::decoder::{Decoder, DecodingResult};
13use tiff::tags::Tag;
14
15use crate::color::{ColorType, ExtendedColorType};
16use crate::error::{
17    DecodingError, EncodingError, ImageError, ImageResult, LimitError, LimitErrorKind,
18    ParameterError, ParameterErrorKind, UnsupportedError, UnsupportedErrorKind,
19};
20use crate::metadata::Orientation;
21use crate::{utils, ImageDecoder, ImageEncoder, ImageFormat};
22
23const TAG_XML_PACKET: Tag = Tag::Unknown(700);
24
25/// Decoder for TIFF images.
26pub struct TiffDecoder<R>
27where
28    R: BufRead + Seek,
29{
30    dimensions: (u32, u32),
31    color_type: ColorType,
32    original_color_type: ExtendedColorType,
33
34    // We only use an Option here so we can call with_limits on the decoder without moving.
35    inner: Option<Decoder<R>>,
36}
37
38impl<R> TiffDecoder<R>
39where
40    R: BufRead + Seek,
41{
42    /// Create a new `TiffDecoder`.
43    pub fn new(r: R) -> Result<TiffDecoder<R>, ImageError> {
44        let mut inner = Decoder::new(r).map_err(ImageError::from_tiff_decode)?;
45
46        let dimensions = inner.dimensions().map_err(ImageError::from_tiff_decode)?;
47        let tiff_color_type = inner.colortype().map_err(ImageError::from_tiff_decode)?;
48
49        match inner.find_tag_unsigned_vec::<u16>(Tag::SampleFormat) {
50            Ok(Some(sample_formats)) => {
51                for format in sample_formats {
52                    check_sample_format(format, tiff_color_type)?;
53                }
54            }
55            Ok(None) => { /* assume UInt format */ }
56            Err(other) => return Err(ImageError::from_tiff_decode(other)),
57        }
58
59        let planar_config = inner
60            .find_tag(Tag::PlanarConfiguration)
61            .map(|res| res.and_then(|r| r.into_u16().ok()).unwrap_or_default())
62            .unwrap_or_default();
63
64        // Decode not supported for non Chunky Planar Configuration
65        if planar_config > 1 {
66            Err(ImageError::Unsupported(
67                UnsupportedError::from_format_and_kind(
68                    ImageFormat::Tiff.into(),
69                    UnsupportedErrorKind::GenericFeature(String::from("PlanarConfiguration = 2")),
70                ),
71            ))?;
72        }
73
74        let color_type = match tiff_color_type {
75            tiff::ColorType::Gray(1) => ColorType::L8,
76            tiff::ColorType::Gray(8) => ColorType::L8,
77            tiff::ColorType::Gray(16) => ColorType::L16,
78            tiff::ColorType::GrayA(8) => ColorType::La8,
79            tiff::ColorType::GrayA(16) => ColorType::La16,
80            tiff::ColorType::RGB(8) => ColorType::Rgb8,
81            tiff::ColorType::RGB(16) => ColorType::Rgb16,
82            tiff::ColorType::RGBA(8) => ColorType::Rgba8,
83            tiff::ColorType::RGBA(16) => ColorType::Rgba16,
84            tiff::ColorType::CMYK(8) => ColorType::Rgb8,
85            tiff::ColorType::CMYK(16) => ColorType::Rgb16,
86            tiff::ColorType::RGB(32) => ColorType::Rgb32F,
87            tiff::ColorType::RGBA(32) => ColorType::Rgba32F,
88
89            tiff::ColorType::Palette(n) | tiff::ColorType::Gray(n) => {
90                return Err(err_unknown_color_type(n))
91            }
92            tiff::ColorType::GrayA(n) => return Err(err_unknown_color_type(n.saturating_mul(2))),
93            tiff::ColorType::RGB(n) => return Err(err_unknown_color_type(n.saturating_mul(3))),
94            tiff::ColorType::YCbCr(n) => return Err(err_unknown_color_type(n.saturating_mul(3))),
95            tiff::ColorType::RGBA(n) | tiff::ColorType::CMYK(n) => {
96                return Err(err_unknown_color_type(n.saturating_mul(4)))
97            }
98            tiff::ColorType::Multiband {
99                bit_depth,
100                num_samples,
101            } => {
102                return Err(err_unknown_color_type(
103                    bit_depth.saturating_mul(num_samples.min(255) as u8),
104                ))
105            }
106            _ => return Err(err_unknown_color_type(0)),
107        };
108
109        let original_color_type = match tiff_color_type {
110            tiff::ColorType::Gray(1) => ExtendedColorType::L1,
111            tiff::ColorType::CMYK(8) => ExtendedColorType::Cmyk8,
112            tiff::ColorType::CMYK(16) => ExtendedColorType::Cmyk16,
113            _ => color_type.into(),
114        };
115
116        Ok(TiffDecoder {
117            dimensions,
118            color_type,
119            original_color_type,
120            inner: Some(inner),
121        })
122    }
123
124    // The buffer can be larger for CMYK than the RGB output
125    fn total_bytes_buffer(&self) -> u64 {
126        let dimensions = self.dimensions();
127        let total_pixels = u64::from(dimensions.0) * u64::from(dimensions.1);
128
129        let bytes_per_pixel = match self.original_color_type {
130            ExtendedColorType::Cmyk8 => 4,
131            ExtendedColorType::Cmyk16 => 8,
132            _ => u64::from(self.color_type().bytes_per_pixel()),
133        };
134        total_pixels.saturating_mul(bytes_per_pixel)
135    }
136}
137
138fn check_sample_format(sample_format: u16, color_type: tiff::ColorType) -> Result<(), ImageError> {
139    use tiff::{tags::SampleFormat, ColorType};
140    let num_bits = match color_type {
141        ColorType::CMYK(k) => k,
142        ColorType::Gray(k) => k,
143        ColorType::RGB(k) => k,
144        ColorType::RGBA(k) => k,
145        ColorType::GrayA(k) => k,
146        ColorType::Palette(k) | ColorType::YCbCr(k) => {
147            return Err(ImageError::Unsupported(
148                UnsupportedError::from_format_and_kind(
149                    ImageFormat::Tiff.into(),
150                    UnsupportedErrorKind::GenericFeature(format!(
151                        "Unhandled TIFF color type {color_type:?} for {k} bits",
152                    )),
153                ),
154            ))
155        }
156        _ => {
157            return Err(ImageError::Unsupported(
158                UnsupportedError::from_format_and_kind(
159                    ImageFormat::Tiff.into(),
160                    UnsupportedErrorKind::GenericFeature(format!(
161                        "Unhandled TIFF color type {color_type:?}",
162                    )),
163                ),
164            ))
165        }
166    };
167
168    match SampleFormat::from_u16(sample_format) {
169        Some(SampleFormat::Uint) if num_bits <= 16 => Ok(()),
170        Some(SampleFormat::IEEEFP) if num_bits == 32 => Ok(()),
171        _ => Err(ImageError::Unsupported(
172            UnsupportedError::from_format_and_kind(
173                ImageFormat::Tiff.into(),
174                UnsupportedErrorKind::GenericFeature(format!(
175                    "Unhandled TIFF sample format {sample_format:?} for {num_bits} bits",
176                )),
177            ),
178        )),
179    }
180}
181
182fn err_unknown_color_type(value: u8) -> ImageError {
183    ImageError::Unsupported(UnsupportedError::from_format_and_kind(
184        ImageFormat::Tiff.into(),
185        UnsupportedErrorKind::Color(ExtendedColorType::Unknown(value)),
186    ))
187}
188
189impl ImageError {
190    fn from_tiff_decode(err: tiff::TiffError) -> ImageError {
191        match err {
192            tiff::TiffError::IoError(err) => ImageError::IoError(err),
193            err @ (tiff::TiffError::FormatError(_)
194            | tiff::TiffError::IntSizeError
195            | tiff::TiffError::UsageError(_)) => {
196                ImageError::Decoding(DecodingError::new(ImageFormat::Tiff.into(), err))
197            }
198            tiff::TiffError::UnsupportedError(desc) => {
199                ImageError::Unsupported(UnsupportedError::from_format_and_kind(
200                    ImageFormat::Tiff.into(),
201                    UnsupportedErrorKind::GenericFeature(desc.to_string()),
202                ))
203            }
204            tiff::TiffError::LimitsExceeded => {
205                ImageError::Limits(LimitError::from_kind(LimitErrorKind::InsufficientMemory))
206            }
207        }
208    }
209
210    fn from_tiff_encode(err: tiff::TiffError) -> ImageError {
211        match err {
212            tiff::TiffError::IoError(err) => ImageError::IoError(err),
213            err @ (tiff::TiffError::FormatError(_)
214            | tiff::TiffError::IntSizeError
215            | tiff::TiffError::UsageError(_)) => {
216                ImageError::Encoding(EncodingError::new(ImageFormat::Tiff.into(), err))
217            }
218            tiff::TiffError::UnsupportedError(desc) => {
219                ImageError::Unsupported(UnsupportedError::from_format_and_kind(
220                    ImageFormat::Tiff.into(),
221                    UnsupportedErrorKind::GenericFeature(desc.to_string()),
222                ))
223            }
224            tiff::TiffError::LimitsExceeded => {
225                ImageError::Limits(LimitError::from_kind(LimitErrorKind::InsufficientMemory))
226            }
227        }
228    }
229}
230
231/// Wrapper struct around a `Cursor<Vec<u8>>`
232#[allow(dead_code)]
233#[deprecated]
234pub struct TiffReader<R>(Cursor<Vec<u8>>, PhantomData<R>);
235#[allow(deprecated)]
236impl<R> Read for TiffReader<R> {
237    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
238        self.0.read(buf)
239    }
240
241    fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
242        if self.0.position() == 0 && buf.is_empty() {
243            mem::swap(buf, self.0.get_mut());
244            Ok(buf.len())
245        } else {
246            self.0.read_to_end(buf)
247        }
248    }
249}
250
251impl<R: BufRead + Seek> ImageDecoder for TiffDecoder<R> {
252    fn dimensions(&self) -> (u32, u32) {
253        self.dimensions
254    }
255
256    fn color_type(&self) -> ColorType {
257        self.color_type
258    }
259
260    fn original_color_type(&self) -> ExtendedColorType {
261        self.original_color_type
262    }
263
264    fn icc_profile(&mut self) -> ImageResult<Option<Vec<u8>>> {
265        if let Some(decoder) = &mut self.inner {
266            Ok(decoder.get_tag_u8_vec(Tag::Unknown(34675)).ok())
267        } else {
268            Ok(None)
269        }
270    }
271
272    fn xmp_metadata(&mut self) -> ImageResult<Option<Vec<u8>>> {
273        let Some(decoder) = &mut self.inner else {
274            return Ok(None);
275        };
276
277        let value = match decoder.get_tag(TAG_XML_PACKET) {
278            Ok(value) => value,
279            Err(tiff::TiffError::FormatError(tiff::TiffFormatError::RequiredTagNotFound(_))) => {
280                return Ok(None);
281            }
282            Err(err) => return Err(ImageError::from_tiff_decode(err)),
283        };
284        value
285            .into_u8_vec()
286            .map(Some)
287            .map_err(ImageError::from_tiff_decode)
288    }
289
290    fn orientation(&mut self) -> ImageResult<Orientation> {
291        if let Some(decoder) = &mut self.inner {
292            Ok(decoder
293                .find_tag(Tag::Orientation)
294                .map_err(ImageError::from_tiff_decode)?
295                .and_then(|v| Orientation::from_exif(v.into_u16().ok()?.min(255) as u8))
296                .unwrap_or(Orientation::NoTransforms))
297        } else {
298            Ok(Orientation::NoTransforms)
299        }
300    }
301
302    fn set_limits(&mut self, limits: crate::Limits) -> ImageResult<()> {
303        limits.check_support(&crate::LimitSupport::default())?;
304
305        let (width, height) = self.dimensions();
306        limits.check_dimensions(width, height)?;
307
308        let max_alloc = limits.max_alloc.unwrap_or(u64::MAX);
309        let max_intermediate_alloc = max_alloc.saturating_sub(self.total_bytes_buffer());
310
311        let mut tiff_limits: tiff::decoder::Limits = Default::default();
312        tiff_limits.decoding_buffer_size =
313            usize::try_from(max_alloc - max_intermediate_alloc).unwrap_or(usize::MAX);
314        tiff_limits.intermediate_buffer_size =
315            usize::try_from(max_intermediate_alloc).unwrap_or(usize::MAX);
316        tiff_limits.ifd_value_size = tiff_limits.intermediate_buffer_size;
317        self.inner = Some(self.inner.take().unwrap().with_limits(tiff_limits));
318
319        Ok(())
320    }
321
322    fn read_image(self, buf: &mut [u8]) -> ImageResult<()> {
323        assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes()));
324
325        match self
326            .inner
327            .unwrap()
328            .read_image()
329            .map_err(ImageError::from_tiff_decode)?
330        {
331            DecodingResult::U8(v) if self.original_color_type == ExtendedColorType::Cmyk8 => {
332                let mut out_cur = Cursor::new(buf);
333                for cmyk in v.chunks_exact(4) {
334                    out_cur.write_all(&cmyk_to_rgb(cmyk))?;
335                }
336            }
337            DecodingResult::U16(v) if self.original_color_type == ExtendedColorType::Cmyk16 => {
338                let mut out_cur = Cursor::new(buf);
339                for cmyk in v.chunks_exact(4) {
340                    out_cur.write_all(bytemuck::cast_slice(&cmyk_to_rgb16(cmyk)))?;
341                }
342            }
343            DecodingResult::U8(v) if self.original_color_type == ExtendedColorType::L1 => {
344                let width = self.dimensions.0;
345                let row_bytes = width.div_ceil(8);
346
347                for (in_row, out_row) in v
348                    .chunks_exact(row_bytes as usize)
349                    .zip(buf.chunks_exact_mut(width as usize))
350                {
351                    out_row.copy_from_slice(&utils::expand_bits(1, width, in_row));
352                }
353            }
354            DecodingResult::U8(v) => {
355                buf.copy_from_slice(&v);
356            }
357            DecodingResult::U16(v) => {
358                buf.copy_from_slice(bytemuck::cast_slice(&v));
359            }
360            DecodingResult::U32(v) => {
361                buf.copy_from_slice(bytemuck::cast_slice(&v));
362            }
363            DecodingResult::U64(v) => {
364                buf.copy_from_slice(bytemuck::cast_slice(&v));
365            }
366            DecodingResult::I8(v) => {
367                buf.copy_from_slice(bytemuck::cast_slice(&v));
368            }
369            DecodingResult::I16(v) => {
370                buf.copy_from_slice(bytemuck::cast_slice(&v));
371            }
372            DecodingResult::I32(v) => {
373                buf.copy_from_slice(bytemuck::cast_slice(&v));
374            }
375            DecodingResult::I64(v) => {
376                buf.copy_from_slice(bytemuck::cast_slice(&v));
377            }
378            DecodingResult::F32(v) => {
379                buf.copy_from_slice(bytemuck::cast_slice(&v));
380            }
381            DecodingResult::F64(v) => {
382                buf.copy_from_slice(bytemuck::cast_slice(&v));
383            }
384            DecodingResult::F16(_) => unreachable!(),
385        }
386        Ok(())
387    }
388
389    fn read_image_boxed(self: Box<Self>, buf: &mut [u8]) -> ImageResult<()> {
390        (*self).read_image(buf)
391    }
392}
393
394/// Encoder for tiff images
395pub struct TiffEncoder<W> {
396    w: W,
397}
398
399fn cmyk_to_rgb(cmyk: &[u8]) -> [u8; 3] {
400    let c = f32::from(cmyk[0]);
401    let m = f32::from(cmyk[1]);
402    let y = f32::from(cmyk[2]);
403    let kf = 1. - f32::from(cmyk[3]) / 255.;
404    [
405        ((255. - c) * kf) as u8,
406        ((255. - m) * kf) as u8,
407        ((255. - y) * kf) as u8,
408    ]
409}
410
411fn cmyk_to_rgb16(cmyk: &[u16]) -> [u16; 3] {
412    let c = f32::from(cmyk[0]);
413    let m = f32::from(cmyk[1]);
414    let y = f32::from(cmyk[2]);
415    let kf = 1. - f32::from(cmyk[3]) / 65535.;
416    [
417        ((65535. - c) * kf) as u16,
418        ((65535. - m) * kf) as u16,
419        ((65535. - y) * kf) as u16,
420    ]
421}
422
423/// Convert a slice of sample bytes to its semantic type, being a `Pod`.
424fn u8_slice_as_pod<P: bytemuck::Pod>(buf: &[u8]) -> ImageResult<std::borrow::Cow<'_, [P]>> {
425    bytemuck::try_cast_slice(buf)
426        .map(std::borrow::Cow::Borrowed)
427        .or_else(|err| {
428            match err {
429                bytemuck::PodCastError::TargetAlignmentGreaterAndInputNotAligned => {
430                    // If the buffer is not aligned for a native slice, copy the buffer into a Vec,
431                    // aligning it in the process. This is only done if the element count can be
432                    // represented exactly.
433                    let vec = bytemuck::allocation::pod_collect_to_vec(buf);
434                    Ok(std::borrow::Cow::Owned(vec))
435                }
436                /* only expecting: bytemuck::PodCastError::OutputSliceWouldHaveSlop */
437                _ => {
438                    // `bytemuck::PodCastError` of bytemuck-1.2.0 does not implement `Error` and
439                    // `Display` trait.
440                    // See <https://github.com/Lokathor/bytemuck/issues/22>.
441                    Err(ImageError::Parameter(ParameterError::from_kind(
442                        ParameterErrorKind::Generic(format!(
443                            "Casting samples to their representation failed: {err:?}",
444                        )),
445                    )))
446                }
447            }
448        })
449}
450
451impl<W: Write + Seek> TiffEncoder<W> {
452    /// Create a new encoder that writes its output to `w`
453    pub fn new(w: W) -> TiffEncoder<W> {
454        TiffEncoder { w }
455    }
456
457    /// Encodes the image `image` that has dimensions `width` and `height` and `ColorType` `c`.
458    ///
459    /// 16-bit types assume the buffer is native endian.
460    ///
461    /// # Panics
462    ///
463    /// Panics if `width * height * color_type.bytes_per_pixel() != data.len()`.
464    #[track_caller]
465    pub fn encode(
466        self,
467        buf: &[u8],
468        width: u32,
469        height: u32,
470        color_type: ExtendedColorType,
471    ) -> ImageResult<()> {
472        use tiff::encoder::colortype::{
473            Gray16, Gray8, RGB32Float, RGBA32Float, RGB16, RGB8, RGBA16, RGBA8,
474        };
475        let expected_buffer_len = color_type.buffer_size(width, height);
476        assert_eq!(
477            expected_buffer_len,
478            buf.len() as u64,
479            "Invalid buffer length: expected {expected_buffer_len} got {} for {width}x{height} image",
480            buf.len(),
481        );
482        let mut encoder =
483            tiff::encoder::TiffEncoder::new(self.w).map_err(ImageError::from_tiff_encode)?;
484        match color_type {
485            ExtendedColorType::L8 => encoder.write_image::<Gray8>(width, height, buf),
486            ExtendedColorType::Rgb8 => encoder.write_image::<RGB8>(width, height, buf),
487            ExtendedColorType::Rgba8 => encoder.write_image::<RGBA8>(width, height, buf),
488            ExtendedColorType::L16 => {
489                encoder.write_image::<Gray16>(width, height, u8_slice_as_pod::<u16>(buf)?.as_ref())
490            }
491            ExtendedColorType::Rgb16 => {
492                encoder.write_image::<RGB16>(width, height, u8_slice_as_pod::<u16>(buf)?.as_ref())
493            }
494            ExtendedColorType::Rgba16 => {
495                encoder.write_image::<RGBA16>(width, height, u8_slice_as_pod::<u16>(buf)?.as_ref())
496            }
497            ExtendedColorType::Rgb32F => encoder.write_image::<RGB32Float>(
498                width,
499                height,
500                u8_slice_as_pod::<f32>(buf)?.as_ref(),
501            ),
502            ExtendedColorType::Rgba32F => encoder.write_image::<RGBA32Float>(
503                width,
504                height,
505                u8_slice_as_pod::<f32>(buf)?.as_ref(),
506            ),
507            _ => {
508                return Err(ImageError::Unsupported(
509                    UnsupportedError::from_format_and_kind(
510                        ImageFormat::Tiff.into(),
511                        UnsupportedErrorKind::Color(color_type),
512                    ),
513                ))
514            }
515        }
516        .map_err(ImageError::from_tiff_encode)?;
517
518        Ok(())
519    }
520}
521
522impl<W: Write + Seek> ImageEncoder for TiffEncoder<W> {
523    #[track_caller]
524    fn write_image(
525        self,
526        buf: &[u8],
527        width: u32,
528        height: u32,
529        color_type: ExtendedColorType,
530    ) -> ImageResult<()> {
531        self.encode(buf, width, height, color_type)
532    }
533}