J. Duke | 319a3b9 | 2007-12-01 00:00:00 +0000 | [diff] [blame^] | 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. Sun designates this |
| 7 | * particular file as subject to the "Classpath" exception as provided |
| 8 | * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, |
| 21 | * CA 95054 USA or visit www.sun.com if you need additional information or |
| 22 | * have any 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 | // Little cms |
| 32 | // Copyright (C) 1998-2006 Marti Maria |
| 33 | // |
| 34 | // Permission is hereby granted, free of charge, to any person obtaining |
| 35 | // a copy of this software and associated documentation files (the "Software"), |
| 36 | // to deal in the Software without restriction, including without limitation |
| 37 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, |
| 38 | // and/or sell copies of the Software, and to permit persons to whom the Software |
| 39 | // is furnished to do so, subject to the following conditions: |
| 40 | // |
| 41 | // The above copyright notice and this permission notice shall be included in |
| 42 | // all copies or substantial portions of the Software. |
| 43 | // |
| 44 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
| 45 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO |
| 46 | // THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
| 47 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |
| 48 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
| 49 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
| 50 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
| 51 | |
| 52 | #include "lcms.h" |
| 53 | |
| 54 | // Uncomment this line if you want lcms to use the black point tag in profile, |
| 55 | // if commented, lcms will compute the black point by its own. |
| 56 | // It is safer to leve it commented out |
| 57 | // #define HONOR_BLACK_POINT_TAG |
| 58 | |
| 59 | // Conversions |
| 60 | |
| 61 | void LCMSEXPORT cmsXYZ2xyY(LPcmsCIExyY Dest, const cmsCIEXYZ* Source) |
| 62 | { |
| 63 | double ISum; |
| 64 | |
| 65 | ISum = 1./(Source -> X + Source -> Y + Source -> Z); |
| 66 | |
| 67 | Dest -> x = (Source -> X) * ISum; |
| 68 | Dest -> y = (Source -> Y) * ISum; |
| 69 | Dest -> Y = Source -> Y; |
| 70 | } |
| 71 | |
| 72 | |
| 73 | void LCMSEXPORT cmsxyY2XYZ(LPcmsCIEXYZ Dest, const cmsCIExyY* Source) |
| 74 | { |
| 75 | |
| 76 | Dest -> X = (Source -> x / Source -> y) * Source -> Y; |
| 77 | Dest -> Y = Source -> Y; |
| 78 | Dest -> Z = ((1 - Source -> x - Source -> y) / Source -> y) * Source -> Y; |
| 79 | } |
| 80 | |
| 81 | |
| 82 | |
| 83 | // Obtains WhitePoint from Temperature |
| 84 | |
| 85 | BOOL LCMSEXPORT cmsWhitePointFromTemp(int TempK, LPcmsCIExyY WhitePoint) |
| 86 | { |
| 87 | double x, y; |
| 88 | double T, T2, T3; |
| 89 | // double M1, M2; |
| 90 | |
| 91 | |
| 92 | // No optimization provided. |
| 93 | |
| 94 | T = TempK; |
| 95 | T2 = T*T; // Square |
| 96 | T3 = T2*T; // Cube |
| 97 | |
| 98 | // For correlated color temperature (T) between 4000K and 7000K: |
| 99 | |
| 100 | if (T >= 4000. && T <= 7000.) |
| 101 | { |
| 102 | x = -4.6070*(1E9/T3) + 2.9678*(1E6/T2) + 0.09911*(1E3/T) + 0.244063; |
| 103 | } |
| 104 | else |
| 105 | // or for correlated color temperature (T) between 7000K and 25000K: |
| 106 | |
| 107 | if (T > 7000.0 && T <= 25000.0) |
| 108 | { |
| 109 | x = -2.0064*(1E9/T3) + 1.9018*(1E6/T2) + 0.24748*(1E3/T) + 0.237040; |
| 110 | } |
| 111 | else { |
| 112 | cmsSignalError(LCMS_ERRC_ABORTED, "cmsWhitePointFromTemp: invalid temp"); |
| 113 | return FALSE; |
| 114 | } |
| 115 | |
| 116 | // Obtain y(x) |
| 117 | |
| 118 | y = -3.000*(x*x) + 2.870*x - 0.275; |
| 119 | |
| 120 | // wave factors (not used, but here for futures extensions) |
| 121 | |
| 122 | // M1 = (-1.3515 - 1.7703*x + 5.9114 *y)/(0.0241 + 0.2562*x - 0.7341*y); |
| 123 | // M2 = (0.0300 - 31.4424*x + 30.0717*y)/(0.0241 + 0.2562*x - 0.7341*y); |
| 124 | |
| 125 | |
| 126 | |
| 127 | // Fill WhitePoint struct |
| 128 | |
| 129 | WhitePoint -> x = x; |
| 130 | WhitePoint -> y = y; |
| 131 | WhitePoint -> Y = 1.0; |
| 132 | |
| 133 | return TRUE; |
| 134 | } |
| 135 | |
| 136 | // Build a White point, primary chromas transfer matrix from RGB to CIE XYZ |
| 137 | // This is just an approximation, I am not handling all the non-linear |
| 138 | // aspects of the RGB to XYZ process, and assumming that the gamma correction |
| 139 | // has transitive property in the tranformation chain. |
| 140 | // |
| 141 | // the alghoritm: |
| 142 | // |
| 143 | // - First I build the absolute conversion matrix using |
| 144 | // primaries in XYZ. This matrix is next inverted |
| 145 | // - Then I eval the source white point across this matrix |
| 146 | // obtaining the coeficients of the transformation |
| 147 | // - Then, I apply these coeficients to the original matrix |
| 148 | |
| 149 | |
| 150 | BOOL LCMSEXPORT cmsBuildRGB2XYZtransferMatrix(LPMAT3 r, LPcmsCIExyY WhitePt, |
| 151 | LPcmsCIExyYTRIPLE Primrs) |
| 152 | { |
| 153 | VEC3 WhitePoint, Coef; |
| 154 | MAT3 Result, Primaries; |
| 155 | double xn, yn; |
| 156 | double xr, yr; |
| 157 | double xg, yg; |
| 158 | double xb, yb; |
| 159 | |
| 160 | |
| 161 | xn = WhitePt -> x; |
| 162 | yn = WhitePt -> y; |
| 163 | xr = Primrs -> Red.x; |
| 164 | yr = Primrs -> Red.y; |
| 165 | xg = Primrs -> Green.x; |
| 166 | yg = Primrs -> Green.y; |
| 167 | xb = Primrs -> Blue.x; |
| 168 | yb = Primrs -> Blue.y; |
| 169 | |
| 170 | |
| 171 | // Build Primaries matrix |
| 172 | |
| 173 | VEC3init(&Primaries.v[0], xr, xg, xb); |
| 174 | VEC3init(&Primaries.v[1], yr, yg, yb); |
| 175 | VEC3init(&Primaries.v[2], (1-xr-yr), (1-xg-yg), (1-xb-yb)); |
| 176 | |
| 177 | |
| 178 | // Result = Primaries ^ (-1) inverse matrix |
| 179 | |
| 180 | if (!MAT3inverse(&Primaries, &Result)) |
| 181 | return FALSE; |
| 182 | |
| 183 | |
| 184 | VEC3init(&WhitePoint, xn/yn, 1.0, (1.0-xn-yn)/yn); |
| 185 | |
| 186 | // Across inverse primaries ... |
| 187 | |
| 188 | MAT3eval(&Coef, &Result, &WhitePoint); |
| 189 | |
| 190 | // Give us the Coefs, then I build transformation matrix |
| 191 | |
| 192 | VEC3init(&r -> v[0], Coef.n[VX]*xr, Coef.n[VY]*xg, Coef.n[VZ]*xb); |
| 193 | VEC3init(&r -> v[1], Coef.n[VX]*yr, Coef.n[VY]*yg, Coef.n[VZ]*yb); |
| 194 | VEC3init(&r -> v[2], Coef.n[VX]*(1.0-xr-yr), Coef.n[VY]*(1.0-xg-yg), Coef.n[VZ]*(1.0-xb-yb)); |
| 195 | |
| 196 | |
| 197 | return TRUE; |
| 198 | } |
| 199 | |
| 200 | |
| 201 | |
| 202 | // Compute chromatic adaptation matrix using Chad as cone matrix |
| 203 | |
| 204 | static |
| 205 | void ComputeChromaticAdaptation(LPMAT3 Conversion, |
| 206 | LPcmsCIEXYZ SourceWhitePoint, |
| 207 | LPcmsCIEXYZ DestWhitePoint, |
| 208 | LPMAT3 Chad) |
| 209 | |
| 210 | { |
| 211 | |
| 212 | MAT3 Chad_Inv; |
| 213 | VEC3 ConeSourceXYZ, ConeSourceRGB; |
| 214 | VEC3 ConeDestXYZ, ConeDestRGB; |
| 215 | MAT3 Cone, Tmp; |
| 216 | |
| 217 | |
| 218 | Tmp = *Chad; |
| 219 | MAT3inverse(&Tmp, &Chad_Inv); |
| 220 | |
| 221 | VEC3init(&ConeSourceXYZ, SourceWhitePoint -> X, |
| 222 | SourceWhitePoint -> Y, |
| 223 | SourceWhitePoint -> Z); |
| 224 | |
| 225 | VEC3init(&ConeDestXYZ, DestWhitePoint -> X, |
| 226 | DestWhitePoint -> Y, |
| 227 | DestWhitePoint -> Z); |
| 228 | |
| 229 | MAT3eval(&ConeSourceRGB, Chad, &ConeSourceXYZ); |
| 230 | MAT3eval(&ConeDestRGB, Chad, &ConeDestXYZ); |
| 231 | |
| 232 | // Build matrix |
| 233 | |
| 234 | VEC3init(&Cone.v[0], ConeDestRGB.n[0]/ConeSourceRGB.n[0], 0.0, 0.0); |
| 235 | VEC3init(&Cone.v[1], 0.0, ConeDestRGB.n[1]/ConeSourceRGB.n[1], 0.0); |
| 236 | VEC3init(&Cone.v[2], 0.0, 0.0, ConeDestRGB.n[2]/ConeSourceRGB.n[2]); |
| 237 | |
| 238 | |
| 239 | // Normalize |
| 240 | MAT3per(&Tmp, &Cone, Chad); |
| 241 | MAT3per(Conversion, &Chad_Inv, &Tmp); |
| 242 | |
| 243 | } |
| 244 | |
| 245 | |
| 246 | // Returns the final chrmatic adaptation from illuminant FromIll to Illuminant ToIll |
| 247 | // The cone matrix can be specified in ConeMatrix. If NULL, Bradford is assumed |
| 248 | |
| 249 | BOOL cmsAdaptationMatrix(LPMAT3 r, LPMAT3 ConeMatrix, LPcmsCIEXYZ FromIll, LPcmsCIEXYZ ToIll) |
| 250 | { |
| 251 | MAT3 LamRigg = {{ // Bradford matrix |
| 252 | {{ 0.8951, 0.2664, -0.1614 }}, |
| 253 | {{ -0.7502, 1.7135, 0.0367 }}, |
| 254 | {{ 0.0389, -0.0685, 1.0296 }} |
| 255 | }}; |
| 256 | |
| 257 | |
| 258 | if (ConeMatrix == NULL) |
| 259 | ConeMatrix = &LamRigg; |
| 260 | |
| 261 | ComputeChromaticAdaptation(r, FromIll, ToIll, ConeMatrix); |
| 262 | return TRUE; |
| 263 | |
| 264 | } |
| 265 | |
| 266 | // Same as anterior, but assuming D50 destination. White point is given in xyY |
| 267 | |
| 268 | BOOL cmsAdaptMatrixToD50(LPMAT3 r, LPcmsCIExyY SourceWhitePt) |
| 269 | { |
| 270 | cmsCIEXYZ Dn; |
| 271 | MAT3 Bradford; |
| 272 | MAT3 Tmp; |
| 273 | |
| 274 | cmsxyY2XYZ(&Dn, SourceWhitePt); |
| 275 | |
| 276 | cmsAdaptationMatrix(&Bradford, NULL, &Dn, cmsD50_XYZ()); |
| 277 | |
| 278 | Tmp = *r; |
| 279 | MAT3per(r, &Bradford, &Tmp); |
| 280 | |
| 281 | return TRUE; |
| 282 | } |
| 283 | |
| 284 | |
| 285 | // Same as anterior, but assuming D50 source. White point is given in xyY |
| 286 | |
| 287 | BOOL cmsAdaptMatrixFromD50(LPMAT3 r, LPcmsCIExyY DestWhitePt) |
| 288 | { |
| 289 | cmsCIEXYZ Dn; |
| 290 | MAT3 Bradford; |
| 291 | MAT3 Tmp; |
| 292 | |
| 293 | cmsxyY2XYZ(&Dn, DestWhitePt); |
| 294 | |
| 295 | cmsAdaptationMatrix(&Bradford, NULL, cmsD50_XYZ(), &Dn); |
| 296 | |
| 297 | Tmp = *r; |
| 298 | MAT3per(r, &Bradford, &Tmp); |
| 299 | |
| 300 | return TRUE; |
| 301 | } |
| 302 | |
| 303 | |
| 304 | // Adapts a color to a given illuminant. Original color is expected to have |
| 305 | // a SourceWhitePt white point. |
| 306 | |
| 307 | BOOL LCMSEXPORT cmsAdaptToIlluminant(LPcmsCIEXYZ Result, |
| 308 | LPcmsCIEXYZ SourceWhitePt, |
| 309 | LPcmsCIEXYZ Illuminant, |
| 310 | LPcmsCIEXYZ Value) |
| 311 | { |
| 312 | MAT3 Bradford; |
| 313 | VEC3 In, Out; |
| 314 | |
| 315 | // BradfordLamRiggChromaticAdaptation(&Bradford, SourceWhitePt, Illuminant); |
| 316 | |
| 317 | cmsAdaptationMatrix(&Bradford, NULL, SourceWhitePt, Illuminant); |
| 318 | |
| 319 | VEC3init(&In, Value -> X, Value -> Y, Value -> Z); |
| 320 | MAT3eval(&Out, &Bradford, &In); |
| 321 | |
| 322 | Result -> X = Out.n[0]; |
| 323 | Result -> Y = Out.n[1]; |
| 324 | Result -> Z = Out.n[2]; |
| 325 | |
| 326 | return TRUE; |
| 327 | } |
| 328 | |
| 329 | |
| 330 | |
| 331 | typedef struct { |
| 332 | |
| 333 | double mirek; // temp (in microreciprocal kelvin) |
| 334 | double ut; // u coord of intersection w/ blackbody locus |
| 335 | double vt; // v coord of intersection w/ blackbody locus |
| 336 | double tt; // slope of ISOTEMPERATURE. line |
| 337 | |
| 338 | } ISOTEMPERATURE,FAR* LPISOTEMPERATURE; |
| 339 | |
| 340 | static ISOTEMPERATURE isotempdata[] = { |
| 341 | // {Mirek, Ut, Vt, Tt } |
| 342 | {0, 0.18006, 0.26352, -0.24341}, |
| 343 | {10, 0.18066, 0.26589, -0.25479}, |
| 344 | {20, 0.18133, 0.26846, -0.26876}, |
| 345 | {30, 0.18208, 0.27119, -0.28539}, |
| 346 | {40, 0.18293, 0.27407, -0.30470}, |
| 347 | {50, 0.18388, 0.27709, -0.32675}, |
| 348 | {60, 0.18494, 0.28021, -0.35156}, |
| 349 | {70, 0.18611, 0.28342, -0.37915}, |
| 350 | {80, 0.18740, 0.28668, -0.40955}, |
| 351 | {90, 0.18880, 0.28997, -0.44278}, |
| 352 | {100, 0.19032, 0.29326, -0.47888}, |
| 353 | {125, 0.19462, 0.30141, -0.58204}, |
| 354 | {150, 0.19962, 0.30921, -0.70471}, |
| 355 | {175, 0.20525, 0.31647, -0.84901}, |
| 356 | {200, 0.21142, 0.32312, -1.0182 }, |
| 357 | {225, 0.21807, 0.32909, -1.2168 }, |
| 358 | {250, 0.22511, 0.33439, -1.4512 }, |
| 359 | {275, 0.23247, 0.33904, -1.7298 }, |
| 360 | {300, 0.24010, 0.34308, -2.0637 }, |
| 361 | {325, 0.24702, 0.34655, -2.4681 }, |
| 362 | {350, 0.25591, 0.34951, -2.9641 }, |
| 363 | {375, 0.26400, 0.35200, -3.5814 }, |
| 364 | {400, 0.27218, 0.35407, -4.3633 }, |
| 365 | {425, 0.28039, 0.35577, -5.3762 }, |
| 366 | {450, 0.28863, 0.35714, -6.7262 }, |
| 367 | {475, 0.29685, 0.35823, -8.5955 }, |
| 368 | {500, 0.30505, 0.35907, -11.324 }, |
| 369 | {525, 0.31320, 0.35968, -15.628 }, |
| 370 | {550, 0.32129, 0.36011, -23.325 }, |
| 371 | {575, 0.32931, 0.36038, -40.770 }, |
| 372 | {600, 0.33724, 0.36051, -116.45 } |
| 373 | }; |
| 374 | |
| 375 | #define NISO sizeof(isotempdata)/sizeof(ISOTEMPERATURE) |
| 376 | |
| 377 | |
| 378 | // Robertson's method |
| 379 | |
| 380 | static |
| 381 | double Robertson(LPcmsCIExyY v) |
| 382 | { |
| 383 | int j; |
| 384 | double us,vs; |
| 385 | double uj,vj,tj,di,dj,mi,mj; |
| 386 | double Tc = -1, xs, ys; |
| 387 | |
| 388 | di = mi = 0; |
| 389 | xs = v -> x; |
| 390 | ys = v -> y; |
| 391 | |
| 392 | // convert (x,y) to CIE 1960 (u,v) |
| 393 | |
| 394 | us = (2*xs) / (-xs + 6*ys + 1.5); |
| 395 | vs = (3*ys) / (-xs + 6*ys + 1.5); |
| 396 | |
| 397 | |
| 398 | for (j=0; j < NISO; j++) { |
| 399 | |
| 400 | uj = isotempdata[j].ut; |
| 401 | vj = isotempdata[j].vt; |
| 402 | tj = isotempdata[j].tt; |
| 403 | mj = isotempdata[j].mirek; |
| 404 | |
| 405 | dj = ((vs - vj) - tj * (us - uj)) / sqrt(1 + tj*tj); |
| 406 | |
| 407 | |
| 408 | |
| 409 | if ((j!=0) && (di/dj < 0.0)) { |
| 410 | Tc = 1000000.0 / (mi + (di / (di - dj)) * (mj - mi)); |
| 411 | break; |
| 412 | } |
| 413 | |
| 414 | di = dj; |
| 415 | mi = mj; |
| 416 | } |
| 417 | |
| 418 | |
| 419 | if (j == NISO) return -1; |
| 420 | return Tc; |
| 421 | } |
| 422 | |
| 423 | |
| 424 | |
| 425 | static |
| 426 | BOOL InRange(LPcmsCIExyY a, LPcmsCIExyY b, double tolerance) |
| 427 | { |
| 428 | double dist_x, dist_y; |
| 429 | |
| 430 | dist_x = fabs(a->x - b->x); |
| 431 | dist_y = fabs(a->y - b->y); |
| 432 | |
| 433 | return (tolerance >= dist_x * dist_x + dist_y * dist_y); |
| 434 | |
| 435 | } |
| 436 | |
| 437 | |
| 438 | typedef struct { |
| 439 | char Name[30]; |
| 440 | cmsCIExyY Val; |
| 441 | |
| 442 | } WHITEPOINTS,FAR *LPWHITEPOINTS; |
| 443 | |
| 444 | static |
| 445 | int FromD40toD150(LPWHITEPOINTS pts) |
| 446 | { |
| 447 | int i, n; |
| 448 | |
| 449 | n = 0; |
| 450 | for (i=40; i < 150; i ++) |
| 451 | { |
| 452 | sprintf(pts[n].Name, "D%d", i); |
| 453 | cmsWhitePointFromTemp((int) (i*100.0), &pts[n].Val); |
| 454 | n++; |
| 455 | } |
| 456 | |
| 457 | return n; |
| 458 | } |
| 459 | |
| 460 | |
| 461 | void _cmsIdentifyWhitePoint(char *Buffer, LPcmsCIEXYZ WhitePt) |
| 462 | { |
| 463 | int i, n; |
| 464 | cmsCIExyY Val; |
| 465 | double T; |
| 466 | WHITEPOINTS SomeIlluminants[140] = { |
| 467 | |
| 468 | {"CIE illuminant A", {0.4476, 0.4074, 1.0}}, |
| 469 | {"CIE illuminant C", {0.3101, 0.3162, 1.0}}, |
| 470 | {"D65 (daylight)", {0.3127, 0.3291, 1.0}}, |
| 471 | }; |
| 472 | |
| 473 | n = FromD40toD150(&SomeIlluminants[3]) + 3; |
| 474 | |
| 475 | cmsXYZ2xyY(&Val, WhitePt); |
| 476 | |
| 477 | Val.Y = 1.; |
| 478 | for (i=0; i < n; i++) |
| 479 | { |
| 480 | |
| 481 | if (InRange(&Val, &SomeIlluminants[i].Val, 0.000005)) |
| 482 | { |
| 483 | strcpy(Buffer, "WhitePoint : "); |
| 484 | strcat(Buffer, SomeIlluminants[i].Name); |
| 485 | return; |
| 486 | } |
| 487 | } |
| 488 | |
| 489 | T = Robertson(&Val); |
| 490 | |
| 491 | if (T > 0) |
| 492 | sprintf(Buffer, "White point near %dK", (int) T); |
| 493 | else |
| 494 | { |
| 495 | sprintf(Buffer, "Unknown white point (X:%1.2g, Y:%1.2g, Z:%1.2g)", |
| 496 | WhitePt -> X, WhitePt -> Y, WhitePt -> Z); |
| 497 | |
| 498 | } |
| 499 | |
| 500 | } |
| 501 | |
| 502 | |
| 503 | // Use darker colorant to obtain black point |
| 504 | |
| 505 | static |
| 506 | int BlackPointAsDarkerColorant(cmsHPROFILE hInput, |
| 507 | int Intent, |
| 508 | LPcmsCIEXYZ BlackPoint, |
| 509 | DWORD dwFlags) |
| 510 | { |
| 511 | WORD *Black, *White; |
| 512 | cmsHTRANSFORM xform; |
| 513 | icColorSpaceSignature Space; |
| 514 | int nChannels; |
| 515 | DWORD dwFormat; |
| 516 | cmsHPROFILE hLab; |
| 517 | cmsCIELab Lab; |
| 518 | cmsCIEXYZ BlackXYZ, MediaWhite; |
| 519 | |
| 520 | // If the profile does not support input direction, assume Black point 0 |
| 521 | |
| 522 | if (!cmsIsIntentSupported(hInput, Intent, LCMS_USED_AS_INPUT)) { |
| 523 | |
| 524 | BlackPoint -> X = BlackPoint ->Y = BlackPoint -> Z = 0.0; |
| 525 | return 0; |
| 526 | } |
| 527 | |
| 528 | |
| 529 | // Try to get black by using black colorant |
| 530 | |
| 531 | Space = cmsGetColorSpace(hInput); |
| 532 | |
| 533 | if (!_cmsEndPointsBySpace(Space, &White, &Black, &nChannels)) { |
| 534 | |
| 535 | BlackPoint -> X = BlackPoint ->Y = BlackPoint -> Z = 0.0; |
| 536 | return 0; |
| 537 | } |
| 538 | |
| 539 | dwFormat = CHANNELS_SH(nChannels)|BYTES_SH(2); |
| 540 | |
| 541 | hLab = cmsCreateLabProfile(NULL); |
| 542 | |
| 543 | xform = cmsCreateTransform(hInput, dwFormat, |
| 544 | hLab, TYPE_Lab_DBL, Intent, cmsFLAGS_NOTPRECALC); |
| 545 | |
| 546 | |
| 547 | cmsDoTransform(xform, Black, &Lab, 1); |
| 548 | |
| 549 | // Force it to be neutral, clip to max. L* of 50 |
| 550 | |
| 551 | Lab.a = Lab.b = 0; |
| 552 | if (Lab.L > 50) Lab.L = 50; |
| 553 | |
| 554 | cmsCloseProfile(hLab); |
| 555 | cmsDeleteTransform(xform); |
| 556 | |
| 557 | cmsLab2XYZ(NULL, &BlackXYZ, &Lab); |
| 558 | |
| 559 | if (Intent == INTENT_ABSOLUTE_COLORIMETRIC) { |
| 560 | |
| 561 | *BlackPoint = BlackXYZ; |
| 562 | } |
| 563 | else { |
| 564 | |
| 565 | if (!(dwFlags & LCMS_BPFLAGS_D50_ADAPTED)) { |
| 566 | |
| 567 | cmsTakeMediaWhitePoint(&MediaWhite, hInput); |
| 568 | cmsAdaptToIlluminant(BlackPoint, cmsD50_XYZ(), &MediaWhite, &BlackXYZ); |
| 569 | } |
| 570 | else |
| 571 | *BlackPoint = BlackXYZ; |
| 572 | } |
| 573 | |
| 574 | return 1; |
| 575 | } |
| 576 | |
| 577 | |
| 578 | // Get a black point of output CMYK profile, discounting any ink-limiting embedded |
| 579 | // in the profile. Fou doing that, use perceptual intent in input direction: |
| 580 | // Lab (0, 0, 0) -> [Perceptual] Profile -> CMYK -> [Rel. colorimetric] Profile -> Lab |
| 581 | |
| 582 | static |
| 583 | int BlackPointUsingPerceptualBlack(LPcmsCIEXYZ BlackPoint, |
| 584 | cmsHPROFILE hProfile, |
| 585 | DWORD dwFlags) |
| 586 | { |
| 587 | cmsHTRANSFORM hPercLab2CMYK, hRelColCMYK2Lab; |
| 588 | cmsHPROFILE hLab; |
| 589 | cmsCIELab LabIn, LabOut; |
| 590 | WORD CMYK[MAXCHANNELS]; |
| 591 | cmsCIEXYZ BlackXYZ, MediaWhite; |
| 592 | |
| 593 | |
| 594 | if (!cmsIsIntentSupported(hProfile, INTENT_PERCEPTUAL, LCMS_USED_AS_INPUT)) { |
| 595 | |
| 596 | BlackPoint -> X = BlackPoint ->Y = BlackPoint -> Z = 0.0; |
| 597 | return 0; |
| 598 | } |
| 599 | |
| 600 | hLab = cmsCreateLabProfile(NULL); |
| 601 | |
| 602 | hPercLab2CMYK = cmsCreateTransform(hLab, TYPE_Lab_DBL, |
| 603 | hProfile, TYPE_CMYK_16, |
| 604 | INTENT_PERCEPTUAL, cmsFLAGS_NOTPRECALC); |
| 605 | |
| 606 | hRelColCMYK2Lab = cmsCreateTransform(hProfile, TYPE_CMYK_16, |
| 607 | hLab, TYPE_Lab_DBL, |
| 608 | INTENT_RELATIVE_COLORIMETRIC, cmsFLAGS_NOTPRECALC); |
| 609 | |
| 610 | LabIn.L = LabIn.a = LabIn.b = 0; |
| 611 | |
| 612 | cmsDoTransform(hPercLab2CMYK, &LabIn, CMYK, 1); |
| 613 | cmsDoTransform(hRelColCMYK2Lab, CMYK, &LabOut, 1); |
| 614 | |
| 615 | if (LabOut.L > 50) LabOut.L = 50; |
| 616 | LabOut.a = LabOut.b = 0; |
| 617 | |
| 618 | cmsDeleteTransform(hPercLab2CMYK); |
| 619 | cmsDeleteTransform(hRelColCMYK2Lab); |
| 620 | cmsCloseProfile(hLab); |
| 621 | |
| 622 | cmsLab2XYZ(NULL, &BlackXYZ, &LabOut); |
| 623 | |
| 624 | if (!(dwFlags & LCMS_BPFLAGS_D50_ADAPTED)){ |
| 625 | cmsTakeMediaWhitePoint(&MediaWhite, hProfile); |
| 626 | cmsAdaptToIlluminant(BlackPoint, cmsD50_XYZ(), &MediaWhite, &BlackXYZ); |
| 627 | } |
| 628 | else |
| 629 | *BlackPoint = BlackXYZ; |
| 630 | |
| 631 | return 1; |
| 632 | |
| 633 | } |
| 634 | |
| 635 | |
| 636 | // Get Perceptual black of v4 profiles. |
| 637 | static |
| 638 | int GetV4PerceptualBlack(LPcmsCIEXYZ BlackPoint, cmsHPROFILE hProfile, DWORD dwFlags) |
| 639 | { |
| 640 | if (dwFlags & LCMS_BPFLAGS_D50_ADAPTED) { |
| 641 | |
| 642 | BlackPoint->X = PERCEPTUAL_BLACK_X; |
| 643 | BlackPoint->Y = PERCEPTUAL_BLACK_Y; |
| 644 | BlackPoint->Z = PERCEPTUAL_BLACK_Z; |
| 645 | } |
| 646 | else { |
| 647 | |
| 648 | cmsCIEXYZ D50BlackPoint, MediaWhite; |
| 649 | |
| 650 | cmsTakeMediaWhitePoint(&MediaWhite, hProfile); |
| 651 | D50BlackPoint.X = PERCEPTUAL_BLACK_X; |
| 652 | D50BlackPoint.Y = PERCEPTUAL_BLACK_Y; |
| 653 | D50BlackPoint.Z = PERCEPTUAL_BLACK_Z; |
| 654 | cmsAdaptToIlluminant(BlackPoint, cmsD50_XYZ(), &MediaWhite, &D50BlackPoint); |
| 655 | } |
| 656 | |
| 657 | |
| 658 | return 1; |
| 659 | } |
| 660 | |
| 661 | |
| 662 | // This function shouldn't exist at all -- there is such quantity of broken |
| 663 | // profiles on black point tag, that we must somehow fix chromaticity to |
| 664 | // avoid huge tint when doing Black point compensation. This function does |
| 665 | // just that. If BP is specified, then forces it to neutral and uses only L |
| 666 | // component. If does not exist, computes it by taking 400% of ink or RGB=0 This |
| 667 | // works well on relative intent and is undefined on perceptual & saturation. |
| 668 | // However, I will support all intents for tricking & trapping. |
| 669 | |
| 670 | |
| 671 | int cmsDetectBlackPoint(LPcmsCIEXYZ BlackPoint, cmsHPROFILE hProfile, int Intent, DWORD dwFlags) |
| 672 | { |
| 673 | |
| 674 | // v4 + perceptual & saturation intents does have its own black point |
| 675 | |
| 676 | if ((cmsGetProfileICCversion(hProfile) >= 0x4000000) && |
| 677 | (Intent == INTENT_PERCEPTUAL || Intent == INTENT_SATURATION)) { |
| 678 | |
| 679 | // Matrix shaper share MRC & perceptual intents |
| 680 | |
| 681 | if (_cmsIsMatrixShaper(hProfile)) |
| 682 | return BlackPointAsDarkerColorant(hProfile, INTENT_RELATIVE_COLORIMETRIC, BlackPoint, cmsFLAGS_NOTPRECALC); |
| 683 | |
| 684 | // Get fixed value |
| 685 | return GetV4PerceptualBlack(BlackPoint, hProfile, dwFlags); |
| 686 | } |
| 687 | |
| 688 | |
| 689 | #ifdef HONOR_BLACK_POINT_TAG |
| 690 | |
| 691 | // v2, v4 rel/abs colorimetric |
| 692 | if (cmsIsTag(hProfile, icSigMediaBlackPointTag) && |
| 693 | Intent == INTENT_RELATIVE_COLORIMETRIC) { |
| 694 | |
| 695 | cmsCIEXYZ BlackXYZ, UntrustedBlackPoint, TrustedBlackPoint, MediaWhite; |
| 696 | cmsCIELab Lab; |
| 697 | |
| 698 | // If black point is specified, then use it, |
| 699 | |
| 700 | cmsTakeMediaBlackPoint(&BlackXYZ, hProfile); |
| 701 | cmsTakeMediaWhitePoint(&MediaWhite, hProfile); |
| 702 | |
| 703 | // Black point is absolute XYZ, so adapt to D50 to get PCS value |
| 704 | |
| 705 | cmsAdaptToIlluminant(&UntrustedBlackPoint, &MediaWhite, cmsD50_XYZ(), &BlackXYZ); |
| 706 | |
| 707 | // Force a=b=0 to get rid of any chroma |
| 708 | |
| 709 | cmsXYZ2Lab(NULL, &Lab, &UntrustedBlackPoint); |
| 710 | Lab.a = Lab.b = 0; |
| 711 | if (Lab.L > 50) Lab.L = 50; // Clip to L* <= 50 |
| 712 | |
| 713 | cmsLab2XYZ(NULL, &TrustedBlackPoint, &Lab); |
| 714 | |
| 715 | // Return BP as D50 relative or absolute XYZ (depends on flags) |
| 716 | |
| 717 | if (!(dwFlags & LCMS_BPFLAGS_D50_ADAPTED)) |
| 718 | cmsAdaptToIlluminant(BlackPoint, cmsD50_XYZ(), &MediaWhite, &TrustedBlackPoint); |
| 719 | else |
| 720 | *BlackPoint = TrustedBlackPoint; |
| 721 | |
| 722 | return 1; |
| 723 | } |
| 724 | |
| 725 | #endif |
| 726 | |
| 727 | // If output profile, discount ink-limiting |
| 728 | |
| 729 | if (Intent == INTENT_RELATIVE_COLORIMETRIC && |
| 730 | (cmsGetDeviceClass(hProfile) == icSigOutputClass) && |
| 731 | (cmsGetColorSpace(hProfile) == icSigCmykData)) |
| 732 | return BlackPointUsingPerceptualBlack(BlackPoint, hProfile, dwFlags); |
| 733 | |
| 734 | // Nope, compute BP using current intent. |
| 735 | |
| 736 | return BlackPointAsDarkerColorant(hProfile, Intent, BlackPoint, dwFlags); |
| 737 | |
| 738 | } |