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