Skip to main content

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    buffer: DecodingResult,
37}
38
39impl<R> TiffDecoder<R>
40where
41    R: BufRead + Seek,
42{
43    /// Create a new `TiffDecoder`.
44    pub fn new(r: R) -> Result<TiffDecoder<R>, ImageError> {
45        let mut inner = Decoder::new(r).map_err(ImageError::from_tiff_decode)?;
46
47        let dimensions = inner.dimensions().map_err(ImageError::from_tiff_decode)?;
48        let tiff_color_type = inner.colortype().map_err(ImageError::from_tiff_decode)?;
49
50        match inner.find_tag_unsigned_vec::<u16>(Tag::SampleFormat) {
51            Ok(Some(sample_formats)) => {
52                for format in sample_formats {
53                    check_sample_format(format, tiff_color_type)?;
54                }
55            }
56            Ok(None) => { /* assume UInt format */ }
57            Err(other) => return Err(ImageError::from_tiff_decode(other)),
58        }
59
60        let color_type = match tiff_color_type {
61            tiff::ColorType::Gray(1) => ColorType::L8,
62            tiff::ColorType::Gray(8) => ColorType::L8,
63            tiff::ColorType::Gray(16) => ColorType::L16,
64            tiff::ColorType::GrayA(8) => ColorType::La8,
65            tiff::ColorType::GrayA(16) => ColorType::La16,
66            tiff::ColorType::RGB(8) => ColorType::Rgb8,
67            tiff::ColorType::RGB(16) => ColorType::Rgb16,
68            tiff::ColorType::RGBA(8) => ColorType::Rgba8,
69            tiff::ColorType::RGBA(16) => ColorType::Rgba16,
70            tiff::ColorType::CMYK(8) => ColorType::Rgb8,
71            tiff::ColorType::CMYK(16) => ColorType::Rgb16,
72            tiff::ColorType::RGB(32) => ColorType::Rgb32F,
73            tiff::ColorType::RGBA(32) => ColorType::Rgba32F,
74
75            tiff::ColorType::Palette(n) | tiff::ColorType::Gray(n) => {
76                return Err(err_unknown_color_type(n))
77            }
78            tiff::ColorType::GrayA(n) => return Err(err_unknown_color_type(n.saturating_mul(2))),
79            tiff::ColorType::RGB(n) => return Err(err_unknown_color_type(n.saturating_mul(3))),
80            tiff::ColorType::YCbCr(n) => return Err(err_unknown_color_type(n.saturating_mul(3))),
81            tiff::ColorType::RGBA(n) | tiff::ColorType::CMYK(n) => {
82                return Err(err_unknown_color_type(n.saturating_mul(4)))
83            }
84            tiff::ColorType::Multiband {
85                bit_depth,
86                num_samples,
87            } => {
88                return Err(err_unknown_color_type(
89                    bit_depth.saturating_mul(num_samples.min(255) as u8),
90                ))
91            }
92            _ => return Err(err_unknown_color_type(0)),
93        };
94
95        let original_color_type = match tiff_color_type {
96            tiff::ColorType::Gray(1) => ExtendedColorType::L1,
97            tiff::ColorType::CMYK(8) => ExtendedColorType::Cmyk8,
98            tiff::ColorType::CMYK(16) => ExtendedColorType::Cmyk16,
99            _ => color_type.into(),
100        };
101
102        Ok(TiffDecoder {
103            dimensions,
104            color_type,
105            original_color_type,
106            inner: Some(inner),
107            buffer: DecodingResult::U8(vec![]),
108        })
109    }
110
111    // The buffer can be larger for CMYK than the RGB output
112    fn total_bytes_buffer(&self) -> u64 {
113        let dimensions = self.dimensions();
114        let total_pixels = u64::from(dimensions.0) * u64::from(dimensions.1);
115
116        let bytes_per_pixel = match self.original_color_type {
117            ExtendedColorType::Cmyk8 => 4,
118            ExtendedColorType::Cmyk16 => 8,
119            _ => u64::from(self.color_type().bytes_per_pixel()),
120        };
121        total_pixels.saturating_mul(bytes_per_pixel)
122    }
123
124    /// Interleave planes in our `buffer` into `output`.
125    fn interleave_planes(
126        &mut self,
127        layout: tiff::decoder::BufferLayoutPreference,
128        output: &mut [u8],
129    ) -> ImageResult<()> {
130        if self.original_color_type != self.color_type.into() {
131            return Err(ImageError::Unsupported(
132                UnsupportedError::from_format_and_kind(
133                    ImageFormat::Tiff.into(),
134                    UnsupportedErrorKind::GenericFeature(
135                        "Planar TIFF with CMYK color type is not supported".to_string(),
136                    ),
137                ),
138            ));
139        }
140
141        // This only works if we and `tiff` agree on the layout, including the color type, of
142        // the sample matrix.
143        //
144        // TODO: triple buffer in the other case and fixup the planar layout independent of
145        // sample type. Problem description follows:
146        //
147        // That will suck since we can't call `interleave_planes` with a `ColorType` argument,
148        // Changing that parameter to `ExtendedColorType` is a can of worms, and exposing the
149        // underlying generic function is an optimization killer (we may want to help LLVM
150        // optimize this interleaving by SIMD). For LumaAlpha(1) colors we should do the bit
151        // expansion at the same time as interleaving to avoid wasting the memory traversal but
152        // expand-then-interleave is at least clear, albeit an extra buffer required. Meanwhile
153        // for `Cmyk8`/`Cmyk16` our output is smaller than the tiff buffer (4 samples to 3, or
154        // 5 to 4 if we had alpha) and not wanting multiple conversion function implementations
155        // we should interleave-then-expand?
156        //
157        // The hard part of the solution will be managing complexity.
158        let plane_stride = layout.plane_stride.map_or(0, |n| n.get());
159        let bytes = self.buffer.as_buffer(0);
160
161        let planes = bytes
162            .as_bytes()
163            .chunks_exact(plane_stride)
164            .collect::<Vec<_>>();
165
166        // Gracefully handle a mismatch of expectations. This should not occur in practice as we
167        // check that all planes have been read (see note on `read_image_to_buffer` usage below).
168        if planes.len() < usize::from(self.color_type.channel_count()) {
169            return Err(ImageError::Decoding(DecodingError::new(
170                ImageFormat::Tiff.into(),
171                "Not enough planes read from TIFF image".to_string(),
172            )));
173        }
174
175        utils::interleave_planes(
176            output,
177            self.color_type,
178            &planes[..usize::from(self.color_type.channel_count())],
179        );
180
181        Ok(())
182    }
183}
184
185fn check_sample_format(sample_format: u16, color_type: tiff::ColorType) -> Result<(), ImageError> {
186    use tiff::{tags::SampleFormat, ColorType};
187    let num_bits = match color_type {
188        ColorType::CMYK(k) => k,
189        ColorType::Gray(k) => k,
190        ColorType::RGB(k) => k,
191        ColorType::RGBA(k) => k,
192        ColorType::GrayA(k) => k,
193        ColorType::Palette(k) | ColorType::YCbCr(k) => {
194            return Err(ImageError::Unsupported(
195                UnsupportedError::from_format_and_kind(
196                    ImageFormat::Tiff.into(),
197                    UnsupportedErrorKind::GenericFeature(format!(
198                        "Unhandled TIFF color type {color_type:?} for {k} bits",
199                    )),
200                ),
201            ))
202        }
203        _ => {
204            return Err(ImageError::Unsupported(
205                UnsupportedError::from_format_and_kind(
206                    ImageFormat::Tiff.into(),
207                    UnsupportedErrorKind::GenericFeature(format!(
208                        "Unhandled TIFF color type {color_type:?}",
209                    )),
210                ),
211            ))
212        }
213    };
214
215    match SampleFormat::from_u16(sample_format) {
216        Some(SampleFormat::Uint) if num_bits <= 16 => Ok(()),
217        Some(SampleFormat::IEEEFP) if num_bits == 32 => Ok(()),
218        _ => Err(ImageError::Unsupported(
219            UnsupportedError::from_format_and_kind(
220                ImageFormat::Tiff.into(),
221                UnsupportedErrorKind::GenericFeature(format!(
222                    "Unhandled TIFF sample format {sample_format:?} for {num_bits} bits",
223                )),
224            ),
225        )),
226    }
227}
228
229fn err_unknown_color_type(value: u8) -> ImageError {
230    ImageError::Unsupported(UnsupportedError::from_format_and_kind(
231        ImageFormat::Tiff.into(),
232        UnsupportedErrorKind::Color(ExtendedColorType::Unknown(value)),
233    ))
234}
235
236impl ImageError {
237    fn from_tiff_decode(err: tiff::TiffError) -> ImageError {
238        match err {
239            tiff::TiffError::IoError(err) => ImageError::IoError(err),
240            err @ (tiff::TiffError::FormatError(_)
241            | tiff::TiffError::IntSizeError
242            | tiff::TiffError::UsageError(_)) => {
243                ImageError::Decoding(DecodingError::new(ImageFormat::Tiff.into(), err))
244            }
245            tiff::TiffError::UnsupportedError(desc) => {
246                ImageError::Unsupported(UnsupportedError::from_format_and_kind(
247                    ImageFormat::Tiff.into(),
248                    UnsupportedErrorKind::GenericFeature(desc.to_string()),
249                ))
250            }
251            tiff::TiffError::LimitsExceeded => {
252                ImageError::Limits(LimitError::from_kind(LimitErrorKind::InsufficientMemory))
253            }
254        }
255    }
256
257    fn from_tiff_encode(err: tiff::TiffError) -> ImageError {
258        match err {
259            tiff::TiffError::IoError(err) => ImageError::IoError(err),
260            err @ (tiff::TiffError::FormatError(_)
261            | tiff::TiffError::IntSizeError
262            | tiff::TiffError::UsageError(_)) => {
263                ImageError::Encoding(EncodingError::new(ImageFormat::Tiff.into(), err))
264            }
265            tiff::TiffError::UnsupportedError(desc) => {
266                ImageError::Unsupported(UnsupportedError::from_format_and_kind(
267                    ImageFormat::Tiff.into(),
268                    UnsupportedErrorKind::GenericFeature(desc.to_string()),
269                ))
270            }
271            tiff::TiffError::LimitsExceeded => {
272                ImageError::Limits(LimitError::from_kind(LimitErrorKind::InsufficientMemory))
273            }
274        }
275    }
276}
277
278/// Wrapper struct around a `Cursor<Vec<u8>>`
279#[allow(dead_code)]
280#[deprecated]
281pub struct TiffReader<R>(Cursor<Vec<u8>>, PhantomData<R>);
282#[allow(deprecated)]
283impl<R> Read for TiffReader<R> {
284    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
285        self.0.read(buf)
286    }
287
288    fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
289        if self.0.position() == 0 && buf.is_empty() {
290            mem::swap(buf, self.0.get_mut());
291            Ok(buf.len())
292        } else {
293            self.0.read_to_end(buf)
294        }
295    }
296}
297
298impl<R: BufRead + Seek> ImageDecoder for TiffDecoder<R> {
299    fn dimensions(&self) -> (u32, u32) {
300        self.dimensions
301    }
302
303    fn color_type(&self) -> ColorType {
304        self.color_type
305    }
306
307    fn original_color_type(&self) -> ExtendedColorType {
308        self.original_color_type
309    }
310
311    fn icc_profile(&mut self) -> ImageResult<Option<Vec<u8>>> {
312        if let Some(decoder) = &mut self.inner {
313            Ok(decoder.get_tag_u8_vec(Tag::IccProfile).ok())
314        } else {
315            Ok(None)
316        }
317    }
318
319    fn xmp_metadata(&mut self) -> ImageResult<Option<Vec<u8>>> {
320        let Some(decoder) = &mut self.inner else {
321            return Ok(None);
322        };
323
324        let value = match decoder.get_tag(TAG_XML_PACKET) {
325            Ok(value) => value,
326            Err(tiff::TiffError::FormatError(tiff::TiffFormatError::RequiredTagNotFound(_))) => {
327                return Ok(None);
328            }
329            Err(err) => return Err(ImageError::from_tiff_decode(err)),
330        };
331        value
332            .into_u8_vec()
333            .map(Some)
334            .map_err(ImageError::from_tiff_decode)
335    }
336
337    fn orientation(&mut self) -> ImageResult<Orientation> {
338        if let Some(decoder) = &mut self.inner {
339            Ok(decoder
340                .find_tag(Tag::Orientation)
341                .map_err(ImageError::from_tiff_decode)?
342                .and_then(|v| Orientation::from_exif(v.into_u16().ok()?.min(255) as u8))
343                .unwrap_or(Orientation::NoTransforms))
344        } else {
345            Ok(Orientation::NoTransforms)
346        }
347    }
348
349    fn set_limits(&mut self, limits: crate::Limits) -> ImageResult<()> {
350        limits.check_support(&crate::LimitSupport::default())?;
351
352        let (width, height) = self.dimensions();
353        limits.check_dimensions(width, height)?;
354
355        let max_alloc = limits.max_alloc.unwrap_or(u64::MAX);
356        let max_intermediate_alloc = max_alloc.saturating_sub(self.total_bytes_buffer());
357
358        let mut tiff_limits: tiff::decoder::Limits = Default::default();
359        tiff_limits.decoding_buffer_size =
360            usize::try_from(max_alloc - max_intermediate_alloc).unwrap_or(usize::MAX);
361        tiff_limits.intermediate_buffer_size =
362            usize::try_from(max_intermediate_alloc).unwrap_or(usize::MAX);
363        tiff_limits.ifd_value_size = tiff_limits.intermediate_buffer_size;
364        self.inner = Some(self.inner.take().unwrap().with_limits(tiff_limits));
365
366        Ok(())
367    }
368
369    fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> {
370        assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes()));
371
372        let layout = self
373            .inner
374            .as_mut()
375            .unwrap()
376            .read_image_to_buffer(&mut self.buffer)
377            .map_err(ImageError::from_tiff_decode)?;
378
379        // Check if we have all of the planes. Otherwise we ran into the allocation limit.
380        if self.buffer.as_buffer(0).as_bytes().len() < layout.complete_len {
381            return Err(ImageError::Limits(LimitError::from_kind(
382                LimitErrorKind::InsufficientMemory,
383            )));
384        }
385
386        if layout.planes > 1 {
387            // Note that we do not support planar layouts if we have to do conversion. Yet. See a
388            // more detailed comment in the implementation.
389            return self.interleave_planes(layout, buf);
390        }
391
392        match self.buffer {
393            DecodingResult::U8(v) if self.original_color_type == ExtendedColorType::Cmyk8 => {
394                let mut out_cur = Cursor::new(buf);
395                for cmyk in v.as_chunks::<4>().0 {
396                    out_cur.write_all(&cmyk_to_rgb(cmyk))?;
397                }
398            }
399            DecodingResult::U16(v) if self.original_color_type == ExtendedColorType::Cmyk16 => {
400                let mut out_cur = Cursor::new(buf);
401                for cmyk in v.as_chunks::<4>().0 {
402                    out_cur.write_all(bytemuck::cast_slice(&cmyk_to_rgb16(cmyk)))?;
403                }
404            }
405            DecodingResult::U8(v) if self.original_color_type == ExtendedColorType::L1 => {
406                let width = self.dimensions.0;
407                let row_bytes = width.div_ceil(8);
408
409                for (in_row, out_row) in v
410                    .chunks_exact(row_bytes as usize)
411                    .zip(buf.chunks_exact_mut(width as usize))
412                {
413                    out_row.copy_from_slice(&utils::expand_bits(1, width, in_row));
414                }
415            }
416            DecodingResult::U8(v) => {
417                buf.copy_from_slice(&v);
418            }
419            DecodingResult::U16(v) => {
420                buf.copy_from_slice(bytemuck::cast_slice(&v));
421            }
422            DecodingResult::U32(v) => {
423                buf.copy_from_slice(bytemuck::cast_slice(&v));
424            }
425            DecodingResult::U64(v) => {
426                buf.copy_from_slice(bytemuck::cast_slice(&v));
427            }
428            DecodingResult::I8(v) => {
429                buf.copy_from_slice(bytemuck::cast_slice(&v));
430            }
431            DecodingResult::I16(v) => {
432                buf.copy_from_slice(bytemuck::cast_slice(&v));
433            }
434            DecodingResult::I32(v) => {
435                buf.copy_from_slice(bytemuck::cast_slice(&v));
436            }
437            DecodingResult::I64(v) => {
438                buf.copy_from_slice(bytemuck::cast_slice(&v));
439            }
440            DecodingResult::F32(v) => {
441                buf.copy_from_slice(bytemuck::cast_slice(&v));
442            }
443            DecodingResult::F64(v) => {
444                buf.copy_from_slice(bytemuck::cast_slice(&v));
445            }
446            DecodingResult::F16(_) => unreachable!(),
447        }
448
449        Ok(())
450    }
451
452    fn read_image_boxed(self: Box<Self>, buf: &mut [u8]) -> ImageResult<()> {
453        (*self).read_image(buf)
454    }
455}
456
457/// Encoder for tiff images
458pub struct TiffEncoder<W> {
459    w: W,
460    icc: Option<Vec<u8>>,
461}
462
463fn cmyk_to_rgb(cmyk: &[u8; 4]) -> [u8; 3] {
464    let c = f32::from(cmyk[0]);
465    let m = f32::from(cmyk[1]);
466    let y = f32::from(cmyk[2]);
467    let kf = 1. - f32::from(cmyk[3]) / 255.;
468    [
469        ((255. - c) * kf) as u8,
470        ((255. - m) * kf) as u8,
471        ((255. - y) * kf) as u8,
472    ]
473}
474
475fn cmyk_to_rgb16(cmyk: &[u16; 4]) -> [u16; 3] {
476    let c = f32::from(cmyk[0]);
477    let m = f32::from(cmyk[1]);
478    let y = f32::from(cmyk[2]);
479    let kf = 1. - f32::from(cmyk[3]) / 65535.;
480    [
481        ((65535. - c) * kf) as u16,
482        ((65535. - m) * kf) as u16,
483        ((65535. - y) * kf) as u16,
484    ]
485}
486
487/// Convert a slice of sample bytes to its semantic type, being a `Pod`.
488fn u8_slice_as_pod<P: bytemuck::Pod>(buf: &[u8]) -> ImageResult<std::borrow::Cow<'_, [P]>> {
489    bytemuck::try_cast_slice(buf)
490        .map(std::borrow::Cow::Borrowed)
491        .or_else(|err| {
492            match err {
493                bytemuck::PodCastError::TargetAlignmentGreaterAndInputNotAligned => {
494                    // If the buffer is not aligned for a native slice, copy the buffer into a Vec,
495                    // aligning it in the process. This is only done if the element count can be
496                    // represented exactly.
497                    let vec = bytemuck::allocation::pod_collect_to_vec(buf);
498                    Ok(std::borrow::Cow::Owned(vec))
499                }
500                /* only expecting: bytemuck::PodCastError::OutputSliceWouldHaveSlop */
501                _ => {
502                    // `bytemuck::PodCastError` of bytemuck-1.2.0 does not implement `Error` and
503                    // `Display` trait.
504                    // See <https://github.com/Lokathor/bytemuck/issues/22>.
505                    Err(ImageError::Parameter(ParameterError::from_kind(
506                        ParameterErrorKind::Generic(format!(
507                            "Casting samples to their representation failed: {err:?}",
508                        )),
509                    )))
510                }
511            }
512        })
513}
514
515impl<W: Write + Seek> TiffEncoder<W> {
516    /// Create a new encoder that writes its output to `w`
517    pub fn new(w: W) -> TiffEncoder<W> {
518        TiffEncoder { w, icc: None }
519    }
520
521    /// Private wrapper function to encode the image with a generic color type. This is used to reduce code duplication in the public `write_image` function.
522    fn write_tiff<C: tiff::encoder::colortype::ColorType<Inner: bytemuck::Pod>>(
523        self,
524        width: u32,
525        height: u32,
526        data: &[u8],
527    ) -> ImageResult<()>
528    where
529        [C::Inner]: tiff::encoder::TiffValue,
530    {
531        let mut encoder =
532            tiff::encoder::TiffEncoder::new(self.w).map_err(ImageError::from_tiff_encode)?;
533        let data = u8_slice_as_pod::<C::Inner>(data)?;
534        let mut img_encoder = encoder
535            .new_image::<C>(width, height)
536            .map_err(ImageError::from_tiff_encode)?;
537        if let Some(icc_profile) = self.icc {
538            // An ICC device profile is embedded, in its entirety, as a single TIFF field or Image File Directory (IFD) entry in
539            // the IFD containing the corresponding image data. An IFD should contain no more than one embedded profile.
540            // A TIFF file may contain more than one image, and so, more than one IFD. Each IFD may have its own
541            // embedded profile.
542            // -- Specification ICC.1:2004-10 (Profile version 4.2.0.0), https://www.color.org/icc1V42.pdf
543            let ifd_encoder = img_encoder.encoder(); // low-level TIFF directory encoder
544            ifd_encoder
545                .write_tag(Tag::IccProfile, icc_profile.as_slice())
546                .map_err(ImageError::from_tiff_encode)?;
547        }
548        img_encoder
549            .write_data(&data)
550            .map_err(ImageError::from_tiff_encode)
551    }
552
553    /// See the trait method [`write_image`](#method.write_image) for more details.
554    #[track_caller]
555    #[deprecated = "Use the `write_image` method from the `ImageEncoder` trait directly."]
556    pub fn encode(
557        self,
558        buf: &[u8],
559        width: u32,
560        height: u32,
561        color_type: ExtendedColorType,
562    ) -> ImageResult<()> {
563        // Preserved for API compatibility.
564        self.write_image(buf, width, height, color_type)
565    }
566}
567
568impl<W: Write + Seek> ImageEncoder for TiffEncoder<W> {
569    /// Encodes the image `image` that has dimensions `width` and `height` and `ColorType` `c`.
570    ///
571    /// 16-bit types assume the buffer is native endian.
572    ///
573    /// # Panics
574    ///
575    /// Panics if `width * height * color_type.bytes_per_pixel() != data.len()`.
576    #[track_caller]
577    fn write_image(
578        self,
579        buf: &[u8],
580        width: u32,
581        height: u32,
582        color_type: ExtendedColorType,
583    ) -> ImageResult<()> {
584        use tiff::encoder::colortype::{
585            Gray16, Gray8, RGB32Float, RGBA32Float, RGB16, RGB8, RGBA16, RGBA8,
586        };
587        let expected_buffer_len = color_type.buffer_size(width, height);
588        assert_eq!(
589            expected_buffer_len,
590            buf.len() as u64,
591            "Invalid buffer length: expected {expected_buffer_len} got {} for {width}x{height} image",
592            buf.len(),
593        );
594        match color_type {
595            ExtendedColorType::L8 => self.write_tiff::<Gray8>(width, height, buf),
596            ExtendedColorType::Rgb8 => self.write_tiff::<RGB8>(width, height, buf),
597            ExtendedColorType::Rgba8 => self.write_tiff::<RGBA8>(width, height, buf),
598            ExtendedColorType::L16 => self.write_tiff::<Gray16>(width, height, buf),
599            ExtendedColorType::Rgb16 => self.write_tiff::<RGB16>(width, height, buf),
600            ExtendedColorType::Rgba16 => self.write_tiff::<RGBA16>(width, height, buf),
601            ExtendedColorType::Rgb32F => self.write_tiff::<RGB32Float>(width, height, buf),
602            ExtendedColorType::Rgba32F => self.write_tiff::<RGBA32Float>(width, height, buf),
603            _ => Err(ImageError::Unsupported(
604                UnsupportedError::from_format_and_kind(
605                    ImageFormat::Tiff.into(),
606                    UnsupportedErrorKind::Color(color_type),
607                ),
608            )),
609        }
610    }
611
612    fn set_icc_profile(&mut self, icc_profile: Vec<u8>) -> Result<(), UnsupportedError> {
613        self.icc = Some(icc_profile);
614        Ok(())
615    }
616}