Coverage Report

Created: 2026-09-02 07:14

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
0
    pub fn new(r: R) -> Result<TiffDecoder<R>, ImageError> {
61
0
        let inner = Decoder::new(r).map_err(ImageError::from_tiff_decode)?;
62
63
0
        Ok(TiffDecoder {
64
0
            info: ImageState::Initial,
65
0
            limits: tiff::decoder::Limits::default(),
66
0
            inner: Some(inner),
67
0
            buffer: DecodingResult::U8(vec![]),
68
0
        })
69
0
    }
70
71
0
    fn peek_info(&mut self) -> ImageResult<ImageInfo> {
72
0
        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
0
        if let ImageState::Consumed = self.info {
80
0
            reader.next_image().map_err(ImageError::from_tiff_decode)?;
81
0
            self.info = ImageState::Initial;
82
0
        }
83
84
0
        if let ImageState::Initial = self.info {
85
0
            self.reset_info_from_current_image()?;
86
0
        }
87
88
0
        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
0
        Ok(info)
95
0
    }
96
97
0
    fn reset_info_from_current_image(&mut self) -> ImageResult<()> {
98
        use tiff::tags::SampleFormat::{Uint, IEEEFP};
99
100
0
        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
0
        let dimensions = reader.dimensions().map_err(ImageError::from_tiff_decode)?;
107
0
        let tiff_color_type = reader.colortype().map_err(ImageError::from_tiff_decode)?;
108
0
        let sample_format = reader
109
0
            .image_buffer_layout()
110
0
            .map_err(ImageError::from_tiff_decode)?
111
            .sample_format;
112
113
0
        let color_type = match (tiff_color_type, sample_format) {
114
0
            (tiff::ColorType::Gray(1), Uint) => ColorType::L8,
115
0
            (tiff::ColorType::Gray(8), Uint) => ColorType::L8,
116
0
            (tiff::ColorType::Gray(16), Uint) => ColorType::L16,
117
0
            (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
0
            (tiff::ColorType::RGB(8), Uint) => ColorType::Rgb8,
121
0
            (tiff::ColorType::RGB(16), Uint) => ColorType::Rgb16,
122
0
            (tiff::ColorType::RGBA(8), Uint) => ColorType::Rgba8,
123
0
            (tiff::ColorType::RGBA(16), Uint) => ColorType::Rgba16,
124
0
            (tiff::ColorType::CMYK(8), Uint) => ColorType::Rgb8,
125
0
            (tiff::ColorType::CMYK(16), Uint) => ColorType::Rgb16,
126
0
            (tiff::ColorType::RGB(32), IEEEFP) => ColorType::Rgb32F,
127
0
            (tiff::ColorType::RGBA(32), IEEEFP) => ColorType::Rgba32F,
128
0
            (tiff::ColorType::YCbCr(8), Uint) => ColorType::Rgb8,
129
            _ => {
130
0
                return Err(ImageError::Unsupported(
131
0
                    UnsupportedError::from_format_and_kind(
132
0
                        ImageFormat::Tiff.into(),
133
0
                        UnsupportedErrorKind::GenericFeature(format!(
134
0
                            "Unsupported TIFF color {tiff_color_type:?} with sample format {sample_format:?}"
135
0
                        )),
136
0
                    ),
137
0
                ));
138
            }
139
        };
140
141
0
        let original_color_type = match (tiff_color_type, sample_format) {
142
0
            (tiff::ColorType::Gray(1), Uint) => ExtendedColorType::L1,
143
0
            (tiff::ColorType::CMYK(8), Uint) => ExtendedColorType::Cmyk8,
144
0
            (tiff::ColorType::CMYK(16), Uint) => ExtendedColorType::Cmyk16,
145
0
            (tiff::ColorType::YCbCr(8), Uint) => ExtendedColorType::YCbCr8,
146
0
            _ => color_type.into(),
147
        };
148
149
0
        let mut ycbcr_coefficients = [0.0; 3];
150
0
        if matches!(tiff_color_type, tiff::ColorType::YCbCr(8)) {
151
0
            check_ycbcr_subsampling(reader)?;
152
0
            ycbcr_coefficients = read_ycbcr_coefficients(reader)?;
153
0
        }
154
155
0
        self.info = ImageState::At(ImageInfo {
156
0
            dimensions,
157
0
            color_type,
158
0
            original_color_type,
159
0
            ycbcr_coefficients,
160
0
        });
161
162
0
        self.redistribute_limits();
163
164
0
        Ok(())
165
0
    }
166
167
0
    fn redistribute_limits(&mut self) {
168
0
        let ImageState::At(info) = &self.info else {
169
0
            return;
170
        };
171
172
0
        if self.inner.is_none() {
173
0
            return;
174
0
        }
175
176
0
        let max_alloc = (self.limits.decoding_buffer_size as u64)
177
0
            .saturating_add(self.limits.intermediate_buffer_size as u64);
178
179
0
        let max_intermediate_alloc = max_alloc.saturating_sub(info.total_bytes_buffer());
180
0
        let mut tiff_limits: tiff::decoder::Limits = Default::default();
181
0
        tiff_limits.decoding_buffer_size =
182
0
            usize::try_from(max_alloc - max_intermediate_alloc).unwrap_or(usize::MAX);
183
0
        tiff_limits.intermediate_buffer_size =
184
0
            usize::try_from(max_intermediate_alloc).unwrap_or(usize::MAX);
185
0
        tiff_limits.ifd_value_size = tiff_limits.intermediate_buffer_size;
186
187
0
        self.inner = Some(self.inner.take().unwrap().with_limits(tiff_limits));
188
0
    }
189
190
    /// Interleave planes in our `buffer` into `output`.
191
0
    fn interleave_planes(
192
0
        &mut self,
193
0
        info: ImageInfo,
194
0
        layout: tiff::decoder::BufferLayoutPreference,
195
0
        output: &mut [u8],
196
0
    ) -> ImageResult<()> {
197
0
        if info.original_color_type != info.color_type.into() {
198
0
            return Err(ImageError::Unsupported(
199
0
                UnsupportedError::from_format_and_kind(
200
0
                    ImageFormat::Tiff.into(),
201
0
                    UnsupportedErrorKind::GenericFeature(
202
0
                        "Planar TIFF with CMYK color type is not supported".to_string(),
203
0
                    ),
204
0
                ),
205
0
            ));
206
0
        }
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
0
        let plane_stride = layout.plane_stride.map_or(0, |n| n.get());
226
0
        let bytes = self.buffer.as_buffer(0);
227
228
0
        let planes = bytes
229
0
            .as_bytes()
230
0
            .chunks_exact(plane_stride)
231
0
            .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
0
        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
0
        }
241
242
0
        utils::interleave_planes(
243
0
            output,
244
0
            info.color_type,
245
0
            &planes[..usize::from(info.color_type.channel_count())],
246
        );
247
248
0
        Ok(())
249
0
    }
250
}
251
252
impl ImageInfo {
253
    // The buffer can be larger for CMYK than the RGB output
254
0
    fn total_bytes_buffer(&self) -> u64 {
255
0
        let (width, height) = self.dimensions;
256
0
        let total_pixels = u64::from(width) * u64::from(height);
257
258
0
        let bytes_per_pixel = match self.original_color_type {
259
0
            ExtendedColorType::Cmyk8 => 4,
260
0
            ExtendedColorType::Cmyk16 => 8,
261
0
            _ => u64::from(self.color_type.bytes_per_pixel()),
262
        };
263
264
0
        total_pixels.saturating_mul(bytes_per_pixel)
265
0
    }
266
}
267
268
0
fn check_ycbcr_subsampling<R: BufRead + Seek>(decoder: &mut Decoder<R>) -> ImageResult<()> {
269
0
    let compression = decoder
270
0
        .find_tag(Tag::Compression)
271
0
        .map_err(ImageError::from_tiff_decode)?
272
0
        .and_then(|v| v.into_u16().ok());
273
274
    const COMPRESSION_MODERN_JPEG: u16 = 7;
275
0
    if compression == Some(COMPRESSION_MODERN_JPEG) {
276
0
        return Ok(());
277
0
    }
278
279
0
    let subsampling = decoder
280
0
        .find_tag(TAG_YCBCR_SUBSAMPLING)
281
0
        .map_err(ImageError::from_tiff_decode)?
282
0
        .map(|value| value.into_u16_vec())
283
0
        .transpose()
284
0
        .map_err(ImageError::from_tiff_decode)?;
285
286
0
    let subsampling = subsampling.as_deref().unwrap_or(&[2, 2]);
287
288
0
    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
0
    }
299
300
0
    Ok(())
301
0
}
302
303
0
fn read_ycbcr_coefficients<R: BufRead + Seek>(decoder: &mut Decoder<R>) -> ImageResult<[f32; 3]> {
304
0
    let value = decoder
305
0
        .find_tag(TAG_YCBCR_COEFFICIENTS)
306
0
        .map_err(ImageError::from_tiff_decode)?;
307
308
    const DEFAULT_YCBCR_COEFFICIENTS: [f32; 3] = [0.299, 0.587, 0.114];
309
0
    let Some(value) = value else {
310
0
        return Ok(DEFAULT_YCBCR_COEFFICIENTS);
311
    };
312
313
0
    let list = match value {
314
0
        Value::List(list) if list.len() == 3 => list,
315
        _ => {
316
0
            return Err(ImageError::Decoding(DecodingError::new(
317
0
                ImageFormat::Tiff.into(),
318
0
                "YCbCrCoefficients tag (529) must contain exactly 3 rational values".to_string(),
319
0
            )));
320
        }
321
    };
322
323
0
    let mut coefficients = [0.0f32; 3];
324
0
    for (i, value) in list.iter().enumerate() {
325
0
        match value {
326
0
            Value::Rational(num, denom) if *denom != 0 => {
327
0
                coefficients[i] = *num as f32 / *denom as f32
328
            }
329
            _ => {
330
0
                return Err(ImageError::Decoding(DecodingError::new(
331
0
                    ImageFormat::Tiff.into(),
332
0
                    "YCbCrCoefficients tag (529) contains an invalid rational value".to_string(),
333
0
                )));
334
            }
335
        }
336
    }
337
338
0
    Ok(coefficients)
339
0
}
340
341
impl ImageError {
342
0
    fn from_tiff_decode(err: tiff::TiffError) -> ImageError {
343
0
        match err {
344
0
            tiff::TiffError::IoError(err) => ImageError::IoError(err),
345
0
            err @ (tiff::TiffError::FormatError(_)
346
            | tiff::TiffError::IntSizeError
347
            | tiff::TiffError::UsageError(_)) => {
348
0
                ImageError::Decoding(DecodingError::new(ImageFormat::Tiff.into(), err))
349
            }
350
0
            tiff::TiffError::UnsupportedError(desc) => {
351
0
                ImageError::Unsupported(UnsupportedError::from_format_and_kind(
352
0
                    ImageFormat::Tiff.into(),
353
0
                    UnsupportedErrorKind::GenericFeature(desc.to_string()),
354
0
                ))
355
            }
356
            tiff::TiffError::LimitsExceeded => {
357
0
                ImageError::Limits(LimitError::from_kind(LimitErrorKind::InsufficientMemory))
358
            }
359
        }
360
0
    }
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
0
    fn format_attributes(&self) -> FormatAttributes {
385
0
        FormatAttributes {
386
0
            // is any sort of iTXT chunk.
387
0
            xmp: DecodedMetadataHint::PerImage,
388
0
            icc: DecodedMetadataHint::PerImage,
389
0
            exif: DecodedMetadataHint::PerImage,
390
0
            // not provided above.
391
0
            iptc: DecodedMetadataHint::Unsupported,
392
0
            supports_sequence: true,
393
0
            ..FormatAttributes::default()
394
0
        }
395
0
    }
396
397
0
    fn prepare_image(&mut self) -> ImageResult<DecoderPreparedImage> {
398
0
        let info = self.peek_info()?;
399
0
        let (width, height) = info.dimensions;
400
401
0
        Ok(DecoderPreparedImage::new(width, height, info.color_type))
402
0
    }
403
404
0
    fn icc_profile(&mut self) -> ImageResult<Option<Vec<u8>>> {
405
0
        if let Some(decoder) = &mut self.inner {
406
0
            Ok(decoder.get_tag_u8_vec(Tag::IccProfile).ok())
407
        } else {
408
0
            Ok(None)
409
        }
410
0
    }
411
412
0
    fn xmp_metadata(&mut self) -> ImageResult<Option<Vec<u8>>> {
413
0
        let Some(decoder) = &mut self.inner else {
414
0
            return Ok(None);
415
        };
416
417
0
        let value = match decoder.get_tag(TAG_XML_PACKET) {
418
0
            Ok(value) => value,
419
            Err(tiff::TiffError::FormatError(tiff::TiffFormatError::RequiredTagNotFound(_))) => {
420
0
                return Ok(None);
421
            }
422
0
            Err(err) => return Err(ImageError::from_tiff_decode(err)),
423
        };
424
0
        value
425
0
            .into_u8_vec()
426
0
            .map(Some)
427
0
            .map_err(ImageError::from_tiff_decode)
428
0
    }
429
430
0
    fn set_limits(&mut self, limits: crate::Limits) -> ImageResult<()> {
431
0
        limits.check_support(&crate::LimitSupport::default())?;
432
433
0
        let reserved = match self.info {
434
0
            ImageState::At(info) => info,
435
            // Construct a dummy info that did not consume any memory.
436
0
            _ => ImageInfo {
437
0
                dimensions: (0, 0),
438
0
                color_type: ColorType::L8,
439
0
                original_color_type: ExtendedColorType::L8,
440
0
                ycbcr_coefficients: [0.0; 3],
441
0
            },
442
        };
443
444
0
        let (width, height) = reserved.dimensions;
445
0
        limits.check_dimensions(width, height)?;
446
447
0
        let max_alloc = limits
448
0
            .max_alloc
449
0
            .and_then(|n| usize::try_from(n).ok())
450
0
            .unwrap_or(usize::MAX);
451
452
0
        self.limits.decoding_buffer_size = max_alloc;
453
0
        self.limits.intermediate_buffer_size = 0;
454
0
        self.redistribute_limits();
455
456
0
        Ok(())
457
0
    }
458
459
0
    fn read_image(&mut self, buf: &mut [u8]) -> ImageResult<DecodedImageAttributes> {
460
0
        let info = self.peek_info()?;
461
0
        let layout = self.prepare_image()?;
462
463
0
        let original_color_type = Some(info.original_color_type);
464
0
        assert_eq!(u64::try_from(buf.len()), Ok(layout.total_bytes()));
465
466
0
        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
0
        let layout = reader
473
0
            .read_image_to_buffer(&mut self.buffer)
474
0
            .map_err(ImageError::from_tiff_decode)?;
475
476
        // Check if we have all of the planes. Otherwise we ran into the allocation limit.
477
0
        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
0
        }
482
483
0
        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
0
            self.interleave_planes(info, layout, buf)?;
487
0
            return Ok(DecodedImageAttributes::default());
488
0
        }
489
490
0
        match &self.buffer {
491
0
            DecodingResult::U8(v) if info.original_color_type == ExtendedColorType::Cmyk8 => {
492
0
                let buf = buf.as_chunks_mut::<3>().0;
493
0
                for (cmyk, rgb) in v.as_chunks::<4>().0.iter().zip(buf) {
494
0
                    *rgb = cmyk_to_rgb(cmyk);
495
0
                }
496
            }
497
0
            DecodingResult::U16(v) if info.original_color_type == ExtendedColorType::Cmyk16 => {
498
0
                let buf = buf.as_chunks_mut::<6>().0;
499
0
                for (cmyk, rgb) in v.as_chunks::<4>().0.iter().zip(buf) {
500
0
                    *rgb = bytemuck::cast(cmyk_to_rgb16(cmyk));
501
0
                }
502
            }
503
0
            DecodingResult::U8(v) if info.original_color_type == ExtendedColorType::L1 => {
504
0
                let width = info.dimensions.0;
505
0
                let row_bytes = width.div_ceil(8);
506
507
0
                for (in_row, out_row) in v
508
0
                    .chunks_exact(row_bytes as usize)
509
0
                    .zip(buf.chunks_exact_mut(width as usize))
510
0
                {
511
0
                    out_row.copy_from_slice(&utils::expand_bits(1, width, in_row));
512
0
                }
513
            }
514
0
            DecodingResult::U8(v) if info.original_color_type == ExtendedColorType::YCbCr8 => {
515
0
                let [lr, lg, lb] = info.ycbcr_coefficients;
516
0
                let ycbcr = v.as_chunks::<3>().0;
517
0
                let out = buf.as_chunks_mut::<3>().0;
518
0
519
0
                ycbcr_to_rgb8(ycbcr, lr, lg, lb, out);
520
0
            }
521
0
            DecodingResult::U8(v) => {
522
0
                buf.copy_from_slice(v);
523
0
            }
524
0
            DecodingResult::U16(v) => {
525
0
                buf.copy_from_slice(bytemuck::cast_slice(v));
526
0
            }
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
0
            DecodingResult::F32(v) => {
546
0
                buf.copy_from_slice(bytemuck::cast_slice(v));
547
0
            }
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
0
        let orientation = reader
555
0
            .find_tag(Tag::Orientation)
556
0
            .map_err(ImageError::from_tiff_decode)?
557
0
            .and_then(|v| Orientation::from_exif(v.into_u16().ok()?.min(255) as u8));
558
559
        // Indicate to advance.
560
0
        self.info = ImageState::Consumed;
561
562
0
        Ok(DecodedImageAttributes {
563
0
            orientation,
564
0
            original_color_type,
565
0
            ..DecodedImageAttributes::default()
566
0
        })
567
0
    }
568
569
0
    fn exif_metadata(&mut self) -> ImageResult<Option<Vec<u8>>> {
570
0
        Ok(None)
571
0
    }
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
0
fn ycbcr_to_rgb8(ycbcr: &[[u8; 3]], lr: f32, lg: f32, lb: f32, out: &mut [[u8; 3]]) {
597
0
    let coeff_r = 2.0 * (1.0 - lr);
598
0
    let coeff_b = 2.0 * (1.0 - lb);
599
0
    let inv_lg = 1.0 / lg;
600
601
0
    for (src, dst) in ycbcr.iter().zip(out.iter_mut()) {
602
0
        let y = f32::from(src[0]);
603
0
        let cb = f32::from(src[1]) - 128.0;
604
0
        let cr = f32::from(src[2]) - 128.0;
605
0
606
0
        let r = y + cr * coeff_r;
607
0
        let b = y + cb * coeff_b;
608
0
        let g = (y - lr * r - lb * b) * inv_lg;
609
0
610
0
        dst[0] = (r + 0.5) as u8;
611
0
        dst[1] = (g + 0.5) as u8;
612
0
        dst[2] = (b + 0.5) as u8;
613
0
    }
614
0
}
615
616
0
fn cmyk_to_rgb(cmyk: &[u8; 4]) -> [u8; 3] {
617
0
    let c = cmyk[0] as u32;
618
0
    let m = cmyk[1] as u32;
619
0
    let y = cmyk[2] as u32;
620
0
    let k = cmyk[3] as u32;
621
622
0
    let k_inv = 255 - k;
623
0
    [
624
0
        (((255 - c) * k_inv) / 255) as u8,
625
0
        (((255 - m) * k_inv) / 255) as u8,
626
0
        (((255 - y) * k_inv) / 255) as u8,
627
0
    ]
628
0
}
629
630
0
fn cmyk_to_rgb16(cmyk: &[u16; 4]) -> [u16; 3] {
631
0
    let c = cmyk[0] as u64;
632
0
    let m = cmyk[1] as u64;
633
0
    let y = cmyk[2] as u64;
634
0
    let k = cmyk[3] as u64;
635
636
0
    let k_inv = 65535 - k;
637
0
    [
638
0
        (((65535 - c) * k_inv) / 65535) as u16,
639
0
        (((65535 - m) * k_inv) / 65535) as u16,
640
0
        (((65535 - y) * k_inv) / 65535) as u16,
641
0
    ]
642
0
}
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
}