Coverage Report

Created: 2026-08-03 06:16

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