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