Home | History | Annotate | Download | only in src
      1 //---------------------------------------------------------------------------------
      2 //
      3 //  Little Color Management System
      4 //  Copyright (c) 1998-2012 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 {
     63     return (pMod -> Yb / pMod -> adoptedWhite.XYZ[1]);
     64 }
     65 
     66 static
     67 cmsFloat64Number compute_z(cmsCIECAM02* pMod)
     68 {
     69     return (1.48 + pow(pMod -> n, 0.5));
     70 }
     71 
     72 static
     73 cmsFloat64Number computeNbb(cmsCIECAM02* pMod)
     74 {
     75     return (0.725 * pow((1.0 / pMod -> n), 0.2));
     76 }
     77 
     78 static
     79 cmsFloat64Number computeFL(cmsCIECAM02* pMod)
     80 {
     81     cmsFloat64Number k, FL;
     82 
     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)));
     87 
     88     return FL;
     89 }
     90 
     91 static
     92 cmsFloat64Number computeD(cmsCIECAM02* pMod)
     93 {
     94     cmsFloat64Number D;
     95 
     96     D = pMod->F - (1.0/3.6)*(exp(((-pMod ->LA-42) / 92.0)));
     97 
     98     return D;
     99 }
    100 
    101 
    102 static
    103 CAM02COLOR XYZtoCAT02(CAM02COLOR clr)
    104 {
    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);
    108 
    109     return clr;
    110 }
    111 
    112 static
    113 CAM02COLOR ChromaticAdaptation(CAM02COLOR clr, cmsCIECAM02* pMod)
    114 {
    115     cmsUInt32Number i;
    116 
    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];
    121     }
    122 
    123     return clr;
    124 }
    125 
    126 
    127 static
    128 CAM02COLOR CAT02toHPE(CAM02COLOR clr)
    129 {
    130     cmsFloat64Number M[9];
    131 
    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));
    138     M[6] =(-0.009628);
    139     M[7] =(-0.005698);
    140     M[8] =( 1.015326);
    141 
    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]);
    145 
    146     return  clr;
    147 }
    148 
    149 static
    150 CAM02COLOR NonlinearCompression(CAM02COLOR clr, cmsCIECAM02* pMod)
    151 {
    152     cmsUInt32Number i;
    153     cmsFloat64Number temp;
    154 
    155     for (i = 0; i < 3; i++) {
    156         if (clr.RGBp[i] < 0) {
    157 
    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;
    160         }
    161         else {
    162             temp = pow((pMod->FL * clr.RGBp[i] / 100.0), 0.42);
    163             clr.RGBpa[i] = (400.0 * temp) / (temp + 27.13) + 0.1;
    164         }
    165     }
    166 
    167     clr.A = (((2.0 * clr.RGBpa[0]) + clr.RGBpa[1] +
    168         (clr.RGBpa[2] / 20.0)) - 0.305) * pMod->Nbb;
    169 
    170     return clr;
    171 }
    172 
    173 static
    174 CAM02COLOR ComputeCorrelates(CAM02COLOR clr, cmsCIECAM02* pMod)
    175 {
    176     cmsFloat64Number a, b, temp, e, t, r2d, d2r;
    177 
    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;
    180 
    181     r2d = (180.0 / 3.141592654);
    182     if (a == 0) {
    183         if (b == 0)     clr.h = 0;
    184         else if (b > 0) clr.h = 90;
    185         else            clr.h = 270;
    186     }
    187     else if (a > 0) {
    188         temp = b / a;
    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;
    192     }
    193     else {
    194         temp = b / a;
    195         clr.h = (r2d * atan(temp)) + 180;
    196     }
    197 
    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);
    201 
    202     if (clr.h < 20.14) {
    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;
    205     }
    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;
    209     }
    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);
    213     }
    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);
    217     }
    218     else {
    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);
    221     }
    222 
    223     clr.J = 100.0 * pow((clr.A / pMod->adoptedWhite.A),
    224         (pMod->c * pMod->z));
    225 
    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);
    228 
    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]));
    232 
    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);
    235 
    236     clr.M = clr.C * pow(pMod->FL, 0.25);
    237     clr.s = 100.0 * pow((clr.M / clr.Q), 0.5);
    238 
    239     return clr;
    240 }
    241 
    242 
    243 static
    244 CAM02COLOR InverseCorrelates(CAM02COLOR clr, cmsCIECAM02* pMod)
    245 {
    246 
    247     cmsFloat64Number t, e, p1, p2, p3, p4, p5, hr, d2r;
    248     d2r = 3.141592654 / 180.0;
    249 
    250     t = pow( (clr.C / (pow((clr.J / 100.0), 0.5) *
    251         (pow((1.64 - pow(0.29, pMod->n)), 0.73)))),
    252         (1.0 / 0.9) );
    253     e = ((12500.0 / 13.0) * pMod->Nc * pMod->Ncb) *
    254         (cos((clr.h * d2r + 2.0)) + 3.8);
    255 
    256     clr.A = pMod->adoptedWhite.A * pow(
    257            (clr.J / 100.0),
    258            (1.0 / (pMod->c * pMod->z)));
    259 
    260     p1 = e / t;
    261     p2 = (clr.A / pMod->Nbb) + 0.305;
    262     p3 = 21.0 / 20.0;
    263 
    264     hr = clr.h * d2r;
    265 
    266     if (fabs(sin(hr)) >= fabs(cos(hr))) {
    267         p4 = p1 / sin(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));
    273     }
    274     else {
    275         p5 = p1 / cos(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));
    281     }
    282 
    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);
    292 
    293     return clr;
    294 }
    295 
    296 static
    297 CAM02COLOR InverseNonlinearity(CAM02COLOR clr, cmsCIECAM02* pMod)
    298 {
    299     cmsUInt32Number i;
    300     cmsFloat64Number c1;
    301 
    302     for (i = 0; i < 3; i++) {
    303         if ((clr.RGBpa[i] - 0.1) < 0) c1 = -1;
    304         else                               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))),
    308             (1.0 / 0.42));
    309     }
    310 
    311     return clr;
    312 }
    313 
    314 static
    315 CAM02COLOR HPEtoCAT02(CAM02COLOR clr)
    316 {
    317     cmsFloat64Number M[9];
    318 
    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);;
    328 
    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]);
    332     return clr;
    333 }
    334 
    335 
    336 static
    337 CAM02COLOR InverseChromaticAdaptation(CAM02COLOR clr,  cmsCIECAM02* pMod)
    338 {
    339     cmsUInt32Number i;
    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);
    343     }
    344     return clr;
    345 }
    346 
    347 
    348 static
    349 CAM02COLOR CAT02toXYZ(CAM02COLOR clr)
    350 {
    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);
    354 
    355     return clr;
    356 }
    357 
    358 
    359 cmsHANDLE  CMSEXPORT cmsCIECAM02Init(cmsContext ContextID, const cmsViewingConditions* pVC)
    360 {
    361     cmsCIECAM02* lpMod;
    362 
    363     _cmsAssert(pVC != NULL);
    364 
    365     if((lpMod = (cmsCIECAM02*) _cmsMallocZero(ContextID, sizeof(cmsCIECAM02))) == NULL) {
    366         return NULL;
    367     }
    368 
    369     lpMod ->ContextID = ContextID;
    370 
    371     lpMod ->adoptedWhite.XYZ[0] = pVC ->whitePoint.X;
    372     lpMod ->adoptedWhite.XYZ[1] = pVC ->whitePoint.Y;
    373     lpMod ->adoptedWhite.XYZ[2] = pVC ->whitePoint.Z;
    374 
    375     lpMod -> LA       = pVC ->La;
    376     lpMod -> Yb       = pVC ->Yb;
    377     lpMod -> D        = pVC ->D_value;
    378     lpMod -> surround = pVC ->surround;
    379 
    380     switch (lpMod -> surround) {
    381 
    382 
    383     case CUTSHEET_SURROUND:
    384         lpMod->F = 0.8;
    385         lpMod->c = 0.41;
    386         lpMod->Nc = 0.8;
    387         break;
    388 
    389     case DARK_SURROUND:
    390         lpMod -> F  = 0.8;
    391         lpMod -> c  = 0.525;
    392         lpMod -> Nc = 0.8;
    393         break;
    394 
    395     case DIM_SURROUND:
    396         lpMod -> F  = 0.9;
    397         lpMod -> c  = 0.59;
    398         lpMod -> Nc = 0.95;
    399         break;
    400 
    401     default:
    402         // Average surround
    403         lpMod -> F  = 1.0;
    404         lpMod -> c  = 0.69;
    405         lpMod -> Nc = 1.0;
    406     }
    407 
    408     lpMod -> n   = compute_n(lpMod);
    409     lpMod -> z   = compute_z(lpMod);
    410     lpMod -> Nbb = computeNbb(lpMod);
    411     lpMod -> FL  = computeFL(lpMod);
    412 
    413     if (lpMod -> D == D_CALCULATE) {
    414         lpMod -> D   = computeD(lpMod);
    415     }
    416 
    417     lpMod -> Ncb = lpMod -> Nbb;
    418 
    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);
    423 
    424     return (cmsHANDLE) lpMod;
    425 
    426 }
    427 
    428 void CMSEXPORT cmsCIECAM02Done(cmsHANDLE hModel)
    429 {
    430     cmsCIECAM02* lpMod = (cmsCIECAM02*) hModel;
    431 
    432     if (lpMod) _cmsFree(lpMod ->ContextID, lpMod);
    433 }
    434 
    435 
    436 void CMSEXPORT cmsCIECAM02Forward(cmsHANDLE hModel, const cmsCIEXYZ* pIn, cmsJCh* pOut)
    437 {
    438     CAM02COLOR clr;
    439     cmsCIECAM02* lpMod = (cmsCIECAM02*) hModel;
    440 
    441     _cmsAssert(lpMod != NULL);
    442     _cmsAssert(pIn != NULL);
    443     _cmsAssert(pOut != NULL);
    444 
    445     memset(&clr, 0, sizeof(clr));
    446 
    447     clr.XYZ[0] = pIn ->X;
    448     clr.XYZ[1] = pIn ->Y;
    449     clr.XYZ[2] = pIn ->Z;
    450 
    451     clr = XYZtoCAT02(clr);
    452     clr = ChromaticAdaptation(clr, lpMod);
    453     clr = CAT02toHPE(clr);
    454     clr = NonlinearCompression(clr, lpMod);
    455     clr = ComputeCorrelates(clr, lpMod);
    456 
    457     pOut ->J = clr.J;
    458     pOut ->C = clr.C;
    459     pOut ->h = clr.h;
    460 }
    461 
    462 void CMSEXPORT cmsCIECAM02Reverse(cmsHANDLE hModel, const cmsJCh* pIn, cmsCIEXYZ* pOut)
    463 {
    464     CAM02COLOR clr;
    465     cmsCIECAM02* lpMod = (cmsCIECAM02*) hModel;
    466 
    467     _cmsAssert(lpMod != NULL);
    468     _cmsAssert(pIn != NULL);
    469     _cmsAssert(pOut != NULL);
    470 
    471     memset(&clr, 0, sizeof(clr));
    472 
    473     clr.J = pIn -> J;
    474     clr.C = pIn -> C;
    475     clr.h = pIn -> h;
    476 
    477     clr = InverseCorrelates(clr, lpMod);
    478     clr = InverseNonlinearity(clr, lpMod);
    479     clr = HPEtoCAT02(clr);
    480     clr = InverseChromaticAdaptation(clr, lpMod);
    481     clr = CAT02toXYZ(clr);
    482 
    483     pOut ->X = clr.XYZ[0];
    484     pOut ->Y = clr.XYZ[1];
    485     pOut ->Z = clr.XYZ[2];
    486 }
    487