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