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