Coverage Report

Created: 2026-09-14 07:34

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/ghostpdl/lcms2mt/src/cmscnvrt.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
30
// This is the default routine for ICC-style intents. A user may decide to override it by using a plugin.
31
// Supported intents are perceptual, relative colorimetric, saturation and ICC-absolute colorimetric
32
static
33
cmsPipeline* DefaultICCintents(cmsContext     ContextID,
34
                               cmsUInt32Number nProfiles,
35
                               cmsUInt32Number Intents[],
36
                               cmsHPROFILE     hProfiles[],
37
                               cmsBool         BPC[],
38
                               cmsFloat64Number AdaptationStates[],
39
                               cmsUInt32Number dwFlags);
40
41
//---------------------------------------------------------------------------------
42
43
// This is the entry for black-preserving K-only intents, which are non-ICC. Last profile have to be a output profile
44
// to do the trick (no devicelinks allowed at that position)
45
static
46
cmsPipeline*  BlackPreservingKOnlyIntents(cmsContext     ContextID,
47
                                          cmsUInt32Number nProfiles,
48
                                          cmsUInt32Number Intents[],
49
                                          cmsHPROFILE     hProfiles[],
50
                                          cmsBool         BPC[],
51
                                          cmsFloat64Number AdaptationStates[],
52
                                          cmsUInt32Number dwFlags);
53
54
//---------------------------------------------------------------------------------
55
56
// This is the entry for black-plane preserving, which are non-ICC. Again, Last profile have to be a output profile
57
// to do the trick (no devicelinks allowed at that position)
58
static
59
cmsPipeline*  BlackPreservingKPlaneIntents(cmsContext     ContextID,
60
                                           cmsUInt32Number nProfiles,
61
                                           cmsUInt32Number Intents[],
62
                                           cmsHPROFILE     hProfiles[],
63
                                           cmsBool         BPC[],
64
                                           cmsFloat64Number AdaptationStates[],
65
                                           cmsUInt32Number dwFlags);
66
67
//---------------------------------------------------------------------------------
68
69
70
// This is a structure holding implementations for all supported intents.
71
typedef struct _cms_intents_list {
72
73
    cmsUInt32Number Intent;
74
    char            Description[256];
75
    cmsIntentFn     Link;
76
    struct _cms_intents_list*  Next;
77
78
} cmsIntentsList;
79
80
81
// Built-in intents
82
static cmsIntentsList DefaultIntents[] = {
83
84
    { INTENT_PERCEPTUAL,                            "Perceptual",                                   DefaultICCintents,            &DefaultIntents[1] },
85
    { INTENT_RELATIVE_COLORIMETRIC,                 "Relative colorimetric",                        DefaultICCintents,            &DefaultIntents[2] },
86
    { INTENT_SATURATION,                            "Saturation",                                   DefaultICCintents,            &DefaultIntents[3] },
87
    { INTENT_ABSOLUTE_COLORIMETRIC,                 "Absolute colorimetric",                        DefaultICCintents,            &DefaultIntents[4] },
88
    { INTENT_PRESERVE_K_ONLY_PERCEPTUAL,            "Perceptual preserving black ink",              BlackPreservingKOnlyIntents,  &DefaultIntents[5] },
89
    { INTENT_PRESERVE_K_ONLY_RELATIVE_COLORIMETRIC, "Relative colorimetric preserving black ink",   BlackPreservingKOnlyIntents,  &DefaultIntents[6] },
90
    { INTENT_PRESERVE_K_ONLY_SATURATION,            "Saturation preserving black ink",              BlackPreservingKOnlyIntents,  &DefaultIntents[7] },
91
    { INTENT_PRESERVE_K_PLANE_PERCEPTUAL,           "Perceptual preserving black plane",            BlackPreservingKPlaneIntents, &DefaultIntents[8] },
92
    { INTENT_PRESERVE_K_PLANE_RELATIVE_COLORIMETRIC,"Relative colorimetric preserving black plane", BlackPreservingKPlaneIntents, &DefaultIntents[9] },
93
    { INTENT_PRESERVE_K_PLANE_SATURATION,           "Saturation preserving black plane",            BlackPreservingKPlaneIntents, NULL }
94
};
95
96
97
// A pointer to the beginning of the list
98
_cmsIntentsPluginChunkType _cmsIntentsPluginChunk = { NULL };
99
100
// Duplicates the zone of memory used by the plug-in in the new context
101
static
102
void DupPluginIntentsList(struct _cmsContext_struct* ctx,
103
                                               const struct _cmsContext_struct* src)
104
0
{
105
0
   _cmsIntentsPluginChunkType newHead = { NULL };
106
0
   cmsIntentsList*  entry;
107
0
   cmsIntentsList*  Anterior = NULL;
108
0
   _cmsIntentsPluginChunkType* head = (_cmsIntentsPluginChunkType*) src->chunks[IntentPlugin];
109
110
    // Walk the list copying all nodes
111
0
   for (entry = head->Intents;
112
0
        entry != NULL;
113
0
        entry = entry ->Next) {
114
115
0
            cmsIntentsList *newEntry = ( cmsIntentsList *) _cmsSubAllocDup(ctx ->MemPool, entry, sizeof(cmsIntentsList));
116
117
0
            if (newEntry == NULL)
118
0
                return;
119
120
            // We want to keep the linked list order, so this is a little bit tricky
121
0
            newEntry -> Next = NULL;
122
0
            if (Anterior)
123
0
                Anterior -> Next = newEntry;
124
125
0
            Anterior = newEntry;
126
127
0
            if (newHead.Intents == NULL)
128
0
                newHead.Intents = newEntry;
129
0
    }
130
131
0
  ctx ->chunks[IntentPlugin] = _cmsSubAllocDup(ctx->MemPool, &newHead, sizeof(_cmsIntentsPluginChunkType));
132
0
}
133
134
void  _cmsAllocIntentsPluginChunk(struct _cmsContext_struct* ctx,
135
                                         const struct _cmsContext_struct* src)
136
147k
{
137
147k
    if (src != NULL) {
138
139
        // Copy all linked list
140
0
        DupPluginIntentsList(ctx, src);
141
0
    }
142
147k
    else {
143
147k
        static _cmsIntentsPluginChunkType IntentsPluginChunkType = { NULL };
144
147k
        ctx ->chunks[IntentPlugin] = _cmsSubAllocDup(ctx ->MemPool, &IntentsPluginChunkType, sizeof(_cmsIntentsPluginChunkType));
145
147k
    }
146
147k
}
147
148
149
// Search the list for a suitable intent. Returns NULL if not found
150
static
151
cmsIntentsList* SearchIntent(cmsContext ContextID, cmsUInt32Number Intent)
152
2.48M
{
153
2.48M
    _cmsIntentsPluginChunkType* ctx = ( _cmsIntentsPluginChunkType*) _cmsContextGetClientChunk(ContextID, IntentPlugin);
154
2.48M
    cmsIntentsList* pt;
155
156
2.48M
    for (pt = ctx -> Intents; pt != NULL; pt = pt -> Next)
157
0
        if (pt ->Intent == Intent) return pt;
158
159
5.18M
    for (pt = DefaultIntents; pt != NULL; pt = pt -> Next)
160
5.18M
        if (pt ->Intent == Intent) return pt;
161
162
0
    return NULL;
163
2.48M
}
164
165
// Black point compensation. Implemented as a linear scaling in XYZ. Black points
166
// should come relative to the white point. Fills an matrix/offset element m
167
// which is organized as a 4x4 matrix.
168
static
169
void ComputeBlackPointCompensation(cmsContext ContextID, const cmsCIEXYZ* BlackPointIn,
170
                                   const cmsCIEXYZ* BlackPointOut,
171
                                   cmsMAT3* m, cmsVEC3* off)
172
11.6k
{
173
11.6k
  cmsFloat64Number ax, ay, az, bx, by, bz, tx, ty, tz;
174
175
   // Now we need to compute a matrix plus an offset m and of such of
176
   // [m]*bpin + off = bpout
177
   // [m]*D50  + off = D50
178
   //
179
   // This is a linear scaling in the form ax+b, where
180
   // a = (bpout - D50) / (bpin - D50)
181
   // b = - D50* (bpout - bpin) / (bpin - D50)
182
183
11.6k
   tx = BlackPointIn->X - cmsD50_XYZ(ContextID)->X;
184
11.6k
   ty = BlackPointIn->Y - cmsD50_XYZ(ContextID)->Y;
185
11.6k
   tz = BlackPointIn->Z - cmsD50_XYZ(ContextID)->Z;
186
187
11.6k
   ax = (BlackPointOut->X - cmsD50_XYZ(ContextID)->X) / tx;
188
11.6k
   ay = (BlackPointOut->Y - cmsD50_XYZ(ContextID)->Y) / ty;
189
11.6k
   az = (BlackPointOut->Z - cmsD50_XYZ(ContextID)->Z) / tz;
190
191
11.6k
   bx = - cmsD50_XYZ(ContextID)-> X * (BlackPointOut->X - BlackPointIn->X) / tx;
192
11.6k
   by = - cmsD50_XYZ(ContextID)-> Y * (BlackPointOut->Y - BlackPointIn->Y) / ty;
193
11.6k
   bz = - cmsD50_XYZ(ContextID)-> Z * (BlackPointOut->Z - BlackPointIn->Z) / tz;
194
195
11.6k
   _cmsVEC3init(ContextID, &m ->v[0], ax, 0,  0);
196
11.6k
   _cmsVEC3init(ContextID, &m ->v[1], 0, ay,  0);
197
11.6k
   _cmsVEC3init(ContextID, &m ->v[2], 0,  0,  az);
198
11.6k
   _cmsVEC3init(ContextID, off, bx, by, bz);
199
200
11.6k
}
201
202
203
// Approximate a blackbody illuminant based on CHAD information
204
static
205
cmsFloat64Number CHAD2Temp(cmsContext ContextID, const cmsMAT3* Chad)
206
0
{
207
    // Convert D50 across inverse CHAD to get the absolute white point
208
0
    cmsVEC3 d, s;
209
0
    cmsCIEXYZ Dest;
210
0
    cmsCIExyY DestChromaticity;
211
0
    cmsFloat64Number TempK;
212
0
    cmsMAT3 m1, m2;
213
214
0
    m1 = *Chad;
215
0
    if (!_cmsMAT3inverse(ContextID, &m1, &m2)) return FALSE;
216
217
0
    s.n[VX] = cmsD50_XYZ(ContextID) -> X;
218
0
    s.n[VY] = cmsD50_XYZ(ContextID) -> Y;
219
0
    s.n[VZ] = cmsD50_XYZ(ContextID) -> Z;
220
221
0
    _cmsMAT3eval(ContextID, &d, &m2, &s);
222
223
0
    Dest.X = d.n[VX];
224
0
    Dest.Y = d.n[VY];
225
0
    Dest.Z = d.n[VZ];
226
227
0
    cmsXYZ2xyY(ContextID, &DestChromaticity, &Dest);
228
229
0
    if (!cmsTempFromWhitePoint(ContextID, &TempK, &DestChromaticity))
230
0
        return -1.0;
231
232
0
    return TempK;
233
0
}
234
235
// Compute a CHAD based on a given temperature
236
static
237
void Temp2CHAD(cmsContext ContextID, cmsMAT3* Chad, cmsFloat64Number Temp)
238
0
{
239
0
    cmsCIEXYZ White;
240
0
    cmsCIExyY ChromaticityOfWhite;
241
242
0
    cmsWhitePointFromTemp(ContextID, &ChromaticityOfWhite, Temp);
243
0
    cmsxyY2XYZ(ContextID,&White, &ChromaticityOfWhite);
244
0
    _cmsAdaptationMatrix(ContextID, Chad, NULL, &White, cmsD50_XYZ(ContextID));
245
0
}
246
247
// Join scalings to obtain relative input to absolute and then to relative output.
248
// Result is stored in a 3x3 matrix
249
static
250
cmsBool  ComputeAbsoluteIntent(cmsContext ContextID, cmsFloat64Number AdaptationState,
251
                               const cmsCIEXYZ* WhitePointIn,
252
                               const cmsMAT3* ChromaticAdaptationMatrixIn,
253
                               const cmsCIEXYZ* WhitePointOut,
254
                               const cmsMAT3* ChromaticAdaptationMatrixOut,
255
                               cmsMAT3* m)
256
18
{
257
18
    cmsMAT3 Scale, m1, m2, m3, m4;
258
259
    // TODO: Follow Marc Mahy's recommendation to check if CHAD is same by using M1*M2 == M2*M1. If so, do nothing.
260
    // TODO: Add support for ArgyllArts tag
261
262
    // Adaptation state
263
18
    if (AdaptationState == 1.0) {
264
265
        // Observer is fully adapted. Keep chromatic adaptation.
266
        // That is the standard V4 behaviour
267
18
        _cmsVEC3init(ContextID, &m->v[0], WhitePointIn->X / WhitePointOut->X, 0, 0);
268
18
        _cmsVEC3init(ContextID, &m->v[1], 0, WhitePointIn->Y / WhitePointOut->Y, 0);
269
18
        _cmsVEC3init(ContextID, &m->v[2], 0, 0, WhitePointIn->Z / WhitePointOut->Z);
270
271
18
    }
272
0
    else  {
273
274
        // Incomplete adaptation. This is an advanced feature.
275
0
        _cmsVEC3init(ContextID, &Scale.v[0], WhitePointIn->X / WhitePointOut->X, 0, 0);
276
0
        _cmsVEC3init(ContextID, &Scale.v[1], 0,  WhitePointIn->Y / WhitePointOut->Y, 0);
277
0
        _cmsVEC3init(ContextID, &Scale.v[2], 0, 0,  WhitePointIn->Z / WhitePointOut->Z);
278
279
280
0
        if (AdaptationState == 0.0) {
281
282
0
            m1 = *ChromaticAdaptationMatrixOut;
283
0
            _cmsMAT3per(ContextID, &m2, &m1, &Scale);
284
            // m2 holds CHAD from output white to D50 times abs. col. scaling
285
286
            // Observer is not adapted, undo the chromatic adaptation
287
0
            _cmsMAT3per(ContextID, m, &m2, ChromaticAdaptationMatrixOut);
288
289
0
            m3 = *ChromaticAdaptationMatrixIn;
290
0
            if (!_cmsMAT3inverse(ContextID, &m3, &m4)) return FALSE;
291
0
            _cmsMAT3per(ContextID, m, &m2, &m4);
292
293
0
        } else {
294
295
0
            cmsMAT3 MixedCHAD;
296
0
            cmsFloat64Number TempSrc, TempDest, Temp;
297
298
0
            m1 = *ChromaticAdaptationMatrixIn;
299
0
            if (!_cmsMAT3inverse(ContextID, &m1, &m2)) return FALSE;
300
0
            _cmsMAT3per(ContextID, &m3, &m2, &Scale);
301
            // m3 holds CHAD from input white to D50 times abs. col. scaling
302
303
0
            TempSrc  = CHAD2Temp(ContextID, ChromaticAdaptationMatrixIn);
304
0
            TempDest = CHAD2Temp(ContextID, ChromaticAdaptationMatrixOut);
305
306
0
            if (TempSrc < 0.0 || TempDest < 0.0) return FALSE; // Something went wrong
307
308
0
            if (_cmsMAT3isIdentity(ContextID, &Scale) && fabs(TempSrc - TempDest) < 0.01) {
309
310
0
                _cmsMAT3identity(ContextID, m);
311
0
                return TRUE;
312
0
            }
313
314
0
            Temp = (1.0 - AdaptationState) * TempDest + AdaptationState * TempSrc;
315
316
            // Get a CHAD from whatever output temperature to D50. This replaces output CHAD
317
0
            Temp2CHAD(ContextID, &MixedCHAD, Temp);
318
319
0
            _cmsMAT3per(ContextID, m, &m3, &MixedCHAD);
320
0
        }
321
322
0
    }
323
18
    return TRUE;
324
325
18
}
326
327
// Just to see if m matrix should be applied
328
static
329
cmsBool IsEmptyLayer(cmsContext ContextID, cmsMAT3* m, cmsVEC3* off)
330
1.37M
{
331
1.37M
    cmsFloat64Number diff = 0;
332
1.37M
    cmsMAT3 Ident;
333
1.37M
    int i;
334
335
1.37M
    if (m == NULL && off == NULL) return TRUE;  // NULL is allowed as an empty layer
336
1.37M
    if (m == NULL && off != NULL) return FALSE; // This is an internal error
337
338
1.37M
    _cmsMAT3identity(ContextID, &Ident);
339
340
13.7M
    for (i=0; i < 3*3; i++)
341
12.3M
        diff += fabs(((cmsFloat64Number*)m)[i] - ((cmsFloat64Number*)&Ident)[i]);
342
343
5.49M
    for (i=0; i < 3; i++)
344
4.11M
        diff += fabs(((cmsFloat64Number*)off)[i]);
345
346
347
1.37M
    return (diff < 0.002);
348
1.37M
}
349
350
351
// Compute the conversion layer
352
static
353
cmsBool ComputeConversion(cmsContext ContextID,
354
                          cmsUInt32Number i,
355
                          cmsHPROFILE hProfiles[],
356
                          cmsUInt32Number Intent,
357
                          cmsBool BPC,
358
                          cmsFloat64Number AdaptationState,
359
                          cmsMAT3* m, cmsVEC3* off)
360
1.35M
{
361
362
1.35M
    int k;
363
364
    // m  and off are set to identity and this is detected latter on
365
1.35M
    _cmsMAT3identity(ContextID, m);
366
1.35M
    _cmsVEC3init(ContextID, off, 0, 0, 0);
367
368
    // If intent is abs. colorimetric,
369
1.35M
    if (Intent == INTENT_ABSOLUTE_COLORIMETRIC) {
370
371
18
        cmsCIEXYZ WhitePointIn, WhitePointOut;
372
18
        cmsMAT3 ChromaticAdaptationMatrixIn, ChromaticAdaptationMatrixOut;
373
374
18
        if (!_cmsReadMediaWhitePoint(ContextID, &WhitePointIn, hProfiles[i - 1])) return FALSE;
375
18
        if (!_cmsReadCHAD(ContextID, &ChromaticAdaptationMatrixIn, hProfiles[i - 1])) return FALSE;
376
377
18
        if (!_cmsReadMediaWhitePoint(ContextID, &WhitePointOut, hProfiles[i])) return FALSE;
378
18
        if (!_cmsReadCHAD(ContextID, &ChromaticAdaptationMatrixOut, hProfiles[i])) return FALSE;
379
380
18
        if (!ComputeAbsoluteIntent(ContextID, AdaptationState,
381
18
                                  &WhitePointIn,  &ChromaticAdaptationMatrixIn,
382
18
                                  &WhitePointOut, &ChromaticAdaptationMatrixOut, m)) return FALSE;
383
384
18
    }
385
1.35M
    else {
386
        // Rest of intents may apply BPC.
387
388
1.35M
        if (BPC) {
389
390
421k
            cmsCIEXYZ BlackPointIn = { 0, 0, 0}, BlackPointOut = { 0, 0, 0 };
391
392
421k
            cmsDetectBlackPoint(ContextID, &BlackPointIn,  hProfiles[i-1], Intent, 0);
393
421k
            cmsDetectDestinationBlackPoint(ContextID, &BlackPointOut, hProfiles[i], Intent, 0);
394
395
            // If black points are equal, then do nothing
396
421k
            if (BlackPointIn.X != BlackPointOut.X ||
397
410k
                BlackPointIn.Y != BlackPointOut.Y ||
398
410k
                BlackPointIn.Z != BlackPointOut.Z)
399
11.6k
                    ComputeBlackPointCompensation(ContextID, &BlackPointIn, &BlackPointOut, m, off);
400
421k
        }
401
1.35M
    }
402
403
    // Offset should be adjusted because the encoding. We encode XYZ normalized to 0..1.0,
404
    // to do that, we divide by MAX_ENCODEABLE_XZY. The conversion stage goes XYZ -> XYZ so
405
    // we have first to convert from encoded to XYZ and then convert back to encoded.
406
    // y = Mx + Off
407
    // x = x'c
408
    // y = M x'c + Off
409
    // y = y'c; y' = y / c
410
    // y' = (Mx'c + Off) /c = Mx' + (Off / c)
411
412
5.41M
    for (k=0; k < 3; k++) {
413
4.06M
        off ->n[k] /= MAX_ENCODEABLE_XYZ;
414
4.06M
    }
415
416
1.35M
    return TRUE;
417
1.35M
}
418
419
420
// Add a conversion stage if needed. If a matrix/offset m is given, it applies to XYZ space
421
static
422
cmsBool AddConversion(cmsContext ContextID, cmsPipeline* Result, cmsColorSpaceSignature InPCS, cmsColorSpaceSignature OutPCS, cmsMAT3* m, cmsVEC3* off)
423
1.37M
{
424
1.37M
    cmsFloat64Number* m_as_dbl = (cmsFloat64Number*) m;
425
1.37M
    cmsFloat64Number* off_as_dbl = (cmsFloat64Number*) off;
426
427
    // Handle PCS mismatches. A specialized stage is added to the LUT in such case
428
1.37M
    switch (InPCS) {
429
430
1.30M
    case cmsSigXYZData: // Input profile operates in XYZ
431
432
1.30M
        switch (OutPCS) {
433
434
475k
        case cmsSigXYZData:  // XYZ -> XYZ
435
475k
            if (!IsEmptyLayer(ContextID, m, off) &&
436
210
                !cmsPipelineInsertStage(ContextID, Result, cmsAT_END, cmsStageAllocMatrix(ContextID, 3, 3, m_as_dbl, off_as_dbl)))
437
0
                return FALSE;
438
475k
            break;
439
440
832k
        case cmsSigLabData:  // XYZ -> Lab
441
832k
            if (!IsEmptyLayer(ContextID, m, off) &&
442
3.31k
                !cmsPipelineInsertStage(ContextID, Result, cmsAT_END, cmsStageAllocMatrix(ContextID, 3, 3, m_as_dbl, off_as_dbl)))
443
0
                return FALSE;
444
832k
            if (!cmsPipelineInsertStage(ContextID, Result, cmsAT_END, _cmsStageAllocXYZ2Lab(ContextID)))
445
0
                return FALSE;
446
832k
            break;
447
448
832k
        default:
449
0
            return FALSE;   // Colorspace mismatch
450
1.30M
        }
451
1.30M
        break;
452
453
1.30M
    case cmsSigLabData: // Input profile operates in Lab
454
455
65.6k
        switch (OutPCS) {
456
457
9.36k
        case cmsSigXYZData:  // Lab -> XYZ
458
459
9.36k
            if (!cmsPipelineInsertStage(ContextID, Result, cmsAT_END, _cmsStageAllocLab2XYZ(ContextID)))
460
41
                return FALSE;
461
9.32k
            if (!IsEmptyLayer(ContextID, m, off) &&
462
8.01k
                !cmsPipelineInsertStage(ContextID, Result, cmsAT_END, cmsStageAllocMatrix(ContextID, 3, 3, m_as_dbl, off_as_dbl)))
463
0
                return FALSE;
464
9.32k
            break;
465
466
56.2k
        case cmsSigLabData:  // Lab -> Lab
467
468
56.2k
            if (!IsEmptyLayer(ContextID, m, off)) {
469
51
                if (!cmsPipelineInsertStage(ContextID, Result, cmsAT_END, _cmsStageAllocLab2XYZ(ContextID)) ||
470
51
                    !cmsPipelineInsertStage(ContextID, Result, cmsAT_END, cmsStageAllocMatrix(ContextID, 3, 3, m_as_dbl, off_as_dbl)) ||
471
51
                    !cmsPipelineInsertStage(ContextID, Result, cmsAT_END, _cmsStageAllocXYZ2Lab(ContextID)))
472
0
                    return FALSE;
473
51
            }
474
56.2k
            break;
475
476
56.2k
        default:
477
0
            return FALSE;  // Mismatch
478
65.6k
        }
479
65.5k
        break;
480
481
        // On colorspaces other than PCS, check for same space
482
65.5k
    default:
483
0
        if (InPCS != OutPCS) return FALSE;
484
0
        break;
485
1.37M
    }
486
487
1.37M
    return TRUE;
488
1.37M
}
489
490
491
// Is a given space compatible with another?
492
static
493
cmsBool ColorSpaceIsCompatible(cmsColorSpaceSignature a, cmsColorSpaceSignature b)
494
3.85M
{
495
    // If they are same, they are compatible.
496
3.85M
    if (a == b) return TRUE;
497
498
    // Check for MCH4 substitution of CMYK
499
841k
    if ((a == cmsSig4colorData) && (b == cmsSigCmykData)) return TRUE;
500
841k
    if ((a == cmsSigCmykData) && (b == cmsSig4colorData)) return TRUE;
501
502
    // Check for XYZ/Lab. Those spaces are interchangeable as they can be computed one from other.
503
841k
    if ((a == cmsSigXYZData) && (b == cmsSigLabData)) return TRUE;
504
832k
    if ((a == cmsSigLabData) && (b == cmsSigXYZData)) return TRUE;
505
506
0
    return FALSE;
507
832k
}
508
509
510
// Default handler for ICC-style intents
511
static
512
cmsPipeline* DefaultICCintents(cmsContext       ContextID,
513
                               cmsUInt32Number  nProfiles,
514
                               cmsUInt32Number  TheIntents[],
515
                               cmsHPROFILE      hProfiles[],
516
                               cmsBool          BPC[],
517
                               cmsFloat64Number AdaptationStates[],
518
                               cmsUInt32Number  dwFlags)
519
2.48M
{
520
2.48M
    cmsPipeline* Lut = NULL;
521
2.48M
    cmsPipeline* Result;
522
2.48M
    cmsHPROFILE hProfile;
523
2.48M
    cmsMAT3 m;
524
2.48M
    cmsVEC3 off;
525
2.48M
    cmsColorSpaceSignature ColorSpaceIn, ColorSpaceOut = cmsSigLabData, CurrentColorSpace;
526
2.48M
    cmsProfileClassSignature ClassSig;
527
2.48M
    cmsUInt32Number  i, Intent;
528
529
    // For safety
530
2.48M
    if (nProfiles == 0) return NULL;
531
532
    // Allocate an empty LUT for holding the result. 0 as channel count means 'undefined'
533
2.48M
    Result = cmsPipelineAlloc(ContextID, 0, 0);
534
2.48M
    if (Result == NULL) return NULL;
535
536
2.48M
    CurrentColorSpace = cmsGetColorSpace(ContextID, hProfiles[0]);
537
538
5.19M
    for (i=0; i < nProfiles; i++) {
539
540
3.85M
        cmsBool  lIsDeviceLink, lIsInput;
541
542
3.85M
        hProfile      = hProfiles[i];
543
3.85M
        ClassSig      = cmsGetDeviceClass(ContextID, hProfile);
544
3.85M
        lIsDeviceLink = (ClassSig == cmsSigLinkClass || ClassSig == cmsSigAbstractClass );
545
546
        // First profile is used as input unless devicelink or abstract
547
3.85M
        if ((i == 0) && !lIsDeviceLink) {
548
2.46M
            lIsInput = TRUE;
549
2.46M
        }
550
1.39M
        else {
551
          // Else use profile in the input direction if current space is not PCS
552
1.39M
        lIsInput      = (CurrentColorSpace != cmsSigXYZData) &&
553
83.8k
                        (CurrentColorSpace != cmsSigLabData);
554
1.39M
        }
555
556
3.85M
        Intent        = TheIntents[i];
557
558
3.85M
        if (lIsInput || lIsDeviceLink) {
559
560
3.40M
            ColorSpaceIn    = cmsGetColorSpace(ContextID, hProfile);
561
3.40M
            ColorSpaceOut   = cmsGetPCS(ContextID, hProfile);
562
3.40M
        }
563
455k
        else {
564
565
455k
            ColorSpaceIn    = cmsGetPCS(ContextID, hProfile);
566
455k
            ColorSpaceOut   = cmsGetColorSpace(ContextID, hProfile);
567
455k
        }
568
569
3.85M
        if (!ColorSpaceIsCompatible(ColorSpaceIn, CurrentColorSpace)) {
570
571
0
            cmsSignalError(ContextID, cmsERROR_COLORSPACE_CHECK, "ColorSpace mismatch");
572
0
            goto Error;
573
0
        }
574
575
        // If devicelink is found, then no custom intent is allowed and we can
576
        // read the LUT to be applied. Settings don't apply here.
577
3.85M
        if (lIsDeviceLink || ((ClassSig == cmsSigNamedColorClass) && (nProfiles == 1))) {
578
579
            // Get the involved LUT from the profile
580
920k
            Lut = _cmsReadDevicelinkLUT(ContextID, hProfile, Intent);
581
920k
            if (Lut == NULL) goto Error;
582
583
            // What about abstract profiles?
584
920k
             if (ClassSig == cmsSigAbstractClass && i > 0) {
585
902k
                if (!ComputeConversion(ContextID, i, hProfiles, Intent, BPC[i], AdaptationStates[i], &m, &off)) goto Error;
586
902k
             }
587
18.2k
             else {
588
18.2k
                _cmsMAT3identity(ContextID, &m);
589
18.2k
                _cmsVEC3init(ContextID, &off, 0, 0, 0);
590
18.2k
             }
591
592
593
920k
            if (!AddConversion(ContextID, Result, CurrentColorSpace, ColorSpaceIn, &m, &off)) goto Error;
594
595
920k
        }
596
2.93M
        else {
597
598
2.93M
            if (lIsInput) {
599
                // Input direction means non-pcs connection, so proceed like devicelinks
600
2.48M
                Lut = _cmsReadInputLUT(ContextID, hProfile, Intent);
601
2.48M
                if (Lut == NULL) goto Error;
602
2.48M
            }
603
455k
            else {
604
605
                // Output direction means PCS connection. Intent may apply here
606
455k
                Lut = _cmsReadOutputLUT(ContextID, hProfile, Intent);
607
455k
                if (Lut == NULL) goto Error;
608
609
610
452k
                if (!ComputeConversion(ContextID, i, hProfiles, Intent, BPC[i], AdaptationStates[i], &m, &off)) goto Error;
611
452k
                if (!AddConversion(ContextID, Result, CurrentColorSpace, ColorSpaceIn, &m, &off)) goto Error;
612
613
452k
            }
614
2.93M
        }
615
616
        // Concatenate to the output LUT
617
2.71M
        if (!cmsPipelineCat(ContextID, Result, Lut))
618
8
            goto Error;
619
620
2.71M
        cmsPipelineFree(ContextID, Lut);
621
2.71M
        Lut = NULL;
622
623
        // Update current space
624
2.71M
        CurrentColorSpace = ColorSpaceOut;
625
2.71M
    }
626
627
    // Check for non-negatives clip
628
1.33M
    if (dwFlags & cmsFLAGS_NONEGATIVES) {
629
630
0
        if (ColorSpaceOut == cmsSigGrayData ||
631
0
            ColorSpaceOut == cmsSigRgbData ||
632
0
            ColorSpaceOut == cmsSigCmykData) {
633
634
0
            cmsStage* clip = _cmsStageClipNegatives(ContextID, cmsChannelsOfColorSpace(ContextID, ColorSpaceOut));
635
0
            if (clip == NULL) goto Error;
636
637
0
            if (!cmsPipelineInsertStage(ContextID, Result, cmsAT_END, clip))
638
0
                goto Error;
639
0
        }
640
641
0
    }
642
643
1.33M
    if (cmsChannelsOfColorSpace(ContextID, ColorSpaceOut) != (cmsInt32Number) cmsPipelineOutputChannels(ContextID, Result))
644
0
        goto Error;
645
646
1.33M
    return Result;
647
648
1.14M
Error:
649
650
1.14M
    if (Lut != NULL) cmsPipelineFree(ContextID, Lut);
651
1.14M
    if (Result != NULL) cmsPipelineFree(ContextID, Result);
652
1.14M
    return NULL;
653
654
0
    cmsUNUSED_PARAMETER(dwFlags);
655
0
}
656
657
658
// Wrapper for DLL calling convention
659
cmsPipeline*  CMSEXPORT _cmsDefaultICCintents(cmsContext     ContextID,
660
                                              cmsUInt32Number nProfiles,
661
                                              cmsUInt32Number TheIntents[],
662
                                              cmsHPROFILE     hProfiles[],
663
                                              cmsBool         BPC[],
664
                                              cmsFloat64Number AdaptationStates[],
665
                                              cmsUInt32Number dwFlags)
666
0
{
667
0
    return DefaultICCintents(ContextID, nProfiles, TheIntents, hProfiles, BPC, AdaptationStates, dwFlags);
668
0
}
669
670
// Black preserving intents ---------------------------------------------------------------------------------------------
671
672
// Translate black-preserving intents to ICC ones
673
static
674
cmsUInt32Number TranslateNonICCIntents(cmsUInt32Number Intent)
675
6
{
676
6
    switch (Intent) {
677
0
        case INTENT_PRESERVE_K_ONLY_PERCEPTUAL:
678
0
        case INTENT_PRESERVE_K_PLANE_PERCEPTUAL:
679
0
            return INTENT_PERCEPTUAL;
680
681
0
        case INTENT_PRESERVE_K_ONLY_RELATIVE_COLORIMETRIC:
682
6
        case INTENT_PRESERVE_K_PLANE_RELATIVE_COLORIMETRIC:
683
6
            return INTENT_RELATIVE_COLORIMETRIC;
684
685
0
        case INTENT_PRESERVE_K_ONLY_SATURATION:
686
0
        case INTENT_PRESERVE_K_PLANE_SATURATION:
687
0
            return INTENT_SATURATION;
688
689
0
        default: return Intent;
690
6
    }
691
6
}
692
693
// Sampler for Black-only preserving CMYK->CMYK transforms
694
695
typedef struct {
696
    cmsPipeline*    cmyk2cmyk;      // The original transform
697
    cmsToneCurve*   KTone;          // Black-to-black tone curve
698
699
} GrayOnlyParams;
700
701
702
// Preserve black only if that is the only ink used
703
static
704
int BlackPreservingGrayOnlySampler(cmsContext ContextID, CMSREGISTER const cmsUInt16Number In[], CMSREGISTER cmsUInt16Number Out[], CMSREGISTER void* Cargo)
705
0
{
706
0
    GrayOnlyParams* bp = (GrayOnlyParams*) Cargo;
707
708
    // If going across black only, keep black only
709
0
    if (In[0] == 0 && In[1] == 0 && In[2] == 0) {
710
711
        // TAC does not apply because it is black ink!
712
0
        Out[0] = Out[1] = Out[2] = 0;
713
0
        Out[3] = cmsEvalToneCurve16(ContextID, bp->KTone, In[3]);
714
0
        return TRUE;
715
0
    }
716
717
    // Keep normal transform for other colors
718
0
    bp ->cmyk2cmyk ->Eval16Fn(ContextID, In, Out, bp ->cmyk2cmyk->Data);
719
0
    return TRUE;
720
0
}
721
722
723
// Check whatever the profile is a CMYK->CMYK devicelink
724
static
725
cmsBool is_cmyk_devicelink(cmsContext ContextID, cmsHPROFILE hProfile)
726
3
{
727
3
    return cmsGetDeviceClass(ContextID, hProfile) == cmsSigLinkClass &&
728
0
            cmsGetColorSpace(ContextID, hProfile) == cmsSigCmykData;
729
3
}
730
731
// This is the entry for black-preserving K-only intents, which are non-ICC
732
static
733
cmsPipeline*  BlackPreservingKOnlyIntents(cmsContext     ContextID,
734
                                          cmsUInt32Number nProfiles,
735
                                          cmsUInt32Number TheIntents[],
736
                                          cmsHPROFILE     hProfiles[],
737
                                          cmsBool         BPC[],
738
                                          cmsFloat64Number AdaptationStates[],
739
                                          cmsUInt32Number dwFlags)
740
0
{
741
0
    GrayOnlyParams  bp;
742
0
    cmsPipeline*    Result;
743
0
    cmsUInt32Number ICCIntents[256];
744
0
    cmsStage*         CLUT;
745
0
    cmsUInt32Number i, nGridPoints;
746
0
    cmsUInt32Number lastProfilePos;
747
0
    cmsUInt32Number preservationProfilesCount;
748
0
    cmsHPROFILE hLastProfile;
749
750
751
    // Sanity check
752
0
    if (nProfiles < 1 || nProfiles > 255) return NULL;
753
754
    // Translate black-preserving intents to ICC ones
755
0
    for (i=0; i < nProfiles; i++)
756
0
        ICCIntents[i] = TranslateNonICCIntents(TheIntents[i]);
757
758
759
    // Trim all CMYK devicelinks at the end
760
0
    lastProfilePos = nProfiles - 1;
761
0
    hLastProfile = hProfiles[lastProfilePos];
762
763
    // Skip CMYK->CMYK devicelinks on ending
764
0
    while (is_cmyk_devicelink(ContextID, hLastProfile))
765
0
    {
766
0
        if (lastProfilePos < 2)
767
0
            break;
768
769
0
        hLastProfile = hProfiles[--lastProfilePos];
770
0
    }
771
772
773
0
    preservationProfilesCount = lastProfilePos + 1;
774
775
    // Check for non-cmyk profiles
776
0
    if (cmsGetColorSpace(ContextID, hProfiles[0]) != cmsSigCmykData ||
777
0
        !(cmsGetColorSpace(ContextID, hLastProfile) == cmsSigCmykData ||
778
0
        cmsGetDeviceClass(ContextID, hLastProfile) == cmsSigOutputClass))
779
0
           return DefaultICCintents(ContextID, nProfiles, ICCIntents, hProfiles, BPC, AdaptationStates, dwFlags);
780
781
    // Allocate an empty LUT for holding the result
782
0
    Result = cmsPipelineAlloc(ContextID, 4, 4);
783
0
    if (Result == NULL) return NULL;
784
785
0
    memset(&bp, 0, sizeof(bp));
786
787
    // Create a LUT holding normal ICC transform
788
0
    bp.cmyk2cmyk = DefaultICCintents(ContextID,
789
0
        preservationProfilesCount,
790
0
        ICCIntents,
791
0
        hProfiles,
792
0
        BPC,
793
0
        AdaptationStates,
794
0
        dwFlags);
795
796
0
    if (bp.cmyk2cmyk == NULL) goto Error;
797
798
    // Now, compute the tone curve
799
0
    bp.KTone = _cmsBuildKToneCurve(ContextID,
800
0
        4096,
801
0
        preservationProfilesCount,
802
0
        ICCIntents,
803
0
        hProfiles,
804
0
        BPC,
805
0
        AdaptationStates,
806
0
        dwFlags);
807
808
0
    if (bp.KTone == NULL) goto Error;
809
810
811
    // How many gridpoints are we going to use?
812
0
    nGridPoints = _cmsReasonableGridpointsByColorspace(ContextID, cmsSigCmykData, dwFlags);
813
814
    // Create the CLUT. 16 bits
815
0
    CLUT = cmsStageAllocCLut16bit(ContextID, nGridPoints, 4, 4, NULL);
816
0
    if (CLUT == NULL) goto Error;
817
818
    // This is the one and only MPE in this LUT
819
0
    if (!cmsPipelineInsertStage(ContextID, Result, cmsAT_BEGIN, CLUT))
820
0
        goto Error;
821
822
    // Sample it. We cannot afford pre/post linearization this time.
823
0
    if (!cmsStageSampleCLut16bit(ContextID, CLUT, BlackPreservingGrayOnlySampler, (void*) &bp, 0))
824
0
        goto Error;
825
826
827
    // Insert possible devicelinks at the end
828
0
    for (i = lastProfilePos + 1; i < nProfiles; i++)
829
0
    {
830
0
        cmsPipeline* devlink = _cmsReadDevicelinkLUT(ContextID, hProfiles[i], ICCIntents[i]);
831
0
        if (devlink == NULL)
832
0
            goto Error;
833
834
0
        if (!cmsPipelineCat(ContextID, Result, devlink))
835
0
            goto Error;
836
0
    }
837
838
839
    // Get rid of xform and tone curve
840
0
    cmsPipelineFree(ContextID, bp.cmyk2cmyk);
841
0
    cmsFreeToneCurve(ContextID, bp.KTone);
842
843
0
    return Result;
844
845
0
Error:
846
847
0
    if (bp.cmyk2cmyk != NULL) cmsPipelineFree(ContextID, bp.cmyk2cmyk);
848
0
    if (bp.KTone != NULL)  cmsFreeToneCurve(ContextID, bp.KTone);
849
0
    if (Result != NULL) cmsPipelineFree(ContextID, Result);
850
0
    return NULL;
851
852
0
}
853
854
// K Plane-preserving CMYK to CMYK ------------------------------------------------------------------------------------
855
856
typedef struct {
857
858
    cmsPipeline*     cmyk2cmyk;     // The original transform
859
    cmsHTRANSFORM    hProofOutput;  // Output CMYK to Lab (last profile)
860
    cmsHTRANSFORM    cmyk2Lab;      // The input chain
861
    cmsToneCurve*    KTone;         // Black-to-black tone curve
862
    cmsPipeline*     LabK2cmyk;     // The output profile
863
    cmsFloat64Number MaxError;
864
865
    cmsHTRANSFORM    hRoundTrip;
866
    cmsFloat64Number MaxTAC;
867
868
869
} PreserveKPlaneParams;
870
871
872
// The CLUT will be stored at 16 bits, but calculations are performed at cmsFloat32Number precision
873
static
874
int BlackPreservingSampler(cmsContext ContextID, CMSREGISTER const cmsUInt16Number In[], CMSREGISTER cmsUInt16Number Out[], CMSREGISTER void* Cargo)
875
0
{
876
0
    int i;
877
0
    cmsFloat32Number Inf[4], Outf[4];
878
0
    cmsFloat32Number LabK[4];
879
0
    cmsFloat64Number SumCMY, SumCMYK, Error, Ratio;
880
0
    cmsCIELab ColorimetricLab, BlackPreservingLab;
881
0
    PreserveKPlaneParams* bp = (PreserveKPlaneParams*) Cargo;
882
883
    // Convert from 16 bits to floating point
884
0
    for (i=0; i < 4; i++)
885
0
        Inf[i] = (cmsFloat32Number) (In[i] / 65535.0);
886
887
    // Get the K across Tone curve
888
0
    LabK[3] = cmsEvalToneCurveFloat(ContextID, bp ->KTone, Inf[3]);
889
890
    // If going across black only, keep black only
891
0
    if (In[0] == 0 && In[1] == 0 && In[2] == 0) {
892
893
0
        Out[0] = Out[1] = Out[2] = 0;
894
0
        Out[3] = _cmsQuickSaturateWord(LabK[3] * 65535.0);
895
0
        return TRUE;
896
0
    }
897
898
    // Try the original transform,
899
0
    cmsPipelineEvalFloat(ContextID, Inf, Outf, bp ->cmyk2cmyk);
900
901
    // Store a copy of the floating point result into 16-bit
902
0
    for (i=0; i < 4; i++)
903
0
            Out[i] = _cmsQuickSaturateWord(Outf[i] * 65535.0);
904
905
    // Maybe K is already ok (mostly on K=0)
906
0
    if (fabsf(Outf[3] - LabK[3]) < (3.0 / 65535.0)) {
907
0
        return TRUE;
908
0
    }
909
910
    // K differ, measure and keep Lab measurement for further usage
911
    // this is done in relative colorimetric intent
912
0
    cmsDoTransform(ContextID, bp->hProofOutput, Out, &ColorimetricLab, 1);
913
914
    // Is not black only and the transform doesn't keep black.
915
    // Obtain the Lab of output CMYK. After that we have Lab + K
916
0
    cmsDoTransform(ContextID, bp ->cmyk2Lab, Outf, LabK, 1);
917
918
    // Obtain the corresponding CMY using reverse interpolation
919
    // (K is fixed in LabK[3])
920
0
    if (!cmsPipelineEvalReverseFloat(ContextID, LabK, Outf, Outf, bp ->LabK2cmyk)) {
921
922
        // Cannot find a suitable value, so use colorimetric xform
923
        // which is already stored in Out[]
924
0
        return TRUE;
925
0
    }
926
927
    // Make sure to pass through K (which now is fixed)
928
0
    Outf[3] = LabK[3];
929
930
    // Apply TAC if needed
931
0
    SumCMY   = (cmsFloat64Number) Outf[0]  + Outf[1] + Outf[2];
932
0
    SumCMYK  = SumCMY + Outf[3];
933
934
0
    if (SumCMYK > bp ->MaxTAC) {
935
936
0
        Ratio = 1 - ((SumCMYK - bp->MaxTAC) / SumCMY);
937
0
        if (Ratio < 0)
938
0
            Ratio = 0;
939
0
    }
940
0
    else
941
0
       Ratio = 1.0;
942
943
0
    Out[0] = _cmsQuickSaturateWord(Outf[0] * Ratio * 65535.0);     // C
944
0
    Out[1] = _cmsQuickSaturateWord(Outf[1] * Ratio * 65535.0);     // M
945
0
    Out[2] = _cmsQuickSaturateWord(Outf[2] * Ratio * 65535.0);     // Y
946
0
    Out[3] = _cmsQuickSaturateWord(Outf[3] * 65535.0);
947
948
    // Estimate the error (this goes 16 bits to Lab DBL)
949
0
    cmsDoTransform(ContextID, bp->hProofOutput, Out, &BlackPreservingLab, 1);
950
0
    Error = cmsDeltaE(ContextID, &ColorimetricLab, &BlackPreservingLab);
951
0
    if (Error > bp -> MaxError)
952
0
        bp->MaxError = Error;
953
954
0
    return TRUE;
955
0
}
956
957
958
959
// This is the entry for black-plane preserving, which are non-ICC
960
static
961
cmsPipeline* BlackPreservingKPlaneIntents(cmsContext     ContextID,
962
                                          cmsUInt32Number nProfiles,
963
                                          cmsUInt32Number TheIntents[],
964
                                          cmsHPROFILE     hProfiles[],
965
                                          cmsBool         BPC[],
966
                                          cmsFloat64Number AdaptationStates[],
967
                                          cmsUInt32Number dwFlags)
968
3
{
969
3
    PreserveKPlaneParams bp;
970
971
3
    cmsPipeline*    Result = NULL;
972
3
    cmsUInt32Number ICCIntents[256];
973
3
    cmsStage*         CLUT;
974
3
    cmsUInt32Number i, nGridPoints;
975
3
    cmsUInt32Number lastProfilePos;
976
3
    cmsUInt32Number preservationProfilesCount;
977
3
    cmsHPROFILE hLastProfile;
978
3
    cmsHPROFILE hLab;
979
980
    // Sanity check
981
3
    if (nProfiles < 1 || nProfiles > 255) return NULL;
982
983
    // Translate black-preserving intents to ICC ones
984
9
    for (i=0; i < nProfiles; i++)
985
6
        ICCIntents[i] = TranslateNonICCIntents(TheIntents[i]);
986
987
    // Trim all CMYK devicelinks at the end
988
3
    lastProfilePos = nProfiles - 1;
989
3
    hLastProfile = hProfiles[lastProfilePos];
990
991
    // Skip CMYK->CMYK devicelinks on ending
992
3
    while (is_cmyk_devicelink(ContextID, hLastProfile))
993
0
    {
994
0
        if (lastProfilePos < 2)
995
0
            break;
996
997
0
        hLastProfile = hProfiles[--lastProfilePos];
998
0
    }
999
1000
3
    preservationProfilesCount = lastProfilePos + 1;
1001
1002
    // Check for non-cmyk profiles
1003
3
    if (cmsGetColorSpace(ContextID, hProfiles[0]) != cmsSigCmykData ||
1004
3
        !(cmsGetColorSpace(ContextID, hLastProfile) == cmsSigCmykData ||
1005
3
        cmsGetDeviceClass(ContextID, hLastProfile) == cmsSigOutputClass))
1006
3
           return  DefaultICCintents(ContextID, nProfiles, ICCIntents, hProfiles, BPC, AdaptationStates, dwFlags);
1007
1008
0
    memset(&bp, 0, sizeof(bp));
1009
1010
    // We need the input LUT of the last profile, assuming this one is responsible of
1011
    // black generation. This LUT will be searched in inverse order.
1012
0
    bp.LabK2cmyk = _cmsReadInputLUT(ContextID, hLastProfile, INTENT_RELATIVE_COLORIMETRIC);
1013
0
    if (bp.LabK2cmyk == NULL) goto Cleanup;
1014
1015
    // Get total area coverage (in 0..1 domain)
1016
0
    bp.MaxTAC = cmsDetectTAC(ContextID, hLastProfile) / 100.0;
1017
0
    if (bp.MaxTAC <= 0) goto Cleanup;
1018
1019
1020
    // Create a LUT holding normal ICC transform
1021
0
    bp.cmyk2cmyk = DefaultICCintents(ContextID,
1022
0
                                         preservationProfilesCount,
1023
0
                                         ICCIntents,
1024
0
                                         hProfiles,
1025
0
                                         BPC,
1026
0
                                         AdaptationStates,
1027
0
                                         dwFlags);
1028
0
    if (bp.cmyk2cmyk == NULL) goto Cleanup;
1029
1030
    // Now the tone curve
1031
0
    bp.KTone = _cmsBuildKToneCurve(ContextID, 4096, preservationProfilesCount,
1032
0
                                   ICCIntents,
1033
0
                                   hProfiles,
1034
0
                                   BPC,
1035
0
                                   AdaptationStates,
1036
0
                                   dwFlags);
1037
0
    if (bp.KTone == NULL) goto Cleanup;
1038
1039
    // To measure the output, Last profile to Lab
1040
0
    hLab = cmsCreateLab4Profile(ContextID, NULL);
1041
0
    bp.hProofOutput = cmsCreateTransform(ContextID, hLastProfile,
1042
0
                                         CHANNELS_SH(4)|BYTES_SH(2), hLab, TYPE_Lab_DBL,
1043
0
                                         INTENT_RELATIVE_COLORIMETRIC,
1044
0
                                         cmsFLAGS_NOCACHE|cmsFLAGS_NOOPTIMIZE);
1045
0
    if ( bp.hProofOutput == NULL) goto Cleanup;
1046
1047
    // Same as anterior, but lab in the 0..1 range
1048
0
    bp.cmyk2Lab = cmsCreateTransform(ContextID, hLastProfile,
1049
0
                                         FLOAT_SH(1)|CHANNELS_SH(4)|BYTES_SH(4), hLab,
1050
0
                                         FLOAT_SH(1)|CHANNELS_SH(3)|BYTES_SH(4),
1051
0
                                         INTENT_RELATIVE_COLORIMETRIC,
1052
0
                                         cmsFLAGS_NOCACHE|cmsFLAGS_NOOPTIMIZE);
1053
0
    if (bp.cmyk2Lab == NULL) goto Cleanup;
1054
0
    cmsCloseProfile(ContextID, hLab);
1055
1056
    // Error estimation (for debug only)
1057
0
    bp.MaxError = 0;
1058
1059
    // How many gridpoints are we going to use?
1060
0
    nGridPoints = _cmsReasonableGridpointsByColorspace(ContextID, cmsSigCmykData, dwFlags);
1061
1062
1063
0
    CLUT = cmsStageAllocCLut16bit(ContextID, nGridPoints, 4, 4, NULL);
1064
0
    if (CLUT == NULL) goto Cleanup;
1065
1066
    // Allocate an empty LUT for holding the result
1067
0
    Result = cmsPipelineAlloc(ContextID, 4, 4);
1068
0
    if (Result == NULL) goto Cleanup;
1069
1070
0
    if (!cmsPipelineInsertStage(ContextID, Result, cmsAT_BEGIN, CLUT)) {
1071
0
        cmsPipelineFree(ContextID, Result);
1072
0
        Result = NULL;
1073
0
        goto Cleanup;
1074
0
    }
1075
1076
0
    cmsStageSampleCLut16bit(ContextID, CLUT, BlackPreservingSampler, (void*) &bp, 0);
1077
1078
    // Insert possible devicelinks at the end
1079
0
    for (i = lastProfilePos + 1; i < nProfiles; i++)
1080
0
    {
1081
0
        cmsPipeline* devlink = _cmsReadDevicelinkLUT(ContextID, hProfiles[i], ICCIntents[i]);
1082
0
        if (devlink == NULL) {
1083
0
            cmsPipelineFree(ContextID, Result);
1084
0
            Result = NULL;
1085
0
            goto Cleanup;
1086
0
        }
1087
1088
0
        if (!cmsPipelineCat(ContextID, Result, devlink)) {
1089
0
            cmsPipelineFree(ContextID, Result);
1090
0
            Result = NULL;
1091
0
        }
1092
0
    }
1093
1094
1095
0
Cleanup:
1096
1097
0
    if (bp.cmyk2cmyk) cmsPipelineFree(ContextID, bp.cmyk2cmyk);
1098
0
    if (bp.cmyk2Lab) cmsDeleteTransform(ContextID, bp.cmyk2Lab);
1099
0
    if (bp.hProofOutput) cmsDeleteTransform(ContextID, bp.hProofOutput);
1100
1101
0
    if (bp.KTone) cmsFreeToneCurve(ContextID, bp.KTone);
1102
0
    if (bp.LabK2cmyk) cmsPipelineFree(ContextID, bp.LabK2cmyk);
1103
1104
0
    return Result;
1105
0
}
1106
1107
1108
1109
// Link routines ------------------------------------------------------------------------------------------------------
1110
1111
// Chain several profiles into a single LUT. It just checks the parameters and then calls the handler
1112
// for the first intent in chain. The handler may be user-defined. Is up to the handler to deal with the
1113
// rest of intents in chain. A maximum of 255 profiles at time are supported, which is pretty reasonable.
1114
cmsPipeline* _cmsLinkProfiles(cmsContext     ContextID,
1115
                              cmsUInt32Number nProfiles,
1116
                              cmsUInt32Number TheIntents[],
1117
                              cmsHPROFILE     hProfiles[],
1118
                              cmsBool         BPC[],
1119
                              cmsFloat64Number AdaptationStates[],
1120
                              cmsUInt32Number dwFlags)
1121
2.48M
{
1122
2.48M
    cmsUInt32Number i;
1123
2.48M
    cmsIntentsList* Intent;
1124
1125
    // Make sure a reasonable number of profiles is provided
1126
2.48M
    if (nProfiles <= 0 || nProfiles > 255) {
1127
0
         cmsSignalError(ContextID, cmsERROR_RANGE, "Couldn't link '%d' profiles", nProfiles);
1128
0
        return NULL;
1129
0
    }
1130
1131
7.48M
    for (i=0; i < nProfiles; i++) {
1132
1133
        // Check if black point is really needed or allowed. Note that
1134
        // following Adobe's document:
1135
        // BPC does not apply to devicelink profiles, nor to abs colorimetric,
1136
        // and applies always on V4 perceptual and saturation.
1137
1138
5.00M
        if (TheIntents[i] == INTENT_ABSOLUTE_COLORIMETRIC)
1139
254k
            BPC[i] = FALSE;
1140
1141
5.00M
        if (TheIntents[i] == INTENT_PERCEPTUAL || TheIntents[i] == INTENT_SATURATION) {
1142
1143
            // Force BPC for V4 profiles in perceptual and saturation
1144
594k
            if (cmsGetEncodedICCversion(ContextID, hProfiles[i]) >= 0x4000000)
1145
3.92k
                BPC[i] = TRUE;
1146
594k
        }
1147
5.00M
    }
1148
1149
    // Search for a handler. The first intent in the chain defines the handler. That would
1150
    // prevent using multiple custom intents in a multiintent chain, but the behaviour of
1151
    // this case would present some issues if the custom intent tries to do things like
1152
    // preserve primaries. This solution is not perfect, but works well on most cases.
1153
1154
2.48M
    Intent = SearchIntent(ContextID, TheIntents[0]);
1155
2.48M
    if (Intent == NULL) {
1156
0
        cmsSignalError(ContextID, cmsERROR_UNKNOWN_EXTENSION, "Unsupported intent '%d'", TheIntents[0]);
1157
0
        return NULL;
1158
0
    }
1159
1160
    // Call the handler
1161
2.48M
    return Intent ->Link(ContextID, nProfiles, TheIntents, hProfiles, BPC, AdaptationStates, dwFlags);
1162
2.48M
}
1163
1164
// -------------------------------------------------------------------------------------------------
1165
1166
// Get information about available intents. nMax is the maximum space for the supplied "Codes"
1167
// and "Descriptions" the function returns the total number of intents, which may be greater
1168
// than nMax, although the matrices are not populated beyond this level.
1169
cmsUInt32Number CMSEXPORT cmsGetSupportedIntents(cmsContext ContextID, cmsUInt32Number nMax, cmsUInt32Number* Codes, char** Descriptions)
1170
0
{
1171
0
    _cmsIntentsPluginChunkType* ctx = ( _cmsIntentsPluginChunkType*) _cmsContextGetClientChunk(ContextID, IntentPlugin);
1172
0
    cmsIntentsList* pt;
1173
0
    cmsUInt32Number nIntents;
1174
1175
0
    for (nIntents=0, pt = DefaultIntents; pt != NULL; pt = pt -> Next)
1176
0
    {
1177
0
        if (nIntents < nMax) {
1178
0
            if (Codes != NULL)
1179
0
                Codes[nIntents] = pt ->Intent;
1180
1181
0
            if (Descriptions != NULL)
1182
0
                Descriptions[nIntents] = pt ->Description;
1183
0
        }
1184
1185
0
        nIntents++;
1186
0
    }
1187
1188
0
    for (pt = ctx->Intents; pt != NULL; pt = pt -> Next)
1189
0
    {
1190
0
        if (nIntents < nMax) {
1191
0
            if (Codes != NULL)
1192
0
                Codes[nIntents] = pt ->Intent;
1193
1194
0
            if (Descriptions != NULL)
1195
0
                Descriptions[nIntents] = pt ->Description;
1196
0
        }
1197
1198
0
        nIntents++;
1199
0
    }
1200
1201
0
    return nIntents;
1202
0
}
1203
1204
// The plug-in registration. User can add new intents or override default routines
1205
cmsBool  _cmsRegisterRenderingIntentPlugin(cmsContext id, cmsPluginBase* Data)
1206
147k
{
1207
147k
    _cmsIntentsPluginChunkType* ctx = ( _cmsIntentsPluginChunkType*) _cmsContextGetClientChunk(id, IntentPlugin);
1208
147k
    cmsPluginRenderingIntent* Plugin = (cmsPluginRenderingIntent*) Data;
1209
147k
    cmsIntentsList* fl;
1210
1211
    // Do we have to reset the custom intents?
1212
147k
    if (Data == NULL) {
1213
1214
147k
        ctx->Intents = NULL;
1215
147k
        return TRUE;
1216
147k
    }
1217
1218
0
    fl = (cmsIntentsList*) _cmsPluginMalloc(id, sizeof(cmsIntentsList));
1219
0
    if (fl == NULL) return FALSE;
1220
1221
1222
0
    fl ->Intent  = Plugin ->Intent;
1223
0
    strncpy(fl ->Description, Plugin ->Description, sizeof(fl ->Description)-1);
1224
0
    fl ->Description[sizeof(fl ->Description)-1] = 0;
1225
1226
0
    fl ->Link    = Plugin ->Link;
1227
1228
0
    fl ->Next = ctx ->Intents;
1229
0
    ctx ->Intents = fl;
1230
1231
0
    return TRUE;
1232
0
}