Skip to content

CMYK→sRGB conversion produces different results than skcms/libjxl #2

@lilith

Description

@lilith

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

  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
  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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions