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