mm2 / Little-CMS

A free, open source, CMM engine. It provides fast transforms between ICC profiles.
https://www.littlecms.com
MIT License
572 stars 176 forks source link

RGB profiles do not roundtrip? #316

Closed haasn closed 2 years ago

haasn commented 2 years ago

I'm a bit confused as to why created built-in RGB profiles do not round-trip back to the stated primaries, neither when looking at the colorant tags, nor when constructing an RGB->XYZ transform. Here is my code:

    cmsHPROFILE sRGB = cmsCreate_sRGBProfileTHR(s->ctx);
    cmsHPROFILE xyz;
    cmsHTRANSFORM tf;
    cmsCIEXYZ dst[4];

    static const uint8_t src[4][3] = {
        { 0xFF,    0,    0 }, /* red */
        {    0, 0xFF,    0 }, /* green */
        {    0,    0, 0xFF }, /* blue */
        { 0xFF, 0xFF, 0xFF }, /* white */
    };

    xyz = cmsCreateXYZProfileTHR(ctx);
    tf = cmsCreateTransformTHR(ctx, sRGB, TYPE_RGB_8, xyz, TYPE_XYZ_DBL,
                               INTENT_ABSOLUTE_COLORIMETRIC,
                               cmsFLAGS_NOCACHE | cmsFLAGS_NOOPTIMIZE |
                               cmsFLAGS_LOWRESPRECALC);
    cmsCloseProfile(xyz);
    cmsDoTransform(tf, src, dst, 4);
    cmsDeleteTransform(tf);

This is the resulting 'dst' array:

$1 = {{
    X = 0.43604125808087701,
    Y = 0.22248454648774896,
    Z = 0.013920187883229573
  }, {
    X = 0.38511290607402771,
    Y = 0.71690508541450981,
    Z = 0.097067238414751955
  }, {
    X = 0.14304583547755101,
    Y = 0.060610382765844406,
    Z = 0.71391256067727227
  }, {
    X = 0.96420002943432337,
    Y = 0.99999999976716936,
    Z = 0.82490002050235489
  }}

The returned white point is D50 (instead of D65), and the returned primaries are also completely wrong (e.g. red is returned as (x=0.6484, y=0.3308), instead of the (x=0.64, y=0.33) that went into its creation). Specifying relative colorimetric intent instead of absolute colorimetric intent doesn't change the result, even though I would have expected it to matter. Nor does removing any of the flags.

What's going on here? How do I actually figure out what primary coefficients are associated with a given RGB profile? (The RedColorant etc. tags are also completely useless)

mm2 commented 2 years ago

V4 absolute intent uses fully adapted observer hence the D50. To do what you intend you need to use unadapted observer. Just use cmsSetAdaptationState(0) before creating the transform.

haasn commented 2 years ago

Oh, I see. Thanks, that works. Incidentally, is there a way to recover this same information from the colorant tags, or do I absolutely have to use an inverse transform to arrive back at these values? (0.64, 0.33)

haasn commented 2 years ago

Hmm, even with that change, it now still fails on real-world profiles, such as the one embedded in this jpg.

e.g. exiftool has this to say about it:

Connection Space Illuminant     : 0.9642 1 0.82491
Profile Creator                 : Apple Computer Inc.
Profile ID                      : e5bb0e9867bd46cd4bbe446ebd1b7598
Profile Description             : Display P3
Profile Copyright               : Copyright Apple Inc., 2015
Media White Point               : 0.95045 1 1.08905
Red Matrix Column               : 0.51512 0.2412 -0.00105
Green Matrix Column             : 0.29198 0.69225 0.04189
Blue Matrix Column              : 0.1571 0.06657 0.78407

But the values I get from the above code (with the unadapted absolute observer) are:

(gdb) p dst
$1 = {{
    X = 0.47963805262952519,
    Y = 0.22897943141288124,
    Z = -2.2890162618693921e-05
  }, {
    X = 0.26187982903365992,
    Y = 0.69174122283129691,
    Z = 0.05955730024567174
  }, {
    X = 0.19537895811936323,
    Y = 0.0792735788616028,
    Z = 1.3782320718291885
  }, {
    X = 0.93689686958441598,
    Y = 0.99999427780858241,
    Z = 1.4377664283620106
  }}
(gdb) p *prim
$2 = {
  xr = 0.67688641258656734,
  yr = 0.3231458910223175,
  xg = 0.25847357327376586,
  yg = 0.68274378483341891,
  xb = 0.1182048384248727,
  yb = 0.047960745982545433
}
(gdb) p *wp
$3 = {
  xw = 0.27762724026149671,
  yw = 0.29632466564695964
}

Which are just completely off. I'm not even sure how it's reproducing anything that wrong, here. (Notice in particular how the white point doesn't correspond to either the tagged media white point nor the tagged PCS)

What's going on here?

mm2 commented 2 years ago

Matrix columns are NOT the primaries. There is a chromatic adaptation and other adjustmets, that's the reason ICC moved names from "Colorant" to "Matrix Column". This is an internal of the profile and should not be used alone as a source of information.

On the other hand, using a V4 P3 profile works quite well for me, this is using Debian and its system lcms2, primaries of P3 are DCI-P3 https://en.wikipedia.org/wiki/DCI-P3

If you convert XYZ to xyY chromaticity is quite proper.

marti@OFTSOF150901:~$ transicc -d0 -t3 -i DisplayP3-v4.icc -o'*XYZ'
LittleCMS ColorSpace conversion calculator - 4.3 [LittleCMS 2.09]

Enter values, 'q' to quit
R? 255
G? 255
B? 255

X=95.0466 Y=99.9995 Z=108.9061

Enter values, 'q' to quit
R? 255
G? 0
B? 0

X=48.6576 Y=22.8980 Z=-0.0007

Enter values, 'q' to quit
R? 0
G? 255
B? 0

X=26.5669 Y=69.1742 Z=4.5117

Enter values, 'q' to quit
R? 0
G? 0
B? 255

X=19.8220 Y=7.9274 Z=104.3951

Enter values, 'q' to quit
R?
haasn commented 2 years ago

Yeah, closing this as the same code works perfectly on e.g. the ArgyllCMS P3 profiles. I think the instagram ICC profile might just be the odd one out, possibly intentionally so (since it's supposed to be a test image).

Thanks for the assistance