Tizen 2.0 Release
[external/lcms.git] / src / cmscnvrt.c
1 //---------------------------------------------------------------------------------
2 //
3 //  Little Color Management System
4 //  Copyright (c) 1998-2011 Marti Maria Saguer
5 //
6 // Permission is hereby granted, free of charge, to any person obtaining 
7 // a copy of this software and associated documentation files (the "Software"), 
8 // to deal in the Software without restriction, including without limitation 
9 // the rights to use, copy, modify, merge, publish, distribute, sublicense, 
10 // and/or sell copies of the Software, and to permit persons to whom the Software 
11 // is furnished to do so, subject to the following conditions:
12 //
13 // The above copyright notice and this permission notice shall be included in 
14 // all copies or substantial portions of the Software.
15 //
16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
17 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 
18 // THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
19 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 
20 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 
21 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 
22 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 //
24 //---------------------------------------------------------------------------------
25 //
26
27 #include "lcms2_internal.h"
28
29
30 // Link several profiles to obtain a single LUT modelling the whole color transform. Intents, Black point 
31 // compensation and Adaptation parameters may vary across profiles. BPC and Adaptation refers to the PCS
32 // after the profile. I.e, BPC[0] refers to connexion between profile(0) and profile(1)
33 cmsPipeline* _cmsLinkProfiles(cmsContext     ContextID, 
34                               cmsUInt32Number nProfiles,
35                               cmsUInt32Number Intents[], 
36                               cmsHPROFILE     hProfiles[], 
37                               cmsBool         BPC[],
38                               cmsFloat64Number AdaptationStates[],
39                               cmsUInt32Number dwFlags);
40                                                         
41 //---------------------------------------------------------------------------------
42
43 // This is the default routine for ICC-style intents. A user may decide to override it by using a plugin. 
44 // Supported intents are perceptual, relative colorimetric, saturation and ICC-absolute colorimetric
45 static 
46 cmsPipeline* DefaultICCintents(cmsContext     ContextID, 
47                                cmsUInt32Number nProfiles,
48                                cmsUInt32Number Intents[], 
49                                cmsHPROFILE     hProfiles[], 
50                                cmsBool         BPC[],
51                                cmsFloat64Number AdaptationStates[],
52                                cmsUInt32Number dwFlags);
53
54 //---------------------------------------------------------------------------------
55
56 // This is the entry for black-preserving K-only intents, which are non-ICC. Last profile have to be a output profile
57 // to do the trick (no devicelinks allowed at that position)
58 static
59 cmsPipeline*  BlackPreservingKOnlyIntents(cmsContext     ContextID, 
60                                           cmsUInt32Number nProfiles,
61                                           cmsUInt32Number Intents[], 
62                                           cmsHPROFILE     hProfiles[], 
63                                           cmsBool         BPC[],
64                                           cmsFloat64Number AdaptationStates[],
65                                           cmsUInt32Number dwFlags);
66
67 //---------------------------------------------------------------------------------
68
69 // This is the entry for black-plane preserving, which are non-ICC. Again, Last profile have to be a output profile
70 // to do the trick (no devicelinks allowed at that position)
71 static
72 cmsPipeline*  BlackPreservingKPlaneIntents(cmsContext     ContextID, 
73                                            cmsUInt32Number nProfiles,
74                                            cmsUInt32Number Intents[], 
75                                            cmsHPROFILE     hProfiles[], 
76                                            cmsBool         BPC[],
77                                            cmsFloat64Number AdaptationStates[],
78                                            cmsUInt32Number dwFlags);
79
80 //---------------------------------------------------------------------------------
81
82
83 // This is a structure holding implementations for all supported intents.
84 typedef struct _cms_intents_list {
85
86     cmsUInt32Number Intent;
87     char            Description[256];
88     cmsIntentFn     Link;
89     struct _cms_intents_list*  Next;
90
91 } cmsIntentsList;
92
93
94 // Built-in intents
95 static cmsIntentsList DefaultIntents[] = { 
96
97     { INTENT_PERCEPTUAL,                            "Perceptual",                                   DefaultICCintents,            &DefaultIntents[1] },
98     { INTENT_RELATIVE_COLORIMETRIC,                 "Relative colorimetric",                        DefaultICCintents,            &DefaultIntents[2] },
99     { INTENT_SATURATION,                            "Saturation",                                   DefaultICCintents,            &DefaultIntents[3] },
100     { INTENT_ABSOLUTE_COLORIMETRIC,                 "Absolute colorimetric",                        DefaultICCintents,            &DefaultIntents[4] },
101     { INTENT_PRESERVE_K_ONLY_PERCEPTUAL,            "Perceptual preserving black ink",              BlackPreservingKOnlyIntents,  &DefaultIntents[5] },
102     { INTENT_PRESERVE_K_ONLY_RELATIVE_COLORIMETRIC, "Relative colorimetric preserving black ink",   BlackPreservingKOnlyIntents,  &DefaultIntents[6] },
103     { INTENT_PRESERVE_K_ONLY_SATURATION,            "Saturation preserving black ink",              BlackPreservingKOnlyIntents,  &DefaultIntents[7] },
104     { INTENT_PRESERVE_K_PLANE_PERCEPTUAL,           "Perceptual preserving black plane",            BlackPreservingKPlaneIntents, &DefaultIntents[8] },
105     { INTENT_PRESERVE_K_PLANE_RELATIVE_COLORIMETRIC,"Relative colorimetric preserving black plane", BlackPreservingKPlaneIntents, &DefaultIntents[9] },
106     { INTENT_PRESERVE_K_PLANE_SATURATION,           "Saturation preserving black plane",            BlackPreservingKPlaneIntents, NULL } 
107 };
108
109
110 // A pointer to the begining of the list
111 static cmsIntentsList *Intents = DefaultIntents;
112
113 // Search the list for a suitable intent. Returns NULL if not found
114 static 
115 cmsIntentsList* SearchIntent(cmsUInt32Number Intent)
116 {
117     cmsIntentsList* pt;
118
119     for (pt = Intents; pt != NULL; pt = pt -> Next)
120         if (pt ->Intent == Intent) return pt;
121
122     return NULL;
123 }
124
125 // Black point compensation. Implemented as a linear scaling in XYZ. Black points 
126 // should come relative to the white point. Fills an matrix/offset element m
127 // which is organized as a 4x4 matrix.
128 static
129 void ComputeBlackPointCompensation(const cmsCIEXYZ* BlackPointIn, 
130                                    const cmsCIEXYZ* BlackPointOut,
131                                    cmsMAT3* m, cmsVEC3* off)
132
133   cmsFloat64Number ax, ay, az, bx, by, bz, tx, ty, tz;
134    
135    // Now we need to compute a matrix plus an offset m and of such of
136    // [m]*bpin + off = bpout
137    // [m]*D50  + off = D50
138    //
139    // This is a linear scaling in the form ax+b, where
140    // a = (bpout - D50) / (bpin - D50)
141    // b = - D50* (bpout - bpin) / (bpin - D50)
142
143    tx = BlackPointIn->X - cmsD50_XYZ()->X;
144    ty = BlackPointIn->Y - cmsD50_XYZ()->Y;
145    tz = BlackPointIn->Z - cmsD50_XYZ()->Z;
146
147    ax = (BlackPointOut->X - cmsD50_XYZ()->X) / tx;
148    ay = (BlackPointOut->Y - cmsD50_XYZ()->Y) / ty;
149    az = (BlackPointOut->Z - cmsD50_XYZ()->Z) / tz;
150
151    bx = - cmsD50_XYZ()-> X * (BlackPointOut->X - BlackPointIn->X) / tx;
152    by = - cmsD50_XYZ()-> Y * (BlackPointOut->Y - BlackPointIn->Y) / ty;
153    bz = - cmsD50_XYZ()-> Z * (BlackPointOut->Z - BlackPointIn->Z) / tz;
154
155    _cmsVEC3init(&m ->v[0], ax, 0,  0);
156    _cmsVEC3init(&m ->v[1], 0, ay,  0);
157    _cmsVEC3init(&m ->v[2], 0,  0,  az);
158    _cmsVEC3init(off, bx, by, bz);
159
160 }
161
162
163 // Approximate a blackbody illuminant based on CHAD information
164 static
165 cmsFloat64Number CHAD2Temp(const cmsMAT3* Chad)
166 {
167     // Convert D50 across inverse CHAD to get the absolute white point
168     cmsVEC3 d, s;
169     cmsCIEXYZ Dest;
170     cmsCIExyY DestChromaticity;
171     cmsFloat64Number TempK;
172     cmsMAT3 m1, m2;
173
174     m1 = *Chad;
175     if (!_cmsMAT3inverse(&m1, &m2)) return FALSE;
176
177     s.n[VX] = cmsD50_XYZ() -> X;
178     s.n[VY] = cmsD50_XYZ() -> Y;
179     s.n[VZ] = cmsD50_XYZ() -> Z;
180
181     _cmsMAT3eval(&d, &m2, &s);
182
183     Dest.X = d.n[VX];
184     Dest.Y = d.n[VY];
185     Dest.Z = d.n[VZ];
186
187     cmsXYZ2xyY(&DestChromaticity, &Dest);
188
189     if (!cmsTempFromWhitePoint(&TempK, &DestChromaticity))
190         return -1.0;
191
192     return TempK;
193 }
194
195 // Compute a CHAD based on a given temperature
196 static
197     void Temp2CHAD(cmsMAT3* Chad, cmsFloat64Number Temp)
198 {
199     cmsCIEXYZ White;
200     cmsCIExyY ChromaticityOfWhite;
201
202     cmsWhitePointFromTemp(&ChromaticityOfWhite, Temp);  
203     cmsxyY2XYZ(&White, &ChromaticityOfWhite);
204     _cmsAdaptationMatrix(Chad, NULL, &White, cmsD50_XYZ());
205 }
206
207 // Join scalings to obtain relative input to absolute and then to relative output.
208 // Result is stored in a 3x3 matrix
209 static
210 cmsBool  ComputeAbsoluteIntent(cmsFloat64Number AdaptationState,
211                                const cmsCIEXYZ* WhitePointIn,    
212                                const cmsMAT3* ChromaticAdaptationMatrixIn,
213                                const cmsCIEXYZ* WhitePointOut,
214                                const cmsMAT3* ChromaticAdaptationMatrixOut,
215                                cmsMAT3* m)
216 {
217     cmsMAT3 Scale, m1, m2, m3, m4;
218
219     // Adaptation state
220     if (AdaptationState == 1.0) {
221
222         // Observer is fully adapted. Keep chromatic adaptation. 
223         // That is the standard V4 behaviour
224         _cmsVEC3init(&m->v[0], WhitePointIn->X / WhitePointOut->X, 0, 0);
225         _cmsVEC3init(&m->v[1], 0, WhitePointIn->Y / WhitePointOut->Y, 0);
226         _cmsVEC3init(&m->v[2], 0, 0, WhitePointIn->Z / WhitePointOut->Z);
227
228     }
229     else  {
230
231         // Incomplete adaptation. This is an advanced feature.
232         _cmsVEC3init(&Scale.v[0], WhitePointIn->X / WhitePointOut->X, 0, 0);
233         _cmsVEC3init(&Scale.v[1], 0,  WhitePointIn->Y / WhitePointOut->Y, 0);
234         _cmsVEC3init(&Scale.v[2], 0, 0,  WhitePointIn->Z / WhitePointOut->Z);
235
236
237         if (AdaptationState == 0.0) {
238
239             m1 = *ChromaticAdaptationMatrixOut;
240             _cmsMAT3per(&m2, &m1, &Scale);
241             // m2 holds CHAD from output white to D50 times abs. col. scaling
242
243             // Observer is not adapted, undo the chromatic adaptation
244             _cmsMAT3per(m, &m3, ChromaticAdaptationMatrixOut);
245
246             m3 = *ChromaticAdaptationMatrixIn;
247             if (!_cmsMAT3inverse(&m3, &m4)) return FALSE;
248             _cmsMAT3per(m, &m2, &m4);
249
250         } else {
251
252             cmsMAT3 MixedCHAD;
253             cmsFloat64Number TempSrc, TempDest, Temp;
254
255             m1 = *ChromaticAdaptationMatrixIn;
256             if (!_cmsMAT3inverse(&m1, &m2)) return FALSE;
257             _cmsMAT3per(&m3, &m2, &Scale);
258             // m3 holds CHAD from input white to D50 times abs. col. scaling
259
260             TempSrc  = CHAD2Temp(ChromaticAdaptationMatrixIn);  
261             TempDest = CHAD2Temp(ChromaticAdaptationMatrixOut); 
262
263             if (TempSrc < 0.0 || TempDest < 0.0) return FALSE; // Something went wrong
264
265             if (_cmsMAT3isIdentity(&Scale) && fabs(TempSrc - TempDest) < 0.01) {
266
267                 _cmsMAT3identity(m);
268                 return TRUE;
269             }
270
271             Temp = (1.0 - AdaptationState) * TempDest + AdaptationState * TempSrc;
272
273             // Get a CHAD from whatever output temperature to D50. This replaces output CHAD
274             Temp2CHAD(&MixedCHAD, Temp);
275
276             _cmsMAT3per(m, &m3, &MixedCHAD);
277         }
278
279     }
280     return TRUE;
281
282 }
283
284 // Just to see if m matrix should be applied
285 static
286 cmsBool IsEmptyLayer(cmsMAT3* m, cmsVEC3* off)
287 {
288     cmsFloat64Number diff = 0;
289     cmsMAT3 Ident;
290     int i;
291     
292     if (m == NULL && off == NULL) return TRUE;  // NULL is allowed as an empty layer
293     if (m == NULL && off != NULL) return FALSE; // This is an internal error
294
295     _cmsMAT3identity(&Ident);
296     
297     for (i=0; i < 3*3; i++)
298         diff += fabs(((cmsFloat64Number*)m)[i] - ((cmsFloat64Number*)&Ident)[i]);
299
300     for (i=0; i < 3; i++)
301         diff += fabs(((cmsFloat64Number*)off)[i]);
302
303
304     return (diff < 0.002);
305 }
306
307
308 // Compute the conversion layer
309 static
310 cmsBool ComputeConversion(int i, cmsHPROFILE hProfiles[], 
311                                  cmsUInt32Number Intent, 
312                                  cmsBool BPC, 
313                                  cmsFloat64Number AdaptationState, 
314                                  cmsMAT3* m, cmsVEC3* off)
315 {
316
317     int k;
318
319     // m  and off are set to identity and this is detected latter on
320     _cmsMAT3identity(m);
321     _cmsVEC3init(off, 0, 0, 0);
322
323     // If intent is abs. colorimetric,
324     if (Intent == INTENT_ABSOLUTE_COLORIMETRIC) {
325
326         cmsCIEXYZ WhitePointIn, WhitePointOut;
327         cmsMAT3 ChromaticAdaptationMatrixIn, ChromaticAdaptationMatrixOut;  
328
329         _cmsReadMediaWhitePoint(&WhitePointIn,  hProfiles[i-1]);
330         _cmsReadCHAD(&ChromaticAdaptationMatrixIn, hProfiles[i-1]);
331
332         _cmsReadMediaWhitePoint(&WhitePointOut,  hProfiles[i]);
333         _cmsReadCHAD(&ChromaticAdaptationMatrixOut, hProfiles[i]);
334
335         if (!ComputeAbsoluteIntent(AdaptationState, 
336                                   &WhitePointIn,  &ChromaticAdaptationMatrixIn, 
337                                   &WhitePointOut, &ChromaticAdaptationMatrixOut, m)) return FALSE;
338
339     }
340     else {
341         // Rest of intents may apply BPC.
342
343         if (BPC) {
344
345             cmsCIEXYZ BlackPointIn, BlackPointOut;
346
347             cmsDetectBlackPoint(&BlackPointIn,  hProfiles[i-1], Intent, 0);
348             cmsDetectBlackPoint(&BlackPointOut, hProfiles[i], Intent, 0);
349
350             // If black points are equal, then do nothing
351             if (BlackPointIn.X != BlackPointOut.X ||
352                 BlackPointIn.Y != BlackPointOut.Y ||
353                 BlackPointIn.Z != BlackPointOut.Z) 
354                     ComputeBlackPointCompensation(&BlackPointIn, &BlackPointOut, m, off);
355         }
356     }
357
358     // Offset should be adjusted because the encoding. We encode XYZ normalized to 0..1.0,
359     // to do that, we divide by MAX_ENCODEABLE_XZY. The conversion stage goes XYZ -> XYZ so
360     // we have first to convert from encoded to XYZ and then convert back to encoded. 
361     // y = Mx + Off
362     // x = x'c
363     // y = M x'c + Off
364     // y = y'c; y' = y / c
365     // y' = (Mx'c + Off) /c = Mx' + (Off / c) 
366
367     for (k=0; k < 3; k++) {
368         off ->n[k] /= MAX_ENCODEABLE_XYZ;
369     }
370
371     return TRUE;
372 }
373
374
375 // Add a conversion stage if needed. If a matrix/offset m is given, it applies to XYZ space 
376 static
377 cmsBool AddConversion(cmsPipeline* Result, cmsColorSpaceSignature InPCS, cmsColorSpaceSignature OutPCS, cmsMAT3* m, cmsVEC3* off)
378 {
379     cmsFloat64Number* m_as_dbl = (cmsFloat64Number*) m;
380     cmsFloat64Number* off_as_dbl = (cmsFloat64Number*) off;
381
382     // Handle PCS mismatches. A specialized stage is added to the LUT in such case
383     switch (InPCS) {
384
385         case cmsSigXYZData: // Input profile operates in XYZ
386
387             switch (OutPCS) {
388
389             case cmsSigXYZData:  // XYZ -> XYZ
390                 if (!IsEmptyLayer(m, off))
391                     cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl));
392                 break;
393
394             case cmsSigLabData:  // XYZ -> Lab
395                 if (!IsEmptyLayer(m, off))
396                     cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl));
397                 cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocXYZ2Lab(Result ->ContextID));
398                 break;
399
400             default:
401                 return FALSE;   // Colorspace mismatch
402                 }
403                 break;
404
405
406         case cmsSigLabData: // Input profile operates in Lab
407
408             switch (OutPCS) {
409
410             case cmsSigXYZData:  // Lab -> XYZ
411
412                 cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocLab2XYZ(Result ->ContextID));
413                 if (!IsEmptyLayer(m, off))
414                     cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl));
415                 break;
416
417             case cmsSigLabData:  // Lab -> Lab
418
419                 if (!IsEmptyLayer(m, off)) {            
420                     cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocLab2XYZ(Result ->ContextID));        
421                     cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl));
422                     cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocXYZ2Lab(Result ->ContextID));
423                 }
424                 break;
425
426             default:
427                 return FALSE;  // Mismatch
428             }
429             break;
430
431
432             // On colorspaces other than PCS, check for same space
433         default:
434             if (InPCS != OutPCS) return FALSE;
435             break;
436     }
437
438     return TRUE;
439 }
440
441
442 // Is a given space compatible with another?
443 static
444 cmsBool ColorSpaceIsCompatible(cmsColorSpaceSignature a, cmsColorSpaceSignature b)
445 {
446     // If they are same, they are compatible.
447     if (a == b) return TRUE;
448
449     // Check for XYZ/Lab. Those spaces are interchangeable as they can be computed one from other.
450     if ((a == cmsSigXYZData) && (b == cmsSigLabData)) return TRUE;
451     if ((a == cmsSigLabData) && (b == cmsSigXYZData)) return TRUE;
452
453     return FALSE;
454 }
455
456
457 // Default handler for ICC-style intents
458 static
459 cmsPipeline* DefaultICCintents(cmsContext       ContextID, 
460                                cmsUInt32Number  nProfiles,
461                                cmsUInt32Number  TheIntents[], 
462                                cmsHPROFILE      hProfiles[], 
463                                cmsBool          BPC[],
464                                cmsFloat64Number AdaptationStates[],
465                                cmsUInt32Number  dwFlags)
466 {
467     cmsPipeline* Lut, *Result;
468     cmsHPROFILE hProfile;
469     cmsMAT3 m;
470     cmsVEC3 off;
471     cmsColorSpaceSignature ColorSpaceIn, ColorSpaceOut, CurrentColorSpace; 
472     cmsProfileClassSignature ClassSig;
473     cmsUInt32Number  i, Intent;
474
475     // For safety
476     if (nProfiles == 0) return NULL;
477
478     // Allocate an empty LUT for holding the result. 0 as channel count means 'undefined'
479     Result = cmsPipelineAlloc(ContextID, 0, 0);
480     if (Result == NULL) return NULL;
481
482     CurrentColorSpace = cmsGetColorSpace(hProfiles[0]);    
483
484     for (i=0; i < nProfiles; i++) {
485
486         cmsBool  lIsDeviceLink, lIsInput;
487
488         hProfile      = hProfiles[i];
489         ClassSig      = cmsGetDeviceClass(hProfile);
490         lIsDeviceLink = (ClassSig == cmsSigLinkClass || ClassSig == cmsSigAbstractClass );
491
492         // First profile is used as input unless devicelink or abstract
493         if ((i == 0) && !lIsDeviceLink) {
494             lIsInput = TRUE;
495         }
496         else {
497           // Else use profile in the input direction if current space is not PCS
498         lIsInput      = (CurrentColorSpace != cmsSigXYZData) &&
499                         (CurrentColorSpace != cmsSigLabData);
500         }
501
502         Intent        = TheIntents[i];
503
504         if (lIsInput || lIsDeviceLink) {
505
506             ColorSpaceIn    = cmsGetColorSpace(hProfile);
507             ColorSpaceOut   = cmsGetPCS(hProfile);
508         }
509         else {
510
511             ColorSpaceIn    = cmsGetPCS(hProfile);
512             ColorSpaceOut   = cmsGetColorSpace(hProfile);
513         }
514
515         if (!ColorSpaceIsCompatible(ColorSpaceIn, CurrentColorSpace)) {
516
517             cmsSignalError(ContextID, cmsERROR_COLORSPACE_CHECK, "ColorSpace mismatch");
518             goto Error;
519         }
520
521         // If devicelink is found, then no custom intent is allowed and we can 
522         // read the LUT to be applied. Settings don't apply here.       
523         if (lIsDeviceLink || ((ClassSig == cmsSigNamedColorClass) && (nProfiles == 1))) {
524
525             // Get the involved LUT from the profile
526             Lut = _cmsReadDevicelinkLUT(hProfile, Intent);
527             if (Lut == NULL) goto Error;
528
529             // What about abstract profiles?
530              if (ClassSig == cmsSigAbstractClass && i > 0) {
531                 if (!ComputeConversion(i, hProfiles, Intent, BPC[i], AdaptationStates[i], &m, &off)) goto Error;
532              }
533              else {
534                 _cmsMAT3identity(&m);
535                 _cmsVEC3init(&off, 0, 0, 0);
536              }
537         
538
539             if (!AddConversion(Result, CurrentColorSpace, ColorSpaceIn, &m, &off)) goto Error;
540
541         }
542         else {
543
544             if (lIsInput) {
545                 // Input direction means non-pcs connection, so proceed like devicelinks
546                 Lut = _cmsReadInputLUT(hProfile, Intent);       
547                 if (Lut == NULL) goto Error;
548             }
549             else {
550
551                 // Output direction means PCS connection. Intent may apply here
552                 Lut = _cmsReadOutputLUT(hProfile, Intent); 
553                 if (Lut == NULL) goto Error;
554
555
556                 if (!ComputeConversion(i, hProfiles, Intent, BPC[i], AdaptationStates[i], &m, &off)) goto Error;
557                 if (!AddConversion(Result, CurrentColorSpace, ColorSpaceIn, &m, &off)) goto Error;
558
559             }
560         }
561
562         // Concatenate to the output LUT
563         cmsPipelineCat(Result, Lut);
564         cmsPipelineFree(Lut);                            
565
566         // Update current space
567         CurrentColorSpace = ColorSpaceOut;              
568     }
569
570     return Result;
571
572 Error:
573
574     if (Result != NULL) cmsPipelineFree(Result);
575     return NULL;
576
577     cmsUNUSED_PARAMETER(dwFlags);
578 }
579
580
581 // Wrapper for DLL calling convention
582 cmsPipeline*  CMSEXPORT _cmsDefaultICCintents(cmsContext     ContextID, 
583                                               cmsUInt32Number nProfiles,
584                                               cmsUInt32Number TheIntents[], 
585                                               cmsHPROFILE     hProfiles[], 
586                                               cmsBool         BPC[],
587                                               cmsFloat64Number AdaptationStates[],
588                                               cmsUInt32Number dwFlags)
589 {
590     return DefaultICCintents(ContextID, nProfiles, TheIntents, hProfiles, BPC, AdaptationStates, dwFlags);
591 }
592
593 // Black preserving intents ---------------------------------------------------------------------------------------------
594
595 // Translate black-preserving intents to ICC ones
596 static
597 int TranslateNonICCIntents(int Intent)
598 {
599     switch (Intent) {
600         case INTENT_PRESERVE_K_ONLY_PERCEPTUAL:
601         case INTENT_PRESERVE_K_PLANE_PERCEPTUAL:
602             return INTENT_PERCEPTUAL; 
603
604         case INTENT_PRESERVE_K_ONLY_RELATIVE_COLORIMETRIC:
605         case INTENT_PRESERVE_K_PLANE_RELATIVE_COLORIMETRIC:
606             return INTENT_RELATIVE_COLORIMETRIC;
607
608         case INTENT_PRESERVE_K_ONLY_SATURATION:
609         case INTENT_PRESERVE_K_PLANE_SATURATION:
610             return INTENT_SATURATION;
611
612         default: return Intent;
613     }
614 }
615
616 // Sampler for Black-only preserving CMYK->CMYK transforms
617
618 typedef struct {
619     cmsPipeline*    cmyk2cmyk;      // The original transform
620     cmsToneCurve*   KTone;          // Black-to-black tone curve
621
622 } GrayOnlyParams;
623
624
625 // Preserve black only if that is the only ink used
626 static
627 int BlackPreservingGrayOnlySampler(register const cmsUInt16Number In[], register cmsUInt16Number Out[], register void* Cargo)
628 {
629     GrayOnlyParams* bp = (GrayOnlyParams*) Cargo;
630
631     // If going across black only, keep black only
632     if (In[0] == 0 && In[1] == 0 && In[2] == 0) {
633
634         // TAC does not apply because it is black ink!
635         Out[0] = Out[1] = Out[2] = 0;
636         Out[3] = cmsEvalToneCurve16(bp->KTone, In[3]);
637         return TRUE;
638     }
639
640     // Keep normal transform for other colors
641     bp ->cmyk2cmyk ->Eval16Fn(In, Out, bp ->cmyk2cmyk->Data);
642     return TRUE;
643 }
644
645 // This is the entry for black-preserving K-only intents, which are non-ICC
646 static
647 cmsPipeline*  BlackPreservingKOnlyIntents(cmsContext     ContextID, 
648                                           cmsUInt32Number nProfiles,
649                                           cmsUInt32Number TheIntents[], 
650                                           cmsHPROFILE     hProfiles[], 
651                                           cmsBool         BPC[],
652                                           cmsFloat64Number AdaptationStates[],
653                                           cmsUInt32Number dwFlags)
654 {
655     GrayOnlyParams  bp;
656     cmsPipeline*    Result;
657     cmsUInt32Number ICCIntents[256];
658     cmsStage*         CLUT;
659     cmsUInt32Number i, nGridPoints;
660
661
662     // Sanity check
663     if (nProfiles < 1 || nProfiles > 255) return NULL;
664
665     // Translate black-preserving intents to ICC ones
666     for (i=0; i < nProfiles; i++) 
667         ICCIntents[i] = TranslateNonICCIntents(TheIntents[i]); 
668
669     // Check for non-cmyk profiles
670     if (cmsGetColorSpace(hProfiles[0]) != cmsSigCmykData ||
671         cmsGetColorSpace(hProfiles[nProfiles-1]) != cmsSigCmykData) 
672            return DefaultICCintents(ContextID, nProfiles, ICCIntents, hProfiles, BPC, AdaptationStates, dwFlags);
673
674     memset(&bp, 0, sizeof(bp));
675
676     // Allocate an empty LUT for holding the result
677     Result = cmsPipelineAlloc(ContextID, 4, 4);
678     if (Result == NULL) return NULL;
679
680     // Create a LUT holding normal ICC transform
681     bp.cmyk2cmyk = DefaultICCintents(ContextID, 
682         nProfiles,
683         ICCIntents, 
684         hProfiles, 
685         BPC,
686         AdaptationStates,
687         dwFlags);
688
689     if (bp.cmyk2cmyk == NULL) goto Error;
690     
691     // Now, compute the tone curve
692     bp.KTone = _cmsBuildKToneCurve(ContextID, 
693         4096, 
694         nProfiles,
695         ICCIntents, 
696         hProfiles, 
697         BPC,
698         AdaptationStates,
699         dwFlags);
700     
701     if (bp.KTone == NULL) goto Error;
702
703     
704     // How many gridpoints are we going to use?
705     nGridPoints = _cmsReasonableGridpointsByColorspace(cmsSigCmykData, dwFlags);
706     
707     // Create the CLUT. 16 bits
708     CLUT = cmsStageAllocCLut16bit(ContextID, nGridPoints, 4, 4, NULL);
709     if (CLUT == NULL) goto Error;
710
711     // This is the one and only MPE in this LUT
712     cmsPipelineInsertStage(Result, cmsAT_BEGIN, CLUT);
713
714     // Sample it. We cannot afford pre/post linearization this time.
715     if (!cmsStageSampleCLut16bit(CLUT, BlackPreservingGrayOnlySampler, (void*) &bp, 0)) 
716         goto Error;
717     
718     // Get rid of xform and tone curve
719     cmsPipelineFree(bp.cmyk2cmyk);
720     cmsFreeToneCurve(bp.KTone);
721
722     return Result;
723
724 Error:
725
726     if (bp.cmyk2cmyk != NULL) cmsPipelineFree(bp.cmyk2cmyk);
727     if (bp.KTone != NULL)  cmsFreeToneCurve(bp.KTone);
728     if (Result != NULL) cmsPipelineFree(Result);
729     return NULL;
730
731 }
732
733 // K Plane-preserving CMYK to CMYK ------------------------------------------------------------------------------------
734
735 typedef struct {
736
737     cmsPipeline*     cmyk2cmyk;     // The original transform
738     cmsHTRANSFORM    hProofOutput;  // Output CMYK to Lab (last profile)
739     cmsHTRANSFORM    cmyk2Lab;      // The input chain
740     cmsToneCurve*    KTone;         // Black-to-black tone curve
741     cmsPipeline*     LabK2cmyk;     // The output profile
742     cmsFloat64Number MaxError;
743
744     cmsHTRANSFORM    hRoundTrip;               
745     cmsFloat64Number MaxTAC;
746     
747
748 } PreserveKPlaneParams;
749
750
751 // The CLUT will be stored at 16 bits, but calculations are performed at cmsFloat32Number precision
752 static
753 int BlackPreservingSampler(register const cmsUInt16Number In[], register cmsUInt16Number Out[], register void* Cargo)
754 {
755     int i;
756     cmsFloat32Number Inf[4], Outf[4];
757     cmsFloat32Number LabK[4];   
758     cmsFloat64Number SumCMY, SumCMYK, Error, Ratio;
759     cmsCIELab ColorimetricLab, BlackPreservingLab;
760     PreserveKPlaneParams* bp = (PreserveKPlaneParams*) Cargo;
761     
762     // Convert from 16 bits to floating point
763     for (i=0; i < 4; i++) 
764         Inf[i] = (cmsFloat32Number) (In[i] / 65535.0);
765
766     // Get the K across Tone curve
767     LabK[3] = cmsEvalToneCurveFloat(bp ->KTone, Inf[3]);
768     
769     // If going across black only, keep black only
770     if (In[0] == 0 && In[1] == 0 && In[2] == 0) {
771
772         Out[0] = Out[1] = Out[2] = 0;
773         Out[3] = _cmsQuickSaturateWord(LabK[3] * 65535.0);
774         return TRUE;
775     }
776     
777     // Try the original transform, 
778     cmsPipelineEvalFloat( Inf, Outf, bp ->cmyk2cmyk);  
779     
780     // Store a copy of the floating point result into 16-bit
781     for (i=0; i < 4; i++) 
782             Out[i] = _cmsQuickSaturateWord(Outf[i] * 65535.0);
783
784     // Maybe K is already ok (mostly on K=0)
785     if ( fabs(Outf[3] - LabK[3]) < (3.0 / 65535.0) ) {
786         return TRUE;
787     }
788     
789     // K differ, mesure and keep Lab measurement for further usage
790     // this is done in relative colorimetric intent
791     cmsDoTransform(bp->hProofOutput, Out, &ColorimetricLab, 1);
792     
793     // Is not black only and the transform doesn't keep black.
794     // Obtain the Lab of output CMYK. After that we have Lab + K
795     cmsDoTransform(bp ->cmyk2Lab, Outf, LabK, 1);
796
797     // Obtain the corresponding CMY using reverse interpolation 
798     // (K is fixed in LabK[3])
799     if (!cmsPipelineEvalReverseFloat(LabK, Outf, Outf, bp ->LabK2cmyk)) {
800
801         // Cannot find a suitable value, so use colorimetric xform
802         // which is already stored in Out[]
803         return TRUE;
804     }
805
806     // Make sure to pass thru K (which now is fixed)
807     Outf[3] = LabK[3];
808        
809     // Apply TAC if needed    
810     SumCMY   = Outf[0]  + Outf[1] + Outf[2];
811     SumCMYK  = SumCMY + Outf[3];      
812
813     if (SumCMYK > bp ->MaxTAC) {
814
815         Ratio = 1 - ((SumCMYK - bp->MaxTAC) / SumCMY);
816         if (Ratio < 0)
817             Ratio = 0;
818     }
819     else
820        Ratio = 1.0;
821
822     Out[0] = _cmsQuickSaturateWord(Outf[0] * Ratio * 65535.0);     // C
823     Out[1] = _cmsQuickSaturateWord(Outf[1] * Ratio * 65535.0);     // M
824     Out[2] = _cmsQuickSaturateWord(Outf[2] * Ratio * 65535.0);     // Y
825     Out[3] = _cmsQuickSaturateWord(Outf[3] * 65535.0);
826
827     // Estimate the error (this goes 16 bits to Lab DBL)
828     cmsDoTransform(bp->hProofOutput, Out, &BlackPreservingLab, 1);  
829     Error = cmsDeltaE(&ColorimetricLab, &BlackPreservingLab);                   
830     if (Error > bp -> MaxError) 
831         bp->MaxError = Error;
832
833     return TRUE;
834 }
835
836 // This is the entry for black-plane preserving, which are non-ICC
837 static
838 cmsPipeline* BlackPreservingKPlaneIntents(cmsContext     ContextID, 
839                                           cmsUInt32Number nProfiles,
840                                           cmsUInt32Number TheIntents[], 
841                                           cmsHPROFILE     hProfiles[], 
842                                           cmsBool         BPC[],
843                                           cmsFloat64Number AdaptationStates[],
844                                           cmsUInt32Number dwFlags)
845 {
846     PreserveKPlaneParams bp;
847     cmsPipeline*    Result = NULL;
848     cmsUInt32Number ICCIntents[256];
849     cmsStage*         CLUT;
850     cmsUInt32Number i, nGridPoints;    
851     cmsHPROFILE hLab;
852
853     // Sanity check
854     if (nProfiles < 1 || nProfiles > 255) return NULL;
855
856     // Translate black-preserving intents to ICC ones
857     for (i=0; i < nProfiles; i++) 
858         ICCIntents[i] = TranslateNonICCIntents(TheIntents[i]); 
859
860     // Check for non-cmyk profiles
861     if (cmsGetColorSpace(hProfiles[0]) != cmsSigCmykData ||
862         cmsGetColorSpace(hProfiles[nProfiles-1]) != cmsSigCmykData ||
863         cmsGetDeviceClass(hProfiles[nProfiles-1]) != cmsSigOutputClass) 
864            return  DefaultICCintents(ContextID, nProfiles, ICCIntents, hProfiles, BPC, AdaptationStates, dwFlags);
865
866     // Allocate an empty LUT for holding the result
867     Result = cmsPipelineAlloc(ContextID, 4, 4);
868     if (Result == NULL) return NULL;
869
870    
871     memset(&bp, 0, sizeof(bp));
872
873     // We need the input LUT of the last profile, assuming this one is responsible of
874     // black generation. This LUT will be seached in inverse order.
875     bp.LabK2cmyk = _cmsReadInputLUT(hProfiles[nProfiles-1], INTENT_RELATIVE_COLORIMETRIC);
876     if (bp.LabK2cmyk == NULL) goto Cleanup;
877
878     // Get total area coverage (in 0..1 domain)
879     bp.MaxTAC = cmsDetectTAC(hProfiles[nProfiles-1]) / 100.0;
880     if (bp.MaxTAC <= 0) goto Cleanup;
881
882
883     // Create a LUT holding normal ICC transform
884     bp.cmyk2cmyk = DefaultICCintents(ContextID,
885                                          nProfiles,
886                                          ICCIntents, 
887                                          hProfiles, 
888                                          BPC,
889                                          AdaptationStates,
890                                          dwFlags);
891     if (bp.cmyk2cmyk == NULL) goto Cleanup;
892
893     // Now the tone curve
894     bp.KTone = _cmsBuildKToneCurve(ContextID, 4096, nProfiles,
895                                    ICCIntents, 
896                                    hProfiles, 
897                                    BPC, 
898                                    AdaptationStates,
899                                    dwFlags);
900     if (bp.KTone == NULL) goto Cleanup;
901
902     // To measure the output, Last profile to Lab
903     hLab = cmsCreateLab4ProfileTHR(ContextID, NULL);
904     bp.hProofOutput = cmsCreateTransformTHR(ContextID, hProfiles[nProfiles-1], 
905                                          CHANNELS_SH(4)|BYTES_SH(2), hLab, TYPE_Lab_DBL, 
906                                          INTENT_RELATIVE_COLORIMETRIC, 
907                                          cmsFLAGS_NOCACHE|cmsFLAGS_NOOPTIMIZE);
908     if ( bp.hProofOutput == NULL) goto Cleanup;
909
910     // Same as anterior, but lab in the 0..1 range
911     bp.cmyk2Lab = cmsCreateTransformTHR(ContextID, hProfiles[nProfiles-1], 
912                                          FLOAT_SH(1)|CHANNELS_SH(4)|BYTES_SH(4), hLab, 
913                                          FLOAT_SH(1)|CHANNELS_SH(3)|BYTES_SH(4), 
914                                          INTENT_RELATIVE_COLORIMETRIC, 
915                                          cmsFLAGS_NOCACHE|cmsFLAGS_NOOPTIMIZE);
916     if (bp.cmyk2Lab == NULL) goto Cleanup;
917     cmsCloseProfile(hLab);
918
919     // Error estimation (for debug only)
920     bp.MaxError = 0;
921
922     // How many gridpoints are we going to use?
923     nGridPoints = _cmsReasonableGridpointsByColorspace(cmsSigCmykData, dwFlags);
924
925     
926     CLUT = cmsStageAllocCLut16bit(ContextID, nGridPoints, 4, 4, NULL);
927     if (CLUT == NULL) goto Cleanup;
928
929     cmsPipelineInsertStage(Result, cmsAT_BEGIN, CLUT);
930
931     cmsStageSampleCLut16bit(CLUT, BlackPreservingSampler, (void*) &bp, 0);
932
933 Cleanup:
934
935     if (bp.cmyk2cmyk) cmsPipelineFree(bp.cmyk2cmyk);
936     if (bp.cmyk2Lab) cmsDeleteTransform(bp.cmyk2Lab);       
937     if (bp.hProofOutput) cmsDeleteTransform(bp.hProofOutput);
938     
939     if (bp.KTone) cmsFreeToneCurve(bp.KTone);   
940     if (bp.LabK2cmyk) cmsPipelineFree(bp.LabK2cmyk);
941
942     return Result;
943 }
944
945 // Link routines ------------------------------------------------------------------------------------------------------
946
947 // Chain several profiles into a single LUT. It just checks the parameters and then calls the handler
948 // for the first intent in chain. The handler may be user-defined. Is up to the handler to deal with the 
949 // rest of intents in chain. A maximum of 255 profiles at time are supported, which is pretty reasonable.
950 cmsPipeline* _cmsLinkProfiles(cmsContext     ContextID, 
951                               cmsUInt32Number nProfiles,
952                               cmsUInt32Number TheIntents[], 
953                               cmsHPROFILE     hProfiles[], 
954                               cmsBool         BPC[],
955                               cmsFloat64Number AdaptationStates[],
956                               cmsUInt32Number dwFlags)
957 {
958     cmsUInt32Number i;
959     cmsIntentsList* Intent;
960
961     // Make sure a reasonable number of profiles is provided
962     if (nProfiles <= 0 || nProfiles > 255) {
963          cmsSignalError(ContextID, cmsERROR_RANGE, "Couldn't link '%d' profiles", nProfiles);
964         return NULL;
965     }
966
967     for (i=0; i < nProfiles; i++) {
968
969         // Check if black point is really needed or allowed. Note that 
970         // following Adobe's document:
971         // BPC does not apply to devicelink profiles, nor to abs colorimetric, 
972         // and applies always on V4 perceptual and saturation.
973
974         if (TheIntents[i] == INTENT_ABSOLUTE_COLORIMETRIC)
975             BPC[i] = FALSE;
976
977         if (TheIntents[i] == INTENT_PERCEPTUAL || TheIntents[i] == INTENT_SATURATION) {
978
979             // Force BPC for V4 profiles in perceptual and saturation
980             if (cmsGetProfileVersion(hProfiles[i]) >= 4.0)
981                 BPC[i] = TRUE;
982         }      
983     }
984
985     // Search for a handler. The first intent in the chain defines the handler. That would
986     // prevent using multiple custom intents in a multiintent chain, but the behaviour of
987     // this case would present some issues if the custom intent tries to do things like
988     // preserve primaries. This solution is not perfect, but works well on most cases.
989
990     Intent = SearchIntent(TheIntents[0]);
991     if (Intent == NULL) {
992         cmsSignalError(ContextID, cmsERROR_UNKNOWN_EXTENSION, "Unsupported intent '%d'", TheIntents[0]);
993         return NULL;
994     }
995
996     // Call the handler
997     return Intent ->Link(ContextID, nProfiles, TheIntents, hProfiles, BPC, AdaptationStates, dwFlags);
998 }
999
1000 // -------------------------------------------------------------------------------------------------
1001
1002 // Get information about available intents. nMax is the maximum space for the supplied "Codes" 
1003 // and "Descriptions" the function returns the total number of intents, which may be greater 
1004 // than nMax, although the matrices are not populated beyond this level.
1005 cmsUInt32Number CMSEXPORT cmsGetSupportedIntents(cmsUInt32Number nMax, cmsUInt32Number* Codes, char** Descriptions)
1006 {
1007     cmsIntentsList* pt;
1008     cmsUInt32Number nIntents;
1009
1010     for (nIntents=0, pt = Intents; pt != NULL; pt = pt -> Next)
1011     {
1012         if (nIntents < nMax) {
1013             if (Codes != NULL) 
1014                 Codes[nIntents] = pt ->Intent;
1015
1016             if (Descriptions != NULL) 
1017                 Descriptions[nIntents] = pt ->Description;
1018         }
1019
1020         nIntents++;
1021     }
1022
1023     return nIntents;
1024 }
1025
1026 // The plug-in registration. User can add new intents or override default routines
1027 cmsBool  _cmsRegisterRenderingIntentPlugin(cmsPluginBase* Data)
1028 {
1029     cmsPluginRenderingIntent* Plugin = (cmsPluginRenderingIntent*) Data;
1030     cmsIntentsList* fl;
1031
1032     // Do we have to reset the intents?
1033     if (Data == NULL) {
1034     
1035        Intents = DefaultIntents;
1036        return TRUE;
1037     }
1038
1039     fl = SearchIntent(Plugin ->Intent);
1040
1041     if (fl == NULL) {
1042         fl = (cmsIntentsList*) _cmsPluginMalloc(sizeof(cmsIntentsList));
1043         if (fl == NULL) return FALSE;
1044     }
1045
1046     fl ->Intent  = Plugin ->Intent;
1047     strncpy(fl ->Description, Plugin ->Description, 255);
1048     fl ->Description[255] = 0;
1049
1050     fl ->Link    = Plugin ->Link;
1051
1052     fl ->Next = Intents;
1053     Intents = fl;
1054
1055     return TRUE;
1056 }
1057