1 /*
   2  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   3  *
   4  * This code is free software; you can redistribute it and/or modify it
   5  * under the terms of the GNU General Public License version 2 only, as
   6  * published by the Free Software Foundation.  Oracle designates this
   7  * particular file as subject to the "Classpath" exception as provided
   8  * by Oracle in the LICENSE file that accompanied this code.
   9  *
  10  * This code is distributed in the hope that it will be useful, but WITHOUT
  11  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  12  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  13  * version 2 for more details (a copy is included in the LICENSE file that
  14  * accompanied this code).
  15  *
  16  * You should have received a copy of the GNU General Public License version
  17  * 2 along with this work; if not, write to the Free Software Foundation,
  18  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  19  *
  20  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  21  * or visit www.oracle.com if you need additional information or have any
  22  * questions.
  23  */
  24 
  25 // This file is available under and governed by the GNU General Public
  26 // License version 2 only, as published by the Free Software Foundation.
  27 // However, the following notice accompanied the original version of this
  28 // file:
  29 //
  30 //---------------------------------------------------------------------------------
  31 //
  32 //  Little Color Management System
  33 //  Copyright (c) 1998-2020 Marti Maria Saguer
  34 //
  35 // Permission is hereby granted, free of charge, to any person obtaining
  36 // a copy of this software and associated documentation files (the "Software"),
  37 // to deal in the Software without restriction, including without limitation
  38 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
  39 // and/or sell copies of the Software, and to permit persons to whom the Software
  40 // is furnished to do so, subject to the following conditions:
  41 //
  42 // The above copyright notice and this permission notice shall be included in
  43 // all copies or substantial portions of the Software.
  44 //
  45 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  46 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
  47 // THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  48 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  49 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  50 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  51 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  52 //
  53 //---------------------------------------------------------------------------------
  54 //
  55 
  56 #include "lcms2_internal.h"
  57 
  58 
  59 // D50 - Widely used
  60 const cmsCIEXYZ* CMSEXPORT cmsD50_XYZ(void)
  61 {
  62     static cmsCIEXYZ D50XYZ = {cmsD50X, cmsD50Y, cmsD50Z};
  63 
  64     return &D50XYZ;
  65 }
  66 
  67 const cmsCIExyY* CMSEXPORT cmsD50_xyY(void)
  68 {
  69     static cmsCIExyY D50xyY;
  70 
  71     cmsXYZ2xyY(&D50xyY, cmsD50_XYZ());
  72 
  73     return &D50xyY;
  74 }
  75 
  76 // Obtains WhitePoint from Temperature
  77 cmsBool  CMSEXPORT cmsWhitePointFromTemp(cmsCIExyY* WhitePoint, cmsFloat64Number TempK)
  78 {
  79     cmsFloat64Number x, y;
  80     cmsFloat64Number T, T2, T3;
  81     // cmsFloat64Number M1, M2;
  82 
  83     _cmsAssert(WhitePoint != NULL);
  84 
  85     T = TempK;
  86     T2 = T*T;            // Square
  87     T3 = T2*T;           // Cube
  88 
  89     // For correlated color temperature (T) between 4000K and 7000K:
  90 
  91     if (T >= 4000. && T <= 7000.)
  92     {
  93         x = -4.6070*(1E9/T3) + 2.9678*(1E6/T2) + 0.09911*(1E3/T) + 0.244063;
  94     }
  95     else
  96         // or for correlated color temperature (T) between 7000K and 25000K:
  97 
  98         if (T > 7000.0 && T <= 25000.0)
  99         {
 100             x = -2.0064*(1E9/T3) + 1.9018*(1E6/T2) + 0.24748*(1E3/T) + 0.237040;
 101         }
 102         else {
 103             cmsSignalError(0, cmsERROR_RANGE, "cmsWhitePointFromTemp: invalid temp");
 104             return FALSE;
 105         }
 106 
 107     // Obtain y(x)
 108     y = -3.000*(x*x) + 2.870*x - 0.275;
 109 
 110     // wave factors (not used, but here for futures extensions)
 111 
 112     // M1 = (-1.3515 - 1.7703*x + 5.9114 *y)/(0.0241 + 0.2562*x - 0.7341*y);
 113     // M2 = (0.0300 - 31.4424*x + 30.0717*y)/(0.0241 + 0.2562*x - 0.7341*y);
 114 
 115     WhitePoint -> x = x;
 116     WhitePoint -> y = y;
 117     WhitePoint -> Y = 1.0;
 118 
 119     return TRUE;
 120 }
 121 
 122 
 123 
 124 typedef struct {
 125 
 126     cmsFloat64Number mirek;  // temp (in microreciprocal kelvin)
 127     cmsFloat64Number ut;     // u coord of intersection w/ blackbody locus
 128     cmsFloat64Number vt;     // v coord of intersection w/ blackbody locus
 129     cmsFloat64Number tt;     // slope of ISOTEMPERATURE. line
 130 
 131     } ISOTEMPERATURE;
 132 
 133 static const ISOTEMPERATURE isotempdata[] = {
 134 //  {Mirek, Ut,       Vt,      Tt      }
 135     {0,     0.18006,  0.26352,  -0.24341},
 136     {10,    0.18066,  0.26589,  -0.25479},
 137     {20,    0.18133,  0.26846,  -0.26876},
 138     {30,    0.18208,  0.27119,  -0.28539},
 139     {40,    0.18293,  0.27407,  -0.30470},
 140     {50,    0.18388,  0.27709,  -0.32675},
 141     {60,    0.18494,  0.28021,  -0.35156},
 142     {70,    0.18611,  0.28342,  -0.37915},
 143     {80,    0.18740,  0.28668,  -0.40955},
 144     {90,    0.18880,  0.28997,  -0.44278},
 145     {100,   0.19032,  0.29326,  -0.47888},
 146     {125,   0.19462,  0.30141,  -0.58204},
 147     {150,   0.19962,  0.30921,  -0.70471},
 148     {175,   0.20525,  0.31647,  -0.84901},
 149     {200,   0.21142,  0.32312,  -1.0182 },
 150     {225,   0.21807,  0.32909,  -1.2168 },
 151     {250,   0.22511,  0.33439,  -1.4512 },
 152     {275,   0.23247,  0.33904,  -1.7298 },
 153     {300,   0.24010,  0.34308,  -2.0637 },
 154     {325,   0.24702,  0.34655,  -2.4681 },
 155     {350,   0.25591,  0.34951,  -2.9641 },
 156     {375,   0.26400,  0.35200,  -3.5814 },
 157     {400,   0.27218,  0.35407,  -4.3633 },
 158     {425,   0.28039,  0.35577,  -5.3762 },
 159     {450,   0.28863,  0.35714,  -6.7262 },
 160     {475,   0.29685,  0.35823,  -8.5955 },
 161     {500,   0.30505,  0.35907,  -11.324 },
 162     {525,   0.31320,  0.35968,  -15.628 },
 163     {550,   0.32129,  0.36011,  -23.325 },
 164     {575,   0.32931,  0.36038,  -40.770 },
 165     {600,   0.33724,  0.36051,  -116.45  }
 166 };
 167 
 168 #define NISO sizeof(isotempdata)/sizeof(ISOTEMPERATURE)
 169 
 170 
 171 // Robertson's method
 172 cmsBool  CMSEXPORT cmsTempFromWhitePoint(cmsFloat64Number* TempK, const cmsCIExyY* WhitePoint)
 173 {
 174     cmsUInt32Number j;
 175     cmsFloat64Number us,vs;
 176     cmsFloat64Number uj,vj,tj,di,dj,mi,mj;
 177     cmsFloat64Number xs, ys;
 178 
 179     _cmsAssert(WhitePoint != NULL);
 180     _cmsAssert(TempK != NULL);
 181 
 182     di = mi = 0;
 183     xs = WhitePoint -> x;
 184     ys = WhitePoint -> y;
 185 
 186     // convert (x,y) to CIE 1960 (u,WhitePoint)
 187 
 188     us = (2*xs) / (-xs + 6*ys + 1.5);
 189     vs = (3*ys) / (-xs + 6*ys + 1.5);
 190 
 191 
 192     for (j=0; j < NISO; j++) {
 193 
 194         uj = isotempdata[j].ut;
 195         vj = isotempdata[j].vt;
 196         tj = isotempdata[j].tt;
 197         mj = isotempdata[j].mirek;
 198 
 199         dj = ((vs - vj) - tj * (us - uj)) / sqrt(1.0 + tj * tj);
 200 
 201         if ((j != 0) && (di/dj < 0.0)) {
 202 
 203             // Found a match
 204             *TempK = 1000000.0 / (mi + (di / (di - dj)) * (mj - mi));
 205             return TRUE;
 206         }
 207 
 208         di = dj;
 209         mi = mj;
 210     }
 211 
 212     // Not found
 213     return FALSE;
 214 }
 215 
 216 
 217 // Compute chromatic adaptation matrix using Chad as cone matrix
 218 
 219 static
 220 cmsBool ComputeChromaticAdaptation(cmsMAT3* Conversion,
 221                                 const cmsCIEXYZ* SourceWhitePoint,
 222                                 const cmsCIEXYZ* DestWhitePoint,
 223                                 const cmsMAT3* Chad)
 224 
 225 {
 226 
 227     cmsMAT3 Chad_Inv;
 228     cmsVEC3 ConeSourceXYZ, ConeSourceRGB;
 229     cmsVEC3 ConeDestXYZ, ConeDestRGB;
 230     cmsMAT3 Cone, Tmp;
 231 
 232 
 233     Tmp = *Chad;
 234     if (!_cmsMAT3inverse(&Tmp, &Chad_Inv)) return FALSE;
 235 
 236     _cmsVEC3init(&ConeSourceXYZ, SourceWhitePoint -> X,
 237                              SourceWhitePoint -> Y,
 238                              SourceWhitePoint -> Z);
 239 
 240     _cmsVEC3init(&ConeDestXYZ,   DestWhitePoint -> X,
 241                              DestWhitePoint -> Y,
 242                              DestWhitePoint -> Z);
 243 
 244     _cmsMAT3eval(&ConeSourceRGB, Chad, &ConeSourceXYZ);
 245     _cmsMAT3eval(&ConeDestRGB,   Chad, &ConeDestXYZ);
 246 
 247     // Build matrix
 248     _cmsVEC3init(&Cone.v[0], ConeDestRGB.n[0]/ConeSourceRGB.n[0],    0.0,  0.0);
 249     _cmsVEC3init(&Cone.v[1], 0.0,   ConeDestRGB.n[1]/ConeSourceRGB.n[1],   0.0);
 250     _cmsVEC3init(&Cone.v[2], 0.0,   0.0,   ConeDestRGB.n[2]/ConeSourceRGB.n[2]);
 251 
 252 
 253     // Normalize
 254     _cmsMAT3per(&Tmp, &Cone, Chad);
 255     _cmsMAT3per(Conversion, &Chad_Inv, &Tmp);
 256 
 257     return TRUE;
 258 }
 259 
 260 // Returns the final chrmatic adaptation from illuminant FromIll to Illuminant ToIll
 261 // The cone matrix can be specified in ConeMatrix. If NULL, Bradford is assumed
 262 cmsBool  _cmsAdaptationMatrix(cmsMAT3* r, const cmsMAT3* ConeMatrix, const cmsCIEXYZ* FromIll, const cmsCIEXYZ* ToIll)
 263 {
 264     cmsMAT3 LamRigg   = {{ // Bradford matrix
 265         {{  0.8951,  0.2664, -0.1614 }},
 266         {{ -0.7502,  1.7135,  0.0367 }},
 267         {{  0.0389, -0.0685,  1.0296 }}
 268     }};
 269 
 270     if (ConeMatrix == NULL)
 271         ConeMatrix = &LamRigg;
 272 
 273     return ComputeChromaticAdaptation(r, FromIll, ToIll, ConeMatrix);
 274 }
 275 
 276 // Same as anterior, but assuming D50 destination. White point is given in xyY
 277 static
 278 cmsBool _cmsAdaptMatrixToD50(cmsMAT3* r, const cmsCIExyY* SourceWhitePt)
 279 {
 280     cmsCIEXYZ Dn;
 281     cmsMAT3 Bradford;
 282     cmsMAT3 Tmp;
 283 
 284     cmsxyY2XYZ(&Dn, SourceWhitePt);
 285 
 286     if (!_cmsAdaptationMatrix(&Bradford, NULL, &Dn, cmsD50_XYZ())) return FALSE;
 287 
 288     Tmp = *r;
 289     _cmsMAT3per(r, &Bradford, &Tmp);
 290 
 291     return TRUE;
 292 }
 293 
 294 // Build a White point, primary chromas transfer matrix from RGB to CIE XYZ
 295 // This is just an approximation, I am not handling all the non-linear
 296 // aspects of the RGB to XYZ process, and assumming that the gamma correction
 297 // has transitive property in the transformation chain.
 298 //
 299 // the alghoritm:
 300 //
 301 //            - First I build the absolute conversion matrix using
 302 //              primaries in XYZ. This matrix is next inverted
 303 //            - Then I eval the source white point across this matrix
 304 //              obtaining the coeficients of the transformation
 305 //            - Then, I apply these coeficients to the original matrix
 306 //
 307 cmsBool _cmsBuildRGB2XYZtransferMatrix(cmsMAT3* r, const cmsCIExyY* WhitePt, const cmsCIExyYTRIPLE* Primrs)
 308 {
 309     cmsVEC3 WhitePoint, Coef;
 310     cmsMAT3 Result, Primaries;
 311     cmsFloat64Number xn, yn;
 312     cmsFloat64Number xr, yr;
 313     cmsFloat64Number xg, yg;
 314     cmsFloat64Number xb, yb;
 315 
 316     xn = WhitePt -> x;
 317     yn = WhitePt -> y;
 318     xr = Primrs -> Red.x;
 319     yr = Primrs -> Red.y;
 320     xg = Primrs -> Green.x;
 321     yg = Primrs -> Green.y;
 322     xb = Primrs -> Blue.x;
 323     yb = Primrs -> Blue.y;
 324 
 325     // Build Primaries matrix
 326     _cmsVEC3init(&Primaries.v[0], xr,        xg,         xb);
 327     _cmsVEC3init(&Primaries.v[1], yr,        yg,         yb);
 328     _cmsVEC3init(&Primaries.v[2], (1-xr-yr), (1-xg-yg),  (1-xb-yb));
 329 
 330 
 331     // Result = Primaries ^ (-1) inverse matrix
 332     if (!_cmsMAT3inverse(&Primaries, &Result))
 333         return FALSE;
 334 
 335 
 336     _cmsVEC3init(&WhitePoint, xn/yn, 1.0, (1.0-xn-yn)/yn);
 337 
 338     // Across inverse primaries ...
 339     _cmsMAT3eval(&Coef, &Result, &WhitePoint);
 340 
 341     // Give us the Coefs, then I build transformation matrix
 342     _cmsVEC3init(&r -> v[0], Coef.n[VX]*xr,          Coef.n[VY]*xg,          Coef.n[VZ]*xb);
 343     _cmsVEC3init(&r -> v[1], Coef.n[VX]*yr,          Coef.n[VY]*yg,          Coef.n[VZ]*yb);
 344     _cmsVEC3init(&r -> v[2], Coef.n[VX]*(1.0-xr-yr), Coef.n[VY]*(1.0-xg-yg), Coef.n[VZ]*(1.0-xb-yb));
 345 
 346 
 347     return _cmsAdaptMatrixToD50(r, WhitePt);
 348 
 349 }
 350 
 351 
 352 // Adapts a color to a given illuminant. Original color is expected to have
 353 // a SourceWhitePt white point.
 354 cmsBool CMSEXPORT cmsAdaptToIlluminant(cmsCIEXYZ* Result,
 355                                        const cmsCIEXYZ* SourceWhitePt,
 356                                        const cmsCIEXYZ* Illuminant,
 357                                        const cmsCIEXYZ* Value)
 358 {
 359     cmsMAT3 Bradford;
 360     cmsVEC3 In, Out;
 361 
 362     _cmsAssert(Result != NULL);
 363     _cmsAssert(SourceWhitePt != NULL);
 364     _cmsAssert(Illuminant != NULL);
 365     _cmsAssert(Value != NULL);
 366 
 367     if (!_cmsAdaptationMatrix(&Bradford, NULL, SourceWhitePt, Illuminant)) return FALSE;
 368 
 369     _cmsVEC3init(&In, Value -> X, Value -> Y, Value -> Z);
 370     _cmsMAT3eval(&Out, &Bradford, &In);
 371 
 372     Result -> X = Out.n[0];
 373     Result -> Y = Out.n[1];
 374     Result -> Z = Out.n[2];
 375 
 376     return TRUE;
 377 }
 378 
 379