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