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