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