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