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-2017 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(cmsUInt32Number i, 396 cmsHPROFILE hProfiles[], 397 cmsUInt32Number Intent, 398 cmsBool BPC, 399 cmsFloat64Number AdaptationState, 400 cmsMAT3* m, cmsVEC3* off) 401 { 402 403 int k; 404 405 // m and off are set to identity and this is detected latter on 406 _cmsMAT3identity(m); 407 _cmsVEC3init(off, 0, 0, 0); 408 409 // If intent is abs. colorimetric, 410 if (Intent == INTENT_ABSOLUTE_COLORIMETRIC) { 411 412 cmsCIEXYZ WhitePointIn, WhitePointOut; 413 cmsMAT3 ChromaticAdaptationMatrixIn, ChromaticAdaptationMatrixOut; 414 415 _cmsReadMediaWhitePoint(&WhitePointIn, hProfiles[i-1]); 416 _cmsReadCHAD(&ChromaticAdaptationMatrixIn, hProfiles[i-1]); 417 418 _cmsReadMediaWhitePoint(&WhitePointOut, hProfiles[i]); 419 _cmsReadCHAD(&ChromaticAdaptationMatrixOut, hProfiles[i]); 420 421 if (!ComputeAbsoluteIntent(AdaptationState, 422 &WhitePointIn, &ChromaticAdaptationMatrixIn, 423 &WhitePointOut, &ChromaticAdaptationMatrixOut, m)) return FALSE; 424 425 } 426 else { 427 // Rest of intents may apply BPC. 428 429 if (BPC) { 430 431 cmsCIEXYZ BlackPointIn, BlackPointOut; 432 433 cmsDetectBlackPoint(&BlackPointIn, hProfiles[i-1], Intent, 0); 434 cmsDetectDestinationBlackPoint(&BlackPointOut, hProfiles[i], Intent, 0); 435 436 // If black points are equal, then do nothing 437 if (BlackPointIn.X != BlackPointOut.X || 438 BlackPointIn.Y != BlackPointOut.Y || 439 BlackPointIn.Z != BlackPointOut.Z) 440 ComputeBlackPointCompensation(&BlackPointIn, &BlackPointOut, m, off); 441 } 442 } 443 444 // Offset should be adjusted because the encoding. We encode XYZ normalized to 0..1.0, 445 // to do that, we divide by MAX_ENCODEABLE_XZY. The conversion stage goes XYZ -> XYZ so 446 // we have first to convert from encoded to XYZ and then convert back to encoded. 447 // y = Mx + Off 448 // x = x'c 449 // y = M x'c + Off 450 // y = y'c; y' = y / c 451 // y' = (Mx'c + Off) /c = Mx' + (Off / c) 452 453 for (k=0; k < 3; k++) { 454 off ->n[k] /= MAX_ENCODEABLE_XYZ; 455 } 456 457 return TRUE; 458 } 459 460 461 // Add a conversion stage if needed. If a matrix/offset m is given, it applies to XYZ space 462 static 463 cmsBool AddConversion(cmsPipeline* Result, cmsColorSpaceSignature InPCS, cmsColorSpaceSignature OutPCS, cmsMAT3* m, cmsVEC3* off) 464 { 465 cmsFloat64Number* m_as_dbl = (cmsFloat64Number*) m; 466 cmsFloat64Number* off_as_dbl = (cmsFloat64Number*) off; 467 468 // Handle PCS mismatches. A specialized stage is added to the LUT in such case 469 switch (InPCS) { 470 471 case cmsSigXYZData: // Input profile operates in XYZ 472 473 switch (OutPCS) { 474 475 case cmsSigXYZData: // XYZ -> XYZ 476 if (!IsEmptyLayer(m, off) && 477 !cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl))) 478 return FALSE; 479 break; 480 481 case cmsSigLabData: // XYZ -> Lab 482 if (!IsEmptyLayer(m, off) && 483 !cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl))) 484 return FALSE; 485 if (!cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocXYZ2Lab(Result ->ContextID))) 486 return FALSE; 487 break; 488 489 default: 490 return FALSE; // Colorspace mismatch 491 } 492 break; 493 494 case cmsSigLabData: // Input profile operates in Lab 495 496 switch (OutPCS) { 497 498 case cmsSigXYZData: // Lab -> XYZ 499 500 if (!cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocLab2XYZ(Result ->ContextID))) 501 return FALSE; 502 if (!IsEmptyLayer(m, off) && 503 !cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl))) 504 return FALSE; 505 break; 506 507 case cmsSigLabData: // Lab -> Lab 508 509 if (!IsEmptyLayer(m, off)) { 510 if (!cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocLab2XYZ(Result ->ContextID)) || 511 !cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl)) || 512 !cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocXYZ2Lab(Result ->ContextID))) 513 return FALSE; 514 } 515 break; 516 517 default: 518 return FALSE; // Mismatch 519 } 520 break; 521 522 // On colorspaces other than PCS, check for same space 523 default: 524 if (InPCS != OutPCS) return FALSE; 525 break; 526 } 527 528 return TRUE; 529 } 530 531 532 // Is a given space compatible with another? 533 static 534 cmsBool ColorSpaceIsCompatible(cmsColorSpaceSignature a, cmsColorSpaceSignature b) 535 { 536 // If they are same, they are compatible. 537 if (a == b) return TRUE; 538 539 // Check for MCH4 substitution of CMYK 540 if ((a == cmsSig4colorData) && (b == cmsSigCmykData)) return TRUE; 541 if ((a == cmsSigCmykData) && (b == cmsSig4colorData)) return TRUE; 542 543 // Check for XYZ/Lab. Those spaces are interchangeable as they can be computed one from other. 544 if ((a == cmsSigXYZData) && (b == cmsSigLabData)) return TRUE; 545 if ((a == cmsSigLabData) && (b == cmsSigXYZData)) return TRUE; 546 547 return FALSE; 548 } 549 550 551 // Default handler for ICC-style intents 552 static 553 cmsPipeline* DefaultICCintents(cmsContext ContextID, 554 cmsUInt32Number nProfiles, 555 cmsUInt32Number TheIntents[], 556 cmsHPROFILE hProfiles[], 557 cmsBool BPC[], 558 cmsFloat64Number AdaptationStates[], 559 cmsUInt32Number dwFlags) 560 { 561 cmsPipeline* Lut = NULL; 562 cmsPipeline* Result; 563 cmsHPROFILE hProfile; 564 cmsMAT3 m; 565 cmsVEC3 off; 566 cmsColorSpaceSignature ColorSpaceIn, ColorSpaceOut = cmsSigLabData, CurrentColorSpace; 567 cmsProfileClassSignature ClassSig; 568 cmsUInt32Number i, Intent; 569 570 // For safety 571 if (nProfiles == 0) return NULL; 572 573 // Allocate an empty LUT for holding the result. 0 as channel count means 'undefined' 574 Result = cmsPipelineAlloc(ContextID, 0, 0); 575 if (Result == NULL) return NULL; 576 577 CurrentColorSpace = cmsGetColorSpace(hProfiles[0]); 578 579 for (i=0; i < nProfiles; i++) { 580 581 cmsBool lIsDeviceLink, lIsInput; 582 583 hProfile = hProfiles[i]; 584 ClassSig = cmsGetDeviceClass(hProfile); 585 lIsDeviceLink = (ClassSig == cmsSigLinkClass || ClassSig == cmsSigAbstractClass ); 586 587 // First profile is used as input unless devicelink or abstract 588 if ((i == 0) && !lIsDeviceLink) { 589 lIsInput = TRUE; 590 } 591 else { 592 // Else use profile in the input direction if current space is not PCS 593 lIsInput = (CurrentColorSpace != cmsSigXYZData) && 594 (CurrentColorSpace != cmsSigLabData); 595 } 596 597 Intent = TheIntents[i]; 598 599 if (lIsInput || lIsDeviceLink) { 600 601 ColorSpaceIn = cmsGetColorSpace(hProfile); 602 ColorSpaceOut = cmsGetPCS(hProfile); 603 } 604 else { 605 606 ColorSpaceIn = cmsGetPCS(hProfile); 607 ColorSpaceOut = cmsGetColorSpace(hProfile); 608 } 609 610 if (!ColorSpaceIsCompatible(ColorSpaceIn, CurrentColorSpace)) { 611 612 cmsSignalError(ContextID, cmsERROR_COLORSPACE_CHECK, "ColorSpace mismatch"); 613 goto Error; 614 } 615 616 // If devicelink is found, then no custom intent is allowed and we can 617 // read the LUT to be applied. Settings don't apply here. 618 if (lIsDeviceLink || ((ClassSig == cmsSigNamedColorClass) && (nProfiles == 1))) { 619 620 // Get the involved LUT from the profile 621 Lut = _cmsReadDevicelinkLUT(hProfile, Intent); 622 if (Lut == NULL) goto Error; 623 624 // What about abstract profiles? 625 if (ClassSig == cmsSigAbstractClass && i > 0) { 626 if (!ComputeConversion(i, hProfiles, Intent, BPC[i], AdaptationStates[i], &m, &off)) goto Error; 627 } 628 else { 629 _cmsMAT3identity(&m); 630 _cmsVEC3init(&off, 0, 0, 0); 631 } 632 633 634 if (!AddConversion(Result, CurrentColorSpace, ColorSpaceIn, &m, &off)) goto Error; 635 636 } 637 else { 638 639 if (lIsInput) { 640 // Input direction means non-pcs connection, so proceed like devicelinks 641 Lut = _cmsReadInputLUT(hProfile, Intent); 642 if (Lut == NULL) goto Error; 643 } 644 else { 645 646 // Output direction means PCS connection. Intent may apply here 647 Lut = _cmsReadOutputLUT(hProfile, Intent); 648 if (Lut == NULL) goto Error; 649 650 651 if (!ComputeConversion(i, hProfiles, Intent, BPC[i], AdaptationStates[i], &m, &off)) goto Error; 652 if (!AddConversion(Result, CurrentColorSpace, ColorSpaceIn, &m, &off)) goto Error; 653 654 } 655 } 656 657 // Concatenate to the output LUT 658 if (!cmsPipelineCat(Result, Lut)) 659 goto Error; 660 661 cmsPipelineFree(Lut); 662 Lut = NULL; 663 664 // Update current space 665 CurrentColorSpace = ColorSpaceOut; 666 } 667 668 // Check for non-negatives clip 669 if (dwFlags & cmsFLAGS_NONEGATIVES) { 670 671 if (ColorSpaceOut == cmsSigGrayData || 672 ColorSpaceOut == cmsSigRgbData || 673 ColorSpaceOut == cmsSigCmykData) { 674 675 cmsStage* clip = _cmsStageClipNegatives(Result->ContextID, cmsChannelsOf(ColorSpaceOut)); 676 if (clip == NULL) goto Error; 677 678 if (!cmsPipelineInsertStage(Result, cmsAT_END, clip)) 679 goto Error; 680 } 681 682 } 683 684 return Result; 685 686 Error: 687 688 if (Lut != NULL) cmsPipelineFree(Lut); 689 if (Result != NULL) cmsPipelineFree(Result); 690 return NULL; 691 692 cmsUNUSED_PARAMETER(dwFlags); 693 } 694 695 696 // Wrapper for DLL calling convention 697 cmsPipeline* CMSEXPORT _cmsDefaultICCintents(cmsContext ContextID, 698 cmsUInt32Number nProfiles, 699 cmsUInt32Number TheIntents[], 700 cmsHPROFILE hProfiles[], 701 cmsBool BPC[], 702 cmsFloat64Number AdaptationStates[], 703 cmsUInt32Number dwFlags) 704 { 705 return DefaultICCintents(ContextID, nProfiles, TheIntents, hProfiles, BPC, AdaptationStates, dwFlags); 706 } 707 708 // Black preserving intents --------------------------------------------------------------------------------------------- 709 710 // Translate black-preserving intents to ICC ones 711 static 712 cmsUInt32Number TranslateNonICCIntents(cmsUInt32Number Intent) 713 { 714 switch (Intent) { 715 case INTENT_PRESERVE_K_ONLY_PERCEPTUAL: 716 case INTENT_PRESERVE_K_PLANE_PERCEPTUAL: 717 return INTENT_PERCEPTUAL; 718 719 case INTENT_PRESERVE_K_ONLY_RELATIVE_COLORIMETRIC: 720 case INTENT_PRESERVE_K_PLANE_RELATIVE_COLORIMETRIC: 721 return INTENT_RELATIVE_COLORIMETRIC; 722 723 case INTENT_PRESERVE_K_ONLY_SATURATION: 724 case INTENT_PRESERVE_K_PLANE_SATURATION: 725 return INTENT_SATURATION; 726 727 default: return Intent; 728 } 729 } 730 731 // Sampler for Black-only preserving CMYK->CMYK transforms 732 733 typedef struct { 734 cmsPipeline* cmyk2cmyk; // The original transform 735 cmsToneCurve* KTone; // Black-to-black tone curve 736 737 } GrayOnlyParams; 738 739 740 // Preserve black only if that is the only ink used 741 static 742 int BlackPreservingGrayOnlySampler(register const cmsUInt16Number In[], register cmsUInt16Number Out[], register void* Cargo) 743 { 744 GrayOnlyParams* bp = (GrayOnlyParams*) Cargo; 745 746 // If going across black only, keep black only 747 if (In[0] == 0 && In[1] == 0 && In[2] == 0) { 748 749 // TAC does not apply because it is black ink! 750 Out[0] = Out[1] = Out[2] = 0; 751 Out[3] = cmsEvalToneCurve16(bp->KTone, In[3]); 752 return TRUE; 753 } 754 755 // Keep normal transform for other colors 756 bp ->cmyk2cmyk ->Eval16Fn(In, Out, bp ->cmyk2cmyk->Data); 757 return TRUE; 758 } 759 760 // This is the entry for black-preserving K-only intents, which are non-ICC 761 static 762 cmsPipeline* BlackPreservingKOnlyIntents(cmsContext ContextID, 763 cmsUInt32Number nProfiles, 764 cmsUInt32Number TheIntents[], 765 cmsHPROFILE hProfiles[], 766 cmsBool BPC[], 767 cmsFloat64Number AdaptationStates[], 768 cmsUInt32Number dwFlags) 769 { 770 GrayOnlyParams bp; 771 cmsPipeline* Result; 772 cmsUInt32Number ICCIntents[256]; 773 cmsStage* CLUT; 774 cmsUInt32Number i, nGridPoints; 775 776 777 // Sanity check 778 if (nProfiles < 1 || nProfiles > 255) return NULL; 779 780 // Translate black-preserving intents to ICC ones 781 for (i=0; i < nProfiles; i++) 782 ICCIntents[i] = TranslateNonICCIntents(TheIntents[i]); 783 784 // Check for non-cmyk profiles 785 if (cmsGetColorSpace(hProfiles[0]) != cmsSigCmykData || 786 cmsGetColorSpace(hProfiles[nProfiles-1]) != cmsSigCmykData) 787 return DefaultICCintents(ContextID, nProfiles, ICCIntents, hProfiles, BPC, AdaptationStates, dwFlags); 788 789 memset(&bp, 0, sizeof(bp)); 790 791 // Allocate an empty LUT for holding the result 792 Result = cmsPipelineAlloc(ContextID, 4, 4); 793 if (Result == NULL) return NULL; 794 795 // Create a LUT holding normal ICC transform 796 bp.cmyk2cmyk = DefaultICCintents(ContextID, 797 nProfiles, 798 ICCIntents, 799 hProfiles, 800 BPC, 801 AdaptationStates, 802 dwFlags); 803 804 if (bp.cmyk2cmyk == NULL) goto Error; 805 806 // Now, compute the tone curve 807 bp.KTone = _cmsBuildKToneCurve(ContextID, 808 4096, 809 nProfiles, 810 ICCIntents, 811 hProfiles, 812 BPC, 813 AdaptationStates, 814 dwFlags); 815 816 if (bp.KTone == NULL) goto Error; 817 818 819 // How many gridpoints are we going to use? 820 nGridPoints = _cmsReasonableGridpointsByColorspace(cmsSigCmykData, dwFlags); 821 822 // Create the CLUT. 16 bits 823 CLUT = cmsStageAllocCLut16bit(ContextID, nGridPoints, 4, 4, NULL); 824 if (CLUT == NULL) goto Error; 825 826 // This is the one and only MPE in this LUT 827 if (!cmsPipelineInsertStage(Result, cmsAT_BEGIN, CLUT)) 828 goto Error; 829 830 // Sample it. We cannot afford pre/post linearization this time. 831 if (!cmsStageSampleCLut16bit(CLUT, BlackPreservingGrayOnlySampler, (void*) &bp, 0)) 832 goto Error; 833 834 // Get rid of xform and tone curve 835 cmsPipelineFree(bp.cmyk2cmyk); 836 cmsFreeToneCurve(bp.KTone); 837 838 return Result; 839 840 Error: 841 842 if (bp.cmyk2cmyk != NULL) cmsPipelineFree(bp.cmyk2cmyk); 843 if (bp.KTone != NULL) cmsFreeToneCurve(bp.KTone); 844 if (Result != NULL) cmsPipelineFree(Result); 845 return NULL; 846 847 } 848 849 // K Plane-preserving CMYK to CMYK ------------------------------------------------------------------------------------ 850 851 typedef struct { 852 853 cmsPipeline* cmyk2cmyk; // The original transform 854 cmsHTRANSFORM hProofOutput; // Output CMYK to Lab (last profile) 855 cmsHTRANSFORM cmyk2Lab; // The input chain 856 cmsToneCurve* KTone; // Black-to-black tone curve 857 cmsPipeline* LabK2cmyk; // The output profile 858 cmsFloat64Number MaxError; 859 860 cmsHTRANSFORM hRoundTrip; 861 cmsFloat64Number MaxTAC; 862 863 864 } PreserveKPlaneParams; 865 866 867 // The CLUT will be stored at 16 bits, but calculations are performed at cmsFloat32Number precision 868 static 869 int BlackPreservingSampler(register const cmsUInt16Number In[], register cmsUInt16Number Out[], register void* Cargo) 870 { 871 int i; 872 cmsFloat32Number Inf[4], Outf[4]; 873 cmsFloat32Number LabK[4]; 874 cmsFloat64Number SumCMY, SumCMYK, Error, Ratio; 875 cmsCIELab ColorimetricLab, BlackPreservingLab; 876 PreserveKPlaneParams* bp = (PreserveKPlaneParams*) Cargo; 877 878 // Convert from 16 bits to floating point 879 for (i=0; i < 4; i++) 880 Inf[i] = (cmsFloat32Number) (In[i] / 65535.0); 881 882 // Get the K across Tone curve 883 LabK[3] = cmsEvalToneCurveFloat(bp ->KTone, Inf[3]); 884 885 // If going across black only, keep black only 886 if (In[0] == 0 && In[1] == 0 && In[2] == 0) { 887 888 Out[0] = Out[1] = Out[2] = 0; 889 Out[3] = _cmsQuickSaturateWord(LabK[3] * 65535.0); 890 return TRUE; 891 } 892 893 // Try the original transform, 894 cmsPipelineEvalFloat( Inf, Outf, bp ->cmyk2cmyk); 895 896 // Store a copy of the floating point result into 16-bit 897 for (i=0; i < 4; i++) 898 Out[i] = _cmsQuickSaturateWord(Outf[i] * 65535.0); 899 900 // Maybe K is already ok (mostly on K=0) 901 if ( fabs(Outf[3] - LabK[3]) < (3.0 / 65535.0) ) { 902 return TRUE; 903 } 904 905 // K differ, mesure and keep Lab measurement for further usage 906 // this is done in relative colorimetric intent 907 cmsDoTransform(bp->hProofOutput, Out, &ColorimetricLab, 1); 908 909 // Is not black only and the transform doesn't keep black. 910 // Obtain the Lab of output CMYK. After that we have Lab + K 911 cmsDoTransform(bp ->cmyk2Lab, Outf, LabK, 1); 912 913 // Obtain the corresponding CMY using reverse interpolation 914 // (K is fixed in LabK[3]) 915 if (!cmsPipelineEvalReverseFloat(LabK, Outf, Outf, bp ->LabK2cmyk)) { 916 917 // Cannot find a suitable value, so use colorimetric xform 918 // which is already stored in Out[] 919 return TRUE; 920 } 921 922 // Make sure to pass through K (which now is fixed) 923 Outf[3] = LabK[3]; 924 925 // Apply TAC if needed 926 SumCMY = Outf[0] + Outf[1] + Outf[2]; 927 SumCMYK = SumCMY + Outf[3]; 928 929 if (SumCMYK > bp ->MaxTAC) { 930 931 Ratio = 1 - ((SumCMYK - bp->MaxTAC) / SumCMY); 932 if (Ratio < 0) 933 Ratio = 0; 934 } 935 else 936 Ratio = 1.0; 937 938 Out[0] = _cmsQuickSaturateWord(Outf[0] * Ratio * 65535.0); // C 939 Out[1] = _cmsQuickSaturateWord(Outf[1] * Ratio * 65535.0); // M 940 Out[2] = _cmsQuickSaturateWord(Outf[2] * Ratio * 65535.0); // Y 941 Out[3] = _cmsQuickSaturateWord(Outf[3] * 65535.0); 942 943 // Estimate the error (this goes 16 bits to Lab DBL) 944 cmsDoTransform(bp->hProofOutput, Out, &BlackPreservingLab, 1); 945 Error = cmsDeltaE(&ColorimetricLab, &BlackPreservingLab); 946 if (Error > bp -> MaxError) 947 bp->MaxError = Error; 948 949 return TRUE; 950 } 951 952 // This is the entry for black-plane preserving, which are non-ICC 953 static 954 cmsPipeline* BlackPreservingKPlaneIntents(cmsContext ContextID, 955 cmsUInt32Number nProfiles, 956 cmsUInt32Number TheIntents[], 957 cmsHPROFILE hProfiles[], 958 cmsBool BPC[], 959 cmsFloat64Number AdaptationStates[], 960 cmsUInt32Number dwFlags) 961 { 962 PreserveKPlaneParams bp; 963 cmsPipeline* Result = NULL; 964 cmsUInt32Number ICCIntents[256]; 965 cmsStage* CLUT; 966 cmsUInt32Number i, nGridPoints; 967 cmsHPROFILE hLab; 968 969 // Sanity check 970 if (nProfiles < 1 || nProfiles > 255) return NULL; 971 972 // Translate black-preserving intents to ICC ones 973 for (i=0; i < nProfiles; i++) 974 ICCIntents[i] = TranslateNonICCIntents(TheIntents[i]); 975 976 // Check for non-cmyk profiles 977 if (cmsGetColorSpace(hProfiles[0]) != cmsSigCmykData || 978 !(cmsGetColorSpace(hProfiles[nProfiles-1]) == cmsSigCmykData || 979 cmsGetDeviceClass(hProfiles[nProfiles-1]) == cmsSigOutputClass)) 980 return DefaultICCintents(ContextID, nProfiles, ICCIntents, hProfiles, BPC, AdaptationStates, dwFlags); 981 982 // Allocate an empty LUT for holding the result 983 Result = cmsPipelineAlloc(ContextID, 4, 4); 984 if (Result == NULL) return NULL; 985 986 987 memset(&bp, 0, sizeof(bp)); 988 989 // We need the input LUT of the last profile, assuming this one is responsible of 990 // black generation. This LUT will be searched in inverse order. 991 bp.LabK2cmyk = _cmsReadInputLUT(hProfiles[nProfiles-1], INTENT_RELATIVE_COLORIMETRIC); 992 if (bp.LabK2cmyk == NULL) goto Cleanup; 993 994 // Get total area coverage (in 0..1 domain) 995 bp.MaxTAC = cmsDetectTAC(hProfiles[nProfiles-1]) / 100.0; 996 if (bp.MaxTAC <= 0) goto Cleanup; 997 998 999 // Create a LUT holding normal ICC transform 1000 bp.cmyk2cmyk = DefaultICCintents(ContextID, 1001 nProfiles, 1002 ICCIntents, 1003 hProfiles, 1004 BPC, 1005 AdaptationStates, 1006 dwFlags); 1007 if (bp.cmyk2cmyk == NULL) goto Cleanup; 1008 1009 // Now the tone curve 1010 bp.KTone = _cmsBuildKToneCurve(ContextID, 4096, nProfiles, 1011 ICCIntents, 1012 hProfiles, 1013 BPC, 1014 AdaptationStates, 1015 dwFlags); 1016 if (bp.KTone == NULL) goto Cleanup; 1017 1018 // To measure the output, Last profile to Lab 1019 hLab = cmsCreateLab4ProfileTHR(ContextID, NULL); 1020 bp.hProofOutput = cmsCreateTransformTHR(ContextID, hProfiles[nProfiles-1], 1021 CHANNELS_SH(4)|BYTES_SH(2), hLab, TYPE_Lab_DBL, 1022 INTENT_RELATIVE_COLORIMETRIC, 1023 cmsFLAGS_NOCACHE|cmsFLAGS_NOOPTIMIZE); 1024 if ( bp.hProofOutput == NULL) goto Cleanup; 1025 1026 // Same as anterior, but lab in the 0..1 range 1027 bp.cmyk2Lab = cmsCreateTransformTHR(ContextID, hProfiles[nProfiles-1], 1028 FLOAT_SH(1)|CHANNELS_SH(4)|BYTES_SH(4), hLab, 1029 FLOAT_SH(1)|CHANNELS_SH(3)|BYTES_SH(4), 1030 INTENT_RELATIVE_COLORIMETRIC, 1031 cmsFLAGS_NOCACHE|cmsFLAGS_NOOPTIMIZE); 1032 if (bp.cmyk2Lab == NULL) goto Cleanup; 1033 cmsCloseProfile(hLab); 1034 1035 // Error estimation (for debug only) 1036 bp.MaxError = 0; 1037 1038 // How many gridpoints are we going to use? 1039 nGridPoints = _cmsReasonableGridpointsByColorspace(cmsSigCmykData, dwFlags); 1040 1041 1042 CLUT = cmsStageAllocCLut16bit(ContextID, nGridPoints, 4, 4, NULL); 1043 if (CLUT == NULL) goto Cleanup; 1044 1045 if (!cmsPipelineInsertStage(Result, cmsAT_BEGIN, CLUT)) 1046 goto Cleanup; 1047 1048 cmsStageSampleCLut16bit(CLUT, BlackPreservingSampler, (void*) &bp, 0); 1049 1050 Cleanup: 1051 1052 if (bp.cmyk2cmyk) cmsPipelineFree(bp.cmyk2cmyk); 1053 if (bp.cmyk2Lab) cmsDeleteTransform(bp.cmyk2Lab); 1054 if (bp.hProofOutput) cmsDeleteTransform(bp.hProofOutput); 1055 1056 if (bp.KTone) cmsFreeToneCurve(bp.KTone); 1057 if (bp.LabK2cmyk) cmsPipelineFree(bp.LabK2cmyk); 1058 1059 return Result; 1060 } 1061 1062 // Link routines ------------------------------------------------------------------------------------------------------ 1063 1064 // Chain several profiles into a single LUT. It just checks the parameters and then calls the handler 1065 // for the first intent in chain. The handler may be user-defined. Is up to the handler to deal with the 1066 // rest of intents in chain. A maximum of 255 profiles at time are supported, which is pretty reasonable. 1067 cmsPipeline* _cmsLinkProfiles(cmsContext ContextID, 1068 cmsUInt32Number nProfiles, 1069 cmsUInt32Number TheIntents[], 1070 cmsHPROFILE hProfiles[], 1071 cmsBool BPC[], 1072 cmsFloat64Number AdaptationStates[], 1073 cmsUInt32Number dwFlags) 1074 { 1075 cmsUInt32Number i; 1076 cmsIntentsList* Intent; 1077 1078 // Make sure a reasonable number of profiles is provided 1079 if (nProfiles <= 0 || nProfiles > 255) { 1080 cmsSignalError(ContextID, cmsERROR_RANGE, "Couldn't link '%d' profiles", nProfiles); 1081 return NULL; 1082 } 1083 1084 for (i=0; i < nProfiles; i++) { 1085 1086 // Check if black point is really needed or allowed. Note that 1087 // following Adobe's document: 1088 // BPC does not apply to devicelink profiles, nor to abs colorimetric, 1089 // and applies always on V4 perceptual and saturation. 1090 1091 if (TheIntents[i] == INTENT_ABSOLUTE_COLORIMETRIC) 1092 BPC[i] = FALSE; 1093 1094 if (TheIntents[i] == INTENT_PERCEPTUAL || TheIntents[i] == INTENT_SATURATION) { 1095 1096 // Force BPC for V4 profiles in perceptual and saturation 1097 if (cmsGetEncodedICCversion(hProfiles[i]) >= 0x4000000) 1098 BPC[i] = TRUE; 1099 } 1100 } 1101 1102 // Search for a handler. The first intent in the chain defines the handler. That would 1103 // prevent using multiple custom intents in a multiintent chain, but the behaviour of 1104 // this case would present some issues if the custom intent tries to do things like 1105 // preserve primaries. This solution is not perfect, but works well on most cases. 1106 1107 Intent = SearchIntent(ContextID, TheIntents[0]); 1108 if (Intent == NULL) { 1109 cmsSignalError(ContextID, cmsERROR_UNKNOWN_EXTENSION, "Unsupported intent '%d'", TheIntents[0]); 1110 return NULL; 1111 } 1112 1113 // Call the handler 1114 return Intent ->Link(ContextID, nProfiles, TheIntents, hProfiles, BPC, AdaptationStates, dwFlags); 1115 } 1116 1117 // ------------------------------------------------------------------------------------------------- 1118 1119 // Get information about available intents. nMax is the maximum space for the supplied "Codes" 1120 // and "Descriptions" the function returns the total number of intents, which may be greater 1121 // than nMax, although the matrices are not populated beyond this level. 1122 cmsUInt32Number CMSEXPORT cmsGetSupportedIntentsTHR(cmsContext ContextID, cmsUInt32Number nMax, cmsUInt32Number* Codes, char** Descriptions) 1123 { 1124 _cmsIntentsPluginChunkType* ctx = ( _cmsIntentsPluginChunkType*) _cmsContextGetClientChunk(ContextID, IntentPlugin); 1125 cmsIntentsList* pt; 1126 cmsUInt32Number nIntents; 1127 1128 1129 for (nIntents=0, pt = ctx->Intents; 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 1142 for (nIntents=0, pt = DefaultIntents; pt != NULL; pt = pt -> Next) 1143 { 1144 if (nIntents < nMax) { 1145 if (Codes != NULL) 1146 Codes[nIntents] = pt ->Intent; 1147 1148 if (Descriptions != NULL) 1149 Descriptions[nIntents] = pt ->Description; 1150 } 1151 1152 nIntents++; 1153 } 1154 return nIntents; 1155 } 1156 1157 cmsUInt32Number CMSEXPORT cmsGetSupportedIntents(cmsUInt32Number nMax, cmsUInt32Number* Codes, char** Descriptions) 1158 { 1159 return cmsGetSupportedIntentsTHR(NULL, nMax, Codes, Descriptions); 1160 } 1161 1162 // The plug-in registration. User can add new intents or override default routines 1163 cmsBool _cmsRegisterRenderingIntentPlugin(cmsContext id, cmsPluginBase* Data) 1164 { 1165 _cmsIntentsPluginChunkType* ctx = ( _cmsIntentsPluginChunkType*) _cmsContextGetClientChunk(id, IntentPlugin); 1166 cmsPluginRenderingIntent* Plugin = (cmsPluginRenderingIntent*) Data; 1167 cmsIntentsList* fl; 1168 1169 // Do we have to reset the custom intents? 1170 if (Data == NULL) { 1171 1172 ctx->Intents = NULL; 1173 return TRUE; 1174 } 1175 1176 fl = (cmsIntentsList*) _cmsPluginMalloc(id, sizeof(cmsIntentsList)); 1177 if (fl == NULL) return FALSE; 1178 1179 1180 fl ->Intent = Plugin ->Intent; 1181 strncpy(fl ->Description, Plugin ->Description, sizeof(fl ->Description)-1); 1182 fl ->Description[sizeof(fl ->Description)-1] = 0; 1183 1184 fl ->Link = Plugin ->Link; 1185 1186 fl ->Next = ctx ->Intents; 1187 ctx ->Intents = fl; 1188 1189 return TRUE; 1190 } 1191