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