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