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