Coverage Report

Created: 2026-10-28 07:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/Little-CMS/src/cmsvirt.c
Line
Count
Source
1
//---------------------------------------------------------------------------------
2
//
3
//  Little Color Management System
4
//  Copyright (c) 1998-2026 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
33
cmsBool SetTextTags(cmsHPROFILE hProfile, const wchar_t* Description)
34
0
{
35
0
    cmsMLU *DescriptionMLU, *CopyrightMLU;
36
0
    cmsBool  rc = FALSE;
37
0
    cmsContext ContextID = cmsGetProfileContextID(hProfile);
38
39
0
    DescriptionMLU  = cmsMLUalloc(ContextID, 1);
40
0
    CopyrightMLU    = cmsMLUalloc(ContextID, 1);
41
42
0
    if (DescriptionMLU == NULL || CopyrightMLU == NULL) goto Error;
43
44
0
    if (!cmsMLUsetWide(DescriptionMLU,  "en", "US", Description)) goto Error;
45
0
    if (!cmsMLUsetWide(CopyrightMLU,    "en", "US", L"No copyright, use freely")) goto Error;
46
47
0
    if (!cmsWriteTag(hProfile, cmsSigProfileDescriptionTag,  DescriptionMLU)) goto Error;
48
0
    if (!cmsWriteTag(hProfile, cmsSigCopyrightTag,           CopyrightMLU)) goto Error;
49
50
0
    rc = TRUE;
51
52
0
Error:
53
54
0
    if (DescriptionMLU)
55
0
        cmsMLUfree(DescriptionMLU);
56
0
    if (CopyrightMLU)
57
0
        cmsMLUfree(CopyrightMLU);
58
0
    return rc;
59
0
}
60
61
62
static
63
cmsBool  SetSeqDescTag(cmsHPROFILE hProfile, const char* Model)
64
0
{
65
0
    cmsBool  rc = FALSE;
66
0
    cmsContext ContextID = cmsGetProfileContextID(hProfile);
67
0
    cmsSEQ* Seq = cmsAllocProfileSequenceDescription(ContextID, 1);
68
69
0
    if (Seq == NULL) return FALSE;
70
71
0
    Seq->seq[0].deviceMfg = (cmsSignature) 0;
72
0
    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
0
    Seq->seq[0].attributes = 0;
79
0
#endif
80
81
0
    Seq->seq[0].technology = (cmsTechnologySignature) 0;
82
83
0
    cmsMLUsetASCII( Seq->seq[0].Manufacturer, cmsNoLanguage, cmsNoCountry, "Little CMS");
84
0
    cmsMLUsetASCII( Seq->seq[0].Model,        cmsNoLanguage, cmsNoCountry, Model);
85
86
0
    if (!_cmsWriteProfileSequence(hProfile, Seq)) goto Error;
87
88
0
    rc = TRUE;
89
90
0
Error:
91
0
    if (Seq)
92
0
        cmsFreeProfileSequenceDescription(Seq);
93
94
0
    return rc;
95
0
}
96
97
98
99
// This function creates a profile based on White point, primaries and
100
// transfer functions.
101
cmsHPROFILE CMSEXPORT cmsCreateRGBProfileTHR(cmsContext ContextID,
102
                                          const cmsCIExyY* WhitePoint,
103
                                          const cmsCIExyYTRIPLE* Primaries,
104
                                          cmsToneCurve* const TransferFunction[3])
105
0
{
106
0
    cmsHPROFILE hICC;
107
0
    cmsMAT3 MColorants;
108
0
    cmsCIEXYZTRIPLE Colorants;
109
0
    cmsCIExyY MaxWhite;
110
0
    cmsMAT3 CHAD;
111
0
    cmsCIEXYZ WhitePointXYZ;
112
113
0
    hICC = cmsCreateProfilePlaceholder(ContextID);
114
0
    if (!hICC)                          // can't allocate
115
0
        return NULL;
116
117
0
    cmsSetProfileVersion(hICC, 4.4);
118
119
0
    cmsSetDeviceClass(hICC,      cmsSigDisplayClass);
120
0
    cmsSetColorSpace(hICC,       cmsSigRgbData);
121
0
    cmsSetPCS(hICC,              cmsSigXYZData);
122
123
0
    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
0
    if (!SetTextTags(hICC, L"RGB built-in")) goto Error;
142
143
0
    if (WhitePoint) {
144
145
0
        if (!cmsWriteTag(hICC, cmsSigMediaWhitePointTag, cmsD50_XYZ())) goto Error;
146
147
0
        cmsxyY2XYZ(&WhitePointXYZ, WhitePoint);
148
0
        _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
0
        if (!cmsWriteTag(hICC, cmsSigChromaticAdaptationTag, (void*) &CHAD)) goto Error;
152
0
    }
153
154
0
    if (WhitePoint && Primaries) {
155
156
0
        MaxWhite.x =  WhitePoint -> x;
157
0
        MaxWhite.y =  WhitePoint -> y;
158
0
        MaxWhite.Y =  1.0;
159
160
0
        if (!_cmsBuildRGB2XYZtransferMatrix(&MColorants, &MaxWhite, Primaries)) goto Error;
161
162
0
        Colorants.Red.X   = MColorants.v[0].n[0];
163
0
        Colorants.Red.Y   = MColorants.v[1].n[0];
164
0
        Colorants.Red.Z   = MColorants.v[2].n[0];
165
166
0
        Colorants.Green.X = MColorants.v[0].n[1];
167
0
        Colorants.Green.Y = MColorants.v[1].n[1];
168
0
        Colorants.Green.Z = MColorants.v[2].n[1];
169
170
0
        Colorants.Blue.X  = MColorants.v[0].n[2];
171
0
        Colorants.Blue.Y  = MColorants.v[1].n[2];
172
0
        Colorants.Blue.Z  = MColorants.v[2].n[2];
173
174
0
        if (!cmsWriteTag(hICC, cmsSigRedColorantTag,   (void*) &Colorants.Red)) goto Error;
175
0
        if (!cmsWriteTag(hICC, cmsSigBlueColorantTag,  (void*) &Colorants.Blue)) goto Error;
176
0
        if (!cmsWriteTag(hICC, cmsSigGreenColorantTag, (void*) &Colorants.Green)) goto Error;
177
0
    }
178
179
180
0
    if (TransferFunction) {
181
182
        // Tries to minimize space. Thanks to Richard Hughes for this nice idea         
183
0
        if (!cmsWriteTag(hICC, cmsSigRedTRCTag,   (void*) TransferFunction[0])) goto Error;
184
185
0
        if (TransferFunction[1] == TransferFunction[0]) {
186
187
0
            if (!cmsLinkTag (hICC, cmsSigGreenTRCTag, cmsSigRedTRCTag)) goto Error;
188
189
0
        } else {
190
191
0
            if (!cmsWriteTag(hICC, cmsSigGreenTRCTag, (void*) TransferFunction[1])) goto Error;
192
0
        }
193
194
0
        if (TransferFunction[2] == TransferFunction[0]) {
195
196
0
            if (!cmsLinkTag (hICC, cmsSigBlueTRCTag, cmsSigRedTRCTag)) goto Error;
197
198
0
        } else {
199
200
0
            if (!cmsWriteTag(hICC, cmsSigBlueTRCTag, (void*) TransferFunction[2])) goto Error;
201
0
        }
202
0
    }
203
204
0
    if (Primaries) {
205
0
        if (!cmsWriteTag(hICC, cmsSigChromaticityTag, (void*) Primaries)) goto Error;
206
0
    }
207
208
209
0
    return hICC;
210
211
0
Error:
212
0
    if (hICC)
213
0
        cmsCloseProfile(hICC);
214
0
    return NULL;
215
0
}
216
217
cmsHPROFILE CMSEXPORT cmsCreateRGBProfile(const cmsCIExyY* WhitePoint,
218
                                          const cmsCIExyYTRIPLE* Primaries,
219
                                          cmsToneCurve* const TransferFunction[3])
220
0
{
221
0
    return cmsCreateRGBProfileTHR(NULL, WhitePoint, Primaries, TransferFunction);
222
0
}
223
224
225
226
// This function creates a profile based on White point and transfer function.
227
cmsHPROFILE CMSEXPORT cmsCreateGrayProfileTHR(cmsContext ContextID,
228
                                           const cmsCIExyY* WhitePoint,
229
                                           const cmsToneCurve* TransferFunction)
230
0
{
231
0
    cmsHPROFILE hICC;
232
0
    cmsCIEXYZ tmp;
233
234
0
    hICC = cmsCreateProfilePlaceholder(ContextID);
235
0
    if (!hICC)                          // can't allocate
236
0
        return NULL;
237
238
0
    cmsSetProfileVersion(hICC, 4.4);
239
240
0
    cmsSetDeviceClass(hICC,      cmsSigDisplayClass);
241
0
    cmsSetColorSpace(hICC,       cmsSigGrayData);
242
0
    cmsSetPCS(hICC,              cmsSigXYZData);
243
0
    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
0
    if (!SetTextTags(hICC, L"gray built-in")) goto Error;
257
258
259
0
    if (WhitePoint) {
260
261
0
        cmsxyY2XYZ(&tmp, WhitePoint);
262
0
        if (!cmsWriteTag(hICC, cmsSigMediaWhitePointTag, (void*) &tmp)) goto Error;
263
0
    }
264
265
0
    if (TransferFunction) {
266
267
0
        if (!cmsWriteTag(hICC, cmsSigGrayTRCTag, (void*) TransferFunction)) goto Error;
268
0
    }
269
270
0
    return hICC;
271
272
0
Error:
273
0
    if (hICC)
274
0
        cmsCloseProfile(hICC);
275
0
    return NULL;
276
0
}
277
278
279
280
cmsHPROFILE CMSEXPORT cmsCreateGrayProfile(const cmsCIExyY* WhitePoint,
281
                                                    const cmsToneCurve* TransferFunction)
282
0
{
283
0
    return cmsCreateGrayProfileTHR(NULL, WhitePoint, TransferFunction);
284
0
}
285
286
// This is a devicelink operating in the target colorspace with as many transfer functions as components
287
288
cmsHPROFILE CMSEXPORT cmsCreateLinearizationDeviceLinkTHR(cmsContext ContextID,
289
                                                          cmsColorSpaceSignature ColorSpace,
290
                                                          cmsToneCurve* const TransferFunctions[])
291
0
{
292
0
    cmsHPROFILE hICC;
293
0
    cmsPipeline* Pipeline;
294
0
    cmsInt32Number nChannels;
295
296
0
    hICC = cmsCreateProfilePlaceholder(ContextID);
297
0
    if (!hICC)
298
0
        return NULL;
299
300
0
    cmsSetProfileVersion(hICC, 4.4);
301
302
0
    cmsSetDeviceClass(hICC,      cmsSigLinkClass);
303
0
    cmsSetColorSpace(hICC,       ColorSpace);
304
0
    cmsSetPCS(hICC,              ColorSpace);
305
306
0
    cmsSetHeaderRenderingIntent(hICC,  INTENT_PERCEPTUAL);
307
308
    // Set up channels
309
0
    nChannels = cmsChannelsOfColorSpace(ColorSpace);
310
311
    // Creates a Pipeline with prelinearization step only
312
0
    Pipeline = cmsPipelineAlloc(ContextID, nChannels, nChannels);
313
0
    if (Pipeline == NULL) goto Error;
314
315
316
    // Copy tables to Pipeline
317
0
    if (!cmsPipelineInsertStage(Pipeline, cmsAT_BEGIN, cmsStageAllocToneCurves(ContextID, nChannels, TransferFunctions)))
318
0
        goto Error;
319
320
    // Create tags
321
0
    if (!SetTextTags(hICC, L"Linearization built-in")) goto Error;
322
0
    if (!cmsWriteTag(hICC, cmsSigAToB0Tag, (void*) Pipeline)) goto Error;
323
0
    if (!SetSeqDescTag(hICC, "Linearization built-in")) goto Error;
324
325
    // Pipeline is already on virtual profile
326
0
    cmsPipelineFree(Pipeline);
327
328
    // Ok, done
329
0
    return hICC;
330
331
0
Error:
332
0
    cmsPipelineFree(Pipeline);
333
0
    if (hICC)
334
0
        cmsCloseProfile(hICC);
335
336
337
0
    return NULL;
338
0
}
339
340
cmsHPROFILE CMSEXPORT cmsCreateLinearizationDeviceLink(cmsColorSpaceSignature ColorSpace,
341
                                                                 cmsToneCurve* const TransferFunctions[])
342
0
{
343
0
    return cmsCreateLinearizationDeviceLinkTHR(NULL, ColorSpace, TransferFunctions);
344
0
}
345
346
// Ink-limiting algorithm
347
//
348
//  Sum = C + M + Y + K
349
//  If Sum > InkLimit
350
//        Ratio= 1 - (Sum - InkLimit) / (C + M + Y)
351
//        if Ratio <0
352
//              Ratio=0
353
//        endif
354
//     Else
355
//         Ratio=1
356
//     endif
357
//
358
//     C = Ratio * C
359
//     M = Ratio * M
360
//     Y = Ratio * Y
361
//     K: Does not change
362
363
static
364
int InkLimitingSampler(CMSREGISTER const cmsUInt16Number In[], CMSREGISTER cmsUInt16Number Out[], CMSREGISTER void* Cargo)
365
0
{
366
0
    cmsFloat64Number InkLimit = *(cmsFloat64Number *) Cargo;
367
0
    cmsFloat64Number SumCMY, SumCMYK, Ratio;
368
369
0
    InkLimit = (InkLimit * 655.35);
370
371
0
    SumCMY   = (cmsFloat64Number) In[0]  + In[1] + In[2];
372
0
    SumCMYK  = SumCMY + In[3];
373
374
0
    if (SumCMYK > InkLimit && SumCMY > 0) {
375
376
0
        Ratio = 1 - ((SumCMYK - InkLimit) / SumCMY);
377
0
        if (Ratio < 0)
378
0
            Ratio = 0;
379
0
    }
380
0
    else Ratio = 1;
381
382
0
    Out[0] = _cmsQuickSaturateWord(In[0] * Ratio);     // C
383
0
    Out[1] = _cmsQuickSaturateWord(In[1] * Ratio);     // M
384
0
    Out[2] = _cmsQuickSaturateWord(In[2] * Ratio);     // Y
385
386
0
    Out[3] = In[3];                                    // K (untouched)
387
388
0
    return TRUE;
389
0
}
390
391
// This is a devicelink operating in CMYK for ink-limiting
392
393
cmsHPROFILE CMSEXPORT cmsCreateInkLimitingDeviceLinkTHR(cmsContext ContextID,
394
                                                     cmsColorSpaceSignature ColorSpace,
395
                                                     cmsFloat64Number Limit)
396
0
{
397
0
    cmsHPROFILE hICC;
398
0
    cmsPipeline* LUT;
399
0
    cmsStage* CLUT;
400
0
    cmsInt32Number nChannels;
401
402
0
    if (ColorSpace != cmsSigCmykData) {
403
0
        cmsSignalError(ContextID, cmsERROR_COLORSPACE_CHECK, "InkLimiting: Only CMYK currently supported");
404
0
        return NULL;
405
0
    }
406
407
0
    if (Limit < 1.0 || Limit > 400) {
408
409
0
        cmsSignalError(ContextID, cmsERROR_RANGE, "InkLimiting: Limit should be between 1..400");
410
0
        if (Limit < 1) Limit = 1;
411
0
        if (Limit > 400) Limit = 400;
412
0
    }
413
414
0
    hICC = cmsCreateProfilePlaceholder(ContextID);
415
0
    if (!hICC)                          // can't allocate
416
0
        return NULL;
417
418
0
    cmsSetProfileVersion(hICC, 4.4);
419
420
0
    cmsSetDeviceClass(hICC,      cmsSigLinkClass);
421
0
    cmsSetColorSpace(hICC,       ColorSpace);
422
0
    cmsSetPCS(hICC,              ColorSpace);
423
424
0
    cmsSetHeaderRenderingIntent(hICC,  INTENT_PERCEPTUAL);
425
426
427
    // Creates a Pipeline with 3D grid only
428
0
    LUT = cmsPipelineAlloc(ContextID, 4, 4);
429
0
    if (LUT == NULL) goto Error;
430
431
432
0
    nChannels = cmsChannelsOf(ColorSpace);
433
434
0
    CLUT = cmsStageAllocCLut16bit(ContextID, 17, nChannels, nChannels, NULL);
435
0
    if (CLUT == NULL) goto Error;
436
437
0
    if (!cmsStageSampleCLut16bit(CLUT, InkLimitingSampler, (void*) &Limit, 0)) goto Error;
438
439
0
    if (!cmsPipelineInsertStage(LUT, cmsAT_BEGIN, _cmsStageAllocIdentityCurves(ContextID, nChannels)) ||
440
0
        !cmsPipelineInsertStage(LUT, cmsAT_END, CLUT) ||
441
0
        !cmsPipelineInsertStage(LUT, cmsAT_END, _cmsStageAllocIdentityCurves(ContextID, nChannels)))
442
0
        goto Error;
443
444
    // Create tags
445
0
    if (!SetTextTags(hICC, L"ink-limiting built-in")) goto Error;
446
447
0
    if (!cmsWriteTag(hICC, cmsSigAToB0Tag, (void*) LUT))  goto Error;
448
0
    if (!SetSeqDescTag(hICC, "ink-limiting built-in")) goto Error;
449
450
    // cmsPipeline is already on virtual profile
451
0
    cmsPipelineFree(LUT);
452
453
    // Ok, done
454
0
    return hICC;
455
456
0
Error:
457
0
    if (LUT != NULL)
458
0
        cmsPipelineFree(LUT);
459
460
0
    if (hICC != NULL)
461
0
        cmsCloseProfile(hICC);
462
463
0
    return NULL;
464
0
}
465
466
cmsHPROFILE CMSEXPORT cmsCreateInkLimitingDeviceLink(cmsColorSpaceSignature ColorSpace, cmsFloat64Number Limit)
467
0
{
468
0
    return cmsCreateInkLimitingDeviceLinkTHR(NULL, ColorSpace, Limit);
469
0
}
470
471
472
// Creates a fake Lab identity.
473
cmsHPROFILE CMSEXPORT cmsCreateLab2ProfileTHR(cmsContext ContextID, const cmsCIExyY* WhitePoint)
474
0
{
475
0
    cmsHPROFILE hProfile;
476
0
    cmsPipeline* LUT = NULL;
477
0
    cmsCIEXYZ xyz;
478
479
0
    if (WhitePoint == NULL)
480
0
        xyz = *cmsD50_XYZ();
481
0
    else
482
0
        cmsxyY2XYZ(&xyz, WhitePoint);
483
484
0
    hProfile = cmsCreateRGBProfileTHR(ContextID, NULL, NULL, NULL);
485
0
    if (hProfile == NULL) return NULL;
486
487
0
    cmsSetProfileVersion(hProfile, 2.1);
488
489
0
    cmsSetDeviceClass(hProfile, cmsSigAbstractClass);
490
0
    cmsSetColorSpace(hProfile,  cmsSigLabData);
491
0
    cmsSetPCS(hProfile,         cmsSigLabData);
492
493
0
    if (!cmsWriteTag(hProfile, cmsSigMediaWhitePointTag, &xyz)) goto Error;
494
0
    if (!SetTextTags(hProfile, L"Lab identity built-in")) goto Error;
495
496
    // An identity LUT is all we need
497
0
    LUT = cmsPipelineAlloc(ContextID, 3, 3);
498
0
    if (LUT == NULL) 
499
0
        goto Error;
500
501
0
    if (!cmsPipelineInsertStage(LUT, cmsAT_BEGIN, _cmsStageAllocIdentityCLut(ContextID, 3)))
502
0
        goto Error;
503
504
0
    if (!cmsWriteTag(hProfile, cmsSigAToB0Tag, LUT)) goto Error;
505
0
    cmsPipelineFree(LUT);
506
507
0
    return hProfile;
508
509
0
Error:
510
511
0
    if (LUT != NULL)
512
0
        cmsPipelineFree(LUT);
513
514
0
    if (hProfile != NULL)
515
0
        cmsCloseProfile(hProfile);
516
517
0
    return NULL;
518
0
}
519
520
521
cmsHPROFILE CMSEXPORT cmsCreateLab2Profile(const cmsCIExyY* WhitePoint)
522
0
{
523
0
    return cmsCreateLab2ProfileTHR(NULL, WhitePoint);
524
0
}
525
526
527
// Creates a fake Lab V4 identity.
528
cmsHPROFILE CMSEXPORT cmsCreateLab4ProfileTHR(cmsContext ContextID, const cmsCIExyY* WhitePoint)
529
0
{
530
0
    cmsHPROFILE hProfile;
531
0
    cmsPipeline* LUT = NULL;
532
0
    cmsCIEXYZ xyz;
533
    
534
0
    if (WhitePoint == NULL)
535
0
        xyz = *cmsD50_XYZ();
536
0
    else
537
0
        cmsxyY2XYZ(&xyz, WhitePoint);
538
539
0
    hProfile = cmsCreateRGBProfileTHR(ContextID, NULL, NULL, NULL);
540
0
    if (hProfile == NULL) return NULL;
541
542
0
    cmsSetProfileVersion(hProfile, 4.4);
543
544
0
    cmsSetDeviceClass(hProfile, cmsSigAbstractClass);
545
0
    cmsSetColorSpace(hProfile,  cmsSigLabData);
546
0
    cmsSetPCS(hProfile,         cmsSigLabData);
547
548
0
    if (!cmsWriteTag(hProfile, cmsSigMediaWhitePointTag, &xyz)) goto Error;
549
0
    if (!SetTextTags(hProfile, L"Lab identity built-in")) goto Error;
550
551
    // An empty LUTs is all we need
552
0
    LUT = cmsPipelineAlloc(ContextID, 3, 3);
553
0
    if (LUT == NULL) goto Error;
554
555
0
    if (!cmsPipelineInsertStage(LUT, cmsAT_BEGIN, _cmsStageAllocIdentityCurves(ContextID, 3)))
556
0
        goto Error;
557
558
0
    if (!cmsWriteTag(hProfile, cmsSigAToB0Tag, LUT)) goto Error;
559
0
    cmsPipelineFree(LUT);
560
561
0
    return hProfile;
562
563
0
Error:
564
565
0
    if (LUT != NULL)
566
0
        cmsPipelineFree(LUT);
567
568
0
    if (hProfile != NULL)
569
0
        cmsCloseProfile(hProfile);
570
571
0
    return NULL;
572
0
}
573
574
cmsHPROFILE CMSEXPORT cmsCreateLab4Profile(const cmsCIExyY* WhitePoint)
575
0
{
576
0
    return cmsCreateLab4ProfileTHR(NULL, WhitePoint);
577
0
}
578
579
580
// Creates a fake XYZ identity
581
cmsHPROFILE CMSEXPORT cmsCreateXYZProfileTHR(cmsContext ContextID)
582
0
{
583
0
    cmsHPROFILE hProfile;
584
0
    cmsPipeline* LUT = NULL;
585
586
0
    hProfile = cmsCreateRGBProfileTHR(ContextID, cmsD50_xyY(), NULL, NULL);
587
0
    if (hProfile == NULL) return NULL;
588
589
0
    cmsSetProfileVersion(hProfile, 4.4);
590
591
0
    cmsSetDeviceClass(hProfile, cmsSigAbstractClass);
592
0
    cmsSetColorSpace(hProfile,  cmsSigXYZData);
593
0
    cmsSetPCS(hProfile,         cmsSigXYZData);
594
595
0
    if (!SetTextTags(hProfile, L"XYZ identity built-in")) goto Error;
596
597
    // An identity LUT is all we need
598
0
    LUT = cmsPipelineAlloc(ContextID, 3, 3);
599
0
    if (LUT == NULL) goto Error;
600
601
0
    if (!cmsPipelineInsertStage(LUT, cmsAT_BEGIN, _cmsStageAllocIdentityCurves(ContextID, 3)))
602
0
        goto Error;
603
604
0
    if (!cmsWriteTag(hProfile, cmsSigAToB0Tag, LUT)) goto Error;
605
0
    cmsPipelineFree(LUT);
606
607
0
    return hProfile;
608
609
0
Error:
610
611
0
    if (LUT != NULL)
612
0
        cmsPipelineFree(LUT);
613
614
0
    if (hProfile != NULL)
615
0
        cmsCloseProfile(hProfile);
616
617
0
    return NULL;
618
0
}
619
620
621
cmsHPROFILE CMSEXPORT cmsCreateXYZProfile(void)
622
0
{
623
0
    return cmsCreateXYZProfileTHR(NULL);
624
0
}
625
626
627
//sRGB Curves are defined by:
628
//
629
//If  R'sRGB,G'sRGB, B'sRGB < 0.04045
630
//
631
//    R =  R'sRGB / 12.92
632
//    G =  G'sRGB / 12.92
633
//    B =  B'sRGB / 12.92
634
//
635
//
636
//else if  R'sRGB,G'sRGB, B'sRGB >= 0.04045
637
//
638
//    R = ((R'sRGB + 0.055) / 1.055)^2.4
639
//    G = ((G'sRGB + 0.055) / 1.055)^2.4
640
//    B = ((B'sRGB + 0.055) / 1.055)^2.4
641
642
static
643
cmsToneCurve* Build_sRGBGamma(cmsContext ContextID)
644
0
{
645
0
    cmsFloat64Number Parameters[5];
646
647
0
    Parameters[0] = 2.4;
648
0
    Parameters[1] = 1. / 1.055;
649
0
    Parameters[2] = 0.055 / 1.055;
650
0
    Parameters[3] = 1. / 12.92;
651
0
    Parameters[4] = 0.04045;
652
653
0
    return cmsBuildParametricToneCurve(ContextID, 4, Parameters);
654
0
}
655
656
// Create the ICC virtual profile for sRGB space
657
cmsHPROFILE CMSEXPORT cmsCreate_sRGBProfileTHR(cmsContext ContextID)
658
0
{
659
0
       cmsCIExyY       D65 = { 0.3127, 0.3290, 1.0 };
660
0
       cmsCIExyYTRIPLE Rec709Primaries = {
661
0
                                   {0.6400, 0.3300, 1.0},
662
0
                                   {0.3000, 0.6000, 1.0},
663
0
                                   {0.1500, 0.0600, 1.0}
664
0
                                   };
665
0
       cmsToneCurve* Gamma22[3];
666
0
       cmsHPROFILE  hsRGB;
667
668
      // cmsWhitePointFromTemp(&D65, 6504);
669
0
       Gamma22[0] = Gamma22[1] = Gamma22[2] = Build_sRGBGamma(ContextID);
670
0
       if (Gamma22[0] == NULL) return NULL;
671
672
0
       hsRGB = cmsCreateRGBProfileTHR(ContextID, &D65, &Rec709Primaries, Gamma22);
673
0
       cmsFreeToneCurve(Gamma22[0]);
674
0
       if (hsRGB == NULL) return NULL;
675
676
0
       if (!SetTextTags(hsRGB, L"sRGB built-in")) {
677
0
           cmsCloseProfile(hsRGB);
678
0
           return NULL;
679
0
       }
680
681
0
       return hsRGB;
682
0
}
683
684
cmsHPROFILE CMSEXPORT cmsCreate_sRGBProfile(void)
685
0
{
686
0
    return cmsCreate_sRGBProfileTHR(NULL);
687
0
}
688
689
/**
690
* Oklab colorspace profile (experimental)
691
* 
692
* This virtual profile cannot be saved as an ICC file
693
*/
694
cmsHPROFILE CMSEXPORT cmsCreate_OkLabProfile(cmsContext ctx)
695
0
{
696
0
    cmsStage* XYZPCS = _cmsStageNormalizeFromXyzFloat(ctx);
697
0
    cmsStage* PCSXYZ = _cmsStageNormalizeToXyzFloat(ctx);
698
699
0
    const double M_D65_D50[] =
700
0
    {
701
0
       1.047886, 0.022919, -0.050216,
702
0
       0.029582, 0.990484, -0.017079,
703
0
      -0.009252, 0.015073,  0.751678
704
0
    };
705
706
0
    const double M_D50_D65[] =
707
0
    {
708
0
         0.955512609517083, -0.023073214184645,  0.063308961782107,
709
0
        -0.028324949364887,  1.009942432477107,  0.021054814890112,
710
0
         0.012328875695483, -0.020535835374141,  1.330713916450354
711
0
    };
712
713
0
    cmsStage* D65toD50 = cmsStageAllocMatrix(ctx, 3, 3, M_D65_D50, NULL);
714
0
    cmsStage* D50toD65 = cmsStageAllocMatrix(ctx, 3, 3, M_D50_D65, NULL);
715
716
0
    const double M_D65_LMS[] =
717
0
    {
718
0
        0.8189330101, 0.3618667424, -0.1288597137,
719
0
        0.0329845436, 0.9293118715,  0.0361456387,
720
0
        0.0482003018, 0.2643662691,  0.6338517070
721
0
    };
722
   
723
0
    const double M_LMS_D65[] =
724
0
    {
725
0
        1.227013851103521, -0.557799980651822,  0.281256148966468,
726
0
       -0.040580178423281,  1.112256869616830, -0.071676678665601,
727
0
       -0.076381284505707, -0.421481978418013,  1.586163220440795
728
0
    };
729
730
0
    cmsStage* D65toLMS = cmsStageAllocMatrix(ctx, 3, 3, M_D65_LMS, NULL);
731
0
    cmsStage* LMStoD65 = cmsStageAllocMatrix(ctx, 3, 3, M_LMS_D65, NULL);
732
733
0
    cmsToneCurve* CubeRoot = cmsBuildGamma(ctx, 1.0 / 3.0);
734
0
    cmsToneCurve* Cube     = cmsBuildGamma(ctx,  3.0);
735
736
0
    cmsToneCurve* Roots[3] = { CubeRoot, CubeRoot, CubeRoot };
737
0
    cmsToneCurve* Cubes[3] = { Cube, Cube, Cube };
738
739
0
    cmsStage* NonLinearityFw = cmsStageAllocToneCurves(ctx, 3, Roots);
740
0
    cmsStage* NonLinearityRv = cmsStageAllocToneCurves(ctx, 3, Cubes);
741
742
0
    const double M_LMSprime_OkLab[] =
743
0
    {
744
0
        0.2104542553,  0.7936177850, -0.0040720468,
745
0
        1.9779984951, -2.4285922050,  0.4505937099,
746
0
        0.0259040371,  0.7827717662, -0.8086757660
747
0
    };
748
749
0
    const double M_OkLab_LMSprime[] =
750
0
    {
751
0
        0.999999998450520,  0.396337792173768,  0.215803758060759,
752
0
        1.000000008881761, -0.105561342323656, -0.063854174771706,
753
0
        1.000000054672411, -0.089484182094966, -1.291485537864092
754
0
    };
755
    
756
0
    cmsStage* LMSprime_OkLab = cmsStageAllocMatrix(ctx, 3, 3, M_LMSprime_OkLab, NULL);
757
0
    cmsStage* OkLab_LMSprime = cmsStageAllocMatrix(ctx, 3, 3, M_OkLab_LMSprime, NULL);
758
759
0
    cmsPipeline* AToB = cmsPipelineAlloc(ctx, 3, 3);
760
0
    cmsPipeline* BToA = cmsPipelineAlloc(ctx, 3, 3);
761
762
0
    cmsHPROFILE hProfile = cmsCreateProfilePlaceholder(ctx);
763
0
    if (!hProfile)            // can't allocate
764
0
        goto error;
765
  
766
0
    cmsSetProfileVersion(hProfile, 4.4);
767
768
0
    cmsSetDeviceClass(hProfile, cmsSigColorSpaceClass);
769
0
    cmsSetColorSpace(hProfile, cmsSig3colorData);
770
0
    cmsSetPCS(hProfile, cmsSigXYZData);
771
772
0
    cmsSetHeaderRenderingIntent(hProfile, INTENT_RELATIVE_COLORIMETRIC);
773
774
    /**
775
    * Conversion PCS (XYZ/D50) to OkLab 
776
    */
777
0
    if (!cmsPipelineInsertStage(BToA, cmsAT_END, PCSXYZ)) goto error;
778
0
    if (!cmsPipelineInsertStage(BToA, cmsAT_END, D50toD65)) goto error;
779
0
    if (!cmsPipelineInsertStage(BToA, cmsAT_END, D65toLMS)) goto error;
780
0
    if (!cmsPipelineInsertStage(BToA, cmsAT_END, NonLinearityFw)) goto error;
781
0
    if (!cmsPipelineInsertStage(BToA, cmsAT_END, LMSprime_OkLab)) goto error;
782
783
0
    if (!cmsWriteTag(hProfile, cmsSigBToA0Tag, BToA)) goto error;
784
    
785
0
    if (!cmsPipelineInsertStage(AToB, cmsAT_END, OkLab_LMSprime)) goto error;
786
0
    if (!cmsPipelineInsertStage(AToB, cmsAT_END, NonLinearityRv)) goto error;
787
0
    if (!cmsPipelineInsertStage(AToB, cmsAT_END, LMStoD65)) goto error;
788
0
    if (!cmsPipelineInsertStage(AToB, cmsAT_END, D65toD50)) goto error;
789
0
    if (!cmsPipelineInsertStage(AToB, cmsAT_END, XYZPCS)) goto error;
790
791
0
    if (!cmsWriteTag(hProfile, cmsSigAToB0Tag, AToB)) goto error;
792
793
0
    cmsPipelineFree(BToA);
794
0
    cmsPipelineFree(AToB);
795
796
0
    cmsFreeToneCurve(CubeRoot);
797
0
    cmsFreeToneCurve(Cube);
798
799
0
    return hProfile;
800
801
0
error:
802
0
    cmsPipelineFree(BToA);
803
0
    cmsPipelineFree(AToB);
804
805
0
    cmsFreeToneCurve(CubeRoot);
806
0
    cmsFreeToneCurve(Cube);
807
0
    cmsCloseProfile(hProfile);
808
809
0
    return NULL;
810
811
0
}
812
813
814
typedef struct {
815
                cmsFloat64Number Brightness;
816
                cmsFloat64Number Contrast;
817
                cmsFloat64Number Hue;
818
                cmsFloat64Number Saturation;
819
                cmsBool          lAdjustWP;
820
                cmsCIEXYZ WPsrc, WPdest;
821
822
} BCHSWADJUSTS, *LPBCHSWADJUSTS;
823
824
825
static
826
int bchswSampler(CMSREGISTER const cmsUInt16Number In[], CMSREGISTER cmsUInt16Number Out[], CMSREGISTER void* Cargo)
827
0
{
828
0
    cmsCIELab LabIn, LabOut;
829
0
    cmsCIELCh LChIn, LChOut;
830
0
    cmsCIEXYZ XYZ;
831
0
    LPBCHSWADJUSTS bchsw = (LPBCHSWADJUSTS) Cargo;
832
833
834
0
    cmsLabEncoded2Float(&LabIn, In);
835
836
837
0
    cmsLab2LCh(&LChIn, &LabIn);
838
839
    // Do some adjusts on LCh
840
841
0
    LChOut.L = LChIn.L * bchsw ->Contrast + bchsw ->Brightness;
842
0
    LChOut.C = LChIn.C + bchsw -> Saturation;
843
0
    LChOut.h = LChIn.h + bchsw -> Hue;
844
845
846
0
    cmsLCh2Lab(&LabOut, &LChOut);
847
848
    // Move white point in Lab
849
0
    if (bchsw->lAdjustWP) {
850
0
           cmsLab2XYZ(&bchsw->WPsrc, &XYZ, &LabOut);
851
0
           cmsXYZ2Lab(&bchsw->WPdest, &LabOut, &XYZ);
852
0
    }
853
854
    // Back to encoded
855
856
0
    cmsFloat2LabEncoded(Out, &LabOut);
857
858
0
    return TRUE;
859
0
}
860
861
862
// Creates an abstract profile operating in Lab space for Brightness,
863
// contrast, Saturation and white point displacement
864
865
cmsHPROFILE CMSEXPORT cmsCreateBCHSWabstractProfileTHR(cmsContext ContextID,
866
                                                       cmsUInt32Number nLUTPoints,
867
                                                       cmsFloat64Number Bright,
868
                                                       cmsFloat64Number Contrast,
869
                                                       cmsFloat64Number Hue,
870
                                                       cmsFloat64Number Saturation,
871
                                                       cmsUInt32Number TempSrc,
872
                                                       cmsUInt32Number TempDest)
873
0
{
874
0
    cmsHPROFILE hICC;
875
0
    cmsPipeline* Pipeline;
876
0
    BCHSWADJUSTS bchsw;
877
0
    cmsCIExyY WhitePnt;
878
0
    cmsStage* CLUT;
879
0
    cmsUInt32Number Dimensions[MAX_INPUT_DIMENSIONS];
880
0
    cmsUInt32Number i;
881
882
0
    bchsw.Brightness = Bright;
883
0
    bchsw.Contrast   = Contrast;
884
0
    bchsw.Hue        = Hue;
885
0
    bchsw.Saturation = Saturation;
886
0
    if (TempSrc == TempDest) {
887
888
0
           bchsw.lAdjustWP = FALSE;
889
0
    }
890
0
    else {
891
0
           bchsw.lAdjustWP = TRUE;
892
0
           cmsWhitePointFromTemp(&WhitePnt, TempSrc);
893
0
           cmsxyY2XYZ(&bchsw.WPsrc, &WhitePnt);
894
0
           cmsWhitePointFromTemp(&WhitePnt, TempDest);
895
0
           cmsxyY2XYZ(&bchsw.WPdest, &WhitePnt);
896
     
897
0
    }
898
899
0
    hICC = cmsCreateProfilePlaceholder(ContextID);
900
0
    if (!hICC)                          // can't allocate
901
0
        return NULL;
902
903
0
    cmsSetDeviceClass(hICC,      cmsSigAbstractClass);
904
0
    cmsSetColorSpace(hICC,       cmsSigLabData);
905
0
    cmsSetPCS(hICC,              cmsSigLabData);
906
907
0
    cmsSetHeaderRenderingIntent(hICC,  INTENT_PERCEPTUAL);
908
909
    // Creates a Pipeline with 3D grid only
910
0
    Pipeline = cmsPipelineAlloc(ContextID, 3, 3);
911
0
    if (Pipeline == NULL) {
912
0
        cmsCloseProfile(hICC);
913
0
        return NULL;
914
0
    }
915
916
0
    for (i=0; i < MAX_INPUT_DIMENSIONS; i++) Dimensions[i] = nLUTPoints;
917
0
    CLUT = cmsStageAllocCLut16bitGranular(ContextID, Dimensions, 3, 3, NULL);
918
0
    if (CLUT == NULL) 
919
0
        goto Error;
920
921
0
    if (!cmsStageSampleCLut16bit(CLUT, bchswSampler, (void*) &bchsw, 0)) 
922
0
        goto Error;
923
    
924
0
    if (!cmsPipelineInsertStage(Pipeline, cmsAT_END, CLUT)) 
925
0
        goto Error;
926
    
927
    // Create tags
928
0
    if (!SetTextTags(hICC, L"BCHS built-in")) 
929
0
        goto Error;
930
931
0
    if (!cmsWriteTag(hICC, cmsSigMediaWhitePointTag, (void*)cmsD50_XYZ()))
932
0
        goto Error;
933
934
0
    if (!cmsWriteTag(hICC, cmsSigAToB0Tag, (void*)Pipeline))
935
0
        goto Error;
936
937
    // Pipeline is already on virtual profile
938
0
    cmsPipelineFree(Pipeline);
939
940
    // Ok, done
941
0
    return hICC;
942
943
0
Error:
944
0
    cmsPipelineFree(Pipeline);
945
0
    cmsCloseProfile(hICC);
946
0
    return NULL;
947
0
}
948
949
950
CMSAPI cmsHPROFILE   CMSEXPORT cmsCreateBCHSWabstractProfile(cmsUInt32Number nLUTPoints,
951
                                                             cmsFloat64Number Bright,
952
                                                             cmsFloat64Number Contrast,
953
                                                             cmsFloat64Number Hue,
954
                                                             cmsFloat64Number Saturation,
955
                                                             cmsUInt32Number TempSrc,
956
                                                             cmsUInt32Number TempDest)
957
0
{
958
0
    return cmsCreateBCHSWabstractProfileTHR(NULL, nLUTPoints, Bright, Contrast, Hue, Saturation, TempSrc, TempDest);
959
0
}
960
961
962
// Creates a fake NULL profile. This profile return 1 channel as always 0.
963
// Is useful only for gamut checking tricks
964
cmsHPROFILE CMSEXPORT cmsCreateNULLProfileTHR(cmsContext ContextID)
965
0
{
966
0
    cmsHPROFILE hProfile;
967
0
    cmsPipeline* LUT = NULL;
968
0
    cmsStage* PostLin;
969
0
    cmsStage* OutLin;
970
0
    cmsToneCurve* EmptyTab[3];
971
0
    cmsUInt16Number Zero[2] = { 0, 0 };
972
0
    const cmsFloat64Number PickLstarMatrix[] = { 1, 0, 0 };
973
974
0
    hProfile = cmsCreateProfilePlaceholder(ContextID);
975
0
    if (!hProfile)                          // can't allocate
976
0
        return NULL;
977
978
0
    cmsSetProfileVersion(hProfile, 4.4);
979
980
0
    if (!SetTextTags(hProfile, L"NULL profile built-in")) goto Error;
981
982
983
0
    cmsSetDeviceClass(hProfile, cmsSigOutputClass);
984
0
    cmsSetColorSpace(hProfile,  cmsSigGrayData);
985
0
    cmsSetPCS(hProfile,         cmsSigLabData);
986
987
    // Create a valid ICC 4 structure
988
0
    LUT = cmsPipelineAlloc(ContextID, 3, 1);
989
0
    if (LUT == NULL) goto Error;
990
    
991
0
    EmptyTab[0] = EmptyTab[1] = EmptyTab[2] = cmsBuildTabulatedToneCurve16(ContextID, 2, Zero);
992
0
    PostLin = cmsStageAllocToneCurves(ContextID, 3, EmptyTab);
993
0
    OutLin  = cmsStageAllocToneCurves(ContextID, 1, EmptyTab);
994
0
    cmsFreeToneCurve(EmptyTab[0]);
995
996
0
    if (!cmsPipelineInsertStage(LUT, cmsAT_END, PostLin))
997
0
        goto Error;
998
999
0
    if (!cmsPipelineInsertStage(LUT, cmsAT_END, cmsStageAllocMatrix(ContextID, 1, 3, PickLstarMatrix, NULL)))
1000
0
        goto Error;
1001
1002
0
    if (!cmsPipelineInsertStage(LUT, cmsAT_END, OutLin))
1003
0
        goto Error;
1004
1005
0
    if (!cmsWriteTag(hProfile, cmsSigBToA0Tag, (void*) LUT)) goto Error;
1006
0
    if (!cmsWriteTag(hProfile, cmsSigMediaWhitePointTag, cmsD50_XYZ())) goto Error;
1007
1008
0
    cmsPipelineFree(LUT);
1009
0
    return hProfile;
1010
1011
0
Error:
1012
1013
0
    if (LUT != NULL)
1014
0
        cmsPipelineFree(LUT);
1015
1016
0
    if (hProfile != NULL)
1017
0
        cmsCloseProfile(hProfile);
1018
1019
0
    return NULL;
1020
0
}
1021
1022
cmsHPROFILE CMSEXPORT cmsCreateNULLProfile(void)
1023
0
{
1024
0
    return cmsCreateNULLProfileTHR(NULL);
1025
0
}
1026
1027
1028
static
1029
int IsPCS(cmsColorSpaceSignature ColorSpace)
1030
0
{
1031
0
    return (ColorSpace == cmsSigXYZData ||
1032
0
            ColorSpace == cmsSigLabData);
1033
0
}
1034
1035
1036
static
1037
void FixColorSpaces(cmsHPROFILE hProfile,
1038
                              cmsColorSpaceSignature ColorSpace,
1039
                              cmsColorSpaceSignature PCS,
1040
                              cmsUInt32Number dwFlags)
1041
0
{
1042
0
    if (dwFlags & cmsFLAGS_GUESSDEVICECLASS) {
1043
1044
0
            if (IsPCS(ColorSpace) && IsPCS(PCS)) {
1045
1046
0
                    cmsSetDeviceClass(hProfile,      cmsSigAbstractClass);
1047
0
                    cmsSetColorSpace(hProfile,       ColorSpace);
1048
0
                    cmsSetPCS(hProfile,              PCS);
1049
0
                    return;
1050
0
            }
1051
1052
0
            if (IsPCS(ColorSpace) && !IsPCS(PCS)) {
1053
1054
0
                    cmsSetDeviceClass(hProfile, cmsSigOutputClass);
1055
0
                    cmsSetPCS(hProfile,         ColorSpace);
1056
0
                    cmsSetColorSpace(hProfile,  PCS);
1057
0
                    return;
1058
0
            }
1059
1060
0
            if (IsPCS(PCS) && !IsPCS(ColorSpace)) {
1061
1062
0
                   cmsSetDeviceClass(hProfile,  cmsSigInputClass);
1063
0
                   cmsSetColorSpace(hProfile,   ColorSpace);
1064
0
                   cmsSetPCS(hProfile,          PCS);
1065
0
                   return;
1066
0
            }
1067
0
    }
1068
1069
0
    cmsSetDeviceClass(hProfile,      cmsSigLinkClass);
1070
0
    cmsSetColorSpace(hProfile,       ColorSpace);
1071
0
    cmsSetPCS(hProfile,              PCS);
1072
0
}
1073
1074
1075
1076
// This function creates a named color profile dumping all the contents of transform to a single profile
1077
// In this way, LittleCMS may be used to "group" several named color databases into a single profile.
1078
// It has, however, several minor limitations. PCS is always Lab, which is not very critic since this
1079
// is the normal PCS for named color profiles.
1080
static
1081
cmsHPROFILE CreateNamedColorDevicelink(cmsHTRANSFORM xform)
1082
0
{
1083
0
    _cmsTRANSFORM* v = (_cmsTRANSFORM*) xform;
1084
0
    cmsHPROFILE hICC = NULL;
1085
0
    cmsUInt32Number i, nColors;
1086
0
    cmsNAMEDCOLORLIST *nc2 = NULL, *Original = NULL;
1087
1088
    // Create an empty placeholder
1089
0
    hICC = cmsCreateProfilePlaceholder(v->ContextID);
1090
0
    if (hICC == NULL) return NULL;
1091
1092
    // Critical information
1093
0
    cmsSetDeviceClass(hICC, cmsSigNamedColorClass);
1094
0
    cmsSetColorSpace(hICC, v ->ExitColorSpace);
1095
0
    cmsSetPCS(hICC, cmsSigLabData);
1096
1097
    // Tag profile with information
1098
0
    if (!SetTextTags(hICC, L"Named color devicelink")) goto Error;
1099
1100
0
    Original = cmsGetNamedColorList(xform);
1101
0
    if (Original == NULL) goto Error;
1102
1103
0
    nColors = cmsNamedColorCount(Original);
1104
0
    nc2     = cmsDupNamedColorList(Original);
1105
0
    if (nc2 == NULL) goto Error;
1106
1107
    // Colorant count now depends on the output space
1108
0
    nc2 ->ColorantCount = cmsPipelineOutputChannels(v ->Lut);
1109
1110
    // Make sure we have proper formatters
1111
0
    cmsChangeBuffersFormat(xform, TYPE_NAMED_COLOR_INDEX,
1112
0
        FLOAT_SH(0) | COLORSPACE_SH(_cmsLCMScolorSpace(v ->ExitColorSpace))
1113
0
        | BYTES_SH(2) | CHANNELS_SH(cmsChannelsOfColorSpace(v ->ExitColorSpace)));
1114
1115
    // Apply the transfor to colorants.
1116
0
    for (i=0; i < nColors; i++) {
1117
0
        cmsDoTransform(xform, &i, nc2 ->List[i].DeviceColorant, 1);
1118
0
    }
1119
1120
0
    if (!cmsWriteTag(hICC, cmsSigNamedColor2Tag, (void*) nc2)) goto Error;
1121
0
    cmsFreeNamedColorList(nc2);
1122
1123
0
    return hICC;
1124
1125
0
Error:
1126
0
    if (hICC != NULL) cmsCloseProfile(hICC);
1127
0
    return NULL;
1128
0
}
1129
1130
1131
// This structure holds information about which MPU can be stored on a profile based on the version
1132
1133
typedef struct {
1134
    cmsBool              IsV4;             // Is a V4 tag?
1135
    cmsTagSignature      RequiredTag;      // Set to 0 for both types
1136
    cmsTagTypeSignature  LutType;          // The LUT type
1137
    int                  nTypes;           // Number of types (up to 5)
1138
    cmsStageSignature    MpeTypes[5];      // 5 is the maximum number
1139
1140
} cmsAllowedLUT;
1141
1142
#define cmsSig0 ((cmsTagSignature) 0) 
1143
1144
static const cmsAllowedLUT AllowedLUTTypes[] = {
1145
1146
    { FALSE, cmsSig0,        cmsSigLut16Type, 4, { cmsSigMatrixElemType, cmsSigCurveSetElemType, cmsSigCLutElemType, cmsSigCurveSetElemType } },
1147
    { FALSE, cmsSig0,        cmsSigLut16Type, 3, { cmsSigCurveSetElemType, cmsSigCLutElemType, cmsSigCurveSetElemType } },
1148
    { FALSE, cmsSig0,        cmsSigLut16Type, 2, { cmsSigCurveSetElemType, cmsSigCLutElemType } },
1149
    { TRUE,  cmsSig0,        cmsSigLutAtoBType, 1, { cmsSigCurveSetElemType } },
1150
    { TRUE , cmsSigAToB0Tag, cmsSigLutAtoBType,  3,  { cmsSigCurveSetElemType, cmsSigMatrixElemType, cmsSigCurveSetElemType } },
1151
    { TRUE , cmsSigAToB0Tag, cmsSigLutAtoBType,  3,  { cmsSigCurveSetElemType, cmsSigCLutElemType, cmsSigCurveSetElemType   } },
1152
    { TRUE , cmsSigAToB0Tag, cmsSigLutAtoBType,  5,  { cmsSigCurveSetElemType, cmsSigCLutElemType, cmsSigCurveSetElemType, cmsSigMatrixElemType, cmsSigCurveSetElemType }},
1153
    { TRUE , cmsSigBToA0Tag, cmsSigLutBtoAType,  1,  { cmsSigCurveSetElemType }},
1154
    { TRUE , cmsSigBToA0Tag, cmsSigLutBtoAType,  3,  { cmsSigCurveSetElemType, cmsSigMatrixElemType, cmsSigCurveSetElemType }},
1155
    { TRUE , cmsSigBToA0Tag, cmsSigLutBtoAType,  3,  { cmsSigCurveSetElemType, cmsSigCLutElemType, cmsSigCurveSetElemType }},
1156
    { TRUE , cmsSigBToA0Tag, cmsSigLutBtoAType,  5,  { cmsSigCurveSetElemType, cmsSigMatrixElemType, cmsSigCurveSetElemType, cmsSigCLutElemType, cmsSigCurveSetElemType }}
1157
};
1158
1159
0
#define SIZE_OF_ALLOWED_LUT (sizeof(AllowedLUTTypes)/sizeof(cmsAllowedLUT))
1160
1161
// Check a single entry
1162
static
1163
cmsBool CheckOne(const cmsAllowedLUT* Tab, const cmsPipeline* Lut)
1164
0
{
1165
0
    cmsStage* mpe;
1166
0
    int n;
1167
1168
0
    for (n=0, mpe = Lut ->Elements; mpe != NULL; mpe = mpe ->Next, n++) {
1169
1170
0
        if (n >= Tab ->nTypes) return FALSE;
1171
0
        if (cmsStageType(mpe) != Tab ->MpeTypes[n]) return FALSE;
1172
0
    }
1173
1174
0
    return (n == Tab ->nTypes);
1175
0
}
1176
1177
1178
static
1179
const cmsAllowedLUT* FindCombination(const cmsPipeline* Lut, cmsBool IsV4, cmsTagSignature DestinationTag)
1180
0
{
1181
0
    cmsUInt32Number n;
1182
1183
0
    for (n=0; n < SIZE_OF_ALLOWED_LUT; n++) {
1184
1185
0
        const cmsAllowedLUT* Tab = AllowedLUTTypes + n;
1186
1187
0
        if (IsV4 ^ Tab -> IsV4) continue;
1188
0
        if ((Tab ->RequiredTag != 0) && (Tab ->RequiredTag != DestinationTag)) continue;
1189
1190
0
        if (CheckOne(Tab, Lut)) return Tab;
1191
0
    }
1192
1193
0
    return NULL;
1194
0
}
1195
1196
1197
// Does convert a transform into a device link profile
1198
cmsHPROFILE CMSEXPORT cmsTransform2DeviceLink(cmsHTRANSFORM hTransform, cmsFloat64Number Version, cmsUInt32Number dwFlags)
1199
0
{
1200
0
    cmsHPROFILE hProfile = NULL;
1201
0
    cmsUInt32Number FrmIn, FrmOut;
1202
0
    cmsInt32Number ChansIn, ChansOut;
1203
0
    int ColorSpaceBitsIn, ColorSpaceBitsOut;
1204
0
    _cmsTRANSFORM* xform = (_cmsTRANSFORM*) hTransform;
1205
0
    cmsPipeline* LUT = NULL;
1206
0
    cmsStage* mpe;
1207
0
    cmsContext ContextID = cmsGetTransformContextID(hTransform);
1208
0
    const cmsAllowedLUT* AllowedLUT;
1209
0
    cmsTagSignature DestinationTag;
1210
0
    cmsProfileClassSignature deviceClass; 
1211
1212
0
    _cmsAssert(hTransform != NULL);
1213
1214
    // Check if the pipeline holding is valid
1215
0
    if (xform -> Lut == NULL) return NULL;
1216
1217
    // Get the first mpe to check for named color
1218
0
    mpe = cmsPipelineGetPtrToFirstStage(xform ->Lut);
1219
1220
    // Check if is a named color transform
1221
0
    if (mpe != NULL) {
1222
1223
0
        if (cmsStageType(mpe) == cmsSigNamedColorElemType) {
1224
0
            return CreateNamedColorDevicelink(hTransform);
1225
0
        }
1226
0
    }
1227
1228
    // First thing to do is to get a copy of the transformation
1229
0
    LUT = cmsPipelineDup(xform ->Lut);
1230
0
    if (LUT == NULL) return NULL;
1231
1232
    // Time to fix the Lab2/Lab4 issue.
1233
0
    if ((xform ->EntryColorSpace == cmsSigLabData) && (Version < 4.0)) {
1234
1235
0
        if (!cmsPipelineInsertStage(LUT, cmsAT_BEGIN, _cmsStageAllocLabV2ToV4curves(ContextID)))
1236
0
            goto Error;
1237
0
    }
1238
1239
    // On the output side too. Note that due to V2/V4 PCS encoding on lab we cannot fix white misalignments
1240
0
    if ((xform ->ExitColorSpace) == cmsSigLabData && (Version < 4.0)) {
1241
1242
0
        dwFlags |= cmsFLAGS_NOWHITEONWHITEFIXUP;
1243
0
        if (!cmsPipelineInsertStage(LUT, cmsAT_END, _cmsStageAllocLabV4ToV2(ContextID)))
1244
0
            goto Error;
1245
0
    }
1246
1247
1248
0
    hProfile = cmsCreateProfilePlaceholder(ContextID);
1249
0
    if (!hProfile) goto Error;                    // can't allocate
1250
1251
0
    cmsSetProfileVersion(hProfile, Version);
1252
1253
0
    FixColorSpaces(hProfile, xform -> EntryColorSpace, xform -> ExitColorSpace, dwFlags);
1254
1255
    // Optimize the LUT and precalculate a devicelink
1256
1257
0
    ChansIn  = cmsChannelsOfColorSpace(xform -> EntryColorSpace);
1258
0
    ChansOut = cmsChannelsOfColorSpace(xform -> ExitColorSpace);
1259
1260
0
    ColorSpaceBitsIn  = _cmsLCMScolorSpace(xform -> EntryColorSpace);
1261
0
    ColorSpaceBitsOut = _cmsLCMScolorSpace(xform -> ExitColorSpace);
1262
1263
0
    FrmIn  = COLORSPACE_SH(ColorSpaceBitsIn) | CHANNELS_SH(ChansIn)|BYTES_SH(2);
1264
0
    FrmOut = COLORSPACE_SH(ColorSpaceBitsOut) | CHANNELS_SH(ChansOut)|BYTES_SH(2);
1265
1266
0
    deviceClass = cmsGetDeviceClass(hProfile);
1267
1268
0
     if (deviceClass == cmsSigOutputClass)
1269
0
         DestinationTag = cmsSigBToA0Tag;
1270
0
     else
1271
0
         DestinationTag = cmsSigAToB0Tag;
1272
1273
    // Check if the profile/version can store the result
1274
0
    if (dwFlags & cmsFLAGS_FORCE_CLUT)
1275
0
        AllowedLUT = NULL;
1276
0
    else
1277
0
        AllowedLUT = FindCombination(LUT, Version >= 4.0, DestinationTag);
1278
1279
0
    if (AllowedLUT == NULL) {
1280
1281
        // Try to optimize
1282
0
        _cmsOptimizePipeline(ContextID, &LUT, xform ->RenderingIntent, &FrmIn, &FrmOut, &dwFlags);
1283
0
        AllowedLUT = FindCombination(LUT, Version >= 4.0, DestinationTag);
1284
1285
0
    }
1286
1287
    // If no way, then force CLUT that for sure can be written
1288
0
    if (AllowedLUT == NULL) {
1289
1290
0
        cmsStage* FirstStage;
1291
0
        cmsStage* LastStage;
1292
1293
0
        dwFlags |= cmsFLAGS_FORCE_CLUT;
1294
0
        _cmsOptimizePipeline(ContextID, &LUT, xform ->RenderingIntent, &FrmIn, &FrmOut, &dwFlags);
1295
1296
        // Put identity curves if needed
1297
0
        FirstStage = cmsPipelineGetPtrToFirstStage(LUT);
1298
0
        if (FirstStage != NULL && FirstStage ->Type != cmsSigCurveSetElemType)
1299
0
             if (!cmsPipelineInsertStage(LUT, cmsAT_BEGIN, _cmsStageAllocIdentityCurves(ContextID, ChansIn)))
1300
0
                 goto Error;
1301
1302
0
        LastStage = cmsPipelineGetPtrToLastStage(LUT);
1303
0
        if (LastStage != NULL && LastStage ->Type != cmsSigCurveSetElemType)
1304
0
             if (!cmsPipelineInsertStage(LUT, cmsAT_END,   _cmsStageAllocIdentityCurves(ContextID, ChansOut)))
1305
0
                 goto Error;
1306
1307
0
        AllowedLUT = FindCombination(LUT, Version >= 4.0, DestinationTag);
1308
0
    }
1309
1310
    // Somethings is wrong...
1311
0
    if (AllowedLUT == NULL) {
1312
0
        goto Error;
1313
0
    }
1314
1315
1316
0
    if (dwFlags & cmsFLAGS_8BITS_DEVICELINK)
1317
0
                     cmsPipelineSetSaveAs8bitsFlag(LUT, TRUE);
1318
1319
    // Tag profile with information
1320
0
    if (!SetTextTags(hProfile, L"devicelink")) goto Error;
1321
1322
    // Store result
1323
0
    if (!cmsWriteTag(hProfile, DestinationTag, LUT)) goto Error;
1324
1325
1326
    // Colorant tables have special rules depening on deviceClass
1327
0
    if (xform -> InputColorant != NULL && 
1328
0
       (deviceClass == cmsSigLinkClass || deviceClass == cmsSigInputClass)) {
1329
1330
0
           if (!cmsWriteTag(hProfile, cmsSigColorantTableTag, xform->InputColorant)) goto Error;
1331
0
    }
1332
1333
0
    if (xform -> OutputColorant != NULL) {
1334
1335
0
        if (deviceClass == cmsSigLinkClass) {
1336
1337
0
            if (!cmsWriteTag(hProfile, cmsSigColorantTableOutTag, xform->OutputColorant)) goto Error;
1338
0
        }
1339
0
        else
1340
0
        {
1341
0
            if (!cmsWriteTag(hProfile, cmsSigColorantTableTag, xform->OutputColorant)) goto Error;
1342
0
        }
1343
0
    }
1344
1345
0
    if ((deviceClass == cmsSigLinkClass) && (xform ->Sequence != NULL)) {
1346
0
        if (!_cmsWriteProfileSequence(hProfile, xform ->Sequence)) goto Error;
1347
0
    }
1348
1349
    // Set the white point
1350
0
    if (deviceClass == cmsSigInputClass) {
1351
0
        if (!cmsWriteTag(hProfile, cmsSigMediaWhitePointTag, &xform ->EntryWhitePoint)) goto Error;
1352
0
    }
1353
0
    else {
1354
0
         if (!cmsWriteTag(hProfile, cmsSigMediaWhitePointTag, &xform ->ExitWhitePoint)) goto Error;
1355
0
    }
1356
1357
  
1358
    // Per 7.2.15 in spec 4.3
1359
0
    cmsSetHeaderRenderingIntent(hProfile, xform ->RenderingIntent);
1360
1361
0
    cmsPipelineFree(LUT);
1362
0
    return hProfile;
1363
1364
0
Error:
1365
0
    if (LUT != NULL) cmsPipelineFree(LUT);
1366
0
    cmsCloseProfile(hProfile);
1367
    return NULL;
1368
0
}