1//---------------------------------------------------------------------------------
2//
3//  Little Color Management System
4//  Copyright (c) 1998-2014 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;
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                cmsCIEXYZ WPsrc, WPdest;
683
684} BCHSWADJUSTS, *LPBCHSWADJUSTS;
685
686
687static
688int bchswSampler(register const cmsUInt16Number In[], register cmsUInt16Number Out[], register void* Cargo)
689{
690    cmsCIELab LabIn, LabOut;
691    cmsCIELCh LChIn, LChOut;
692    cmsCIEXYZ XYZ;
693    LPBCHSWADJUSTS bchsw = (LPBCHSWADJUSTS) Cargo;
694
695
696    cmsLabEncoded2Float(&LabIn, In);
697
698
699    cmsLab2LCh(&LChIn, &LabIn);
700
701    // Do some adjusts on LCh
702
703    LChOut.L = LChIn.L * bchsw ->Contrast + bchsw ->Brightness;
704    LChOut.C = LChIn.C + bchsw -> Saturation;
705    LChOut.h = LChIn.h + bchsw -> Hue;
706
707
708    cmsLCh2Lab(&LabOut, &LChOut);
709
710    // Move white point in Lab
711
712    cmsLab2XYZ(&bchsw ->WPsrc,  &XYZ, &LabOut);
713    cmsXYZ2Lab(&bchsw ->WPdest, &LabOut, &XYZ);
714
715    // Back to encoded
716
717    cmsFloat2LabEncoded(Out, &LabOut);
718
719    return TRUE;
720}
721
722
723// Creates an abstract profile operating in Lab space for Brightness,
724// contrast, Saturation and white point displacement
725
726cmsHPROFILE CMSEXPORT cmsCreateBCHSWabstractProfileTHR(cmsContext ContextID,
727    int nLUTPoints,
728    cmsFloat64Number Bright,
729    cmsFloat64Number Contrast,
730    cmsFloat64Number Hue,
731    cmsFloat64Number Saturation,
732    int TempSrc,
733    int TempDest)
734{
735    cmsHPROFILE hICC;
736    cmsPipeline* Pipeline;
737    BCHSWADJUSTS bchsw;
738    cmsCIExyY WhitePnt;
739    cmsStage* CLUT;
740    cmsUInt32Number Dimensions[MAX_INPUT_DIMENSIONS];
741    int i;
742
743    bchsw.Brightness = Bright;
744    bchsw.Contrast   = Contrast;
745    bchsw.Hue        = Hue;
746    bchsw.Saturation = Saturation;
747
748    cmsWhitePointFromTemp(&WhitePnt, TempSrc );
749    cmsxyY2XYZ(&bchsw.WPsrc, &WhitePnt);
750
751    cmsWhitePointFromTemp(&WhitePnt, TempDest);
752    cmsxyY2XYZ(&bchsw.WPdest, &WhitePnt);
753
754    hICC = cmsCreateProfilePlaceholder(ContextID);
755    if (!hICC)                          // can't allocate
756        return NULL;
757
758
759    cmsSetDeviceClass(hICC,      cmsSigAbstractClass);
760    cmsSetColorSpace(hICC,       cmsSigLabData);
761    cmsSetPCS(hICC,              cmsSigLabData);
762
763    cmsSetHeaderRenderingIntent(hICC,  INTENT_PERCEPTUAL);
764
765    // Creates a Pipeline with 3D grid only
766    Pipeline = cmsPipelineAlloc(ContextID, 3, 3);
767    if (Pipeline == NULL) {
768        cmsCloseProfile(hICC);
769        return NULL;
770    }
771
772    for (i=0; i < MAX_INPUT_DIMENSIONS; i++) Dimensions[i] = nLUTPoints;
773    CLUT = cmsStageAllocCLut16bitGranular(ContextID, Dimensions, 3, 3, NULL);
774    if (CLUT == NULL) return NULL;
775
776
777    if (!cmsStageSampleCLut16bit(CLUT, bchswSampler, (void*) &bchsw, 0)) {
778
779        // Shouldn't reach here
780        goto Error;
781    }
782
783    if (!cmsPipelineInsertStage(Pipeline, cmsAT_END, CLUT)) {
784        goto Error;
785    }
786
787    // Create tags
788    if (!SetTextTags(hICC, L"BCHS built-in")) return NULL;
789
790    cmsWriteTag(hICC, cmsSigMediaWhitePointTag, (void*) cmsD50_XYZ());
791
792    cmsWriteTag(hICC, cmsSigAToB0Tag, (void*) Pipeline);
793
794    // Pipeline is already on virtual profile
795    cmsPipelineFree(Pipeline);
796
797    // Ok, done
798    return hICC;
799
800Error:
801    cmsPipelineFree(Pipeline);
802    cmsCloseProfile(hICC);
803    return NULL;
804}
805
806
807CMSAPI cmsHPROFILE   CMSEXPORT cmsCreateBCHSWabstractProfile(int nLUTPoints,
808                                                             cmsFloat64Number Bright,
809                                                             cmsFloat64Number Contrast,
810                                                             cmsFloat64Number Hue,
811                                                             cmsFloat64Number Saturation,
812                                                             int TempSrc,
813                                                             int TempDest)
814{
815    return cmsCreateBCHSWabstractProfileTHR(NULL, nLUTPoints, Bright, Contrast, Hue, Saturation, TempSrc, TempDest);
816}
817
818
819// Creates a fake NULL profile. This profile return 1 channel as always 0.
820// Is useful only for gamut checking tricks
821cmsHPROFILE CMSEXPORT cmsCreateNULLProfileTHR(cmsContext ContextID)
822{
823    cmsHPROFILE hProfile;
824    cmsPipeline* LUT = NULL;
825    cmsStage* PostLin;
826    cmsToneCurve* EmptyTab;
827    cmsUInt16Number Zero[2] = { 0, 0 };
828
829    hProfile = cmsCreateProfilePlaceholder(ContextID);
830    if (!hProfile)                          // can't allocate
831        return NULL;
832
833    cmsSetProfileVersion(hProfile, 4.3);
834
835    if (!SetTextTags(hProfile, L"NULL profile built-in")) goto Error;
836
837
838
839    cmsSetDeviceClass(hProfile, cmsSigOutputClass);
840    cmsSetColorSpace(hProfile,  cmsSigGrayData);
841    cmsSetPCS(hProfile,         cmsSigLabData);
842
843    // An empty LUTs is all we need
844    LUT = cmsPipelineAlloc(ContextID, 1, 1);
845    if (LUT == NULL) goto Error;
846
847    EmptyTab = cmsBuildTabulatedToneCurve16(ContextID, 2, Zero);
848    PostLin = cmsStageAllocToneCurves(ContextID, 1, &EmptyTab);
849    cmsFreeToneCurve(EmptyTab);
850
851    if (!cmsPipelineInsertStage(LUT, cmsAT_END, PostLin))
852        goto Error;
853
854    if (!cmsWriteTag(hProfile, cmsSigBToA0Tag, (void*) LUT)) goto Error;
855    if (!cmsWriteTag(hProfile, cmsSigMediaWhitePointTag, cmsD50_XYZ())) goto Error;
856
857    cmsPipelineFree(LUT);
858    return hProfile;
859
860Error:
861
862    if (LUT != NULL)
863        cmsPipelineFree(LUT);
864
865    if (hProfile != NULL)
866        cmsCloseProfile(hProfile);
867
868    return NULL;
869}
870
871cmsHPROFILE CMSEXPORT cmsCreateNULLProfile(void)
872{
873    return cmsCreateNULLProfileTHR(NULL);
874}
875
876
877static
878int IsPCS(cmsColorSpaceSignature ColorSpace)
879{
880    return (ColorSpace == cmsSigXYZData ||
881            ColorSpace == cmsSigLabData);
882}
883
884
885static
886void FixColorSpaces(cmsHPROFILE hProfile,
887                              cmsColorSpaceSignature ColorSpace,
888                              cmsColorSpaceSignature PCS,
889                              cmsUInt32Number dwFlags)
890{
891    if (dwFlags & cmsFLAGS_GUESSDEVICECLASS) {
892
893            if (IsPCS(ColorSpace) && IsPCS(PCS)) {
894
895                    cmsSetDeviceClass(hProfile,      cmsSigAbstractClass);
896                    cmsSetColorSpace(hProfile,       ColorSpace);
897                    cmsSetPCS(hProfile,              PCS);
898                    return;
899            }
900
901            if (IsPCS(ColorSpace) && !IsPCS(PCS)) {
902
903                    cmsSetDeviceClass(hProfile, cmsSigOutputClass);
904                    cmsSetPCS(hProfile,         ColorSpace);
905                    cmsSetColorSpace(hProfile,  PCS);
906                    return;
907            }
908
909            if (IsPCS(PCS) && !IsPCS(ColorSpace)) {
910
911                   cmsSetDeviceClass(hProfile,  cmsSigInputClass);
912                   cmsSetColorSpace(hProfile,   ColorSpace);
913                   cmsSetPCS(hProfile,          PCS);
914                   return;
915            }
916    }
917
918    cmsSetDeviceClass(hProfile,      cmsSigLinkClass);
919    cmsSetColorSpace(hProfile,       ColorSpace);
920    cmsSetPCS(hProfile,              PCS);
921}
922
923
924
925// This function creates a named color profile dumping all the contents of transform to a single profile
926// In this way, LittleCMS may be used to "group" several named color databases into a single profile.
927// It has, however, several minor limitations. PCS is always Lab, which is not very critic since this
928// is the normal PCS for named color profiles.
929static
930cmsHPROFILE CreateNamedColorDevicelink(cmsHTRANSFORM xform)
931{
932    _cmsTRANSFORM* v = (_cmsTRANSFORM*) xform;
933    cmsHPROFILE hICC = NULL;
934    int i, nColors;
935    cmsNAMEDCOLORLIST *nc2 = NULL, *Original = NULL;
936
937    // Create an empty placeholder
938    hICC = cmsCreateProfilePlaceholder(v->ContextID);
939    if (hICC == NULL) return NULL;
940
941    // Critical information
942    cmsSetDeviceClass(hICC, cmsSigNamedColorClass);
943    cmsSetColorSpace(hICC, v ->ExitColorSpace);
944    cmsSetPCS(hICC, cmsSigLabData);
945
946    // Tag profile with information
947    if (!SetTextTags(hICC, L"Named color devicelink")) goto Error;
948
949    Original = cmsGetNamedColorList(xform);
950    if (Original == NULL) goto Error;
951
952    nColors = cmsNamedColorCount(Original);
953    nc2     = cmsDupNamedColorList(Original);
954    if (nc2 == NULL) goto Error;
955
956    // Colorant count now depends on the output space
957    nc2 ->ColorantCount = cmsPipelineOutputChannels(v ->Lut);
958
959    // Make sure we have proper formatters
960    cmsChangeBuffersFormat(xform, TYPE_NAMED_COLOR_INDEX,
961        FLOAT_SH(0) | COLORSPACE_SH(_cmsLCMScolorSpace(v ->ExitColorSpace))
962        | BYTES_SH(2) | CHANNELS_SH(cmsChannelsOf(v ->ExitColorSpace)));
963
964    // Apply the transfor to colorants.
965    for (i=0; i < nColors; i++) {
966        cmsDoTransform(xform, &i, nc2 ->List[i].DeviceColorant, 1);
967    }
968
969    if (!cmsWriteTag(hICC, cmsSigNamedColor2Tag, (void*) nc2)) goto Error;
970    cmsFreeNamedColorList(nc2);
971
972    return hICC;
973
974Error:
975    if (hICC != NULL) cmsCloseProfile(hICC);
976    return NULL;
977}
978
979
980// This structure holds information about which MPU can be stored on a profile based on the version
981
982typedef struct {
983    cmsBool              IsV4;             // Is a V4 tag?
984    cmsTagSignature      RequiredTag;      // Set to 0 for both types
985    cmsTagTypeSignature  LutType;          // The LUT type
986    int                  nTypes;           // Number of types (up to 5)
987    cmsStageSignature    MpeTypes[5];      // 5 is the maximum number
988
989} cmsAllowedLUT;
990
991static const cmsAllowedLUT AllowedLUTTypes[] = {
992
993    { FALSE, 0,              cmsSigLut16Type,    4,  { cmsSigMatrixElemType,   cmsSigCurveSetElemType, cmsSigCLutElemType, cmsSigCurveSetElemType}},
994    { FALSE, 0,              cmsSigLut16Type,    3,  { cmsSigCurveSetElemType, cmsSigCLutElemType, cmsSigCurveSetElemType}},
995    { FALSE, 0,              cmsSigLut16Type,    2,  { cmsSigCurveSetElemType, cmsSigCLutElemType}},
996    { TRUE , 0,              cmsSigLutAtoBType,  1,  { cmsSigCurveSetElemType }},
997    { TRUE , cmsSigAToB0Tag, cmsSigLutAtoBType,  3,  { cmsSigCurveSetElemType, cmsSigMatrixElemType, cmsSigCurveSetElemType } },
998    { TRUE , cmsSigAToB0Tag, cmsSigLutAtoBType,  3,  { cmsSigCurveSetElemType, cmsSigCLutElemType, cmsSigCurveSetElemType   } },
999    { TRUE , cmsSigAToB0Tag, cmsSigLutAtoBType,  5,  { cmsSigCurveSetElemType, cmsSigCLutElemType, cmsSigCurveSetElemType, cmsSigMatrixElemType, cmsSigCurveSetElemType }},
1000    { TRUE , cmsSigBToA0Tag, cmsSigLutBtoAType,  1,  { cmsSigCurveSetElemType }},
1001    { TRUE , cmsSigBToA0Tag, cmsSigLutBtoAType,  3,  { cmsSigCurveSetElemType, cmsSigMatrixElemType, cmsSigCurveSetElemType }},
1002    { TRUE , cmsSigBToA0Tag, cmsSigLutBtoAType,  3,  { cmsSigCurveSetElemType, cmsSigCLutElemType, cmsSigCurveSetElemType }},
1003    { TRUE , cmsSigBToA0Tag, cmsSigLutBtoAType,  5,  { cmsSigCurveSetElemType, cmsSigMatrixElemType, cmsSigCurveSetElemType, cmsSigCLutElemType, cmsSigCurveSetElemType }}
1004};
1005
1006#define SIZE_OF_ALLOWED_LUT (sizeof(AllowedLUTTypes)/sizeof(cmsAllowedLUT))
1007
1008// Check a single entry
1009static
1010cmsBool CheckOne(const cmsAllowedLUT* Tab, const cmsPipeline* Lut)
1011{
1012    cmsStage* mpe;
1013    int n;
1014
1015    for (n=0, mpe = Lut ->Elements; mpe != NULL; mpe = mpe ->Next, n++) {
1016
1017        if (n > Tab ->nTypes) return FALSE;
1018        if (cmsStageType(mpe) != Tab ->MpeTypes[n]) return FALSE;
1019    }
1020
1021    return (n == Tab ->nTypes);
1022}
1023
1024
1025static
1026const cmsAllowedLUT* FindCombination(const cmsPipeline* Lut, cmsBool IsV4, cmsTagSignature DestinationTag)
1027{
1028    cmsUInt32Number n;
1029
1030    for (n=0; n < SIZE_OF_ALLOWED_LUT; n++) {
1031
1032        const cmsAllowedLUT* Tab = AllowedLUTTypes + n;
1033
1034        if (IsV4 ^ Tab -> IsV4) continue;
1035        if ((Tab ->RequiredTag != 0) && (Tab ->RequiredTag != DestinationTag)) continue;
1036
1037        if (CheckOne(Tab, Lut)) return Tab;
1038    }
1039
1040    return NULL;
1041}
1042
1043
1044// Does convert a transform into a device link profile
1045cmsHPROFILE CMSEXPORT cmsTransform2DeviceLink(cmsHTRANSFORM hTransform, cmsFloat64Number Version, cmsUInt32Number dwFlags)
1046{
1047    cmsHPROFILE hProfile = NULL;
1048    cmsUInt32Number FrmIn, FrmOut, ChansIn, ChansOut;
1049    cmsUInt32Number ColorSpaceBitsIn, ColorSpaceBitsOut;
1050    _cmsTRANSFORM* xform = (_cmsTRANSFORM*) hTransform;
1051    cmsPipeline* LUT = NULL;
1052    cmsStage* mpe;
1053    cmsContext ContextID = cmsGetTransformContextID(hTransform);
1054    const cmsAllowedLUT* AllowedLUT;
1055    cmsTagSignature DestinationTag;
1056    cmsProfileClassSignature deviceClass;
1057
1058    _cmsAssert(hTransform != NULL);
1059
1060    // Get the first mpe to check for named color
1061    mpe = cmsPipelineGetPtrToFirstStage(xform ->Lut);
1062
1063    // Check if is a named color transform
1064    if (mpe != NULL) {
1065
1066        if (cmsStageType(mpe) == cmsSigNamedColorElemType) {
1067            return CreateNamedColorDevicelink(hTransform);
1068        }
1069    }
1070
1071    // First thing to do is to get a copy of the transformation
1072    LUT = cmsPipelineDup(xform ->Lut);
1073    if (LUT == NULL) return NULL;
1074
1075    // Time to fix the Lab2/Lab4 issue.
1076    if ((xform ->EntryColorSpace == cmsSigLabData) && (Version < 4.0)) {
1077
1078        if (!cmsPipelineInsertStage(LUT, cmsAT_BEGIN, _cmsStageAllocLabV2ToV4curves(ContextID)))
1079            goto Error;
1080    }
1081
1082    // On the output side too
1083    if ((xform ->ExitColorSpace) == cmsSigLabData && (Version < 4.0)) {
1084
1085        if (!cmsPipelineInsertStage(LUT, cmsAT_END, _cmsStageAllocLabV4ToV2(ContextID)))
1086            goto Error;
1087    }
1088
1089
1090    hProfile = cmsCreateProfilePlaceholder(ContextID);
1091    if (!hProfile) goto Error;                    // can't allocate
1092
1093    cmsSetProfileVersion(hProfile, Version);
1094
1095    FixColorSpaces(hProfile, xform -> EntryColorSpace, xform -> ExitColorSpace, dwFlags);
1096
1097    // Optimize the LUT and precalculate a devicelink
1098
1099    ChansIn  = cmsChannelsOf(xform -> EntryColorSpace);
1100    ChansOut = cmsChannelsOf(xform -> ExitColorSpace);
1101
1102    ColorSpaceBitsIn  = _cmsLCMScolorSpace(xform -> EntryColorSpace);
1103    ColorSpaceBitsOut = _cmsLCMScolorSpace(xform -> ExitColorSpace);
1104
1105    FrmIn  = COLORSPACE_SH(ColorSpaceBitsIn) | CHANNELS_SH(ChansIn)|BYTES_SH(2);
1106    FrmOut = COLORSPACE_SH(ColorSpaceBitsOut) | CHANNELS_SH(ChansOut)|BYTES_SH(2);
1107
1108    deviceClass = cmsGetDeviceClass(hProfile);
1109
1110     if (deviceClass == cmsSigOutputClass)
1111         DestinationTag = cmsSigBToA0Tag;
1112     else
1113         DestinationTag = cmsSigAToB0Tag;
1114
1115    // Check if the profile/version can store the result
1116    if (dwFlags & cmsFLAGS_FORCE_CLUT)
1117        AllowedLUT = NULL;
1118    else
1119        AllowedLUT = FindCombination(LUT, Version >= 4.0, DestinationTag);
1120
1121    if (AllowedLUT == NULL) {
1122
1123        // Try to optimize
1124        _cmsOptimizePipeline(ContextID, &LUT, xform ->RenderingIntent, &FrmIn, &FrmOut, &dwFlags);
1125        AllowedLUT = FindCombination(LUT, Version >= 4.0, DestinationTag);
1126
1127    }
1128
1129    // If no way, then force CLUT that for sure can be written
1130    if (AllowedLUT == NULL) {
1131
1132        dwFlags |= cmsFLAGS_FORCE_CLUT;
1133        _cmsOptimizePipeline(ContextID, &LUT, xform ->RenderingIntent, &FrmIn, &FrmOut, &dwFlags);
1134
1135        // Put identity curves if needed
1136        if (cmsPipelineGetPtrToFirstStage(LUT) ->Type != cmsSigCurveSetElemType)
1137             if (!cmsPipelineInsertStage(LUT, cmsAT_BEGIN, _cmsStageAllocIdentityCurves(ContextID, ChansIn)))
1138                 goto Error;
1139
1140        if (cmsPipelineGetPtrToLastStage(LUT) ->Type != cmsSigCurveSetElemType)
1141             if (!cmsPipelineInsertStage(LUT, cmsAT_END,   _cmsStageAllocIdentityCurves(ContextID, ChansOut)))
1142                 goto Error;
1143
1144        AllowedLUT = FindCombination(LUT, Version >= 4.0, DestinationTag);
1145    }
1146
1147    // Somethings is wrong...
1148    if (AllowedLUT == NULL) {
1149        goto Error;
1150    }
1151
1152
1153    if (dwFlags & cmsFLAGS_8BITS_DEVICELINK)
1154                     cmsPipelineSetSaveAs8bitsFlag(LUT, TRUE);
1155
1156    // Tag profile with information
1157    if (!SetTextTags(hProfile, L"devicelink")) goto Error;
1158
1159    // Store result
1160    if (!cmsWriteTag(hProfile, DestinationTag, LUT)) goto Error;
1161
1162
1163    if (xform -> InputColorant != NULL) {
1164           if (!cmsWriteTag(hProfile, cmsSigColorantTableTag, xform->InputColorant)) goto Error;
1165    }
1166
1167    if (xform -> OutputColorant != NULL) {
1168           if (!cmsWriteTag(hProfile, cmsSigColorantTableOutTag, xform->OutputColorant)) goto Error;
1169    }
1170
1171    if ((deviceClass == cmsSigLinkClass) && (xform ->Sequence != NULL)) {
1172        if (!_cmsWriteProfileSequence(hProfile, xform ->Sequence)) goto Error;
1173    }
1174
1175    // Set the white point
1176    if (deviceClass == cmsSigInputClass) {
1177        if (!cmsWriteTag(hProfile, cmsSigMediaWhitePointTag, &xform ->EntryWhitePoint)) goto Error;
1178    }
1179    else {
1180         if (!cmsWriteTag(hProfile, cmsSigMediaWhitePointTag, &xform ->ExitWhitePoint)) goto Error;
1181    }
1182
1183
1184    // Per 7.2.15 in spec 4.3
1185    cmsSetHeaderRenderingIntent(hProfile, xform ->RenderingIntent);
1186
1187    cmsPipelineFree(LUT);
1188    return hProfile;
1189
1190Error:
1191    if (LUT != NULL) cmsPipelineFree(LUT);
1192    cmsCloseProfile(hProfile);
1193    return NULL;
1194}
1195