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