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