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 0..400");
 439         if (Limit < 0) Limit = 0;
 440         if (Limit > 400) Limit = 400;
 441 
 442     }
 443 
 444     hICC = cmsCreateProfilePlaceholder(ContextID);
 445     if (!hICC)                          // can't allocate
 446         return NULL;
 447 
 448     cmsSetProfileVersion(hICC, 4.4);
 449 
 450     cmsSetDeviceClass(hICC,      cmsSigLinkClass);
 451     cmsSetColorSpace(hICC,       ColorSpace);
 452     cmsSetPCS(hICC,              ColorSpace);
 453 
 454     cmsSetHeaderRenderingIntent(hICC,  INTENT_PERCEPTUAL);
 455 
 456 
 457     // Creates a Pipeline with 3D grid only
 458     LUT = cmsPipelineAlloc(ContextID, 4, 4);
 459     if (LUT == NULL) goto Error;
 460 
 461 
 462     nChannels = cmsChannelsOf(ColorSpace);
 463 
 464     CLUT = cmsStageAllocCLut16bit(ContextID, 17, nChannels, nChannels, NULL);
 465     if (CLUT == NULL) goto Error;
 466 
 467     if (!cmsStageSampleCLut16bit(CLUT, InkLimitingSampler, (void*) &Limit, 0)) goto Error;
 468 
 469     if (!cmsPipelineInsertStage(LUT, cmsAT_BEGIN, _cmsStageAllocIdentityCurves(ContextID, nChannels)) ||
 470         !cmsPipelineInsertStage(LUT, cmsAT_END, CLUT) ||
 471         !cmsPipelineInsertStage(LUT, cmsAT_END, _cmsStageAllocIdentityCurves(ContextID, nChannels)))
 472         goto Error;
 473 
 474     // Create tags
 475     if (!SetTextTags(hICC, L"ink-limiting built-in")) goto Error;
 476 
 477     if (!cmsWriteTag(hICC, cmsSigAToB0Tag, (void*) LUT))  goto Error;
 478     if (!SetSeqDescTag(hICC, "ink-limiting built-in")) goto Error;
 479 
 480     // cmsPipeline is already on virtual profile
 481     cmsPipelineFree(LUT);
 482 
 483     // Ok, done
 484     return hICC;
 485 
 486 Error:
 487     if (LUT != NULL)
 488         cmsPipelineFree(LUT);
 489 
 490     if (hICC != NULL)
 491         cmsCloseProfile(hICC);
 492 
 493     return NULL;
 494 }
 495 
 496 cmsHPROFILE CMSEXPORT cmsCreateInkLimitingDeviceLink(cmsColorSpaceSignature ColorSpace, cmsFloat64Number Limit)
 497 {
 498     return cmsCreateInkLimitingDeviceLinkTHR(NULL, ColorSpace, Limit);
 499 }
 500 
 501 
 502 // Creates a fake Lab identity.
 503 cmsHPROFILE CMSEXPORT cmsCreateLab2ProfileTHR(cmsContext ContextID, const cmsCIExyY* WhitePoint)
 504 {
 505     cmsHPROFILE hProfile;
 506     cmsPipeline* LUT = NULL;
 507 
 508     hProfile = cmsCreateRGBProfileTHR(ContextID, WhitePoint == NULL ? cmsD50_xyY() : WhitePoint, NULL, NULL);
 509     if (hProfile == NULL) return NULL;
 510 
 511     cmsSetProfileVersion(hProfile, 2.1);
 512 
 513     cmsSetDeviceClass(hProfile, cmsSigAbstractClass);
 514     cmsSetColorSpace(hProfile,  cmsSigLabData);
 515     cmsSetPCS(hProfile,         cmsSigLabData);
 516 
 517     if (!SetTextTags(hProfile, L"Lab identity built-in")) return NULL;
 518 
 519     // An identity LUT is all we need
 520     LUT = cmsPipelineAlloc(ContextID, 3, 3);
 521     if (LUT == NULL) goto Error;
 522 
 523     if (!cmsPipelineInsertStage(LUT, cmsAT_BEGIN, _cmsStageAllocIdentityCLut(ContextID, 3)))
 524         goto Error;
 525 
 526     if (!cmsWriteTag(hProfile, cmsSigAToB0Tag, LUT)) goto Error;
 527     cmsPipelineFree(LUT);
 528 
 529     return hProfile;
 530 
 531 Error:
 532 
 533     if (LUT != NULL)
 534         cmsPipelineFree(LUT);
 535 
 536     if (hProfile != NULL)
 537         cmsCloseProfile(hProfile);
 538 
 539     return NULL;
 540 }
 541 
 542 
 543 cmsHPROFILE CMSEXPORT cmsCreateLab2Profile(const cmsCIExyY* WhitePoint)
 544 {
 545     return cmsCreateLab2ProfileTHR(NULL, WhitePoint);
 546 }
 547 
 548 
 549 // Creates a fake Lab V4 identity.
 550 cmsHPROFILE CMSEXPORT cmsCreateLab4ProfileTHR(cmsContext ContextID, const cmsCIExyY* WhitePoint)
 551 {
 552     cmsHPROFILE hProfile;
 553     cmsPipeline* LUT = NULL;
 554 
 555     hProfile = cmsCreateRGBProfileTHR(ContextID, WhitePoint == NULL ? cmsD50_xyY() : WhitePoint, NULL, NULL);
 556     if (hProfile == NULL) return NULL;
 557 
 558     cmsSetProfileVersion(hProfile, 4.4);
 559 
 560     cmsSetDeviceClass(hProfile, cmsSigAbstractClass);
 561     cmsSetColorSpace(hProfile,  cmsSigLabData);
 562     cmsSetPCS(hProfile,         cmsSigLabData);
 563 
 564     if (!SetTextTags(hProfile, L"Lab identity built-in")) goto Error;
 565 
 566     // An empty LUTs is all we need
 567     LUT = cmsPipelineAlloc(ContextID, 3, 3);
 568     if (LUT == NULL) goto Error;
 569 
 570     if (!cmsPipelineInsertStage(LUT, cmsAT_BEGIN, _cmsStageAllocIdentityCurves(ContextID, 3)))
 571         goto Error;
 572 
 573     if (!cmsWriteTag(hProfile, cmsSigAToB0Tag, LUT)) goto Error;
 574     cmsPipelineFree(LUT);
 575 
 576     return hProfile;
 577 
 578 Error:
 579 
 580     if (LUT != NULL)
 581         cmsPipelineFree(LUT);
 582 
 583     if (hProfile != NULL)
 584         cmsCloseProfile(hProfile);
 585 
 586     return NULL;
 587 }
 588 
 589 cmsHPROFILE CMSEXPORT cmsCreateLab4Profile(const cmsCIExyY* WhitePoint)
 590 {
 591     return cmsCreateLab4ProfileTHR(NULL, WhitePoint);
 592 }
 593 
 594 
 595 // Creates a fake XYZ identity
 596 cmsHPROFILE CMSEXPORT cmsCreateXYZProfileTHR(cmsContext ContextID)
 597 {
 598     cmsHPROFILE hProfile;
 599     cmsPipeline* LUT = NULL;
 600 
 601     hProfile = cmsCreateRGBProfileTHR(ContextID, cmsD50_xyY(), NULL, NULL);
 602     if (hProfile == NULL) return NULL;
 603 
 604     cmsSetProfileVersion(hProfile, 4.4);
 605 
 606     cmsSetDeviceClass(hProfile, cmsSigAbstractClass);
 607     cmsSetColorSpace(hProfile,  cmsSigXYZData);
 608     cmsSetPCS(hProfile,         cmsSigXYZData);
 609 
 610     if (!SetTextTags(hProfile, L"XYZ identity built-in")) goto Error;
 611 
 612     // An identity LUT is all we need
 613     LUT = cmsPipelineAlloc(ContextID, 3, 3);
 614     if (LUT == NULL) goto Error;
 615 
 616     if (!cmsPipelineInsertStage(LUT, cmsAT_BEGIN, _cmsStageAllocIdentityCurves(ContextID, 3)))
 617         goto Error;
 618 
 619     if (!cmsWriteTag(hProfile, cmsSigAToB0Tag, LUT)) goto Error;
 620     cmsPipelineFree(LUT);
 621 
 622     return hProfile;
 623 
 624 Error:
 625 
 626     if (LUT != NULL)
 627         cmsPipelineFree(LUT);
 628 
 629     if (hProfile != NULL)
 630         cmsCloseProfile(hProfile);
 631 
 632     return NULL;
 633 }
 634 
 635 
 636 cmsHPROFILE CMSEXPORT cmsCreateXYZProfile(void)
 637 {
 638     return cmsCreateXYZProfileTHR(NULL);
 639 }
 640 
 641 
 642 //sRGB Curves are defined by:
 643 //
 644 //If  R'sRGB,G'sRGB, B'sRGB < 0.04045
 645 //
 646 //    R =  R'sRGB / 12.92
 647 //    G =  G'sRGB / 12.92
 648 //    B =  B'sRGB / 12.92
 649 //
 650 //
 651 //else if  R'sRGB,G'sRGB, B'sRGB >= 0.04045
 652 //
 653 //    R = ((R'sRGB + 0.055) / 1.055)^2.4
 654 //    G = ((G'sRGB + 0.055) / 1.055)^2.4
 655 //    B = ((B'sRGB + 0.055) / 1.055)^2.4
 656 
 657 static
 658 cmsToneCurve* Build_sRGBGamma(cmsContext ContextID)
 659 {
 660     cmsFloat64Number Parameters[5];
 661 
 662     Parameters[0] = 2.4;
 663     Parameters[1] = 1. / 1.055;
 664     Parameters[2] = 0.055 / 1.055;
 665     Parameters[3] = 1. / 12.92;
 666     Parameters[4] = 0.04045;
 667 
 668     return cmsBuildParametricToneCurve(ContextID, 4, Parameters);
 669 }
 670 
 671 // Create the ICC virtual profile for sRGB space
 672 cmsHPROFILE CMSEXPORT cmsCreate_sRGBProfileTHR(cmsContext ContextID)
 673 {
 674        cmsCIExyY       D65 = { 0.3127, 0.3290, 1.0 };
 675        cmsCIExyYTRIPLE Rec709Primaries = {
 676                                    {0.6400, 0.3300, 1.0},
 677                                    {0.3000, 0.6000, 1.0},
 678                                    {0.1500, 0.0600, 1.0}
 679                                    };
 680        cmsToneCurve* Gamma22[3];
 681        cmsHPROFILE  hsRGB;
 682 
 683       // cmsWhitePointFromTemp(&D65, 6504);
 684        Gamma22[0] = Gamma22[1] = Gamma22[2] = Build_sRGBGamma(ContextID);
 685        if (Gamma22[0] == NULL) return NULL;
 686 
 687        hsRGB = cmsCreateRGBProfileTHR(ContextID, &D65, &Rec709Primaries, Gamma22);
 688        cmsFreeToneCurve(Gamma22[0]);
 689        if (hsRGB == NULL) return NULL;
 690 
 691        if (!SetTextTags(hsRGB, L"sRGB built-in")) {
 692            cmsCloseProfile(hsRGB);
 693            return NULL;
 694        }
 695 
 696        return hsRGB;
 697 }
 698 
 699 cmsHPROFILE CMSEXPORT cmsCreate_sRGBProfile(void)
 700 {
 701     return cmsCreate_sRGBProfileTHR(NULL);
 702 }
 703 
 704 
 705 
 706 typedef struct {
 707                 cmsFloat64Number Brightness;
 708                 cmsFloat64Number Contrast;
 709                 cmsFloat64Number Hue;
 710                 cmsFloat64Number Saturation;
 711                 cmsBool          lAdjustWP;
 712                 cmsCIEXYZ WPsrc, WPdest;
 713 
 714 } BCHSWADJUSTS, *LPBCHSWADJUSTS;
 715 
 716 
 717 static
 718 int bchswSampler(CMSREGISTER const cmsUInt16Number In[], CMSREGISTER cmsUInt16Number Out[], CMSREGISTER void* Cargo)
 719 {
 720     cmsCIELab LabIn, LabOut;
 721     cmsCIELCh LChIn, LChOut;
 722     cmsCIEXYZ XYZ;
 723     LPBCHSWADJUSTS bchsw = (LPBCHSWADJUSTS) Cargo;
 724 
 725 
 726     cmsLabEncoded2Float(&LabIn, In);
 727 
 728 
 729     cmsLab2LCh(&LChIn, &LabIn);
 730 
 731     // Do some adjusts on LCh
 732 
 733     LChOut.L = LChIn.L * bchsw ->Contrast + bchsw ->Brightness;
 734     LChOut.C = LChIn.C + bchsw -> Saturation;
 735     LChOut.h = LChIn.h + bchsw -> Hue;
 736 
 737 
 738     cmsLCh2Lab(&LabOut, &LChOut);
 739 
 740     // Move white point in Lab
 741     if (bchsw->lAdjustWP) {
 742            cmsLab2XYZ(&bchsw->WPsrc, &XYZ, &LabOut);
 743            cmsXYZ2Lab(&bchsw->WPdest, &LabOut, &XYZ);
 744     }
 745 
 746     // Back to encoded
 747 
 748     cmsFloat2LabEncoded(Out, &LabOut);
 749 
 750     return TRUE;
 751 }
 752 
 753 
 754 // Creates an abstract profile operating in Lab space for Brightness,
 755 // contrast, Saturation and white point displacement
 756 
 757 cmsHPROFILE CMSEXPORT cmsCreateBCHSWabstractProfileTHR(cmsContext ContextID,
 758                                                        cmsUInt32Number nLUTPoints,
 759                                                        cmsFloat64Number Bright,
 760                                                        cmsFloat64Number Contrast,
 761                                                        cmsFloat64Number Hue,
 762                                                        cmsFloat64Number Saturation,
 763                                                        cmsUInt32Number TempSrc,
 764                                                        cmsUInt32Number TempDest)
 765 {
 766     cmsHPROFILE hICC;
 767     cmsPipeline* Pipeline;
 768     BCHSWADJUSTS bchsw;
 769     cmsCIExyY WhitePnt;
 770     cmsStage* CLUT;
 771     cmsUInt32Number Dimensions[MAX_INPUT_DIMENSIONS];
 772     cmsUInt32Number i;
 773 
 774     bchsw.Brightness = Bright;
 775     bchsw.Contrast   = Contrast;
 776     bchsw.Hue        = Hue;
 777     bchsw.Saturation = Saturation;
 778     if (TempSrc == TempDest) {
 779 
 780            bchsw.lAdjustWP = FALSE;
 781     }
 782     else {
 783            bchsw.lAdjustWP = TRUE;
 784            cmsWhitePointFromTemp(&WhitePnt, TempSrc);
 785            cmsxyY2XYZ(&bchsw.WPsrc, &WhitePnt);
 786            cmsWhitePointFromTemp(&WhitePnt, TempDest);
 787            cmsxyY2XYZ(&bchsw.WPdest, &WhitePnt);
 788 
 789     }
 790 
 791     hICC = cmsCreateProfilePlaceholder(ContextID);
 792     if (!hICC)                          // can't allocate
 793         return NULL;
 794 
 795     cmsSetDeviceClass(hICC,      cmsSigAbstractClass);
 796     cmsSetColorSpace(hICC,       cmsSigLabData);
 797     cmsSetPCS(hICC,              cmsSigLabData);
 798 
 799     cmsSetHeaderRenderingIntent(hICC,  INTENT_PERCEPTUAL);
 800 
 801     // Creates a Pipeline with 3D grid only
 802     Pipeline = cmsPipelineAlloc(ContextID, 3, 3);
 803     if (Pipeline == NULL) {
 804         cmsCloseProfile(hICC);
 805         return NULL;
 806     }
 807 
 808     for (i=0; i < MAX_INPUT_DIMENSIONS; i++) Dimensions[i] = nLUTPoints;
 809     CLUT = cmsStageAllocCLut16bitGranular(ContextID, Dimensions, 3, 3, NULL);
 810     if (CLUT == NULL) goto Error;
 811 
 812 
 813     if (!cmsStageSampleCLut16bit(CLUT, bchswSampler, (void*) &bchsw, 0)) {
 814 
 815         // Shouldn't reach here
 816         goto Error;
 817     }
 818 
 819     if (!cmsPipelineInsertStage(Pipeline, cmsAT_END, CLUT)) {
 820         goto Error;
 821     }
 822 
 823     // Create tags
 824     if (!SetTextTags(hICC, L"BCHS built-in")) return NULL;
 825 
 826     cmsWriteTag(hICC, cmsSigMediaWhitePointTag, (void*) cmsD50_XYZ());
 827 
 828     cmsWriteTag(hICC, cmsSigAToB0Tag, (void*) Pipeline);
 829 
 830     // Pipeline is already on virtual profile
 831     cmsPipelineFree(Pipeline);
 832 
 833     // Ok, done
 834     return hICC;
 835 
 836 Error:
 837     cmsPipelineFree(Pipeline);
 838     cmsCloseProfile(hICC);
 839     return NULL;
 840 }
 841 
 842 
 843 CMSAPI cmsHPROFILE   CMSEXPORT cmsCreateBCHSWabstractProfile(cmsUInt32Number nLUTPoints,
 844                                                              cmsFloat64Number Bright,
 845                                                              cmsFloat64Number Contrast,
 846                                                              cmsFloat64Number Hue,
 847                                                              cmsFloat64Number Saturation,
 848                                                              cmsUInt32Number TempSrc,
 849                                                              cmsUInt32Number TempDest)
 850 {
 851     return cmsCreateBCHSWabstractProfileTHR(NULL, nLUTPoints, Bright, Contrast, Hue, Saturation, TempSrc, TempDest);
 852 }
 853 
 854 
 855 // Creates a fake NULL profile. This profile return 1 channel as always 0.
 856 // Is useful only for gamut checking tricks
 857 cmsHPROFILE CMSEXPORT cmsCreateNULLProfileTHR(cmsContext ContextID)
 858 {
 859     cmsHPROFILE hProfile;
 860     cmsPipeline* LUT = NULL;
 861     cmsStage* PostLin;
 862     cmsStage* OutLin;
 863     cmsToneCurve* EmptyTab[3];
 864     cmsUInt16Number Zero[2] = { 0, 0 };
 865     const cmsFloat64Number PickLstarMatrix[] = { 1, 0, 0 };
 866 
 867     hProfile = cmsCreateProfilePlaceholder(ContextID);
 868     if (!hProfile)                          // can't allocate
 869         return NULL;
 870 
 871     cmsSetProfileVersion(hProfile, 4.4);
 872 
 873     if (!SetTextTags(hProfile, L"NULL profile built-in")) goto Error;
 874 
 875 
 876     cmsSetDeviceClass(hProfile, cmsSigOutputClass);
 877     cmsSetColorSpace(hProfile,  cmsSigGrayData);
 878     cmsSetPCS(hProfile,         cmsSigLabData);
 879 
 880     // Create a valid ICC 4 structure
 881     LUT = cmsPipelineAlloc(ContextID, 3, 1);
 882     if (LUT == NULL) goto Error;
 883 
 884     EmptyTab[0] = EmptyTab[1] = EmptyTab[2] = cmsBuildTabulatedToneCurve16(ContextID, 2, Zero);
 885     PostLin = cmsStageAllocToneCurves(ContextID, 3, EmptyTab);
 886     OutLin  = cmsStageAllocToneCurves(ContextID, 1, EmptyTab);
 887     cmsFreeToneCurve(EmptyTab[0]);
 888 
 889     if (!cmsPipelineInsertStage(LUT, cmsAT_END, PostLin))
 890         goto Error;
 891 
 892     if (!cmsPipelineInsertStage(LUT, cmsAT_END, cmsStageAllocMatrix(ContextID, 1, 3, PickLstarMatrix, NULL)))
 893         goto Error;
 894 
 895     if (!cmsPipelineInsertStage(LUT, cmsAT_END, OutLin))
 896         goto Error;
 897 
 898     if (!cmsWriteTag(hProfile, cmsSigBToA0Tag, (void*) LUT)) goto Error;
 899     if (!cmsWriteTag(hProfile, cmsSigMediaWhitePointTag, cmsD50_XYZ())) goto Error;
 900 
 901     cmsPipelineFree(LUT);
 902     return hProfile;
 903 
 904 Error:
 905 
 906     if (LUT != NULL)
 907         cmsPipelineFree(LUT);
 908 
 909     if (hProfile != NULL)
 910         cmsCloseProfile(hProfile);
 911 
 912     return NULL;
 913 }
 914 
 915 cmsHPROFILE CMSEXPORT cmsCreateNULLProfile(void)
 916 {
 917     return cmsCreateNULLProfileTHR(NULL);
 918 }
 919 
 920 
 921 static
 922 int IsPCS(cmsColorSpaceSignature ColorSpace)
 923 {
 924     return (ColorSpace == cmsSigXYZData ||
 925             ColorSpace == cmsSigLabData);
 926 }
 927 
 928 
 929 static
 930 void FixColorSpaces(cmsHPROFILE hProfile,
 931                               cmsColorSpaceSignature ColorSpace,
 932                               cmsColorSpaceSignature PCS,
 933                               cmsUInt32Number dwFlags)
 934 {
 935     if (dwFlags & cmsFLAGS_GUESSDEVICECLASS) {
 936 
 937             if (IsPCS(ColorSpace) && IsPCS(PCS)) {
 938 
 939                     cmsSetDeviceClass(hProfile,      cmsSigAbstractClass);
 940                     cmsSetColorSpace(hProfile,       ColorSpace);
 941                     cmsSetPCS(hProfile,              PCS);
 942                     return;
 943             }
 944 
 945             if (IsPCS(ColorSpace) && !IsPCS(PCS)) {
 946 
 947                     cmsSetDeviceClass(hProfile, cmsSigOutputClass);
 948                     cmsSetPCS(hProfile,         ColorSpace);
 949                     cmsSetColorSpace(hProfile,  PCS);
 950                     return;
 951             }
 952 
 953             if (IsPCS(PCS) && !IsPCS(ColorSpace)) {
 954 
 955                    cmsSetDeviceClass(hProfile,  cmsSigInputClass);
 956                    cmsSetColorSpace(hProfile,   ColorSpace);
 957                    cmsSetPCS(hProfile,          PCS);
 958                    return;
 959             }
 960     }
 961 
 962     cmsSetDeviceClass(hProfile,      cmsSigLinkClass);
 963     cmsSetColorSpace(hProfile,       ColorSpace);
 964     cmsSetPCS(hProfile,              PCS);
 965 }
 966 
 967 
 968 
 969 // This function creates a named color profile dumping all the contents of transform to a single profile
 970 // In this way, LittleCMS may be used to "group" several named color databases into a single profile.
 971 // It has, however, several minor limitations. PCS is always Lab, which is not very critic since this
 972 // is the normal PCS for named color profiles.
 973 static
 974 cmsHPROFILE CreateNamedColorDevicelink(cmsHTRANSFORM xform)
 975 {
 976     _cmsTRANSFORM* v = (_cmsTRANSFORM*) xform;
 977     cmsHPROFILE hICC = NULL;
 978     cmsUInt32Number i, nColors;
 979     cmsNAMEDCOLORLIST *nc2 = NULL, *Original = NULL;
 980 
 981     // Create an empty placeholder
 982     hICC = cmsCreateProfilePlaceholder(v->ContextID);
 983     if (hICC == NULL) return NULL;
 984 
 985     // Critical information
 986     cmsSetDeviceClass(hICC, cmsSigNamedColorClass);
 987     cmsSetColorSpace(hICC, v ->ExitColorSpace);
 988     cmsSetPCS(hICC, cmsSigLabData);
 989 
 990     // Tag profile with information
 991     if (!SetTextTags(hICC, L"Named color devicelink")) goto Error;
 992 
 993     Original = cmsGetNamedColorList(xform);
 994     if (Original == NULL) goto Error;
 995 
 996     nColors = cmsNamedColorCount(Original);
 997     nc2     = cmsDupNamedColorList(Original);
 998     if (nc2 == NULL) goto Error;
 999 
1000     // Colorant count now depends on the output space
1001     nc2 ->ColorantCount = cmsPipelineOutputChannels(v ->Lut);
1002 
1003     // Make sure we have proper formatters
1004     cmsChangeBuffersFormat(xform, TYPE_NAMED_COLOR_INDEX,
1005         FLOAT_SH(0) | COLORSPACE_SH(_cmsLCMScolorSpace(v ->ExitColorSpace))
1006         | BYTES_SH(2) | CHANNELS_SH(cmsChannelsOfColorSpace(v ->ExitColorSpace)));
1007 
1008     // Apply the transfor to colorants.
1009     for (i=0; i < nColors; i++) {
1010         cmsDoTransform(xform, &i, nc2 ->List[i].DeviceColorant, 1);
1011     }
1012 
1013     if (!cmsWriteTag(hICC, cmsSigNamedColor2Tag, (void*) nc2)) goto Error;
1014     cmsFreeNamedColorList(nc2);
1015 
1016     return hICC;
1017 
1018 Error:
1019     if (hICC != NULL) cmsCloseProfile(hICC);
1020     return NULL;
1021 }
1022 
1023 
1024 // This structure holds information about which MPU can be stored on a profile based on the version
1025 
1026 typedef struct {
1027     cmsBool              IsV4;             // Is a V4 tag?
1028     cmsTagSignature      RequiredTag;      // Set to 0 for both types
1029     cmsTagTypeSignature  LutType;          // The LUT type
1030     int                  nTypes;           // Number of types (up to 5)
1031     cmsStageSignature    MpeTypes[5];      // 5 is the maximum number
1032 
1033 } cmsAllowedLUT;
1034 
1035 #define cmsSig0 ((cmsTagSignature) 0)
1036 
1037 static const cmsAllowedLUT AllowedLUTTypes[] = {
1038 
1039     { FALSE, cmsSig0,        cmsSigLut16Type, 4, { cmsSigMatrixElemType, cmsSigCurveSetElemType, cmsSigCLutElemType, cmsSigCurveSetElemType } },
1040     { FALSE, cmsSig0,        cmsSigLut16Type, 3, { cmsSigCurveSetElemType, cmsSigCLutElemType, cmsSigCurveSetElemType } },
1041     { FALSE, cmsSig0,        cmsSigLut16Type, 2, { cmsSigCurveSetElemType, cmsSigCLutElemType } },
1042     { TRUE,  cmsSig0,        cmsSigLutAtoBType, 1, { cmsSigCurveSetElemType } },
1043     { TRUE , cmsSigAToB0Tag, cmsSigLutAtoBType,  3,  { cmsSigCurveSetElemType, cmsSigMatrixElemType, cmsSigCurveSetElemType } },
1044     { TRUE , cmsSigAToB0Tag, cmsSigLutAtoBType,  3,  { cmsSigCurveSetElemType, cmsSigCLutElemType, cmsSigCurveSetElemType   } },
1045     { TRUE , cmsSigAToB0Tag, cmsSigLutAtoBType,  5,  { cmsSigCurveSetElemType, cmsSigCLutElemType, cmsSigCurveSetElemType, cmsSigMatrixElemType, cmsSigCurveSetElemType }},
1046     { TRUE , cmsSigBToA0Tag, cmsSigLutBtoAType,  1,  { cmsSigCurveSetElemType }},
1047     { TRUE , cmsSigBToA0Tag, cmsSigLutBtoAType,  3,  { cmsSigCurveSetElemType, cmsSigMatrixElemType, cmsSigCurveSetElemType }},
1048     { TRUE , cmsSigBToA0Tag, cmsSigLutBtoAType,  3,  { cmsSigCurveSetElemType, cmsSigCLutElemType, cmsSigCurveSetElemType }},
1049     { TRUE , cmsSigBToA0Tag, cmsSigLutBtoAType,  5,  { cmsSigCurveSetElemType, cmsSigMatrixElemType, cmsSigCurveSetElemType, cmsSigCLutElemType, cmsSigCurveSetElemType }}
1050 };
1051 
1052 #define SIZE_OF_ALLOWED_LUT (sizeof(AllowedLUTTypes)/sizeof(cmsAllowedLUT))
1053 
1054 // Check a single entry
1055 static
1056 cmsBool CheckOne(const cmsAllowedLUT* Tab, const cmsPipeline* Lut)
1057 {
1058     cmsStage* mpe;
1059     int n;
1060 
1061     for (n=0, mpe = Lut ->Elements; mpe != NULL; mpe = mpe ->Next, n++) {
1062 
1063         if (n > Tab ->nTypes) return FALSE;
1064         if (cmsStageType(mpe) != Tab ->MpeTypes[n]) return FALSE;
1065     }
1066 
1067     return (n == Tab ->nTypes);
1068 }
1069 
1070 
1071 static
1072 const cmsAllowedLUT* FindCombination(const cmsPipeline* Lut, cmsBool IsV4, cmsTagSignature DestinationTag)
1073 {
1074     cmsUInt32Number n;
1075 
1076     for (n=0; n < SIZE_OF_ALLOWED_LUT; n++) {
1077 
1078         const cmsAllowedLUT* Tab = AllowedLUTTypes + n;
1079 
1080         if (IsV4 ^ Tab -> IsV4) continue;
1081         if ((Tab ->RequiredTag != 0) && (Tab ->RequiredTag != DestinationTag)) continue;
1082 
1083         if (CheckOne(Tab, Lut)) return Tab;
1084     }
1085 
1086     return NULL;
1087 }
1088 
1089 
1090 // Does convert a transform into a device link profile
1091 cmsHPROFILE CMSEXPORT cmsTransform2DeviceLink(cmsHTRANSFORM hTransform, cmsFloat64Number Version, cmsUInt32Number dwFlags)
1092 {
1093     cmsHPROFILE hProfile = NULL;
1094         cmsUInt32Number FrmIn, FrmOut;
1095         cmsInt32Number ChansIn, ChansOut;
1096         int ColorSpaceBitsIn, ColorSpaceBitsOut;
1097     _cmsTRANSFORM* xform = (_cmsTRANSFORM*) hTransform;
1098     cmsPipeline* LUT = NULL;
1099     cmsStage* mpe;
1100     cmsContext ContextID = cmsGetTransformContextID(hTransform);
1101     const cmsAllowedLUT* AllowedLUT;
1102     cmsTagSignature DestinationTag;
1103     cmsProfileClassSignature deviceClass;
1104 
1105     _cmsAssert(hTransform != NULL);
1106 
1107     // Get the first mpe to check for named color
1108     mpe = cmsPipelineGetPtrToFirstStage(xform ->Lut);
1109 
1110     // Check if is a named color transform
1111     if (mpe != NULL) {
1112 
1113         if (cmsStageType(mpe) == cmsSigNamedColorElemType) {
1114             return CreateNamedColorDevicelink(hTransform);
1115         }
1116     }
1117 
1118     // First thing to do is to get a copy of the transformation
1119     LUT = cmsPipelineDup(xform ->Lut);
1120     if (LUT == NULL) return NULL;
1121 
1122     // Time to fix the Lab2/Lab4 issue.
1123     if ((xform ->EntryColorSpace == cmsSigLabData) && (Version < 4.0)) {
1124 
1125         if (!cmsPipelineInsertStage(LUT, cmsAT_BEGIN, _cmsStageAllocLabV2ToV4curves(ContextID)))
1126             goto Error;
1127     }
1128 
1129     // On the output side too. Note that due to V2/V4 PCS encoding on lab we cannot fix white misalignments
1130     if ((xform ->ExitColorSpace) == cmsSigLabData && (Version < 4.0)) {
1131 
1132         dwFlags |= cmsFLAGS_NOWHITEONWHITEFIXUP;
1133         if (!cmsPipelineInsertStage(LUT, cmsAT_END, _cmsStageAllocLabV4ToV2(ContextID)))
1134             goto Error;
1135     }
1136 
1137 
1138     hProfile = cmsCreateProfilePlaceholder(ContextID);
1139     if (!hProfile) goto Error;                    // can't allocate
1140 
1141     cmsSetProfileVersion(hProfile, Version);
1142 
1143     FixColorSpaces(hProfile, xform -> EntryColorSpace, xform -> ExitColorSpace, dwFlags);
1144 
1145     // Optimize the LUT and precalculate a devicelink
1146 
1147     ChansIn  = cmsChannelsOfColorSpace(xform -> EntryColorSpace);
1148     ChansOut = cmsChannelsOfColorSpace(xform -> ExitColorSpace);
1149 
1150     ColorSpaceBitsIn  = _cmsLCMScolorSpace(xform -> EntryColorSpace);
1151     ColorSpaceBitsOut = _cmsLCMScolorSpace(xform -> ExitColorSpace);
1152 
1153     FrmIn  = COLORSPACE_SH(ColorSpaceBitsIn) | CHANNELS_SH(ChansIn)|BYTES_SH(2);
1154     FrmOut = COLORSPACE_SH(ColorSpaceBitsOut) | CHANNELS_SH(ChansOut)|BYTES_SH(2);
1155 
1156     deviceClass = cmsGetDeviceClass(hProfile);
1157 
1158      if (deviceClass == cmsSigOutputClass)
1159          DestinationTag = cmsSigBToA0Tag;
1160      else
1161          DestinationTag = cmsSigAToB0Tag;
1162 
1163     // Check if the profile/version can store the result
1164     if (dwFlags & cmsFLAGS_FORCE_CLUT)
1165         AllowedLUT = NULL;
1166     else
1167         AllowedLUT = FindCombination(LUT, Version >= 4.0, DestinationTag);
1168 
1169     if (AllowedLUT == NULL) {
1170 
1171         // Try to optimize
1172         _cmsOptimizePipeline(ContextID, &LUT, xform ->RenderingIntent, &FrmIn, &FrmOut, &dwFlags);
1173         AllowedLUT = FindCombination(LUT, Version >= 4.0, DestinationTag);
1174 
1175     }
1176 
1177     // If no way, then force CLUT that for sure can be written
1178     if (AllowedLUT == NULL) {
1179 
1180         cmsStage* FirstStage;
1181         cmsStage* LastStage;
1182 
1183         dwFlags |= cmsFLAGS_FORCE_CLUT;
1184         _cmsOptimizePipeline(ContextID, &LUT, xform ->RenderingIntent, &FrmIn, &FrmOut, &dwFlags);
1185 
1186         // Put identity curves if needed
1187         FirstStage = cmsPipelineGetPtrToFirstStage(LUT);
1188         if (FirstStage != NULL && FirstStage ->Type != cmsSigCurveSetElemType)
1189              if (!cmsPipelineInsertStage(LUT, cmsAT_BEGIN, _cmsStageAllocIdentityCurves(ContextID, ChansIn)))
1190                  goto Error;
1191 
1192         LastStage = cmsPipelineGetPtrToLastStage(LUT);
1193         if (LastStage != NULL && LastStage ->Type != cmsSigCurveSetElemType)
1194              if (!cmsPipelineInsertStage(LUT, cmsAT_END,   _cmsStageAllocIdentityCurves(ContextID, ChansOut)))
1195                  goto Error;
1196 
1197         AllowedLUT = FindCombination(LUT, Version >= 4.0, DestinationTag);
1198     }
1199 
1200     // Somethings is wrong...
1201     if (AllowedLUT == NULL) {
1202         goto Error;
1203     }
1204 
1205 
1206     if (dwFlags & cmsFLAGS_8BITS_DEVICELINK)
1207                      cmsPipelineSetSaveAs8bitsFlag(LUT, TRUE);
1208 
1209     // Tag profile with information
1210     if (!SetTextTags(hProfile, L"devicelink")) goto Error;
1211 
1212     // Store result
1213     if (!cmsWriteTag(hProfile, DestinationTag, LUT)) goto Error;
1214 
1215 
1216     if (xform -> InputColorant != NULL) {
1217            if (!cmsWriteTag(hProfile, cmsSigColorantTableTag, xform->InputColorant)) goto Error;
1218     }
1219 
1220     if (xform -> OutputColorant != NULL) {
1221            if (!cmsWriteTag(hProfile, cmsSigColorantTableOutTag, xform->OutputColorant)) goto Error;
1222     }
1223 
1224     if ((deviceClass == cmsSigLinkClass) && (xform ->Sequence != NULL)) {
1225         if (!_cmsWriteProfileSequence(hProfile, xform ->Sequence)) goto Error;
1226     }
1227 
1228     // Set the white point
1229     if (deviceClass == cmsSigInputClass) {
1230         if (!cmsWriteTag(hProfile, cmsSigMediaWhitePointTag, &xform ->EntryWhitePoint)) goto Error;
1231     }
1232     else {
1233          if (!cmsWriteTag(hProfile, cmsSigMediaWhitePointTag, &xform ->ExitWhitePoint)) goto Error;
1234     }
1235 
1236 
1237     // Per 7.2.15 in spec 4.3
1238     cmsSetHeaderRenderingIntent(hProfile, xform ->RenderingIntent);
1239 
1240     cmsPipelineFree(LUT);
1241     return hProfile;
1242 
1243 Error:
1244     if (LUT != NULL) cmsPipelineFree(LUT);
1245     cmsCloseProfile(hProfile);
1246     return NULL;
1247 }