CMYK→sRGB conversion produces different results than skcms/libjxl
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 0
- Forks
- 0
- PR merge metrics
- No merged PRs in 30d
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](https://github.com/imazen/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
```rust
// 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 = vec![0.0, 0.0, 0.0, 0.0]; // no ink = white
let mut rgb_output: Vec = 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
1. **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).
2. **Rendering intent**: We tested Perceptual, RelativeColorimetric, and AbsoluteColorimetric. Perceptual gives the best results (lowest error_count), but all produce different results than skcms.
3. **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
1. Are there known differences between moxcms and skcms in CMYK handling?
2. Is there a specific configuration that would make moxcms more closely match skcms output?
3. Should moxcms read the rendering intent from the ICC profile header rather than using TransformOptions?
## To Reproduce
1. Clone [codec-corpus](https://github.com/imazen/codec-corpus)
2. Run `./jxl/generate_references.sh` with djxl to generate reference outputs
3. Use moxcms to transform the CMYK values from `cmyk_layers.jxl` through its embedded ICC profile
4. 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
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reproducing the mismatch with cmyk_layers.jxl from the codec-corpus and the embedded ICC profile, using create_transform_f32 and transform. Run jxl/generate_references.sh with djxl, then compare moxcms output against the skcms reference across rendering intents. Done means identifying the cause or supported configuration and documenting a reproducible comparison or regression test.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- computer-graphics
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 38/100