1//---------------------------------------------------------------------------------
2//
3//  Little Color Management System
4//  Copyright (c) 1998-2016 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
32static
33cmsBool 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
52Error:
53
54    if (DescriptionMLU)
55        cmsMLUfree(DescriptionMLU);
56    if (CopyrightMLU)
57        cmsMLUfree(CopyrightMLU);
58    return rc;
59}
60
61
62static
63cmsBool  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
90Error:
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.
101cmsHPROFILE CMSEXPORT cmsCreateRGBProfileTHR(cmsContext ContextID,
102                                          const cmsCIExyY* WhitePoint,
103                                          const cmsCIExyYTRIPLE* Primaries,
104                                          cmsToneCurve* const TransferFunction[3])
105{
106    cmsHPROFILE hICC;
107    cmsMAT3 MColorants;
108    cmsCIEXYZTRIPLE Colorants;
109    cmsCIExyY MaxWhite;
110    cmsMAT3 CHAD;
111    cmsCIEXYZ WhitePointXYZ;
112
113    hICC = cmsCreateProfilePlaceholder(ContextID);
114    if (!hICC)                          // can't allocate
115        return NULL;
116
117    cmsSetProfileVersion(hICC, 4.3);
118
119    cmsSetDeviceClass(hICC,      cmsSigDisplayClass);
120    cmsSetColorSpace(hICC,       cmsSigRgbData);
121    cmsSetPCS(hICC,              cmsSigXYZData);
122
123    cmsSetHeaderRenderingIntent(hICC,  INTENT_PERCEPTUAL);
124
125
126    // Implement profile using following tags:
127    //
128    //  1 cmsSigProfileDescriptionTag
129    //  2 cmsSigMediaWhitePointTag
130    //  3 cmsSigRedColorantTag
131    //  4 cmsSigGreenColorantTag
132    //  5 cmsSigBlueColorantTag
133    //  6 cmsSigRedTRCTag
134    //  7 cmsSigGreenTRCTag
135    //  8 cmsSigBlueTRCTag
136    //  9 Chromatic adaptation Tag
137    // This conforms a standard RGB DisplayProfile as says ICC, and then I add (As per addendum II)
138    // 10 cmsSigChromaticityTag
139
140
141    if (!SetTextTags(hICC, L"RGB built-in")) goto Error;
142
143    if (WhitePoint) {
144
145        if (!cmsWriteTag(hICC, cmsSigMediaWhitePointTag, cmsD50_XYZ())) goto Error;
146
147        cmsxyY2XYZ(&WhitePointXYZ, WhitePoint);
148        _cmsAdaptationMatrix(&CHAD, NULL, &WhitePointXYZ, cmsD50_XYZ());
149
150        // This is a V4 tag, but many CMM does read and understand it no matter which version
151        if (!cmsWriteTag(hICC, cmsSigChromaticAdaptationTag, (void*) &CHAD)) goto Error;
152    }
153
154    if (WhitePoint && Primaries) {
155
156        MaxWhite.x =  WhitePoint -> x;
157        MaxWhite.y =  WhitePoint -> y;
158        MaxWhite.Y =  1.0;
159
160        if (!_cmsBuildRGB2XYZtransferMatrix(&MColorants, &MaxWhite, Primaries)) goto Error;
161
162        Colorants.Red.X   = MColorants.v[0].n[0];
163        Colorants.Red.Y   = MColorants.v[1].n[0];
164        Colorants.Red.Z   = MColorants.v[2].n[0];
165
166        Colorants.Green.X = MColorants.v[0].n[1];
167        Colorants.Green.Y = MColorants.v[1].n[1];
168        Colorants.Green.Z = MColorants.v[2].n[1];
169
170        Colorants.Blue.X  = MColorants.v[0].n[2];
171        Colorants.Blue.Y  = MColorants.v[1].n[2];
172        Colorants.Blue.Z  = MColorants.v[2].n[2];
173
174        if (!cmsWriteTag(hICC, cmsSigRedColorantTag,   (void*) &Colorants.Red)) goto Error;
175        if (!cmsWriteTag(hICC, cmsSigBlueColorantTag,  (void*) &Colorants.Blue)) goto Error;
176        if (!cmsWriteTag(hICC, cmsSigGreenColorantTag, (void*) &Colorants.Green)) goto Error;
177    }
178
179
180    if (TransferFunction) {
181
182        // Tries to minimize space. Thanks to Richard Hughes for this nice idea
183        if (!cmsWriteTag(hICC, cmsSigRedTRCTag,   (void*) TransferFunction[0])) goto Error;
184
185        if (TransferFunction[1] == TransferFunction[0]) {
186
187            if (!cmsLinkTag (hICC, cmsSigGreenTRCTag, cmsSigRedTRCTag)) goto Error;
188
189        } else {
190
191            if (!cmsWriteTag(hICC, cmsSigGreenTRCTag, (void*) TransferFunction[1])) goto Error;
192        }
193
194        if (TransferFunction[2] == TransferFunction[0]) {
195
196            if (!cmsLinkTag (hICC, cmsSigBlueTRCTag, cmsSigRedTRCTag)) goto Error;
197
198        } else {
199
200            if (!cmsWriteTag(hICC, cmsSigBlueTRCTag, (void*) TransferFunction[2])) goto Error;
201        }
202    }
203
204    if (Primaries) {
205        if (!cmsWriteTag(hICC, cmsSigChromaticityTag, (void*) Primaries)) goto Error;
206    }
207
208
209    return hICC;
210
211Error:
212    if (hICC)
213        cmsCloseProfile(hICC);
214    return NULL;
215}
216
217cmsHPROFILE 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.
227cmsHPROFILE CMSEXPORT cmsCreateGrayProfileTHR(cmsContext ContextID,
228                                           const cmsCIExyY* WhitePoint,
229                                           const cmsToneCurve* TransferFunction)
230{
231    cmsHPROFILE hICC;
232    cmsCIEXYZ tmp;
233
234    hICC = cmsCreateProfilePlaceholder(ContextID);
235    if (!hICC)                          // can't allocate
236        return NULL;
237
238    cmsSetProfileVersion(hICC, 4.3);
239
240    cmsSetDeviceClass(hICC,      cmsSigDisplayClass);
241    cmsSetColorSpace(hICC,       cmsSigGrayData);
242    cmsSetPCS(hICC,              cmsSigXYZData);
243    cmsSetHeaderRenderingIntent(hICC,  INTENT_PERCEPTUAL);
244
245
246    // Implement profile using following tags:
247    //
248    //  1 cmsSigProfileDescriptionTag
249    //  2 cmsSigMediaWhitePointTag
250    //  3 cmsSigGrayTRCTag
251
252    // This conforms a standard Gray DisplayProfile
253
254    // Fill-in the tags
255
256    if (!SetTextTags(hICC, L"gray built-in")) goto Error;
257
258
259    if (WhitePoint) {
260
261        cmsxyY2XYZ(&tmp, WhitePoint);
262        if (!cmsWriteTag(hICC, cmsSigMediaWhitePointTag, (void*) &tmp)) goto Error;
263    }
264
265    if (TransferFunction) {
266
267        if (!cmsWriteTag(hICC, cmsSigGrayTRCTag, (void*) TransferFunction)) goto Error;
268    }
269
270    return hICC;
271
272Error:
273    if (hICC)
274        cmsCloseProfile(hICC);
275    return NULL;
276}
277
278
279
280cmsHPROFILE 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
288cmsHPROFILE CMSEXPORT cmsCreateLinearizationDeviceLinkTHR(cmsContext ContextID,
289                                                          cmsColorSpaceSignature ColorSpace,
290                                                          cmsToneCurve* const TransferFunctions[])
291{
292    cmsHPROFILE hICC;
293    cmsPipeline* Pipeline;
294    int nChannels;
295
296    hICC = cmsCreateProfilePlaceholder(ContextID);
297    if (!hICC)
298        return NULL;
299
300    cmsSetProfileVersion(hICC, 4.3);
301
302    cmsSetDeviceClass(hICC,      cmsSigLinkClass);
303    cmsSetColorSpace(hICC,       ColorSpace);
304    cmsSetPCS(hICC,              ColorSpace);
305
306    cmsSetHeaderRenderingIntent(hICC,  INTENT_PERCEPTUAL);
307
308    // Set up channels
309    nChannels = cmsChannelsOf(ColorSpace);
310
311    // Creates a Pipeline with prelinearization step only
312    Pipeline = cmsPipelineAlloc(ContextID, nChannels, nChannels);
313    if (Pipeline == NULL) goto Error;
314
315
316    // Copy tables to Pipeline
317    if (!cmsPipelineInsertStage(Pipeline, cmsAT_BEGIN, cmsStageAllocToneCurves(ContextID, nChannels, TransferFunctions)))
318        goto Error;
319
320    // Create tags
321    if (!SetTextTags(hICC, L"Linearization built-in")) goto Error;
322    if (!cmsWriteTag(hICC, cmsSigAToB0Tag, (void*) Pipeline)) goto Error;
323    if (!SetSeqDescTag(hICC, "Linearization built-in")) goto Error;
324
325    // Pipeline is already on virtual profile
326    cmsPipelineFree(Pipeline);
327
328    // Ok, done
329    return hICC;
330
331Error:
332    cmsPipelineFree(Pipeline);
333    if (hICC)
334        cmsCloseProfile(hICC);
335
336
337    return NULL;
338}
339
340cmsHPROFILE 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
363static
364int InkLimitingSampler(register const cmsUInt16Number In[], register cmsUInt16Number Out[], register void* Cargo)
365{
366    cmsFloat64Number InkLimit = *(cmsFloat64Number *) Cargo;
367    cmsFloat64Number SumCMY, SumCMYK, Ratio;
368
369    InkLimit = (InkLimit * 655.35);
370
371    SumCMY   = In[0]  + In[1] + In[2];
372    SumCMYK  = SumCMY + In[3];
373
374    if (SumCMYK > InkLimit) {
375
376        Ratio = 1 - ((SumCMYK - InkLimit) / SumCMY);
377        if (Ratio < 0)
378            Ratio = 0;
379    }
380    else Ratio = 1;
381
382    Out[0] = _cmsQuickSaturateWord(In[0] * Ratio);     // C
383    Out[1] = _cmsQuickSaturateWord(In[1] * Ratio);     // M
384    Out[2] = _cmsQuickSaturateWord(In[2] * Ratio);     // Y
385
386    Out[3] = In[3];                                 // K (untouched)
387
388    return TRUE;
389}
390
391// This is a devicelink operating in CMYK for ink-limiting
392
393cmsHPROFILE CMSEXPORT cmsCreateInkLimitingDeviceLinkTHR(cmsContext ContextID,
394                                                     cmsColorSpaceSignature ColorSpace,
395                                                     cmsFloat64Number Limit)
396{
397    cmsHPROFILE hICC;
398    cmsPipeline* LUT;
399    cmsStage* CLUT;
400    int nChannels;
401
402    if (ColorSpace != cmsSigCmykData) {
403        cmsSignalError(ContextID, cmsERROR_COLORSPACE_CHECK, "InkLimiting: Only CMYK currently supported");
404        return NULL;
405    }
406
407    if (Limit < 0.0 || Limit > 400) {
408
409        cmsSignalError(ContextID, cmsERROR_RANGE, "InkLimiting: Limit should be between 0..400");
410        if (Limit < 0) Limit = 0;
411        if (Limit > 400) Limit = 400;
412
413    }
414
415    hICC = cmsCreateProfilePlaceholder(ContextID);
416    if (!hICC)                          // can't allocate
417        return NULL;
418
419    cmsSetProfileVersion(hICC, 4.3);
420
421    cmsSetDeviceClass(hICC,      cmsSigLinkClass);
422    cmsSetColorSpace(hICC,       ColorSpace);
423    cmsSetPCS(hICC,              ColorSpace);
424
425    cmsSetHeaderRenderingIntent(hICC,  INTENT_PERCEPTUAL);
426
427
428    // Creates a Pipeline with 3D grid only
429    LUT = cmsPipelineAlloc(ContextID, 4, 4);
430    if (LUT == NULL) goto Error;
431
432
433    nChannels = cmsChannelsOf(ColorSpace);
434
435    CLUT = cmsStageAllocCLut16bit(ContextID, 17, nChannels, nChannels, NULL);
436    if (CLUT == NULL) goto Error;
437
438    if (!cmsStageSampleCLut16bit(CLUT, InkLimitingSampler, (void*) &Limit, 0)) goto Error;
439
440    if (!cmsPipelineInsertStage(LUT, cmsAT_BEGIN, _cmsStageAllocIdentityCurves(ContextID, nChannels)) ||
441        !cmsPipelineInsertStage(LUT, cmsAT_END, CLUT) ||
442        !cmsPipelineInsertStage(LUT, cmsAT_END, _cmsStageAllocIdentityCurves(ContextID, nChannels)))
443        goto Error;
444
445    // Create tags
446    if (!SetTextTags(hICC, L"ink-limiting built-in")) goto Error;
447
448    if (!cmsWriteTag(hICC, cmsSigAToB0Tag, (void*) LUT))  goto Error;
449    if (!SetSeqDescTag(hICC, "ink-limiting built-in")) goto Error;
450
451    // cmsPipeline is already on virtual profile
452    cmsPipelineFree(LUT);
453
454    // Ok, done
455    return hICC;
456
457Error:
458    if (LUT != NULL)
459        cmsPipelineFree(LUT);
460
461    if (hICC != NULL)
462        cmsCloseProfile(hICC);
463
464    return NULL;
465}
466
467cmsHPROFILE CMSEXPORT cmsCreateInkLimitingDeviceLink(cmsColorSpaceSignature ColorSpace, cmsFloat64Number Limit)
468{
469    return cmsCreateInkLimitingDeviceLinkTHR(NULL, ColorSpace, Limit);
470}
471
472
473// Creates a fake Lab identity.
474cmsHPROFILE 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
502Error:
503
504    if (LUT != NULL)
505        cmsPipelineFree(LUT);
506
507    if (hProfile != NULL)
508        cmsCloseProfile(hProfile);
509
510    return NULL;
511}
512
513
514cmsHPROFILE CMSEXPORT cmsCreateLab2Profile(const cmsCIExyY* WhitePoint)
515{
516    return cmsCreateLab2ProfileTHR(NULL, WhitePoint);
517}
518
519
520// Creates a fake Lab V4 identity.
521cmsHPROFILE CMSEXPORT cmsCreateLab4ProfileTHR(cmsContext ContextID, const cmsCIExyY* WhitePoint)
522{
523    cmsHPROFILE hProfile;
524    cmsPipeline* LUT = NULL;
525
526    hProfile = cmsCreateRGBProfileTHR(ContextID, WhitePoint == NULL ? cmsD50_xyY() : WhitePoint, NULL, NULL);
527    if (hProfile == NULL) return NULL;
528
529    cmsSetProfileVersion(hProfile, 4.3);
530
531    cmsSetDeviceClass(hProfile, cmsSigAbstractClass);
532    cmsSetColorSpace(hProfile,  cmsSigLabData);
533    cmsSetPCS(hProfile,         cmsSigLabData);
534
535    if (!SetTextTags(hProfile, L"Lab identity built-in")) goto Error;
536
537    // An empty LUTs is all we need
538    LUT = cmsPipelineAlloc(ContextID, 3, 3);
539    if (LUT == NULL) goto Error;
540
541    if (!cmsPipelineInsertStage(LUT, cmsAT_BEGIN, _cmsStageAllocIdentityCurves(ContextID, 3)))
542        goto Error;
543
544    if (!cmsWriteTag(hProfile, cmsSigAToB0Tag, LUT)) goto Error;
545    cmsPipelineFree(LUT);
546
547    return hProfile;
548
549Error:
550
551    if (LUT != NULL)
552        cmsPipelineFree(LUT);
553
554    if (hProfile != NULL)
555        cmsCloseProfile(hProfile);
556
557    return NULL;
558}
559
560cmsHPROFILE CMSEXPORT cmsCreateLab4Profile(const cmsCIExyY* WhitePoint)
561{
562    return cmsCreateLab4ProfileTHR(NULL, WhitePoint);
563}
564
565
566// Creates a fake XYZ identity
567cmsHPROFILE CMSEXPORT cmsCreateXYZProfileTHR(cmsContext ContextID)
568{
569    cmsHPROFILE hProfile;
570    cmsPipeline* LUT = NULL;
571
572    hProfile = cmsCreateRGBProfileTHR(ContextID, cmsD50_xyY(), NULL, NULL);
573    if (hProfile == NULL) return NULL;
574
575    cmsSetProfileVersion(hProfile, 4.3);
576
577    cmsSetDeviceClass(hProfile, cmsSigAbstractClass);
578    cmsSetColorSpace(hProfile,  cmsSigXYZData);
579    cmsSetPCS(hProfile,         cmsSigXYZData);
580
581    if (!SetTextTags(hProfile, L"XYZ identity built-in")) goto Error;
582
583    // An identity LUT is all we need
584    LUT = cmsPipelineAlloc(ContextID, 3, 3);
585    if (LUT == NULL) goto Error;
586
587    if (!cmsPipelineInsertStage(LUT, cmsAT_BEGIN, _cmsStageAllocIdentityCurves(ContextID, 3)))
588        goto Error;
589
590    if (!cmsWriteTag(hProfile, cmsSigAToB0Tag, LUT)) goto Error;
591    cmsPipelineFree(LUT);
592
593    return hProfile;
594
595Error:
596
597    if (LUT != NULL)
598        cmsPipelineFree(LUT);
599
600    if (hProfile != NULL)
601        cmsCloseProfile(hProfile);
602
603    return NULL;
604}
605
606
607cmsHPROFILE 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
628static
629cmsToneCurve* 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
643cmsHPROFILE 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
670cmsHPROFILE CMSEXPORT cmsCreate_sRGBProfile(void)
671{
672    return cmsCreate_sRGBProfileTHR(NULL);
673}
674
675
676
677typedef 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
688static
689int bchswSampler(register const cmsUInt16Number In[], register cmsUInt16Number Out[], register 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
728cmsHPROFILE CMSEXPORT cmsCreateBCHSWabstractProfileTHR(cmsContext ContextID,
729    int nLUTPoints,
730    cmsFloat64Number Bright,
731    cmsFloat64Number Contrast,
732    cmsFloat64Number Hue,
733    cmsFloat64Number Saturation,
734    int TempSrc,
735    int TempDest)
736{
737    cmsHPROFILE hICC;
738    cmsPipeline* Pipeline;
739    BCHSWADJUSTS bchsw;
740    cmsCIExyY WhitePnt;
741    cmsStage* CLUT;
742    cmsUInt32Number Dimensions[MAX_INPUT_DIMENSIONS];
743    int 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) return NULL;
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
807Error:
808    cmsPipelineFree(Pipeline);
809    cmsCloseProfile(hICC);
810    return NULL;
811}
812
813
814CMSAPI cmsHPROFILE   CMSEXPORT cmsCreateBCHSWabstractProfile(int nLUTPoints,
815                                                             cmsFloat64Number Bright,
816                                                             cmsFloat64Number Contrast,
817                                                             cmsFloat64Number Hue,
818                                                             cmsFloat64Number Saturation,
819                                                             int TempSrc,
820                                                             int 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
828cmsHPROFILE CMSEXPORT cmsCreateNULLProfileTHR(cmsContext ContextID)
829{
830    cmsHPROFILE hProfile;
831    cmsPipeline* LUT = NULL;
832    cmsStage* PostLin;
833    cmsToneCurve* EmptyTab;
834    cmsUInt16Number Zero[2] = { 0, 0 };
835
836    hProfile = cmsCreateProfilePlaceholder(ContextID);
837    if (!hProfile)                          // can't allocate
838        return NULL;
839
840    cmsSetProfileVersion(hProfile, 4.3);
841
842    if (!SetTextTags(hProfile, L"NULL profile built-in")) goto Error;
843
844
845
846    cmsSetDeviceClass(hProfile, cmsSigOutputClass);
847    cmsSetColorSpace(hProfile,  cmsSigGrayData);
848    cmsSetPCS(hProfile,         cmsSigLabData);
849
850    // An empty LUTs is all we need
851    LUT = cmsPipelineAlloc(ContextID, 1, 1);
852    if (LUT == NULL) goto Error;
853
854    EmptyTab = cmsBuildTabulatedToneCurve16(ContextID, 2, Zero);
855    PostLin = cmsStageAllocToneCurves(ContextID, 1, &EmptyTab);
856    cmsFreeToneCurve(EmptyTab);
857
858    if (!cmsPipelineInsertStage(LUT, cmsAT_END, PostLin))
859        goto Error;
860
861    if (!cmsWriteTag(hProfile, cmsSigBToA0Tag, (void*) LUT)) goto Error;
862    if (!cmsWriteTag(hProfile, cmsSigMediaWhitePointTag, cmsD50_XYZ())) goto Error;
863
864    cmsPipelineFree(LUT);
865    return hProfile;
866
867Error:
868
869    if (LUT != NULL)
870        cmsPipelineFree(LUT);
871
872    if (hProfile != NULL)
873        cmsCloseProfile(hProfile);
874
875    return NULL;
876}
877
878cmsHPROFILE CMSEXPORT cmsCreateNULLProfile(void)
879{
880    return cmsCreateNULLProfileTHR(NULL);
881}
882
883
884static
885int IsPCS(cmsColorSpaceSignature ColorSpace)
886{
887    return (ColorSpace == cmsSigXYZData ||
888            ColorSpace == cmsSigLabData);
889}
890
891
892static
893void FixColorSpaces(cmsHPROFILE hProfile,
894                              cmsColorSpaceSignature ColorSpace,
895                              cmsColorSpaceSignature PCS,
896                              cmsUInt32Number dwFlags)
897{
898    if (dwFlags & cmsFLAGS_GUESSDEVICECLASS) {
899
900            if (IsPCS(ColorSpace) && IsPCS(PCS)) {
901
902                    cmsSetDeviceClass(hProfile,      cmsSigAbstractClass);
903                    cmsSetColorSpace(hProfile,       ColorSpace);
904                    cmsSetPCS(hProfile,              PCS);
905                    return;
906            }
907
908            if (IsPCS(ColorSpace) && !IsPCS(PCS)) {
909
910                    cmsSetDeviceClass(hProfile, cmsSigOutputClass);
911                    cmsSetPCS(hProfile,         ColorSpace);
912                    cmsSetColorSpace(hProfile,  PCS);
913                    return;
914            }
915
916            if (IsPCS(PCS) && !IsPCS(ColorSpace)) {
917
918                   cmsSetDeviceClass(hProfile,  cmsSigInputClass);
919                   cmsSetColorSpace(hProfile,   ColorSpace);
920                   cmsSetPCS(hProfile,          PCS);
921                   return;
922            }
923    }
924
925    cmsSetDeviceClass(hProfile,      cmsSigLinkClass);
926    cmsSetColorSpace(hProfile,       ColorSpace);
927    cmsSetPCS(hProfile,              PCS);
928}
929
930
931
932// This function creates a named color profile dumping all the contents of transform to a single profile
933// In this way, LittleCMS may be used to "group" several named color databases into a single profile.
934// It has, however, several minor limitations. PCS is always Lab, which is not very critic since this
935// is the normal PCS for named color profiles.
936static
937cmsHPROFILE CreateNamedColorDevicelink(cmsHTRANSFORM xform)
938{
939    _cmsTRANSFORM* v = (_cmsTRANSFORM*) xform;
940    cmsHPROFILE hICC = NULL;
941    int i, nColors;
942    cmsNAMEDCOLORLIST *nc2 = NULL, *Original = NULL;
943
944    // Create an empty placeholder
945    hICC = cmsCreateProfilePlaceholder(v->ContextID);
946    if (hICC == NULL) return NULL;
947
948    // Critical information
949    cmsSetDeviceClass(hICC, cmsSigNamedColorClass);
950    cmsSetColorSpace(hICC, v ->ExitColorSpace);
951    cmsSetPCS(hICC, cmsSigLabData);
952
953    // Tag profile with information
954    if (!SetTextTags(hICC, L"Named color devicelink")) goto Error;
955
956    Original = cmsGetNamedColorList(xform);
957    if (Original == NULL) goto Error;
958
959    nColors = cmsNamedColorCount(Original);
960    nc2     = cmsDupNamedColorList(Original);
961    if (nc2 == NULL) goto Error;
962
963    // Colorant count now depends on the output space
964    nc2 ->ColorantCount = cmsPipelineOutputChannels(v ->Lut);
965
966    // Make sure we have proper formatters
967    cmsChangeBuffersFormat(xform, TYPE_NAMED_COLOR_INDEX,
968        FLOAT_SH(0) | COLORSPACE_SH(_cmsLCMScolorSpace(v ->ExitColorSpace))
969        | BYTES_SH(2) | CHANNELS_SH(cmsChannelsOf(v ->ExitColorSpace)));
970
971    // Apply the transfor to colorants.
972    for (i=0; i < nColors; i++) {
973        cmsDoTransform(xform, &i, nc2 ->List[i].DeviceColorant, 1);
974    }
975
976    if (!cmsWriteTag(hICC, cmsSigNamedColor2Tag, (void*) nc2)) goto Error;
977    cmsFreeNamedColorList(nc2);
978
979    return hICC;
980
981Error:
982    if (hICC != NULL) cmsCloseProfile(hICC);
983    return NULL;
984}
985
986
987// This structure holds information about which MPU can be stored on a profile based on the version
988
989typedef struct {
990    cmsBool              IsV4;             // Is a V4 tag?
991    cmsTagSignature      RequiredTag;      // Set to 0 for both types
992    cmsTagTypeSignature  LutType;          // The LUT type
993    int                  nTypes;           // Number of types (up to 5)
994    cmsStageSignature    MpeTypes[5];      // 5 is the maximum number
995
996} cmsAllowedLUT;
997
998#define cmsSig0 ((cmsTagSignature) 0)
999
1000static const cmsAllowedLUT AllowedLUTTypes[] = {
1001
1002    { FALSE, cmsSig0,        cmsSigLut16Type, 4, { cmsSigMatrixElemType, cmsSigCurveSetElemType, cmsSigCLutElemType, cmsSigCurveSetElemType } },
1003    { FALSE, cmsSig0,        cmsSigLut16Type, 3, { cmsSigCurveSetElemType, cmsSigCLutElemType, cmsSigCurveSetElemType } },
1004    { FALSE, cmsSig0,        cmsSigLut16Type, 2, { cmsSigCurveSetElemType, cmsSigCLutElemType } },
1005    { TRUE,  cmsSig0,        cmsSigLutAtoBType, 1, { cmsSigCurveSetElemType } },
1006    { TRUE , cmsSigAToB0Tag, cmsSigLutAtoBType,  3,  { cmsSigCurveSetElemType, cmsSigMatrixElemType, cmsSigCurveSetElemType } },
1007    { TRUE , cmsSigAToB0Tag, cmsSigLutAtoBType,  3,  { cmsSigCurveSetElemType, cmsSigCLutElemType, cmsSigCurveSetElemType   } },
1008    { TRUE , cmsSigAToB0Tag, cmsSigLutAtoBType,  5,  { cmsSigCurveSetElemType, cmsSigCLutElemType, cmsSigCurveSetElemType, cmsSigMatrixElemType, cmsSigCurveSetElemType }},
1009    { TRUE , cmsSigBToA0Tag, cmsSigLutBtoAType,  1,  { cmsSigCurveSetElemType }},
1010    { TRUE , cmsSigBToA0Tag, cmsSigLutBtoAType,  3,  { cmsSigCurveSetElemType, cmsSigMatrixElemType, cmsSigCurveSetElemType }},
1011    { TRUE , cmsSigBToA0Tag, cmsSigLutBtoAType,  3,  { cmsSigCurveSetElemType, cmsSigCLutElemType, cmsSigCurveSetElemType }},
1012    { TRUE , cmsSigBToA0Tag, cmsSigLutBtoAType,  5,  { cmsSigCurveSetElemType, cmsSigMatrixElemType, cmsSigCurveSetElemType, cmsSigCLutElemType, cmsSigCurveSetElemType }}
1013};
1014
1015#define SIZE_OF_ALLOWED_LUT (sizeof(AllowedLUTTypes)/sizeof(cmsAllowedLUT))
1016
1017// Check a single entry
1018static
1019cmsBool CheckOne(const cmsAllowedLUT* Tab, const cmsPipeline* Lut)
1020{
1021    cmsStage* mpe;
1022    int n;
1023
1024    for (n=0, mpe = Lut ->Elements; mpe != NULL; mpe = mpe ->Next, n++) {
1025
1026        if (n > Tab ->nTypes) return FALSE;
1027        if (cmsStageType(mpe) != Tab ->MpeTypes[n]) return FALSE;
1028    }
1029
1030    return (n == Tab ->nTypes);
1031}
1032
1033
1034static
1035const cmsAllowedLUT* FindCombination(const cmsPipeline* Lut, cmsBool IsV4, cmsTagSignature DestinationTag)
1036{
1037    cmsUInt32Number n;
1038
1039    for (n=0; n < SIZE_OF_ALLOWED_LUT; n++) {
1040
1041        const cmsAllowedLUT* Tab = AllowedLUTTypes + n;
1042
1043        if (IsV4 ^ Tab -> IsV4) continue;
1044        if ((Tab ->RequiredTag != 0) && (Tab ->RequiredTag != DestinationTag)) continue;
1045
1046        if (CheckOne(Tab, Lut)) return Tab;
1047    }
1048
1049    return NULL;
1050}
1051
1052
1053// Does convert a transform into a device link profile
1054cmsHPROFILE CMSEXPORT cmsTransform2DeviceLink(cmsHTRANSFORM hTransform, cmsFloat64Number Version, cmsUInt32Number dwFlags)
1055{
1056    cmsHPROFILE hProfile = NULL;
1057    cmsUInt32Number FrmIn, FrmOut, ChansIn, ChansOut;
1058    cmsUInt32Number ColorSpaceBitsIn, ColorSpaceBitsOut;
1059    _cmsTRANSFORM* xform = (_cmsTRANSFORM*) hTransform;
1060    cmsPipeline* LUT = NULL;
1061    cmsStage* mpe;
1062    cmsContext ContextID = cmsGetTransformContextID(hTransform);
1063    const cmsAllowedLUT* AllowedLUT;
1064    cmsTagSignature DestinationTag;
1065    cmsProfileClassSignature deviceClass;
1066
1067    _cmsAssert(hTransform != NULL);
1068
1069    // Get the first mpe to check for named color
1070    mpe = cmsPipelineGetPtrToFirstStage(xform ->Lut);
1071
1072    // Check if is a named color transform
1073    if (mpe != NULL) {
1074
1075        if (cmsStageType(mpe) == cmsSigNamedColorElemType) {
1076            return CreateNamedColorDevicelink(hTransform);
1077        }
1078    }
1079
1080    // First thing to do is to get a copy of the transformation
1081    LUT = cmsPipelineDup(xform ->Lut);
1082    if (LUT == NULL) return NULL;
1083
1084    // Time to fix the Lab2/Lab4 issue.
1085    if ((xform ->EntryColorSpace == cmsSigLabData) && (Version < 4.0)) {
1086
1087        if (!cmsPipelineInsertStage(LUT, cmsAT_BEGIN, _cmsStageAllocLabV2ToV4curves(ContextID)))
1088            goto Error;
1089    }
1090
1091    // On the output side too
1092    if ((xform ->ExitColorSpace) == cmsSigLabData && (Version < 4.0)) {
1093
1094        if (!cmsPipelineInsertStage(LUT, cmsAT_END, _cmsStageAllocLabV4ToV2(ContextID)))
1095            goto Error;
1096    }
1097
1098
1099    hProfile = cmsCreateProfilePlaceholder(ContextID);
1100    if (!hProfile) goto Error;                    // can't allocate
1101
1102    cmsSetProfileVersion(hProfile, Version);
1103
1104    FixColorSpaces(hProfile, xform -> EntryColorSpace, xform -> ExitColorSpace, dwFlags);
1105
1106    // Optimize the LUT and precalculate a devicelink
1107
1108    ChansIn  = cmsChannelsOf(xform -> EntryColorSpace);
1109    ChansOut = cmsChannelsOf(xform -> ExitColorSpace);
1110
1111    ColorSpaceBitsIn  = _cmsLCMScolorSpace(xform -> EntryColorSpace);
1112    ColorSpaceBitsOut = _cmsLCMScolorSpace(xform -> ExitColorSpace);
1113
1114    FrmIn  = COLORSPACE_SH(ColorSpaceBitsIn) | CHANNELS_SH(ChansIn)|BYTES_SH(2);
1115    FrmOut = COLORSPACE_SH(ColorSpaceBitsOut) | CHANNELS_SH(ChansOut)|BYTES_SH(2);
1116
1117    deviceClass = cmsGetDeviceClass(hProfile);
1118
1119     if (deviceClass == cmsSigOutputClass)
1120         DestinationTag = cmsSigBToA0Tag;
1121     else
1122         DestinationTag = cmsSigAToB0Tag;
1123
1124    // Check if the profile/version can store the result
1125    if (dwFlags & cmsFLAGS_FORCE_CLUT)
1126        AllowedLUT = NULL;
1127    else
1128        AllowedLUT = FindCombination(LUT, Version >= 4.0, DestinationTag);
1129
1130    if (AllowedLUT == NULL) {
1131
1132        // Try to optimize
1133        _cmsOptimizePipeline(ContextID, &LUT, xform ->RenderingIntent, &FrmIn, &FrmOut, &dwFlags);
1134        AllowedLUT = FindCombination(LUT, Version >= 4.0, DestinationTag);
1135
1136    }
1137
1138    // If no way, then force CLUT that for sure can be written
1139    if (AllowedLUT == NULL) {
1140
1141        cmsStage* FirstStage;
1142        cmsStage* LastStage;
1143
1144        dwFlags |= cmsFLAGS_FORCE_CLUT;
1145        _cmsOptimizePipeline(ContextID, &LUT, xform ->RenderingIntent, &FrmIn, &FrmOut, &dwFlags);
1146
1147        // Put identity curves if needed
1148        FirstStage = cmsPipelineGetPtrToFirstStage(LUT);
1149        if (FirstStage != NULL && FirstStage ->Type != cmsSigCurveSetElemType)
1150             if (!cmsPipelineInsertStage(LUT, cmsAT_BEGIN, _cmsStageAllocIdentityCurves(ContextID, ChansIn)))
1151                 goto Error;
1152
1153        LastStage = cmsPipelineGetPtrToLastStage(LUT);
1154        if (LastStage != NULL && LastStage ->Type != cmsSigCurveSetElemType)
1155             if (!cmsPipelineInsertStage(LUT, cmsAT_END,   _cmsStageAllocIdentityCurves(ContextID, ChansOut)))
1156                 goto Error;
1157
1158        AllowedLUT = FindCombination(LUT, Version >= 4.0, DestinationTag);
1159    }
1160
1161    // Somethings is wrong...
1162    if (AllowedLUT == NULL) {
1163        goto Error;
1164    }
1165
1166
1167    if (dwFlags & cmsFLAGS_8BITS_DEVICELINK)
1168                     cmsPipelineSetSaveAs8bitsFlag(LUT, TRUE);
1169
1170    // Tag profile with information
1171    if (!SetTextTags(hProfile, L"devicelink")) goto Error;
1172
1173    // Store result
1174    if (!cmsWriteTag(hProfile, DestinationTag, LUT)) goto Error;
1175
1176
1177    if (xform -> InputColorant != NULL) {
1178           if (!cmsWriteTag(hProfile, cmsSigColorantTableTag, xform->InputColorant)) goto Error;
1179    }
1180
1181    if (xform -> OutputColorant != NULL) {
1182           if (!cmsWriteTag(hProfile, cmsSigColorantTableOutTag, xform->OutputColorant)) goto Error;
1183    }
1184
1185    if ((deviceClass == cmsSigLinkClass) && (xform ->Sequence != NULL)) {
1186        if (!_cmsWriteProfileSequence(hProfile, xform ->Sequence)) goto Error;
1187    }
1188
1189    // Set the white point
1190    if (deviceClass == cmsSigInputClass) {
1191        if (!cmsWriteTag(hProfile, cmsSigMediaWhitePointTag, &xform ->EntryWhitePoint)) goto Error;
1192    }
1193    else {
1194         if (!cmsWriteTag(hProfile, cmsSigMediaWhitePointTag, &xform ->ExitWhitePoint)) goto Error;
1195    }
1196
1197
1198    // Per 7.2.15 in spec 4.3
1199    cmsSetHeaderRenderingIntent(hProfile, xform ->RenderingIntent);
1200
1201    cmsPipelineFree(LUT);
1202    return hProfile;
1203
1204Error:
1205    if (LUT != NULL) cmsPipelineFree(LUT);
1206    cmsCloseProfile(hProfile);
1207    return NULL;
1208}
1209