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