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-2012 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 // Link several profiles to obtain a single LUT modelling the whole color transform. Intents, Black point 60 // compensation and Adaptation parameters may vary across profiles. BPC and Adaptation refers to the PCS 61 // after the profile. I.e, BPC[0] refers to connexion between profile(0) and profile(1) 62 cmsPipeline* _cmsLinkProfiles(cmsContext ContextID, 63 cmsUInt32Number nProfiles, 64 cmsUInt32Number Intents[], 65 cmsHPROFILE hProfiles[], 66 cmsBool BPC[], 67 cmsFloat64Number AdaptationStates[], 68 cmsUInt32Number dwFlags); 69 70 //--------------------------------------------------------------------------------- 71 72 // This is the default routine for ICC-style intents. A user may decide to override it by using a plugin. 73 // Supported intents are perceptual, relative colorimetric, saturation and ICC-absolute colorimetric 74 static 75 cmsPipeline* DefaultICCintents(cmsContext ContextID, 76 cmsUInt32Number nProfiles, 77 cmsUInt32Number Intents[], 78 cmsHPROFILE hProfiles[], 79 cmsBool BPC[], 80 cmsFloat64Number AdaptationStates[], 81 cmsUInt32Number dwFlags); 82 83 //--------------------------------------------------------------------------------- 84 85 // This is the entry for black-preserving K-only intents, which are non-ICC. Last profile have to be a output profile 86 // to do the trick (no devicelinks allowed at that position) 87 static 88 cmsPipeline* BlackPreservingKOnlyIntents(cmsContext ContextID, 89 cmsUInt32Number nProfiles, 90 cmsUInt32Number Intents[], 91 cmsHPROFILE hProfiles[], 92 cmsBool BPC[], 93 cmsFloat64Number AdaptationStates[], 94 cmsUInt32Number dwFlags); 95 96 //--------------------------------------------------------------------------------- 97 98 // This is the entry for black-plane preserving, which are non-ICC. Again, Last profile have to be a output profile 99 // to do the trick (no devicelinks allowed at that position) 100 static 101 cmsPipeline* BlackPreservingKPlaneIntents(cmsContext ContextID, 102 cmsUInt32Number nProfiles, 103 cmsUInt32Number Intents[], 104 cmsHPROFILE hProfiles[], 105 cmsBool BPC[], 106 cmsFloat64Number AdaptationStates[], 107 cmsUInt32Number dwFlags); 108 109 //--------------------------------------------------------------------------------- 110 111 112 // This is a structure holding implementations for all supported intents. 113 typedef struct _cms_intents_list { 114 115 cmsUInt32Number Intent; 116 char Description[256]; 117 cmsIntentFn Link; 118 struct _cms_intents_list* Next; 119 120 } cmsIntentsList; 121 122 123 // Built-in intents 124 static cmsIntentsList DefaultIntents[] = { 125 126 { INTENT_PERCEPTUAL, "Perceptual", DefaultICCintents, &DefaultIntents[1] }, 127 { INTENT_RELATIVE_COLORIMETRIC, "Relative colorimetric", DefaultICCintents, &DefaultIntents[2] }, 128 { INTENT_SATURATION, "Saturation", DefaultICCintents, &DefaultIntents[3] }, 129 { INTENT_ABSOLUTE_COLORIMETRIC, "Absolute colorimetric", DefaultICCintents, &DefaultIntents[4] }, 130 { INTENT_PRESERVE_K_ONLY_PERCEPTUAL, "Perceptual preserving black ink", BlackPreservingKOnlyIntents, &DefaultIntents[5] }, 131 { INTENT_PRESERVE_K_ONLY_RELATIVE_COLORIMETRIC, "Relative colorimetric preserving black ink", BlackPreservingKOnlyIntents, &DefaultIntents[6] }, 132 { INTENT_PRESERVE_K_ONLY_SATURATION, "Saturation preserving black ink", BlackPreservingKOnlyIntents, &DefaultIntents[7] }, 133 { INTENT_PRESERVE_K_PLANE_PERCEPTUAL, "Perceptual preserving black plane", BlackPreservingKPlaneIntents, &DefaultIntents[8] }, 134 { INTENT_PRESERVE_K_PLANE_RELATIVE_COLORIMETRIC,"Relative colorimetric preserving black plane", BlackPreservingKPlaneIntents, &DefaultIntents[9] }, 135 { INTENT_PRESERVE_K_PLANE_SATURATION, "Saturation preserving black plane", BlackPreservingKPlaneIntents, NULL } 136 }; 137 138 139 // A pointer to the begining of the list 140 static cmsIntentsList *Intents = DefaultIntents; 141 142 // Search the list for a suitable intent. Returns NULL if not found 143 static 144 cmsIntentsList* SearchIntent(cmsUInt32Number Intent) 145 { 146 cmsIntentsList* pt; 147 148 for (pt = Intents; pt != NULL; pt = pt -> Next) 149 if (pt ->Intent == Intent) return pt; 150 151 return NULL; 152 } 153 154 // Black point compensation. Implemented as a linear scaling in XYZ. Black points 155 // should come relative to the white point. Fills an matrix/offset element m 156 // which is organized as a 4x4 matrix. 157 static 158 void ComputeBlackPointCompensation(const cmsCIEXYZ* BlackPointIn, 159 const cmsCIEXYZ* BlackPointOut, 160 cmsMAT3* m, cmsVEC3* off) 161 { 162 cmsFloat64Number ax, ay, az, bx, by, bz, tx, ty, tz; 163 164 // Now we need to compute a matrix plus an offset m and of such of 165 // [m]*bpin + off = bpout 166 // [m]*D50 + off = D50 167 // 168 // This is a linear scaling in the form ax+b, where 169 // a = (bpout - D50) / (bpin - D50) 170 // b = - D50* (bpout - bpin) / (bpin - D50) 171 172 tx = BlackPointIn->X - cmsD50_XYZ()->X; 173 ty = BlackPointIn->Y - cmsD50_XYZ()->Y; 174 tz = BlackPointIn->Z - cmsD50_XYZ()->Z; 175 176 ax = (BlackPointOut->X - cmsD50_XYZ()->X) / tx; 177 ay = (BlackPointOut->Y - cmsD50_XYZ()->Y) / ty; 178 az = (BlackPointOut->Z - cmsD50_XYZ()->Z) / tz; 179 180 bx = - cmsD50_XYZ()-> X * (BlackPointOut->X - BlackPointIn->X) / tx; 181 by = - cmsD50_XYZ()-> Y * (BlackPointOut->Y - BlackPointIn->Y) / ty; 182 bz = - cmsD50_XYZ()-> Z * (BlackPointOut->Z - BlackPointIn->Z) / tz; 183 184 _cmsVEC3init(&m ->v[0], ax, 0, 0); 185 _cmsVEC3init(&m ->v[1], 0, ay, 0); 186 _cmsVEC3init(&m ->v[2], 0, 0, az); 187 _cmsVEC3init(off, bx, by, bz); 188 189 } 190 191 192 // Approximate a blackbody illuminant based on CHAD information 193 static 194 cmsFloat64Number CHAD2Temp(const cmsMAT3* Chad) 195 { 196 // Convert D50 across inverse CHAD to get the absolute white point 197 cmsVEC3 d, s; 198 cmsCIEXYZ Dest; 199 cmsCIExyY DestChromaticity; 200 cmsFloat64Number TempK; 201 cmsMAT3 m1, m2; 202 203 m1 = *Chad; 204 if (!_cmsMAT3inverse(&m1, &m2)) return FALSE; 205 206 s.n[VX] = cmsD50_XYZ() -> X; 207 s.n[VY] = cmsD50_XYZ() -> Y; 208 s.n[VZ] = cmsD50_XYZ() -> Z; 209 210 _cmsMAT3eval(&d, &m2, &s); 211 212 Dest.X = d.n[VX]; 213 Dest.Y = d.n[VY]; 214 Dest.Z = d.n[VZ]; 215 216 cmsXYZ2xyY(&DestChromaticity, &Dest); 217 218 if (!cmsTempFromWhitePoint(&TempK, &DestChromaticity)) 219 return -1.0; 220 221 return TempK; 222 } 223 224 // Compute a CHAD based on a given temperature 225 static 226 void Temp2CHAD(cmsMAT3* Chad, cmsFloat64Number Temp) 227 { 228 cmsCIEXYZ White; 229 cmsCIExyY ChromaticityOfWhite; 230 231 cmsWhitePointFromTemp(&ChromaticityOfWhite, Temp); 232 cmsxyY2XYZ(&White, &ChromaticityOfWhite); 233 _cmsAdaptationMatrix(Chad, NULL, &White, cmsD50_XYZ()); 234 } 235 236 // Join scalings to obtain relative input to absolute and then to relative output. 237 // Result is stored in a 3x3 matrix 238 static 239 cmsBool ComputeAbsoluteIntent(cmsFloat64Number AdaptationState, 240 const cmsCIEXYZ* WhitePointIn, 241 const cmsMAT3* ChromaticAdaptationMatrixIn, 242 const cmsCIEXYZ* WhitePointOut, 243 const cmsMAT3* ChromaticAdaptationMatrixOut, 244 cmsMAT3* m) 245 { 246 cmsMAT3 Scale, m1, m2, m3, m4; 247 248 // Adaptation state 249 if (AdaptationState == 1.0) { 250 251 // Observer is fully adapted. Keep chromatic adaptation. 252 // That is the standard V4 behaviour 253 _cmsVEC3init(&m->v[0], WhitePointIn->X / WhitePointOut->X, 0, 0); 254 _cmsVEC3init(&m->v[1], 0, WhitePointIn->Y / WhitePointOut->Y, 0); 255 _cmsVEC3init(&m->v[2], 0, 0, WhitePointIn->Z / WhitePointOut->Z); 256 257 } 258 else { 259 260 // Incomplete adaptation. This is an advanced feature. 261 _cmsVEC3init(&Scale.v[0], WhitePointIn->X / WhitePointOut->X, 0, 0); 262 _cmsVEC3init(&Scale.v[1], 0, WhitePointIn->Y / WhitePointOut->Y, 0); 263 _cmsVEC3init(&Scale.v[2], 0, 0, WhitePointIn->Z / WhitePointOut->Z); 264 265 266 if (AdaptationState == 0.0) { 267 268 m1 = *ChromaticAdaptationMatrixOut; 269 _cmsMAT3per(&m2, &m1, &Scale); 270 // m2 holds CHAD from output white to D50 times abs. col. scaling 271 272 // Observer is not adapted, undo the chromatic adaptation 273 _cmsMAT3per(m, &m3, ChromaticAdaptationMatrixOut); 274 275 m3 = *ChromaticAdaptationMatrixIn; 276 if (!_cmsMAT3inverse(&m3, &m4)) return FALSE; 277 _cmsMAT3per(m, &m2, &m4); 278 279 } else { 280 281 cmsMAT3 MixedCHAD; 282 cmsFloat64Number TempSrc, TempDest, Temp; 283 284 m1 = *ChromaticAdaptationMatrixIn; 285 if (!_cmsMAT3inverse(&m1, &m2)) return FALSE; 286 _cmsMAT3per(&m3, &m2, &Scale); 287 // m3 holds CHAD from input white to D50 times abs. col. scaling 288 289 TempSrc = CHAD2Temp(ChromaticAdaptationMatrixIn); 290 TempDest = CHAD2Temp(ChromaticAdaptationMatrixOut); 291 292 if (TempSrc < 0.0 || TempDest < 0.0) return FALSE; // Something went wrong 293 294 if (_cmsMAT3isIdentity(&Scale) && fabs(TempSrc - TempDest) < 0.01) { 295 296 _cmsMAT3identity(m); 297 return TRUE; 298 } 299 300 Temp = (1.0 - AdaptationState) * TempDest + AdaptationState * TempSrc; 301 302 // Get a CHAD from whatever output temperature to D50. This replaces output CHAD 303 Temp2CHAD(&MixedCHAD, Temp); 304 305 _cmsMAT3per(m, &m3, &MixedCHAD); 306 } 307 308 } 309 return TRUE; 310 311 } 312 313 // Just to see if m matrix should be applied 314 static 315 cmsBool IsEmptyLayer(cmsMAT3* m, cmsVEC3* off) 316 { 317 cmsFloat64Number diff = 0; 318 cmsMAT3 Ident; 319 int i; 320 321 if (m == NULL && off == NULL) return TRUE; // NULL is allowed as an empty layer 322 if (m == NULL && off != NULL) return FALSE; // This is an internal error 323 324 _cmsMAT3identity(&Ident); 325 326 for (i=0; i < 3*3; i++) 327 diff += fabs(((cmsFloat64Number*)m)[i] - ((cmsFloat64Number*)&Ident)[i]); 328 329 for (i=0; i < 3; i++) 330 diff += fabs(((cmsFloat64Number*)off)[i]); 331 332 333 return (diff < 0.002); 334 } 335 336 337 // Compute the conversion layer 338 static 339 cmsBool ComputeConversion(int i, cmsHPROFILE hProfiles[], 340 cmsUInt32Number Intent, 341 cmsBool BPC, 342 cmsFloat64Number AdaptationState, 343 cmsMAT3* m, cmsVEC3* off) 344 { 345 346 int k; 347 348 // m and off are set to identity and this is detected latter on 349 _cmsMAT3identity(m); 350 _cmsVEC3init(off, 0, 0, 0); 351 352 // If intent is abs. colorimetric, 353 if (Intent == INTENT_ABSOLUTE_COLORIMETRIC) { 354 355 cmsCIEXYZ WhitePointIn, WhitePointOut; 356 cmsMAT3 ChromaticAdaptationMatrixIn, ChromaticAdaptationMatrixOut; 357 358 _cmsReadMediaWhitePoint(&WhitePointIn, hProfiles[i-1]); 359 _cmsReadCHAD(&ChromaticAdaptationMatrixIn, hProfiles[i-1]); 360 361 _cmsReadMediaWhitePoint(&WhitePointOut, hProfiles[i]); 362 _cmsReadCHAD(&ChromaticAdaptationMatrixOut, hProfiles[i]); 363 364 if (!ComputeAbsoluteIntent(AdaptationState, 365 &WhitePointIn, &ChromaticAdaptationMatrixIn, 366 &WhitePointOut, &ChromaticAdaptationMatrixOut, m)) return FALSE; 367 368 } 369 else { 370 // Rest of intents may apply BPC. 371 372 if (BPC) { 373 374 cmsCIEXYZ BlackPointIn, BlackPointOut; 375 376 cmsDetectBlackPoint(&BlackPointIn, hProfiles[i-1], Intent, 0); 377 cmsDetectDestinationBlackPoint(&BlackPointOut, hProfiles[i], Intent, 0); 378 379 // If black points are equal, then do nothing 380 if (BlackPointIn.X != BlackPointOut.X || 381 BlackPointIn.Y != BlackPointOut.Y || 382 BlackPointIn.Z != BlackPointOut.Z) 383 ComputeBlackPointCompensation(&BlackPointIn, &BlackPointOut, m, off); 384 } 385 } 386 387 // Offset should be adjusted because the encoding. We encode XYZ normalized to 0..1.0, 388 // to do that, we divide by MAX_ENCODEABLE_XZY. The conversion stage goes XYZ -> XYZ so 389 // we have first to convert from encoded to XYZ and then convert back to encoded. 390 // y = Mx + Off 391 // x = x'c 392 // y = M x'c + Off 393 // y = y'c; y' = y / c 394 // y' = (Mx'c + Off) /c = Mx' + (Off / c) 395 396 for (k=0; k < 3; k++) { 397 off ->n[k] /= MAX_ENCODEABLE_XYZ; 398 } 399 400 return TRUE; 401 } 402 403 404 // Add a conversion stage if needed. If a matrix/offset m is given, it applies to XYZ space 405 static 406 cmsBool AddConversion(cmsPipeline* Result, cmsColorSpaceSignature InPCS, cmsColorSpaceSignature OutPCS, cmsMAT3* m, cmsVEC3* off) 407 { 408 cmsFloat64Number* m_as_dbl = (cmsFloat64Number*) m; 409 cmsFloat64Number* off_as_dbl = (cmsFloat64Number*) off; 410 411 // Handle PCS mismatches. A specialized stage is added to the LUT in such case 412 switch (InPCS) { 413 414 case cmsSigXYZData: // Input profile operates in XYZ 415 416 switch (OutPCS) { 417 418 case cmsSigXYZData: // XYZ -> XYZ 419 if (!IsEmptyLayer(m, off)) 420 cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl)); 421 break; 422 423 case cmsSigLabData: // XYZ -> Lab 424 if (!IsEmptyLayer(m, off)) 425 cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl)); 426 cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocXYZ2Lab(Result ->ContextID)); 427 break; 428 429 default: 430 return FALSE; // Colorspace mismatch 431 } 432 break; 433 434 435 case cmsSigLabData: // Input profile operates in Lab 436 437 switch (OutPCS) { 438 439 case cmsSigXYZData: // Lab -> XYZ 440 441 cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocLab2XYZ(Result ->ContextID)); 442 if (!IsEmptyLayer(m, off)) 443 cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl)); 444 break; 445 446 case cmsSigLabData: // Lab -> Lab 447 448 if (!IsEmptyLayer(m, off)) { 449 cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocLab2XYZ(Result ->ContextID)); 450 cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl)); 451 cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocXYZ2Lab(Result ->ContextID)); 452 } 453 break; 454 455 default: 456 return FALSE; // Mismatch 457 } 458 break; 459 460 461 // On colorspaces other than PCS, check for same space 462 default: 463 if (InPCS != OutPCS) return FALSE; 464 break; 465 } 466 467 return TRUE; 468 } 469 470 471 // Is a given space compatible with another? 472 static 473 cmsBool ColorSpaceIsCompatible(cmsColorSpaceSignature a, cmsColorSpaceSignature b) 474 { 475 // If they are same, they are compatible. 476 if (a == b) return TRUE; 477 478 // Check for MCH4 substitution of CMYK 479 if ((a == cmsSig4colorData) && (b == cmsSigCmykData)) return TRUE; 480 if ((a == cmsSigCmykData) && (b == cmsSig4colorData)) return TRUE; 481 482 // Check for XYZ/Lab. Those spaces are interchangeable as they can be computed one from other. 483 if ((a == cmsSigXYZData) && (b == cmsSigLabData)) return TRUE; 484 if ((a == cmsSigLabData) && (b == cmsSigXYZData)) return TRUE; 485 486 return FALSE; 487 } 488 489 490 // Default handler for ICC-style intents 491 static 492 cmsPipeline* DefaultICCintents(cmsContext ContextID, 493 cmsUInt32Number nProfiles, 494 cmsUInt32Number TheIntents[], 495 cmsHPROFILE hProfiles[], 496 cmsBool BPC[], 497 cmsFloat64Number AdaptationStates[], 498 cmsUInt32Number dwFlags) 499 { 500 cmsPipeline* Lut, *Result; 501 cmsHPROFILE hProfile; 502 cmsMAT3 m; 503 cmsVEC3 off; 504 cmsColorSpaceSignature ColorSpaceIn, ColorSpaceOut, CurrentColorSpace; 505 cmsProfileClassSignature ClassSig; 506 cmsUInt32Number i, Intent; 507 508 // For safety 509 if (nProfiles == 0) return NULL; 510 511 // Allocate an empty LUT for holding the result. 0 as channel count means 'undefined' 512 Result = cmsPipelineAlloc(ContextID, 0, 0); 513 if (Result == NULL) return NULL; 514 515 CurrentColorSpace = cmsGetColorSpace(hProfiles[0]); 516 517 for (i=0; i < nProfiles; i++) { 518 519 cmsBool lIsDeviceLink, lIsInput; 520 521 hProfile = hProfiles[i]; 522 ClassSig = cmsGetDeviceClass(hProfile); 523 lIsDeviceLink = (ClassSig == cmsSigLinkClass || ClassSig == cmsSigAbstractClass ); 524 525 // First profile is used as input unless devicelink or abstract 526 if ((i == 0) && !lIsDeviceLink) { 527 lIsInput = TRUE; 528 } 529 else { 530 // Else use profile in the input direction if current space is not PCS 531 lIsInput = (CurrentColorSpace != cmsSigXYZData) && 532 (CurrentColorSpace != cmsSigLabData); 533 } 534 535 Intent = TheIntents[i]; 536 537 if (lIsInput || lIsDeviceLink) { 538 539 ColorSpaceIn = cmsGetColorSpace(hProfile); 540 ColorSpaceOut = cmsGetPCS(hProfile); 541 } 542 else { 543 544 ColorSpaceIn = cmsGetPCS(hProfile); 545 ColorSpaceOut = cmsGetColorSpace(hProfile); 546 } 547 548 if (!ColorSpaceIsCompatible(ColorSpaceIn, CurrentColorSpace)) { 549 550 cmsSignalError(ContextID, cmsERROR_COLORSPACE_CHECK, "ColorSpace mismatch"); 551 goto Error; 552 } 553 554 // If devicelink is found, then no custom intent is allowed and we can 555 // read the LUT to be applied. Settings don't apply here. 556 if (lIsDeviceLink || ((ClassSig == cmsSigNamedColorClass) && (nProfiles == 1))) { 557 558 // Get the involved LUT from the profile 559 Lut = _cmsReadDevicelinkLUT(hProfile, Intent); 560 if (Lut == NULL) goto Error; 561 562 // What about abstract profiles? 563 if (ClassSig == cmsSigAbstractClass && i > 0) { 564 if (!ComputeConversion(i, hProfiles, Intent, BPC[i], AdaptationStates[i], &m, &off)) goto Error; 565 } 566 else { 567 _cmsMAT3identity(&m); 568 _cmsVEC3init(&off, 0, 0, 0); 569 } 570 571 572 if (!AddConversion(Result, CurrentColorSpace, ColorSpaceIn, &m, &off)) goto Error; 573 574 } 575 else { 576 577 if (lIsInput) { 578 // Input direction means non-pcs connection, so proceed like devicelinks 579 Lut = _cmsReadInputLUT(hProfile, Intent); 580 if (Lut == NULL) goto Error; 581 } 582 else { 583 584 // Output direction means PCS connection. Intent may apply here 585 Lut = _cmsReadOutputLUT(hProfile, Intent); 586 if (Lut == NULL) goto Error; 587 588 589 if (!ComputeConversion(i, hProfiles, Intent, BPC[i], AdaptationStates[i], &m, &off)) goto Error; 590 if (!AddConversion(Result, CurrentColorSpace, ColorSpaceIn, &m, &off)) goto Error; 591 592 } 593 } 594 595 // Concatenate to the output LUT 596 cmsPipelineCat(Result, Lut); 597 cmsPipelineFree(Lut); 598 599 // Update current space 600 CurrentColorSpace = ColorSpaceOut; 601 } 602 603 return Result; 604 605 Error: 606 607 if (Result != NULL) cmsPipelineFree(Result); 608 return NULL; 609 610 cmsUNUSED_PARAMETER(dwFlags); 611 } 612 613 614 // Wrapper for DLL calling convention 615 cmsPipeline* CMSEXPORT _cmsDefaultICCintents(cmsContext ContextID, 616 cmsUInt32Number nProfiles, 617 cmsUInt32Number TheIntents[], 618 cmsHPROFILE hProfiles[], 619 cmsBool BPC[], 620 cmsFloat64Number AdaptationStates[], 621 cmsUInt32Number dwFlags) 622 { 623 return DefaultICCintents(ContextID, nProfiles, TheIntents, hProfiles, BPC, AdaptationStates, dwFlags); 624 } 625 626 // Black preserving intents --------------------------------------------------------------------------------------------- 627 628 // Translate black-preserving intents to ICC ones 629 static 630 int TranslateNonICCIntents(int Intent) 631 { 632 switch (Intent) { 633 case INTENT_PRESERVE_K_ONLY_PERCEPTUAL: 634 case INTENT_PRESERVE_K_PLANE_PERCEPTUAL: 635 return INTENT_PERCEPTUAL; 636 637 case INTENT_PRESERVE_K_ONLY_RELATIVE_COLORIMETRIC: 638 case INTENT_PRESERVE_K_PLANE_RELATIVE_COLORIMETRIC: 639 return INTENT_RELATIVE_COLORIMETRIC; 640 641 case INTENT_PRESERVE_K_ONLY_SATURATION: 642 case INTENT_PRESERVE_K_PLANE_SATURATION: 643 return INTENT_SATURATION; 644 645 default: return Intent; 646 } 647 } 648 649 // Sampler for Black-only preserving CMYK->CMYK transforms 650 651 typedef struct { 652 cmsPipeline* cmyk2cmyk; // The original transform 653 cmsToneCurve* KTone; // Black-to-black tone curve 654 655 } GrayOnlyParams; 656 657 658 // Preserve black only if that is the only ink used 659 static 660 int BlackPreservingGrayOnlySampler(register const cmsUInt16Number In[], register cmsUInt16Number Out[], register void* Cargo) 661 { 662 GrayOnlyParams* bp = (GrayOnlyParams*) Cargo; 663 664 // If going across black only, keep black only 665 if (In[0] == 0 && In[1] == 0 && In[2] == 0) { 666 667 // TAC does not apply because it is black ink! 668 Out[0] = Out[1] = Out[2] = 0; 669 Out[3] = cmsEvalToneCurve16(bp->KTone, In[3]); 670 return TRUE; 671 } 672 673 // Keep normal transform for other colors 674 bp ->cmyk2cmyk ->Eval16Fn(In, Out, bp ->cmyk2cmyk->Data); 675 return TRUE; 676 } 677 678 // This is the entry for black-preserving K-only intents, which are non-ICC 679 static 680 cmsPipeline* BlackPreservingKOnlyIntents(cmsContext ContextID, 681 cmsUInt32Number nProfiles, 682 cmsUInt32Number TheIntents[], 683 cmsHPROFILE hProfiles[], 684 cmsBool BPC[], 685 cmsFloat64Number AdaptationStates[], 686 cmsUInt32Number dwFlags) 687 { 688 GrayOnlyParams bp; 689 cmsPipeline* Result; 690 cmsUInt32Number ICCIntents[256]; 691 cmsStage* CLUT; 692 cmsUInt32Number i, nGridPoints; 693 694 695 // Sanity check 696 if (nProfiles < 1 || nProfiles > 255) return NULL; 697 698 // Translate black-preserving intents to ICC ones 699 for (i=0; i < nProfiles; i++) 700 ICCIntents[i] = TranslateNonICCIntents(TheIntents[i]); 701 702 // Check for non-cmyk profiles 703 if (cmsGetColorSpace(hProfiles[0]) != cmsSigCmykData || 704 cmsGetColorSpace(hProfiles[nProfiles-1]) != cmsSigCmykData) 705 return DefaultICCintents(ContextID, nProfiles, ICCIntents, hProfiles, BPC, AdaptationStates, dwFlags); 706 707 memset(&bp, 0, sizeof(bp)); 708 709 // Allocate an empty LUT for holding the result 710 Result = cmsPipelineAlloc(ContextID, 4, 4); 711 if (Result == NULL) return NULL; 712 713 // Create a LUT holding normal ICC transform 714 bp.cmyk2cmyk = DefaultICCintents(ContextID, 715 nProfiles, 716 ICCIntents, 717 hProfiles, 718 BPC, 719 AdaptationStates, 720 dwFlags); 721 722 if (bp.cmyk2cmyk == NULL) goto Error; 723 724 // Now, compute the tone curve 725 bp.KTone = _cmsBuildKToneCurve(ContextID, 726 4096, 727 nProfiles, 728 ICCIntents, 729 hProfiles, 730 BPC, 731 AdaptationStates, 732 dwFlags); 733 734 if (bp.KTone == NULL) goto Error; 735 736 737 // How many gridpoints are we going to use? 738 nGridPoints = _cmsReasonableGridpointsByColorspace(cmsSigCmykData, dwFlags); 739 740 // Create the CLUT. 16 bits 741 CLUT = cmsStageAllocCLut16bit(ContextID, nGridPoints, 4, 4, NULL); 742 if (CLUT == NULL) goto Error; 743 744 // This is the one and only MPE in this LUT 745 cmsPipelineInsertStage(Result, cmsAT_BEGIN, CLUT); 746 747 // Sample it. We cannot afford pre/post linearization this time. 748 if (!cmsStageSampleCLut16bit(CLUT, BlackPreservingGrayOnlySampler, (void*) &bp, 0)) 749 goto Error; 750 751 // Get rid of xform and tone curve 752 cmsPipelineFree(bp.cmyk2cmyk); 753 cmsFreeToneCurve(bp.KTone); 754 755 return Result; 756 757 Error: 758 759 if (bp.cmyk2cmyk != NULL) cmsPipelineFree(bp.cmyk2cmyk); 760 if (bp.KTone != NULL) cmsFreeToneCurve(bp.KTone); 761 if (Result != NULL) cmsPipelineFree(Result); 762 return NULL; 763 764 } 765 766 // K Plane-preserving CMYK to CMYK ------------------------------------------------------------------------------------ 767 768 typedef struct { 769 770 cmsPipeline* cmyk2cmyk; // The original transform 771 cmsHTRANSFORM hProofOutput; // Output CMYK to Lab (last profile) 772 cmsHTRANSFORM cmyk2Lab; // The input chain 773 cmsToneCurve* KTone; // Black-to-black tone curve 774 cmsPipeline* LabK2cmyk; // The output profile 775 cmsFloat64Number MaxError; 776 777 cmsHTRANSFORM hRoundTrip; 778 cmsFloat64Number MaxTAC; 779 780 781 } PreserveKPlaneParams; 782 783 784 // The CLUT will be stored at 16 bits, but calculations are performed at cmsFloat32Number precision 785 static 786 int BlackPreservingSampler(register const cmsUInt16Number In[], register cmsUInt16Number Out[], register void* Cargo) 787 { 788 int i; 789 cmsFloat32Number Inf[4], Outf[4]; 790 cmsFloat32Number LabK[4]; 791 cmsFloat64Number SumCMY, SumCMYK, Error, Ratio; 792 cmsCIELab ColorimetricLab, BlackPreservingLab; 793 PreserveKPlaneParams* bp = (PreserveKPlaneParams*) Cargo; 794 795 // Convert from 16 bits to floating point 796 for (i=0; i < 4; i++) 797 Inf[i] = (cmsFloat32Number) (In[i] / 65535.0); 798 799 // Get the K across Tone curve 800 LabK[3] = cmsEvalToneCurveFloat(bp ->KTone, Inf[3]); 801 802 // If going across black only, keep black only 803 if (In[0] == 0 && In[1] == 0 && In[2] == 0) { 804 805 Out[0] = Out[1] = Out[2] = 0; 806 Out[3] = _cmsQuickSaturateWord(LabK[3] * 65535.0); 807 return TRUE; 808 } 809 810 // Try the original transform, 811 cmsPipelineEvalFloat( Inf, Outf, bp ->cmyk2cmyk); 812 813 // Store a copy of the floating point result into 16-bit 814 for (i=0; i < 4; i++) 815 Out[i] = _cmsQuickSaturateWord(Outf[i] * 65535.0); 816 817 // Maybe K is already ok (mostly on K=0) 818 if ( fabs(Outf[3] - LabK[3]) < (3.0 / 65535.0) ) { 819 return TRUE; 820 } 821 822 // K differ, mesure and keep Lab measurement for further usage 823 // this is done in relative colorimetric intent 824 cmsDoTransform(bp->hProofOutput, Out, &ColorimetricLab, 1); 825 826 // Is not black only and the transform doesn't keep black. 827 // Obtain the Lab of output CMYK. After that we have Lab + K 828 cmsDoTransform(bp ->cmyk2Lab, Outf, LabK, 1); 829 830 // Obtain the corresponding CMY using reverse interpolation 831 // (K is fixed in LabK[3]) 832 if (!cmsPipelineEvalReverseFloat(LabK, Outf, Outf, bp ->LabK2cmyk)) { 833 834 // Cannot find a suitable value, so use colorimetric xform 835 // which is already stored in Out[] 836 return TRUE; 837 } 838 839 // Make sure to pass thru K (which now is fixed) 840 Outf[3] = LabK[3]; 841 842 // Apply TAC if needed 843 SumCMY = Outf[0] + Outf[1] + Outf[2]; 844 SumCMYK = SumCMY + Outf[3]; 845 846 if (SumCMYK > bp ->MaxTAC) { 847 848 Ratio = 1 - ((SumCMYK - bp->MaxTAC) / SumCMY); 849 if (Ratio < 0) 850 Ratio = 0; 851 } 852 else 853 Ratio = 1.0; 854 855 Out[0] = _cmsQuickSaturateWord(Outf[0] * Ratio * 65535.0); // C 856 Out[1] = _cmsQuickSaturateWord(Outf[1] * Ratio * 65535.0); // M 857 Out[2] = _cmsQuickSaturateWord(Outf[2] * Ratio * 65535.0); // Y 858 Out[3] = _cmsQuickSaturateWord(Outf[3] * 65535.0); 859 860 // Estimate the error (this goes 16 bits to Lab DBL) 861 cmsDoTransform(bp->hProofOutput, Out, &BlackPreservingLab, 1); 862 Error = cmsDeltaE(&ColorimetricLab, &BlackPreservingLab); 863 if (Error > bp -> MaxError) 864 bp->MaxError = Error; 865 866 return TRUE; 867 } 868 869 // This is the entry for black-plane preserving, which are non-ICC 870 static 871 cmsPipeline* BlackPreservingKPlaneIntents(cmsContext ContextID, 872 cmsUInt32Number nProfiles, 873 cmsUInt32Number TheIntents[], 874 cmsHPROFILE hProfiles[], 875 cmsBool BPC[], 876 cmsFloat64Number AdaptationStates[], 877 cmsUInt32Number dwFlags) 878 { 879 PreserveKPlaneParams bp; 880 cmsPipeline* Result = NULL; 881 cmsUInt32Number ICCIntents[256]; 882 cmsStage* CLUT; 883 cmsUInt32Number i, nGridPoints; 884 cmsHPROFILE hLab; 885 886 // Sanity check 887 if (nProfiles < 1 || nProfiles > 255) return NULL; 888 889 // Translate black-preserving intents to ICC ones 890 for (i=0; i < nProfiles; i++) 891 ICCIntents[i] = TranslateNonICCIntents(TheIntents[i]); 892 893 // Check for non-cmyk profiles 894 if (cmsGetColorSpace(hProfiles[0]) != cmsSigCmykData || 895 !(cmsGetColorSpace(hProfiles[nProfiles-1]) == cmsSigCmykData || 896 cmsGetDeviceClass(hProfiles[nProfiles-1]) == cmsSigOutputClass)) 897 return DefaultICCintents(ContextID, nProfiles, ICCIntents, hProfiles, BPC, AdaptationStates, dwFlags); 898 899 // Allocate an empty LUT for holding the result 900 Result = cmsPipelineAlloc(ContextID, 4, 4); 901 if (Result == NULL) return NULL; 902 903 904 memset(&bp, 0, sizeof(bp)); 905 906 // We need the input LUT of the last profile, assuming this one is responsible of 907 // black generation. This LUT will be seached in inverse order. 908 bp.LabK2cmyk = _cmsReadInputLUT(hProfiles[nProfiles-1], INTENT_RELATIVE_COLORIMETRIC); 909 if (bp.LabK2cmyk == NULL) goto Cleanup; 910 911 // Get total area coverage (in 0..1 domain) 912 bp.MaxTAC = cmsDetectTAC(hProfiles[nProfiles-1]) / 100.0; 913 if (bp.MaxTAC <= 0) goto Cleanup; 914 915 916 // Create a LUT holding normal ICC transform 917 bp.cmyk2cmyk = DefaultICCintents(ContextID, 918 nProfiles, 919 ICCIntents, 920 hProfiles, 921 BPC, 922 AdaptationStates, 923 dwFlags); 924 if (bp.cmyk2cmyk == NULL) goto Cleanup; 925 926 // Now the tone curve 927 bp.KTone = _cmsBuildKToneCurve(ContextID, 4096, nProfiles, 928 ICCIntents, 929 hProfiles, 930 BPC, 931 AdaptationStates, 932 dwFlags); 933 if (bp.KTone == NULL) goto Cleanup; 934 935 // To measure the output, Last profile to Lab 936 hLab = cmsCreateLab4ProfileTHR(ContextID, NULL); 937 bp.hProofOutput = cmsCreateTransformTHR(ContextID, hProfiles[nProfiles-1], 938 CHANNELS_SH(4)|BYTES_SH(2), hLab, TYPE_Lab_DBL, 939 INTENT_RELATIVE_COLORIMETRIC, 940 cmsFLAGS_NOCACHE|cmsFLAGS_NOOPTIMIZE); 941 if ( bp.hProofOutput == NULL) goto Cleanup; 942 943 // Same as anterior, but lab in the 0..1 range 944 bp.cmyk2Lab = cmsCreateTransformTHR(ContextID, hProfiles[nProfiles-1], 945 FLOAT_SH(1)|CHANNELS_SH(4)|BYTES_SH(4), hLab, 946 FLOAT_SH(1)|CHANNELS_SH(3)|BYTES_SH(4), 947 INTENT_RELATIVE_COLORIMETRIC, 948 cmsFLAGS_NOCACHE|cmsFLAGS_NOOPTIMIZE); 949 if (bp.cmyk2Lab == NULL) goto Cleanup; 950 cmsCloseProfile(hLab); 951 952 // Error estimation (for debug only) 953 bp.MaxError = 0; 954 955 // How many gridpoints are we going to use? 956 nGridPoints = _cmsReasonableGridpointsByColorspace(cmsSigCmykData, dwFlags); 957 958 959 CLUT = cmsStageAllocCLut16bit(ContextID, nGridPoints, 4, 4, NULL); 960 if (CLUT == NULL) goto Cleanup; 961 962 cmsPipelineInsertStage(Result, cmsAT_BEGIN, CLUT); 963 964 cmsStageSampleCLut16bit(CLUT, BlackPreservingSampler, (void*) &bp, 0); 965 966 Cleanup: 967 968 if (bp.cmyk2cmyk) cmsPipelineFree(bp.cmyk2cmyk); 969 if (bp.cmyk2Lab) cmsDeleteTransform(bp.cmyk2Lab); 970 if (bp.hProofOutput) cmsDeleteTransform(bp.hProofOutput); 971 972 if (bp.KTone) cmsFreeToneCurve(bp.KTone); 973 if (bp.LabK2cmyk) cmsPipelineFree(bp.LabK2cmyk); 974 975 return Result; 976 } 977 978 // Link routines ------------------------------------------------------------------------------------------------------ 979 980 // Chain several profiles into a single LUT. It just checks the parameters and then calls the handler 981 // for the first intent in chain. The handler may be user-defined. Is up to the handler to deal with the 982 // rest of intents in chain. A maximum of 255 profiles at time are supported, which is pretty reasonable. 983 cmsPipeline* _cmsLinkProfiles(cmsContext ContextID, 984 cmsUInt32Number nProfiles, 985 cmsUInt32Number TheIntents[], 986 cmsHPROFILE hProfiles[], 987 cmsBool BPC[], 988 cmsFloat64Number AdaptationStates[], 989 cmsUInt32Number dwFlags) 990 { 991 cmsUInt32Number i; 992 cmsIntentsList* Intent; 993 994 // Make sure a reasonable number of profiles is provided 995 if (nProfiles <= 0 || nProfiles > 255) { 996 cmsSignalError(ContextID, cmsERROR_RANGE, "Couldn't link '%d' profiles", nProfiles); 997 return NULL; 998 } 999 1000 for (i=0; i < nProfiles; i++) { 1001 1002 // Check if black point is really needed or allowed. Note that 1003 // following Adobe's document: 1004 // BPC does not apply to devicelink profiles, nor to abs colorimetric, 1005 // and applies always on V4 perceptual and saturation. 1006 1007 if (TheIntents[i] == INTENT_ABSOLUTE_COLORIMETRIC) 1008 BPC[i] = FALSE; 1009 1010 if (TheIntents[i] == INTENT_PERCEPTUAL || TheIntents[i] == INTENT_SATURATION) { 1011 1012 // Force BPC for V4 profiles in perceptual and saturation 1013 if (cmsGetProfileVersion(hProfiles[i]) >= 4.0) 1014 BPC[i] = TRUE; 1015 } 1016 } 1017 1018 // Search for a handler. The first intent in the chain defines the handler. That would 1019 // prevent using multiple custom intents in a multiintent chain, but the behaviour of 1020 // this case would present some issues if the custom intent tries to do things like 1021 // preserve primaries. This solution is not perfect, but works well on most cases. 1022 1023 Intent = SearchIntent(TheIntents[0]); 1024 if (Intent == NULL) { 1025 cmsSignalError(ContextID, cmsERROR_UNKNOWN_EXTENSION, "Unsupported intent '%d'", TheIntents[0]); 1026 return NULL; 1027 } 1028 1029 // Call the handler 1030 return Intent ->Link(ContextID, nProfiles, TheIntents, hProfiles, BPC, AdaptationStates, dwFlags); 1031 } 1032 1033 // ------------------------------------------------------------------------------------------------- 1034 1035 // Get information about available intents. nMax is the maximum space for the supplied "Codes" 1036 // and "Descriptions" the function returns the total number of intents, which may be greater 1037 // than nMax, although the matrices are not populated beyond this level. 1038 cmsUInt32Number CMSEXPORT cmsGetSupportedIntents(cmsUInt32Number nMax, cmsUInt32Number* Codes, char** Descriptions) 1039 { 1040 cmsIntentsList* pt; 1041 cmsUInt32Number nIntents; 1042 1043 for (nIntents=0, pt = Intents; pt != NULL; pt = pt -> Next) 1044 { 1045 if (nIntents < nMax) { 1046 if (Codes != NULL) 1047 Codes[nIntents] = pt ->Intent; 1048 1049 if (Descriptions != NULL) 1050 Descriptions[nIntents] = pt ->Description; 1051 } 1052 1053 nIntents++; 1054 } 1055 1056 return nIntents; 1057 } 1058 1059 // The plug-in registration. User can add new intents or override default routines 1060 cmsBool _cmsRegisterRenderingIntentPlugin(cmsPluginBase* Data) 1061 { 1062 cmsPluginRenderingIntent* Plugin = (cmsPluginRenderingIntent*) Data; 1063 cmsIntentsList* fl; 1064 1065 // Do we have to reset the intents? 1066 if (Data == NULL) { 1067 1068 Intents = DefaultIntents; 1069 return TRUE; 1070 } 1071 1072 fl = SearchIntent(Plugin ->Intent); 1073 1074 if (fl == NULL) { 1075 fl = (cmsIntentsList*) _cmsPluginMalloc(sizeof(cmsIntentsList)); 1076 if (fl == NULL) return FALSE; 1077 } 1078 1079 fl ->Intent = Plugin ->Intent; 1080 strncpy(fl ->Description, Plugin ->Description, 255); 1081 fl ->Description[255] = 0; 1082 1083 fl ->Link = Plugin ->Link; 1084 1085 fl ->Next = Intents; 1086 Intents = fl; 1087 1088 return TRUE; 1089 } 1090