1 //---------------------------------------------------------------------------------
3 // Little Color Management System
4 // Copyright (c) 1998-2012 Marti Maria Saguer
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:
13 // The above copyright notice and this permission notice shall be included in
14 // all copies or substantial portions of the Software.
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.
24 //---------------------------------------------------------------------------------
27 #include "lcms2_internal.h"
30 // Auxiliar: append a Lab identity after the given sequence of profiles
31 // and return the transform. Lab profile is closed, rest of profiles are kept open.
32 cmsHTRANSFORM
_cmsChain2Lab(cmsContext ContextID
,
33 cmsUInt32Number nProfiles
,
34 cmsUInt32Number InputFormat
,
35 cmsUInt32Number OutputFormat
,
36 const cmsUInt32Number Intents
[],
37 const cmsHPROFILE hProfiles
[],
39 const cmsFloat64Number AdaptationStates
[],
40 cmsUInt32Number dwFlags
)
44 cmsHPROFILE ProfileList
[256];
46 cmsFloat64Number AdaptationList
[256];
47 cmsUInt32Number IntentList
[256];
50 // This is a rather big number and there is no need of dynamic memory
51 // since we are adding a profile, 254 + 1 = 255 and this is the limit
52 if (nProfiles
> 254) return NULL
;
55 hLab
= cmsCreateLab4ProfileTHR(ContextID
, NULL
);
56 if (hLab
== NULL
) return NULL
;
58 // Create a copy of parameters
59 for (i
=0; i
< nProfiles
; i
++) {
61 ProfileList
[i
] = hProfiles
[i
];
63 AdaptationList
[i
] = AdaptationStates
[i
];
64 IntentList
[i
] = Intents
[i
];
67 // Place Lab identity at chain's end.
68 ProfileList
[nProfiles
] = hLab
;
69 BPCList
[nProfiles
] = 0;
70 AdaptationList
[nProfiles
] = 1.0;
71 IntentList
[nProfiles
] = INTENT_RELATIVE_COLORIMETRIC
;
73 // Create the transform
74 xform
= cmsCreateExtendedTransform(ContextID
, nProfiles
+ 1, ProfileList
,
83 cmsCloseProfile(hLab
);
89 // Compute K -> L* relationship. Flags may include black point compensation. In this case,
90 // the relationship is assumed from the profile with BPC to a black point zero.
92 cmsToneCurve
* ComputeKToLstar(cmsContext ContextID
,
93 cmsUInt32Number nPoints
,
94 cmsUInt32Number nProfiles
,
95 const cmsUInt32Number Intents
[],
96 const cmsHPROFILE hProfiles
[],
98 const cmsFloat64Number AdaptationStates
[],
99 cmsUInt32Number dwFlags
)
101 cmsToneCurve
* out
= NULL
;
105 cmsFloat32Number cmyk
[4];
106 cmsFloat32Number
* SampledPoints
;
108 xform
= _cmsChain2Lab(ContextID
, nProfiles
, TYPE_CMYK_FLT
, TYPE_Lab_DBL
, Intents
, hProfiles
, BPC
, AdaptationStates
, dwFlags
);
109 if (xform
== NULL
) return NULL
;
111 SampledPoints
= (cmsFloat32Number
*) _cmsCalloc(ContextID
, nPoints
, sizeof(cmsFloat32Number
));
112 if (SampledPoints
== NULL
) goto Error
;
114 for (i
=0; i
< nPoints
; i
++) {
119 cmyk
[3] = (cmsFloat32Number
) ((i
* 100.0) / (nPoints
-1));
121 cmsDoTransform(xform
, cmyk
, &Lab
, 1);
122 SampledPoints
[i
]= (cmsFloat32Number
) (1.0 - Lab
.L
/ 100.0); // Negate K for easier operation
125 out
= cmsBuildTabulatedToneCurveFloat(ContextID
, nPoints
, SampledPoints
);
129 cmsDeleteTransform(xform
);
130 if (SampledPoints
) _cmsFree(ContextID
, SampledPoints
);
136 // Compute Black tone curve on a CMYK -> CMYK transform. This is done by
137 // using the proof direction on both profiles to find K->L* relationship
138 // then joining both curves. dwFlags may include black point compensation.
139 cmsToneCurve
* _cmsBuildKToneCurve(cmsContext ContextID
,
140 cmsUInt32Number nPoints
,
141 cmsUInt32Number nProfiles
,
142 const cmsUInt32Number Intents
[],
143 const cmsHPROFILE hProfiles
[],
145 const cmsFloat64Number AdaptationStates
[],
146 cmsUInt32Number dwFlags
)
148 cmsToneCurve
*in
, *out
, *KTone
;
150 // Make sure CMYK -> CMYK
151 if (cmsGetColorSpace(hProfiles
[0]) != cmsSigCmykData
||
152 cmsGetColorSpace(hProfiles
[nProfiles
-1])!= cmsSigCmykData
) return NULL
;
155 // Make sure last is an output profile
156 if (cmsGetDeviceClass(hProfiles
[nProfiles
- 1]) != cmsSigOutputClass
) return NULL
;
158 // Create individual curves. BPC works also as each K to L* is
159 // computed as a BPC to zero black point in case of L*
160 in
= ComputeKToLstar(ContextID
, nPoints
, nProfiles
- 1, Intents
, hProfiles
, BPC
, AdaptationStates
, dwFlags
);
161 if (in
== NULL
) return NULL
;
163 out
= ComputeKToLstar(ContextID
, nPoints
, 1,
164 Intents
+ (nProfiles
- 1),
165 hProfiles
+ (nProfiles
- 1),
166 BPC
+ (nProfiles
- 1),
167 AdaptationStates
+ (nProfiles
- 1),
170 cmsFreeToneCurve(in
);
174 // Build the relationship. This effectively limits the maximum accuracy to 16 bits, but
175 // since this is used on black-preserving LUTs, we are not loosing accuracy in any case
176 KTone
= cmsJoinToneCurve(ContextID
, in
, out
, nPoints
);
178 // Get rid of components
179 cmsFreeToneCurve(in
); cmsFreeToneCurve(out
);
181 // Something went wrong...
182 if (KTone
== NULL
) return NULL
;
184 // Make sure it is monotonic
185 if (!cmsIsToneCurveMonotonic(KTone
)) {
186 cmsFreeToneCurve(KTone
);
194 // Gamut LUT Creation -----------------------------------------------------------------------------------------
196 // Used by gamut & softproofing
200 cmsHTRANSFORM hInput
; // From whatever input color space. 16 bits to DBL
201 cmsHTRANSFORM hForward
, hReverse
; // Transforms going from Lab to colorant and back
202 cmsFloat64Number Thereshold
; // The thereshold after which is considered out of gamut
206 // This sampler does compute gamut boundaries by comparing original
207 // values with a transform going back and forth. Values above ERR_THERESHOLD
208 // of maximum are considered out of gamut.
210 #define ERR_THERESHOLD 5
214 int GamutSampler(register const cmsUInt16Number In
[], register cmsUInt16Number Out
[], register void* Cargo
)
216 GAMUTCHAIN
* t
= (GAMUTCHAIN
* ) Cargo
;
217 cmsCIELab LabIn1
, LabOut1
;
218 cmsCIELab LabIn2
, LabOut2
;
219 cmsUInt16Number Proof
[cmsMAXCHANNELS
], Proof2
[cmsMAXCHANNELS
];
220 cmsFloat64Number dE1
, dE2
, ErrorRatio
;
222 // Assume in-gamut by default.
225 // Convert input to Lab
226 cmsDoTransform(t
-> hInput
, In
, &LabIn1
, 1);
228 // converts from PCS to colorant. This always
229 // does return in-gamut values,
230 cmsDoTransform(t
-> hForward
, &LabIn1
, Proof
, 1);
232 // Now, do the inverse, from colorant to PCS.
233 cmsDoTransform(t
-> hReverse
, Proof
, &LabOut1
, 1);
235 memmove(&LabIn2
, &LabOut1
, sizeof(cmsCIELab
));
237 // Try again, but this time taking Check as input
238 cmsDoTransform(t
-> hForward
, &LabOut1
, Proof2
, 1);
239 cmsDoTransform(t
-> hReverse
, Proof2
, &LabOut2
, 1);
241 // Take difference of direct value
242 dE1
= cmsDeltaE(&LabIn1
, &LabOut1
);
244 // Take difference of converted value
245 dE2
= cmsDeltaE(&LabIn2
, &LabOut2
);
248 // if dE1 is small and dE2 is small, value is likely to be in gamut
249 if (dE1
< t
->Thereshold
&& dE2
< t
->Thereshold
)
253 // if dE1 is small and dE2 is big, undefined. Assume in gamut
254 if (dE1
< t
->Thereshold
&& dE2
> t
->Thereshold
)
257 // dE1 is big and dE2 is small, clearly out of gamut
258 if (dE1
> t
->Thereshold
&& dE2
< t
->Thereshold
)
259 Out
[0] = (cmsUInt16Number
) _cmsQuickFloor((dE1
- t
->Thereshold
) + .5);
262 // dE1 is big and dE2 is also big, could be due to perceptual mapping
263 // so take error ratio
267 ErrorRatio
= dE1
/ dE2
;
269 if (ErrorRatio
> t
->Thereshold
)
270 Out
[0] = (cmsUInt16Number
) _cmsQuickFloor((ErrorRatio
- t
->Thereshold
) + .5);
280 // Does compute a gamut LUT going back and forth across pcs -> relativ. colorimetric intent -> pcs
281 // the dE obtained is then annotated on the LUT. Values truely out of gamut are clipped to dE = 0xFFFE
282 // and values changed are supposed to be handled by any gamut remapping, so, are out of gamut as well.
284 // **WARNING: This algorithm does assume that gamut remapping algorithms does NOT move in-gamut colors,
285 // of course, many perceptual and saturation intents does not work in such way, but relativ. ones should.
287 cmsPipeline
* _cmsCreateGamutCheckPipeline(cmsContext ContextID
,
288 cmsHPROFILE hProfiles
[],
290 cmsUInt32Number Intents
[],
291 cmsFloat64Number AdaptationStates
[],
292 cmsUInt32Number nGamutPCSposition
,
298 cmsUInt32Number dwFormat
;
300 int nChannels
, nGridpoints
;
301 cmsColorSpaceSignature ColorSpace
;
303 cmsHPROFILE ProfileList
[256];
304 cmsBool BPCList
[256];
305 cmsFloat64Number AdaptationList
[256];
306 cmsUInt32Number IntentList
[256];
308 memset(&Chain
, 0, sizeof(GAMUTCHAIN
));
311 if (nGamutPCSposition
<= 0 || nGamutPCSposition
> 255) {
312 cmsSignalError(ContextID
, cmsERROR_RANGE
, "Wrong position of PCS. 1..255 expected, %d found.", nGamutPCSposition
);
316 hLab
= cmsCreateLab4ProfileTHR(ContextID
, NULL
);
317 if (hLab
== NULL
) return NULL
;
320 // The figure of merit. On matrix-shaper profiles, should be almost zero as
321 // the conversion is pretty exact. On LUT based profiles, different resolutions
322 // of input and output CLUT may result in differences.
324 if (cmsIsMatrixShaper(hGamut
)) {
326 Chain
.Thereshold
= 1.0;
329 Chain
.Thereshold
= ERR_THERESHOLD
;
333 // Create a copy of parameters
334 for (i
=0; i
< nGamutPCSposition
; i
++) {
335 ProfileList
[i
] = hProfiles
[i
];
337 AdaptationList
[i
] = AdaptationStates
[i
];
338 IntentList
[i
] = Intents
[i
];
342 ProfileList
[nGamutPCSposition
] = hLab
;
343 BPCList
[nGamutPCSposition
] = 0;
344 AdaptationList
[nGamutPCSposition
] = 1.0;
345 IntentList
[nGamutPCSposition
] = INTENT_RELATIVE_COLORIMETRIC
;
348 ColorSpace
= cmsGetColorSpace(hGamut
);
350 nChannels
= cmsChannelsOf(ColorSpace
);
351 nGridpoints
= _cmsReasonableGridpointsByColorspace(ColorSpace
, cmsFLAGS_HIGHRESPRECALC
);
352 dwFormat
= (CHANNELS_SH(nChannels
)|BYTES_SH(2));
354 // 16 bits to Lab double
355 Chain
.hInput
= cmsCreateExtendedTransform(ContextID
,
356 nGamutPCSposition
+ 1,
362 dwFormat
, TYPE_Lab_DBL
,
366 // Does create the forward step. Lab double to device
367 dwFormat
= (CHANNELS_SH(nChannels
)|BYTES_SH(2));
368 Chain
.hForward
= cmsCreateTransformTHR(ContextID
,
371 INTENT_RELATIVE_COLORIMETRIC
,
374 // Does create the backwards step
375 Chain
.hReverse
= cmsCreateTransformTHR(ContextID
, hGamut
, dwFormat
,
377 INTENT_RELATIVE_COLORIMETRIC
,
382 if (Chain
.hInput
&& Chain
.hForward
&& Chain
.hReverse
) {
384 // Go on, try to compute gamut LUT from PCS. This consist on a single channel containing
385 // dE when doing a transform back and forth on the colorimetric intent.
387 Gamut
= cmsPipelineAlloc(ContextID
, 3, 1);
390 CLUT
= cmsStageAllocCLut16bit(ContextID
, nGridpoints
, nChannels
, 1, NULL
);
391 if (!cmsPipelineInsertStage(Gamut
, cmsAT_BEGIN
, CLUT
)) {
392 cmsPipelineFree(Gamut
);
396 cmsStageSampleCLut16bit(CLUT
, GamutSampler
, (void*) &Chain
, 0);
401 Gamut
= NULL
; // Didn't work...
403 // Free all needed stuff.
404 if (Chain
.hInput
) cmsDeleteTransform(Chain
.hInput
);
405 if (Chain
.hForward
) cmsDeleteTransform(Chain
.hForward
);
406 if (Chain
.hReverse
) cmsDeleteTransform(Chain
.hReverse
);
407 if (hLab
) cmsCloseProfile(hLab
);
409 // And return computed hull
413 // Total Area Coverage estimation ----------------------------------------------------------------
416 cmsUInt32Number nOutputChans
;
417 cmsHTRANSFORM hRoundTrip
;
418 cmsFloat32Number MaxTAC
;
419 cmsFloat32Number MaxInput
[cmsMAXCHANNELS
];
424 // This callback just accounts the maximum ink dropped in the given node. It does not populate any
425 // memory, as the destination table is NULL. Its only purpose it to know the global maximum.
427 int EstimateTAC(register const cmsUInt16Number In
[], register cmsUInt16Number Out
[], register void * Cargo
)
429 cmsTACestimator
* bp
= (cmsTACestimator
*) Cargo
;
430 cmsFloat32Number RoundTrip
[cmsMAXCHANNELS
];
432 cmsFloat32Number Sum
;
435 // Evaluate the xform
436 cmsDoTransform(bp
->hRoundTrip
, In
, RoundTrip
, 1);
438 // All all amounts of ink
439 for (Sum
=0, i
=0; i
< bp
->nOutputChans
; i
++)
442 // If above maximum, keep track of input values
443 if (Sum
> bp
->MaxTAC
) {
447 for (i
=0; i
< bp
->nOutputChans
; i
++) {
448 bp
->MaxInput
[i
] = In
[i
];
454 cmsUNUSED_PARAMETER(Out
);
458 // Detect Total area coverage of the profile
459 cmsFloat64Number CMSEXPORT
cmsDetectTAC(cmsHPROFILE hProfile
)
462 cmsUInt32Number dwFormatter
;
463 cmsUInt32Number GridPoints
[MAX_INPUT_DIMENSIONS
];
465 cmsContext ContextID
= cmsGetProfileContextID(hProfile
);
467 // TAC only works on output profiles
468 if (cmsGetDeviceClass(hProfile
) != cmsSigOutputClass
) {
472 // Create a fake formatter for result
473 dwFormatter
= cmsFormatterForColorspaceOfProfile(hProfile
, 4, TRUE
);
475 bp
.nOutputChans
= T_CHANNELS(dwFormatter
);
476 bp
.MaxTAC
= 0; // Initial TAC is 0
479 if (bp
.nOutputChans
>= cmsMAXCHANNELS
) return 0;
481 hLab
= cmsCreateLab4ProfileTHR(ContextID
, NULL
);
482 if (hLab
== NULL
) return 0;
483 // Setup a roundtrip on perceptual intent in output profile for TAC estimation
484 bp
.hRoundTrip
= cmsCreateTransformTHR(ContextID
, hLab
, TYPE_Lab_16
,
485 hProfile
, dwFormatter
, INTENT_PERCEPTUAL
, cmsFLAGS_NOOPTIMIZE
|cmsFLAGS_NOCACHE
);
487 cmsCloseProfile(hLab
);
488 if (bp
.hRoundTrip
== NULL
) return 0;
490 // For L* we only need black and white. For C* we need many points
496 if (!cmsSliceSpace16(3, GridPoints
, EstimateTAC
, &bp
)) {
500 cmsDeleteTransform(bp
.hRoundTrip
);
507 // Carefully, clamp on CIELab space.
509 cmsBool CMSEXPORT
cmsDesaturateLab(cmsCIELab
* Lab
,
510 double amax
, double amin
,
511 double bmax
, double bmin
)
514 // Whole Luma surface to zero
518 Lab
-> L
= Lab
->a
= Lab
-> b
= 0.0;
522 // Clamp white, DISCARD HIGHLIGHTS. This is done
523 // in such way because icc spec doesn't allow the
524 // use of L>100 as a highlight means.
529 // Check out gamut prism, on a, b faces
531 if (Lab
-> a
< amin
|| Lab
->a
> amax
||
532 Lab
-> b
< bmin
|| Lab
->b
> bmax
) {
537 // Falls outside a, b limits. Transports to LCh space,
538 // and then do the clipping
541 if (Lab
-> a
== 0.0) { // Is hue exactly 90?
543 // atan will not work, so clamp here
544 Lab
-> b
= Lab
->b
< 0 ? bmin
: bmax
;
548 cmsLab2LCh(&LCh
, Lab
);
550 slope
= Lab
-> b
/ Lab
-> a
;
555 if ((h
>= 0. && h
< 45.) ||
556 (h
>= 315 && h
<= 360.)) {
560 Lab
-> b
= amax
* slope
;
563 if (h
>= 45. && h
< 135.)
567 Lab
-> a
= bmax
/ slope
;
570 if (h
>= 135. && h
< 225.) {
573 Lab
-> b
= amin
* slope
;
577 if (h
>= 225. && h
< 315.) {
580 Lab
-> a
= bmin
/ slope
;
583 cmsSignalError(0, cmsERROR_RANGE
, "Invalid angle");