Skip to content

Commit

Permalink
tiff: Support CMYK images
Browse files Browse the repository at this point in the history
  • Loading branch information
sophie-h committed Dec 18, 2023
1 parent d970bf7 commit ce4f11c
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 10 deletions.
51 changes: 43 additions & 8 deletions src/codecs/tiff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ use std::io::{self, Cursor, Read, Seek, Write};
use std::marker::PhantomData;
use std::mem;

use crate::color::{ColorType, ExtendedColorType};
use crate::color::{Cmyk, ColorType, ExtendedColorType, IntoColor};
use crate::error::{
DecodingError, EncodingError, ImageError, ImageResult, LimitError, LimitErrorKind,
ParameterError, ParameterErrorKind, UnsupportedError, UnsupportedErrorKind,
};
use crate::image::{ImageDecoder, ImageEncoder, ImageFormat};
use crate::utils;
use crate::{utils, Rgb};

/// Decoder for TIFF images.
pub struct TiffDecoder<R>
Expand All @@ -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,29 @@ 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),
})
}

fn total_buffer_bytes(&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 +195,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 +214,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_buffer_bytes());

let mut tiff_limits: tiff::decoder::Limits = Default::default();
tiff_limits.decoding_buffer_size =
Expand Down Expand Up @@ -228,12 +251,24 @@ impl<'a, R: 'a + Read + Seek> ImageDecoder<'a> for TiffDecoder<R> {

fn read_image(self, buf: &mut [u8]) -> ImageResult<()> {
assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes()));
match self
let r = self
.inner
.unwrap()
.read_image()
.map_err(ImageError::from_tiff_decode)?
{
.map_err(ImageError::from_tiff_decode)?;

match r {
tiff::decoder::DecodingResult::U8(v)
if self.original_color_type == ExtendedColorType::Cmyk8 =>
{
let mut in_cur = Cursor::new(v);
let mut out_cur = Cursor::new(buf);
let mut cmyk = [0_u8; 4];
while in_cur.read_exact(&mut cmyk).is_ok() {
let rgb: Rgb<u8> = Cmyk(cmyk).into_color();
out_cur.write_all(&rgb.0)?;
}
}
tiff::decoder::DecodingResult::U8(v) => {
buf.copy_from_slice(&v);
}
Expand Down
71 changes: 70 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 Expand Up @@ -367,6 +371,8 @@ define_colors! {
pub struct Rgba<T: Primitive Enlargeable>([T; 4, 1]) = "RGBA";
/// Grayscale colors + alpha channel
pub struct LumaA<T: Primitive>([T; 2, 1]) = "YA";
/// CMYK
pub struct Cmyk<T: Primitive>([T; 4, 1]) = "CMYK";
}

/// Convert from one pixel component type to another. For example, convert from `u8` to `f32` pixel values.
Expand Down Expand Up @@ -682,6 +688,57 @@ where
}
}

impl<S: Primitive, T: Primitive> FromColor<Cmyk<S>> for Rgb<T>
where
T: FromPrimitive<S>,
{
fn from_color(&mut self, other: &Cmyk<S>) {
let [r, g, b] = &mut self.0;

(*r, *g, *b) = cmyk_to_rgb(other.0);
}
}

fn cmyk_to_rgb<S: Primitive, T: Primitive>([c, m, y, k]: [S; 4]) -> (T, T, T) {
let max = S::max_value().into_f32();
let c = c.into_f32();
let m = m.into_f32();
let y = y.into_f32();
let kf = 1. - k.into_f32() / max;
(
T::from((max - c) * kf).unwrap(),
T::from((max - m) * kf).unwrap(),
T::from((max - y) * kf).unwrap(),
)
}

impl<S: Primitive, T: Primitive> FromColor<Cmyk<S>> for Rgba<T>
where
T: FromPrimitive<S>,
{
fn from_color(&mut self, other: &Cmyk<S>) {
todo!()
}
}

impl<S: Primitive, T: Primitive> FromColor<Cmyk<S>> for Luma<T>
where
T: FromPrimitive<S>,
{
fn from_color(&mut self, other: &Cmyk<S>) {
todo!()
}
}

impl<S: Primitive, T: Primitive> FromColor<Cmyk<S>> for LumaA<T>
where
T: FromPrimitive<S>,
{
fn from_color(&mut self, other: &Cmyk<S>) {
todo!()
}
}

/// Blends a color inter another one
pub(crate) trait Blend {
/// Blends a color in-place.
Expand Down Expand Up @@ -797,6 +854,12 @@ impl<T: Primitive> Blend for Rgb<T> {
}
}

impl<T: Primitive> Blend for Cmyk<T> {
fn blend(&mut self, other: &Cmyk<T>) {
*self = *other
}
}

/// Invert a color
pub(crate) trait Invert {
/// Inverts a color in-place.
Expand Down Expand Up @@ -847,6 +910,12 @@ impl<T: Primitive> Invert for Rgb<T> {
}
}

impl<T: Primitive> Invert for Cmyk<T> {
fn invert(&mut self) {
todo!()
}
}

#[cfg(test)]
mod tests {
use super::{Luma, LumaA, Pixel, Rgb, Rgba};
Expand Down
12 changes: 11 additions & 1 deletion src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ impl EncodableLayout for [f32] {

/// The type of each channel in a pixel. For example, this can be `u8`, `u16`, `f32`.
// TODO rename to `PixelComponent`? Split up into separate traits? Seal?
pub trait Primitive: Copy + NumCast + Num + PartialOrd<Self> + Clone + Bounded {
pub trait Primitive: Copy + NumCast + Num + PartialOrd<Self> + Clone + Bounded + IntoF32 {
/// The maximum value for this type of primitive within the context of color.
/// For floats, the maximum is `1.0`, whereas the integer types inherit their usual maximum values.
const DEFAULT_MAX_VALUE: Self;
Expand All @@ -44,12 +44,22 @@ pub trait Primitive: Copy + NumCast + Num + PartialOrd<Self> + Clone + Bounded {
const DEFAULT_MIN_VALUE: Self;
}

pub trait IntoF32 {
fn into_f32(self) -> f32;
}

macro_rules! declare_primitive {
($base:ty: ($from:expr)..$to:expr) => {
impl Primitive for $base {
const DEFAULT_MAX_VALUE: Self = $to;
const DEFAULT_MIN_VALUE: Self = $from;
}

impl IntoF32 for $base {
fn into_f32(self) -> f32 {
self as f32
}
}
};
}

Expand Down

0 comments on commit ce4f11c

Please sign in to comment.