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, 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::utils; |
22 | |
23 | /// Decoder for TIFF images. |
24 | pub struct TiffDecoder<R> |
25 | where |
26 | R: Read + 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: Read + 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 {:?}" , |
112 | other |
113 | )), |
114 | ), |
115 | )), |
116 | None => Err(ImageError::Decoding(DecodingError::from_format_hint( |
117 | format:ImageFormat::Tiff.into(), |
118 | ))), |
119 | } |
120 | } |
121 | |
122 | fn err_unknown_color_type(value: u8) -> ImageError { |
123 | ImageError::Unsupported(UnsupportedError::from_format_and_kind( |
124 | format:ImageFormat::Tiff.into(), |
125 | kind:UnsupportedErrorKind::Color(ExtendedColorType::Unknown(value)), |
126 | )) |
127 | } |
128 | |
129 | impl ImageError { |
130 | fn from_tiff_decode(err: tiff::TiffError) -> ImageError { |
131 | match err { |
132 | tiff::TiffError::IoError(err) => ImageError::IoError(err), |
133 | err @ tiff::TiffError::FormatError(_) |
134 | | err @ tiff::TiffError::IntSizeError |
135 | | err @ tiff::TiffError::UsageError(_) => { |
136 | ImageError::Decoding(DecodingError::new(ImageFormat::Tiff.into(), err)) |
137 | } |
138 | tiff::TiffError::UnsupportedError(desc) => { |
139 | ImageError::Unsupported(UnsupportedError::from_format_and_kind( |
140 | ImageFormat::Tiff.into(), |
141 | UnsupportedErrorKind::GenericFeature(desc.to_string()), |
142 | )) |
143 | } |
144 | tiff::TiffError::LimitsExceeded => { |
145 | ImageError::Limits(LimitError::from_kind(LimitErrorKind::InsufficientMemory)) |
146 | } |
147 | } |
148 | } |
149 | |
150 | fn from_tiff_encode(err: tiff::TiffError) -> ImageError { |
151 | match err { |
152 | tiff::TiffError::IoError(err) => ImageError::IoError(err), |
153 | err @ tiff::TiffError::FormatError(_) |
154 | | err @ tiff::TiffError::IntSizeError |
155 | | err @ tiff::TiffError::UsageError(_) => { |
156 | ImageError::Encoding(EncodingError::new(ImageFormat::Tiff.into(), err)) |
157 | } |
158 | tiff::TiffError::UnsupportedError(desc) => { |
159 | ImageError::Unsupported(UnsupportedError::from_format_and_kind( |
160 | ImageFormat::Tiff.into(), |
161 | UnsupportedErrorKind::GenericFeature(desc.to_string()), |
162 | )) |
163 | } |
164 | tiff::TiffError::LimitsExceeded => { |
165 | ImageError::Limits(LimitError::from_kind(LimitErrorKind::InsufficientMemory)) |
166 | } |
167 | } |
168 | } |
169 | } |
170 | |
171 | /// Wrapper struct around a `Cursor<Vec<u8>>` |
172 | pub struct TiffReader<R>(Cursor<Vec<u8>>, PhantomData<R>); |
173 | impl<R> Read for TiffReader<R> { |
174 | fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { |
175 | self.0.read(buf) |
176 | } |
177 | fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> { |
178 | if self.0.position() == 0 && buf.is_empty() { |
179 | mem::swap(x:buf, self.0.get_mut()); |
180 | Ok(buf.len()) |
181 | } else { |
182 | self.0.read_to_end(buf) |
183 | } |
184 | } |
185 | } |
186 | |
187 | impl<'a, R: 'a + Read + Seek> ImageDecoder<'a> for TiffDecoder<R> { |
188 | type Reader = TiffReader<R>; |
189 | |
190 | fn dimensions(&self) -> (u32, u32) { |
191 | self.dimensions |
192 | } |
193 | |
194 | fn color_type(&self) -> ColorType { |
195 | self.color_type |
196 | } |
197 | |
198 | fn original_color_type(&self) -> ExtendedColorType { |
199 | self.original_color_type |
200 | } |
201 | |
202 | fn icc_profile(&mut self) -> Option<Vec<u8>> { |
203 | if let Some(decoder) = &mut self.inner { |
204 | decoder.get_tag_u8_vec(tiff::tags::Tag::Unknown(34675)).ok() |
205 | } else { |
206 | None |
207 | } |
208 | } |
209 | |
210 | fn set_limits(&mut self, limits: crate::io::Limits) -> ImageResult<()> { |
211 | limits.check_support(&crate::io::LimitSupport::default())?; |
212 | |
213 | let (width, height) = self.dimensions(); |
214 | limits.check_dimensions(width, height)?; |
215 | |
216 | let max_alloc = limits.max_alloc.unwrap_or(u64::MAX); |
217 | let max_intermediate_alloc = max_alloc.saturating_sub(self.total_bytes_buffer()); |
218 | |
219 | let mut tiff_limits: tiff::decoder::Limits = Default::default(); |
220 | tiff_limits.decoding_buffer_size = |
221 | usize::try_from(max_alloc - max_intermediate_alloc).unwrap_or(usize::MAX); |
222 | tiff_limits.intermediate_buffer_size = |
223 | usize::try_from(max_intermediate_alloc).unwrap_or(usize::MAX); |
224 | tiff_limits.ifd_value_size = tiff_limits.intermediate_buffer_size; |
225 | self.inner = Some(self.inner.take().unwrap().with_limits(tiff_limits)); |
226 | |
227 | Ok(()) |
228 | } |
229 | |
230 | fn into_reader(self) -> ImageResult<Self::Reader> { |
231 | let buf = match self |
232 | .inner |
233 | .unwrap() |
234 | .read_image() |
235 | .map_err(ImageError::from_tiff_decode)? |
236 | { |
237 | tiff::decoder::DecodingResult::U8(v) => v, |
238 | tiff::decoder::DecodingResult::U16(v) => utils::vec_copy_to_u8(&v), |
239 | tiff::decoder::DecodingResult::U32(v) => utils::vec_copy_to_u8(&v), |
240 | tiff::decoder::DecodingResult::U64(v) => utils::vec_copy_to_u8(&v), |
241 | tiff::decoder::DecodingResult::I8(v) => utils::vec_copy_to_u8(&v), |
242 | tiff::decoder::DecodingResult::I16(v) => utils::vec_copy_to_u8(&v), |
243 | tiff::decoder::DecodingResult::I32(v) => utils::vec_copy_to_u8(&v), |
244 | tiff::decoder::DecodingResult::I64(v) => utils::vec_copy_to_u8(&v), |
245 | tiff::decoder::DecodingResult::F32(v) => utils::vec_copy_to_u8(&v), |
246 | tiff::decoder::DecodingResult::F64(v) => utils::vec_copy_to_u8(&v), |
247 | }; |
248 | |
249 | Ok(TiffReader(Cursor::new(buf), PhantomData)) |
250 | } |
251 | |
252 | fn read_image(self, buf: &mut [u8]) -> ImageResult<()> { |
253 | assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes())); |
254 | match self |
255 | .inner |
256 | .unwrap() |
257 | .read_image() |
258 | .map_err(ImageError::from_tiff_decode)? |
259 | { |
260 | tiff::decoder::DecodingResult::U8(v) |
261 | if self.original_color_type == ExtendedColorType::Cmyk8 => |
262 | { |
263 | let mut out_cur = Cursor::new(buf); |
264 | for cmyk in v.chunks_exact(4) { |
265 | out_cur.write_all(&cmyk_to_rgb(cmyk))?; |
266 | } |
267 | } |
268 | tiff::decoder::DecodingResult::U8(v) => { |
269 | buf.copy_from_slice(&v); |
270 | } |
271 | tiff::decoder::DecodingResult::U16(v) => { |
272 | buf.copy_from_slice(bytemuck::cast_slice(&v)); |
273 | } |
274 | tiff::decoder::DecodingResult::U32(v) => { |
275 | buf.copy_from_slice(bytemuck::cast_slice(&v)); |
276 | } |
277 | tiff::decoder::DecodingResult::U64(v) => { |
278 | buf.copy_from_slice(bytemuck::cast_slice(&v)); |
279 | } |
280 | tiff::decoder::DecodingResult::I8(v) => { |
281 | buf.copy_from_slice(bytemuck::cast_slice(&v)); |
282 | } |
283 | tiff::decoder::DecodingResult::I16(v) => { |
284 | buf.copy_from_slice(bytemuck::cast_slice(&v)); |
285 | } |
286 | tiff::decoder::DecodingResult::I32(v) => { |
287 | buf.copy_from_slice(bytemuck::cast_slice(&v)); |
288 | } |
289 | tiff::decoder::DecodingResult::I64(v) => { |
290 | buf.copy_from_slice(bytemuck::cast_slice(&v)); |
291 | } |
292 | tiff::decoder::DecodingResult::F32(v) => { |
293 | buf.copy_from_slice(bytemuck::cast_slice(&v)); |
294 | } |
295 | tiff::decoder::DecodingResult::F64(v) => { |
296 | buf.copy_from_slice(bytemuck::cast_slice(&v)); |
297 | } |
298 | } |
299 | Ok(()) |
300 | } |
301 | } |
302 | |
303 | /// Encoder for tiff images |
304 | pub struct TiffEncoder<W> { |
305 | w: W, |
306 | } |
307 | |
308 | fn cmyk_to_rgb(cmyk: &[u8]) -> [u8; 3] { |
309 | let c: f32 = cmyk[0] as f32; |
310 | let m: f32 = cmyk[1] as f32; |
311 | let y: f32 = cmyk[2] as f32; |
312 | let kf: f32 = 1. - cmyk[3] as f32 / 255.; |
313 | [ |
314 | ((255. - c) * kf) as u8, |
315 | ((255. - m) * kf) as u8, |
316 | ((255. - y) * kf) as u8, |
317 | ] |
318 | } |
319 | |
320 | // Utility to simplify and deduplicate error handling during 16-bit encoding. |
321 | fn u8_slice_as_u16(buf: &[u8]) -> ImageResult<&[u16]> { |
322 | bytemuck::try_cast_slice(buf).map_err(|err: PodCastError| { |
323 | // If the buffer is not aligned or the correct length for a u16 slice, err. |
324 | // |
325 | // `bytemuck::PodCastError` of bytemuck-1.2.0 does not implement |
326 | // `Error` and `Display` trait. |
327 | // See <https://github.com/Lokathor/bytemuck/issues/22>. |
328 | ImageError::Parameter(ParameterError::from_kind(ParameterErrorKind::Generic( |
329 | format!(" {:?}" , err), |
330 | ))) |
331 | }) |
332 | } |
333 | |
334 | impl<W: Write + Seek> TiffEncoder<W> { |
335 | /// Create a new encoder that writes its output to `w` |
336 | pub fn new(w: W) -> TiffEncoder<W> { |
337 | TiffEncoder { w } |
338 | } |
339 | |
340 | /// Encodes the image `image` that has dimensions `width` and `height` and `ColorType` `c`. |
341 | /// |
342 | /// 16-bit types assume the buffer is native endian. |
343 | /// |
344 | /// # Panics |
345 | /// |
346 | /// Panics if `width * height * color_type.bytes_per_pixel() != data.len()`. |
347 | #[track_caller ] |
348 | pub fn encode(self, data: &[u8], width: u32, height: u32, color: ColorType) -> ImageResult<()> { |
349 | let expected_buffer_len = |
350 | (width as u64 * height as u64).saturating_mul(color.bytes_per_pixel() as u64); |
351 | assert_eq!( |
352 | expected_buffer_len, |
353 | data.len() as u64, |
354 | "Invalid buffer length: expected {expected_buffer_len} got {} for {width}x {height} image" , |
355 | data.len(), |
356 | ); |
357 | |
358 | let mut encoder = |
359 | tiff::encoder::TiffEncoder::new(self.w).map_err(ImageError::from_tiff_encode)?; |
360 | match color { |
361 | ColorType::L8 => { |
362 | encoder.write_image::<tiff::encoder::colortype::Gray8>(width, height, data) |
363 | } |
364 | ColorType::Rgb8 => { |
365 | encoder.write_image::<tiff::encoder::colortype::RGB8>(width, height, data) |
366 | } |
367 | ColorType::Rgba8 => { |
368 | encoder.write_image::<tiff::encoder::colortype::RGBA8>(width, height, data) |
369 | } |
370 | ColorType::L16 => encoder.write_image::<tiff::encoder::colortype::Gray16>( |
371 | width, |
372 | height, |
373 | u8_slice_as_u16(data)?, |
374 | ), |
375 | ColorType::Rgb16 => encoder.write_image::<tiff::encoder::colortype::RGB16>( |
376 | width, |
377 | height, |
378 | u8_slice_as_u16(data)?, |
379 | ), |
380 | ColorType::Rgba16 => encoder.write_image::<tiff::encoder::colortype::RGBA16>( |
381 | width, |
382 | height, |
383 | u8_slice_as_u16(data)?, |
384 | ), |
385 | _ => { |
386 | return Err(ImageError::Unsupported( |
387 | UnsupportedError::from_format_and_kind( |
388 | ImageFormat::Tiff.into(), |
389 | UnsupportedErrorKind::Color(color.into()), |
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: ColorType, |
408 | ) -> ImageResult<()> { |
409 | self.encode(data:buf, width, height, color_type) |
410 | } |
411 | } |
412 | |