1 //--------------------------------------------------------------------------------- 2 // 3 // Little Color Management System 4 // Copyright (c) 1998-2014 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 // Virtual (built-in) profiles 30 // ----------------------------------------------------------------------------------- 31 32 static 33 cmsBool SetTextTags(cmsHPROFILE hProfile, const wchar_t* Description) 34 { 35 cmsMLU *DescriptionMLU, *CopyrightMLU; 36 cmsBool rc = FALSE; 37 cmsContext ContextID = cmsGetProfileContextID(hProfile); 38 39 DescriptionMLU = cmsMLUalloc(ContextID, 1); 40 CopyrightMLU = cmsMLUalloc(ContextID, 1); 41 42 if (DescriptionMLU == NULL || CopyrightMLU == NULL) goto Error; 43 44 if (!cmsMLUsetWide(DescriptionMLU, "en", "US", Description)) goto Error; 45 if (!cmsMLUsetWide(CopyrightMLU, "en", "US", L"No copyright, use freely")) goto Error; 46 47 if (!cmsWriteTag(hProfile, cmsSigProfileDescriptionTag, DescriptionMLU)) goto Error; 48 if (!cmsWriteTag(hProfile, cmsSigCopyrightTag, CopyrightMLU)) goto Error; 49 50 rc = TRUE; 51 52 Error: 53 54 if (DescriptionMLU) 55 cmsMLUfree(DescriptionMLU); 56 if (CopyrightMLU) 57 cmsMLUfree(CopyrightMLU); 58 return rc; 59 } 60 61 62 static 63 cmsBool SetSeqDescTag(cmsHPROFILE hProfile, const char* Model) 64 { 65 cmsBool rc = FALSE; 66 cmsContext ContextID = cmsGetProfileContextID(hProfile); 67 cmsSEQ* Seq = cmsAllocProfileSequenceDescription(ContextID, 1); 68 69 if (Seq == NULL) return FALSE; 70 71 Seq->seq[0].deviceMfg = (cmsSignature) 0; 72 Seq->seq[0].deviceModel = (cmsSignature) 0; 73 74 #ifdef CMS_DONT_USE_INT64 75 Seq->seq[0].attributes[0] = 0; 76 Seq->seq[0].attributes[1] = 0; 77 #else 78 Seq->seq[0].attributes = 0; 79 #endif 80 81 Seq->seq[0].technology = (cmsTechnologySignature) 0; 82 83 cmsMLUsetASCII( Seq->seq[0].Manufacturer, cmsNoLanguage, cmsNoCountry, "Little CMS"); 84 cmsMLUsetASCII( Seq->seq[0].Model, cmsNoLanguage, cmsNoCountry, Model); 85 86 if (!_cmsWriteProfileSequence(hProfile, Seq)) goto Error; 87 88 rc = TRUE; 89 90 Error: 91 if (Seq) 92 cmsFreeProfileSequenceDescription(Seq); 93 94 return rc; 95 } 96 97 98 99 // This function creates a profile based on White point, primaries and 100 // transfer functions. 101 cmsHPROFILE CMSEXPORT cmsCreateRGBProfileTHR(cmsContext ContextID, 102 const cmsCIExyY* WhitePoint, 103 const cmsCIExyYTRIPLE* Primaries, 104 cmsToneCurve* const TransferFunction[3]) 105 { 106 cmsHPROFILE hICC; 107 cmsMAT3 MColorants; 108 cmsCIEXYZTRIPLE Colorants; 109 cmsCIExyY MaxWhite; 110 cmsMAT3 CHAD; 111 cmsCIEXYZ WhitePointXYZ; 112 113 hICC = cmsCreateProfilePlaceholder(ContextID); 114 if (!hICC) // can't allocate 115 return NULL; 116 117 cmsSetProfileVersion(hICC, 4.3); 118 119 cmsSetDeviceClass(hICC, cmsSigDisplayClass); 120 cmsSetColorSpace(hICC, cmsSigRgbData); 121 cmsSetPCS(hICC, cmsSigXYZData); 122 123 cmsSetHeaderRenderingIntent(hICC, INTENT_PERCEPTUAL); 124 125 126 // Implement profile using following tags: 127 // 128 // 1 cmsSigProfileDescriptionTag 129 // 2 cmsSigMediaWhitePointTag 130 // 3 cmsSigRedColorantTag 131 // 4 cmsSigGreenColorantTag 132 // 5 cmsSigBlueColorantTag 133 // 6 cmsSigRedTRCTag 134 // 7 cmsSigGreenTRCTag 135 // 8 cmsSigBlueTRCTag 136 // 9 Chromatic adaptation Tag 137 // This conforms a standard RGB DisplayProfile as says ICC, and then I add (As per addendum II) 138 // 10 cmsSigChromaticityTag 139 140 141 if (!SetTextTags(hICC, L"RGB built-in")) goto Error; 142 143 if (WhitePoint) { 144 145 if (!cmsWriteTag(hICC, cmsSigMediaWhitePointTag, cmsD50_XYZ())) goto Error; 146 147 cmsxyY2XYZ(&WhitePointXYZ, WhitePoint); 148 _cmsAdaptationMatrix(&CHAD, NULL, &WhitePointXYZ, cmsD50_XYZ()); 149 150 // This is a V4 tag, but many CMM does read and understand it no matter which version 151 if (!cmsWriteTag(hICC, cmsSigChromaticAdaptationTag, (void*) &CHAD)) goto Error; 152 } 153 154 if (WhitePoint && Primaries) { 155 156 MaxWhite.x = WhitePoint -> x; 157 MaxWhite.y = WhitePoint -> y; 158 MaxWhite.Y = 1.0; 159 160 if (!_cmsBuildRGB2XYZtransferMatrix(&MColorants, &MaxWhite, Primaries)) goto Error; 161 162 Colorants.Red.X = MColorants.v[0].n[0]; 163 Colorants.Red.Y = MColorants.v[1].n[0]; 164 Colorants.Red.Z = MColorants.v[2].n[0]; 165 166 Colorants.Green.X = MColorants.v[0].n[1]; 167 Colorants.Green.Y = MColorants.v[1].n[1]; 168 Colorants.Green.Z = MColorants.v[2].n[1]; 169 170 Colorants.Blue.X = MColorants.v[0].n[2]; 171 Colorants.Blue.Y = MColorants.v[1].n[2]; 172 Colorants.Blue.Z = MColorants.v[2].n[2]; 173 174 if (!cmsWriteTag(hICC, cmsSigRedColorantTag, (void*) &Colorants.Red)) goto Error; 175 if (!cmsWriteTag(hICC, cmsSigBlueColorantTag, (void*) &Colorants.Blue)) goto Error; 176 if (!cmsWriteTag(hICC, cmsSigGreenColorantTag, (void*) &Colorants.Green)) goto Error; 177 } 178 179 180 if (TransferFunction) { 181 182 // Tries to minimize space. Thanks to Richard Hughes for this nice idea 183 if (!cmsWriteTag(hICC, cmsSigRedTRCTag, (void*) TransferFunction[0])) goto Error; 184 185 if (TransferFunction[1] == TransferFunction[0]) { 186 187 if (!cmsLinkTag (hICC, cmsSigGreenTRCTag, cmsSigRedTRCTag)) goto Error; 188 189 } else { 190 191 if (!cmsWriteTag(hICC, cmsSigGreenTRCTag, (void*) TransferFunction[1])) goto Error; 192 } 193 194 if (TransferFunction[2] == TransferFunction[0]) { 195 196 if (!cmsLinkTag (hICC, cmsSigBlueTRCTag, cmsSigRedTRCTag)) goto Error; 197 198 } else { 199 200 if (!cmsWriteTag(hICC, cmsSigBlueTRCTag, (void*) TransferFunction[2])) goto Error; 201 } 202 } 203 204 if (Primaries) { 205 if (!cmsWriteTag(hICC, cmsSigChromaticityTag, (void*) Primaries)) goto Error; 206 } 207 208 209 return hICC; 210 211 Error: 212 if (hICC) 213 cmsCloseProfile(hICC); 214 return NULL; 215 } 216 217 cmsHPROFILE CMSEXPORT cmsCreateRGBProfile(const cmsCIExyY* WhitePoint, 218 const cmsCIExyYTRIPLE* Primaries, 219 cmsToneCurve* const TransferFunction[3]) 220 { 221 return cmsCreateRGBProfileTHR(NULL, WhitePoint, Primaries, TransferFunction); 222 } 223 224 225 226 // This function creates a profile based on White point and transfer function. 227 cmsHPROFILE CMSEXPORT cmsCreateGrayProfileTHR(cmsContext ContextID, 228 const cmsCIExyY* WhitePoint, 229 const cmsToneCurve* TransferFunction) 230 { 231 cmsHPROFILE hICC; 232 cmsCIEXYZ tmp; 233 234 hICC = cmsCreateProfilePlaceholder(ContextID); 235 if (!hICC) // can't allocate 236 return NULL; 237 238 cmsSetProfileVersion(hICC, 4.3); 239 240 cmsSetDeviceClass(hICC, cmsSigDisplayClass); 241 cmsSetColorSpace(hICC, cmsSigGrayData); 242 cmsSetPCS(hICC, cmsSigXYZData); 243 cmsSetHeaderRenderingIntent(hICC, INTENT_PERCEPTUAL); 244 245 246 // Implement profile using following tags: 247 // 248 // 1 cmsSigProfileDescriptionTag 249 // 2 cmsSigMediaWhitePointTag 250 // 3 cmsSigGrayTRCTag 251 252 // This conforms a standard Gray DisplayProfile 253 254 // Fill-in the tags 255 256 if (!SetTextTags(hICC, L"gray built-in")) goto Error; 257 258 259 if (WhitePoint) { 260 261 cmsxyY2XYZ(&tmp, WhitePoint); 262 if (!cmsWriteTag(hICC, cmsSigMediaWhitePointTag, (void*) &tmp)) goto Error; 263 } 264 265 if (TransferFunction) { 266 267 if (!cmsWriteTag(hICC, cmsSigGrayTRCTag, (void*) TransferFunction)) goto Error; 268 } 269 270 return hICC; 271 272 Error: 273 if (hICC) 274 cmsCloseProfile(hICC); 275 return NULL; 276 } 277 278 279 280 cmsHPROFILE CMSEXPORT cmsCreateGrayProfile(const cmsCIExyY* WhitePoint, 281 const cmsToneCurve* TransferFunction) 282 { 283 return cmsCreateGrayProfileTHR(NULL, WhitePoint, TransferFunction); 284 } 285 286 // This is a devicelink operating in the target colorspace with as many transfer functions as components 287 288 cmsHPROFILE CMSEXPORT cmsCreateLinearizationDeviceLinkTHR(cmsContext ContextID, 289 cmsColorSpaceSignature ColorSpace, 290 cmsToneCurve* const TransferFunctions[]) 291 { 292 cmsHPROFILE hICC; 293 cmsPipeline* Pipeline; 294 int nChannels; 295 296 hICC = cmsCreateProfilePlaceholder(ContextID); 297 if (!hICC) 298 return NULL; 299 300 cmsSetProfileVersion(hICC, 4.3); 301 302 cmsSetDeviceClass(hICC, cmsSigLinkClass); 303 cmsSetColorSpace(hICC, ColorSpace); 304 cmsSetPCS(hICC, ColorSpace); 305 306 cmsSetHeaderRenderingIntent(hICC, INTENT_PERCEPTUAL); 307 308 // Set up channels 309 nChannels = cmsChannelsOf(ColorSpace); 310 311 // Creates a Pipeline with prelinearization step only 312 Pipeline = cmsPipelineAlloc(ContextID, nChannels, nChannels); 313 if (Pipeline == NULL) goto Error; 314 315 316 // Copy tables to Pipeline 317 if (!cmsPipelineInsertStage(Pipeline, cmsAT_BEGIN, cmsStageAllocToneCurves(ContextID, nChannels, TransferFunctions))) 318 goto Error; 319 320 // Create tags 321 if (!SetTextTags(hICC, L"Linearization built-in")) goto Error; 322 if (!cmsWriteTag(hICC, cmsSigAToB0Tag, (void*) Pipeline)) goto Error; 323 if (!SetSeqDescTag(hICC, "Linearization built-in")) goto Error; 324 325 // Pipeline is already on virtual profile 326 cmsPipelineFree(Pipeline); 327 328 // Ok, done 329 return hICC; 330 331 Error: 332 cmsPipelineFree(Pipeline); 333 if (hICC) 334 cmsCloseProfile(hICC); 335 336 337 return NULL; 338 } 339 340 cmsHPROFILE CMSEXPORT cmsCreateLinearizationDeviceLink(cmsColorSpaceSignature ColorSpace, 341 cmsToneCurve* const TransferFunctions[]) 342 { 343 return cmsCreateLinearizationDeviceLinkTHR(NULL, ColorSpace, TransferFunctions); 344 } 345 346 // Ink-limiting algorithm 347 // 348 // Sum = C + M + Y + K 349 // If Sum > InkLimit 350 // Ratio= 1 - (Sum - InkLimit) / (C + M + Y) 351 // if Ratio <0 352 // Ratio=0 353 // endif 354 // Else 355 // Ratio=1 356 // endif 357 // 358 // C = Ratio * C 359 // M = Ratio * M 360 // Y = Ratio * Y 361 // K: Does not change 362 363 static 364 int InkLimitingSampler(register const cmsUInt16Number In[], register cmsUInt16Number Out[], register void* Cargo) 365 { 366 cmsFloat64Number InkLimit = *(cmsFloat64Number *) Cargo; 367 cmsFloat64Number SumCMY, SumCMYK, Ratio; 368 369 InkLimit = (InkLimit * 655.35); 370 371 SumCMY = In[0] + In[1] + In[2]; 372 SumCMYK = SumCMY + In[3]; 373 374 if (SumCMYK > InkLimit) { 375 376 Ratio = 1 - ((SumCMYK - InkLimit) / SumCMY); 377 if (Ratio < 0) 378 Ratio = 0; 379 } 380 else Ratio = 1; 381 382 Out[0] = _cmsQuickSaturateWord(In[0] * Ratio); // C 383 Out[1] = _cmsQuickSaturateWord(In[1] * Ratio); // M 384 Out[2] = _cmsQuickSaturateWord(In[2] * Ratio); // Y 385 386 Out[3] = In[3]; // K (untouched) 387 388 return TRUE; 389 } 390 391 // This is a devicelink operating in CMYK for ink-limiting 392 393 cmsHPROFILE CMSEXPORT cmsCreateInkLimitingDeviceLinkTHR(cmsContext ContextID, 394 cmsColorSpaceSignature ColorSpace, 395 cmsFloat64Number Limit) 396 { 397 cmsHPROFILE hICC; 398 cmsPipeline* LUT; 399 cmsStage* CLUT; 400 int nChannels; 401 402 if (ColorSpace != cmsSigCmykData) { 403 cmsSignalError(ContextID, cmsERROR_COLORSPACE_CHECK, "InkLimiting: Only CMYK currently supported"); 404 return NULL; 405 } 406 407 if (Limit < 0.0 || Limit > 400) { 408 409 cmsSignalError(ContextID, cmsERROR_RANGE, "InkLimiting: Limit should be between 0..400"); 410 if (Limit < 0) Limit = 0; 411 if (Limit > 400) Limit = 400; 412 413 } 414 415 hICC = cmsCreateProfilePlaceholder(ContextID); 416 if (!hICC) // can't allocate 417 return NULL; 418 419 cmsSetProfileVersion(hICC, 4.3); 420 421 cmsSetDeviceClass(hICC, cmsSigLinkClass); 422 cmsSetColorSpace(hICC, ColorSpace); 423 cmsSetPCS(hICC, ColorSpace); 424 425 cmsSetHeaderRenderingIntent(hICC, INTENT_PERCEPTUAL); 426 427 428 // Creates a Pipeline with 3D grid only 429 LUT = cmsPipelineAlloc(ContextID, 4, 4); 430 if (LUT == NULL) goto Error; 431 432 433 nChannels = cmsChannelsOf(ColorSpace); 434 435 CLUT = cmsStageAllocCLut16bit(ContextID, 17, nChannels, nChannels, NULL); 436 if (CLUT == NULL) goto Error; 437 438 if (!cmsStageSampleCLut16bit(CLUT, InkLimitingSampler, (void*) &Limit, 0)) goto Error; 439 440 if (!cmsPipelineInsertStage(LUT, cmsAT_BEGIN, _cmsStageAllocIdentityCurves(ContextID, nChannels)) || 441 !cmsPipelineInsertStage(LUT, cmsAT_END, CLUT) || 442 !cmsPipelineInsertStage(LUT, cmsAT_END, _cmsStageAllocIdentityCurves(ContextID, nChannels))) 443 goto Error; 444 445 // Create tags 446 if (!SetTextTags(hICC, L"ink-limiting built-in")) goto Error; 447 448 if (!cmsWriteTag(hICC, cmsSigAToB0Tag, (void*) LUT)) goto Error; 449 if (!SetSeqDescTag(hICC, "ink-limiting built-in")) goto Error; 450 451 // cmsPipeline is already on virtual profile 452 cmsPipelineFree(LUT); 453 454 // Ok, done 455 return hICC; 456 457 Error: 458 if (LUT != NULL) 459 cmsPipelineFree(LUT); 460 461 if (hICC != NULL) 462 cmsCloseProfile(hICC); 463 464 return NULL; 465 } 466 467 cmsHPROFILE CMSEXPORT cmsCreateInkLimitingDeviceLink(cmsColorSpaceSignature ColorSpace, cmsFloat64Number Limit) 468 { 469 return cmsCreateInkLimitingDeviceLinkTHR(NULL, ColorSpace, Limit); 470 } 471 472 473 // Creates a fake Lab identity. 474 cmsHPROFILE CMSEXPORT cmsCreateLab2ProfileTHR(cmsContext ContextID, const cmsCIExyY* WhitePoint) 475 { 476 cmsHPROFILE hProfile; 477 cmsPipeline* LUT = NULL; 478 479 hProfile = cmsCreateRGBProfileTHR(ContextID, WhitePoint == NULL ? cmsD50_xyY() : WhitePoint, NULL, NULL); 480 if (hProfile == NULL) return NULL; 481 482 cmsSetProfileVersion(hProfile, 2.1); 483 484 cmsSetDeviceClass(hProfile, cmsSigAbstractClass); 485 cmsSetColorSpace(hProfile, cmsSigLabData); 486 cmsSetPCS(hProfile, cmsSigLabData); 487 488 if (!SetTextTags(hProfile, L"Lab identity built-in")) return NULL; 489 490 // An identity LUT is all we need 491 LUT = cmsPipelineAlloc(ContextID, 3, 3); 492 if (LUT == NULL) goto Error; 493 494 if (!cmsPipelineInsertStage(LUT, cmsAT_BEGIN, _cmsStageAllocIdentityCLut(ContextID, 3))) 495 goto Error; 496 497 if (!cmsWriteTag(hProfile, cmsSigAToB0Tag, LUT)) goto Error; 498 cmsPipelineFree(LUT); 499 500 return hProfile; 501 502 Error: 503 504 if (LUT != NULL) 505 cmsPipelineFree(LUT); 506 507 if (hProfile != NULL) 508 cmsCloseProfile(hProfile); 509 510 return NULL; 511 } 512 513 514 cmsHPROFILE CMSEXPORT cmsCreateLab2Profile(const cmsCIExyY* WhitePoint) 515 { 516 return cmsCreateLab2ProfileTHR(NULL, WhitePoint); 517 } 518 519 520 // Creates a fake Lab V4 identity. 521 cmsHPROFILE CMSEXPORT cmsCreateLab4ProfileTHR(cmsContext ContextID, const cmsCIExyY* WhitePoint) 522 { 523 cmsHPROFILE hProfile; 524 cmsPipeline* LUT = NULL; 525 526 hProfile = cmsCreateRGBProfileTHR(ContextID, WhitePoint == NULL ? cmsD50_xyY() : WhitePoint, NULL, NULL); 527 if (hProfile == NULL) return NULL; 528 529 cmsSetProfileVersion(hProfile, 4.3); 530 531 cmsSetDeviceClass(hProfile, cmsSigAbstractClass); 532 cmsSetColorSpace(hProfile, cmsSigLabData); 533 cmsSetPCS(hProfile, cmsSigLabData); 534 535 if (!SetTextTags(hProfile, L"Lab identity built-in")) goto Error; 536 537 // An empty LUTs is all we need 538 LUT = cmsPipelineAlloc(ContextID, 3, 3); 539 if (LUT == NULL) goto Error; 540 541 if (!cmsPipelineInsertStage(LUT, cmsAT_BEGIN, _cmsStageAllocIdentityCurves(ContextID, 3))) 542 goto Error; 543 544 if (!cmsWriteTag(hProfile, cmsSigAToB0Tag, LUT)) goto Error; 545 cmsPipelineFree(LUT); 546 547 return hProfile; 548 549 Error: 550 551 if (LUT != NULL) 552 cmsPipelineFree(LUT); 553 554 if (hProfile != NULL) 555 cmsCloseProfile(hProfile); 556 557 return NULL; 558 } 559 560 cmsHPROFILE CMSEXPORT cmsCreateLab4Profile(const cmsCIExyY* WhitePoint) 561 { 562 return cmsCreateLab4ProfileTHR(NULL, WhitePoint); 563 } 564 565 566 // Creates a fake XYZ identity 567 cmsHPROFILE CMSEXPORT cmsCreateXYZProfileTHR(cmsContext ContextID) 568 { 569 cmsHPROFILE hProfile; 570 cmsPipeline* LUT = NULL; 571 572 hProfile = cmsCreateRGBProfileTHR(ContextID, cmsD50_xyY(), NULL, NULL); 573 if (hProfile == NULL) return NULL; 574 575 cmsSetProfileVersion(hProfile, 4.3); 576 577 cmsSetDeviceClass(hProfile, cmsSigAbstractClass); 578 cmsSetColorSpace(hProfile, cmsSigXYZData); 579 cmsSetPCS(hProfile, cmsSigXYZData); 580 581 if (!SetTextTags(hProfile, L"XYZ identity built-in")) goto Error; 582 583 // An identity LUT is all we need 584 LUT = cmsPipelineAlloc(ContextID, 3, 3); 585 if (LUT == NULL) goto Error; 586 587 if (!cmsPipelineInsertStage(LUT, cmsAT_BEGIN, _cmsStageAllocIdentityCurves(ContextID, 3))) 588 goto Error; 589 590 if (!cmsWriteTag(hProfile, cmsSigAToB0Tag, LUT)) goto Error; 591 cmsPipelineFree(LUT); 592 593 return hProfile; 594 595 Error: 596 597 if (LUT != NULL) 598 cmsPipelineFree(LUT); 599 600 if (hProfile != NULL) 601 cmsCloseProfile(hProfile); 602 603 return NULL; 604 } 605 606 607 cmsHPROFILE CMSEXPORT cmsCreateXYZProfile(void) 608 { 609 return cmsCreateXYZProfileTHR(NULL); 610 } 611 612 613 //sRGB Curves are defined by: 614 // 615 //If RsRGB,GsRGB, BsRGB < 0.04045 616 // 617 // R = RsRGB / 12.92 618 // G = GsRGB / 12.92 619 // B = BsRGB / 12.92 620 // 621 // 622 //else if RsRGB,GsRGB, BsRGB >= 0.04045 623 // 624 // R = ((RsRGB + 0.055) / 1.055)^2.4 625 // G = ((GsRGB + 0.055) / 1.055)^2.4 626 // B = ((BsRGB + 0.055) / 1.055)^2.4 627 628 static 629 cmsToneCurve* Build_sRGBGamma(cmsContext ContextID) 630 { 631 cmsFloat64Number Parameters[5]; 632 633 Parameters[0] = 2.4; 634 Parameters[1] = 1. / 1.055; 635 Parameters[2] = 0.055 / 1.055; 636 Parameters[3] = 1. / 12.92; 637 Parameters[4] = 0.04045; 638 639 return cmsBuildParametricToneCurve(ContextID, 4, Parameters); 640 } 641 642 // Create the ICC virtual profile for sRGB space 643 cmsHPROFILE CMSEXPORT cmsCreate_sRGBProfileTHR(cmsContext ContextID) 644 { 645 cmsCIExyY D65; 646 cmsCIExyYTRIPLE Rec709Primaries = { 647 {0.6400, 0.3300, 1.0}, 648 {0.3000, 0.6000, 1.0}, 649 {0.1500, 0.0600, 1.0} 650 }; 651 cmsToneCurve* Gamma22[3]; 652 cmsHPROFILE hsRGB; 653 654 cmsWhitePointFromTemp(&D65, 6504); 655 Gamma22[0] = Gamma22[1] = Gamma22[2] = Build_sRGBGamma(ContextID); 656 if (Gamma22[0] == NULL) return NULL; 657 658 hsRGB = cmsCreateRGBProfileTHR(ContextID, &D65, &Rec709Primaries, Gamma22); 659 cmsFreeToneCurve(Gamma22[0]); 660 if (hsRGB == NULL) return NULL; 661 662 if (!SetTextTags(hsRGB, L"sRGB built-in")) { 663 cmsCloseProfile(hsRGB); 664 return NULL; 665 } 666 667 return hsRGB; 668 } 669 670 cmsHPROFILE CMSEXPORT cmsCreate_sRGBProfile(void) 671 { 672 return cmsCreate_sRGBProfileTHR(NULL); 673 } 674 675 676 677 typedef struct { 678 cmsFloat64Number Brightness; 679 cmsFloat64Number Contrast; 680 cmsFloat64Number Hue; 681 cmsFloat64Number Saturation; 682 cmsCIEXYZ WPsrc, WPdest; 683 684 } BCHSWADJUSTS, *LPBCHSWADJUSTS; 685 686 687 static 688 int bchswSampler(register const cmsUInt16Number In[], register cmsUInt16Number Out[], register void* Cargo) 689 { 690 cmsCIELab LabIn, LabOut; 691 cmsCIELCh LChIn, LChOut; 692 cmsCIEXYZ XYZ; 693 LPBCHSWADJUSTS bchsw = (LPBCHSWADJUSTS) Cargo; 694 695 696 cmsLabEncoded2Float(&LabIn, In); 697 698 699 cmsLab2LCh(&LChIn, &LabIn); 700 701 // Do some adjusts on LCh 702 703 LChOut.L = LChIn.L * bchsw ->Contrast + bchsw ->Brightness; 704 LChOut.C = LChIn.C + bchsw -> Saturation; 705 LChOut.h = LChIn.h + bchsw -> Hue; 706 707 708 cmsLCh2Lab(&LabOut, &LChOut); 709 710 // Move white point in Lab 711 712 cmsLab2XYZ(&bchsw ->WPsrc, &XYZ, &LabOut); 713 cmsXYZ2Lab(&bchsw ->WPdest, &LabOut, &XYZ); 714 715 // Back to encoded 716 717 cmsFloat2LabEncoded(Out, &LabOut); 718 719 return TRUE; 720 } 721 722 723 // Creates an abstract profile operating in Lab space for Brightness, 724 // contrast, Saturation and white point displacement 725 726 cmsHPROFILE CMSEXPORT cmsCreateBCHSWabstractProfileTHR(cmsContext ContextID, 727 int nLUTPoints, 728 cmsFloat64Number Bright, 729 cmsFloat64Number Contrast, 730 cmsFloat64Number Hue, 731 cmsFloat64Number Saturation, 732 int TempSrc, 733 int TempDest) 734 { 735 cmsHPROFILE hICC; 736 cmsPipeline* Pipeline; 737 BCHSWADJUSTS bchsw; 738 cmsCIExyY WhitePnt; 739 cmsStage* CLUT; 740 cmsUInt32Number Dimensions[MAX_INPUT_DIMENSIONS]; 741 int i; 742 743 bchsw.Brightness = Bright; 744 bchsw.Contrast = Contrast; 745 bchsw.Hue = Hue; 746 bchsw.Saturation = Saturation; 747 748 cmsWhitePointFromTemp(&WhitePnt, TempSrc ); 749 cmsxyY2XYZ(&bchsw.WPsrc, &WhitePnt); 750 751 cmsWhitePointFromTemp(&WhitePnt, TempDest); 752 cmsxyY2XYZ(&bchsw.WPdest, &WhitePnt); 753 754 hICC = cmsCreateProfilePlaceholder(ContextID); 755 if (!hICC) // can't allocate 756 return NULL; 757 758 759 cmsSetDeviceClass(hICC, cmsSigAbstractClass); 760 cmsSetColorSpace(hICC, cmsSigLabData); 761 cmsSetPCS(hICC, cmsSigLabData); 762 763 cmsSetHeaderRenderingIntent(hICC, INTENT_PERCEPTUAL); 764 765 // Creates a Pipeline with 3D grid only 766 Pipeline = cmsPipelineAlloc(ContextID, 3, 3); 767 if (Pipeline == NULL) { 768 cmsCloseProfile(hICC); 769 return NULL; 770 } 771 772 for (i=0; i < MAX_INPUT_DIMENSIONS; i++) Dimensions[i] = nLUTPoints; 773 CLUT = cmsStageAllocCLut16bitGranular(ContextID, Dimensions, 3, 3, NULL); 774 if (CLUT == NULL) return NULL; 775 776 777 if (!cmsStageSampleCLut16bit(CLUT, bchswSampler, (void*) &bchsw, 0)) { 778 779 // Shouldn't reach here 780 goto Error; 781 } 782 783 if (!cmsPipelineInsertStage(Pipeline, cmsAT_END, CLUT)) { 784 goto Error; 785 } 786 787 // Create tags 788 if (!SetTextTags(hICC, L"BCHS built-in")) return NULL; 789 790 cmsWriteTag(hICC, cmsSigMediaWhitePointTag, (void*) cmsD50_XYZ()); 791 792 cmsWriteTag(hICC, cmsSigAToB0Tag, (void*) Pipeline); 793 794 // Pipeline is already on virtual profile 795 cmsPipelineFree(Pipeline); 796 797 // Ok, done 798 return hICC; 799 800 Error: 801 cmsPipelineFree(Pipeline); 802 cmsCloseProfile(hICC); 803 return NULL; 804 } 805 806 807 CMSAPI cmsHPROFILE CMSEXPORT cmsCreateBCHSWabstractProfile(int nLUTPoints, 808 cmsFloat64Number Bright, 809 cmsFloat64Number Contrast, 810 cmsFloat64Number Hue, 811 cmsFloat64Number Saturation, 812 int TempSrc, 813 int TempDest) 814 { 815 return cmsCreateBCHSWabstractProfileTHR(NULL, nLUTPoints, Bright, Contrast, Hue, Saturation, TempSrc, TempDest); 816 } 817 818 819 // Creates a fake NULL profile. This profile return 1 channel as always 0. 820 // Is useful only for gamut checking tricks 821 cmsHPROFILE CMSEXPORT cmsCreateNULLProfileTHR(cmsContext ContextID) 822 { 823 cmsHPROFILE hProfile; 824 cmsPipeline* LUT = NULL; 825 cmsStage* PostLin; 826 cmsToneCurve* EmptyTab; 827 cmsUInt16Number Zero[2] = { 0, 0 }; 828 829 hProfile = cmsCreateProfilePlaceholder(ContextID); 830 if (!hProfile) // can't allocate 831 return NULL; 832 833 cmsSetProfileVersion(hProfile, 4.3); 834 835 if (!SetTextTags(hProfile, L"NULL profile built-in")) goto Error; 836 837 838 839 cmsSetDeviceClass(hProfile, cmsSigOutputClass); 840 cmsSetColorSpace(hProfile, cmsSigGrayData); 841 cmsSetPCS(hProfile, cmsSigLabData); 842 843 // An empty LUTs is all we need 844 LUT = cmsPipelineAlloc(ContextID, 1, 1); 845 if (LUT == NULL) goto Error; 846 847 EmptyTab = cmsBuildTabulatedToneCurve16(ContextID, 2, Zero); 848 PostLin = cmsStageAllocToneCurves(ContextID, 1, &EmptyTab); 849 cmsFreeToneCurve(EmptyTab); 850 851 if (!cmsPipelineInsertStage(LUT, cmsAT_END, PostLin)) 852 goto Error; 853 854 if (!cmsWriteTag(hProfile, cmsSigBToA0Tag, (void*) LUT)) goto Error; 855 if (!cmsWriteTag(hProfile, cmsSigMediaWhitePointTag, cmsD50_XYZ())) goto Error; 856 857 cmsPipelineFree(LUT); 858 return hProfile; 859 860 Error: 861 862 if (LUT != NULL) 863 cmsPipelineFree(LUT); 864 865 if (hProfile != NULL) 866 cmsCloseProfile(hProfile); 867 868 return NULL; 869 } 870 871 cmsHPROFILE CMSEXPORT cmsCreateNULLProfile(void) 872 { 873 return cmsCreateNULLProfileTHR(NULL); 874 } 875 876 877 static 878 int IsPCS(cmsColorSpaceSignature ColorSpace) 879 { 880 return (ColorSpace == cmsSigXYZData || 881 ColorSpace == cmsSigLabData); 882 } 883 884 885 static 886 void FixColorSpaces(cmsHPROFILE hProfile, 887 cmsColorSpaceSignature ColorSpace, 888 cmsColorSpaceSignature PCS, 889 cmsUInt32Number dwFlags) 890 { 891 if (dwFlags & cmsFLAGS_GUESSDEVICECLASS) { 892 893 if (IsPCS(ColorSpace) && IsPCS(PCS)) { 894 895 cmsSetDeviceClass(hProfile, cmsSigAbstractClass); 896 cmsSetColorSpace(hProfile, ColorSpace); 897 cmsSetPCS(hProfile, PCS); 898 return; 899 } 900 901 if (IsPCS(ColorSpace) && !IsPCS(PCS)) { 902 903 cmsSetDeviceClass(hProfile, cmsSigOutputClass); 904 cmsSetPCS(hProfile, ColorSpace); 905 cmsSetColorSpace(hProfile, PCS); 906 return; 907 } 908 909 if (IsPCS(PCS) && !IsPCS(ColorSpace)) { 910 911 cmsSetDeviceClass(hProfile, cmsSigInputClass); 912 cmsSetColorSpace(hProfile, ColorSpace); 913 cmsSetPCS(hProfile, PCS); 914 return; 915 } 916 } 917 918 cmsSetDeviceClass(hProfile, cmsSigLinkClass); 919 cmsSetColorSpace(hProfile, ColorSpace); 920 cmsSetPCS(hProfile, PCS); 921 } 922 923 924 925 // This function creates a named color profile dumping all the contents of transform to a single profile 926 // In this way, LittleCMS may be used to "group" several named color databases into a single profile. 927 // It has, however, several minor limitations. PCS is always Lab, which is not very critic since this 928 // is the normal PCS for named color profiles. 929 static 930 cmsHPROFILE CreateNamedColorDevicelink(cmsHTRANSFORM xform) 931 { 932 _cmsTRANSFORM* v = (_cmsTRANSFORM*) xform; 933 cmsHPROFILE hICC = NULL; 934 int i, nColors; 935 cmsNAMEDCOLORLIST *nc2 = NULL, *Original = NULL; 936 937 // Create an empty placeholder 938 hICC = cmsCreateProfilePlaceholder(v->ContextID); 939 if (hICC == NULL) return NULL; 940 941 // Critical information 942 cmsSetDeviceClass(hICC, cmsSigNamedColorClass); 943 cmsSetColorSpace(hICC, v ->ExitColorSpace); 944 cmsSetPCS(hICC, cmsSigLabData); 945 946 // Tag profile with information 947 if (!SetTextTags(hICC, L"Named color devicelink")) goto Error; 948 949 Original = cmsGetNamedColorList(xform); 950 if (Original == NULL) goto Error; 951 952 nColors = cmsNamedColorCount(Original); 953 nc2 = cmsDupNamedColorList(Original); 954 if (nc2 == NULL) goto Error; 955 956 // Colorant count now depends on the output space 957 nc2 ->ColorantCount = cmsPipelineOutputChannels(v ->Lut); 958 959 // Make sure we have proper formatters 960 cmsChangeBuffersFormat(xform, TYPE_NAMED_COLOR_INDEX, 961 FLOAT_SH(0) | COLORSPACE_SH(_cmsLCMScolorSpace(v ->ExitColorSpace)) 962 | BYTES_SH(2) | CHANNELS_SH(cmsChannelsOf(v ->ExitColorSpace))); 963 964 // Apply the transfor to colorants. 965 for (i=0; i < nColors; i++) { 966 cmsDoTransform(xform, &i, nc2 ->List[i].DeviceColorant, 1); 967 } 968 969 if (!cmsWriteTag(hICC, cmsSigNamedColor2Tag, (void*) nc2)) goto Error; 970 cmsFreeNamedColorList(nc2); 971 972 return hICC; 973 974 Error: 975 if (hICC != NULL) cmsCloseProfile(hICC); 976 return NULL; 977 } 978 979 980 // This structure holds information about which MPU can be stored on a profile based on the version 981 982 typedef struct { 983 cmsBool IsV4; // Is a V4 tag? 984 cmsTagSignature RequiredTag; // Set to 0 for both types 985 cmsTagTypeSignature LutType; // The LUT type 986 int nTypes; // Number of types (up to 5) 987 cmsStageSignature MpeTypes[5]; // 5 is the maximum number 988 989 } cmsAllowedLUT; 990 991 static const cmsAllowedLUT AllowedLUTTypes[] = { 992 993 { FALSE, 0, cmsSigLut16Type, 4, { cmsSigMatrixElemType, cmsSigCurveSetElemType, cmsSigCLutElemType, cmsSigCurveSetElemType}}, 994 { FALSE, 0, cmsSigLut16Type, 3, { cmsSigCurveSetElemType, cmsSigCLutElemType, cmsSigCurveSetElemType}}, 995 { FALSE, 0, cmsSigLut16Type, 2, { cmsSigCurveSetElemType, cmsSigCLutElemType}}, 996 { TRUE , 0, cmsSigLutAtoBType, 1, { cmsSigCurveSetElemType }}, 997 { TRUE , cmsSigAToB0Tag, cmsSigLutAtoBType, 3, { cmsSigCurveSetElemType, cmsSigMatrixElemType, cmsSigCurveSetElemType } }, 998 { TRUE , cmsSigAToB0Tag, cmsSigLutAtoBType, 3, { cmsSigCurveSetElemType, cmsSigCLutElemType, cmsSigCurveSetElemType } }, 999 { TRUE , cmsSigAToB0Tag, cmsSigLutAtoBType, 5, { cmsSigCurveSetElemType, cmsSigCLutElemType, cmsSigCurveSetElemType, cmsSigMatrixElemType, cmsSigCurveSetElemType }}, 1000 { TRUE , cmsSigBToA0Tag, cmsSigLutBtoAType, 1, { cmsSigCurveSetElemType }}, 1001 { TRUE , cmsSigBToA0Tag, cmsSigLutBtoAType, 3, { cmsSigCurveSetElemType, cmsSigMatrixElemType, cmsSigCurveSetElemType }}, 1002 { TRUE , cmsSigBToA0Tag, cmsSigLutBtoAType, 3, { cmsSigCurveSetElemType, cmsSigCLutElemType, cmsSigCurveSetElemType }}, 1003 { TRUE , cmsSigBToA0Tag, cmsSigLutBtoAType, 5, { cmsSigCurveSetElemType, cmsSigMatrixElemType, cmsSigCurveSetElemType, cmsSigCLutElemType, cmsSigCurveSetElemType }} 1004 }; 1005 1006 #define SIZE_OF_ALLOWED_LUT (sizeof(AllowedLUTTypes)/sizeof(cmsAllowedLUT)) 1007 1008 // Check a single entry 1009 static 1010 cmsBool CheckOne(const cmsAllowedLUT* Tab, const cmsPipeline* Lut) 1011 { 1012 cmsStage* mpe; 1013 int n; 1014 1015 for (n=0, mpe = Lut ->Elements; mpe != NULL; mpe = mpe ->Next, n++) { 1016 1017 if (n > Tab ->nTypes) return FALSE; 1018 if (cmsStageType(mpe) != Tab ->MpeTypes[n]) return FALSE; 1019 } 1020 1021 return (n == Tab ->nTypes); 1022 } 1023 1024 1025 static 1026 const cmsAllowedLUT* FindCombination(const cmsPipeline* Lut, cmsBool IsV4, cmsTagSignature DestinationTag) 1027 { 1028 cmsUInt32Number n; 1029 1030 for (n=0; n < SIZE_OF_ALLOWED_LUT; n++) { 1031 1032 const cmsAllowedLUT* Tab = AllowedLUTTypes + n; 1033 1034 if (IsV4 ^ Tab -> IsV4) continue; 1035 if ((Tab ->RequiredTag != 0) && (Tab ->RequiredTag != DestinationTag)) continue; 1036 1037 if (CheckOne(Tab, Lut)) return Tab; 1038 } 1039 1040 return NULL; 1041 } 1042 1043 1044 // Does convert a transform into a device link profile 1045 cmsHPROFILE CMSEXPORT cmsTransform2DeviceLink(cmsHTRANSFORM hTransform, cmsFloat64Number Version, cmsUInt32Number dwFlags) 1046 { 1047 cmsHPROFILE hProfile = NULL; 1048 cmsUInt32Number FrmIn, FrmOut, ChansIn, ChansOut; 1049 cmsUInt32Number ColorSpaceBitsIn, ColorSpaceBitsOut; 1050 _cmsTRANSFORM* xform = (_cmsTRANSFORM*) hTransform; 1051 cmsPipeline* LUT = NULL; 1052 cmsStage* mpe; 1053 cmsContext ContextID = cmsGetTransformContextID(hTransform); 1054 const cmsAllowedLUT* AllowedLUT; 1055 cmsTagSignature DestinationTag; 1056 cmsProfileClassSignature deviceClass; 1057 1058 _cmsAssert(hTransform != NULL); 1059 1060 // Get the first mpe to check for named color 1061 mpe = cmsPipelineGetPtrToFirstStage(xform ->Lut); 1062 1063 // Check if is a named color transform 1064 if (mpe != NULL) { 1065 1066 if (cmsStageType(mpe) == cmsSigNamedColorElemType) { 1067 return CreateNamedColorDevicelink(hTransform); 1068 } 1069 } 1070 1071 // First thing to do is to get a copy of the transformation 1072 LUT = cmsPipelineDup(xform ->Lut); 1073 if (LUT == NULL) return NULL; 1074 1075 // Time to fix the Lab2/Lab4 issue. 1076 if ((xform ->EntryColorSpace == cmsSigLabData) && (Version < 4.0)) { 1077 1078 if (!cmsPipelineInsertStage(LUT, cmsAT_BEGIN, _cmsStageAllocLabV2ToV4curves(ContextID))) 1079 goto Error; 1080 } 1081 1082 // On the output side too 1083 if ((xform ->ExitColorSpace) == cmsSigLabData && (Version < 4.0)) { 1084 1085 if (!cmsPipelineInsertStage(LUT, cmsAT_END, _cmsStageAllocLabV4ToV2(ContextID))) 1086 goto Error; 1087 } 1088 1089 1090 hProfile = cmsCreateProfilePlaceholder(ContextID); 1091 if (!hProfile) goto Error; // can't allocate 1092 1093 cmsSetProfileVersion(hProfile, Version); 1094 1095 FixColorSpaces(hProfile, xform -> EntryColorSpace, xform -> ExitColorSpace, dwFlags); 1096 1097 // Optimize the LUT and precalculate a devicelink 1098 1099 ChansIn = cmsChannelsOf(xform -> EntryColorSpace); 1100 ChansOut = cmsChannelsOf(xform -> ExitColorSpace); 1101 1102 ColorSpaceBitsIn = _cmsLCMScolorSpace(xform -> EntryColorSpace); 1103 ColorSpaceBitsOut = _cmsLCMScolorSpace(xform -> ExitColorSpace); 1104 1105 FrmIn = COLORSPACE_SH(ColorSpaceBitsIn) | CHANNELS_SH(ChansIn)|BYTES_SH(2); 1106 FrmOut = COLORSPACE_SH(ColorSpaceBitsOut) | CHANNELS_SH(ChansOut)|BYTES_SH(2); 1107 1108 deviceClass = cmsGetDeviceClass(hProfile); 1109 1110 if (deviceClass == cmsSigOutputClass) 1111 DestinationTag = cmsSigBToA0Tag; 1112 else 1113 DestinationTag = cmsSigAToB0Tag; 1114 1115 // Check if the profile/version can store the result 1116 if (dwFlags & cmsFLAGS_FORCE_CLUT) 1117 AllowedLUT = NULL; 1118 else 1119 AllowedLUT = FindCombination(LUT, Version >= 4.0, DestinationTag); 1120 1121 if (AllowedLUT == NULL) { 1122 1123 // Try to optimize 1124 _cmsOptimizePipeline(ContextID, &LUT, xform ->RenderingIntent, &FrmIn, &FrmOut, &dwFlags); 1125 AllowedLUT = FindCombination(LUT, Version >= 4.0, DestinationTag); 1126 1127 } 1128 1129 // If no way, then force CLUT that for sure can be written 1130 if (AllowedLUT == NULL) { 1131 1132 dwFlags |= cmsFLAGS_FORCE_CLUT; 1133 _cmsOptimizePipeline(ContextID, &LUT, xform ->RenderingIntent, &FrmIn, &FrmOut, &dwFlags); 1134 1135 // Put identity curves if needed 1136 if (cmsPipelineGetPtrToFirstStage(LUT) ->Type != cmsSigCurveSetElemType) 1137 if (!cmsPipelineInsertStage(LUT, cmsAT_BEGIN, _cmsStageAllocIdentityCurves(ContextID, ChansIn))) 1138 goto Error; 1139 1140 if (cmsPipelineGetPtrToLastStage(LUT) ->Type != cmsSigCurveSetElemType) 1141 if (!cmsPipelineInsertStage(LUT, cmsAT_END, _cmsStageAllocIdentityCurves(ContextID, ChansOut))) 1142 goto Error; 1143 1144 AllowedLUT = FindCombination(LUT, Version >= 4.0, DestinationTag); 1145 } 1146 1147 // Somethings is wrong... 1148 if (AllowedLUT == NULL) { 1149 goto Error; 1150 } 1151 1152 1153 if (dwFlags & cmsFLAGS_8BITS_DEVICELINK) 1154 cmsPipelineSetSaveAs8bitsFlag(LUT, TRUE); 1155 1156 // Tag profile with information 1157 if (!SetTextTags(hProfile, L"devicelink")) goto Error; 1158 1159 // Store result 1160 if (!cmsWriteTag(hProfile, DestinationTag, LUT)) goto Error; 1161 1162 1163 if (xform -> InputColorant != NULL) { 1164 if (!cmsWriteTag(hProfile, cmsSigColorantTableTag, xform->InputColorant)) goto Error; 1165 } 1166 1167 if (xform -> OutputColorant != NULL) { 1168 if (!cmsWriteTag(hProfile, cmsSigColorantTableOutTag, xform->OutputColorant)) goto Error; 1169 } 1170 1171 if ((deviceClass == cmsSigLinkClass) && (xform ->Sequence != NULL)) { 1172 if (!_cmsWriteProfileSequence(hProfile, xform ->Sequence)) goto Error; 1173 } 1174 1175 // Set the white point 1176 if (deviceClass == cmsSigInputClass) { 1177 if (!cmsWriteTag(hProfile, cmsSigMediaWhitePointTag, &xform ->EntryWhitePoint)) goto Error; 1178 } 1179 else { 1180 if (!cmsWriteTag(hProfile, cmsSigMediaWhitePointTag, &xform ->ExitWhitePoint)) goto Error; 1181 } 1182 1183 1184 // Per 7.2.15 in spec 4.3 1185 cmsSetHeaderRenderingIntent(hProfile, xform ->RenderingIntent); 1186 1187 cmsPipelineFree(LUT); 1188 return hProfile; 1189 1190 Error: 1191 if (LUT != NULL) cmsPipelineFree(LUT); 1192 cmsCloseProfile(hProfile); 1193 return NULL; 1194 } 1195