imazen / imazen/oxcms

CMYK→sRGB conversion produces different results than skcms/libjxl

Open
#2 3 comments 0 reactions 0 assignees View on GitHub

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

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.