Coverage Report

Created: 2026-07-25 06:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/lcms/src/cmsxform.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
// Transformations stuff
30
// -----------------------------------------------------------------------
31
32
12.9k
#define DEFAULT_OBSERVER_ADAPTATION_STATE 1.0
33
34
// The Context0 observer adaptation state.
35
_cmsAdaptationStateChunkType _cmsAdaptationStateChunk = { DEFAULT_OBSERVER_ADAPTATION_STATE };
36
37
// Init and duplicate observer adaptation state
38
void _cmsAllocAdaptationStateChunk(struct _cmsContext_struct* ctx, 
39
                                   const struct _cmsContext_struct* src)
40
12.9k
{
41
12.9k
    static _cmsAdaptationStateChunkType AdaptationStateChunk = { DEFAULT_OBSERVER_ADAPTATION_STATE };
42
12.9k
    void* from;
43
     
44
12.9k
    if (src != NULL) {
45
0
        from = src ->chunks[AdaptationStateContext];       
46
0
    }
47
12.9k
    else {
48
12.9k
       from = &AdaptationStateChunk;
49
12.9k
    }
50
    
51
12.9k
    ctx ->chunks[AdaptationStateContext] = _cmsSubAllocDup(ctx ->MemPool, from, sizeof(_cmsAdaptationStateChunkType));     
52
12.9k
}
53
54
55
// Sets adaptation state for absolute colorimetric intent in the given context.  Adaptation state applies on all 
56
// but cmsCreateExtendedTransformTHR().  Little CMS can handle incomplete adaptation states.
57
cmsFloat64Number CMSEXPORT cmsSetAdaptationStateTHR(cmsContext ContextID, cmsFloat64Number d)
58
98.8k
{
59
98.8k
    cmsFloat64Number prev;
60
98.8k
    _cmsAdaptationStateChunkType* ptr = (_cmsAdaptationStateChunkType*) _cmsContextGetClientChunk(ContextID, AdaptationStateContext);
61
62
    // Get previous value for return
63
98.8k
    prev = ptr ->AdaptationState;
64
65
    // Set the value if d is positive or zero
66
98.8k
    if (d >= 0.0) {
67
68
0
        ptr ->AdaptationState = d;
69
0
    }
70
71
    // Always return previous value
72
98.8k
    return prev;
73
98.8k
}
74
75
76
// The adaptation state may be defaulted by this function. If you don't like it, use the extended transform routine
77
cmsFloat64Number CMSEXPORT cmsSetAdaptationState(cmsFloat64Number d)
78
0
{    
79
0
    return cmsSetAdaptationStateTHR(NULL, d);
80
0
}
81
82
// -----------------------------------------------------------------------
83
84
// Alarm codes for 16-bit transformations, because the fixed range of containers there are
85
// no values left to mark out of gamut. 
86
87
12.9k
#define DEFAULT_ALARM_CODES_VALUE {0x7F00, 0x7F00, 0x7F00, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
88
89
_cmsAlarmCodesChunkType _cmsAlarmCodesChunk = { DEFAULT_ALARM_CODES_VALUE };
90
91
// Sets the codes used to mark out-out-gamut on Proofing transforms for a given context. Values are meant to be 
92
// encoded in 16 bits.
93
void CMSEXPORT cmsSetAlarmCodesTHR(cmsContext ContextID, const cmsUInt16Number AlarmCodesP[cmsMAXCHANNELS])
94
0
{
95
0
    _cmsAlarmCodesChunkType* ContextAlarmCodes = (_cmsAlarmCodesChunkType*) _cmsContextGetClientChunk(ContextID, AlarmCodesContext);
96
       
97
0
    _cmsAssert(ContextAlarmCodes != NULL); // Can't happen
98
    
99
0
    memcpy(ContextAlarmCodes->AlarmCodes, AlarmCodesP, sizeof(ContextAlarmCodes->AlarmCodes));    
100
0
}
101
102
// Gets the current codes used to mark out-out-gamut on Proofing transforms for the given context.
103
// Values are meant to be encoded in 16 bits.
104
void CMSEXPORT cmsGetAlarmCodesTHR(cmsContext ContextID, cmsUInt16Number AlarmCodesP[cmsMAXCHANNELS])
105
0
{
106
0
    _cmsAlarmCodesChunkType* ContextAlarmCodes = (_cmsAlarmCodesChunkType*) _cmsContextGetClientChunk(ContextID, AlarmCodesContext);
107
108
0
    _cmsAssert(ContextAlarmCodes != NULL); // Can't happen
109
110
0
    memcpy(AlarmCodesP, ContextAlarmCodes->AlarmCodes, sizeof(ContextAlarmCodes->AlarmCodes));
111
0
}
112
113
void CMSEXPORT cmsSetAlarmCodes(const cmsUInt16Number NewAlarm[cmsMAXCHANNELS])
114
0
{
115
0
    _cmsAssert(NewAlarm != NULL);
116
117
0
    cmsSetAlarmCodesTHR(NULL, NewAlarm);
118
0
}
119
120
void CMSEXPORT cmsGetAlarmCodes(cmsUInt16Number OldAlarm[cmsMAXCHANNELS])
121
0
{ 
122
0
    _cmsAssert(OldAlarm != NULL);
123
0
    cmsGetAlarmCodesTHR(NULL, OldAlarm);
124
0
}
125
126
127
// Init and duplicate alarm codes
128
void _cmsAllocAlarmCodesChunk(struct _cmsContext_struct* ctx, 
129
                              const struct _cmsContext_struct* src)
130
12.9k
{
131
12.9k
    static _cmsAlarmCodesChunkType AlarmCodesChunk = { DEFAULT_ALARM_CODES_VALUE };
132
12.9k
    void* from;
133
     
134
12.9k
    if (src != NULL) {
135
0
        from = src ->chunks[AlarmCodesContext];       
136
0
    }
137
12.9k
    else {
138
12.9k
       from = &AlarmCodesChunk;
139
12.9k
    }
140
    
141
12.9k
    ctx ->chunks[AlarmCodesContext] = _cmsSubAllocDup(ctx ->MemPool, from, sizeof(_cmsAlarmCodesChunkType));     
142
12.9k
}
143
144
// -----------------------------------------------------------------------
145
146
// Get rid of transform resources
147
void CMSEXPORT cmsDeleteTransform(cmsHTRANSFORM hTransform)
148
18.1k
{
149
18.1k
    _cmsTRANSFORM* p = (_cmsTRANSFORM*) hTransform;
150
151
18.1k
    _cmsAssert(p != NULL);
152
153
18.1k
    if (p -> GamutCheck)
154
0
        cmsPipelineFree(p -> GamutCheck);
155
156
18.1k
    if (p -> Lut)
157
17.5k
        cmsPipelineFree(p -> Lut);
158
159
18.1k
    if (p ->InputColorant)
160
144
        cmsFreeNamedColorList(p ->InputColorant);
161
162
18.1k
    if (p -> OutputColorant)
163
47
        cmsFreeNamedColorList(p ->OutputColorant);
164
165
18.1k
    if (p ->Sequence)
166
827
        cmsFreeProfileSequenceDescription(p ->Sequence);
167
168
18.1k
    if (p ->UserData)
169
0
        p ->FreeUserData(p ->ContextID, p ->UserData);
170
171
18.1k
    _cmsFree(p ->ContextID, (void *) p);
172
18.1k
}
173
174
175
static
176
cmsUInt32Number PixelSize(cmsUInt32Number Format)
177
3.18M
{
178
3.18M
    cmsUInt32Number fmt_bytes = T_BYTES(Format);
179
180
    // For double, the T_BYTES field is zero
181
3.18M
    if (fmt_bytes == 0)
182
282k
        return sizeof(cmsUInt64Number);
183
184
    // Otherwise, it is already correct for all formats
185
2.90M
    return fmt_bytes;
186
3.18M
}
187
188
189
190
191
// Apply transform.
192
void CMSEXPORT cmsDoTransform(cmsHTRANSFORM  Transform,
193
                              const void* InputBuffer,
194
                              void* OutputBuffer,
195
                              cmsUInt32Number Size)
196
197
1.59M
{
198
1.59M
    _cmsTRANSFORM* p = (_cmsTRANSFORM*) Transform;
199
1.59M
    cmsStride stride;
200
201
1.59M
    stride.BytesPerLineIn = 0;  // Not used
202
1.59M
    stride.BytesPerLineOut = 0;
203
1.59M
    stride.BytesPerPlaneIn = Size * PixelSize(p->InputFormat);
204
1.59M
    stride.BytesPerPlaneOut = Size * PixelSize(p->OutputFormat);
205
           
206
1.59M
    p -> xform(p, InputBuffer, OutputBuffer, Size, 1, &stride);
207
1.59M
}
208
209
210
// This is a legacy stride for planar
211
void CMSEXPORT cmsDoTransformStride(cmsHTRANSFORM  Transform,
212
                              const void* InputBuffer,
213
                              void* OutputBuffer,
214
                              cmsUInt32Number Size, cmsUInt32Number Stride)
215
216
0
{
217
0
    _cmsTRANSFORM* p = (_cmsTRANSFORM*) Transform;
218
0
    cmsStride stride;
219
220
0
    stride.BytesPerLineIn = 0;  
221
0
    stride.BytesPerLineOut = 0;
222
0
    stride.BytesPerPlaneIn = Stride;
223
0
    stride.BytesPerPlaneOut = Stride;
224
225
0
    p -> xform(p, InputBuffer, OutputBuffer, Size, 1, &stride);
226
0
}
227
228
// This is the "fast" function for plugins
229
void CMSEXPORT cmsDoTransformLineStride(cmsHTRANSFORM  Transform,
230
                              const void* InputBuffer,
231
                              void* OutputBuffer,
232
                              cmsUInt32Number PixelsPerLine,
233
                              cmsUInt32Number LineCount,
234
                              cmsUInt32Number BytesPerLineIn,
235
                              cmsUInt32Number BytesPerLineOut,
236
                              cmsUInt32Number BytesPerPlaneIn,
237
                              cmsUInt32Number BytesPerPlaneOut)
238
239
0
{
240
0
    _cmsTRANSFORM* p = (_cmsTRANSFORM*) Transform;
241
0
    cmsStride stride;
242
243
0
    stride.BytesPerLineIn = BytesPerLineIn;
244
0
    stride.BytesPerLineOut = BytesPerLineOut;
245
0
    stride.BytesPerPlaneIn = BytesPerPlaneIn;
246
0
    stride.BytesPerPlaneOut = BytesPerPlaneOut;
247
248
0
    p->xform(p, InputBuffer, OutputBuffer, PixelsPerLine, LineCount, &stride);
249
0
}
250
251
252
253
// Transform routines ----------------------------------------------------------------------------------------------------------
254
255
// Float xform converts floats. Since there are no performance issues, one routine does all job, including gamut check.
256
// Note that because extended range, we can use a -1.0 value for out of gamut in this case.
257
static
258
void FloatXFORM(_cmsTRANSFORM* p,
259
                const void* in,
260
                void* out, 
261
                cmsUInt32Number PixelsPerLine,
262
                cmsUInt32Number LineCount,
263
                const cmsStride* Stride)
264
1.55M
{
265
1.55M
    cmsUInt8Number* accum;
266
1.55M
    cmsUInt8Number* output;
267
1.55M
    cmsFloat32Number fIn[cmsMAXCHANNELS], fOut[cmsMAXCHANNELS];
268
1.55M
    cmsFloat32Number OutOfGamut;
269
1.55M
    size_t i, j, c, strideIn, strideOut;
270
271
1.55M
    _cmsHandleExtraChannels(p, in, out, PixelsPerLine, LineCount, Stride);
272
273
1.55M
    strideIn = 0;
274
1.55M
    strideOut = 0;
275
1.55M
    memset(fIn, 0, sizeof(fIn));
276
1.55M
    memset(fOut, 0, sizeof(fOut));
277
278
3.11M
    for (i = 0; i < LineCount; i++) {
279
280
1.55M
        accum = (cmsUInt8Number*)in + strideIn;
281
1.55M
        output = (cmsUInt8Number*)out + strideOut;
282
283
3.23M
        for (j = 0; j < PixelsPerLine; j++) {
284
285
1.67M
            accum = p->FromInputFloat(p, fIn, accum, Stride->BytesPerPlaneIn);
286
287
            // Any gamut check to do?
288
1.67M
            if (p->GamutCheck != NULL) {
289
290
                // Evaluate gamut marker.
291
0
                cmsPipelineEvalFloat(fIn, &OutOfGamut, p->GamutCheck);
292
293
                // Is current color out of gamut?
294
0
                if (OutOfGamut > 0.0) {
295
296
0
                    _cmsAlarmCodesChunkType* ContextAlarmCodes = (_cmsAlarmCodesChunkType*)_cmsContextGetClientChunk(p->ContextID, AlarmCodesContext);
297
298
                    // Certainly, out of gamut
299
0
                    for (c = 0; c < cmsMAXCHANNELS; c++)
300
0
                        fOut[c] = ContextAlarmCodes->AlarmCodes[c] / 65535.0F;
301
302
0
                }
303
0
                else {
304
                    // No, proceed normally
305
0
                    cmsPipelineEvalFloat(fIn, fOut, p->Lut);
306
0
                }
307
0
            }
308
1.67M
            else {
309
310
                // No gamut check at all
311
1.67M
                cmsPipelineEvalFloat(fIn, fOut, p->Lut);
312
1.67M
            }
313
314
315
1.67M
            output = p->ToOutputFloat(p, fOut, output, Stride->BytesPerPlaneOut);
316
1.67M
        }
317
318
1.55M
        strideIn += Stride->BytesPerLineIn;
319
1.55M
        strideOut += Stride->BytesPerLineOut;
320
1.55M
    }
321
322
1.55M
}
323
324
325
static
326
void NullFloatXFORM(_cmsTRANSFORM* p,
327
                    const void* in,
328
                    void* out, 
329
                    cmsUInt32Number PixelsPerLine,
330
                    cmsUInt32Number LineCount,
331
                    const cmsStride* Stride)
332
333
50
{
334
50
    cmsUInt8Number* accum;
335
50
    cmsUInt8Number* output;
336
50
    cmsFloat32Number fIn[cmsMAXCHANNELS];
337
50
    size_t i, j, strideIn, strideOut;
338
339
50
    _cmsHandleExtraChannels(p, in, out, PixelsPerLine, LineCount, Stride);
340
341
50
    strideIn = 0;
342
50
    strideOut = 0;
343
50
    memset(fIn, 0, sizeof(fIn));
344
345
100
    for (i = 0; i < LineCount; i++) {
346
347
50
           accum = (cmsUInt8Number*) in + strideIn;
348
50
           output = (cmsUInt8Number*) out + strideOut;
349
350
100
           for (j = 0; j < PixelsPerLine; j++) {
351
352
50
                  accum = p->FromInputFloat(p, fIn, accum, Stride ->BytesPerPlaneIn);
353
50
                  output = p->ToOutputFloat(p, fIn, output, Stride->BytesPerPlaneOut);
354
50
           }
355
356
50
           strideIn += Stride->BytesPerLineIn;
357
50
           strideOut += Stride->BytesPerLineOut;
358
50
    }
359
50
}
360
361
// 16 bit precision -----------------------------------------------------------------------------------------------------------
362
363
// Null transformation, only applies formatters. No cache
364
static
365
void NullXFORM(_cmsTRANSFORM* p,
366
               const void* in,
367
               void* out,
368
               cmsUInt32Number PixelsPerLine,
369
               cmsUInt32Number LineCount,
370
               const cmsStride* Stride)
371
56
{
372
56
    cmsUInt8Number* accum;
373
56
    cmsUInt8Number* output;
374
56
    cmsUInt16Number wIn[cmsMAXCHANNELS];
375
56
    size_t i, j, strideIn, strideOut;
376
377
56
    _cmsHandleExtraChannels(p, in, out, PixelsPerLine, LineCount, Stride);
378
379
56
    strideIn = 0;
380
56
    strideOut = 0;
381
56
    memset(wIn, 0, sizeof(wIn));
382
383
112
    for (i = 0; i < LineCount; i++) {
384
385
56
        accum = (cmsUInt8Number*)in + strideIn;
386
56
        output = (cmsUInt8Number*)out + strideOut;
387
388
112
        for (j = 0; j < PixelsPerLine; j++) {
389
390
56
            accum = p->FromInput(p, wIn, accum, Stride->BytesPerPlaneIn);
391
56
            output = p->ToOutput(p, wIn, output, Stride->BytesPerPlaneOut);
392
56
        }
393
394
56
        strideIn += Stride->BytesPerLineIn;
395
56
        strideOut += Stride->BytesPerLineOut;
396
56
    }
397
398
56
}
399
400
401
// No gamut check, no cache, 16 bits
402
static
403
void PrecalculatedXFORM(_cmsTRANSFORM* p,
404
                        const void* in,
405
                        void* out, 
406
                        cmsUInt32Number PixelsPerLine,
407
                        cmsUInt32Number LineCount,
408
                        const cmsStride* Stride)
409
505
{
410
505
    CMSREGISTER cmsUInt8Number* accum;
411
505
    CMSREGISTER cmsUInt8Number* output;
412
505
    cmsUInt16Number wIn[cmsMAXCHANNELS], wOut[cmsMAXCHANNELS];
413
505
    size_t i, j, strideIn, strideOut;
414
415
505
    _cmsHandleExtraChannels(p, in, out, PixelsPerLine, LineCount, Stride);
416
417
505
    strideIn = 0;
418
505
    strideOut = 0;
419
505
    memset(wIn, 0, sizeof(wIn));
420
505
    memset(wOut, 0, sizeof(wOut));
421
422
1.01k
    for (i = 0; i < LineCount; i++) {
423
424
505
        accum = (cmsUInt8Number*)in + strideIn;
425
505
        output = (cmsUInt8Number*)out + strideOut;
426
427
1.01k
        for (j = 0; j < PixelsPerLine; j++) {
428
429
505
            accum = p->FromInput(p, wIn, accum, Stride->BytesPerPlaneIn);
430
505
            p->Lut->Eval16Fn(wIn, wOut, p->Lut->Data);
431
505
            output = p->ToOutput(p, wOut, output, Stride->BytesPerPlaneOut);
432
505
        }
433
434
505
        strideIn += Stride->BytesPerLineIn;
435
505
        strideOut += Stride->BytesPerLineOut;
436
505
    }
437
438
505
}
439
440
441
// Auxiliary: Handle precalculated gamut check. The retrieval of context may be alittle bit slow, but this function is not critical.
442
static
443
void TransformOnePixelWithGamutCheck(_cmsTRANSFORM* p,
444
                                     const cmsUInt16Number wIn[],
445
                                     cmsUInt16Number wOut[])
446
0
{
447
0
    cmsUInt16Number wOutOfGamut;
448
449
0
    p ->GamutCheck ->Eval16Fn(wIn, &wOutOfGamut, p ->GamutCheck ->Data);
450
0
    if (wOutOfGamut >= 1) {
451
452
0
        cmsUInt32Number i;
453
0
        _cmsAlarmCodesChunkType* ContextAlarmCodes = (_cmsAlarmCodesChunkType*) _cmsContextGetClientChunk(p->ContextID, AlarmCodesContext);        
454
455
0
        for (i=0; i < p ->Lut->OutputChannels; i++) {
456
457
0
            wOut[i] = ContextAlarmCodes ->AlarmCodes[i];
458
0
        }
459
0
    }
460
0
    else
461
0
        p ->Lut ->Eval16Fn(wIn, wOut, p -> Lut->Data);
462
0
}
463
464
// Gamut check, No cache, 16 bits.
465
static
466
void PrecalculatedXFORMGamutCheck(_cmsTRANSFORM* p,
467
                                  const void* in,
468
                                  void* out, 
469
                                  cmsUInt32Number PixelsPerLine,
470
                                  cmsUInt32Number LineCount,
471
                                  const cmsStride* Stride)
472
0
{
473
0
    cmsUInt8Number* accum;
474
0
    cmsUInt8Number* output;
475
0
    cmsUInt16Number wIn[cmsMAXCHANNELS], wOut[cmsMAXCHANNELS];
476
0
    size_t i, j, strideIn, strideOut;
477
478
0
    _cmsHandleExtraChannels(p, in, out, PixelsPerLine, LineCount, Stride);
479
480
0
    strideIn = 0;
481
0
    strideOut = 0;
482
0
    memset(wIn, 0, sizeof(wIn));
483
0
    memset(wOut, 0, sizeof(wOut));
484
485
0
    for (i = 0; i < LineCount; i++) {
486
487
0
        accum = (cmsUInt8Number*)in + strideIn;
488
0
        output = (cmsUInt8Number*)out + strideOut;
489
490
0
        for (j = 0; j < PixelsPerLine; j++) {
491
492
0
            accum = p->FromInput(p, wIn, accum, Stride->BytesPerPlaneIn);
493
0
            TransformOnePixelWithGamutCheck(p, wIn, wOut);
494
0
            output = p->ToOutput(p, wOut, output, Stride->BytesPerPlaneOut);
495
0
        }
496
497
0
        strideIn += Stride->BytesPerLineIn;
498
0
        strideOut += Stride->BytesPerLineOut;
499
0
    }
500
0
}
501
502
// No gamut check, Cache, 16 bits,
503
static
504
void CachedXFORM(_cmsTRANSFORM* p,
505
                 const void* in,
506
                 void* out,
507
                 cmsUInt32Number PixelsPerLine,
508
                 cmsUInt32Number LineCount,
509
                 const cmsStride* Stride)
510
34.3k
{
511
34.3k
    cmsUInt8Number* accum;
512
34.3k
    cmsUInt8Number* output;
513
34.3k
    cmsUInt16Number wIn[cmsMAXCHANNELS], wOut[cmsMAXCHANNELS];
514
34.3k
    _cmsCACHE Cache;
515
34.3k
    size_t i, j, strideIn, strideOut;
516
517
34.3k
    _cmsHandleExtraChannels(p, in, out, PixelsPerLine, LineCount, Stride);
518
519
    // Empty buffers for quick memcmp
520
34.3k
    memset(wIn, 0, sizeof(wIn));
521
34.3k
    memset(wOut, 0, sizeof(wOut));
522
523
    // Get copy of zero cache
524
34.3k
    memcpy(&Cache, &p->Cache, sizeof(Cache));
525
526
34.3k
    strideIn = 0;
527
34.3k
    strideOut = 0;
528
529
68.6k
    for (i = 0; i < LineCount; i++) {
530
531
34.3k
        accum = (cmsUInt8Number*)in + strideIn;
532
34.3k
        output = (cmsUInt8Number*)out + strideOut;
533
534
68.6k
        for (j = 0; j < PixelsPerLine; j++) {
535
536
34.3k
            accum = p->FromInput(p, wIn, accum, Stride->BytesPerPlaneIn);
537
538
34.3k
            if (memcmp(wIn, Cache.CacheIn, sizeof(Cache.CacheIn)) == 0) {
539
540
277
                memcpy(wOut, Cache.CacheOut, sizeof(Cache.CacheOut));
541
277
            }
542
34.0k
            else {
543
34.0k
                p->Lut->Eval16Fn(wIn, wOut, p->Lut->Data);
544
545
34.0k
                memcpy(Cache.CacheIn, wIn, sizeof(Cache.CacheIn));
546
34.0k
                memcpy(Cache.CacheOut, wOut, sizeof(Cache.CacheOut));
547
34.0k
            }
548
549
34.3k
            output = p->ToOutput(p, wOut, output, Stride->BytesPerPlaneOut);
550
34.3k
        }
551
552
34.3k
        strideIn += Stride->BytesPerLineIn;
553
34.3k
        strideOut += Stride->BytesPerLineOut;
554
34.3k
    }
555
34.3k
}
556
557
// All those nice features together
558
static
559
void CachedXFORMGamutCheck(_cmsTRANSFORM* p,
560
                           const void* in,
561
                           void* out, 
562
                           cmsUInt32Number PixelsPerLine,
563
                           cmsUInt32Number LineCount,
564
                           const cmsStride* Stride)
565
0
{
566
0
    cmsUInt8Number* accum;
567
0
    cmsUInt8Number* output;
568
0
    cmsUInt16Number wIn[cmsMAXCHANNELS], wOut[cmsMAXCHANNELS];
569
0
    _cmsCACHE Cache;
570
0
    size_t i, j, strideIn, strideOut;
571
572
0
    _cmsHandleExtraChannels(p, in, out, PixelsPerLine, LineCount, Stride);
573
574
    // Empty buffers for quick memcmp
575
0
    memset(wIn, 0, sizeof(wIn));
576
0
    memset(wOut, 0, sizeof(wOut));
577
578
    // Get copy of zero cache
579
0
    memcpy(&Cache, &p->Cache, sizeof(Cache));
580
581
0
    strideIn = 0;
582
0
    strideOut = 0;
583
584
0
    for (i = 0; i < LineCount; i++) {
585
586
0
        accum = (cmsUInt8Number*)in + strideIn;
587
0
        output = (cmsUInt8Number*)out + strideOut;
588
589
0
        for (j = 0; j < PixelsPerLine; j++) {
590
591
0
            accum = p->FromInput(p, wIn, accum, Stride->BytesPerPlaneIn);
592
593
0
            if (memcmp(wIn, Cache.CacheIn, sizeof(Cache.CacheIn)) == 0) {
594
595
0
                memcpy(wOut, Cache.CacheOut, sizeof(Cache.CacheOut));
596
0
            }
597
0
            else {
598
0
                TransformOnePixelWithGamutCheck(p, wIn, wOut);
599
600
0
                memcpy(Cache.CacheIn, wIn, sizeof(Cache.CacheIn));
601
0
                memcpy(Cache.CacheOut, wOut, sizeof(Cache.CacheOut));
602
0
            }
603
604
0
            output = p->ToOutput(p, wOut, output, Stride->BytesPerPlaneOut);
605
0
        }
606
607
0
        strideIn += Stride->BytesPerLineIn;
608
0
        strideOut += Stride->BytesPerLineOut;
609
0
    }
610
0
}
611
612
// Transform plug-ins ----------------------------------------------------------------------------------------------------
613
614
// List of used-defined transform factories
615
typedef struct _cmsTransformCollection_st {
616
617
    _cmsTransform2Factory  Factory;
618
    cmsBool                OldXform;   // Factory returns xform function in the old style
619
620
    struct _cmsTransformCollection_st *Next;
621
622
} _cmsTransformCollection;
623
624
// The linked list head
625
_cmsTransformPluginChunkType _cmsTransformPluginChunk = { NULL };
626
627
628
// Duplicates the zone of memory used by the plug-in in the new context
629
static
630
void DupPluginTransformList(struct _cmsContext_struct* ctx, 
631
                                               const struct _cmsContext_struct* src)
632
0
{
633
0
   _cmsTransformPluginChunkType newHead = { NULL };
634
0
   _cmsTransformCollection*  entry;
635
0
   _cmsTransformCollection*  Anterior = NULL;
636
0
   _cmsTransformPluginChunkType* head = (_cmsTransformPluginChunkType*) src->chunks[TransformPlugin];
637
638
    // Walk the list copying all nodes
639
0
   for (entry = head->TransformCollection;
640
0
        entry != NULL;
641
0
        entry = entry ->Next) {
642
643
0
            _cmsTransformCollection *newEntry = ( _cmsTransformCollection *) _cmsSubAllocDup(ctx ->MemPool, entry, sizeof(_cmsTransformCollection));
644
   
645
0
            if (newEntry == NULL) 
646
0
                return;
647
648
            // We want to keep the linked list order, so this is a little bit tricky
649
0
            newEntry -> Next = NULL;
650
0
            if (Anterior)
651
0
                Anterior -> Next = newEntry;
652
     
653
0
            Anterior = newEntry;
654
655
0
            if (newHead.TransformCollection == NULL)
656
0
                newHead.TransformCollection = newEntry;
657
0
    }
658
659
0
  ctx ->chunks[TransformPlugin] = _cmsSubAllocDup(ctx->MemPool, &newHead, sizeof(_cmsTransformPluginChunkType));
660
0
}
661
662
// Allocates memory for transform plugin factory
663
void _cmsAllocTransformPluginChunk(struct _cmsContext_struct* ctx, 
664
                                        const struct _cmsContext_struct* src)
665
12.9k
{
666
12.9k
    if (src != NULL) {
667
668
        // Copy all linked list
669
0
        DupPluginTransformList(ctx, src);
670
0
    }
671
12.9k
    else {
672
12.9k
        static _cmsTransformPluginChunkType TransformPluginChunkType = { NULL };
673
12.9k
        ctx ->chunks[TransformPlugin] = _cmsSubAllocDup(ctx ->MemPool, &TransformPluginChunkType, sizeof(_cmsTransformPluginChunkType));
674
12.9k
    }
675
12.9k
}
676
677
// Adaptor for old versions of plug-in
678
static
679
void _cmsTransform2toTransformAdaptor(struct _cmstransform_struct *CMMcargo,
680
                                      const void* InputBuffer,
681
                                      void* OutputBuffer,
682
                                      cmsUInt32Number PixelsPerLine,
683
                                      cmsUInt32Number LineCount,
684
                                      const cmsStride* Stride)
685
0
{
686
     
687
0
       size_t i, strideIn, strideOut;
688
689
0
       _cmsHandleExtraChannels(CMMcargo, InputBuffer, OutputBuffer, PixelsPerLine, LineCount, Stride);
690
691
0
       strideIn = 0;
692
0
       strideOut = 0;
693
694
0
       for (i = 0; i < LineCount; i++) {
695
696
0
              void *accum = (cmsUInt8Number*)InputBuffer + strideIn;
697
0
              void *output = (cmsUInt8Number*)OutputBuffer + strideOut;
698
699
0
              CMMcargo->OldXform(CMMcargo, accum, output, PixelsPerLine, Stride->BytesPerPlaneIn);
700
701
0
              strideIn += Stride->BytesPerLineIn;
702
0
              strideOut += Stride->BytesPerLineOut;
703
0
       }
704
0
}
705
706
707
708
// Register new ways to transform
709
cmsBool  _cmsRegisterTransformPlugin(cmsContext ContextID, cmsPluginBase* Data)
710
12.9k
{
711
12.9k
    cmsPluginTransform* Plugin = (cmsPluginTransform*) Data;
712
12.9k
    _cmsTransformCollection* fl;
713
12.9k
    _cmsTransformPluginChunkType* ctx = ( _cmsTransformPluginChunkType*) _cmsContextGetClientChunk(ContextID,TransformPlugin);
714
715
12.9k
    if (Data == NULL) {
716
717
        // Free the chain. Memory is safely freed at exit
718
12.9k
        ctx->TransformCollection = NULL;
719
12.9k
        return TRUE;
720
12.9k
    }
721
722
    // Factory callback is required
723
0
    if (Plugin->factories.xform == NULL) return FALSE;
724
725
726
0
    fl = (_cmsTransformCollection*) _cmsPluginMalloc(ContextID, sizeof(_cmsTransformCollection));
727
0
    if (fl == NULL) return FALSE;
728
729
    // Check for full xform plug-ins previous to 2.8, we would need an adapter in that case
730
0
    if (Plugin->base.ExpectedVersion < 2080) {
731
732
0
           fl->OldXform = TRUE;
733
0
    }
734
0
    else
735
0
           fl->OldXform = FALSE;
736
737
    // Copy the parameters
738
0
    fl->Factory = Plugin->factories.xform;
739
740
    // Keep linked list
741
0
    fl ->Next = ctx->TransformCollection;
742
0
    ctx->TransformCollection = fl;
743
744
    // All is ok
745
0
    return TRUE;
746
0
}
747
748
749
void CMSEXPORT _cmsSetTransformUserData(struct _cmstransform_struct *CMMcargo, void* ptr, _cmsFreeUserDataFn FreePrivateDataFn)
750
0
{
751
0
    _cmsAssert(CMMcargo != NULL);
752
0
    CMMcargo ->UserData = ptr;
753
0
    CMMcargo ->FreeUserData = FreePrivateDataFn;
754
0
}
755
756
// returns the pointer defined by the plug-in to store private data
757
void * CMSEXPORT _cmsGetTransformUserData(struct _cmstransform_struct *CMMcargo)
758
0
{
759
0
    _cmsAssert(CMMcargo != NULL);
760
0
    return CMMcargo ->UserData;
761
0
}
762
763
// returns the current formatters
764
void CMSEXPORT _cmsGetTransformFormatters16(struct _cmstransform_struct *CMMcargo, cmsFormatter16* FromInput, cmsFormatter16* ToOutput)
765
0
{
766
0
     _cmsAssert(CMMcargo != NULL);
767
0
     if (FromInput) *FromInput = CMMcargo ->FromInput;
768
0
     if (ToOutput)  *ToOutput  = CMMcargo ->ToOutput;
769
0
}
770
771
void CMSEXPORT _cmsGetTransformFormattersFloat(struct _cmstransform_struct *CMMcargo, cmsFormatterFloat* FromInput, cmsFormatterFloat* ToOutput)
772
0
{
773
0
     _cmsAssert(CMMcargo != NULL);
774
0
     if (FromInput) *FromInput = CMMcargo ->FromInputFloat;
775
0
     if (ToOutput)  *ToOutput  = CMMcargo ->ToOutputFloat;
776
0
}
777
778
// returns original flags
779
cmsUInt32Number CMSEXPORT _cmsGetTransformFlags(struct _cmstransform_struct* CMMcargo)
780
0
{
781
0
    _cmsAssert(CMMcargo != NULL);
782
0
    return CMMcargo->dwOriginalFlags;
783
0
}
784
785
// Returns the worker callback for parallelization plug-ins
786
_cmsTransform2Fn CMSEXPORT _cmsGetTransformWorker(struct _cmstransform_struct* CMMcargo)
787
0
{
788
0
    _cmsAssert(CMMcargo != NULL);
789
0
    return CMMcargo->Worker;
790
0
}
791
792
// This field holds maximum number of workers or -1 to auto 
793
cmsInt32Number CMSEXPORT _cmsGetTransformMaxWorkers(struct _cmstransform_struct* CMMcargo)
794
0
{
795
0
    _cmsAssert(CMMcargo != NULL);
796
0
    return CMMcargo->MaxWorkers;
797
0
}
798
799
// This field is actually unused and reserved
800
cmsUInt32Number CMSEXPORT _cmsGetTransformWorkerFlags(struct _cmstransform_struct* CMMcargo)
801
0
{
802
0
    _cmsAssert(CMMcargo != NULL);
803
0
    return CMMcargo->WorkerFlags;
804
0
}
805
806
// In the case there is a parallelization plug-in, let it to do its job
807
static
808
void ParalellizeIfSuitable(_cmsTRANSFORM* p)
809
16.0k
{
810
16.0k
    _cmsParallelizationPluginChunkType* ctx = (_cmsParallelizationPluginChunkType*)_cmsContextGetClientChunk(p->ContextID, ParallelizationPlugin);
811
812
16.0k
    _cmsAssert(p != NULL);
813
16.0k
    if (ctx != NULL && ctx->SchedulerFn != NULL) {
814
815
0
        p->Worker = p->xform;
816
0
        p->xform = ctx->SchedulerFn;
817
0
        p->MaxWorkers = ctx->MaxWorkers;
818
0
        p->WorkerFlags = ctx->WorkerFlags;
819
0
    }
820
16.0k
}
821
822
823
/**
824
* An empty unroll to avoid a check with NULL on cmsDoTransform()
825
*/
826
static
827
cmsUInt8Number* UnrollNothing(CMSREGISTER _cmsTRANSFORM* info,
828
                              CMSREGISTER cmsUInt16Number wIn[],
829
                              CMSREGISTER cmsUInt8Number* accum,
830
                              CMSREGISTER cmsUInt32Number Stride)
831
0
{    
832
0
    return accum;
833
834
0
    cmsUNUSED_PARAMETER(info);
835
0
    cmsUNUSED_PARAMETER(wIn);
836
0
    cmsUNUSED_PARAMETER(Stride);
837
0
}
838
839
static
840
cmsUInt8Number* PackNothing(CMSREGISTER _cmsTRANSFORM* info,
841
                           CMSREGISTER cmsUInt16Number wOut[],
842
                           CMSREGISTER cmsUInt8Number* output,
843
                           CMSREGISTER cmsUInt32Number Stride)
844
0
{
845
0
    return output;
846
847
0
    cmsUNUSED_PARAMETER(info);
848
0
    cmsUNUSED_PARAMETER(wOut);
849
0
    cmsUNUSED_PARAMETER(Stride);
850
0
}
851
852
// Allocate transform struct and set it to defaults. Ask the optimization plug-in about if those formats are proper
853
// for separated transforms. If this is the case,
854
static
855
_cmsTRANSFORM* AllocEmptyTransform(cmsContext ContextID, cmsPipeline* lut,
856
                                               cmsUInt32Number Intent, cmsUInt32Number* InputFormat, cmsUInt32Number* OutputFormat, cmsUInt32Number* dwFlags)
857
18.1k
{
858
18.1k
     _cmsTransformPluginChunkType* ctx = ( _cmsTransformPluginChunkType*) _cmsContextGetClientChunk(ContextID, TransformPlugin);
859
18.1k
     _cmsTransformCollection* Plugin;
860
861
       // Allocate needed memory
862
18.1k
       _cmsTRANSFORM* p = (_cmsTRANSFORM*)_cmsMallocZero(ContextID, sizeof(_cmsTRANSFORM));
863
18.1k
       if (!p) {
864
0
              cmsPipelineFree(lut);
865
0
              return NULL;
866
0
       }
867
868
       // Store the proposed pipeline
869
18.1k
       p->Lut = lut;
870
871
       // Let's see if any plug-in want to do the transform by itself
872
18.1k
       if (p->Lut != NULL) {
873
874
17.5k
           if (!(*dwFlags & cmsFLAGS_NOOPTIMIZE))
875
8.05k
           {
876
8.05k
               for (Plugin = ctx->TransformCollection;
877
8.05k
                   Plugin != NULL;
878
8.05k
                   Plugin = Plugin->Next) {
879
880
0
                   if (Plugin->Factory(&p->xform, &p->UserData, &p->FreeUserData, &p->Lut, InputFormat, OutputFormat, dwFlags)) {
881
882
                       // Last plugin in the declaration order takes control. We just keep
883
                       // the original parameters as a logging. 
884
                       // Note that cmsFLAGS_CAN_CHANGE_FORMATTER is not set, so by default 
885
                       // an optimized transform is not reusable. The plug-in can, however, change
886
                       // the flags and make it suitable.
887
888
0
                       p->ContextID = ContextID;
889
0
                       p->InputFormat = *InputFormat;
890
0
                       p->OutputFormat = *OutputFormat;
891
0
                       p->dwOriginalFlags = *dwFlags;
892
893
                       // Fill the formatters just in case the optimized routine is interested.
894
                       // No error is thrown if the formatter doesn't exist. It is up to the optimization 
895
                       // factory to decide what to do in those cases.
896
0
                       p->FromInput = _cmsGetFormatter(ContextID, *InputFormat, cmsFormatterInput, CMS_PACK_FLAGS_16BITS).Fmt16;
897
0
                       p->ToOutput = _cmsGetFormatter(ContextID, *OutputFormat, cmsFormatterOutput, CMS_PACK_FLAGS_16BITS).Fmt16;
898
0
                       p->FromInputFloat = _cmsGetFormatter(ContextID, *InputFormat, cmsFormatterInput, CMS_PACK_FLAGS_FLOAT).FmtFloat;
899
0
                       p->ToOutputFloat = _cmsGetFormatter(ContextID, *OutputFormat, cmsFormatterOutput, CMS_PACK_FLAGS_FLOAT).FmtFloat;
900
901
                       // Save the day? (Ignore the warning)
902
0
                       if (Plugin->OldXform) {
903
0
                           p->OldXform = (_cmsTransformFn)(void*)p->xform;
904
0
                           p->xform = _cmsTransform2toTransformAdaptor;
905
0
                       }
906
907
0
                       ParalellizeIfSuitable(p);
908
0
                       return p;
909
0
                   }
910
0
               }
911
8.05k
           }
912
913
           // Not suitable for the transform plug-in, let's check  the pipeline plug-in
914
17.5k
           _cmsOptimizePipeline(ContextID, &p->Lut, Intent, InputFormat, OutputFormat, dwFlags);
915
17.5k
       }
916
917
    // Check whatever this is a true floating point transform
918
18.1k
    if (_cmsFormatterIsFloat(*InputFormat) || _cmsFormatterIsFloat(*OutputFormat)) {
919
920
        // Get formatter function always return a valid union, but the contents of this union may be NULL.
921
11.2k
        p ->FromInputFloat = _cmsGetFormatter(ContextID, *InputFormat,  cmsFormatterInput, CMS_PACK_FLAGS_FLOAT).FmtFloat;
922
11.2k
        p ->ToOutputFloat  = _cmsGetFormatter(ContextID, *OutputFormat, cmsFormatterOutput, CMS_PACK_FLAGS_FLOAT).FmtFloat;
923
11.2k
        *dwFlags |= cmsFLAGS_CAN_CHANGE_FORMATTER;
924
925
11.2k
        if (p ->FromInputFloat == NULL || p ->ToOutputFloat == NULL) {
926
927
128
            cmsSignalError(ContextID, cmsERROR_UNKNOWN_EXTENSION, "Unsupported raster format");
928
128
            cmsDeleteTransform(p);
929
128
            return NULL;
930
128
        }
931
932
11.1k
        if (*dwFlags & cmsFLAGS_NULLTRANSFORM) {
933
934
61
            p ->xform = NullFloatXFORM;
935
61
        }
936
11.0k
        else {
937
            // Float transforms don't use cache, always are non-NULL
938
11.0k
            p ->xform = FloatXFORM;
939
11.0k
        }
940
941
11.1k
    }
942
6.82k
    else {
943
944
        // Formats are intended to be changed before use
945
6.82k
        if (*InputFormat == 0 && *OutputFormat == 0) {
946
118
            p->FromInput = UnrollNothing;
947
118
            p->ToOutput = PackNothing;
948
118
            *dwFlags |= cmsFLAGS_CAN_CHANGE_FORMATTER;
949
118
        }
950
6.70k
        else {
951
952
6.70k
            cmsUInt32Number BytesPerPixelInput;
953
954
6.70k
            p ->FromInput = _cmsGetFormatter(ContextID, *InputFormat,  cmsFormatterInput, CMS_PACK_FLAGS_16BITS).Fmt16;
955
6.70k
            p ->ToOutput  = _cmsGetFormatter(ContextID, *OutputFormat, cmsFormatterOutput, CMS_PACK_FLAGS_16BITS).Fmt16;
956
957
6.70k
            if (p ->FromInput == NULL || p ->ToOutput == NULL) {
958
959
1.87k
                cmsSignalError(ContextID, cmsERROR_UNKNOWN_EXTENSION, "Unsupported raster format");
960
1.87k
                cmsDeleteTransform(p);
961
1.87k
                return NULL;
962
1.87k
            }
963
964
4.82k
            BytesPerPixelInput = T_BYTES(*InputFormat);
965
4.82k
            if (BytesPerPixelInput == 0 || BytesPerPixelInput >= 2)
966
429
                   *dwFlags |= cmsFLAGS_CAN_CHANGE_FORMATTER;
967
968
4.82k
        }
969
970
4.94k
        if (*dwFlags & cmsFLAGS_NULLTRANSFORM) {
971
972
124
            p ->xform = NullXFORM;
973
124
        }
974
4.82k
        else {
975
4.82k
            if (*dwFlags & cmsFLAGS_NOCACHE) {
976
977
1.11k
                if (*dwFlags & cmsFLAGS_GAMUTCHECK)
978
0
                    p ->xform = PrecalculatedXFORMGamutCheck;  // Gamut check, no cache
979
1.11k
                else
980
1.11k
                    p ->xform = PrecalculatedXFORM;  // No cache, no gamut check
981
1.11k
            }
982
3.71k
            else {
983
984
3.71k
                if (*dwFlags & cmsFLAGS_GAMUTCHECK)
985
0
                    p ->xform = CachedXFORMGamutCheck;    // Gamut check, cache
986
3.71k
                else
987
3.71k
                    p ->xform = CachedXFORM;  // No gamut check, cache
988
989
3.71k
            }
990
4.82k
        }
991
4.94k
    }
992
993
    /**
994
    * Check consistency for alpha channel copy
995
    */
996
16.1k
    if (*dwFlags & cmsFLAGS_COPY_ALPHA)
997
647
    {
998
647
        if (T_EXTRA(*InputFormat) != T_EXTRA(*OutputFormat))
999
30
        {
1000
30
            cmsSignalError(ContextID, cmsERROR_NOT_SUITABLE, "Mismatched alpha channels");
1001
30
            cmsDeleteTransform(p);
1002
30
            return NULL;
1003
30
        }
1004
647
    }
1005
1006
16.0k
    p ->InputFormat     = *InputFormat;
1007
16.0k
    p ->OutputFormat    = *OutputFormat;
1008
16.0k
    p ->dwOriginalFlags = *dwFlags;
1009
16.0k
    p ->ContextID       = ContextID;
1010
16.0k
    p ->UserData        = NULL;
1011
16.0k
    ParalellizeIfSuitable(p);
1012
16.0k
    return p;
1013
16.1k
}
1014
1015
static
1016
cmsBool GetXFormColorSpaces(cmsUInt32Number nProfiles, cmsHPROFILE hProfiles[], cmsColorSpaceSignature* Input, cmsColorSpaceSignature* Output)
1017
55.3k
{
1018
55.3k
    cmsColorSpaceSignature ColorSpaceIn, ColorSpaceOut;
1019
55.3k
    cmsColorSpaceSignature PostColorSpace;
1020
55.3k
    cmsUInt32Number i;
1021
1022
55.3k
    if (nProfiles == 0) return FALSE;
1023
55.3k
    if (hProfiles[0] == NULL) return FALSE;
1024
1025
52.8k
    *Input = PostColorSpace = cmsGetColorSpace(hProfiles[0]);
1026
1027
151k
    for (i=0; i < nProfiles; i++) {
1028
1029
98.6k
        cmsProfileClassSignature cls;
1030
98.6k
        cmsHPROFILE hProfile = hProfiles[i];
1031
1032
98.6k
        int lIsInput = (PostColorSpace != cmsSigXYZData) &&
1033
83.9k
                       (PostColorSpace != cmsSigLabData);
1034
1035
98.6k
        if (hProfile == NULL) return FALSE;
1036
1037
98.6k
        cls = cmsGetDeviceClass(hProfile);
1038
1039
98.6k
        if (cls == cmsSigNamedColorClass) {
1040
1041
1.96k
            ColorSpaceIn    = cmsSig1colorData;
1042
1.96k
            ColorSpaceOut   = (nProfiles > 1) ? cmsGetPCS(hProfile) : cmsGetColorSpace(hProfile);
1043
1.96k
        }
1044
96.6k
        else
1045
96.6k
        if (lIsInput || (cls == cmsSigLinkClass)) {
1046
1047
49.6k
            ColorSpaceIn    = cmsGetColorSpace(hProfile);
1048
49.6k
            ColorSpaceOut   = cmsGetPCS(hProfile);
1049
49.6k
        }
1050
46.9k
        else
1051
46.9k
        {
1052
46.9k
            ColorSpaceIn    = cmsGetPCS(hProfile);
1053
46.9k
            ColorSpaceOut   = cmsGetColorSpace(hProfile);
1054
46.9k
        }
1055
1056
98.6k
        if (i==0)
1057
52.8k
            *Input = ColorSpaceIn;
1058
1059
98.6k
        PostColorSpace = ColorSpaceOut;
1060
98.6k
    }
1061
1062
52.8k
    *Output = PostColorSpace;
1063
1064
52.8k
    return TRUE;
1065
52.8k
}
1066
1067
// Check colorspace
1068
static
1069
cmsBool  IsProperColorSpace(cmsColorSpaceSignature Check, cmsUInt32Number dwFormat)
1070
103k
{
1071
103k
    int Space1 = (int) T_COLORSPACE(dwFormat);
1072
103k
    int Space2 = _cmsLCMScolorSpace(Check);
1073
1074
103k
    if (dwFormat == 0) return TRUE; // Bypass used by linkicc
1075
1076
98.0k
    if (Space1 == PT_ANY) return (T_CHANNELS(dwFormat) == cmsChannelsOf(Check));
1077
79.3k
    if (Space1 == Space2) return TRUE;
1078
1079
5.72k
    if (Space1 == PT_LabV2 && Space2 == PT_Lab) return TRUE;
1080
4.97k
    if (Space1 == PT_Lab   && Space2 == PT_LabV2) return TRUE;
1081
1082
4.97k
    return FALSE;
1083
4.97k
}
1084
1085
// ----------------------------------------------------------------------------------------------------------------
1086
1087
// Jun-21-2000: Some profiles (those that comes with W2K) comes
1088
// with the media white (media black?) x 100. Add a sanity check
1089
1090
static
1091
void NormalizeXYZ(cmsCIEXYZ* Dest)
1092
17.1k
{
1093
17.6k
    while (Dest -> X > 2. &&
1094
663
           Dest -> Y > 2. &&
1095
557
           Dest -> Z > 2.) {
1096
1097
495
               Dest -> X /= 10.;
1098
495
               Dest -> Y /= 10.;
1099
495
               Dest -> Z /= 10.;
1100
495
       }
1101
17.1k
}
1102
1103
static
1104
void SetWhitePoint(cmsCIEXYZ* wtPt, const cmsCIEXYZ* src)
1105
31.8k
{
1106
31.8k
    if (src == NULL) {
1107
14.6k
        wtPt ->X = cmsD50X;
1108
14.6k
        wtPt ->Y = cmsD50Y;
1109
14.6k
        wtPt ->Z = cmsD50Z;
1110
14.6k
    }
1111
17.1k
    else {
1112
17.1k
        wtPt ->X = src->X;
1113
17.1k
        wtPt ->Y = src->Y;
1114
17.1k
        wtPt ->Z = src->Z;
1115
1116
17.1k
        NormalizeXYZ(wtPt);
1117
17.1k
    }
1118
1119
31.8k
}
1120
1121
// New to lcms 2.0 -- have all parameters available.
1122
cmsHTRANSFORM CMSEXPORT cmsCreateExtendedTransform(cmsContext ContextID,
1123
                                                   cmsUInt32Number nProfiles, cmsHPROFILE hProfiles[],
1124
                                                   cmsBool  BPC[],
1125
                                                   cmsUInt32Number Intents[],
1126
                                                   cmsFloat64Number AdaptationStates[],
1127
                                                   cmsHPROFILE hGamutProfile,
1128
                                                   cmsUInt32Number nGamutPCSposition,
1129
                                                   cmsUInt32Number InputFormat,
1130
                                                   cmsUInt32Number OutputFormat,
1131
                                                   cmsUInt32Number dwFlags)
1132
55.9k
{
1133
55.9k
    _cmsTRANSFORM* xform;    
1134
55.9k
    cmsColorSpaceSignature EntryColorSpace;
1135
55.9k
    cmsColorSpaceSignature ExitColorSpace;
1136
55.9k
    cmsPipeline* Lut;
1137
55.9k
    cmsUInt32Number LastIntent;
1138
1139
    // Safeguard
1140
55.9k
    if (nProfiles <= 0 || nProfiles > 255) {
1141
0
        cmsSignalError(ContextID, cmsERROR_RANGE, "Wrong number of profiles. 1..255 expected, %d found.", nProfiles);
1142
0
        return NULL;
1143
0
    }
1144
1145
55.9k
    LastIntent = Intents[nProfiles - 1];
1146
1147
    // If it is a fake transform
1148
55.9k
    if (dwFlags & cmsFLAGS_NULLTRANSFORM)
1149
541
    {
1150
541
        return AllocEmptyTransform(ContextID, NULL, INTENT_PERCEPTUAL, &InputFormat, &OutputFormat, &dwFlags);
1151
541
    }
1152
1153
    // If gamut check is requested, make sure we have a gamut profile
1154
55.3k
    if (dwFlags & cmsFLAGS_GAMUTCHECK) {
1155
3.89k
        if (hGamutProfile == NULL) dwFlags &= ~cmsFLAGS_GAMUTCHECK;
1156
3.89k
    }
1157
1158
55.3k
    if ((dwFlags & cmsFLAGS_GAMUTCHECK) && (nGamutPCSposition <= 0 || nGamutPCSposition > nProfiles - 1)) {
1159
0
        cmsSignalError(ContextID, cmsERROR_RANGE, "Wrong gamut PCS position '%d'", nGamutPCSposition);
1160
0
        return NULL;
1161
0
    }
1162
1163
    // On floating point transforms, inhibit cache
1164
55.3k
    if (_cmsFormatterIsFloat(InputFormat) || _cmsFormatterIsFloat(OutputFormat))
1165
23.5k
        dwFlags |= cmsFLAGS_NOCACHE;
1166
1167
    // Mark entry/exit spaces
1168
55.3k
    if (!GetXFormColorSpaces(nProfiles, hProfiles, &EntryColorSpace, &ExitColorSpace)) {
1169
2.52k
        cmsSignalError(ContextID, cmsERROR_NULL, "NULL input profiles on transform");
1170
2.52k
        return NULL;
1171
2.52k
    }
1172
1173
    // Check if proper colorspaces
1174
52.8k
    if (!IsProperColorSpace(EntryColorSpace, InputFormat)) {
1175
2.03k
        cmsSignalError(ContextID, cmsERROR_COLORSPACE_CHECK, "Wrong input color space on transform");
1176
2.03k
        return NULL;
1177
2.03k
    }
1178
1179
50.8k
    if (!IsProperColorSpace(ExitColorSpace, OutputFormat)) {
1180
3.53k
        cmsSignalError(ContextID, cmsERROR_COLORSPACE_CHECK, "Wrong output color space on transform");
1181
3.53k
        return NULL;
1182
3.53k
    }
1183
1184
    // Check whatever the transform is 16 bits and involves linear RGB in first profile. If so, disable optimizations
1185
47.3k
    if (EntryColorSpace == cmsSigRgbData && T_BYTES(InputFormat) == 2 && !(dwFlags & cmsFLAGS_NOOPTIMIZE))
1186
1.89k
    {
1187
1.89k
        cmsFloat64Number gamma = cmsDetectRGBProfileGamma(hProfiles[0], 0.1);
1188
1189
1.89k
        if (gamma > 0 && gamma < 1.6)
1190
9
            dwFlags |= cmsFLAGS_NOOPTIMIZE;
1191
1.89k
    }
1192
1193
    // Create a pipeline with all transformations
1194
47.3k
    Lut = _cmsLinkProfiles(ContextID, nProfiles, Intents, hProfiles, BPC, AdaptationStates, dwFlags);
1195
47.3k
    if (Lut == NULL) {
1196
26.8k
        cmsSignalError(ContextID, cmsERROR_NOT_SUITABLE, "Couldn't link the profiles");
1197
26.8k
        return NULL;
1198
26.8k
    }
1199
1200
    // Check channel count
1201
20.4k
    if ((cmsChannelsOfColorSpace(EntryColorSpace) != (cmsInt32Number) cmsPipelineInputChannels(Lut)) ||
1202
17.5k
        (cmsChannelsOfColorSpace(ExitColorSpace)  != (cmsInt32Number) cmsPipelineOutputChannels(Lut))) {
1203
2.91k
        cmsPipelineFree(Lut);
1204
2.91k
        cmsSignalError(ContextID, cmsERROR_NOT_SUITABLE, "Channel count doesn't match. Profile is corrupted");
1205
2.91k
        return NULL;
1206
2.91k
    }
1207
1208
1209
    // All seems ok
1210
17.5k
    xform = AllocEmptyTransform(ContextID, Lut, LastIntent, &InputFormat, &OutputFormat, &dwFlags);
1211
17.5k
    if (xform == NULL) {
1212
1.66k
        return NULL;
1213
1.66k
    }
1214
1215
    // Keep values
1216
15.9k
    xform ->EntryColorSpace = EntryColorSpace;
1217
15.9k
    xform ->ExitColorSpace  = ExitColorSpace;
1218
15.9k
    xform ->RenderingIntent = Intents[nProfiles-1];
1219
1220
    // Take white points
1221
15.9k
    SetWhitePoint(&xform->EntryWhitePoint, (cmsCIEXYZ*) cmsReadTag(hProfiles[0], cmsSigMediaWhitePointTag));
1222
15.9k
    SetWhitePoint(&xform->ExitWhitePoint,  (cmsCIEXYZ*) cmsReadTag(hProfiles[nProfiles-1], cmsSigMediaWhitePointTag));
1223
   
1224
1225
    // Create a gamut check LUT if requested
1226
15.9k
    if (hGamutProfile != NULL && (dwFlags & cmsFLAGS_GAMUTCHECK))
1227
0
        xform ->GamutCheck  = _cmsCreateGamutCheckPipeline(ContextID, hProfiles,
1228
0
                                                        BPC, Intents,
1229
0
                                                        AdaptationStates,
1230
0
                                                        nGamutPCSposition,
1231
0
                                                        hGamutProfile);
1232
1233
1234
    // Try to read input and output colorant table
1235
15.9k
    if (cmsIsTag(hProfiles[0], cmsSigColorantTableTag)) {
1236
1237
        // Input table can only come in this way.
1238
719
        xform ->InputColorant = cmsDupNamedColorList((cmsNAMEDCOLORLIST*) cmsReadTag(hProfiles[0], cmsSigColorantTableTag));
1239
719
    }
1240
1241
    // Output is a little bit more complex.
1242
15.9k
    if (cmsGetDeviceClass(hProfiles[nProfiles-1]) == cmsSigLinkClass) {
1243
1244
        // This tag may exist only on devicelink profiles.
1245
738
        if (cmsIsTag(hProfiles[nProfiles-1], cmsSigColorantTableOutTag)) {
1246
1247
            // It may be NULL if error
1248
6
            xform ->OutputColorant = cmsDupNamedColorList((cmsNAMEDCOLORLIST*) cmsReadTag(hProfiles[nProfiles-1], cmsSigColorantTableOutTag));
1249
6
        }
1250
1251
15.1k
    } else {
1252
1253
15.1k
        if (cmsIsTag(hProfiles[nProfiles-1], cmsSigColorantTableTag)) {
1254
1255
219
            xform -> OutputColorant = cmsDupNamedColorList((cmsNAMEDCOLORLIST*) cmsReadTag(hProfiles[nProfiles-1], cmsSigColorantTableTag));
1256
219
        }
1257
15.1k
    }
1258
1259
    // Store the sequence of profiles
1260
15.9k
    if (dwFlags & cmsFLAGS_KEEP_SEQUENCE) {
1261
827
        xform ->Sequence = _cmsCompileProfileSequence(ContextID, nProfiles, hProfiles);
1262
827
    }
1263
15.0k
    else
1264
15.0k
        xform ->Sequence = NULL;
1265
1266
    // If this is a cached transform, init first value, which is zero (16 bits only)
1267
15.9k
    if (!(dwFlags & cmsFLAGS_NOCACHE)) {
1268
1269
3.70k
        memset(&xform ->Cache.CacheIn, 0, sizeof(xform ->Cache.CacheIn));
1270
1271
3.70k
        if (xform ->GamutCheck != NULL) {
1272
0
            TransformOnePixelWithGamutCheck(xform, xform ->Cache.CacheIn, xform->Cache.CacheOut);
1273
0
        }
1274
3.70k
        else {
1275
1276
3.70k
            xform ->Lut ->Eval16Fn(xform ->Cache.CacheIn, xform->Cache.CacheOut, xform -> Lut->Data);
1277
3.70k
        }
1278
1279
3.70k
    }
1280
1281
15.9k
    return (cmsHTRANSFORM) xform;
1282
17.5k
}
1283
1284
// Multiprofile transforms: Gamut check is not available here, as it is unclear from which profile the gamut comes.
1285
cmsHTRANSFORM CMSEXPORT cmsCreateMultiprofileTransformTHR(cmsContext ContextID,
1286
                                                       cmsHPROFILE hProfiles[],
1287
                                                       cmsUInt32Number nProfiles,
1288
                                                       cmsUInt32Number InputFormat,
1289
                                                       cmsUInt32Number OutputFormat,
1290
                                                       cmsUInt32Number Intent,
1291
                                                       cmsUInt32Number dwFlags)
1292
55.1k
{
1293
55.1k
    cmsUInt32Number i;
1294
55.1k
    cmsBool BPC[256];
1295
55.1k
    cmsUInt32Number Intents[256];
1296
55.1k
    cmsFloat64Number AdaptationStates[256];
1297
1298
55.1k
    if (nProfiles <= 0 || nProfiles > 255) {
1299
0
         cmsSignalError(ContextID, cmsERROR_RANGE, "Wrong number of profiles. 1..255 expected, %d found.", nProfiles);
1300
0
        return NULL;
1301
0
    }
1302
1303
153k
    for (i=0; i < nProfiles; i++) {
1304
98.8k
        BPC[i] = dwFlags & cmsFLAGS_BLACKPOINTCOMPENSATION ? TRUE : FALSE;
1305
98.8k
        Intents[i] = Intent;
1306
98.8k
        AdaptationStates[i] = cmsSetAdaptationStateTHR(ContextID, -1);
1307
98.8k
    }
1308
1309
1310
55.1k
    return cmsCreateExtendedTransform(ContextID, nProfiles, hProfiles, BPC, Intents, AdaptationStates, NULL, 0, InputFormat, OutputFormat, dwFlags);
1311
55.1k
}
1312
1313
1314
1315
cmsHTRANSFORM CMSEXPORT cmsCreateMultiprofileTransform(cmsHPROFILE hProfiles[],
1316
                                                  cmsUInt32Number nProfiles,
1317
                                                  cmsUInt32Number InputFormat,
1318
                                                  cmsUInt32Number OutputFormat,
1319
                                                  cmsUInt32Number Intent,
1320
                                                  cmsUInt32Number dwFlags)
1321
1.35k
{
1322
1323
1.35k
    if (nProfiles <= 0 || nProfiles > 255) {
1324
0
         cmsSignalError(NULL, cmsERROR_RANGE, "Wrong number of profiles. 1..255 expected, %d found.", nProfiles);
1325
0
         return NULL;
1326
0
    }
1327
1328
1.35k
    return cmsCreateMultiprofileTransformTHR(cmsGetProfileContextID(hProfiles[0]),
1329
1.35k
                                                  hProfiles,
1330
1.35k
                                                  nProfiles,
1331
1.35k
                                                  InputFormat,
1332
1.35k
                                                  OutputFormat,
1333
1.35k
                                                  Intent,
1334
1.35k
                                                  dwFlags);
1335
1.35k
}
1336
1337
cmsHTRANSFORM CMSEXPORT cmsCreateTransformTHR(cmsContext ContextID,
1338
                                              cmsHPROFILE Input,
1339
                                              cmsUInt32Number InputFormat,
1340
                                              cmsHPROFILE Output,
1341
                                              cmsUInt32Number OutputFormat,
1342
                                              cmsUInt32Number Intent,
1343
                                              cmsUInt32Number dwFlags)
1344
48.2k
{
1345
1346
48.2k
    cmsHPROFILE hArray[2];
1347
1348
48.2k
    hArray[0] = Input;
1349
48.2k
    hArray[1] = Output;
1350
1351
48.2k
    return cmsCreateMultiprofileTransformTHR(ContextID, hArray, Output == NULL ? 1U : 2U, InputFormat, OutputFormat, Intent, dwFlags);
1352
48.2k
}
1353
1354
CMSAPI cmsHTRANSFORM CMSEXPORT cmsCreateTransform(cmsHPROFILE Input,
1355
                                                  cmsUInt32Number InputFormat,
1356
                                                  cmsHPROFILE Output,
1357
                                                  cmsUInt32Number OutputFormat,
1358
                                                  cmsUInt32Number Intent,
1359
                                                  cmsUInt32Number dwFlags)
1360
30.4k
{
1361
30.4k
    return cmsCreateTransformTHR(cmsGetProfileContextID(Input), Input, InputFormat, Output, OutputFormat, Intent, dwFlags);
1362
30.4k
}
1363
1364
1365
cmsHTRANSFORM CMSEXPORT cmsCreateProofingTransformTHR(cmsContext ContextID,
1366
                                                   cmsHPROFILE InputProfile,
1367
                                                   cmsUInt32Number InputFormat,
1368
                                                   cmsHPROFILE OutputProfile,
1369
                                                   cmsUInt32Number OutputFormat,
1370
                                                   cmsHPROFILE ProofingProfile,
1371
                                                   cmsUInt32Number nIntent,
1372
                                                   cmsUInt32Number ProofingIntent,
1373
                                                   cmsUInt32Number dwFlags)
1374
0
{
1375
0
    cmsHPROFILE hArray[4];
1376
0
    cmsUInt32Number Intents[4];
1377
0
    cmsBool  BPC[4];
1378
0
    cmsFloat64Number Adaptation[4];
1379
0
    cmsBool  DoBPC = (dwFlags & cmsFLAGS_BLACKPOINTCOMPENSATION) ? TRUE : FALSE;
1380
1381
1382
0
    hArray[0]  = InputProfile; hArray[1] = ProofingProfile; hArray[2]  = ProofingProfile;               hArray[3] = OutputProfile;
1383
0
    Intents[0] = nIntent;      Intents[1] = nIntent;        Intents[2] = INTENT_RELATIVE_COLORIMETRIC;  Intents[3] = ProofingIntent;
1384
0
    BPC[0]     = DoBPC;        BPC[1] = DoBPC;              BPC[2] = 0;                                 BPC[3] = 0;
1385
1386
0
    Adaptation[0] = Adaptation[1] = Adaptation[2] = Adaptation[3] = cmsSetAdaptationStateTHR(ContextID, -1);
1387
1388
0
    if (!(dwFlags & (cmsFLAGS_SOFTPROOFING|cmsFLAGS_GAMUTCHECK)))
1389
0
        return cmsCreateTransformTHR(ContextID, InputProfile, InputFormat, OutputProfile, OutputFormat, nIntent, dwFlags);
1390
1391
0
    return cmsCreateExtendedTransform(ContextID, 4, hArray, BPC, Intents, Adaptation,
1392
0
                                        ProofingProfile, 1, InputFormat, OutputFormat, dwFlags);
1393
1394
0
}
1395
1396
1397
cmsHTRANSFORM CMSEXPORT cmsCreateProofingTransform(cmsHPROFILE InputProfile,
1398
                                                   cmsUInt32Number InputFormat,
1399
                                                   cmsHPROFILE OutputProfile,
1400
                                                   cmsUInt32Number OutputFormat,
1401
                                                   cmsHPROFILE ProofingProfile,
1402
                                                   cmsUInt32Number nIntent,
1403
                                                   cmsUInt32Number ProofingIntent,
1404
                                                   cmsUInt32Number dwFlags)
1405
0
{
1406
0
    return cmsCreateProofingTransformTHR(cmsGetProfileContextID(InputProfile),
1407
0
                                                   InputProfile,
1408
0
                                                   InputFormat,
1409
0
                                                   OutputProfile,
1410
0
                                                   OutputFormat,
1411
0
                                                   ProofingProfile,
1412
0
                                                   nIntent,
1413
0
                                                   ProofingIntent,
1414
0
                                                   dwFlags);
1415
0
}
1416
1417
1418
// Grab the ContextID from an open transform. Returns NULL if a NULL transform is passed
1419
cmsContext CMSEXPORT cmsGetTransformContextID(cmsHTRANSFORM hTransform)
1420
908
{
1421
908
    _cmsTRANSFORM* xform = (_cmsTRANSFORM*) hTransform;
1422
1423
908
    if (xform == NULL) return NULL;
1424
908
    return xform -> ContextID;
1425
908
}
1426
1427
// Grab the input/output formats
1428
cmsUInt32Number CMSEXPORT cmsGetTransformInputFormat(cmsHTRANSFORM hTransform)
1429
0
{
1430
0
    _cmsTRANSFORM* xform = (_cmsTRANSFORM*) hTransform;
1431
1432
0
    if (xform == NULL) return 0;
1433
0
    return xform->InputFormat;
1434
0
}
1435
1436
cmsUInt32Number CMSEXPORT cmsGetTransformOutputFormat(cmsHTRANSFORM hTransform)
1437
0
{
1438
0
    _cmsTRANSFORM* xform = (_cmsTRANSFORM*) hTransform;
1439
1440
0
    if (xform == NULL) return 0;
1441
0
    return xform->OutputFormat;
1442
0
}
1443
1444
// Returns the optimized pipeline (Lut) inside a transform. Read-only; do not free.
1445
cmsPipeline* CMSEXPORT cmsGetTransformPipeline(cmsHTRANSFORM hTransform)
1446
0
{
1447
0
    _cmsTRANSFORM* xform = (_cmsTRANSFORM*) hTransform;
1448
0
    if (xform == NULL) return NULL;
1449
0
    return xform->Lut;
1450
0
}
1451
1452
// Returns the gamut-check pipeline inside a transform. Read-only; do not free.
1453
cmsPipeline* CMSEXPORT cmsGetTransformGamutCheckPipeline(cmsHTRANSFORM hTransform)
1454
0
{
1455
0
    _cmsTRANSFORM* xform = (_cmsTRANSFORM*) hTransform;
1456
0
    if (xform == NULL) return NULL;
1457
0
    return xform->GamutCheck;
1458
0
}
1459
1460
// For backwards compatibility
1461
cmsBool CMSEXPORT cmsChangeBuffersFormat(cmsHTRANSFORM hTransform,
1462
                                         cmsUInt32Number InputFormat,
1463
                                         cmsUInt32Number OutputFormat)
1464
67
{
1465
67
    _cmsTRANSFORM* xform = (_cmsTRANSFORM*) hTransform;
1466
67
    cmsFormatter16 FromInput, ToOutput;
1467
1468
1469
    // We only can afford to change formatters if previous transform is at least 16 bits
1470
67
    if (!(xform ->dwOriginalFlags & cmsFLAGS_CAN_CHANGE_FORMATTER)) {
1471
1472
36
        cmsSignalError(xform ->ContextID, cmsERROR_NOT_SUITABLE, "cmsChangeBuffersFormat works only on transforms created originally with at least 16 bits of precision");
1473
36
        return FALSE;
1474
36
    }
1475
1476
31
    FromInput = _cmsGetFormatter(xform->ContextID, InputFormat,  cmsFormatterInput, CMS_PACK_FLAGS_16BITS).Fmt16;
1477
31
    ToOutput  = _cmsGetFormatter(xform->ContextID, OutputFormat, cmsFormatterOutput, CMS_PACK_FLAGS_16BITS).Fmt16;
1478
1479
31
    if (FromInput == NULL || ToOutput == NULL) {
1480
1481
0
        cmsSignalError(xform -> ContextID, cmsERROR_UNKNOWN_EXTENSION, "Unsupported raster format");
1482
0
        return FALSE;
1483
0
    }
1484
1485
31
    xform ->InputFormat  = InputFormat;
1486
31
    xform ->OutputFormat = OutputFormat;
1487
31
    xform ->FromInput    = FromInput;
1488
31
    xform ->ToOutput     = ToOutput;
1489
31
    return TRUE;
1490
31
}
1491
1492
cmsNAMEDCOLORLIST* CMSEXPORT cmsGetTransformInputColorants(cmsHTRANSFORM hTransform)
1493
0
{
1494
0
    _cmsTRANSFORM* xform = (_cmsTRANSFORM*)hTransform;
1495
1496
0
    if (xform == NULL) return NULL;
1497
0
    return xform->InputColorant;
1498
0
}
1499
1500
cmsNAMEDCOLORLIST* CMSEXPORT cmsGetTransformOutputColorants(cmsHTRANSFORM hTransform)
1501
0
{
1502
0
    _cmsTRANSFORM* xform = (_cmsTRANSFORM*)hTransform;
1503
1504
0
    if (xform == NULL) return NULL;
1505
0
    return xform->OutputColorant;
1506
0
}