1 //--------------------------------------------------------------------------------- 2 // 3 // Little Color Management System 4 // Copyright (c) 1998-2012 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 // Auxiliar: append a Lab identity after the given sequence of profiles 31 // and return the transform. Lab profile is closed, rest of profiles are kept open. 32 cmsHTRANSFORM _cmsChain2Lab(cmsContext ContextID, 33 cmsUInt32Number nProfiles, 34 cmsUInt32Number InputFormat, 35 cmsUInt32Number OutputFormat, 36 const cmsUInt32Number Intents[], 37 const cmsHPROFILE hProfiles[], 38 const cmsBool BPC[], 39 const cmsFloat64Number AdaptationStates[], 40 cmsUInt32Number dwFlags) 41 { 42 cmsHTRANSFORM xform; 43 cmsHPROFILE hLab; 44 cmsHPROFILE ProfileList[256]; 45 cmsBool BPCList[256]; 46 cmsFloat64Number AdaptationList[256]; 47 cmsUInt32Number IntentList[256]; 48 cmsUInt32Number i; 49 50 // This is a rather big number and there is no need of dynamic memory 51 // since we are adding a profile, 254 + 1 = 255 and this is the limit 52 if (nProfiles > 254) return NULL; 53 54 // The output space 55 hLab = cmsCreateLab4ProfileTHR(ContextID, NULL); 56 if (hLab == NULL) return NULL; 57 58 // Create a copy of parameters 59 for (i=0; i < nProfiles; i++) { 60 61 ProfileList[i] = hProfiles[i]; 62 BPCList[i] = BPC[i]; 63 AdaptationList[i] = AdaptationStates[i]; 64 IntentList[i] = Intents[i]; 65 } 66 67 // Place Lab identity at chain's end. 68 ProfileList[nProfiles] = hLab; 69 BPCList[nProfiles] = 0; 70 AdaptationList[nProfiles] = 1.0; 71 IntentList[nProfiles] = INTENT_RELATIVE_COLORIMETRIC; 72 73 // Create the transform 74 xform = cmsCreateExtendedTransform(ContextID, nProfiles + 1, ProfileList, 75 BPCList, 76 IntentList, 77 AdaptationList, 78 NULL, 0, 79 InputFormat, 80 OutputFormat, 81 dwFlags); 82 83 cmsCloseProfile(hLab); 84 85 return xform; 86 } 87 88 89 // Compute K -> L* relationship. Flags may include black point compensation. In this case, 90 // the relationship is assumed from the profile with BPC to a black point zero. 91 static 92 cmsToneCurve* ComputeKToLstar(cmsContext ContextID, 93 cmsUInt32Number nPoints, 94 cmsUInt32Number nProfiles, 95 const cmsUInt32Number Intents[], 96 const cmsHPROFILE hProfiles[], 97 const cmsBool BPC[], 98 const cmsFloat64Number AdaptationStates[], 99 cmsUInt32Number dwFlags) 100 { 101 cmsToneCurve* out = NULL; 102 cmsUInt32Number i; 103 cmsHTRANSFORM xform; 104 cmsCIELab Lab; 105 cmsFloat32Number cmyk[4]; 106 cmsFloat32Number* SampledPoints; 107 108 xform = _cmsChain2Lab(ContextID, nProfiles, TYPE_CMYK_FLT, TYPE_Lab_DBL, Intents, hProfiles, BPC, AdaptationStates, dwFlags); 109 if (xform == NULL) return NULL; 110 111 SampledPoints = (cmsFloat32Number*) _cmsCalloc(ContextID, nPoints, sizeof(cmsFloat32Number)); 112 if (SampledPoints == NULL) goto Error; 113 114 for (i=0; i < nPoints; i++) { 115 116 cmyk[0] = 0; 117 cmyk[1] = 0; 118 cmyk[2] = 0; 119 cmyk[3] = (cmsFloat32Number) ((i * 100.0) / (nPoints-1)); 120 121 cmsDoTransform(xform, cmyk, &Lab, 1); 122 SampledPoints[i]= (cmsFloat32Number) (1.0 - Lab.L / 100.0); // Negate K for easier operation 123 } 124 125 out = cmsBuildTabulatedToneCurveFloat(ContextID, nPoints, SampledPoints); 126 127 Error: 128 129 cmsDeleteTransform(xform); 130 if (SampledPoints) _cmsFree(ContextID, SampledPoints); 131 132 return out; 133 } 134 135 136 // Compute Black tone curve on a CMYK -> CMYK transform. This is done by 137 // using the proof direction on both profiles to find K->L* relationship 138 // then joining both curves. dwFlags may include black point compensation. 139 cmsToneCurve* _cmsBuildKToneCurve(cmsContext ContextID, 140 cmsUInt32Number nPoints, 141 cmsUInt32Number nProfiles, 142 const cmsUInt32Number Intents[], 143 const cmsHPROFILE hProfiles[], 144 const cmsBool BPC[], 145 const cmsFloat64Number AdaptationStates[], 146 cmsUInt32Number dwFlags) 147 { 148 cmsToneCurve *in, *out, *KTone; 149 150 // Make sure CMYK -> CMYK 151 if (cmsGetColorSpace(hProfiles[0]) != cmsSigCmykData || 152 cmsGetColorSpace(hProfiles[nProfiles-1])!= cmsSigCmykData) return NULL; 153 154 155 // Make sure last is an output profile 156 if (cmsGetDeviceClass(hProfiles[nProfiles - 1]) != cmsSigOutputClass) return NULL; 157 158 // Create individual curves. BPC works also as each K to L* is 159 // computed as a BPC to zero black point in case of L* 160 in = ComputeKToLstar(ContextID, nPoints, nProfiles - 1, Intents, hProfiles, BPC, AdaptationStates, dwFlags); 161 if (in == NULL) return NULL; 162 163 out = ComputeKToLstar(ContextID, nPoints, 1, 164 Intents + (nProfiles - 1), 165 &hProfiles [nProfiles - 1], 166 BPC + (nProfiles - 1), 167 AdaptationStates + (nProfiles - 1), 168 dwFlags); 169 if (out == NULL) { 170 cmsFreeToneCurve(in); 171 return NULL; 172 } 173 174 // Build the relationship. This effectively limits the maximum accuracy to 16 bits, but 175 // since this is used on black-preserving LUTs, we are not loosing accuracy in any case 176 KTone = cmsJoinToneCurve(ContextID, in, out, nPoints); 177 178 // Get rid of components 179 cmsFreeToneCurve(in); cmsFreeToneCurve(out); 180 181 // Something went wrong... 182 if (KTone == NULL) return NULL; 183 184 // Make sure it is monotonic 185 if (!cmsIsToneCurveMonotonic(KTone)) { 186 cmsFreeToneCurve(KTone); 187 return NULL; 188 } 189 190 return KTone; 191 } 192 193 194 // Gamut LUT Creation ----------------------------------------------------------------------------------------- 195 196 // Used by gamut & softproofing 197 198 typedef struct { 199 200 cmsHTRANSFORM hInput; // From whatever input color space. 16 bits to DBL 201 cmsHTRANSFORM hForward, hReverse; // Transforms going from Lab to colorant and back 202 cmsFloat64Number Thereshold; // The thereshold after which is considered out of gamut 203 204 } GAMUTCHAIN; 205 206 // This sampler does compute gamut boundaries by comparing original 207 // values with a transform going back and forth. Values above ERR_THERESHOLD 208 // of maximum are considered out of gamut. 209 210 #define ERR_THERESHOLD 5 211 212 213 static 214 int GamutSampler(register const cmsUInt16Number In[], register cmsUInt16Number Out[], register void* Cargo) 215 { 216 GAMUTCHAIN* t = (GAMUTCHAIN* ) Cargo; 217 cmsCIELab LabIn1, LabOut1; 218 cmsCIELab LabIn2, LabOut2; 219 cmsUInt16Number Proof[cmsMAXCHANNELS], Proof2[cmsMAXCHANNELS]; 220 cmsFloat64Number dE1, dE2, ErrorRatio; 221 222 // Assume in-gamut by default. 223 ErrorRatio = 1.0; 224 225 // Convert input to Lab 226 cmsDoTransform(t -> hInput, In, &LabIn1, 1); 227 228 // converts from PCS to colorant. This always 229 // does return in-gamut values, 230 cmsDoTransform(t -> hForward, &LabIn1, Proof, 1); 231 232 // Now, do the inverse, from colorant to PCS. 233 cmsDoTransform(t -> hReverse, Proof, &LabOut1, 1); 234 235 memmove(&LabIn2, &LabOut1, sizeof(cmsCIELab)); 236 237 // Try again, but this time taking Check as input 238 cmsDoTransform(t -> hForward, &LabOut1, Proof2, 1); 239 cmsDoTransform(t -> hReverse, Proof2, &LabOut2, 1); 240 241 // Take difference of direct value 242 dE1 = cmsDeltaE(&LabIn1, &LabOut1); 243 244 // Take difference of converted value 245 dE2 = cmsDeltaE(&LabIn2, &LabOut2); 246 247 248 // if dE1 is small and dE2 is small, value is likely to be in gamut 249 if (dE1 < t->Thereshold && dE2 < t->Thereshold) 250 Out[0] = 0; 251 else { 252 253 // if dE1 is small and dE2 is big, undefined. Assume in gamut 254 if (dE1 < t->Thereshold && dE2 > t->Thereshold) 255 Out[0] = 0; 256 else 257 // dE1 is big and dE2 is small, clearly out of gamut 258 if (dE1 > t->Thereshold && dE2 < t->Thereshold) 259 Out[0] = (cmsUInt16Number) _cmsQuickFloor((dE1 - t->Thereshold) + .5); 260 else { 261 262 // dE1 is big and dE2 is also big, could be due to perceptual mapping 263 // so take error ratio 264 if (dE2 == 0.0) 265 ErrorRatio = dE1; 266 else 267 ErrorRatio = dE1 / dE2; 268 269 if (ErrorRatio > t->Thereshold) 270 Out[0] = (cmsUInt16Number) _cmsQuickFloor((ErrorRatio - t->Thereshold) + .5); 271 else 272 Out[0] = 0; 273 } 274 } 275 276 277 return TRUE; 278 } 279 280 // Does compute a gamut LUT going back and forth across pcs -> relativ. colorimetric intent -> pcs 281 // the dE obtained is then annotated on the LUT. Values truely out of gamut are clipped to dE = 0xFFFE 282 // and values changed are supposed to be handled by any gamut remapping, so, are out of gamut as well. 283 // 284 // **WARNING: This algorithm does assume that gamut remapping algorithms does NOT move in-gamut colors, 285 // of course, many perceptual and saturation intents does not work in such way, but relativ. ones should. 286 287 cmsPipeline* _cmsCreateGamutCheckPipeline(cmsContext ContextID, 288 cmsHPROFILE hProfiles[], 289 cmsBool BPC[], 290 cmsUInt32Number Intents[], 291 cmsFloat64Number AdaptationStates[], 292 cmsUInt32Number nGamutPCSposition, 293 cmsHPROFILE hGamut) 294 { 295 cmsHPROFILE hLab; 296 cmsPipeline* Gamut; 297 cmsStage* CLUT; 298 cmsUInt32Number dwFormat; 299 GAMUTCHAIN Chain; 300 int nChannels, nGridpoints; 301 cmsColorSpaceSignature ColorSpace; 302 cmsUInt32Number i; 303 cmsHPROFILE ProfileList[256]; 304 cmsBool BPCList[256]; 305 cmsFloat64Number AdaptationList[256]; 306 cmsUInt32Number IntentList[256]; 307 308 memset(&Chain, 0, sizeof(GAMUTCHAIN)); 309 310 311 if (nGamutPCSposition <= 0 || nGamutPCSposition > 255) { 312 cmsSignalError(ContextID, cmsERROR_RANGE, "Wrong position of PCS. 1..255 expected, %d found.", nGamutPCSposition); 313 return NULL; 314 } 315 316 hLab = cmsCreateLab4ProfileTHR(ContextID, NULL); 317 if (hLab == NULL) return NULL; 318 319 320 // The figure of merit. On matrix-shaper profiles, should be almost zero as 321 // the conversion is pretty exact. On LUT based profiles, different resolutions 322 // of input and output CLUT may result in differences. 323 324 if (cmsIsMatrixShaper(hGamut)) { 325 326 Chain.Thereshold = 1.0; 327 } 328 else { 329 Chain.Thereshold = ERR_THERESHOLD; 330 } 331 332 333 // Create a copy of parameters 334 for (i=0; i < nGamutPCSposition; i++) { 335 ProfileList[i] = hProfiles[i]; 336 BPCList[i] = BPC[i]; 337 AdaptationList[i] = AdaptationStates[i]; 338 IntentList[i] = Intents[i]; 339 } 340 341 // Fill Lab identity 342 ProfileList[nGamutPCSposition] = hLab; 343 BPCList[nGamutPCSposition] = 0; 344 AdaptationList[nGamutPCSposition] = 1.0; 345 IntentList[nGamutPCSposition] = INTENT_RELATIVE_COLORIMETRIC; 346 347 348 ColorSpace = cmsGetColorSpace(hGamut); 349 350 nChannels = cmsChannelsOf(ColorSpace); 351 nGridpoints = _cmsReasonableGridpointsByColorspace(ColorSpace, cmsFLAGS_HIGHRESPRECALC); 352 dwFormat = (CHANNELS_SH(nChannels)|BYTES_SH(2)); 353 354 // 16 bits to Lab double 355 Chain.hInput = cmsCreateExtendedTransform(ContextID, 356 nGamutPCSposition + 1, 357 ProfileList, 358 BPCList, 359 IntentList, 360 AdaptationList, 361 NULL, 0, 362 dwFormat, TYPE_Lab_DBL, 363 cmsFLAGS_NOCACHE); 364 365 366 // Does create the forward step. Lab double to device 367 dwFormat = (CHANNELS_SH(nChannels)|BYTES_SH(2)); 368 Chain.hForward = cmsCreateTransformTHR(ContextID, 369 hLab, TYPE_Lab_DBL, 370 hGamut, dwFormat, 371 INTENT_RELATIVE_COLORIMETRIC, 372 cmsFLAGS_NOCACHE); 373 374 // Does create the backwards step 375 Chain.hReverse = cmsCreateTransformTHR(ContextID, hGamut, dwFormat, 376 hLab, TYPE_Lab_DBL, 377 INTENT_RELATIVE_COLORIMETRIC, 378 cmsFLAGS_NOCACHE); 379 380 381 // All ok? 382 if (Chain.hInput && Chain.hForward && Chain.hReverse) { 383 384 // Go on, try to compute gamut LUT from PCS. This consist on a single channel containing 385 // dE when doing a transform back and forth on the colorimetric intent. 386 387 Gamut = cmsPipelineAlloc(ContextID, 3, 1); 388 if (Gamut != NULL) { 389 390 CLUT = cmsStageAllocCLut16bit(ContextID, nGridpoints, nChannels, 1, NULL); 391 if (!cmsPipelineInsertStage(Gamut, cmsAT_BEGIN, CLUT)) { 392 cmsPipelineFree(Gamut); 393 Gamut = NULL; 394 } 395 else { 396 cmsStageSampleCLut16bit(CLUT, GamutSampler, (void*) &Chain, 0); 397 } 398 } 399 } 400 else 401 Gamut = NULL; // Didn't work... 402 403 // Free all needed stuff. 404 if (Chain.hInput) cmsDeleteTransform(Chain.hInput); 405 if (Chain.hForward) cmsDeleteTransform(Chain.hForward); 406 if (Chain.hReverse) cmsDeleteTransform(Chain.hReverse); 407 if (hLab) cmsCloseProfile(hLab); 408 409 // And return computed hull 410 return Gamut; 411 } 412 413 // Total Area Coverage estimation ---------------------------------------------------------------- 414 415 typedef struct { 416 cmsUInt32Number nOutputChans; 417 cmsHTRANSFORM hRoundTrip; 418 cmsFloat32Number MaxTAC; 419 cmsFloat32Number MaxInput[cmsMAXCHANNELS]; 420 421 } cmsTACestimator; 422 423 424 // This callback just accounts the maximum ink dropped in the given node. It does not populate any 425 // memory, as the destination table is NULL. Its only purpose it to know the global maximum. 426 static 427 int EstimateTAC(register const cmsUInt16Number In[], register cmsUInt16Number Out[], register void * Cargo) 428 { 429 cmsTACestimator* bp = (cmsTACestimator*) Cargo; 430 cmsFloat32Number RoundTrip[cmsMAXCHANNELS]; 431 cmsUInt32Number i; 432 cmsFloat32Number Sum; 433 434 435 // Evaluate the xform 436 cmsDoTransform(bp->hRoundTrip, In, RoundTrip, 1); 437 438 // All all amounts of ink 439 for (Sum=0, i=0; i < bp ->nOutputChans; i++) 440 Sum += RoundTrip[i]; 441 442 // If above maximum, keep track of input values 443 if (Sum > bp ->MaxTAC) { 444 445 bp ->MaxTAC = Sum; 446 447 for (i=0; i < bp ->nOutputChans; i++) { 448 bp ->MaxInput[i] = In[i]; 449 } 450 } 451 452 return TRUE; 453 454 cmsUNUSED_PARAMETER(Out); 455 } 456 457 458 // Detect Total area coverage of the profile 459 cmsFloat64Number CMSEXPORT cmsDetectTAC(cmsHPROFILE hProfile) 460 { 461 cmsTACestimator bp; 462 cmsUInt32Number dwFormatter; 463 cmsUInt32Number GridPoints[MAX_INPUT_DIMENSIONS]; 464 cmsHPROFILE hLab; 465 cmsContext ContextID = cmsGetProfileContextID(hProfile); 466 467 // TAC only works on output profiles 468 if (cmsGetDeviceClass(hProfile) != cmsSigOutputClass) { 469 return 0; 470 } 471 472 // Create a fake formatter for result 473 dwFormatter = cmsFormatterForColorspaceOfProfile(hProfile, 4, TRUE); 474 475 bp.nOutputChans = T_CHANNELS(dwFormatter); 476 bp.MaxTAC = 0; // Initial TAC is 0 477 478 // for safety 479 if (bp.nOutputChans >= cmsMAXCHANNELS) return 0; 480 481 hLab = cmsCreateLab4ProfileTHR(ContextID, NULL); 482 if (hLab == NULL) return 0; 483 // Setup a roundtrip on perceptual intent in output profile for TAC estimation 484 bp.hRoundTrip = cmsCreateTransformTHR(ContextID, hLab, TYPE_Lab_16, 485 hProfile, dwFormatter, INTENT_PERCEPTUAL, cmsFLAGS_NOOPTIMIZE|cmsFLAGS_NOCACHE); 486 487 cmsCloseProfile(hLab); 488 if (bp.hRoundTrip == NULL) return 0; 489 490 // For L* we only need black and white. For C* we need many points 491 GridPoints[0] = 6; 492 GridPoints[1] = 74; 493 GridPoints[2] = 74; 494 495 496 if (!cmsSliceSpace16(3, GridPoints, EstimateTAC, &bp)) { 497 bp.MaxTAC = 0; 498 } 499 500 cmsDeleteTransform(bp.hRoundTrip); 501 502 // Results in % 503 return bp.MaxTAC; 504 } 505 506 507 // Carefully, clamp on CIELab space. 508 509 cmsBool CMSEXPORT cmsDesaturateLab(cmsCIELab* Lab, 510 double amax, double amin, 511 double bmax, double bmin) 512 { 513 514 // Whole Luma surface to zero 515 516 if (Lab -> L < 0) { 517 518 Lab-> L = Lab->a = Lab-> b = 0.0; 519 return FALSE; 520 } 521 522 // Clamp white, DISCARD HIGHLIGHTS. This is done 523 // in such way because icc spec doesn't allow the 524 // use of L>100 as a highlight means. 525 526 if (Lab->L > 100) 527 Lab -> L = 100; 528 529 // Check out gamut prism, on a, b faces 530 531 if (Lab -> a < amin || Lab->a > amax|| 532 Lab -> b < bmin || Lab->b > bmax) { 533 534 cmsCIELCh LCh; 535 double h, slope; 536 537 // Falls outside a, b limits. Transports to LCh space, 538 // and then do the clipping 539 540 541 if (Lab -> a == 0.0) { // Is hue exactly 90? 542 543 // atan will not work, so clamp here 544 Lab -> b = Lab->b < 0 ? bmin : bmax; 545 return TRUE; 546 } 547 548 cmsLab2LCh(&LCh, Lab); 549 550 slope = Lab -> b / Lab -> a; 551 h = LCh.h; 552 553 // There are 4 zones 554 555 if ((h >= 0. && h < 45.) || 556 (h >= 315 && h <= 360.)) { 557 558 // clip by amax 559 Lab -> a = amax; 560 Lab -> b = amax * slope; 561 } 562 else 563 if (h >= 45. && h < 135.) 564 { 565 // clip by bmax 566 Lab -> b = bmax; 567 Lab -> a = bmax / slope; 568 } 569 else 570 if (h >= 135. && h < 225.) { 571 // clip by amin 572 Lab -> a = amin; 573 Lab -> b = amin * slope; 574 575 } 576 else 577 if (h >= 225. && h < 315.) { 578 // clip by bmin 579 Lab -> b = bmin; 580 Lab -> a = bmin / slope; 581 } 582 else { 583 cmsSignalError(0, cmsERROR_RANGE, "Invalid angle"); 584 return FALSE; 585 } 586 587 } 588 589 return TRUE; 590 } 591