/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 | | } |