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