Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/mobile
Path: blob/master/src/java.desktop/share/native/liblcms/cmscnvrt.c
41152 views
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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.
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.
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
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
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
1245