1 //---------------------------------------------------------------------------------
3 // Little Color Management System
4 // Copyright (c) 1998-2012 Marti Maria Saguer
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:
13 // The above copyright notice and this permission notice shall be included in
14 // all copies or substantial portions of the Software.
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.
24 //---------------------------------------------------------------------------------
27 #include "lcms2_internal.h"
29 // CIECAM 02 appearance model. Many thanks to Jordi Vilar for the debugging.
31 // ---------- Implementation --------------------------------------------
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];
49 CAM02COLOR adoptedWhite
;
50 cmsFloat64Number LA
, Yb
;
51 cmsFloat64Number F
, c
, Nc
;
52 cmsUInt32Number surround
;
53 cmsFloat64Number n
, Nbb
, Ncb
, z
, FL
, D
;
61 cmsFloat64Number
compute_n(cmsCIECAM02
* pMod
)
63 return (pMod
-> Yb
/ pMod
-> adoptedWhite
.XYZ
[1]);
67 cmsFloat64Number
compute_z(cmsCIECAM02
* pMod
)
69 return (1.48 + pow(pMod
-> n
, 0.5));
73 cmsFloat64Number
computeNbb(cmsCIECAM02
* pMod
)
75 return (0.725 * pow((1.0 / pMod
-> n
), 0.2));
79 cmsFloat64Number
computeFL(cmsCIECAM02
* pMod
)
81 cmsFloat64Number k
, FL
;
83 k
= 1.0 / ((5.0 * pMod
->LA
) + 1.0);
84 FL
= 0.2 * pow(k
, 4.0) * (5.0 * pMod
->LA
) + 0.1 *
85 (pow((1.0 - pow(k
, 4.0)), 2.0)) *
86 (pow((5.0 * pMod
->LA
), (1.0 / 3.0)));
92 cmsFloat64Number
computeD(cmsCIECAM02
* pMod
)
96 D
= pMod
->F
- (1.0/3.6)*(exp(((-pMod
->LA
-42) / 92.0)));
103 CAM02COLOR
XYZtoCAT02(CAM02COLOR clr
)
105 clr
.RGB
[0] = (clr
.XYZ
[0] * 0.7328) + (clr
.XYZ
[1] * 0.4296) + (clr
.XYZ
[2] * -0.1624);
106 clr
.RGB
[1] = (clr
.XYZ
[0] * -0.7036) + (clr
.XYZ
[1] * 1.6975) + (clr
.XYZ
[2] * 0.0061);
107 clr
.RGB
[2] = (clr
.XYZ
[0] * 0.0030) + (clr
.XYZ
[1] * 0.0136) + (clr
.XYZ
[2] * 0.9834);
113 CAM02COLOR
ChromaticAdaptation(CAM02COLOR clr
, cmsCIECAM02
* pMod
)
117 for (i
= 0; i
< 3; i
++) {
118 clr
.RGBc
[i
] = ((pMod
-> adoptedWhite
.XYZ
[1] *
119 (pMod
->D
/ pMod
-> adoptedWhite
.RGB
[i
])) +
120 (1.0 - pMod
->D
)) * clr
.RGB
[i
];
128 CAM02COLOR
CAT02toHPE(CAM02COLOR clr
)
130 cmsFloat64Number M
[9];
132 M
[0] =(( 0.38971 * 1.096124) + (0.68898 * 0.454369) + (-0.07868 * -0.009628));
133 M
[1] =(( 0.38971 * -0.278869) + (0.68898 * 0.473533) + (-0.07868 * -0.005698));
134 M
[2] =(( 0.38971 * 0.182745) + (0.68898 * 0.072098) + (-0.07868 * 1.015326));
135 M
[3] =((-0.22981 * 1.096124) + (1.18340 * 0.454369) + ( 0.04641 * -0.009628));
136 M
[4] =((-0.22981 * -0.278869) + (1.18340 * 0.473533) + ( 0.04641 * -0.005698));
137 M
[5] =((-0.22981 * 0.182745) + (1.18340 * 0.072098) + ( 0.04641 * 1.015326));
142 clr
.RGBp
[0] = (clr
.RGBc
[0] * M
[0]) + (clr
.RGBc
[1] * M
[1]) + (clr
.RGBc
[2] * M
[2]);
143 clr
.RGBp
[1] = (clr
.RGBc
[0] * M
[3]) + (clr
.RGBc
[1] * M
[4]) + (clr
.RGBc
[2] * M
[5]);
144 clr
.RGBp
[2] = (clr
.RGBc
[0] * M
[6]) + (clr
.RGBc
[1] * M
[7]) + (clr
.RGBc
[2] * M
[8]);
150 CAM02COLOR
NonlinearCompression(CAM02COLOR clr
, cmsCIECAM02
* pMod
)
153 cmsFloat64Number temp
;
155 for (i
= 0; i
< 3; i
++) {
156 if (clr
.RGBp
[i
] < 0) {
158 temp
= pow((-1.0 * pMod
->FL
* clr
.RGBp
[i
] / 100.0), 0.42);
159 clr
.RGBpa
[i
] = (-1.0 * 400.0 * temp
) / (temp
+ 27.13) + 0.1;
162 temp
= pow((pMod
->FL
* clr
.RGBp
[i
] / 100.0), 0.42);
163 clr
.RGBpa
[i
] = (400.0 * temp
) / (temp
+ 27.13) + 0.1;
167 clr
.A
= (((2.0 * clr
.RGBpa
[0]) + clr
.RGBpa
[1] +
168 (clr
.RGBpa
[2] / 20.0)) - 0.305) * pMod
->Nbb
;
174 CAM02COLOR
ComputeCorrelates(CAM02COLOR clr
, cmsCIECAM02
* pMod
)
176 cmsFloat64Number a
, b
, temp
, e
, t
, r2d
, d2r
;
178 a
= clr
.RGBpa
[0] - (12.0 * clr
.RGBpa
[1] / 11.0) + (clr
.RGBpa
[2] / 11.0);
179 b
= (clr
.RGBpa
[0] + clr
.RGBpa
[1] - (2.0 * clr
.RGBpa
[2])) / 9.0;
181 r2d
= (180.0 / 3.141592654);
183 if (b
== 0) clr
.h
= 0;
184 else if (b
> 0) clr
.h
= 90;
189 if (b
> 0) clr
.h
= (r2d
* atan(temp
));
190 else if (b
== 0) clr
.h
= 0;
191 else clr
.h
= (r2d
* atan(temp
)) + 360;
195 clr
.h
= (r2d
* atan(temp
)) + 180;
198 d2r
= (3.141592654 / 180.0);
199 e
= ((12500.0 / 13.0) * pMod
->Nc
* pMod
->Ncb
) *
200 (cos((clr
.h
* d2r
+ 2.0)) + 3.8);
203 temp
= ((clr
.h
+ 122.47)/1.2) + ((20.14 - clr
.h
)/0.8);
204 clr
.H
= 300 + (100*((clr
.h
+ 122.47)/1.2)) / temp
;
206 else if (clr
.h
< 90.0) {
207 temp
= ((clr
.h
- 20.14)/0.8) + ((90.00 - clr
.h
)/0.7);
208 clr
.H
= (100*((clr
.h
- 20.14)/0.8)) / temp
;
210 else if (clr
.h
< 164.25) {
211 temp
= ((clr
.h
- 90.00)/0.7) + ((164.25 - clr
.h
)/1.0);
212 clr
.H
= 100 + ((100*((clr
.h
- 90.00)/0.7)) / temp
);
214 else if (clr
.h
< 237.53) {
215 temp
= ((clr
.h
- 164.25)/1.0) + ((237.53 - clr
.h
)/1.2);
216 clr
.H
= 200 + ((100*((clr
.h
- 164.25)/1.0)) / temp
);
219 temp
= ((clr
.h
- 237.53)/1.2) + ((360 - clr
.h
+ 20.14)/0.8);
220 clr
.H
= 300 + ((100*((clr
.h
- 237.53)/1.2)) / temp
);
223 clr
.J
= 100.0 * pow((clr
.A
/ pMod
->adoptedWhite
.A
),
224 (pMod
->c
* pMod
->z
));
226 clr
.Q
= (4.0 / pMod
->c
) * pow((clr
.J
/ 100.0), 0.5) *
227 (pMod
->adoptedWhite
.A
+ 4.0) * pow(pMod
->FL
, 0.25);
229 t
= (e
* pow(((a
* a
) + (b
* b
)), 0.5)) /
230 (clr
.RGBpa
[0] + clr
.RGBpa
[1] +
231 ((21.0 / 20.0) * clr
.RGBpa
[2]));
233 clr
.C
= pow(t
, 0.9) * pow((clr
.J
/ 100.0), 0.5) *
234 pow((1.64 - pow(0.29, pMod
->n
)), 0.73);
236 clr
.M
= clr
.C
* pow(pMod
->FL
, 0.25);
237 clr
.s
= 100.0 * pow((clr
.M
/ clr
.Q
), 0.5);
244 CAM02COLOR
InverseCorrelates(CAM02COLOR clr
, cmsCIECAM02
* pMod
)
247 cmsFloat64Number t
, e
, p1
, p2
, p3
, p4
, p5
, hr
, d2r
;
248 d2r
= 3.141592654 / 180.0;
250 t
= pow( (clr
.C
/ (pow((clr
.J
/ 100.0), 0.5) *
251 (pow((1.64 - pow(0.29, pMod
->n
)), 0.73)))),
253 e
= ((12500.0 / 13.0) * pMod
->Nc
* pMod
->Ncb
) *
254 (cos((clr
.h
* d2r
+ 2.0)) + 3.8);
256 clr
.A
= pMod
->adoptedWhite
.A
* pow(
258 (1.0 / (pMod
->c
* pMod
->z
)));
261 p2
= (clr
.A
/ pMod
->Nbb
) + 0.305;
266 if (fabs(sin(hr
)) >= fabs(cos(hr
))) {
268 clr
.b
= (p2
* (2.0 + p3
) * (460.0 / 1403.0)) /
269 (p4
+ (2.0 + p3
) * (220.0 / 1403.0) *
270 (cos(hr
) / sin(hr
)) - (27.0 / 1403.0) +
271 p3
* (6300.0 / 1403.0));
272 clr
.a
= clr
.b
* (cos(hr
) / sin(hr
));
276 clr
.a
= (p2
* (2.0 + p3
) * (460.0 / 1403.0)) /
277 (p5
+ (2.0 + p3
) * (220.0 / 1403.0) -
278 ((27.0 / 1403.0) - p3
* (6300.0 / 1403.0)) *
279 (sin(hr
) / cos(hr
)));
280 clr
.b
= clr
.a
* (sin(hr
) / cos(hr
));
283 clr
.RGBpa
[0] = ((460.0 / 1403.0) * p2
) +
284 ((451.0 / 1403.0) * clr
.a
) +
285 ((288.0 / 1403.0) * clr
.b
);
286 clr
.RGBpa
[1] = ((460.0 / 1403.0) * p2
) -
287 ((891.0 / 1403.0) * clr
.a
) -
288 ((261.0 / 1403.0) * clr
.b
);
289 clr
.RGBpa
[2] = ((460.0 / 1403.0) * p2
) -
290 ((220.0 / 1403.0) * clr
.a
) -
291 ((6300.0 / 1403.0) * clr
.b
);
297 CAM02COLOR
InverseNonlinearity(CAM02COLOR clr
, cmsCIECAM02
* pMod
)
302 for (i
= 0; i
< 3; i
++) {
303 if ((clr
.RGBpa
[i
] - 0.1) < 0) c1
= -1;
305 clr
.RGBp
[i
] = c1
* (100.0 / pMod
->FL
) *
306 pow(((27.13 * fabs(clr
.RGBpa
[i
] - 0.1)) /
307 (400.0 - fabs(clr
.RGBpa
[i
] - 0.1))),
315 CAM02COLOR
HPEtoCAT02(CAM02COLOR clr
)
317 cmsFloat64Number M
[9];
319 M
[0] = (( 0.7328 * 1.910197) + (0.4296 * 0.370950));
320 M
[1] = (( 0.7328 * -1.112124) + (0.4296 * 0.629054));
321 M
[2] = (( 0.7328 * 0.201908) + (0.4296 * 0.000008) - 0.1624);
322 M
[3] = ((-0.7036 * 1.910197) + (1.6975 * 0.370950));
323 M
[4] = ((-0.7036 * -1.112124) + (1.6975 * 0.629054));
324 M
[5] = ((-0.7036 * 0.201908) + (1.6975 * 0.000008) + 0.0061);
325 M
[6] = (( 0.0030 * 1.910197) + (0.0136 * 0.370950));
326 M
[7] = (( 0.0030 * -1.112124) + (0.0136 * 0.629054));
327 M
[8] = (( 0.0030 * 0.201908) + (0.0136 * 0.000008) + 0.9834);;
329 clr
.RGBc
[0] = (clr
.RGBp
[0] * M
[0]) + (clr
.RGBp
[1] * M
[1]) + (clr
.RGBp
[2] * M
[2]);
330 clr
.RGBc
[1] = (clr
.RGBp
[0] * M
[3]) + (clr
.RGBp
[1] * M
[4]) + (clr
.RGBp
[2] * M
[5]);
331 clr
.RGBc
[2] = (clr
.RGBp
[0] * M
[6]) + (clr
.RGBp
[1] * M
[7]) + (clr
.RGBp
[2] * M
[8]);
337 CAM02COLOR
InverseChromaticAdaptation(CAM02COLOR clr
, cmsCIECAM02
* pMod
)
340 for (i
= 0; i
< 3; i
++) {
341 clr
.RGB
[i
] = clr
.RGBc
[i
] /
342 ((pMod
->adoptedWhite
.XYZ
[1] * pMod
->D
/ pMod
->adoptedWhite
.RGB
[i
]) + 1.0 - pMod
->D
);
349 CAM02COLOR
CAT02toXYZ(CAM02COLOR clr
)
351 clr
.XYZ
[0] = (clr
.RGB
[0] * 1.096124) + (clr
.RGB
[1] * -0.278869) + (clr
.RGB
[2] * 0.182745);
352 clr
.XYZ
[1] = (clr
.RGB
[0] * 0.454369) + (clr
.RGB
[1] * 0.473533) + (clr
.RGB
[2] * 0.072098);
353 clr
.XYZ
[2] = (clr
.RGB
[0] * -0.009628) + (clr
.RGB
[1] * -0.005698) + (clr
.RGB
[2] * 1.015326);
359 cmsHANDLE CMSEXPORT
cmsCIECAM02Init(cmsContext ContextID
, const cmsViewingConditions
* pVC
)
363 _cmsAssert(pVC
!= NULL
);
365 if((lpMod
= (cmsCIECAM02
*) _cmsMallocZero(ContextID
, sizeof(cmsCIECAM02
))) == NULL
) {
369 lpMod
->ContextID
= ContextID
;
371 lpMod
->adoptedWhite
.XYZ
[0] = pVC
->whitePoint
.X
;
372 lpMod
->adoptedWhite
.XYZ
[1] = pVC
->whitePoint
.Y
;
373 lpMod
->adoptedWhite
.XYZ
[2] = pVC
->whitePoint
.Z
;
375 lpMod
-> LA
= pVC
->La
;
376 lpMod
-> Yb
= pVC
->Yb
;
377 lpMod
-> D
= pVC
->D_value
;
378 lpMod
-> surround
= pVC
->surround
;
380 switch (lpMod
-> surround
) {
383 case CUTSHEET_SURROUND
:
408 lpMod
-> n
= compute_n(lpMod
);
409 lpMod
-> z
= compute_z(lpMod
);
410 lpMod
-> Nbb
= computeNbb(lpMod
);
411 lpMod
-> FL
= computeFL(lpMod
);
413 if (lpMod
-> D
== D_CALCULATE
) {
414 lpMod
-> D
= computeD(lpMod
);
417 lpMod
-> Ncb
= lpMod
-> Nbb
;
419 lpMod
-> adoptedWhite
= XYZtoCAT02(lpMod
-> adoptedWhite
);
420 lpMod
-> adoptedWhite
= ChromaticAdaptation(lpMod
-> adoptedWhite
, lpMod
);
421 lpMod
-> adoptedWhite
= CAT02toHPE(lpMod
-> adoptedWhite
);
422 lpMod
-> adoptedWhite
= NonlinearCompression(lpMod
-> adoptedWhite
, lpMod
);
424 return (cmsHANDLE
) lpMod
;
428 void CMSEXPORT
cmsCIECAM02Done(cmsHANDLE hModel
)
430 cmsCIECAM02
* lpMod
= (cmsCIECAM02
*) hModel
;
432 if (lpMod
) _cmsFree(lpMod
->ContextID
, lpMod
);
436 void CMSEXPORT
cmsCIECAM02Forward(cmsHANDLE hModel
, const cmsCIEXYZ
* pIn
, cmsJCh
* pOut
)
439 cmsCIECAM02
* lpMod
= (cmsCIECAM02
*) hModel
;
441 _cmsAssert(lpMod
!= NULL
);
442 _cmsAssert(pIn
!= NULL
);
443 _cmsAssert(pOut
!= NULL
);
445 clr
.XYZ
[0] = pIn
->X
;
446 clr
.XYZ
[1] = pIn
->Y
;
447 clr
.XYZ
[2] = pIn
->Z
;
449 clr
= XYZtoCAT02(clr
);
450 clr
= ChromaticAdaptation(clr
, lpMod
);
451 clr
= CAT02toHPE(clr
);
452 clr
= NonlinearCompression(clr
, lpMod
);
453 clr
= ComputeCorrelates(clr
, lpMod
);
460 void CMSEXPORT
cmsCIECAM02Reverse(cmsHANDLE hModel
, const cmsJCh
* pIn
, cmsCIEXYZ
* pOut
)
463 cmsCIECAM02
* lpMod
= (cmsCIECAM02
*) hModel
;
465 _cmsAssert(lpMod
!= NULL
);
466 _cmsAssert(pIn
!= NULL
);
467 _cmsAssert(pOut
!= NULL
);
473 clr
= InverseCorrelates(clr
, lpMod
);
474 clr
= InverseNonlinearity(clr
, lpMod
);
475 clr
= HPEtoCAT02(clr
);
476 clr
= InverseChromaticAdaptation(clr
, lpMod
);
477 clr
= CAT02toXYZ(clr
);
479 pOut
->X
= clr
.XYZ
[0];
480 pOut
->Y
= clr
.XYZ
[1];
481 pOut
->Z
= clr
.XYZ
[2];