1//---------------------------------------------------------------------------------
2//
3//  Little Color Management System
4//  Copyright (c) 1998-2014 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#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
38void _cmsAllocAdaptationStateChunk(struct _cmsContext_struct* ctx,
39                                   const struct _cmsContext_struct* src)
40{
41    static _cmsAdaptationStateChunkType AdaptationStateChunk = { DEFAULT_OBSERVER_ADAPTATION_STATE };
42    void* from;
43
44    if (src != NULL) {
45        from = src ->chunks[AdaptationStateContext];
46    }
47    else {
48       from = &AdaptationStateChunk;
49    }
50
51    ctx ->chunks[AdaptationStateContext] = _cmsSubAllocDup(ctx ->MemPool, from, sizeof(_cmsAdaptationStateChunkType));
52}
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.
57cmsFloat64Number CMSEXPORT cmsSetAdaptationStateTHR(cmsContext ContextID, cmsFloat64Number d)
58{
59    cmsFloat64Number prev;
60    _cmsAdaptationStateChunkType* ptr = (_cmsAdaptationStateChunkType*) _cmsContextGetClientChunk(ContextID, AdaptationStateContext);
61
62    // Get previous value for return
63    prev = ptr ->AdaptationState;
64
65    // Set the value if d is positive or zero
66    if (d >= 0.0) {
67
68        ptr ->AdaptationState = d;
69    }
70
71    // Always return previous value
72    return prev;
73}
74
75
76// The adaptation state may be defaulted by this function. If you don't like it, use the extended transform routine
77cmsFloat64Number CMSEXPORT cmsSetAdaptationState(cmsFloat64Number d)
78{
79    return cmsSetAdaptationStateTHR(NULL, d);
80}
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#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.
93void CMSEXPORT cmsSetAlarmCodesTHR(cmsContext ContextID, const cmsUInt16Number AlarmCodesP[cmsMAXCHANNELS])
94{
95    _cmsAlarmCodesChunkType* ContextAlarmCodes = (_cmsAlarmCodesChunkType*) _cmsContextGetClientChunk(ContextID, AlarmCodesContext);
96
97    _cmsAssert(ContextAlarmCodes != NULL); // Can't happen
98
99    memcpy(ContextAlarmCodes->AlarmCodes, AlarmCodesP, sizeof(ContextAlarmCodes->AlarmCodes));
100}
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.
104void CMSEXPORT cmsGetAlarmCodesTHR(cmsContext ContextID, cmsUInt16Number AlarmCodesP[cmsMAXCHANNELS])
105{
106    _cmsAlarmCodesChunkType* ContextAlarmCodes = (_cmsAlarmCodesChunkType*) _cmsContextGetClientChunk(ContextID, AlarmCodesContext);
107
108    _cmsAssert(ContextAlarmCodes != NULL); // Can't happen
109
110    memcpy(AlarmCodesP, ContextAlarmCodes->AlarmCodes, sizeof(ContextAlarmCodes->AlarmCodes));
111}
112
113void CMSEXPORT cmsSetAlarmCodes(const cmsUInt16Number NewAlarm[cmsMAXCHANNELS])
114{
115    _cmsAssert(NewAlarm != NULL);
116
117    cmsSetAlarmCodesTHR(NULL, NewAlarm);
118}
119
120void CMSEXPORT cmsGetAlarmCodes(cmsUInt16Number OldAlarm[cmsMAXCHANNELS])
121{
122    _cmsAssert(OldAlarm != NULL);
123    cmsGetAlarmCodesTHR(NULL, OldAlarm);
124}
125
126
127// Init and duplicate alarm codes
128void _cmsAllocAlarmCodesChunk(struct _cmsContext_struct* ctx,
129                              const struct _cmsContext_struct* src)
130{
131    static _cmsAlarmCodesChunkType AlarmCodesChunk = { DEFAULT_ALARM_CODES_VALUE };
132    void* from;
133
134    if (src != NULL) {
135        from = src ->chunks[AlarmCodesContext];
136    }
137    else {
138       from = &AlarmCodesChunk;
139    }
140
141    ctx ->chunks[AlarmCodesContext] = _cmsSubAllocDup(ctx ->MemPool, from, sizeof(_cmsAlarmCodesChunkType));
142}
143
144// -----------------------------------------------------------------------
145
146// Get rid of transform resources
147void CMSEXPORT cmsDeleteTransform(cmsHTRANSFORM hTransform)
148{
149    _cmsTRANSFORM* p = (_cmsTRANSFORM*) hTransform;
150
151    _cmsAssert(p != NULL);
152
153    if (p -> GamutCheck)
154        cmsPipelineFree(p -> GamutCheck);
155
156    if (p -> Lut)
157        cmsPipelineFree(p -> Lut);
158
159    if (p ->InputColorant)
160        cmsFreeNamedColorList(p ->InputColorant);
161
162    if (p -> OutputColorant)
163        cmsFreeNamedColorList(p ->OutputColorant);
164
165    if (p ->Sequence)
166        cmsFreeProfileSequenceDescription(p ->Sequence);
167
168    if (p ->UserData)
169        p ->FreeUserData(p ->ContextID, p ->UserData);
170
171    _cmsFree(p ->ContextID, (void *) p);
172}
173
174// Apply transform.
175void CMSEXPORT cmsDoTransform(cmsHTRANSFORM  Transform,
176                              const void* InputBuffer,
177                              void* OutputBuffer,
178                              cmsUInt32Number Size)
179
180{
181    _cmsTRANSFORM* p = (_cmsTRANSFORM*) Transform;
182
183    p -> xform(p, InputBuffer, OutputBuffer, Size, Size);
184}
185
186
187// Apply transform.
188void CMSEXPORT cmsDoTransformStride(cmsHTRANSFORM  Transform,
189                              const void* InputBuffer,
190                              void* OutputBuffer,
191                              cmsUInt32Number Size, cmsUInt32Number Stride)
192
193{
194    _cmsTRANSFORM* p = (_cmsTRANSFORM*) Transform;
195
196    p -> xform(p, InputBuffer, OutputBuffer, Size, Stride);
197}
198
199
200// Transform routines ----------------------------------------------------------------------------------------------------------
201
202// Float xform converts floats. Since there are no performance issues, one routine does all job, including gamut check.
203// Note that because extended range, we can use a -1.0 value for out of gamut in this case.
204static
205void FloatXFORM(_cmsTRANSFORM* p,
206                const void* in,
207                void* out, cmsUInt32Number Size, cmsUInt32Number Stride)
208{
209    cmsUInt8Number* accum;
210    cmsUInt8Number* output;
211    cmsFloat32Number fIn[cmsMAXCHANNELS], fOut[cmsMAXCHANNELS];
212    cmsFloat32Number OutOfGamut;
213    cmsUInt32Number i, j;
214
215    accum  = (cmsUInt8Number*)  in;
216    output = (cmsUInt8Number*)  out;
217
218    for (i=0; i < Size; i++) {
219
220        accum = p -> FromInputFloat(p, fIn, accum, Stride);
221
222        // Any gamut chack to do?
223        if (p ->GamutCheck != NULL) {
224
225            // Evaluate gamut marker.
226            cmsPipelineEvalFloat( fIn, &OutOfGamut, p ->GamutCheck);
227
228            // Is current color out of gamut?
229            if (OutOfGamut > 0.0) {
230
231                // Certainly, out of gamut
232                for (j=0; j < cmsMAXCHANNELS; j++)
233                    fOut[j] = -1.0;
234
235            }
236            else {
237                // No, proceed normally
238                cmsPipelineEvalFloat(fIn, fOut, p -> Lut);
239            }
240        }
241        else {
242
243            // No gamut check at all
244            cmsPipelineEvalFloat(fIn, fOut, p -> Lut);
245        }
246
247        // Back to asked representation
248        output = p -> ToOutputFloat(p, fOut, output, Stride);
249    }
250}
251
252
253static
254void NullFloatXFORM(_cmsTRANSFORM* p,
255                    const void* in,
256                    void* out,
257                    cmsUInt32Number Size,
258                    cmsUInt32Number Stride)
259{
260    cmsUInt8Number* accum;
261    cmsUInt8Number* output;
262    cmsFloat32Number fIn[cmsMAXCHANNELS];
263    cmsUInt32Number i, n;
264
265    accum  = (cmsUInt8Number*)  in;
266    output = (cmsUInt8Number*)  out;
267    n = Size;
268
269    for (i=0; i < n; i++) {
270
271        accum  = p -> FromInputFloat(p, fIn, accum, Stride);
272        output = p -> ToOutputFloat(p, fIn, output, Stride);
273    }
274}
275
276// 16 bit precision -----------------------------------------------------------------------------------------------------------
277
278// Null transformation, only applies formatters. No cach?static
279void NullXFORM(_cmsTRANSFORM* p,
280               const void* in,
281               void* out, cmsUInt32Number Size,
282               cmsUInt32Number Stride)
283{
284    cmsUInt8Number* accum;
285    cmsUInt8Number* output;
286    cmsUInt16Number wIn[cmsMAXCHANNELS];
287    cmsUInt32Number i, n;
288
289    accum  = (cmsUInt8Number*)  in;
290    output = (cmsUInt8Number*)  out;
291    n = Size;                    // Buffer len
292
293    for (i=0; i < n; i++) {
294
295        accum  = p -> FromInput(p, wIn, accum, Stride);
296        output = p -> ToOutput(p, wIn, output, Stride);
297    }
298}
299
300
301// No gamut check, no cache, 16 bits
302static
303void PrecalculatedXFORM(_cmsTRANSFORM* p,
304                        const void* in,
305                        void* out, cmsUInt32Number Size, cmsUInt32Number Stride)
306{
307    register cmsUInt8Number* accum;
308    register cmsUInt8Number* output;
309    cmsUInt16Number wIn[cmsMAXCHANNELS], wOut[cmsMAXCHANNELS];
310    cmsUInt32Number i, n;
311
312    accum  = (cmsUInt8Number*)  in;
313    output = (cmsUInt8Number*)  out;
314    n = Size;
315
316    for (i=0; i < n; i++) {
317
318        accum = p -> FromInput(p, wIn, accum, Stride);
319        p ->Lut ->Eval16Fn(wIn, wOut, p -> Lut->Data);
320        output = p -> ToOutput(p, wOut, output, Stride);
321    }
322}
323
324
325// Auxiliar: Handle precalculated gamut check. The retrieval of context may be alittle bit slow, but this function is not critical.
326static
327void TransformOnePixelWithGamutCheck(_cmsTRANSFORM* p,
328                                     const cmsUInt16Number wIn[],
329                                     cmsUInt16Number wOut[])
330{
331    cmsUInt16Number wOutOfGamut;
332
333    p ->GamutCheck ->Eval16Fn(wIn, &wOutOfGamut, p ->GamutCheck ->Data);
334    if (wOutOfGamut >= 1) {
335
336        cmsUInt16Number i;
337        _cmsAlarmCodesChunkType* ContextAlarmCodes = (_cmsAlarmCodesChunkType*) _cmsContextGetClientChunk(p->ContextID, AlarmCodesContext);
338
339        for (i=0; i < p ->Lut->OutputChannels; i++) {
340
341            wOut[i] = ContextAlarmCodes ->AlarmCodes[i];
342        }
343    }
344    else
345        p ->Lut ->Eval16Fn(wIn, wOut, p -> Lut->Data);
346}
347
348// Gamut check, No cach? 16 bits.
349static
350void PrecalculatedXFORMGamutCheck(_cmsTRANSFORM* p,
351                                  const void* in,
352                                  void* out, cmsUInt32Number Size, cmsUInt32Number Stride)
353{
354    cmsUInt8Number* accum;
355    cmsUInt8Number* output;
356    cmsUInt16Number wIn[cmsMAXCHANNELS], wOut[cmsMAXCHANNELS];
357    cmsUInt32Number i, n;
358
359    accum  = (cmsUInt8Number*)  in;
360    output = (cmsUInt8Number*)  out;
361    n = Size;                    // Buffer len
362
363    for (i=0; i < n; i++) {
364
365        accum = p -> FromInput(p, wIn, accum, Stride);
366        TransformOnePixelWithGamutCheck(p, wIn, wOut);
367        output = p -> ToOutput(p, wOut, output, Stride);
368    }
369}
370
371
372// No gamut check, Cach? 16 bits,
373static
374void CachedXFORM(_cmsTRANSFORM* p,
375                 const void* in,
376                 void* out, cmsUInt32Number Size, cmsUInt32Number Stride)
377{
378    cmsUInt8Number* accum;
379    cmsUInt8Number* output;
380    cmsUInt16Number wIn[cmsMAXCHANNELS], wOut[cmsMAXCHANNELS];
381    cmsUInt32Number i, n;
382    _cmsCACHE Cache;
383
384    accum  = (cmsUInt8Number*)  in;
385    output = (cmsUInt8Number*)  out;
386    n = Size;                    // Buffer len
387
388    // Empty buffers for quick memcmp
389    memset(wIn,  0, sizeof(wIn));
390    memset(wOut, 0, sizeof(wOut));
391
392    // Get copy of zero cache
393    memcpy(&Cache, &p ->Cache, sizeof(Cache));
394
395    for (i=0; i < n; i++) {
396
397        accum = p -> FromInput(p, wIn, accum, Stride);
398
399        if (memcmp(wIn, Cache.CacheIn, sizeof(Cache.CacheIn)) == 0) {
400
401            memcpy(wOut, Cache.CacheOut, sizeof(Cache.CacheOut));
402        }
403        else {
404
405            p ->Lut ->Eval16Fn(wIn, wOut, p -> Lut->Data);
406
407            memcpy(Cache.CacheIn,  wIn,  sizeof(Cache.CacheIn));
408            memcpy(Cache.CacheOut, wOut, sizeof(Cache.CacheOut));
409        }
410
411        output = p -> ToOutput(p, wOut, output, Stride);
412    }
413
414}
415
416
417// All those nice features together
418static
419void CachedXFORMGamutCheck(_cmsTRANSFORM* p,
420                           const void* in,
421                           void* out, cmsUInt32Number Size, cmsUInt32Number Stride)
422{
423       cmsUInt8Number* accum;
424       cmsUInt8Number* output;
425       cmsUInt16Number wIn[cmsMAXCHANNELS], wOut[cmsMAXCHANNELS];
426       cmsUInt32Number i, n;
427       _cmsCACHE Cache;
428
429       accum  = (cmsUInt8Number*)  in;
430       output = (cmsUInt8Number*)  out;
431       n = Size;                    // Buffer len
432
433       // Empty buffers for quick memcmp
434       memset(wIn,  0, sizeof(cmsUInt16Number) * cmsMAXCHANNELS);
435       memset(wOut, 0, sizeof(cmsUInt16Number) * cmsMAXCHANNELS);
436
437       // Get copy of zero cache
438       memcpy(&Cache, &p ->Cache, sizeof(Cache));
439
440       for (i=0; i < n; i++) {
441
442            accum = p -> FromInput(p, wIn, accum, Stride);
443
444            if (memcmp(wIn, Cache.CacheIn, sizeof(Cache.CacheIn)) == 0) {
445                    memcpy(wOut, Cache.CacheOut, sizeof(Cache.CacheOut));
446            }
447            else {
448                    TransformOnePixelWithGamutCheck(p, wIn, wOut);
449                    memcpy(Cache.CacheIn, wIn, sizeof(Cache.CacheIn));
450                    memcpy(Cache.CacheOut, wOut, sizeof(Cache.CacheOut));
451            }
452
453            output = p -> ToOutput(p, wOut, output, Stride);
454       }
455
456}
457
458// -------------------------------------------------------------------------------------------------------------
459
460// List of used-defined transform factories
461typedef struct _cmsTransformCollection_st {
462
463    _cmsTransformFactory  Factory;
464    struct _cmsTransformCollection_st *Next;
465
466} _cmsTransformCollection;
467
468// The linked list head
469_cmsTransformPluginChunkType _cmsTransformPluginChunk = { NULL };
470
471
472// Duplicates the zone of memory used by the plug-in in the new context
473static
474void DupPluginTransformList(struct _cmsContext_struct* ctx,
475                                               const struct _cmsContext_struct* src)
476{
477   _cmsTransformPluginChunkType newHead = { NULL };
478   _cmsTransformCollection*  entry;
479   _cmsTransformCollection*  Anterior = NULL;
480   _cmsTransformPluginChunkType* head = (_cmsTransformPluginChunkType*) src->chunks[TransformPlugin];
481
482    // Walk the list copying all nodes
483   for (entry = head->TransformCollection;
484        entry != NULL;
485        entry = entry ->Next) {
486
487            _cmsTransformCollection *newEntry = ( _cmsTransformCollection *) _cmsSubAllocDup(ctx ->MemPool, entry, sizeof(_cmsTransformCollection));
488
489            if (newEntry == NULL)
490                return;
491
492            // We want to keep the linked list order, so this is a little bit tricky
493            newEntry -> Next = NULL;
494            if (Anterior)
495                Anterior -> Next = newEntry;
496
497            Anterior = newEntry;
498
499            if (newHead.TransformCollection == NULL)
500                newHead.TransformCollection = newEntry;
501    }
502
503  ctx ->chunks[TransformPlugin] = _cmsSubAllocDup(ctx->MemPool, &newHead, sizeof(_cmsTransformPluginChunkType));
504}
505
506void _cmsAllocTransformPluginChunk(struct _cmsContext_struct* ctx,
507                                        const struct _cmsContext_struct* src)
508{
509    if (src != NULL) {
510
511        // Copy all linked list
512        DupPluginTransformList(ctx, src);
513    }
514    else {
515        static _cmsTransformPluginChunkType TransformPluginChunkType = { NULL };
516        ctx ->chunks[TransformPlugin] = _cmsSubAllocDup(ctx ->MemPool, &TransformPluginChunkType, sizeof(_cmsTransformPluginChunkType));
517    }
518}
519
520
521
522// Register new ways to transform
523cmsBool  _cmsRegisterTransformPlugin(cmsContext ContextID, cmsPluginBase* Data)
524{
525    cmsPluginTransform* Plugin = (cmsPluginTransform*) Data;
526    _cmsTransformCollection* fl;
527    _cmsTransformPluginChunkType* ctx = ( _cmsTransformPluginChunkType*) _cmsContextGetClientChunk(ContextID,TransformPlugin);
528
529    if (Data == NULL) {
530
531        // Free the chain. Memory is safely freed at exit
532        ctx->TransformCollection = NULL;
533        return TRUE;
534    }
535
536    // Factory callback is required
537    if (Plugin ->Factory == NULL) return FALSE;
538
539
540    fl = (_cmsTransformCollection*) _cmsPluginMalloc(ContextID, sizeof(_cmsTransformCollection));
541    if (fl == NULL) return FALSE;
542
543    // Copy the parameters
544    fl ->Factory = Plugin ->Factory;
545
546    // Keep linked list
547    fl ->Next = ctx->TransformCollection;
548    ctx->TransformCollection = fl;
549
550    // All is ok
551    return TRUE;
552}
553
554
555void CMSEXPORT _cmsSetTransformUserData(struct _cmstransform_struct *CMMcargo, void* ptr, _cmsFreeUserDataFn FreePrivateDataFn)
556{
557    _cmsAssert(CMMcargo != NULL);
558    CMMcargo ->UserData = ptr;
559    CMMcargo ->FreeUserData = FreePrivateDataFn;
560}
561
562// returns the pointer defined by the plug-in to store private data
563void * CMSEXPORT _cmsGetTransformUserData(struct _cmstransform_struct *CMMcargo)
564{
565    _cmsAssert(CMMcargo != NULL);
566    return CMMcargo ->UserData;
567}
568
569// returns the current formatters
570void CMSEXPORT _cmsGetTransformFormatters16(struct _cmstransform_struct *CMMcargo, cmsFormatter16* FromInput, cmsFormatter16* ToOutput)
571{
572     _cmsAssert(CMMcargo != NULL);
573     if (FromInput) *FromInput = CMMcargo ->FromInput;
574     if (ToOutput)  *ToOutput  = CMMcargo ->ToOutput;
575}
576
577void CMSEXPORT _cmsGetTransformFormattersFloat(struct _cmstransform_struct *CMMcargo, cmsFormatterFloat* FromInput, cmsFormatterFloat* ToOutput)
578{
579     _cmsAssert(CMMcargo != NULL);
580     if (FromInput) *FromInput = CMMcargo ->FromInputFloat;
581     if (ToOutput)  *ToOutput  = CMMcargo ->ToOutputFloat;
582}
583
584
585// Allocate transform struct and set it to defaults. Ask the optimization plug-in about if those formats are proper
586// for separated transforms. If this is the case,
587static
588_cmsTRANSFORM* AllocEmptyTransform(cmsContext ContextID, cmsPipeline* lut,
589                                               cmsUInt32Number Intent, cmsUInt32Number* InputFormat, cmsUInt32Number* OutputFormat, cmsUInt32Number* dwFlags)
590{
591     _cmsTransformPluginChunkType* ctx = ( _cmsTransformPluginChunkType*) _cmsContextGetClientChunk(ContextID, TransformPlugin);
592     _cmsTransformCollection* Plugin;
593
594    // Allocate needed memory
595    _cmsTRANSFORM* p = (_cmsTRANSFORM*) _cmsMallocZero(ContextID, sizeof(_cmsTRANSFORM));
596    if (!p) return NULL;
597
598    // Store the proposed pipeline
599    p ->Lut = lut;
600
601    // Let's see if any plug-in want to do the transform by itself
602    for (Plugin = ctx ->TransformCollection;
603        Plugin != NULL;
604        Plugin = Plugin ->Next) {
605
606            if (Plugin ->Factory(&p->xform, &p->UserData, &p ->FreeUserData, &p ->Lut, InputFormat, OutputFormat, dwFlags)) {
607
608                // Last plugin in the declaration order takes control. We just keep
609                // the original parameters as a logging.
610                // Note that cmsFLAGS_CAN_CHANGE_FORMATTER is not set, so by default
611                // an optimized transform is not reusable. The plug-in can, however, change
612                // the flags and make it suitable.
613
614                p ->ContextID       = ContextID;
615                p ->InputFormat     = *InputFormat;
616                p ->OutputFormat    = *OutputFormat;
617                p ->dwOriginalFlags = *dwFlags;
618
619                // Fill the formatters just in case the optimized routine is interested.
620                // No error is thrown if the formatter doesn't exist. It is up to the optimization
621                // factory to decide what to do in those cases.
622                p ->FromInput      = _cmsGetFormatter(ContextID, *InputFormat,  cmsFormatterInput, CMS_PACK_FLAGS_16BITS).Fmt16;
623                p ->ToOutput       = _cmsGetFormatter(ContextID, *OutputFormat, cmsFormatterOutput, CMS_PACK_FLAGS_16BITS).Fmt16;
624                p ->FromInputFloat = _cmsGetFormatter(ContextID, *InputFormat,  cmsFormatterInput, CMS_PACK_FLAGS_FLOAT).FmtFloat;
625                p ->ToOutputFloat  = _cmsGetFormatter(ContextID, *OutputFormat, cmsFormatterOutput, CMS_PACK_FLAGS_FLOAT).FmtFloat;
626
627                return p;
628            }
629    }
630
631    // Not suitable for the transform plug-in, let's check  the pipeline plug-in
632    if (p ->Lut != NULL)
633        _cmsOptimizePipeline(ContextID, &p->Lut, Intent, InputFormat, OutputFormat, dwFlags);
634
635    // Check whatever this is a true floating point transform
636    if (_cmsFormatterIsFloat(*InputFormat) && _cmsFormatterIsFloat(*OutputFormat)) {
637
638        // Get formatter function always return a valid union, but the contents of this union may be NULL.
639        p ->FromInputFloat = _cmsGetFormatter(ContextID, *InputFormat,  cmsFormatterInput, CMS_PACK_FLAGS_FLOAT).FmtFloat;
640        p ->ToOutputFloat  = _cmsGetFormatter(ContextID, *OutputFormat, cmsFormatterOutput, CMS_PACK_FLAGS_FLOAT).FmtFloat;
641        *dwFlags |= cmsFLAGS_CAN_CHANGE_FORMATTER;
642
643        if (p ->FromInputFloat == NULL || p ->ToOutputFloat == NULL) {
644
645            cmsSignalError(ContextID, cmsERROR_UNKNOWN_EXTENSION, "Unsupported raster format");
646            _cmsFree(ContextID, p);
647            return NULL;
648        }
649
650        if (*dwFlags & cmsFLAGS_NULLTRANSFORM) {
651
652            p ->xform = NullFloatXFORM;
653        }
654        else {
655            // Float transforms don't use cach? always are non-NULL
656            p ->xform = FloatXFORM;
657        }
658
659    }
660    else {
661
662        if (*InputFormat == 0 && *OutputFormat == 0) {
663            p ->FromInput = p ->ToOutput = NULL;
664            *dwFlags |= cmsFLAGS_CAN_CHANGE_FORMATTER;
665        }
666        else {
667
668            int BytesPerPixelInput;
669
670            p ->FromInput = _cmsGetFormatter(ContextID, *InputFormat,  cmsFormatterInput, CMS_PACK_FLAGS_16BITS).Fmt16;
671            p ->ToOutput  = _cmsGetFormatter(ContextID, *OutputFormat, cmsFormatterOutput, CMS_PACK_FLAGS_16BITS).Fmt16;
672
673            if (p ->FromInput == NULL || p ->ToOutput == NULL) {
674
675                cmsSignalError(ContextID, cmsERROR_UNKNOWN_EXTENSION, "Unsupported raster format");
676                _cmsFree(ContextID, p);
677                return NULL;
678            }
679
680            BytesPerPixelInput = T_BYTES(p ->InputFormat);
681            if (BytesPerPixelInput == 0 || BytesPerPixelInput >= 2)
682                   *dwFlags |= cmsFLAGS_CAN_CHANGE_FORMATTER;
683
684        }
685
686        if (*dwFlags & cmsFLAGS_NULLTRANSFORM) {
687
688            p ->xform = NullXFORM;
689        }
690        else {
691            if (*dwFlags & cmsFLAGS_NOCACHE) {
692
693                if (*dwFlags & cmsFLAGS_GAMUTCHECK)
694                    p ->xform = PrecalculatedXFORMGamutCheck;  // Gamut check, no cach?                else
695                    p ->xform = PrecalculatedXFORM;  // No cach? no gamut check
696            }
697            else {
698
699                if (*dwFlags & cmsFLAGS_GAMUTCHECK)
700                    p ->xform = CachedXFORMGamutCheck;    // Gamut check, cach?                else
701                    p ->xform = CachedXFORM;  // No gamut check, cach?
702            }
703        }
704    }
705
706    p ->InputFormat     = *InputFormat;
707    p ->OutputFormat    = *OutputFormat;
708    p ->dwOriginalFlags = *dwFlags;
709    p ->ContextID       = ContextID;
710    p ->UserData        = NULL;
711    return p;
712}
713
714static
715cmsBool GetXFormColorSpaces(int nProfiles, cmsHPROFILE hProfiles[], cmsColorSpaceSignature* Input, cmsColorSpaceSignature* Output)
716{
717    cmsColorSpaceSignature ColorSpaceIn, ColorSpaceOut;
718    cmsColorSpaceSignature PostColorSpace;
719    int i;
720
721    if (nProfiles <= 0) return FALSE;
722    if (hProfiles[0] == NULL) return FALSE;
723
724    *Input = PostColorSpace = cmsGetColorSpace(hProfiles[0]);
725
726    for (i=0; i < nProfiles; i++) {
727
728        cmsProfileClassSignature cls;
729        cmsHPROFILE hProfile = hProfiles[i];
730
731        int lIsInput = (PostColorSpace != cmsSigXYZData) &&
732                       (PostColorSpace != cmsSigLabData);
733
734        if (hProfile == NULL) return FALSE;
735
736        cls = cmsGetDeviceClass(hProfile);
737
738        if (cls == cmsSigNamedColorClass) {
739
740            ColorSpaceIn    = cmsSig1colorData;
741            ColorSpaceOut   = (nProfiles > 1) ? cmsGetPCS(hProfile) : cmsGetColorSpace(hProfile);
742        }
743        else
744        if (lIsInput || (cls == cmsSigLinkClass)) {
745
746            ColorSpaceIn    = cmsGetColorSpace(hProfile);
747            ColorSpaceOut   = cmsGetPCS(hProfile);
748        }
749        else
750        {
751            ColorSpaceIn    = cmsGetPCS(hProfile);
752            ColorSpaceOut   = cmsGetColorSpace(hProfile);
753        }
754
755        if (i==0)
756            *Input = ColorSpaceIn;
757
758        PostColorSpace = ColorSpaceOut;
759    }
760
761    *Output = PostColorSpace;
762
763    return TRUE;
764}
765
766// Check colorspace
767static
768cmsBool  IsProperColorSpace(cmsColorSpaceSignature Check, cmsUInt32Number dwFormat)
769{
770    int Space1 = T_COLORSPACE(dwFormat);
771    int Space2 = _cmsLCMScolorSpace(Check);
772
773    if (Space1 == PT_ANY) return TRUE;
774    if (Space1 == Space2) return TRUE;
775
776    if (Space1 == PT_LabV2 && Space2 == PT_Lab) return TRUE;
777    if (Space1 == PT_Lab   && Space2 == PT_LabV2) return TRUE;
778
779    return FALSE;
780}
781
782// ----------------------------------------------------------------------------------------------------------------
783
784static
785void SetWhitePoint(cmsCIEXYZ* wtPt, const cmsCIEXYZ* src)
786{
787    if (src == NULL) {
788        wtPt ->X = cmsD50X;
789        wtPt ->Y = cmsD50Y;
790        wtPt ->Z = cmsD50Z;
791    }
792    else {
793        wtPt ->X = src->X;
794        wtPt ->Y = src->Y;
795        wtPt ->Z = src->Z;
796    }
797
798}
799
800// New to lcms 2.0 -- have all parameters available.
801cmsHTRANSFORM CMSEXPORT cmsCreateExtendedTransform(cmsContext ContextID,
802                                                   cmsUInt32Number nProfiles, cmsHPROFILE hProfiles[],
803                                                   cmsBool  BPC[],
804                                                   cmsUInt32Number Intents[],
805                                                   cmsFloat64Number AdaptationStates[],
806                                                   cmsHPROFILE hGamutProfile,
807                                                   cmsUInt32Number nGamutPCSposition,
808                                                   cmsUInt32Number InputFormat,
809                                                   cmsUInt32Number OutputFormat,
810                                                   cmsUInt32Number dwFlags)
811{
812    _cmsTRANSFORM* xform;
813    cmsColorSpaceSignature EntryColorSpace;
814    cmsColorSpaceSignature ExitColorSpace;
815    cmsPipeline* Lut;
816    cmsUInt32Number LastIntent = Intents[nProfiles-1];
817
818    // If it is a fake transform
819    if (dwFlags & cmsFLAGS_NULLTRANSFORM)
820    {
821        return AllocEmptyTransform(ContextID, NULL, INTENT_PERCEPTUAL, &InputFormat, &OutputFormat, &dwFlags);
822    }
823
824    // If gamut check is requested, make sure we have a gamut profile
825    if (dwFlags & cmsFLAGS_GAMUTCHECK) {
826        if (hGamutProfile == NULL) dwFlags &= ~cmsFLAGS_GAMUTCHECK;
827    }
828
829    // On floating point transforms, inhibit cache
830    if (_cmsFormatterIsFloat(InputFormat) || _cmsFormatterIsFloat(OutputFormat))
831        dwFlags |= cmsFLAGS_NOCACHE;
832
833    // Mark entry/exit spaces
834    if (!GetXFormColorSpaces(nProfiles, hProfiles, &EntryColorSpace, &ExitColorSpace)) {
835        cmsSignalError(ContextID, cmsERROR_NULL, "NULL input profiles on transform");
836        return NULL;
837    }
838
839    // Check if proper colorspaces
840    if (!IsProperColorSpace(EntryColorSpace, InputFormat)) {
841        cmsSignalError(ContextID, cmsERROR_COLORSPACE_CHECK, "Wrong input color space on transform");
842        return NULL;
843    }
844
845    if (!IsProperColorSpace(ExitColorSpace, OutputFormat)) {
846        cmsSignalError(ContextID, cmsERROR_COLORSPACE_CHECK, "Wrong output color space on transform");
847        return NULL;
848    }
849
850    // Create a pipeline with all transformations
851    Lut = _cmsLinkProfiles(ContextID, nProfiles, Intents, hProfiles, BPC, AdaptationStates, dwFlags);
852    if (Lut == NULL) {
853        cmsSignalError(ContextID, cmsERROR_NOT_SUITABLE, "Couldn't link the profiles");
854        return NULL;
855    }
856
857    // Check channel count
858    if ((cmsChannelsOf(EntryColorSpace) != cmsPipelineInputChannels(Lut)) ||
859        (cmsChannelsOf(ExitColorSpace)  != cmsPipelineOutputChannels(Lut))) {
860        cmsPipelineFree(Lut);
861        cmsSignalError(ContextID, cmsERROR_NOT_SUITABLE, "Channel count doesn't match. Profile is corrupted");
862        return NULL;
863    }
864
865
866    // All seems ok
867    xform = AllocEmptyTransform(ContextID, Lut, LastIntent, &InputFormat, &OutputFormat, &dwFlags);
868    if (xform == NULL) {
869        return NULL;
870    }
871
872    // Keep values
873    xform ->EntryColorSpace = EntryColorSpace;
874    xform ->ExitColorSpace  = ExitColorSpace;
875    xform ->RenderingIntent = Intents[nProfiles-1];
876
877    // Take white points
878    SetWhitePoint(&xform->EntryWhitePoint, (cmsCIEXYZ*) cmsReadTag(hProfiles[0], cmsSigMediaWhitePointTag));
879    SetWhitePoint(&xform->ExitWhitePoint,  (cmsCIEXYZ*) cmsReadTag(hProfiles[nProfiles-1], cmsSigMediaWhitePointTag));
880
881
882    // Create a gamut check LUT if requested
883    if (hGamutProfile != NULL && (dwFlags & cmsFLAGS_GAMUTCHECK))
884        xform ->GamutCheck  = _cmsCreateGamutCheckPipeline(ContextID, hProfiles,
885                                                        BPC, Intents,
886                                                        AdaptationStates,
887                                                        nGamutPCSposition,
888                                                        hGamutProfile);
889
890
891    // Try to read input and output colorant table
892    if (cmsIsTag(hProfiles[0], cmsSigColorantTableTag)) {
893
894        // Input table can only come in this way.
895        xform ->InputColorant = cmsDupNamedColorList((cmsNAMEDCOLORLIST*) cmsReadTag(hProfiles[0], cmsSigColorantTableTag));
896    }
897
898    // Output is a little bit more complex.
899    if (cmsGetDeviceClass(hProfiles[nProfiles-1]) == cmsSigLinkClass) {
900
901        // This tag may exist only on devicelink profiles.
902        if (cmsIsTag(hProfiles[nProfiles-1], cmsSigColorantTableOutTag)) {
903
904            // It may be NULL if error
905            xform ->OutputColorant = cmsDupNamedColorList((cmsNAMEDCOLORLIST*) cmsReadTag(hProfiles[nProfiles-1], cmsSigColorantTableOutTag));
906        }
907
908    } else {
909
910        if (cmsIsTag(hProfiles[nProfiles-1], cmsSigColorantTableTag)) {
911
912            xform -> OutputColorant = cmsDupNamedColorList((cmsNAMEDCOLORLIST*) cmsReadTag(hProfiles[nProfiles-1], cmsSigColorantTableTag));
913        }
914    }
915
916    // Store the sequence of profiles
917    if (dwFlags & cmsFLAGS_KEEP_SEQUENCE) {
918        xform ->Sequence = _cmsCompileProfileSequence(ContextID, nProfiles, hProfiles);
919    }
920    else
921        xform ->Sequence = NULL;
922
923    // If this is a cached transform, init first value, which is zero (16 bits only)
924    if (!(dwFlags & cmsFLAGS_NOCACHE)) {
925
926        memset(&xform ->Cache.CacheIn, 0, sizeof(xform ->Cache.CacheIn));
927
928        if (xform ->GamutCheck != NULL) {
929            TransformOnePixelWithGamutCheck(xform, xform ->Cache.CacheIn, xform->Cache.CacheOut);
930        }
931        else {
932
933            xform ->Lut ->Eval16Fn(xform ->Cache.CacheIn, xform->Cache.CacheOut, xform -> Lut->Data);
934        }
935
936    }
937
938    return (cmsHTRANSFORM) xform;
939}
940
941// Multiprofile transforms: Gamut check is not available here, as it is unclear from which profile the gamut comes.
942cmsHTRANSFORM CMSEXPORT cmsCreateMultiprofileTransformTHR(cmsContext ContextID,
943                                                       cmsHPROFILE hProfiles[],
944                                                       cmsUInt32Number nProfiles,
945                                                       cmsUInt32Number InputFormat,
946                                                       cmsUInt32Number OutputFormat,
947                                                       cmsUInt32Number Intent,
948                                                       cmsUInt32Number dwFlags)
949{
950    cmsUInt32Number i;
951    cmsBool BPC[256];
952    cmsUInt32Number Intents[256];
953    cmsFloat64Number AdaptationStates[256];
954
955    if (nProfiles <= 0 || nProfiles > 255) {
956         cmsSignalError(ContextID, cmsERROR_RANGE, "Wrong number of profiles. 1..255 expected, %d found.", nProfiles);
957        return NULL;
958    }
959
960    for (i=0; i < nProfiles; i++) {
961        BPC[i] = dwFlags & cmsFLAGS_BLACKPOINTCOMPENSATION ? TRUE : FALSE;
962        Intents[i] = Intent;
963        AdaptationStates[i] = cmsSetAdaptationStateTHR(ContextID, -1);
964    }
965
966
967    return cmsCreateExtendedTransform(ContextID, nProfiles, hProfiles, BPC, Intents, AdaptationStates, NULL, 0, InputFormat, OutputFormat, dwFlags);
968}
969
970
971
972cmsHTRANSFORM CMSEXPORT cmsCreateMultiprofileTransform(cmsHPROFILE hProfiles[],
973                                                  cmsUInt32Number nProfiles,
974                                                  cmsUInt32Number InputFormat,
975                                                  cmsUInt32Number OutputFormat,
976                                                  cmsUInt32Number Intent,
977                                                  cmsUInt32Number dwFlags)
978{
979
980    if (nProfiles <= 0 || nProfiles > 255) {
981         cmsSignalError(NULL, cmsERROR_RANGE, "Wrong number of profiles. 1..255 expected, %d found.", nProfiles);
982         return NULL;
983    }
984
985    return cmsCreateMultiprofileTransformTHR(cmsGetProfileContextID(hProfiles[0]),
986                                                  hProfiles,
987                                                  nProfiles,
988                                                  InputFormat,
989                                                  OutputFormat,
990                                                  Intent,
991                                                  dwFlags);
992}
993
994cmsHTRANSFORM CMSEXPORT cmsCreateTransformTHR(cmsContext ContextID,
995                                              cmsHPROFILE Input,
996                                              cmsUInt32Number InputFormat,
997                                              cmsHPROFILE Output,
998                                              cmsUInt32Number OutputFormat,
999                                              cmsUInt32Number Intent,
1000                                              cmsUInt32Number dwFlags)
1001{
1002
1003    cmsHPROFILE hArray[2];
1004
1005    hArray[0] = Input;
1006    hArray[1] = Output;
1007
1008    return cmsCreateMultiprofileTransformTHR(ContextID, hArray, Output == NULL ? 1 : 2, InputFormat, OutputFormat, Intent, dwFlags);
1009}
1010
1011CMSAPI cmsHTRANSFORM CMSEXPORT cmsCreateTransform(cmsHPROFILE Input,
1012                                                  cmsUInt32Number InputFormat,
1013                                                  cmsHPROFILE Output,
1014                                                  cmsUInt32Number OutputFormat,
1015                                                  cmsUInt32Number Intent,
1016                                                  cmsUInt32Number dwFlags)
1017{
1018    return cmsCreateTransformTHR(cmsGetProfileContextID(Input), Input, InputFormat, Output, OutputFormat, Intent, dwFlags);
1019}
1020
1021
1022cmsHTRANSFORM CMSEXPORT cmsCreateProofingTransformTHR(cmsContext ContextID,
1023                                                   cmsHPROFILE InputProfile,
1024                                                   cmsUInt32Number InputFormat,
1025                                                   cmsHPROFILE OutputProfile,
1026                                                   cmsUInt32Number OutputFormat,
1027                                                   cmsHPROFILE ProofingProfile,
1028                                                   cmsUInt32Number nIntent,
1029                                                   cmsUInt32Number ProofingIntent,
1030                                                   cmsUInt32Number dwFlags)
1031{
1032    cmsHPROFILE hArray[4];
1033    cmsUInt32Number Intents[4];
1034    cmsBool  BPC[4];
1035    cmsFloat64Number Adaptation[4];
1036    cmsBool  DoBPC = (dwFlags & cmsFLAGS_BLACKPOINTCOMPENSATION) ? TRUE : FALSE;
1037
1038
1039    hArray[0]  = InputProfile; hArray[1] = ProofingProfile; hArray[2]  = ProofingProfile;               hArray[3] = OutputProfile;
1040    Intents[0] = nIntent;      Intents[1] = nIntent;        Intents[2] = INTENT_RELATIVE_COLORIMETRIC;  Intents[3] = ProofingIntent;
1041    BPC[0]     = DoBPC;        BPC[1] = DoBPC;              BPC[2] = 0;                                 BPC[3] = 0;
1042
1043    Adaptation[0] = Adaptation[1] = Adaptation[2] = Adaptation[3] = cmsSetAdaptationStateTHR(ContextID, -1);
1044
1045    if (!(dwFlags & (cmsFLAGS_SOFTPROOFING|cmsFLAGS_GAMUTCHECK)))
1046        return cmsCreateTransformTHR(ContextID, InputProfile, InputFormat, OutputProfile, OutputFormat, nIntent, dwFlags);
1047
1048    return cmsCreateExtendedTransform(ContextID, 4, hArray, BPC, Intents, Adaptation,
1049                                        ProofingProfile, 1, InputFormat, OutputFormat, dwFlags);
1050
1051}
1052
1053
1054cmsHTRANSFORM CMSEXPORT cmsCreateProofingTransform(cmsHPROFILE InputProfile,
1055                                                   cmsUInt32Number InputFormat,
1056                                                   cmsHPROFILE OutputProfile,
1057                                                   cmsUInt32Number OutputFormat,
1058                                                   cmsHPROFILE ProofingProfile,
1059                                                   cmsUInt32Number nIntent,
1060                                                   cmsUInt32Number ProofingIntent,
1061                                                   cmsUInt32Number dwFlags)
1062{
1063    return cmsCreateProofingTransformTHR(cmsGetProfileContextID(InputProfile),
1064                                                   InputProfile,
1065                                                   InputFormat,
1066                                                   OutputProfile,
1067                                                   OutputFormat,
1068                                                   ProofingProfile,
1069                                                   nIntent,
1070                                                   ProofingIntent,
1071                                                   dwFlags);
1072}
1073
1074
1075// Grab the ContextID from an open transform. Returns NULL if a NULL transform is passed
1076cmsContext CMSEXPORT cmsGetTransformContextID(cmsHTRANSFORM hTransform)
1077{
1078    _cmsTRANSFORM* xform = (_cmsTRANSFORM*) hTransform;
1079
1080    if (xform == NULL) return NULL;
1081    return xform -> ContextID;
1082}
1083
1084// Grab the input/output formats
1085cmsUInt32Number CMSEXPORT cmsGetTransformInputFormat(cmsHTRANSFORM hTransform)
1086{
1087    _cmsTRANSFORM* xform = (_cmsTRANSFORM*) hTransform;
1088
1089    if (xform == NULL) return 0;
1090    return xform->InputFormat;
1091}
1092
1093cmsUInt32Number CMSEXPORT cmsGetTransformOutputFormat(cmsHTRANSFORM hTransform)
1094{
1095    _cmsTRANSFORM* xform = (_cmsTRANSFORM*) hTransform;
1096
1097    if (xform == NULL) return 0;
1098    return xform->OutputFormat;
1099}
1100
1101// For backwards compatibility
1102cmsBool CMSEXPORT cmsChangeBuffersFormat(cmsHTRANSFORM hTransform,
1103                                         cmsUInt32Number InputFormat,
1104                                         cmsUInt32Number OutputFormat)
1105{
1106
1107    _cmsTRANSFORM* xform = (_cmsTRANSFORM*) hTransform;
1108    cmsFormatter16 FromInput, ToOutput;
1109
1110
1111    // We only can afford to change formatters if previous transform is at least 16 bits
1112    if (!(xform ->dwOriginalFlags & cmsFLAGS_CAN_CHANGE_FORMATTER)) {
1113
1114        cmsSignalError(xform ->ContextID, cmsERROR_NOT_SUITABLE, "cmsChangeBuffersFormat works only on transforms created originally with at least 16 bits of precision");
1115        return FALSE;
1116    }
1117
1118    FromInput = _cmsGetFormatter(xform->ContextID, InputFormat,  cmsFormatterInput, CMS_PACK_FLAGS_16BITS).Fmt16;
1119    ToOutput  = _cmsGetFormatter(xform->ContextID, OutputFormat, cmsFormatterOutput, CMS_PACK_FLAGS_16BITS).Fmt16;
1120
1121    if (FromInput == NULL || ToOutput == NULL) {
1122
1123        cmsSignalError(xform -> ContextID, cmsERROR_UNKNOWN_EXTENSION, "Unsupported raster format");
1124        return FALSE;
1125    }
1126
1127    xform ->InputFormat  = InputFormat;
1128    xform ->OutputFormat = OutputFormat;
1129    xform ->FromInput    = FromInput;
1130    xform ->ToOutput     = ToOutput;
1131    return TRUE;
1132}
1133