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