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