comparison mupdf-source/thirdparty/lcms2/src/cmscnvrt.c @ 2:b50eed0cc0ef upstream

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