Skip to content

Commit

Permalink
tiff: Support CMYK images (#2075)
Browse files Browse the repository at this point in the history
  • Loading branch information
sophie-h authored Dec 24, 2023
1 parent d970bf7 commit 139e809
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 4 deletions.
50 changes: 47 additions & 3 deletions src/codecs/tiff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<tiff::decoder::Decoder<R>>,
Expand All @@ -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::<u16>(tiff::tags::Tag::SampleFormat) {
Ok(Some(sample_formats)) => {
for format in sample_formats {
Expand All @@ -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,
Expand All @@ -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))
Expand All @@ -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> {
Expand Down Expand Up @@ -176,6 +196,10 @@ impl<'a, R: 'a + Read + Seek> ImageDecoder<'a> for TiffDecoder<R> {
self.color_type
}

fn original_color_type(&self) -> ExtendedColorType {
self.original_color_type
}

fn icc_profile(&mut self) -> Option<Vec<u8>> {
if let Some(decoder) = &mut self.inner {
decoder.get_tag_u8_vec(tiff::tags::Tag::Unknown(34675)).ok()
Expand All @@ -191,7 +215,7 @@ impl<'a, R: 'a + Read + Seek> ImageDecoder<'a> for TiffDecoder<R> {
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 =
Expand Down Expand Up @@ -234,6 +258,14 @@ impl<'a, R: 'a + Read + Seek> ImageDecoder<'a> for TiffDecoder<R> {
.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);
}
Expand Down Expand Up @@ -274,6 +306,18 @@ pub struct TiffEncoder<W> {
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| {
Expand Down
6 changes: 5 additions & 1 deletion src/color.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -180,7 +183,8 @@ impl ExtendedColorType {
| ExtendedColorType::Rgba8
| ExtendedColorType::Rgba16
| ExtendedColorType::Rgba32F
| ExtendedColorType::Bgra8 => 4,
| ExtendedColorType::Bgra8
| ExtendedColorType::Cmyk8 => 4,
}
}
}
Expand Down
Binary file added tests/images/tiff/testsuite/hpredict_cmyk.tiff
Binary file not shown.

0 comments on commit 139e809

Please sign in to comment.