Coverage Report

Created: 2026-11-12 08:04

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/image/src/codecs/tiff.rs
Line
Count
Source
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
8
use std::io::{BufRead, Seek, Write};
9
10
use tiff::decoder::ifd::Value;
11
use tiff::decoder::{Decoder, DecodingResult};
12
use tiff::tags::Tag;
13
14
use crate::color::{ColorType, ExtendedColorType};
15
use crate::error::{
16
    DecodingError, EncodingError, ImageError, ImageResult, LimitError, LimitErrorKind,
17
    ParameterError, ParameterErrorKind, UnsupportedError, UnsupportedErrorKind,
18
};
19
use crate::io::decoder::DecodedMetadataHint;
20
use crate::io::{DecodedImageAttributes, DecoderPreparedImage, FormatAttributes};
21
use crate::metadata::Orientation;
22
use crate::{utils, ImageDecoder, ImageEncoder, ImageFormat};
23
24
const TAG_XML_PACKET: Tag = Tag::Unknown(700);
25
const TAG_YCBCR_COEFFICIENTS: Tag = Tag::Unknown(529);
26
const TAG_YCBCR_SUBSAMPLING: Tag = Tag::Unknown(530);
27
28
/// Decoder for TIFF images.
29
pub struct TiffDecoder<R>
30
where
31
    R: BufRead + Seek,
32
{
33
    info: ImageState,
34
    /// The individual allocations attribute to parts of the decoder.
35
    limits: tiff::decoder::Limits,
36
    // We only use an Option here so we can call with_limits on the decoder without moving.
37
    inner: Option<Decoder<R>>,
38
    buffer: DecodingResult,
39
}
40
41
enum ImageState {
42
    Initial,
43
    At(ImageInfo),
44
    Consumed,
45
}
46
47
#[derive(Clone, Copy)]
48
struct ImageInfo {
49
    dimensions: (u32, u32),
50
    color_type: ColorType,
51
    original_color_type: ExtendedColorType,
52
    ycbcr_coefficients: [f32; 3],
53
}
54
55
impl<R> TiffDecoder<R>
56
where
57
    R: BufRead + Seek,
58
{
59
    /// Create a new `TiffDecoder`.
60
15.9k
    pub fn new(r: R) -> Result<TiffDecoder<R>, ImageError> {
61
15.9k
        let inner = Decoder::new(r).map_err(ImageError::from_tiff_decode)?;
62
63
11.9k
        Ok(TiffDecoder {
64
11.9k
            info: ImageState::Initial,
65
11.9k
            limits: tiff::decoder::Limits::default(),
66
11.9k
            inner: Some(inner),
67
11.9k
            buffer: DecodingResult::U8(vec![]),
68
11.9k
        })
69
15.9k
    }
70
71
46.7k
    fn peek_info(&mut self) -> ImageResult<ImageInfo> {
72
46.7k
        let Some(reader) = &mut self.inner else {
73
0
            return Err(ImageError::Parameter(ParameterError::from_kind(
74
0
                ParameterErrorKind::FailedAlready,
75
0
            )));
76
        };
77
78
        // This image may have been consumed, we should advance.
79
46.7k
        if let ImageState::Consumed = self.info {
80
0
            reader.next_image().map_err(ImageError::from_tiff_decode)?;
81
0
            self.info = ImageState::Initial;
82
46.7k
        }
83
84
46.7k
        if let ImageState::Initial = self.info {
85
11.9k
            self.reset_info_from_current_image()?;
86
34.8k
        }
87
88
46.5k
        let ImageState::At(info) = self.info else {
89
0
            return Err(ImageError::Parameter(ParameterError::from_kind(
90
0
                ParameterErrorKind::FailedAlready,
91
0
            )));
92
        };
93
94
46.5k
        Ok(info)
95
46.7k
    }
96
97
11.9k
    fn reset_info_from_current_image(&mut self) -> ImageResult<()> {
98
        use tiff::tags::SampleFormat::{Uint, IEEEFP};
99
100
11.9k
        let Some(reader) = &mut self.inner else {
101
0
            return Err(ImageError::Parameter(ParameterError::from_kind(
102
0
                ParameterErrorKind::FailedAlready,
103
0
            )));
104
        };
105
106
11.9k
        let dimensions = reader.dimensions().map_err(ImageError::from_tiff_decode)?;
107
11.9k
        let tiff_color_type = reader.colortype().map_err(ImageError::from_tiff_decode)?;
108
11.8k
        let sample_format = reader
109
11.8k
            .image_buffer_layout()
110
11.8k
            .map_err(ImageError::from_tiff_decode)?
111
            .sample_format;
112
113
11.8k
        let color_type = match (tiff_color_type, sample_format) {
114
6.95k
            (tiff::ColorType::Gray(1), Uint) => ColorType::L8,
115
1.54k
            (tiff::ColorType::Gray(8), Uint) => ColorType::L8,
116
858
            (tiff::ColorType::Gray(16), Uint) => ColorType::L16,
117
35
            (tiff::ColorType::Gray(32), IEEEFP) => ColorType::L32F,
118
0
            (tiff::ColorType::GrayA(8), Uint) => ColorType::La8,
119
0
            (tiff::ColorType::GrayA(16), Uint) => ColorType::La16,
120
1.00k
            (tiff::ColorType::RGB(8), Uint) => ColorType::Rgb8,
121
221
            (tiff::ColorType::RGB(16), Uint) => ColorType::Rgb16,
122
632
            (tiff::ColorType::RGBA(8), Uint) => ColorType::Rgba8,
123
67
            (tiff::ColorType::RGBA(16), Uint) => ColorType::Rgba16,
124
111
            (tiff::ColorType::CMYK(8), Uint) => ColorType::Rgb8,
125
38
            (tiff::ColorType::CMYK(16), Uint) => ColorType::Rgb16,
126
110
            (tiff::ColorType::RGB(32), IEEEFP) => ColorType::Rgb32F,
127
83
            (tiff::ColorType::RGBA(32), IEEEFP) => ColorType::Rgba32F,
128
99
            (tiff::ColorType::YCbCr(8), Uint) => ColorType::Rgb8,
129
            _ => {
130
77
                return Err(ImageError::Unsupported(
131
77
                    UnsupportedError::from_format_and_kind(
132
77
                        ImageFormat::Tiff.into(),
133
77
                        UnsupportedErrorKind::GenericFeature(format!(
134
77
                            "Unsupported TIFF color {tiff_color_type:?} with sample format {sample_format:?}"
135
77
                        )),
136
77
                    ),
137
77
                ));
138
            }
139
        };
140
141
11.7k
        let original_color_type = match (tiff_color_type, sample_format) {
142
6.95k
            (tiff::ColorType::Gray(1), Uint) => ExtendedColorType::L1,
143
111
            (tiff::ColorType::CMYK(8), Uint) => ExtendedColorType::Cmyk8,
144
38
            (tiff::ColorType::CMYK(16), Uint) => ExtendedColorType::Cmyk16,
145
99
            (tiff::ColorType::YCbCr(8), Uint) => ExtendedColorType::YCbCr8,
146
4.55k
            _ => color_type.into(),
147
        };
148
149
11.7k
        let mut ycbcr_coefficients = [0.0; 3];
150
11.7k
        if matches!(tiff_color_type, tiff::ColorType::YCbCr(8)) {
151
99
            check_ycbcr_subsampling(reader)?;
152
99
            ycbcr_coefficients = read_ycbcr_coefficients(reader)?;
153
11.6k
        }
154
155
11.7k
        self.info = ImageState::At(ImageInfo {
156
11.7k
            dimensions,
157
11.7k
            color_type,
158
11.7k
            original_color_type,
159
11.7k
            ycbcr_coefficients,
160
11.7k
        });
161
162
11.7k
        self.redistribute_limits();
163
164
11.7k
        Ok(())
165
11.9k
    }
166
167
23.6k
    fn redistribute_limits(&mut self) {
168
23.6k
        let ImageState::At(info) = &self.info else {
169
11.9k
            return;
170
        };
171
172
11.7k
        if self.inner.is_none() {
173
0
            return;
174
11.7k
        }
175
176
11.7k
        let max_alloc = (self.limits.decoding_buffer_size as u64)
177
11.7k
            .saturating_add(self.limits.intermediate_buffer_size as u64);
178
179
11.7k
        let max_intermediate_alloc = max_alloc.saturating_sub(info.total_bytes_buffer());
180
11.7k
        let mut tiff_limits: tiff::decoder::Limits = Default::default();
181
11.7k
        tiff_limits.decoding_buffer_size =
182
11.7k
            usize::try_from(max_alloc - max_intermediate_alloc).unwrap_or(usize::MAX);
183
11.7k
        tiff_limits.intermediate_buffer_size =
184
11.7k
            usize::try_from(max_intermediate_alloc).unwrap_or(usize::MAX);
185
11.7k
        tiff_limits.ifd_value_size = tiff_limits.intermediate_buffer_size;
186
187
11.7k
        self.inner = Some(self.inner.take().unwrap().with_limits(tiff_limits));
188
23.6k
    }
189
190
    /// Interleave planes in our `buffer` into `output`.
191
12
    fn interleave_planes(
192
12
        &mut self,
193
12
        info: ImageInfo,
194
12
        layout: tiff::decoder::BufferLayoutPreference,
195
12
        output: &mut [u8],
196
12
    ) -> ImageResult<()> {
197
12
        if info.original_color_type != info.color_type.into() {
198
1
            return Err(ImageError::Unsupported(
199
1
                UnsupportedError::from_format_and_kind(
200
1
                    ImageFormat::Tiff.into(),
201
1
                    UnsupportedErrorKind::GenericFeature(
202
1
                        "Planar TIFF with CMYK color type is not supported".to_string(),
203
1
                    ),
204
1
                ),
205
1
            ));
206
11
        }
207
208
        // This only works if we and `tiff` agree on the layout, including the color type, of
209
        // the sample matrix.
210
        //
211
        // TODO: triple buffer in the other case and fixup the planar layout independent of
212
        // sample type. Problem description follows:
213
        //
214
        // That will suck since we can't call `interleave_planes` with a `ColorType` argument,
215
        // Changing that parameter to `ExtendedColorType` is a can of worms, and exposing the
216
        // underlying generic function is an optimization killer (we may want to help LLVM
217
        // optimize this interleaving by SIMD). For LumaAlpha(1) colors we should do the bit
218
        // expansion at the same time as interleaving to avoid wasting the memory traversal but
219
        // expand-then-interleave is at least clear, albeit an extra buffer required. Meanwhile
220
        // for `Cmyk8`/`Cmyk16` our output is smaller than the tiff buffer (4 samples to 3, or
221
        // 5 to 4 if we had alpha) and not wanting multiple conversion function implementations
222
        // we should interleave-then-expand?
223
        //
224
        // The hard part of the solution will be managing complexity.
225
11
        let plane_stride = layout.plane_stride.map_or(0, |n| n.get());
226
11
        let bytes = self.buffer.as_buffer(0);
227
228
11
        let planes = bytes
229
11
            .as_bytes()
230
11
            .chunks_exact(plane_stride)
231
11
            .collect::<Vec<_>>();
232
233
        // Gracefully handle a mismatch of expectations. This should not occur in practice as we
234
        // check that all planes have been read (see note on `read_image_to_buffer` usage below).
235
11
        if planes.len() < usize::from(info.color_type.channel_count()) {
236
0
            return Err(ImageError::Decoding(DecodingError::new(
237
0
                ImageFormat::Tiff.into(),
238
0
                "Not enough planes read from TIFF image".to_string(),
239
0
            )));
240
11
        }
241
242
11
        utils::interleave_planes(
243
11
            output,
244
11
            info.color_type,
245
11
            &planes[..usize::from(info.color_type.channel_count())],
246
        );
247
248
11
        Ok(())
249
12
    }
250
}
251
252
impl ImageInfo {
253
    // The buffer can be larger for CMYK than the RGB output
254
11.7k
    fn total_bytes_buffer(&self) -> u64 {
255
11.7k
        let (width, height) = self.dimensions;
256
11.7k
        let total_pixels = u64::from(width) * u64::from(height);
257
258
11.7k
        let bytes_per_pixel = match self.original_color_type {
259
111
            ExtendedColorType::Cmyk8 => 4,
260
38
            ExtendedColorType::Cmyk16 => 8,
261
11.6k
            _ => u64::from(self.color_type.bytes_per_pixel()),
262
        };
263
264
11.7k
        total_pixels.saturating_mul(bytes_per_pixel)
265
11.7k
    }
266
}
267
268
99
fn check_ycbcr_subsampling<R: BufRead + Seek>(decoder: &mut Decoder<R>) -> ImageResult<()> {
269
99
    let compression = decoder
270
99
        .find_tag(Tag::Compression)
271
99
        .map_err(ImageError::from_tiff_decode)?
272
99
        .and_then(|v| v.into_u16().ok());
273
274
    const COMPRESSION_MODERN_JPEG: u16 = 7;
275
99
    if compression == Some(COMPRESSION_MODERN_JPEG) {
276
27
        return Ok(());
277
72
    }
278
279
72
    let subsampling = decoder
280
72
        .find_tag(TAG_YCBCR_SUBSAMPLING)
281
72
        .map_err(ImageError::from_tiff_decode)?
282
72
        .map(|value| value.into_u16_vec())
283
72
        .transpose()
284
72
        .map_err(ImageError::from_tiff_decode)?;
285
286
72
    let subsampling = subsampling.as_deref().unwrap_or(&[2, 2]);
287
288
72
    if subsampling != [1, 1] {
289
0
        return Err(ImageError::Unsupported(
290
0
            UnsupportedError::from_format_and_kind(
291
0
                ImageFormat::Tiff.into(),
292
0
                UnsupportedErrorKind::GenericFeature(format!(
293
0
                "Subsampling {:?} is not supported. Only (1,1) is supported for non-JPEG YCbCr.",
294
0
                subsampling
295
0
            )),
296
0
            ),
297
0
        ));
298
72
    }
299
300
72
    Ok(())
301
99
}
302
303
99
fn read_ycbcr_coefficients<R: BufRead + Seek>(decoder: &mut Decoder<R>) -> ImageResult<[f32; 3]> {
304
99
    let value = decoder
305
99
        .find_tag(TAG_YCBCR_COEFFICIENTS)
306
99
        .map_err(ImageError::from_tiff_decode)?;
307
308
    const DEFAULT_YCBCR_COEFFICIENTS: [f32; 3] = [0.299, 0.587, 0.114];
309
98
    let Some(value) = value else {
310
36
        return Ok(DEFAULT_YCBCR_COEFFICIENTS);
311
    };
312
313
61
    let list = match value {
314
61
        Value::List(list) if list.len() == 3 => list,
315
        _ => {
316
4
            return Err(ImageError::Decoding(DecodingError::new(
317
4
                ImageFormat::Tiff.into(),
318
4
                "YCbCrCoefficients tag (529) must contain exactly 3 rational values".to_string(),
319
4
            )));
320
        }
321
    };
322
323
58
    let mut coefficients = [0.0f32; 3];
324
167
    for (i, value) in list.iter().enumerate() {
325
166
        match value {
326
166
            Value::Rational(num, denom) if *denom != 0 => {
327
161
                coefficients[i] = *num as f32 / *denom as f32
328
            }
329
            _ => {
330
6
                return Err(ImageError::Decoding(DecodingError::new(
331
6
                    ImageFormat::Tiff.into(),
332
6
                    "YCbCrCoefficients tag (529) contains an invalid rational value".to_string(),
333
6
                )));
334
            }
335
        }
336
    }
337
338
52
    Ok(coefficients)
339
99
}
340
341
impl ImageError {
342
12.0k
    fn from_tiff_decode(err: tiff::TiffError) -> ImageError {
343
12.0k
        match err {
344
5.32k
            tiff::TiffError::IoError(err) => ImageError::IoError(err),
345
4.82k
            err @ (tiff::TiffError::FormatError(_)
346
            | tiff::TiffError::IntSizeError
347
            | tiff::TiffError::UsageError(_)) => {
348
4.82k
                ImageError::Decoding(DecodingError::new(ImageFormat::Tiff.into(), err))
349
            }
350
342
            tiff::TiffError::UnsupportedError(desc) => {
351
342
                ImageError::Unsupported(UnsupportedError::from_format_and_kind(
352
342
                    ImageFormat::Tiff.into(),
353
342
                    UnsupportedErrorKind::GenericFeature(desc.to_string()),
354
342
                ))
355
            }
356
            tiff::TiffError::LimitsExceeded => {
357
1.56k
                ImageError::Limits(LimitError::from_kind(LimitErrorKind::InsufficientMemory))
358
            }
359
        }
360
12.0k
    }
361
362
0
    fn from_tiff_encode(err: tiff::TiffError) -> ImageError {
363
0
        match err {
364
0
            tiff::TiffError::IoError(err) => ImageError::IoError(err),
365
0
            err @ (tiff::TiffError::FormatError(_)
366
            | tiff::TiffError::IntSizeError
367
            | tiff::TiffError::UsageError(_)) => {
368
0
                ImageError::Encoding(EncodingError::new(ImageFormat::Tiff.into(), err))
369
            }
370
0
            tiff::TiffError::UnsupportedError(desc) => {
371
0
                ImageError::Unsupported(UnsupportedError::from_format_and_kind(
372
0
                    ImageFormat::Tiff.into(),
373
0
                    UnsupportedErrorKind::GenericFeature(desc.to_string()),
374
0
                ))
375
            }
376
            tiff::TiffError::LimitsExceeded => {
377
0
                ImageError::Limits(LimitError::from_kind(LimitErrorKind::InsufficientMemory))
378
            }
379
        }
380
0
    }
381
}
382
383
impl<R: BufRead + Seek> ImageDecoder for TiffDecoder<R> {
384
19.0k
    fn format_attributes(&self) -> FormatAttributes {
385
19.0k
        FormatAttributes {
386
19.0k
            // is any sort of iTXT chunk.
387
19.0k
            xmp: DecodedMetadataHint::PerImage,
388
19.0k
            icc: DecodedMetadataHint::PerImage,
389
19.0k
            exif: DecodedMetadataHint::PerImage,
390
19.0k
            // not provided above.
391
19.0k
            iptc: DecodedMetadataHint::Unsupported,
392
19.0k
            supports_sequence: true,
393
19.0k
            ..FormatAttributes::default()
394
19.0k
        }
395
19.0k
    }
396
397
35.1k
    fn prepare_image(&mut self) -> ImageResult<DecoderPreparedImage> {
398
35.1k
        let info = self.peek_info()?;
399
34.9k
        let (width, height) = info.dimensions;
400
401
34.9k
        Ok(DecoderPreparedImage::new(width, height, info.color_type))
402
35.1k
    }
403
404
11.7k
    fn icc_profile(&mut self) -> ImageResult<Option<Vec<u8>>> {
405
11.7k
        if let Some(decoder) = &mut self.inner {
406
11.7k
            Ok(decoder.get_tag_u8_vec(Tag::IccProfile).ok())
407
        } else {
408
0
            Ok(None)
409
        }
410
11.7k
    }
411
412
11.7k
    fn xmp_metadata(&mut self) -> ImageResult<Option<Vec<u8>>> {
413
11.7k
        let Some(decoder) = &mut self.inner else {
414
0
            return Ok(None);
415
        };
416
417
11.7k
        let value = match decoder.get_tag(TAG_XML_PACKET) {
418
6
            Ok(value) => value,
419
            Err(tiff::TiffError::FormatError(tiff::TiffFormatError::RequiredTagNotFound(_))) => {
420
11.7k
                return Ok(None);
421
            }
422
15
            Err(err) => return Err(ImageError::from_tiff_decode(err)),
423
        };
424
6
        value
425
6
            .into_u8_vec()
426
6
            .map(Some)
427
6
            .map_err(ImageError::from_tiff_decode)
428
11.7k
    }
429
430
11.9k
    fn set_limits(&mut self, limits: crate::Limits) -> ImageResult<()> {
431
11.9k
        limits.check_support(&crate::LimitSupport::default())?;
432
433
11.9k
        let reserved = match self.info {
434
0
            ImageState::At(info) => info,
435
            // Construct a dummy info that did not consume any memory.
436
11.9k
            _ => ImageInfo {
437
11.9k
                dimensions: (0, 0),
438
11.9k
                color_type: ColorType::L8,
439
11.9k
                original_color_type: ExtendedColorType::L8,
440
11.9k
                ycbcr_coefficients: [0.0; 3],
441
11.9k
            },
442
        };
443
444
11.9k
        let (width, height) = reserved.dimensions;
445
11.9k
        limits.check_dimensions(width, height)?;
446
447
11.9k
        let max_alloc = limits
448
11.9k
            .max_alloc
449
11.9k
            .and_then(|n| usize::try_from(n).ok())
450
11.9k
            .unwrap_or(usize::MAX);
451
452
11.9k
        self.limits.decoding_buffer_size = max_alloc;
453
11.9k
        self.limits.intermediate_buffer_size = 0;
454
11.9k
        self.redistribute_limits();
455
456
11.9k
        Ok(())
457
11.9k
    }
458
459
11.6k
    fn read_image(&mut self, buf: &mut [u8]) -> ImageResult<DecodedImageAttributes> {
460
11.6k
        let info = self.peek_info()?;
461
11.6k
        let layout = self.prepare_image()?;
462
463
11.6k
        let original_color_type = Some(info.original_color_type);
464
11.6k
        assert_eq!(u64::try_from(buf.len()), Ok(layout.total_bytes()));
465
466
11.6k
        let Some(reader) = &mut self.inner else {
467
0
            return Err(ImageError::Parameter(ParameterError::from_kind(
468
0
                ParameterErrorKind::FailedAlready,
469
0
            )));
470
        };
471
472
11.6k
        let layout = reader
473
11.6k
            .read_image_to_buffer(&mut self.buffer)
474
11.6k
            .map_err(ImageError::from_tiff_decode)?;
475
476
        // Check if we have all of the planes. Otherwise we ran into the allocation limit.
477
3.65k
        if self.buffer.as_buffer(0).as_bytes().len() < layout.complete_len {
478
0
            return Err(ImageError::Limits(LimitError::from_kind(
479
0
                LimitErrorKind::InsufficientMemory,
480
0
            )));
481
3.65k
        }
482
483
3.65k
        if layout.planes > 1 {
484
            // Note that we do not support planar layouts if we have to do conversion. Yet. See a
485
            // more detailed comment in the implementation.
486
12
            self.interleave_planes(info, layout, buf)?;
487
11
            return Ok(DecodedImageAttributes::default());
488
3.64k
        }
489
490
970
        match &self.buffer {
491
3.34k
            DecodingResult::U8(v) if info.original_color_type == ExtendedColorType::Cmyk8 => {
492
72
                let buf = buf.as_chunks_mut::<3>().0;
493
102k
                for (cmyk, rgb) in v.as_chunks::<4>().0.iter().zip(buf) {
494
102k
                    *rgb = cmyk_to_rgb(cmyk);
495
102k
                }
496
            }
497
179
            DecodingResult::U16(v) if info.original_color_type == ExtendedColorType::Cmyk16 => {
498
37
                let buf = buf.as_chunks_mut::<6>().0;
499
12.2k
                for (cmyk, rgb) in v.as_chunks::<4>().0.iter().zip(buf) {
500
12.2k
                    *rgb = bytemuck::cast(cmyk_to_rgb16(cmyk));
501
12.2k
                }
502
            }
503
3.27k
            DecodingResult::U8(v) if info.original_color_type == ExtendedColorType::L1 => {
504
2.30k
                let width = info.dimensions.0;
505
2.30k
                let row_bytes = width.div_ceil(8);
506
507
124k
                for (in_row, out_row) in v
508
2.30k
                    .chunks_exact(row_bytes as usize)
509
2.30k
                    .zip(buf.chunks_exact_mut(width as usize))
510
124k
                {
511
124k
                    out_row.copy_from_slice(&utils::expand_bits(1, width, in_row));
512
124k
                }
513
            }
514
970
            DecodingResult::U8(v) if info.original_color_type == ExtendedColorType::YCbCr8 => {
515
59
                let [lr, lg, lb] = info.ycbcr_coefficients;
516
59
                let ycbcr = v.as_chunks::<3>().0;
517
59
                let out = buf.as_chunks_mut::<3>().0;
518
59
519
59
                ycbcr_to_rgb8(ycbcr, lr, lg, lb, out);
520
59
            }
521
911
            DecodingResult::U8(v) => {
522
911
                buf.copy_from_slice(v);
523
911
            }
524
142
            DecodingResult::U16(v) => {
525
142
                buf.copy_from_slice(bytemuck::cast_slice(v));
526
142
            }
527
0
            DecodingResult::U32(v) => {
528
0
                buf.copy_from_slice(bytemuck::cast_slice(v));
529
0
            }
530
0
            DecodingResult::U64(v) => {
531
0
                buf.copy_from_slice(bytemuck::cast_slice(v));
532
0
            }
533
0
            DecodingResult::I8(v) => {
534
0
                buf.copy_from_slice(bytemuck::cast_slice(v));
535
0
            }
536
0
            DecodingResult::I16(v) => {
537
0
                buf.copy_from_slice(bytemuck::cast_slice(v));
538
0
            }
539
0
            DecodingResult::I32(v) => {
540
0
                buf.copy_from_slice(bytemuck::cast_slice(v));
541
0
            }
542
0
            DecodingResult::I64(v) => {
543
0
                buf.copy_from_slice(bytemuck::cast_slice(v));
544
0
            }
545
116
            DecodingResult::F32(v) => {
546
116
                buf.copy_from_slice(bytemuck::cast_slice(v));
547
116
            }
548
0
            DecodingResult::F64(v) => {
549
0
                buf.copy_from_slice(bytemuck::cast_slice(v));
550
0
            }
551
0
            DecodingResult::F16(_) => unreachable!(),
552
        }
553
554
3.64k
        let orientation = reader
555
3.64k
            .find_tag(Tag::Orientation)
556
3.64k
            .map_err(ImageError::from_tiff_decode)?
557
3.63k
            .and_then(|v| Orientation::from_exif(v.into_u16().ok()?.min(255) as u8));
558
559
        // Indicate to advance.
560
3.63k
        self.info = ImageState::Consumed;
561
562
3.63k
        Ok(DecodedImageAttributes {
563
3.63k
            orientation,
564
3.63k
            original_color_type,
565
3.63k
            ..DecodedImageAttributes::default()
566
3.63k
        })
567
11.6k
    }
568
569
11.7k
    fn exif_metadata(&mut self) -> ImageResult<Option<Vec<u8>>> {
570
11.7k
        Ok(None)
571
11.7k
    }
572
573
0
    fn iptc_metadata(&mut self) -> ImageResult<Option<Vec<u8>>> {
574
0
        Ok(None)
575
0
    }
576
577
0
    fn more_images(&self) -> crate::io::SequenceControl {
578
0
        self.inner
579
0
            .as_ref()
580
0
            .and_then(|reader| {
581
0
                reader
582
0
                    .more_images()
583
0
                    .then_some(crate::io::SequenceControl::MaybeMore)
584
0
            })
585
0
            .unwrap_or(crate::io::SequenceControl::None)
586
0
    }
587
}
588
589
/// Encoder for tiff images
590
pub struct TiffEncoder<W> {
591
    w: W,
592
    icc: Option<Vec<u8>>,
593
    xmp: Option<Vec<u8>>,
594
}
595
596
59
fn ycbcr_to_rgb8(ycbcr: &[[u8; 3]], lr: f32, lg: f32, lb: f32, out: &mut [[u8; 3]]) {
597
59
    let coeff_r = 2.0 * (1.0 - lr);
598
59
    let coeff_b = 2.0 * (1.0 - lb);
599
59
    let inv_lg = 1.0 / lg;
600
601
32.6k
    for (src, dst) in ycbcr.iter().zip(out.iter_mut()) {
602
32.6k
        let y = f32::from(src[0]);
603
32.6k
        let cb = f32::from(src[1]) - 128.0;
604
32.6k
        let cr = f32::from(src[2]) - 128.0;
605
32.6k
606
32.6k
        let r = y + cr * coeff_r;
607
32.6k
        let b = y + cb * coeff_b;
608
32.6k
        let g = (y - lr * r - lb * b) * inv_lg;
609
32.6k
610
32.6k
        dst[0] = (r + 0.5) as u8;
611
32.6k
        dst[1] = (g + 0.5) as u8;
612
32.6k
        dst[2] = (b + 0.5) as u8;
613
32.6k
    }
614
59
}
615
616
102k
fn cmyk_to_rgb(cmyk: &[u8; 4]) -> [u8; 3] {
617
102k
    let c = cmyk[0] as u32;
618
102k
    let m = cmyk[1] as u32;
619
102k
    let y = cmyk[2] as u32;
620
102k
    let k = cmyk[3] as u32;
621
622
102k
    let k_inv = 255 - k;
623
102k
    [
624
102k
        (((255 - c) * k_inv) / 255) as u8,
625
102k
        (((255 - m) * k_inv) / 255) as u8,
626
102k
        (((255 - y) * k_inv) / 255) as u8,
627
102k
    ]
628
102k
}
629
630
12.2k
fn cmyk_to_rgb16(cmyk: &[u16; 4]) -> [u16; 3] {
631
12.2k
    let c = cmyk[0] as u64;
632
12.2k
    let m = cmyk[1] as u64;
633
12.2k
    let y = cmyk[2] as u64;
634
12.2k
    let k = cmyk[3] as u64;
635
636
12.2k
    let k_inv = 65535 - k;
637
12.2k
    [
638
12.2k
        (((65535 - c) * k_inv) / 65535) as u16,
639
12.2k
        (((65535 - m) * k_inv) / 65535) as u16,
640
12.2k
        (((65535 - y) * k_inv) / 65535) as u16,
641
12.2k
    ]
642
12.2k
}
643
644
/// Convert a slice of sample bytes to its semantic type, being a `Pod`.
645
0
fn u8_slice_as_pod<P: bytemuck::Pod>(buf: &[u8]) -> ImageResult<std::borrow::Cow<'_, [P]>> {
646
0
    bytemuck::try_cast_slice(buf)
647
0
        .map(std::borrow::Cow::Borrowed)
648
0
        .or_else(|err| {
649
0
            match err {
650
                bytemuck::PodCastError::TargetAlignmentGreaterAndInputNotAligned => {
651
                    // If the buffer is not aligned for a native slice, copy the buffer into a Vec,
652
                    // aligning it in the process. This is only done if the element count can be
653
                    // represented exactly.
654
0
                    let vec = bytemuck::allocation::pod_collect_to_vec(buf);
655
0
                    Ok(std::borrow::Cow::Owned(vec))
656
                }
657
                /* only expecting: bytemuck::PodCastError::OutputSliceWouldHaveSlop */
658
                _ => {
659
                    // `bytemuck::PodCastError` of bytemuck-1.2.0 does not implement `Error` and
660
                    // `Display` trait.
661
                    // See <https://github.com/Lokathor/bytemuck/issues/22>.
662
0
                    Err(ImageError::Parameter(ParameterError::from_kind(
663
0
                        ParameterErrorKind::Generic(format!(
664
0
                            "Casting samples to their representation failed: {err:?}",
665
0
                        )),
666
0
                    )))
667
                }
668
            }
669
0
        })
Unexecuted instantiation: image::codecs::tiff::u8_slice_as_pod::<_>::{closure#0}
Unexecuted instantiation: image::codecs::tiff::u8_slice_as_pod::<f32>::{closure#0}
Unexecuted instantiation: image::codecs::tiff::u8_slice_as_pod::<u8>::{closure#0}
Unexecuted instantiation: image::codecs::tiff::u8_slice_as_pod::<u16>::{closure#0}
670
0
}
Unexecuted instantiation: image::codecs::tiff::u8_slice_as_pod::<_>
Unexecuted instantiation: image::codecs::tiff::u8_slice_as_pod::<f32>
Unexecuted instantiation: image::codecs::tiff::u8_slice_as_pod::<u8>
Unexecuted instantiation: image::codecs::tiff::u8_slice_as_pod::<u16>
671
672
impl<W: Write + Seek> TiffEncoder<W> {
673
    /// Create a new encoder that writes its output to `w`
674
0
    pub fn new(w: W) -> TiffEncoder<W> {
675
0
        TiffEncoder {
676
0
            w,
677
0
            icc: None,
678
0
            xmp: None,
679
0
        }
680
0
    }
Unexecuted instantiation: <image::codecs::tiff::TiffEncoder<_>>::new
Unexecuted instantiation: <image::codecs::tiff::TiffEncoder<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>::new
681
682
    /// 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.
683
0
    fn write_tiff<C: tiff::encoder::colortype::ColorType<Inner: bytemuck::Pod>>(
684
0
        self,
685
0
        width: u32,
686
0
        height: u32,
687
0
        data: &[u8],
688
0
    ) -> ImageResult<()>
689
0
    where
690
0
        [C::Inner]: tiff::encoder::TiffValue,
691
    {
692
0
        let mut encoder =
693
0
            tiff::encoder::TiffEncoder::new(self.w).map_err(ImageError::from_tiff_encode)?;
694
0
        let data = u8_slice_as_pod::<C::Inner>(data)?;
695
0
        let mut img_encoder = encoder
696
0
            .new_image::<C>(width, height)
697
0
            .map_err(ImageError::from_tiff_encode)?;
698
0
        if self.icc.is_some() || self.xmp.is_some() {
699
0
            let ifd_encoder = img_encoder.encoder();
700
0
            if let Some(icc_profile) = self.icc {
701
0
                ifd_encoder
702
0
                    .write_tag(Tag::IccProfile, icc_profile.as_slice())
703
0
                    .map_err(ImageError::from_tiff_encode)?;
704
0
            }
705
0
            if let Some(xmp) = self.xmp {
706
0
                ifd_encoder
707
0
                    .write_tag(TAG_XML_PACKET, xmp.as_slice())
708
0
                    .map_err(ImageError::from_tiff_encode)?;
709
0
            }
710
0
        }
711
0
        img_encoder
712
0
            .write_data(&data)
713
0
            .map_err(ImageError::from_tiff_encode)
714
0
    }
Unexecuted instantiation: <image::codecs::tiff::TiffEncoder<_>>::write_tiff::<_>
Unexecuted instantiation: <image::codecs::tiff::TiffEncoder<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>::write_tiff::<tiff::encoder::colortype::RGB32Float>
Unexecuted instantiation: <image::codecs::tiff::TiffEncoder<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>::write_tiff::<tiff::encoder::colortype::Gray32Float>
Unexecuted instantiation: <image::codecs::tiff::TiffEncoder<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>::write_tiff::<tiff::encoder::colortype::RGBA32Float>
Unexecuted instantiation: <image::codecs::tiff::TiffEncoder<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>::write_tiff::<tiff::encoder::colortype::RGB8>
Unexecuted instantiation: <image::codecs::tiff::TiffEncoder<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>::write_tiff::<tiff::encoder::colortype::Gray8>
Unexecuted instantiation: <image::codecs::tiff::TiffEncoder<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>::write_tiff::<tiff::encoder::colortype::RGB16>
Unexecuted instantiation: <image::codecs::tiff::TiffEncoder<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>::write_tiff::<tiff::encoder::colortype::RGBA8>
Unexecuted instantiation: <image::codecs::tiff::TiffEncoder<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>::write_tiff::<tiff::encoder::colortype::Gray16>
Unexecuted instantiation: <image::codecs::tiff::TiffEncoder<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>::write_tiff::<tiff::encoder::colortype::RGBA16>
715
}
716
717
impl<W: Write + Seek> ImageEncoder for TiffEncoder<W> {
718
    /// Encodes the image `image` that has dimensions `width` and `height` and `ColorType` `c`.
719
    ///
720
    /// 16-bit types assume the buffer is native endian.
721
    ///
722
    /// # Panics
723
    ///
724
    /// Panics if the buffer does not hold exactly the number of bytes required for the given
725
    /// `width`, `height`, and `color_type`, accounting for rows padded to whole bytes for
726
    /// sub-byte color types: `height * ((width * color_type.bits_per_pixel() as u32 + 7) / 8)`.
727
    #[track_caller]
728
0
    fn write_image(
729
0
        self,
730
0
        buf: &[u8],
731
0
        width: u32,
732
0
        height: u32,
733
0
        color_type: ExtendedColorType,
734
0
    ) -> ImageResult<()> {
735
        use tiff::encoder::colortype::{
736
            Gray16, Gray32Float, Gray8, RGB32Float, RGBA32Float, RGB16, RGB8, RGBA16, RGBA8,
737
        };
738
0
        let expected_buffer_len = color_type.buffer_size(width, height);
739
0
        assert_eq!(
740
            expected_buffer_len,
741
0
            buf.len() as u64,
742
0
            "Invalid buffer length: expected {expected_buffer_len} got {} for {width}x{height} image",
743
0
            buf.len(),
744
        );
745
0
        match color_type {
746
0
            ExtendedColorType::L8 => self.write_tiff::<Gray8>(width, height, buf),
747
0
            ExtendedColorType::Rgb8 => self.write_tiff::<RGB8>(width, height, buf),
748
0
            ExtendedColorType::Rgba8 => self.write_tiff::<RGBA8>(width, height, buf),
749
0
            ExtendedColorType::L16 => self.write_tiff::<Gray16>(width, height, buf),
750
0
            ExtendedColorType::Rgb16 => self.write_tiff::<RGB16>(width, height, buf),
751
0
            ExtendedColorType::Rgba16 => self.write_tiff::<RGBA16>(width, height, buf),
752
0
            ExtendedColorType::L32F => self.write_tiff::<Gray32Float>(width, height, buf),
753
0
            ExtendedColorType::Rgb32F => self.write_tiff::<RGB32Float>(width, height, buf),
754
0
            ExtendedColorType::Rgba32F => self.write_tiff::<RGBA32Float>(width, height, buf),
755
0
            _ => Err(ImageError::Unsupported(
756
0
                UnsupportedError::from_format_and_kind(
757
0
                    ImageFormat::Tiff.into(),
758
0
                    UnsupportedErrorKind::Color(color_type),
759
0
                ),
760
0
            )),
761
        }
762
0
    }
Unexecuted instantiation: <image::codecs::tiff::TiffEncoder<_> as image::io::encoder::ImageEncoder>::write_image
Unexecuted instantiation: <image::codecs::tiff::TiffEncoder<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>> as image::io::encoder::ImageEncoder>::write_image
763
764
0
    fn set_icc_profile(&mut self, icc_profile: Vec<u8>) -> Result<(), UnsupportedError> {
765
0
        self.icc = Some(icc_profile);
766
0
        Ok(())
767
0
    }
Unexecuted instantiation: <image::codecs::tiff::TiffEncoder<_> as image::io::encoder::ImageEncoder>::set_icc_profile
Unexecuted instantiation: <image::codecs::tiff::TiffEncoder<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>> as image::io::encoder::ImageEncoder>::set_icc_profile
768
769
0
    fn set_xmp_metadata(&mut self, xmp: Vec<u8>) -> Result<(), UnsupportedError> {
770
0
        self.xmp = Some(xmp);
771
0
        Ok(())
772
0
    }
Unexecuted instantiation: <image::codecs::tiff::TiffEncoder<_> as image::io::encoder::ImageEncoder>::set_xmp_metadata
Unexecuted instantiation: <image::codecs::tiff::TiffEncoder<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>> as image::io::encoder::ImageEncoder>::set_xmp_metadata
773
}
774
775
#[cfg(test)]
776
mod tests {
777
    use std::io::Cursor;
778
779
    use crate::{
780
        EncodableLayout, ExtendedColorType, GenericImageView, ImageBuffer, ImageDecoder as _,
781
        ImageEncoder, ImageReader, Luma, Pixel, PixelWithColorType, Rgb, Rgba,
782
    };
783
784
    use super::{TiffDecoder, TiffEncoder};
785
786
    #[test]
787
    fn roundtrip_xmp() {
788
        let img = [255u8, 0, 0, 0, 255, 0, 0, 0, 255];
789
        let xmp = b"<x:xmpmeta xmlns:x=\"adobe:ns:meta/\"><rdf:RDF></rdf:RDF></x:xmpmeta>".to_vec();
790
791
        let mut encoded = Vec::new();
792
        {
793
            let mut encoder = TiffEncoder::new(Cursor::new(&mut encoded));
794
            encoder.set_xmp_metadata(xmp.clone()).unwrap();
795
            encoder
796
                .write_image(&img, 3, 1, ExtendedColorType::Rgb8)
797
                .expect("Could not encode image");
798
        }
799
800
        let mut decoder = TiffDecoder::new(Cursor::new(&encoded)).expect("Could not decode image");
801
        let decoded_xmp = decoder
802
            .xmp_metadata()
803
            .expect("Error decoding XMP")
804
            .expect("XMP is empty");
805
        assert_eq!(xmp, decoded_xmp);
806
    }
807
808
    #[test]
809
    fn roundtrip_color_types() {
810
        let img_l8 = ImageBuffer::from_fn(32, 32, |x, y| Luma([(x + y) as u8]));
811
        let img_rgb8 = ImageBuffer::from_fn(32, 32, |x, y| Rgb([x as u8, y as u8, (x + y) as u8]));
812
        let img_rgba8 = ImageBuffer::from_fn(32, 32, |x, y| {
813
            Rgba([x as u8, y as u8, (x + y) as u8, (x * y) as u8])
814
        });
815
816
        assert_roundtrip::<Luma<u8>>(&img_l8);
817
        assert_roundtrip::<Rgb<u8>>(&img_rgb8);
818
        assert_roundtrip::<Rgba<u8>>(&img_rgba8);
819
820
        assert_roundtrip::<Luma<u16>>(&img_l8.convert());
821
        assert_roundtrip::<Rgb<u16>>(&img_rgb8.convert());
822
        assert_roundtrip::<Rgba<u16>>(&img_rgba8.convert());
823
824
        assert_roundtrip::<Luma<f32>>(&img_l8.convert());
825
        assert_roundtrip::<Rgb<f32>>(&img_rgb8.convert());
826
        assert_roundtrip::<Rgba<f32>>(&img_rgba8.convert());
827
828
        fn assert_roundtrip<P: Pixel + PixelWithColorType>(img: &ImageBuffer<P, Vec<P::Subpixel>>)
829
        where
830
            [P::Subpixel]: EncodableLayout,
831
            Rgba<u8>: crate::color::FromColor<P>,
832
        {
833
            let mut encoded = Vec::new();
834
            let encoder = TiffEncoder::new(Cursor::new(&mut encoded));
835
            ImageBuffer::write_with_encoder(img, encoder).expect("Could not encoder image");
836
837
            let mut reader = ImageReader::new(Cursor::new(encoded)).unwrap();
838
            let (decoded, meta) = reader.decode().expect("Could not decode image");
839
840
            assert_eq!(img.dimensions(), decoded.dimensions());
841
            assert_eq!(Some(P::COLOR_TYPE), meta.attributes().original_color_type);
842
843
            let img_rgba8 = img.convert::<Rgba<u8>>();
844
            let decoded_rgba8 = decoded.to_rgba8();
845
846
            assert_eq!(img_rgba8, decoded_rgba8);
847
        }
848
    }
849
}