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