-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Summary
When using moxcms to convert CMYK images to sRGB using an embedded ICC profile, the results differ from libjxl's reference output (which uses skcms).
Context
We're implementing a JPEG XL decoder in Rust (jxl-rs) and using moxcms for color management. For CMYK images with embedded ICC profiles, we need to convert CMYK→sRGB using the profile's lookup tables.
Test Case
Source file: cmyk_layers.jxl from the codec-corpus JXL conformance test suite.
File details:
- Image size: 512x512
- Color space: CMYK (stored as RGB + Black extra channel)
- Embedded ICC profile: ~557KB CMYK profile
- 4 frames (multi-layer composition)
Current Results
| Metric | moxcms (Perceptual) | libjxl/skcms |
|---|---|---|
| max_error | 60 | 0 (reference) |
| error_count | 251,108 (24%) | 0 |
Example pixel at (0,0):
- Reference (djxl/skcms):
[252, 254, 255, 255](near white) - moxcms output:
[255, 255, 255, 255](pure white)
Input CMYK values (already inverted from JXL convention):
- C=0.0, M=0.0, Y=0.0, K=0.0 (no ink = white paper)
Code
// Creating the transform
let src_profile = moxcms::ColorProfile::new_from_slice(&cmyk_icc_data)?;
let dst_profile = moxcms::ColorProfile::new_srgb();
let options = moxcms::TransformOptions::default(); // Perceptual intent
let transform = src_profile.create_transform_f32(
moxcms::Layout::Rgba, // CMYK 4 channels
&dst_profile,
moxcms::Layout::Rgb, // sRGB 3 channels
options
)?;
// Applying the transform
let cmyk_input: Vec<f32> = vec![0.0, 0.0, 0.0, 0.0]; // no ink = white
let mut rgb_output: Vec<f32> = vec![0.0; 3];
transform.transform(&cmyk_input, &mut rgb_output)?;
// Output: [0.9999, 1.0, 1.0] ≈ [255, 255, 255]
// Expected: [0.988, 0.996, 1.0] ≈ [252, 254, 255]Notes
-
Value convention: JXL stores CMYK as 1=no ink, 0=max ink. We invert these before passing to moxcms (so moxcms receives standard ICC convention: 0=no ink, 1=max ink).
-
Rendering intent: We tested Perceptual, RelativeColorimetric, and AbsoluteColorimetric. Perceptual gives the best results (lowest error_count), but all produce different results than skcms.
-
The difference is small but consistent: Most errors are 1-3 units out of 255, but some reach 60 units. The pattern suggests different LUT interpolation or precision.
Questions
- Are there known differences between moxcms and skcms in CMYK handling?
- Is there a specific configuration that would make moxcms more closely match skcms output?
- Should moxcms read the rendering intent from the ICC profile header rather than using TransformOptions?
To Reproduce
- Clone codec-corpus
- Run
./jxl/generate_references.shwith djxl to generate reference outputs - Use moxcms to transform the CMYK values from
cmyk_layers.jxlthrough its embedded ICC profile - Compare with the reference output
The embedded ICC profile can be extracted from the JXL file - it's stored after the file header. The JXL file is available at: https://github.com/imazen/codec-corpus/blob/main/jxl/conformance/cmyk_layers.jxl
Environment
- moxcms version: (using latest from crates.io via Cargo)
- Rust: stable
- Platform: Linux x86_64