From 139e809282053f736cb039229e844b7478605c55 Mon Sep 17 00:00:00 2001 From: Sophie Herold Date: Mon, 25 Dec 2023 00:22:41 +0100 Subject: [PATCH] tiff: Support CMYK images (#2075) --- src/codecs/tiff.rs | 50 ++++++++++++++++-- src/color.rs | 6 ++- .../images/tiff/testsuite/hpredict_cmyk.tiff | Bin 0 -> 2668 bytes 3 files changed, 52 insertions(+), 4 deletions(-) create mode 100644 tests/images/tiff/testsuite/hpredict_cmyk.tiff diff --git a/src/codecs/tiff.rs b/src/codecs/tiff.rs index 7ee05e1047..6ad356230a 100644 --- a/src/codecs/tiff.rs +++ b/src/codecs/tiff.rs @@ -28,6 +28,7 @@ where { dimensions: (u32, u32), color_type: ColorType, + original_color_type: ExtendedColorType, // We only use an Option here so we can call with_limits on the decoder without moving. inner: Option>, @@ -42,7 +43,7 @@ where let mut inner = tiff::decoder::Decoder::new(r).map_err(ImageError::from_tiff_decode)?; let dimensions = inner.dimensions().map_err(ImageError::from_tiff_decode)?; - let color_type = inner.colortype().map_err(ImageError::from_tiff_decode)?; + let tiff_color_type = inner.colortype().map_err(ImageError::from_tiff_decode)?; match inner.find_tag_unsigned_vec::(tiff::tags::Tag::SampleFormat) { Ok(Some(sample_formats)) => { for format in sample_formats { @@ -53,7 +54,7 @@ where Err(other) => return Err(ImageError::from_tiff_decode(other)), }; - let color_type = match color_type { + let color_type = match tiff_color_type { tiff::ColorType::Gray(8) => ColorType::L8, tiff::ColorType::Gray(16) => ColorType::L16, tiff::ColorType::GrayA(8) => ColorType::La8, @@ -62,6 +63,7 @@ where tiff::ColorType::RGB(16) => ColorType::Rgb16, tiff::ColorType::RGBA(8) => ColorType::Rgba8, tiff::ColorType::RGBA(16) => ColorType::Rgba16, + tiff::ColorType::CMYK(8) => ColorType::Rgb8, tiff::ColorType::Palette(n) | tiff::ColorType::Gray(n) => { return Err(err_unknown_color_type(n)) @@ -74,12 +76,30 @@ where } }; + let original_color_type = match tiff_color_type { + tiff::ColorType::CMYK(8) => ExtendedColorType::Cmyk8, + _ => color_type.into(), + }; + Ok(TiffDecoder { dimensions, color_type, + original_color_type, inner: Some(inner), }) } + + // The buffer can be larger for CMYK than the RGB output + fn total_bytes_buffer(&self) -> u64 { + let dimensions = self.dimensions(); + let total_pixels = u64::from(dimensions.0) * u64::from(dimensions.1); + let bytes_per_pixel = if self.original_color_type == ExtendedColorType::Cmyk8 { + 16 + } else { + u64::from(self.color_type().bytes_per_pixel()) + }; + total_pixels.saturating_mul(bytes_per_pixel) + } } fn check_sample_format(sample_format: u16) -> Result<(), ImageError> { @@ -176,6 +196,10 @@ impl<'a, R: 'a + Read + Seek> ImageDecoder<'a> for TiffDecoder { self.color_type } + fn original_color_type(&self) -> ExtendedColorType { + self.original_color_type + } + fn icc_profile(&mut self) -> Option> { if let Some(decoder) = &mut self.inner { decoder.get_tag_u8_vec(tiff::tags::Tag::Unknown(34675)).ok() @@ -191,7 +215,7 @@ impl<'a, R: 'a + Read + Seek> ImageDecoder<'a> for TiffDecoder { limits.check_dimensions(width, height)?; let max_alloc = limits.max_alloc.unwrap_or(u64::MAX); - let max_intermediate_alloc = max_alloc.saturating_sub(self.total_bytes()); + let max_intermediate_alloc = max_alloc.saturating_sub(self.total_bytes_buffer()); let mut tiff_limits: tiff::decoder::Limits = Default::default(); tiff_limits.decoding_buffer_size = @@ -234,6 +258,14 @@ impl<'a, R: 'a + Read + Seek> ImageDecoder<'a> for TiffDecoder { .read_image() .map_err(ImageError::from_tiff_decode)? { + tiff::decoder::DecodingResult::U8(v) + if self.original_color_type == ExtendedColorType::Cmyk8 => + { + let mut out_cur = Cursor::new(buf); + for cmyk in v.chunks_exact(4) { + out_cur.write_all(&cmyk_to_rgb(cmyk))?; + } + } tiff::decoder::DecodingResult::U8(v) => { buf.copy_from_slice(&v); } @@ -274,6 +306,18 @@ pub struct TiffEncoder { w: W, } +fn cmyk_to_rgb(cmyk: &[u8]) -> [u8; 3] { + let c = cmyk[0] as f32; + let m = cmyk[1] as f32; + let y = cmyk[2] as f32; + let kf = 1. - cmyk[3] as f32 / 255.; + [ + ((255. - c) * kf) as u8, + ((255. - m) * kf) as u8, + ((255. - y) * kf) as u8, + ] +} + // Utility to simplify and deduplicate error handling during 16-bit encoding. fn u8_slice_as_u16(buf: &[u8]) -> ImageResult<&[u16]> { bytemuck::try_cast_slice(buf).map_err(|err| { diff --git a/src/color.rs b/src/color.rs index 5ec6a4677b..47f7fee3e4 100644 --- a/src/color.rs +++ b/src/color.rs @@ -142,6 +142,9 @@ pub enum ExtendedColorType { /// Pixel is 32-bit float RGBA Rgba32F, + /// Pixel is 8-bit CMYK + Cmyk8, + /// Pixel is of unknown color type with the specified bits per pixel. This can apply to pixels /// which are associated with an external palette. In that case, the pixel value is an index /// into the palette. @@ -180,7 +183,8 @@ impl ExtendedColorType { | ExtendedColorType::Rgba8 | ExtendedColorType::Rgba16 | ExtendedColorType::Rgba32F - | ExtendedColorType::Bgra8 => 4, + | ExtendedColorType::Bgra8 + | ExtendedColorType::Cmyk8 => 4, } } } diff --git a/tests/images/tiff/testsuite/hpredict_cmyk.tiff b/tests/images/tiff/testsuite/hpredict_cmyk.tiff new file mode 100644 index 0000000000000000000000000000000000000000..412072e9d35fbc5dbd324e719cebee41f1f00af8 GIT binary patch literal 2668 zcmYk82T)UK7lywSl8{gm2#5hf0O?54P=bJ=hzSzFfZe>Kgw(bwA;6DHIaVVo`?(FS4X4xh^8`0` zRmDApekcK2?b5@{w@?&|xT$rI$~r_#+b?3Dr|1>%;BC#zOFFX{Xe-O~Yr$Wp@~p0L zF4x;>RKxxZ{1_J$oD+sNvWQ;*?3p(>;|s~PHmQ}pl@QAHDzZyG($sk@GF!doc_hC( z!rUTQaZV{5*g@*1zUo;OJ#S4S5Vv*h0%^fh-}3^yf$ZDX`)cxPmYm&n=@=8JDprul~H&c>f@lgTDo z_O}OYZ*_dYJFbwXZmWohT%XTy3bGGdqET~78m5QBvie$PFX_$!<#FO zR9v@s{H=_^U4Pn}8z)Zov59Ui@H5uaxEVm&-YO>|nhI0KulY%zhdrKo_d}z8N=TdE zvT?)dG`-f8vG{lzd4cU=4#n896ldwSMxWG{?MMUovV>x4Vw*WYRLGQJ=f$=6aWQ98qb}L1&|F;*4jfyu| z97R%)h0U(J&12zb0mtD(5I}Zz1I(kd)V`8iIt-#u%?LfD{_>|7^%@KR=*Sw-A{#~G zw#&wln&RP@1Jh=ka zX?uY_q&JkI``exoTK_A7>aT|b6C#84nS-RZE7;J)Ul%*k6xEfu(}JLM4cbhvi(W@F zN-HjIzdr8cI~LF>f3kFk2eytgbZyd()+4PlwikBU3%|Wob(=3@Q1ba`o|Pc}N3ePP z`esC*afw=MXvXRN7n?F^%NJ`u3-XVC)Aj#)_PPK)0Kim;^V0|vT<%Zlv3DjLdWjZ& zfKP`jl0W!2IMmS*s?Z7Y9M|hgyRkze%1f_fUkM8`C|P0YIBWotIeGO2avBmXUO$=X z2X}i${hCyPI&a;fB+mj+KQ0u(Jx-h$T=Fy_p$!1CR)MhrukGo{XKi*5kQ>^k?Hs{> zc=r?orOyUf9+34r=kxMtk{1Vu>a<`$vgd4kGgP3;W#LkoSlMLet;g%U&nl_#Mgf*G z@a6<0eMm)3#_g0!_EnEDtxfqbD$|3UhbdNR6lR2VNY^_>F9Q&LBU= z%r|gxU*=@tgB*fex{@F&$R8h3d!+sW06mS+#V9BXSfa4bBsZQa)(AR02LSBz958k9 zI-;Ve2Nb^-+&-oCE^V`nV<_daIm%JocSGvtIKA=q;`hv)UbnRdOeLtdhkq4LZnarpE_ob z;;I$yT;D<2l{9Fa9UDkVF>VfkrG0c-w;4jH-+h)y6fI=Y^cQW*?=NwLJrtUF($Xn; zDNWd8;Q`bE>=GN=L;!jQ*8(5s%Ia_-uJyzw_sLvteyF?e3$JHp%4RpRKMmJqzu(l@ zEl6eh#Z*<}s_yeMI9h&a+jM=gr4xUN)8TIuXlQBzj4B0Hjz^;*)6;6_^YMS>xSkM_9vT>BK?J)m zmU3X)l>Kbat5wKaY8e==0! zHIVSCN;a1K=lr|>YHTm@uBxxZyH&yn2^&k;ZB3tDYw9Jh0ZnpN_y12(;J@d>>`qCZ p1v>uZNfbbPpSrCofhpD{GO{m<1r){4MgRjAQL$w4@Cd~ literal 0 HcmV?d00001