Coverage Report

Created: 2026-04-03 06:44

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/lcms/src/cmscam02.c
Line
Count
Source
1
//---------------------------------------------------------------------------------
2
//
3
//  Little Color Management System
4
//  Copyright (c) 1998-2026 Marti Maria Saguer
5
//
6
// Permission is hereby granted, free of charge, to any person obtaining
7
// a copy of this software and associated documentation files (the "Software"),
8
// to deal in the Software without restriction, including without limitation
9
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
10
// and/or sell copies of the Software, and to permit persons to whom the Software
11
// is furnished to do so, subject to the following conditions:
12
//
13
// The above copyright notice and this permission notice shall be included in
14
// all copies or substantial portions of the Software.
15
//
16
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
18
// THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
//
24
//---------------------------------------------------------------------------------
25
//
26
27
#include "lcms2_internal.h"
28
29
// CIECAM 02 appearance model. Many thanks to Jordi Vilar for the debugging.
30
31
// ---------- Implementation --------------------------------------------
32
33
typedef struct  {
34
35
    cmsFloat64Number XYZ[3];
36
    cmsFloat64Number RGB[3];
37
    cmsFloat64Number RGBc[3];
38
    cmsFloat64Number RGBp[3];
39
    cmsFloat64Number RGBpa[3];
40
    cmsFloat64Number a, b, h, e, H, A, J, Q, s, t, C, M;
41
    cmsFloat64Number abC[2];
42
    cmsFloat64Number abs[2];
43
    cmsFloat64Number abM[2];
44
45
} CAM02COLOR;
46
47
typedef struct  {
48
49
    CAM02COLOR adoptedWhite;
50
    cmsFloat64Number LA, Yb;
51
    cmsFloat64Number F, c, Nc;
52
    cmsUInt32Number surround;
53
    cmsFloat64Number n, Nbb, Ncb, z, FL, D;
54
55
    cmsContext ContextID;
56
57
} cmsCIECAM02;
58
59
60
static
61
cmsFloat64Number compute_n(cmsCIECAM02* pMod)
62
22
{
63
22
    return (pMod -> Yb / pMod -> adoptedWhite.XYZ[1]);
64
22
}
65
66
static
67
cmsFloat64Number compute_z(cmsCIECAM02* pMod)
68
22
{
69
22
    return (1.48 + pow(pMod -> n, 0.5));
70
22
}
71
72
static
73
cmsFloat64Number computeNbb(cmsCIECAM02* pMod)
74
22
{
75
22
    return (0.725 * pow((1.0 / pMod -> n), 0.2));
76
22
}
77
78
static
79
cmsFloat64Number computeFL(cmsCIECAM02* pMod)
80
22
{
81
22
    cmsFloat64Number k, FL;
82
83
22
    k = 1.0 / ((5.0 * pMod->LA) + 1.0);
84
22
    FL = 0.2 * pow(k, 4.0) * (5.0 * pMod->LA) + 0.1 *
85
22
        (pow((1.0 - pow(k, 4.0)), 2.0)) *
86
22
        (pow((5.0 * pMod->LA), (1.0 / 3.0)));
87
88
22
    return FL;
89
22
}
90
91
static cmsFloat64Number computeD(cmsCIECAM02* pMod) 
92
0
{
93
0
    cmsFloat64Number D, temp;
94
95
0
    temp = 1.0 - ((1.0 / 3.6) * exp((-pMod->LA - 42) / 92.0));
96
    
97
0
    D = pMod->F * temp;
98
0
    return D;
99
0
}
100
101
static
102
CAM02COLOR XYZtoCAT02(CAM02COLOR clr)
103
44
{
104
44
    clr.RGB[0] = (clr.XYZ[0] *  0.7328) + (clr.XYZ[1] *  0.4296) + (clr.XYZ[2] * -0.1624);
105
44
    clr.RGB[1] = (clr.XYZ[0] * -0.7036) + (clr.XYZ[1] *  1.6975) + (clr.XYZ[2] *  0.0061);
106
44
    clr.RGB[2] = (clr.XYZ[0] *  0.0030) + (clr.XYZ[1] *  0.0136) + (clr.XYZ[2] *  0.9834);
107
108
44
    return clr;
109
44
}
110
111
static
112
CAM02COLOR ChromaticAdaptation(CAM02COLOR clr, cmsCIECAM02* pMod)
113
44
{
114
44
    cmsUInt32Number i;
115
116
176
    for (i = 0; i < 3; i++) {
117
132
        clr.RGBc[i] = ((pMod -> adoptedWhite.XYZ[1] *
118
132
            (pMod->D / pMod -> adoptedWhite.RGB[i])) +
119
132
            (1.0 - pMod->D)) * clr.RGB[i];
120
132
    }
121
122
44
    return clr;
123
44
}
124
125
126
static
127
CAM02COLOR CAT02toHPE(CAM02COLOR clr)
128
44
{
129
44
    cmsFloat64Number M[9];
130
131
44
    M[0] =(( 0.38971 *  1.096124) + (0.68898 * 0.454369) + (-0.07868 * -0.009628));
132
44
    M[1] =(( 0.38971 * -0.278869) + (0.68898 * 0.473533) + (-0.07868 * -0.005698));
133
44
    M[2] =(( 0.38971 *  0.182745) + (0.68898 * 0.072098) + (-0.07868 *  1.015326));
134
44
    M[3] =((-0.22981 *  1.096124) + (1.18340 * 0.454369) + ( 0.04641 * -0.009628));
135
44
    M[4] =((-0.22981 * -0.278869) + (1.18340 * 0.473533) + ( 0.04641 * -0.005698));
136
44
    M[5] =((-0.22981 *  0.182745) + (1.18340 * 0.072098) + ( 0.04641 *  1.015326));
137
44
    M[6] =(-0.009628);
138
44
    M[7] =(-0.005698);
139
44
    M[8] =( 1.015326);
140
141
44
    clr.RGBp[0] = (clr.RGBc[0] * M[0]) +  (clr.RGBc[1] * M[1]) + (clr.RGBc[2] * M[2]);
142
44
    clr.RGBp[1] = (clr.RGBc[0] * M[3]) +  (clr.RGBc[1] * M[4]) + (clr.RGBc[2] * M[5]);
143
44
    clr.RGBp[2] = (clr.RGBc[0] * M[6]) +  (clr.RGBc[1] * M[7]) + (clr.RGBc[2] * M[8]);
144
145
44
    return  clr;
146
44
}
147
148
static
149
CAM02COLOR NonlinearCompression(CAM02COLOR clr, cmsCIECAM02* pMod)
150
44
{
151
44
    cmsUInt32Number i;
152
44
    cmsFloat64Number temp;
153
154
176
    for (i = 0; i < 3; i++) {
155
132
        if (clr.RGBp[i] < 0) {
156
157
18
            temp = pow((-1.0 * pMod->FL * clr.RGBp[i] / 100.0), 0.42);
158
18
            clr.RGBpa[i] = (-1.0 * 400.0 * temp) / (temp + 27.13) + 0.1;
159
18
        }
160
114
        else {
161
114
            temp = pow((pMod->FL * clr.RGBp[i] / 100.0), 0.42);
162
114
            clr.RGBpa[i] = (400.0 * temp) / (temp + 27.13) + 0.1;
163
114
        }
164
132
    }
165
166
44
    clr.A = (((2.0 * clr.RGBpa[0]) + clr.RGBpa[1] +
167
44
        (clr.RGBpa[2] / 20.0)) - 0.305) * pMod->Nbb;
168
169
44
    return clr;
170
44
}
171
172
static
173
CAM02COLOR ComputeCorrelates(CAM02COLOR clr, cmsCIECAM02* pMod)
174
22
{
175
22
    cmsFloat64Number a, b, temp, e, t, r2d, d2r;
176
177
22
    a = clr.RGBpa[0] - (12.0 * clr.RGBpa[1] / 11.0) + (clr.RGBpa[2] / 11.0);
178
22
    b = (clr.RGBpa[0] + clr.RGBpa[1] - (2.0 * clr.RGBpa[2])) / 9.0;
179
180
22
    r2d = (180.0 / 3.141592654);
181
22
    if (a == 0) {
182
0
        if (b == 0)     clr.h = 0;
183
0
        else if (b > 0) clr.h = 90;
184
0
        else            clr.h = 270;
185
0
    }
186
22
    else if (a > 0) {
187
3
        temp = b / a;
188
3
        if (b > 0)       clr.h = (r2d * atan(temp));
189
1
        else if (b == 0) clr.h = 0;
190
1
        else             clr.h = (r2d * atan(temp)) + 360;
191
3
    }
192
19
    else {
193
19
        temp = b / a;
194
19
        clr.h = (r2d * atan(temp)) + 180;
195
19
    }
196
197
22
    d2r = (3.141592654 / 180.0);
198
22
    e = ((12500.0 / 13.0) * pMod->Nc * pMod->Ncb) *
199
22
        (cos((clr.h * d2r + 2.0)) + 3.8);
200
201
22
    if (clr.h < 20.14) {
202
1
        temp = ((clr.h + 122.47)/1.2) + ((20.14 - clr.h)/0.8);
203
1
        clr.H = 300 + (100*((clr.h + 122.47)/1.2)) / temp;
204
1
    }
205
21
    else if (clr.h < 90.0) {
206
1
        temp = ((clr.h - 20.14)/0.8) + ((90.00 - clr.h)/0.7);
207
1
        clr.H = (100*((clr.h - 20.14)/0.8)) / temp;
208
1
    }
209
20
    else if (clr.h < 164.25) {
210
2
        temp = ((clr.h - 90.00)/0.7) + ((164.25 - clr.h)/1.0);
211
2
        clr.H = 100 + ((100*((clr.h - 90.00)/0.7)) / temp);
212
2
    }
213
18
    else if (clr.h < 237.53) {
214
5
        temp = ((clr.h - 164.25)/1.0) + ((237.53 - clr.h)/1.2);
215
5
        clr.H = 200 + ((100*((clr.h - 164.25)/1.0)) / temp);
216
5
    }
217
13
    else {
218
13
        temp = ((clr.h - 237.53)/1.2) + ((360 - clr.h + 20.14)/0.8);
219
13
        clr.H = 300 + ((100*((clr.h - 237.53)/1.2)) / temp);
220
13
    }
221
222
22
    clr.J = 100.0 * pow((clr.A / pMod->adoptedWhite.A),
223
22
        (pMod->c * pMod->z));
224
225
22
    clr.Q = (4.0 / pMod->c) * pow((clr.J / 100.0), 0.5) *
226
22
        (pMod->adoptedWhite.A + 4.0) * pow(pMod->FL, 0.25);
227
228
22
    t = (e * pow(((a * a) + (b * b)), 0.5)) /
229
22
        (clr.RGBpa[0] + clr.RGBpa[1] +
230
22
        ((21.0 / 20.0) * clr.RGBpa[2]));
231
232
22
    clr.C = pow(t, 0.9) * pow((clr.J / 100.0), 0.5) *
233
22
        pow((1.64 - pow(0.29, pMod->n)), 0.73);
234
235
22
    clr.M = clr.C * pow(pMod->FL, 0.25);
236
22
    clr.s = 100.0 * pow((clr.M / clr.Q), 0.5);
237
238
22
    return clr;
239
22
}
240
241
242
static
243
CAM02COLOR InverseCorrelates(CAM02COLOR clr, cmsCIECAM02* pMod)
244
22
{
245
246
22
    cmsFloat64Number t, e, p1, p2, p3, p4, p5, hr, d2r;
247
22
    d2r = 3.141592654 / 180.0;
248
249
22
    t = pow( (clr.C / (pow((clr.J / 100.0), 0.5) *
250
22
        (pow((1.64 - pow(0.29, pMod->n)), 0.73)))),
251
22
        (1.0 / 0.9) );
252
22
    e = ((12500.0 / 13.0) * pMod->Nc * pMod->Ncb) *
253
22
        (cos((clr.h * d2r + 2.0)) + 3.8);
254
255
22
    clr.A = pMod->adoptedWhite.A * pow(
256
22
           (clr.J / 100.0),
257
22
           (1.0 / (pMod->c * pMod->z)));
258
259
22
    p2 = (clr.A / pMod->Nbb) + 0.305;
260
261
22
    if ( t <= 0.0 ) {     // special case from spec notes, avoid divide by zero
262
0
        clr.a = clr.b = 0.0;
263
0
    }
264
22
    else {
265
22
        hr = clr.h * d2r;
266
22
        p1 = e / t;
267
22
        p3 = 21.0 / 20.0;
268
269
22
        if (fabs(sin(hr)) >= fabs(cos(hr))) {
270
1
            p4 = p1 / sin(hr);
271
1
            clr.b = (p2 * (2.0 + p3) * (460.0 / 1403.0)) /
272
1
                (p4 + (2.0 + p3) * (220.0 / 1403.0) *
273
1
                (cos(hr) / sin(hr)) - (27.0 / 1403.0) +
274
1
                p3 * (6300.0 / 1403.0));
275
1
            clr.a = clr.b * (cos(hr) / sin(hr));
276
1
        }
277
21
        else {
278
21
            p5 = p1 / cos(hr);
279
21
            clr.a = (p2 * (2.0 + p3) * (460.0 / 1403.0)) /
280
21
                (p5 + (2.0 + p3) * (220.0 / 1403.0) -
281
21
                ((27.0 / 1403.0) - p3 * (6300.0 / 1403.0)) *
282
21
                (sin(hr) / cos(hr)));
283
21
            clr.b = clr.a * (sin(hr) / cos(hr));
284
21
        }
285
22
    }
286
287
22
    clr.RGBpa[0] = ((460.0 / 1403.0) * p2) +
288
22
              ((451.0 / 1403.0) * clr.a) +
289
22
              ((288.0 / 1403.0) * clr.b);
290
22
    clr.RGBpa[1] = ((460.0 / 1403.0) * p2) -
291
22
              ((891.0 / 1403.0) * clr.a) -
292
22
              ((261.0 / 1403.0) * clr.b);
293
22
    clr.RGBpa[2] = ((460.0 / 1403.0) * p2) -
294
22
              ((220.0 / 1403.0) * clr.a) -
295
22
              ((6300.0 / 1403.0) * clr.b);
296
297
22
    return clr;
298
22
}
299
300
static
301
CAM02COLOR InverseNonlinearity(CAM02COLOR clr, cmsCIECAM02* pMod)
302
22
{
303
22
    cmsUInt32Number i;
304
22
    cmsFloat64Number c1;
305
306
88
    for (i = 0; i < 3; i++) {
307
66
        if ((clr.RGBpa[i] - 0.1) < 0) c1 = -1;
308
61
        else                               c1 = 1;
309
66
        clr.RGBp[i] = c1 * (100.0 / pMod->FL) *
310
66
            pow(((27.13 * fabs(clr.RGBpa[i] - 0.1)) /
311
66
            (400.0 - fabs(clr.RGBpa[i] - 0.1))),
312
66
            (1.0 / 0.42));
313
66
    }
314
315
22
    return clr;
316
22
}
317
318
static
319
CAM02COLOR HPEtoCAT02(CAM02COLOR clr)
320
22
{
321
22
    cmsFloat64Number M[9];
322
323
22
    M[0] = (( 0.7328 *  1.910197) + (0.4296 * 0.370950));
324
22
    M[1] = (( 0.7328 * -1.112124) + (0.4296 * 0.629054));
325
22
    M[2] = (( 0.7328 *  0.201908) + (0.4296 * 0.000008) - 0.1624);
326
22
    M[3] = ((-0.7036 *  1.910197) + (1.6975 * 0.370950));
327
22
    M[4] = ((-0.7036 * -1.112124) + (1.6975 * 0.629054));
328
22
    M[5] = ((-0.7036 *  0.201908) + (1.6975 * 0.000008) + 0.0061);
329
22
    M[6] = (( 0.0030 *  1.910197) + (0.0136 * 0.370950));
330
22
    M[7] = (( 0.0030 * -1.112124) + (0.0136 * 0.629054));
331
22
    M[8] = (( 0.0030 *  0.201908) + (0.0136 * 0.000008) + 0.9834);
332
333
22
    clr.RGBc[0] = (clr.RGBp[0] * M[0]) + (clr.RGBp[1] * M[1]) + (clr.RGBp[2] * M[2]);
334
22
    clr.RGBc[1] = (clr.RGBp[0] * M[3]) + (clr.RGBp[1] * M[4]) + (clr.RGBp[2] * M[5]);
335
22
    clr.RGBc[2] = (clr.RGBp[0] * M[6]) + (clr.RGBp[1] * M[7]) + (clr.RGBp[2] * M[8]);
336
22
    return clr;
337
22
}
338
339
340
static
341
CAM02COLOR InverseChromaticAdaptation(CAM02COLOR clr,  cmsCIECAM02* pMod)
342
22
{
343
22
    cmsUInt32Number i;
344
88
    for (i = 0; i < 3; i++) {
345
66
        clr.RGB[i] = clr.RGBc[i] /
346
66
            ((pMod->adoptedWhite.XYZ[1] * pMod->D / pMod->adoptedWhite.RGB[i]) + 1.0 - pMod->D);
347
66
    }
348
22
    return clr;
349
22
}
350
351
352
static
353
CAM02COLOR CAT02toXYZ(CAM02COLOR clr)
354
22
{
355
22
    clr.XYZ[0] = (clr.RGB[0] *  1.096124) + (clr.RGB[1] * -0.278869) + (clr.RGB[2] *  0.182745);
356
22
    clr.XYZ[1] = (clr.RGB[0] *  0.454369) + (clr.RGB[1] *  0.473533) + (clr.RGB[2] *  0.072098);
357
22
    clr.XYZ[2] = (clr.RGB[0] * -0.009628) + (clr.RGB[1] * -0.005698) + (clr.RGB[2] *  1.015326);
358
359
22
    return clr;
360
22
}
361
362
363
cmsHANDLE  CMSEXPORT cmsCIECAM02Init(cmsContext ContextID, const cmsViewingConditions* pVC)
364
22
{
365
22
    cmsCIECAM02* lpMod;
366
367
22
    _cmsAssert(pVC != NULL);
368
369
22
    if((lpMod = (cmsCIECAM02*) _cmsMallocZero(ContextID, sizeof(cmsCIECAM02))) == NULL) {
370
0
        return NULL;
371
0
    }
372
373
22
    lpMod ->ContextID = ContextID;
374
375
22
    lpMod ->adoptedWhite.XYZ[0] = pVC ->whitePoint.X;
376
22
    lpMod ->adoptedWhite.XYZ[1] = pVC ->whitePoint.Y;
377
22
    lpMod ->adoptedWhite.XYZ[2] = pVC ->whitePoint.Z;
378
379
22
    lpMod -> LA       = pVC ->La;
380
22
    lpMod -> Yb       = pVC ->Yb;
381
22
    lpMod -> D        = pVC ->D_value;
382
22
    lpMod -> surround = pVC ->surround;
383
384
22
    switch (lpMod -> surround) {
385
386
387
2
    case CUTSHEET_SURROUND:
388
2
        lpMod->F = 0.8;
389
2
        lpMod->c = 0.41;
390
2
        lpMod->Nc = 0.8;
391
2
        break;
392
393
0
    case DARK_SURROUND:
394
0
        lpMod -> F  = 0.8;
395
0
        lpMod -> c  = 0.525;
396
0
        lpMod -> Nc = 0.8;
397
0
        break;
398
399
5
    case DIM_SURROUND:
400
5
        lpMod -> F  = 0.9;
401
5
        lpMod -> c  = 0.59;
402
5
        lpMod -> Nc = 0.95;
403
5
        break;
404
405
15
    default:
406
        // Average surround
407
15
        lpMod -> F  = 1.0;
408
15
        lpMod -> c  = 0.69;
409
15
        lpMod -> Nc = 1.0;
410
22
    }
411
412
22
    lpMod -> n   = compute_n(lpMod);
413
22
    lpMod -> z   = compute_z(lpMod);
414
22
    lpMod -> Nbb = computeNbb(lpMod);
415
22
    lpMod -> FL  = computeFL(lpMod);
416
417
22
    if (lpMod -> D == D_CALCULATE) {
418
0
        lpMod -> D   = computeD(lpMod);
419
0
    }
420
421
22
    lpMod -> Ncb = lpMod -> Nbb;
422
423
22
    lpMod -> adoptedWhite = XYZtoCAT02(lpMod -> adoptedWhite);
424
22
    lpMod -> adoptedWhite = ChromaticAdaptation(lpMod -> adoptedWhite, lpMod);
425
22
    lpMod -> adoptedWhite = CAT02toHPE(lpMod -> adoptedWhite);
426
22
    lpMod -> adoptedWhite = NonlinearCompression(lpMod -> adoptedWhite, lpMod);
427
428
22
    return (cmsHANDLE) lpMod;
429
430
22
}
431
432
void CMSEXPORT cmsCIECAM02Done(cmsHANDLE hModel)
433
22
{
434
22
    cmsCIECAM02* lpMod = (cmsCIECAM02*) hModel;
435
436
22
    if (lpMod) _cmsFree(lpMod ->ContextID, lpMod);
437
22
}
438
439
440
void CMSEXPORT cmsCIECAM02Forward(cmsHANDLE hModel, const cmsCIEXYZ* pIn, cmsJCh* pOut)
441
22
{
442
22
    CAM02COLOR clr;
443
22
    cmsCIECAM02* lpMod = (cmsCIECAM02*) hModel;
444
  
445
22
    _cmsAssert(lpMod != NULL);
446
22
    _cmsAssert(pIn != NULL);
447
22
    _cmsAssert(pOut != NULL);
448
449
22
    memset(&clr, 0, sizeof(clr));
450
451
22
    clr.XYZ[0] = pIn ->X;
452
22
    clr.XYZ[1] = pIn ->Y;
453
22
    clr.XYZ[2] = pIn ->Z;
454
455
22
    clr = XYZtoCAT02(clr);
456
22
    clr = ChromaticAdaptation(clr, lpMod);
457
22
    clr = CAT02toHPE(clr);
458
22
    clr = NonlinearCompression(clr, lpMod);
459
22
    clr = ComputeCorrelates(clr, lpMod);
460
461
22
    pOut ->J = clr.J;
462
22
    pOut ->C = clr.C;
463
22
    pOut ->h = clr.h;
464
22
}
465
466
void CMSEXPORT cmsCIECAM02Reverse(cmsHANDLE hModel, const cmsJCh* pIn, cmsCIEXYZ* pOut)
467
22
{
468
22
    CAM02COLOR clr;
469
22
    cmsCIECAM02* lpMod = (cmsCIECAM02*) hModel;
470
    
471
22
    _cmsAssert(lpMod != NULL);
472
22
    _cmsAssert(pIn != NULL);
473
22
    _cmsAssert(pOut != NULL);
474
475
22
    memset(&clr, 0, sizeof(clr));
476
477
22
    clr.J = pIn -> J;
478
22
    clr.C = pIn -> C;
479
22
    clr.h = pIn -> h;
480
481
22
    clr = InverseCorrelates(clr, lpMod);
482
22
    clr = InverseNonlinearity(clr, lpMod);
483
22
    clr = HPEtoCAT02(clr);
484
22
    clr = InverseChromaticAdaptation(clr, lpMod);
485
22
    clr = CAT02toXYZ(clr);
486
487
22
    pOut ->X = clr.XYZ[0];
488
22
    pOut ->Y = clr.XYZ[1];
489
22
    pOut ->Z = clr.XYZ[2];
490
22
}