Coverage Report

Created: 2025-08-26 06:14

/src/lcms/src/cmsvirt.c
Line
Count
Source (jump to first uncovered line)
1
//---------------------------------------------------------------------------------
2
//
3
//  Little Color Management System
4
//  Copyright (c) 1998-2025 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
11.1k
{
35
11.1k
    cmsMLU *DescriptionMLU, *CopyrightMLU;
36
11.1k
    cmsBool  rc = FALSE;
37
11.1k
    cmsContext ContextID = cmsGetProfileContextID(hProfile);
38
39
11.1k
    DescriptionMLU  = cmsMLUalloc(ContextID, 1);
40
11.1k
    CopyrightMLU    = cmsMLUalloc(ContextID, 1);
41
42
11.1k
    if (DescriptionMLU == NULL || CopyrightMLU == NULL) goto Error;
43
44
11.1k
    if (!cmsMLUsetWide(DescriptionMLU,  "en", "US", Description)) goto Error;
45
11.1k
    if (!cmsMLUsetWide(CopyrightMLU,    "en", "US", L"No copyright, use freely")) goto Error;
46
47
11.1k
    if (!cmsWriteTag(hProfile, cmsSigProfileDescriptionTag,  DescriptionMLU)) goto Error;
48
11.1k
    if (!cmsWriteTag(hProfile, cmsSigCopyrightTag,           CopyrightMLU)) goto Error;
49
50
11.1k
    rc = TRUE;
51
52
11.1k
Error:
53
54
11.1k
    if (DescriptionMLU)
55
11.1k
        cmsMLUfree(DescriptionMLU);
56
11.1k
    if (CopyrightMLU)
57
11.1k
        cmsMLUfree(CopyrightMLU);
58
11.1k
    return rc;
59
11.1k
}
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
5.56k
{
106
5.56k
    cmsHPROFILE hICC;
107
5.56k
    cmsMAT3 MColorants;
108
5.56k
    cmsCIEXYZTRIPLE Colorants;
109
5.56k
    cmsCIExyY MaxWhite;
110
5.56k
    cmsMAT3 CHAD;
111
5.56k
    cmsCIEXYZ WhitePointXYZ;
112
113
5.56k
    hICC = cmsCreateProfilePlaceholder(ContextID);
114
5.56k
    if (!hICC)                          // can't allocate
115
0
        return NULL;
116
117
5.56k
    cmsSetProfileVersion(hICC, 4.4);
118
119
5.56k
    cmsSetDeviceClass(hICC,      cmsSigDisplayClass);
120
5.56k
    cmsSetColorSpace(hICC,       cmsSigRgbData);
121
5.56k
    cmsSetPCS(hICC,              cmsSigXYZData);
122
123
5.56k
    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
5.56k
    if (!SetTextTags(hICC, L"RGB built-in")) goto Error;
142
143
5.56k
    if (WhitePoint) {
144
145
5.56k
        if (!cmsWriteTag(hICC, cmsSigMediaWhitePointTag, cmsD50_XYZ())) goto Error;
146
147
5.56k
        cmsxyY2XYZ(&WhitePointXYZ, WhitePoint);
148
5.56k
        _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
5.56k
        if (!cmsWriteTag(hICC, cmsSigChromaticAdaptationTag, (void*) &CHAD)) goto Error;
152
5.56k
    }
153
154
5.56k
    if (WhitePoint && Primaries) {
155
156
4.13k
        MaxWhite.x =  WhitePoint -> x;
157
4.13k
        MaxWhite.y =  WhitePoint -> y;
158
4.13k
        MaxWhite.Y =  1.0;
159
160
4.13k
        if (!_cmsBuildRGB2XYZtransferMatrix(&MColorants, &MaxWhite, Primaries)) goto Error;
161
162
4.13k
        Colorants.Red.X   = MColorants.v[0].n[0];
163
4.13k
        Colorants.Red.Y   = MColorants.v[1].n[0];
164
4.13k
        Colorants.Red.Z   = MColorants.v[2].n[0];
165
166
4.13k
        Colorants.Green.X = MColorants.v[0].n[1];
167
4.13k
        Colorants.Green.Y = MColorants.v[1].n[1];
168
4.13k
        Colorants.Green.Z = MColorants.v[2].n[1];
169
170
4.13k
        Colorants.Blue.X  = MColorants.v[0].n[2];
171
4.13k
        Colorants.Blue.Y  = MColorants.v[1].n[2];
172
4.13k
        Colorants.Blue.Z  = MColorants.v[2].n[2];
173
174
4.13k
        if (!cmsWriteTag(hICC, cmsSigRedColorantTag,   (void*) &Colorants.Red)) goto Error;
175
4.13k
        if (!cmsWriteTag(hICC, cmsSigBlueColorantTag,  (void*) &Colorants.Blue)) goto Error;
176
4.13k
        if (!cmsWriteTag(hICC, cmsSigGreenColorantTag, (void*) &Colorants.Green)) goto Error;
177
4.13k
    }
178
179
180
5.56k
    if (TransferFunction) {
181
182
        // Tries to minimize space. Thanks to Richard Hughes for this nice idea         
183
4.13k
        if (!cmsWriteTag(hICC, cmsSigRedTRCTag,   (void*) TransferFunction[0])) goto Error;
184
185
4.13k
        if (TransferFunction[1] == TransferFunction[0]) {
186
187
4.13k
            if (!cmsLinkTag (hICC, cmsSigGreenTRCTag, cmsSigRedTRCTag)) goto Error;
188
189
4.13k
        } else {
190
191
0
            if (!cmsWriteTag(hICC, cmsSigGreenTRCTag, (void*) TransferFunction[1])) goto Error;
192
0
        }
193
194
4.13k
        if (TransferFunction[2] == TransferFunction[0]) {
195
196
4.13k
            if (!cmsLinkTag (hICC, cmsSigBlueTRCTag, cmsSigRedTRCTag)) goto Error;
197
198
4.13k
        } else {
199
200
0
            if (!cmsWriteTag(hICC, cmsSigBlueTRCTag, (void*) TransferFunction[2])) goto Error;
201
0
        }
202
4.13k
    }
203
204
5.56k
    if (Primaries) {
205
4.13k
        if (!cmsWriteTag(hICC, cmsSigChromaticityTag, (void*) Primaries)) goto Error;
206
4.13k
    }
207
208
209
5.56k
    return hICC;
210
211
0
Error:
212
0
    if (hICC)
213
0
        cmsCloseProfile(hICC);
214
0
    return NULL;
215
5.56k
}
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) {
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
1.43k
{
475
1.43k
    cmsHPROFILE hProfile;
476
1.43k
    cmsPipeline* LUT = NULL;
477
478
1.43k
    hProfile = cmsCreateRGBProfileTHR(ContextID, WhitePoint == NULL ? cmsD50_xyY() : WhitePoint, NULL, NULL);
479
1.43k
    if (hProfile == NULL) return NULL;
480
481
1.43k
    cmsSetProfileVersion(hProfile, 2.1);
482
483
1.43k
    cmsSetDeviceClass(hProfile, cmsSigAbstractClass);
484
1.43k
    cmsSetColorSpace(hProfile,  cmsSigLabData);
485
1.43k
    cmsSetPCS(hProfile,         cmsSigLabData);
486
487
1.43k
    if (!SetTextTags(hProfile, L"Lab identity built-in"))
488
0
        goto Error;
489
490
    // An identity LUT is all we need
491
1.43k
    LUT = cmsPipelineAlloc(ContextID, 3, 3);
492
1.43k
    if (LUT == NULL) 
493
0
        goto Error;
494
495
1.43k
    if (!cmsPipelineInsertStage(LUT, cmsAT_BEGIN, _cmsStageAllocIdentityCLut(ContextID, 3)))
496
0
        goto Error;
497
498
1.43k
    if (!cmsWriteTag(hProfile, cmsSigAToB0Tag, LUT)) 
499
0
        goto Error;
500
501
1.43k
    cmsPipelineFree(LUT);
502
503
1.43k
    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
1.43k
}
515
516
517
cmsHPROFILE CMSEXPORT cmsCreateLab2Profile(const cmsCIExyY* WhitePoint)
518
0
{
519
0
    return cmsCreateLab2ProfileTHR(NULL, WhitePoint);
520
0
}
521
522
523
// Creates a fake Lab V4 identity.
524
cmsHPROFILE CMSEXPORT cmsCreateLab4ProfileTHR(cmsContext ContextID, const cmsCIExyY* WhitePoint)
525
0
{
526
0
    cmsHPROFILE hProfile;
527
0
    cmsPipeline* LUT = NULL;
528
0
    cmsCIEXYZ xyz;
529
    
530
0
    if (WhitePoint == NULL)
531
0
        xyz = *cmsD50_XYZ();
532
0
    else
533
0
        cmsxyY2XYZ(&xyz, WhitePoint);
534
535
0
    hProfile = cmsCreateRGBProfileTHR(ContextID, NULL, NULL, NULL);
536
0
    if (hProfile == NULL) return NULL;
537
538
0
    cmsSetProfileVersion(hProfile, 4.4);
539
540
0
    cmsSetDeviceClass(hProfile, cmsSigAbstractClass);
541
0
    cmsSetColorSpace(hProfile,  cmsSigLabData);
542
0
    cmsSetPCS(hProfile,         cmsSigLabData);
543
544
0
    if (!cmsWriteTag(hProfile, cmsSigMediaWhitePointTag, &xyz)) goto Error;
545
0
    if (!SetTextTags(hProfile, L"Lab identity built-in")) goto Error;
546
547
    // An empty LUTs is all we need
548
0
    LUT = cmsPipelineAlloc(ContextID, 3, 3);
549
0
    if (LUT == NULL) goto Error;
550
551
0
    if (!cmsPipelineInsertStage(LUT, cmsAT_BEGIN, _cmsStageAllocIdentityCurves(ContextID, 3)))
552
0
        goto Error;
553
554
0
    if (!cmsWriteTag(hProfile, cmsSigAToB0Tag, LUT)) goto Error;
555
0
    cmsPipelineFree(LUT);
556
557
0
    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
0
}
569
570
cmsHPROFILE CMSEXPORT cmsCreateLab4Profile(const cmsCIExyY* WhitePoint)
571
0
{
572
0
    return cmsCreateLab4ProfileTHR(NULL, WhitePoint);
573
0
}
574
575
576
// Creates a fake XYZ identity
577
cmsHPROFILE CMSEXPORT cmsCreateXYZProfileTHR(cmsContext ContextID)
578
0
{
579
0
    cmsHPROFILE hProfile;
580
0
    cmsPipeline* LUT = NULL;
581
582
0
    hProfile = cmsCreateRGBProfileTHR(ContextID, cmsD50_xyY(), NULL, NULL);
583
0
    if (hProfile == NULL) return NULL;
584
585
0
    cmsSetProfileVersion(hProfile, 4.4);
586
587
0
    cmsSetDeviceClass(hProfile, cmsSigAbstractClass);
588
0
    cmsSetColorSpace(hProfile,  cmsSigXYZData);
589
0
    cmsSetPCS(hProfile,         cmsSigXYZData);
590
591
0
    if (!SetTextTags(hProfile, L"XYZ identity built-in")) goto Error;
592
593
    // An identity LUT is all we need
594
0
    LUT = cmsPipelineAlloc(ContextID, 3, 3);
595
0
    if (LUT == NULL) goto Error;
596
597
0
    if (!cmsPipelineInsertStage(LUT, cmsAT_BEGIN, _cmsStageAllocIdentityCurves(ContextID, 3)))
598
0
        goto Error;
599
600
0
    if (!cmsWriteTag(hProfile, cmsSigAToB0Tag, LUT)) goto Error;
601
0
    cmsPipelineFree(LUT);
602
603
0
    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
0
}
615
616
617
cmsHPROFILE CMSEXPORT cmsCreateXYZProfile(void)
618
0
{
619
0
    return cmsCreateXYZProfileTHR(NULL);
620
0
}
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
4.13k
{
641
4.13k
    cmsFloat64Number Parameters[5];
642
643
4.13k
    Parameters[0] = 2.4;
644
4.13k
    Parameters[1] = 1. / 1.055;
645
4.13k
    Parameters[2] = 0.055 / 1.055;
646
4.13k
    Parameters[3] = 1. / 12.92;
647
4.13k
    Parameters[4] = 0.04045;
648
649
4.13k
    return cmsBuildParametricToneCurve(ContextID, 4, Parameters);
650
4.13k
}
651
652
// Create the ICC virtual profile for sRGB space
653
cmsHPROFILE CMSEXPORT cmsCreate_sRGBProfileTHR(cmsContext ContextID)
654
4.13k
{
655
4.13k
       cmsCIExyY       D65 = { 0.3127, 0.3290, 1.0 };
656
4.13k
       cmsCIExyYTRIPLE Rec709Primaries = {
657
4.13k
                                   {0.6400, 0.3300, 1.0},
658
4.13k
                                   {0.3000, 0.6000, 1.0},
659
4.13k
                                   {0.1500, 0.0600, 1.0}
660
4.13k
                                   };
661
4.13k
       cmsToneCurve* Gamma22[3];
662
4.13k
       cmsHPROFILE  hsRGB;
663
664
      // cmsWhitePointFromTemp(&D65, 6504);
665
4.13k
       Gamma22[0] = Gamma22[1] = Gamma22[2] = Build_sRGBGamma(ContextID);
666
4.13k
       if (Gamma22[0] == NULL) return NULL;
667
668
4.13k
       hsRGB = cmsCreateRGBProfileTHR(ContextID, &D65, &Rec709Primaries, Gamma22);
669
4.13k
       cmsFreeToneCurve(Gamma22[0]);
670
4.13k
       if (hsRGB == NULL) return NULL;
671
672
4.13k
       if (!SetTextTags(hsRGB, L"sRGB built-in")) {
673
0
           cmsCloseProfile(hsRGB);
674
0
           return NULL;
675
0
       }
676
677
4.13k
       return hsRGB;
678
4.13k
}
679
680
cmsHPROFILE CMSEXPORT cmsCreate_sRGBProfile(void)
681
4.13k
{
682
4.13k
    return cmsCreate_sRGBProfileTHR(NULL);
683
4.13k
}
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
0
{
692
0
    cmsStage* XYZPCS = _cmsStageNormalizeFromXyzFloat(ctx);
693
0
    cmsStage* PCSXYZ = _cmsStageNormalizeToXyzFloat(ctx);
694
695
0
    const double M_D65_D50[] =
696
0
    {
697
0
       1.047886, 0.022919, -0.050216,
698
0
       0.029582, 0.990484, -0.017079,
699
0
      -0.009252, 0.015073,  0.751678
700
0
    };
701
702
0
    const double M_D50_D65[] =
703
0
    {
704
0
         0.955512609517083, -0.023073214184645,  0.063308961782107,
705
0
        -0.028324949364887,  1.009942432477107,  0.021054814890112,
706
0
         0.012328875695483, -0.020535835374141,  1.330713916450354
707
0
    };
708
709
0
    cmsStage* D65toD50 = cmsStageAllocMatrix(ctx, 3, 3, M_D65_D50, NULL);
710
0
    cmsStage* D50toD65 = cmsStageAllocMatrix(ctx, 3, 3, M_D50_D65, NULL);
711
712
0
    const double M_D65_LMS[] =
713
0
    {
714
0
        0.8189330101, 0.3618667424, -0.1288597137,
715
0
        0.0329845436, 0.9293118715,  0.0361456387,
716
0
        0.0482003018, 0.2643662691,  0.6338517070
717
0
    };
718
   
719
0
    const double M_LMS_D65[] =
720
0
    {
721
0
        1.227013851103521, -0.557799980651822,  0.281256148966468,
722
0
       -0.040580178423281,  1.112256869616830, -0.071676678665601,
723
0
       -0.076381284505707, -0.421481978418013,  1.586163220440795
724
0
    };
725
726
0
    cmsStage* D65toLMS = cmsStageAllocMatrix(ctx, 3, 3, M_D65_LMS, NULL);
727
0
    cmsStage* LMStoD65 = cmsStageAllocMatrix(ctx, 3, 3, M_LMS_D65, NULL);
728
729
0
    cmsToneCurve* CubeRoot = cmsBuildGamma(ctx, 1.0 / 3.0);
730
0
    cmsToneCurve* Cube     = cmsBuildGamma(ctx,  3.0);
731
732
0
    cmsToneCurve* Roots[3] = { CubeRoot, CubeRoot, CubeRoot };
733
0
    cmsToneCurve* Cubes[3] = { Cube, Cube, Cube };
734
735
0
    cmsStage* NonLinearityFw = cmsStageAllocToneCurves(ctx, 3, Roots);
736
0
    cmsStage* NonLinearityRv = cmsStageAllocToneCurves(ctx, 3, Cubes);
737
738
0
    const double M_LMSprime_OkLab[] =
739
0
    {
740
0
        0.2104542553,  0.7936177850, -0.0040720468,
741
0
        1.9779984951, -2.4285922050,  0.4505937099,
742
0
        0.0259040371,  0.7827717662, -0.8086757660
743
0
    };
744
745
0
    const double M_OkLab_LMSprime[] =
746
0
    {
747
0
        0.999999998450520,  0.396337792173768,  0.215803758060759,
748
0
        1.000000008881761, -0.105561342323656, -0.063854174771706,
749
0
        1.000000054672411, -0.089484182094966, -1.291485537864092
750
0
    };
751
    
752
0
    cmsStage* LMSprime_OkLab = cmsStageAllocMatrix(ctx, 3, 3, M_LMSprime_OkLab, NULL);
753
0
    cmsStage* OkLab_LMSprime = cmsStageAllocMatrix(ctx, 3, 3, M_OkLab_LMSprime, NULL);
754
755
0
    cmsPipeline* AToB = cmsPipelineAlloc(ctx, 3, 3);
756
0
    cmsPipeline* BToA = cmsPipelineAlloc(ctx, 3, 3);
757
758
0
    cmsHPROFILE hProfile = cmsCreateProfilePlaceholder(ctx);
759
0
    if (!hProfile)            // can't allocate
760
0
        goto error;
761
  
762
0
    cmsSetProfileVersion(hProfile, 4.4);
763
764
0
    cmsSetDeviceClass(hProfile, cmsSigColorSpaceClass);
765
0
    cmsSetColorSpace(hProfile, cmsSig3colorData);
766
0
    cmsSetPCS(hProfile, cmsSigXYZData);
767
768
0
    cmsSetHeaderRenderingIntent(hProfile, INTENT_RELATIVE_COLORIMETRIC);
769
770
    /**
771
    * Conversion PCS (XYZ/D50) to OkLab 
772
    */
773
0
    if (!cmsPipelineInsertStage(BToA, cmsAT_END, PCSXYZ)) goto error;
774
0
    if (!cmsPipelineInsertStage(BToA, cmsAT_END, D50toD65)) goto error;
775
0
    if (!cmsPipelineInsertStage(BToA, cmsAT_END, D65toLMS)) goto error;
776
0
    if (!cmsPipelineInsertStage(BToA, cmsAT_END, NonLinearityFw)) goto error;
777
0
    if (!cmsPipelineInsertStage(BToA, cmsAT_END, LMSprime_OkLab)) goto error;
778
779
0
    if (!cmsWriteTag(hProfile, cmsSigBToA0Tag, BToA)) goto error;
780
    
781
0
    if (!cmsPipelineInsertStage(AToB, cmsAT_END, OkLab_LMSprime)) goto error;
782
0
    if (!cmsPipelineInsertStage(AToB, cmsAT_END, NonLinearityRv)) goto error;
783
0
    if (!cmsPipelineInsertStage(AToB, cmsAT_END, LMStoD65)) goto error;
784
0
    if (!cmsPipelineInsertStage(AToB, cmsAT_END, D65toD50)) goto error;
785
0
    if (!cmsPipelineInsertStage(AToB, cmsAT_END, XYZPCS)) goto error;
786
787
0
    if (!cmsWriteTag(hProfile, cmsSigAToB0Tag, AToB)) goto error;
788
789
0
    cmsPipelineFree(BToA);
790
0
    cmsPipelineFree(AToB);
791
792
0
    cmsFreeToneCurve(CubeRoot);
793
0
    cmsFreeToneCurve(Cube);
794
795
0
    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
0
}
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
0
{
824
0
    cmsCIELab LabIn, LabOut;
825
0
    cmsCIELCh LChIn, LChOut;
826
0
    cmsCIEXYZ XYZ;
827
0
    LPBCHSWADJUSTS bchsw = (LPBCHSWADJUSTS) Cargo;
828
829
830
0
    cmsLabEncoded2Float(&LabIn, In);
831
832
833
0
    cmsLab2LCh(&LChIn, &LabIn);
834
835
    // Do some adjusts on LCh
836
837
0
    LChOut.L = LChIn.L * bchsw ->Contrast + bchsw ->Brightness;
838
0
    LChOut.C = LChIn.C + bchsw -> Saturation;
839
0
    LChOut.h = LChIn.h + bchsw -> Hue;
840
841
842
0
    cmsLCh2Lab(&LabOut, &LChOut);
843
844
    // Move white point in Lab
845
0
    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
0
    cmsFloat2LabEncoded(Out, &LabOut);
853
854
0
    return TRUE;
855
0
}
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
0
{
870
0
    cmsHPROFILE hICC;
871
0
    cmsPipeline* Pipeline;
872
0
    BCHSWADJUSTS bchsw;
873
0
    cmsCIExyY WhitePnt;
874
0
    cmsStage* CLUT;
875
0
    cmsUInt32Number Dimensions[MAX_INPUT_DIMENSIONS];
876
0
    cmsUInt32Number i;
877
878
0
    bchsw.Brightness = Bright;
879
0
    bchsw.Contrast   = Contrast;
880
0
    bchsw.Hue        = Hue;
881
0
    bchsw.Saturation = Saturation;
882
0
    if (TempSrc == TempDest) {
883
884
0
           bchsw.lAdjustWP = FALSE;
885
0
    }
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
0
    hICC = cmsCreateProfilePlaceholder(ContextID);
896
0
    if (!hICC)                          // can't allocate
897
0
        return NULL;
898
899
0
    cmsSetDeviceClass(hICC,      cmsSigAbstractClass);
900
0
    cmsSetColorSpace(hICC,       cmsSigLabData);
901
0
    cmsSetPCS(hICC,              cmsSigLabData);
902
903
0
    cmsSetHeaderRenderingIntent(hICC,  INTENT_PERCEPTUAL);
904
905
    // Creates a Pipeline with 3D grid only
906
0
    Pipeline = cmsPipelineAlloc(ContextID, 3, 3);
907
0
    if (Pipeline == NULL) {
908
0
        cmsCloseProfile(hICC);
909
0
        return NULL;
910
0
    }
911
912
0
    for (i=0; i < MAX_INPUT_DIMENSIONS; i++) Dimensions[i] = nLUTPoints;
913
0
    CLUT = cmsStageAllocCLut16bitGranular(ContextID, Dimensions, 3, 3, NULL);
914
0
    if (CLUT == NULL) 
915
0
        goto Error;
916
917
0
    if (!cmsStageSampleCLut16bit(CLUT, bchswSampler, (void*) &bchsw, 0)) 
918
0
        goto Error;
919
    
920
0
    if (!cmsPipelineInsertStage(Pipeline, cmsAT_END, CLUT)) 
921
0
        goto Error;
922
    
923
    // Create tags
924
0
    if (!SetTextTags(hICC, L"BCHS built-in")) 
925
0
        goto Error;
926
927
0
    if (!cmsWriteTag(hICC, cmsSigMediaWhitePointTag, (void*)cmsD50_XYZ()))
928
0
        goto Error;
929
930
0
    if (!cmsWriteTag(hICC, cmsSigAToB0Tag, (void*)Pipeline))
931
0
        goto Error;
932
933
    // Pipeline is already on virtual profile
934
0
    cmsPipelineFree(Pipeline);
935
936
    // Ok, done
937
0
    return hICC;
938
939
0
Error:
940
0
    cmsPipelineFree(Pipeline);
941
0
    cmsCloseProfile(hICC);
942
0
    return NULL;
943
0
}
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
0
{
954
0
    return cmsCreateBCHSWabstractProfileTHR(NULL, nLUTPoints, Bright, Contrast, Hue, Saturation, TempSrc, TempDest);
955
0
}
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
0
{
962
0
    cmsHPROFILE hProfile;
963
0
    cmsPipeline* LUT = NULL;
964
0
    cmsStage* PostLin;
965
0
    cmsStage* OutLin;
966
0
    cmsToneCurve* EmptyTab[3];
967
0
    cmsUInt16Number Zero[2] = { 0, 0 };
968
0
    const cmsFloat64Number PickLstarMatrix[] = { 1, 0, 0 };
969
970
0
    hProfile = cmsCreateProfilePlaceholder(ContextID);
971
0
    if (!hProfile)                          // can't allocate
972
0
        return NULL;
973
974
0
    cmsSetProfileVersion(hProfile, 4.4);
975
976
0
    if (!SetTextTags(hProfile, L"NULL profile built-in")) goto Error;
977
978
979
0
    cmsSetDeviceClass(hProfile, cmsSigOutputClass);
980
0
    cmsSetColorSpace(hProfile,  cmsSigGrayData);
981
0
    cmsSetPCS(hProfile,         cmsSigLabData);
982
983
    // Create a valid ICC 4 structure
984
0
    LUT = cmsPipelineAlloc(ContextID, 3, 1);
985
0
    if (LUT == NULL) goto Error;
986
    
987
0
    EmptyTab[0] = EmptyTab[1] = EmptyTab[2] = cmsBuildTabulatedToneCurve16(ContextID, 2, Zero);
988
0
    PostLin = cmsStageAllocToneCurves(ContextID, 3, EmptyTab);
989
0
    OutLin  = cmsStageAllocToneCurves(ContextID, 1, EmptyTab);
990
0
    cmsFreeToneCurve(EmptyTab[0]);
991
992
0
    if (!cmsPipelineInsertStage(LUT, cmsAT_END, PostLin))
993
0
        goto Error;
994
995
0
    if (!cmsPipelineInsertStage(LUT, cmsAT_END, cmsStageAllocMatrix(ContextID, 1, 3, PickLstarMatrix, NULL)))
996
0
        goto Error;
997
998
0
    if (!cmsPipelineInsertStage(LUT, cmsAT_END, OutLin))
999
0
        goto Error;
1000
1001
0
    if (!cmsWriteTag(hProfile, cmsSigBToA0Tag, (void*) LUT)) goto Error;
1002
0
    if (!cmsWriteTag(hProfile, cmsSigMediaWhitePointTag, cmsD50_XYZ())) goto Error;
1003
1004
0
    cmsPipelineFree(LUT);
1005
0
    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
0
}
1017
1018
cmsHPROFILE CMSEXPORT cmsCreateNULLProfile(void)
1019
0
{
1020
0
    return cmsCreateNULLProfileTHR(NULL);
1021
0
}
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
0
{
1038
0
    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
0
    cmsSetDeviceClass(hProfile,      cmsSigLinkClass);
1066
0
    cmsSetColorSpace(hProfile,       ColorSpace);
1067
0
    cmsSetPCS(hProfile,              PCS);
1068
0
}
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
0
{
1079
0
    _cmsTRANSFORM* v = (_cmsTRANSFORM*) xform;
1080
0
    cmsHPROFILE hICC = NULL;
1081
0
    cmsUInt32Number i, nColors;
1082
0
    cmsNAMEDCOLORLIST *nc2 = NULL, *Original = NULL;
1083
1084
    // Create an empty placeholder
1085
0
    hICC = cmsCreateProfilePlaceholder(v->ContextID);
1086
0
    if (hICC == NULL) return NULL;
1087
1088
    // Critical information
1089
0
    cmsSetDeviceClass(hICC, cmsSigNamedColorClass);
1090
0
    cmsSetColorSpace(hICC, v ->ExitColorSpace);
1091
0
    cmsSetPCS(hICC, cmsSigLabData);
1092
1093
    // Tag profile with information
1094
0
    if (!SetTextTags(hICC, L"Named color devicelink")) goto Error;
1095
1096
0
    Original = cmsGetNamedColorList(xform);
1097
0
    if (Original == NULL) goto Error;
1098
1099
0
    nColors = cmsNamedColorCount(Original);
1100
0
    nc2     = cmsDupNamedColorList(Original);
1101
0
    if (nc2 == NULL) goto Error;
1102
1103
    // Colorant count now depends on the output space
1104
0
    nc2 ->ColorantCount = cmsPipelineOutputChannels(v ->Lut);
1105
1106
    // Make sure we have proper formatters
1107
0
    cmsChangeBuffersFormat(xform, TYPE_NAMED_COLOR_INDEX,
1108
0
        FLOAT_SH(0) | COLORSPACE_SH(_cmsLCMScolorSpace(v ->ExitColorSpace))
1109
0
        | BYTES_SH(2) | CHANNELS_SH(cmsChannelsOfColorSpace(v ->ExitColorSpace)));
1110
1111
    // Apply the transfor to colorants.
1112
0
    for (i=0; i < nColors; i++) {
1113
0
        cmsDoTransform(xform, &i, nc2 ->List[i].DeviceColorant, 1);
1114
0
    }
1115
1116
0
    if (!cmsWriteTag(hICC, cmsSigNamedColor2Tag, (void*) nc2)) goto Error;
1117
0
    cmsFreeNamedColorList(nc2);
1118
1119
0
    return hICC;
1120
1121
0
Error:
1122
0
    if (hICC != NULL) cmsCloseProfile(hICC);
1123
0
    return NULL;
1124
0
}
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
0
#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
0
{
1161
0
    cmsStage* mpe;
1162
0
    int n;
1163
1164
0
    for (n=0, mpe = Lut ->Elements; mpe != NULL; mpe = mpe ->Next, n++) {
1165
1166
0
        if (n >= Tab ->nTypes) return FALSE;
1167
0
        if (cmsStageType(mpe) != Tab ->MpeTypes[n]) return FALSE;
1168
0
    }
1169
1170
0
    return (n == Tab ->nTypes);
1171
0
}
1172
1173
1174
static
1175
const cmsAllowedLUT* FindCombination(const cmsPipeline* Lut, cmsBool IsV4, cmsTagSignature DestinationTag)
1176
0
{
1177
0
    cmsUInt32Number n;
1178
1179
0
    for (n=0; n < SIZE_OF_ALLOWED_LUT; n++) {
1180
1181
0
        const cmsAllowedLUT* Tab = AllowedLUTTypes + n;
1182
1183
0
        if (IsV4 ^ Tab -> IsV4) continue;
1184
0
        if ((Tab ->RequiredTag != 0) && (Tab ->RequiredTag != DestinationTag)) continue;
1185
1186
0
        if (CheckOne(Tab, Lut)) return Tab;
1187
0
    }
1188
1189
0
    return NULL;
1190
0
}
1191
1192
1193
// Does convert a transform into a device link profile
1194
cmsHPROFILE CMSEXPORT cmsTransform2DeviceLink(cmsHTRANSFORM hTransform, cmsFloat64Number Version, cmsUInt32Number dwFlags)
1195
0
{
1196
0
    cmsHPROFILE hProfile = NULL;
1197
0
    cmsUInt32Number FrmIn, FrmOut;
1198
0
    cmsInt32Number ChansIn, ChansOut;
1199
0
    int ColorSpaceBitsIn, ColorSpaceBitsOut;
1200
0
    _cmsTRANSFORM* xform = (_cmsTRANSFORM*) hTransform;
1201
0
    cmsPipeline* LUT = NULL;
1202
0
    cmsStage* mpe;
1203
0
    cmsContext ContextID = cmsGetTransformContextID(hTransform);
1204
0
    const cmsAllowedLUT* AllowedLUT;
1205
0
    cmsTagSignature DestinationTag;
1206
0
    cmsProfileClassSignature deviceClass; 
1207
1208
0
    _cmsAssert(hTransform != NULL);
1209
1210
    // Check if the pipeline holding is valid
1211
0
    if (xform -> Lut == NULL) return NULL;
1212
1213
    // Get the first mpe to check for named color
1214
0
    mpe = cmsPipelineGetPtrToFirstStage(xform ->Lut);
1215
1216
    // Check if is a named color transform
1217
0
    if (mpe != NULL) {
1218
1219
0
        if (cmsStageType(mpe) == cmsSigNamedColorElemType) {
1220
0
            return CreateNamedColorDevicelink(hTransform);
1221
0
        }
1222
0
    }
1223
1224
    // First thing to do is to get a copy of the transformation
1225
0
    LUT = cmsPipelineDup(xform ->Lut);
1226
0
    if (LUT == NULL) return NULL;
1227
1228
    // Time to fix the Lab2/Lab4 issue.
1229
0
    if ((xform ->EntryColorSpace == cmsSigLabData) && (Version < 4.0)) {
1230
1231
0
        if (!cmsPipelineInsertStage(LUT, cmsAT_BEGIN, _cmsStageAllocLabV2ToV4curves(ContextID)))
1232
0
            goto Error;
1233
0
    }
1234
1235
    // On the output side too. Note that due to V2/V4 PCS encoding on lab we cannot fix white misalignments
1236
0
    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
0
    hProfile = cmsCreateProfilePlaceholder(ContextID);
1245
0
    if (!hProfile) goto Error;                    // can't allocate
1246
1247
0
    cmsSetProfileVersion(hProfile, Version);
1248
1249
0
    FixColorSpaces(hProfile, xform -> EntryColorSpace, xform -> ExitColorSpace, dwFlags);
1250
1251
    // Optimize the LUT and precalculate a devicelink
1252
1253
0
    ChansIn  = cmsChannelsOfColorSpace(xform -> EntryColorSpace);
1254
0
    ChansOut = cmsChannelsOfColorSpace(xform -> ExitColorSpace);
1255
1256
0
    ColorSpaceBitsIn  = _cmsLCMScolorSpace(xform -> EntryColorSpace);
1257
0
    ColorSpaceBitsOut = _cmsLCMScolorSpace(xform -> ExitColorSpace);
1258
1259
0
    FrmIn  = COLORSPACE_SH(ColorSpaceBitsIn) | CHANNELS_SH(ChansIn)|BYTES_SH(2);
1260
0
    FrmOut = COLORSPACE_SH(ColorSpaceBitsOut) | CHANNELS_SH(ChansOut)|BYTES_SH(2);
1261
1262
0
    deviceClass = cmsGetDeviceClass(hProfile);
1263
1264
0
     if (deviceClass == cmsSigOutputClass)
1265
0
         DestinationTag = cmsSigBToA0Tag;
1266
0
     else
1267
0
         DestinationTag = cmsSigAToB0Tag;
1268
1269
    // Check if the profile/version can store the result
1270
0
    if (dwFlags & cmsFLAGS_FORCE_CLUT)
1271
0
        AllowedLUT = NULL;
1272
0
    else
1273
0
        AllowedLUT = FindCombination(LUT, Version >= 4.0, DestinationTag);
1274
1275
0
    if (AllowedLUT == NULL) {
1276
1277
        // Try to optimize
1278
0
        _cmsOptimizePipeline(ContextID, &LUT, xform ->RenderingIntent, &FrmIn, &FrmOut, &dwFlags);
1279
0
        AllowedLUT = FindCombination(LUT, Version >= 4.0, DestinationTag);
1280
1281
0
    }
1282
1283
    // If no way, then force CLUT that for sure can be written
1284
0
    if (AllowedLUT == NULL) {
1285
1286
0
        cmsStage* FirstStage;
1287
0
        cmsStage* LastStage;
1288
1289
0
        dwFlags |= cmsFLAGS_FORCE_CLUT;
1290
0
        _cmsOptimizePipeline(ContextID, &LUT, xform ->RenderingIntent, &FrmIn, &FrmOut, &dwFlags);
1291
1292
        // Put identity curves if needed
1293
0
        FirstStage = cmsPipelineGetPtrToFirstStage(LUT);
1294
0
        if (FirstStage != NULL && FirstStage ->Type != cmsSigCurveSetElemType)
1295
0
             if (!cmsPipelineInsertStage(LUT, cmsAT_BEGIN, _cmsStageAllocIdentityCurves(ContextID, ChansIn)))
1296
0
                 goto Error;
1297
1298
0
        LastStage = cmsPipelineGetPtrToLastStage(LUT);
1299
0
        if (LastStage != NULL && LastStage ->Type != cmsSigCurveSetElemType)
1300
0
             if (!cmsPipelineInsertStage(LUT, cmsAT_END,   _cmsStageAllocIdentityCurves(ContextID, ChansOut)))
1301
0
                 goto Error;
1302
1303
0
        AllowedLUT = FindCombination(LUT, Version >= 4.0, DestinationTag);
1304
0
    }
1305
1306
    // Somethings is wrong...
1307
0
    if (AllowedLUT == NULL) {
1308
0
        goto Error;
1309
0
    }
1310
1311
1312
0
    if (dwFlags & cmsFLAGS_8BITS_DEVICELINK)
1313
0
                     cmsPipelineSetSaveAs8bitsFlag(LUT, TRUE);
1314
1315
    // Tag profile with information
1316
0
    if (!SetTextTags(hProfile, L"devicelink")) goto Error;
1317
1318
    // Store result
1319
0
    if (!cmsWriteTag(hProfile, DestinationTag, LUT)) goto Error;
1320
1321
1322
0
    if (xform -> InputColorant != NULL) {
1323
0
           if (!cmsWriteTag(hProfile, cmsSigColorantTableTag, xform->InputColorant)) goto Error;
1324
0
    }
1325
1326
0
    if (xform -> OutputColorant != NULL) {
1327
0
           if (!cmsWriteTag(hProfile, cmsSigColorantTableOutTag, xform->OutputColorant)) goto Error;
1328
0
    }
1329
1330
0
    if ((deviceClass == cmsSigLinkClass) && (xform ->Sequence != NULL)) {
1331
0
        if (!_cmsWriteProfileSequence(hProfile, xform ->Sequence)) goto Error;
1332
0
    }
1333
1334
    // Set the white point
1335
0
    if (deviceClass == cmsSigInputClass) {
1336
0
        if (!cmsWriteTag(hProfile, cmsSigMediaWhitePointTag, &xform ->EntryWhitePoint)) goto Error;
1337
0
    }
1338
0
    else {
1339
0
         if (!cmsWriteTag(hProfile, cmsSigMediaWhitePointTag, &xform ->ExitWhitePoint)) goto Error;
1340
0
    }
1341
1342
  
1343
    // Per 7.2.15 in spec 4.3
1344
0
    cmsSetHeaderRenderingIntent(hProfile, xform ->RenderingIntent);
1345
1346
0
    cmsPipelineFree(LUT);
1347
0
    return hProfile;
1348
1349
0
Error:
1350
0
    if (LUT != NULL) cmsPipelineFree(LUT);
1351
0
    cmsCloseProfile(hProfile);
1352
0
    return NULL;
1353
0
}