Skip to main content

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