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