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