From 2bc295ce65f08d5e9cf5f91b7e72ee49be8a8926 Mon Sep 17 00:00:00 2001 From: Wonwoo Choi Date: Tue, 29 Oct 2024 17:04:37 +0900 Subject: [PATCH] jxl-oxide: Fix CMYK to RGB conversion (#370) * jxl-oxide: Fix CMYK to RGB conversion * Update CHANGELOG.md --- CHANGELOG.md | 1 + crates/jxl-color/src/convert.rs | 10 ++++++++++ crates/jxl-color/src/icc/parse.rs | 4 ++++ crates/jxl-oxide/src/fb.rs | 25 ++++++++++++++----------- crates/jxl-oxide/src/lib.rs | 28 +++++++--------------------- 5 files changed, 36 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ba902db..d03e0a18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - `jxl-render`: Fix requested color encoding not applied in some cases (#369). +- `jxl-oxide`: Fix CMYK to RGB conversion (#370). ## [0.9.1] - 2024-10-12 diff --git a/crates/jxl-color/src/convert.rs b/crates/jxl-color/src/convert.rs index f9fd8bf7..e026424a 100644 --- a/crates/jxl-color/src/convert.rs +++ b/crates/jxl-color/src/convert.rs @@ -12,6 +12,7 @@ mod tone_map; pub struct ColorEncodingWithProfile { encoding: ColourEncoding, icc_profile: Vec, + is_cmyk: bool, } impl std::fmt::Debug for ColorEncodingWithProfile { @@ -22,6 +23,7 @@ impl std::fmt::Debug for ColorEncodingWithProfile { "icc_profile", &format_args!("({} byte(s))", self.icc_profile.len()), ) + .field("is_cmyk", &self.is_cmyk) .finish() } } @@ -32,6 +34,7 @@ impl ColorEncodingWithProfile { Self { encoding: ColourEncoding::Enum(encoding), icc_profile: Vec::new(), + is_cmyk: false, } } @@ -47,6 +50,7 @@ impl ColorEncodingWithProfile { Ok(Self { encoding: ColourEncoding::IccProfile(raw.color_space()), icc_profile: icc_profile.to_vec(), + is_cmyk: raw.is_cmyk(), }) } Err(e) => Err(e), @@ -73,6 +77,12 @@ impl ColorEncodingWithProfile { ColourEncoding::IccProfile(color_space) => *color_space == ColourSpace::Grey, } } + + /// Returns whether the color encoding represents CMYK color space. + #[inline] + pub fn is_cmyk(&self) -> bool { + self.is_cmyk + } } impl ColorEncodingWithProfile { diff --git a/crates/jxl-color/src/icc/parse.rs b/crates/jxl-color/src/icc/parse.rs index e212798c..74072689 100644 --- a/crates/jxl-color/src/icc/parse.rs +++ b/crates/jxl-color/src/icc/parse.rs @@ -215,6 +215,10 @@ impl IccProfile<'_> { ColourSpace::Unknown } } + + pub(crate) fn is_cmyk(&self) -> bool { + &self.header.color_space == b"CMYK" + } } struct RawTag<'a> { diff --git a/crates/jxl-oxide/src/fb.rs b/crates/jxl-oxide/src/fb.rs index 7647ff60..b8fe5067 100644 --- a/crates/jxl-oxide/src/fb.rs +++ b/crates/jxl-oxide/src/fb.rs @@ -211,19 +211,22 @@ impl<'r> ImageStream<'r> { } // Find black - for (ec_idx, (ec, (region, _))) in render - .extra_channels - .iter() - .zip(®ions_and_shifts[color_channels..]) - .enumerate() - { - if ec.is_black() { - grids.push(&fb[color_channels + ec_idx]); - bit_depth.push(ec.bit_depth); - start_offset_xy.push((left - region.left, top - region.top)); - break; + if render.is_cmyk { + for (ec_idx, (ec, (region, _))) in render + .extra_channels + .iter() + .zip(®ions_and_shifts[color_channels..]) + .enumerate() + { + if ec.is_black() { + grids.push(&fb[color_channels + ec_idx]); + bit_depth.push(ec.bit_depth); + start_offset_xy.push((left - region.left, top - region.top)); + break; + } } } + // Find alpha for (ec_idx, (ec, (region, _))) in render .extra_channels diff --git a/crates/jxl-oxide/src/lib.rs b/crates/jxl-oxide/src/lib.rs index a896642b..56cd462c 100644 --- a/crates/jxl-oxide/src/lib.rs +++ b/crates/jxl-oxide/src/lib.rs @@ -599,28 +599,9 @@ impl JxlImage { /// Returns the pixel format of the rendered image. pub fn pixel_format(&self) -> PixelFormat { - use jxl_color::{ColourEncoding, ColourSpace}; - let encoding = self.ctx.requested_color_encoding(); - let (is_grayscale, has_black) = match encoding.encoding() { - ColourEncoding::Enum(EnumColourEncoding { - colour_space: ColourSpace::Grey, - .. - }) => (true, false), - ColourEncoding::Enum(_) => (false, false), - ColourEncoding::IccProfile(_) => { - let profile = encoding.icc_profile(); - if profile.len() < 0x14 { - (false, false) - } else { - match &profile[0x10..0x14] { - [b'G', b'R', b'A', b'Y'] => (true, false), - [b'C', b'M', b'Y', b'K'] => (false, true), - _ => (false, false), - } - } - } - }; + let is_grayscale = encoding.is_grayscale(); + let has_black = encoding.is_cmyk(); let mut has_alpha = false; for ec_info in &self.image_header.metadata.ec_info { if ec_info.is_alpha() { @@ -754,6 +735,7 @@ impl JxlImage { let frame_header = frame.header(); let target_frame_region = image_region.translate(-frame_header.x0, -frame_header.y0); + let is_cmyk = self.ctx.requested_color_encoding().is_cmyk(); let result = Render { keyframe_index, name: frame_header.name.clone(), @@ -763,6 +745,7 @@ impl JxlImage { extra_channels: self.convert_ec_info(), target_frame_region, color_bit_depth: self.image_header.metadata.bit_depth, + is_cmyk, render_spot_color: self.render_spot_color, }; Ok(result) @@ -792,6 +775,7 @@ impl JxlImage { let frame_header = frame.header(); let target_frame_region = image_region.translate(-frame_header.x0, -frame_header.y0); + let is_cmyk = self.ctx.requested_color_encoding().is_cmyk(); let result = Render { keyframe_index: self.ctx.loaded_keyframes(), name, @@ -801,6 +785,7 @@ impl JxlImage { extra_channels: self.convert_ec_info(), target_frame_region, color_bit_depth: self.image_header.metadata.bit_depth, + is_cmyk, render_spot_color: self.render_spot_color, }; Ok(result) @@ -928,6 +913,7 @@ pub struct Render { extra_channels: Vec, target_frame_region: Region, color_bit_depth: BitDepth, + is_cmyk: bool, render_spot_color: bool, }