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