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 // Link several profiles to obtain a single LUT modelling the whole color transform. Intents, Black point
31 // compensation and Adaptation parameters may vary across profiles. BPC and Adaptation refers to the PCS
32 // after the profile. I.e, BPC[0] refers to connexion between profile(0) and profile(1)
33 cmsPipeline
* _cmsLinkProfiles(cmsContext ContextID
,
34 cmsUInt32Number nProfiles
,
35 cmsUInt32Number Intents
[],
36 cmsHPROFILE hProfiles
[],
38 cmsFloat64Number AdaptationStates
[],
39 cmsUInt32Number dwFlags
);
41 //---------------------------------------------------------------------------------
43 // This is the default routine for ICC-style intents. A user may decide to override it by using a plugin.
44 // Supported intents are perceptual, relative colorimetric, saturation and ICC-absolute colorimetric
46 cmsPipeline
* DefaultICCintents(cmsContext ContextID
,
47 cmsUInt32Number nProfiles
,
48 cmsUInt32Number Intents
[],
49 cmsHPROFILE hProfiles
[],
51 cmsFloat64Number AdaptationStates
[],
52 cmsUInt32Number dwFlags
);
54 //---------------------------------------------------------------------------------
56 // This is the entry for black-preserving K-only intents, which are non-ICC. Last profile have to be a output profile
57 // to do the trick (no devicelinks allowed at that position)
59 cmsPipeline
* BlackPreservingKOnlyIntents(cmsContext ContextID
,
60 cmsUInt32Number nProfiles
,
61 cmsUInt32Number Intents
[],
62 cmsHPROFILE hProfiles
[],
64 cmsFloat64Number AdaptationStates
[],
65 cmsUInt32Number dwFlags
);
67 //---------------------------------------------------------------------------------
69 // This is the entry for black-plane preserving, which are non-ICC. Again, Last profile have to be a output profile
70 // to do the trick (no devicelinks allowed at that position)
72 cmsPipeline
* BlackPreservingKPlaneIntents(cmsContext ContextID
,
73 cmsUInt32Number nProfiles
,
74 cmsUInt32Number Intents
[],
75 cmsHPROFILE hProfiles
[],
77 cmsFloat64Number AdaptationStates
[],
78 cmsUInt32Number dwFlags
);
80 //---------------------------------------------------------------------------------
83 // This is a structure holding implementations for all supported intents.
84 typedef struct _cms_intents_list
{
86 cmsUInt32Number Intent
;
87 char Description
[256];
89 struct _cms_intents_list
* Next
;
95 static cmsIntentsList DefaultIntents
[] = {
97 { INTENT_PERCEPTUAL
, "Perceptual", DefaultICCintents
, &DefaultIntents
[1] },
98 { INTENT_RELATIVE_COLORIMETRIC
, "Relative colorimetric", DefaultICCintents
, &DefaultIntents
[2] },
99 { INTENT_SATURATION
, "Saturation", DefaultICCintents
, &DefaultIntents
[3] },
100 { INTENT_ABSOLUTE_COLORIMETRIC
, "Absolute colorimetric", DefaultICCintents
, &DefaultIntents
[4] },
101 { INTENT_PRESERVE_K_ONLY_PERCEPTUAL
, "Perceptual preserving black ink", BlackPreservingKOnlyIntents
, &DefaultIntents
[5] },
102 { INTENT_PRESERVE_K_ONLY_RELATIVE_COLORIMETRIC
, "Relative colorimetric preserving black ink", BlackPreservingKOnlyIntents
, &DefaultIntents
[6] },
103 { INTENT_PRESERVE_K_ONLY_SATURATION
, "Saturation preserving black ink", BlackPreservingKOnlyIntents
, &DefaultIntents
[7] },
104 { INTENT_PRESERVE_K_PLANE_PERCEPTUAL
, "Perceptual preserving black plane", BlackPreservingKPlaneIntents
, &DefaultIntents
[8] },
105 { INTENT_PRESERVE_K_PLANE_RELATIVE_COLORIMETRIC
,"Relative colorimetric preserving black plane", BlackPreservingKPlaneIntents
, &DefaultIntents
[9] },
106 { INTENT_PRESERVE_K_PLANE_SATURATION
, "Saturation preserving black plane", BlackPreservingKPlaneIntents
, NULL
}
110 // A pointer to the begining of the list
111 static cmsIntentsList
*Intents
= DefaultIntents
;
113 // Search the list for a suitable intent. Returns NULL if not found
115 cmsIntentsList
* SearchIntent(cmsUInt32Number Intent
)
119 for (pt
= Intents
; pt
!= NULL
; pt
= pt
-> Next
)
120 if (pt
->Intent
== Intent
) return pt
;
125 // Black point compensation. Implemented as a linear scaling in XYZ. Black points
126 // should come relative to the white point. Fills an matrix/offset element m
127 // which is organized as a 4x4 matrix.
129 void ComputeBlackPointCompensation(const cmsCIEXYZ
* BlackPointIn
,
130 const cmsCIEXYZ
* BlackPointOut
,
131 cmsMAT3
* m
, cmsVEC3
* off
)
133 cmsFloat64Number ax
, ay
, az
, bx
, by
, bz
, tx
, ty
, tz
;
135 // Now we need to compute a matrix plus an offset m and of such of
136 // [m]*bpin + off = bpout
137 // [m]*D50 + off = D50
139 // This is a linear scaling in the form ax+b, where
140 // a = (bpout - D50) / (bpin - D50)
141 // b = - D50* (bpout - bpin) / (bpin - D50)
143 tx
= BlackPointIn
->X
- cmsD50_XYZ()->X
;
144 ty
= BlackPointIn
->Y
- cmsD50_XYZ()->Y
;
145 tz
= BlackPointIn
->Z
- cmsD50_XYZ()->Z
;
147 ax
= (BlackPointOut
->X
- cmsD50_XYZ()->X
) / tx
;
148 ay
= (BlackPointOut
->Y
- cmsD50_XYZ()->Y
) / ty
;
149 az
= (BlackPointOut
->Z
- cmsD50_XYZ()->Z
) / tz
;
151 bx
= - cmsD50_XYZ()-> X
* (BlackPointOut
->X
- BlackPointIn
->X
) / tx
;
152 by
= - cmsD50_XYZ()-> Y
* (BlackPointOut
->Y
- BlackPointIn
->Y
) / ty
;
153 bz
= - cmsD50_XYZ()-> Z
* (BlackPointOut
->Z
- BlackPointIn
->Z
) / tz
;
155 _cmsVEC3init(&m
->v
[0], ax
, 0, 0);
156 _cmsVEC3init(&m
->v
[1], 0, ay
, 0);
157 _cmsVEC3init(&m
->v
[2], 0, 0, az
);
158 _cmsVEC3init(off
, bx
, by
, bz
);
163 // Approximate a blackbody illuminant based on CHAD information
165 cmsFloat64Number
CHAD2Temp(const cmsMAT3
* Chad
)
167 // Convert D50 across inverse CHAD to get the absolute white point
170 cmsCIExyY DestChromaticity
;
171 cmsFloat64Number TempK
;
175 if (!_cmsMAT3inverse(&m1
, &m2
)) return FALSE
;
177 s
.n
[VX
] = cmsD50_XYZ() -> X
;
178 s
.n
[VY
] = cmsD50_XYZ() -> Y
;
179 s
.n
[VZ
] = cmsD50_XYZ() -> Z
;
181 _cmsMAT3eval(&d
, &m2
, &s
);
187 cmsXYZ2xyY(&DestChromaticity
, &Dest
);
189 if (!cmsTempFromWhitePoint(&TempK
, &DestChromaticity
))
195 // Compute a CHAD based on a given temperature
197 void Temp2CHAD(cmsMAT3
* Chad
, cmsFloat64Number Temp
)
200 cmsCIExyY ChromaticityOfWhite
;
202 cmsWhitePointFromTemp(&ChromaticityOfWhite
, Temp
);
203 cmsxyY2XYZ(&White
, &ChromaticityOfWhite
);
204 _cmsAdaptationMatrix(Chad
, NULL
, &White
, cmsD50_XYZ());
207 // Join scalings to obtain relative input to absolute and then to relative output.
208 // Result is stored in a 3x3 matrix
210 cmsBool
ComputeAbsoluteIntent(cmsFloat64Number AdaptationState
,
211 const cmsCIEXYZ
* WhitePointIn
,
212 const cmsMAT3
* ChromaticAdaptationMatrixIn
,
213 const cmsCIEXYZ
* WhitePointOut
,
214 const cmsMAT3
* ChromaticAdaptationMatrixOut
,
217 cmsMAT3 Scale
, m1
, m2
, m3
, m4
;
220 if (AdaptationState
== 1.0) {
222 // Observer is fully adapted. Keep chromatic adaptation.
223 // That is the standard V4 behaviour
224 _cmsVEC3init(&m
->v
[0], WhitePointIn
->X
/ WhitePointOut
->X
, 0, 0);
225 _cmsVEC3init(&m
->v
[1], 0, WhitePointIn
->Y
/ WhitePointOut
->Y
, 0);
226 _cmsVEC3init(&m
->v
[2], 0, 0, WhitePointIn
->Z
/ WhitePointOut
->Z
);
231 // Incomplete adaptation. This is an advanced feature.
232 _cmsVEC3init(&Scale
.v
[0], WhitePointIn
->X
/ WhitePointOut
->X
, 0, 0);
233 _cmsVEC3init(&Scale
.v
[1], 0, WhitePointIn
->Y
/ WhitePointOut
->Y
, 0);
234 _cmsVEC3init(&Scale
.v
[2], 0, 0, WhitePointIn
->Z
/ WhitePointOut
->Z
);
237 if (AdaptationState
== 0.0) {
239 m1
= *ChromaticAdaptationMatrixOut
;
240 _cmsMAT3per(&m2
, &m1
, &Scale
);
241 // m2 holds CHAD from output white to D50 times abs. col. scaling
243 // Observer is not adapted, undo the chromatic adaptation
244 _cmsMAT3per(m
, &m2
, ChromaticAdaptationMatrixOut
);
246 m3
= *ChromaticAdaptationMatrixIn
;
247 if (!_cmsMAT3inverse(&m3
, &m4
)) return FALSE
;
248 _cmsMAT3per(m
, &m2
, &m4
);
253 cmsFloat64Number TempSrc
, TempDest
, Temp
;
255 m1
= *ChromaticAdaptationMatrixIn
;
256 if (!_cmsMAT3inverse(&m1
, &m2
)) return FALSE
;
257 _cmsMAT3per(&m3
, &m2
, &Scale
);
258 // m3 holds CHAD from input white to D50 times abs. col. scaling
260 TempSrc
= CHAD2Temp(ChromaticAdaptationMatrixIn
);
261 TempDest
= CHAD2Temp(ChromaticAdaptationMatrixOut
);
263 if (TempSrc
< 0.0 || TempDest
< 0.0) return FALSE
; // Something went wrong
265 if (_cmsMAT3isIdentity(&Scale
) && fabs(TempSrc
- TempDest
) < 0.01) {
271 Temp
= (1.0 - AdaptationState
) * TempDest
+ AdaptationState
* TempSrc
;
273 // Get a CHAD from whatever output temperature to D50. This replaces output CHAD
274 Temp2CHAD(&MixedCHAD
, Temp
);
276 _cmsMAT3per(m
, &m3
, &MixedCHAD
);
284 // Just to see if m matrix should be applied
286 cmsBool
IsEmptyLayer(cmsMAT3
* m
, cmsVEC3
* off
)
288 cmsFloat64Number diff
= 0;
292 if (m
== NULL
&& off
== NULL
) return TRUE
; // NULL is allowed as an empty layer
293 if (m
== NULL
&& off
!= NULL
) return FALSE
; // This is an internal error
295 _cmsMAT3identity(&Ident
);
297 for (i
=0; i
< 3*3; i
++)
298 diff
+= fabs(((cmsFloat64Number
*)m
)[i
] - ((cmsFloat64Number
*)&Ident
)[i
]);
300 for (i
=0; i
< 3; i
++)
301 diff
+= fabs(((cmsFloat64Number
*)off
)[i
]);
304 return (diff
< 0.002);
308 // Compute the conversion layer
310 cmsBool
ComputeConversion(int i
, cmsHPROFILE hProfiles
[],
311 cmsUInt32Number Intent
,
313 cmsFloat64Number AdaptationState
,
314 cmsMAT3
* m
, cmsVEC3
* off
)
319 // m and off are set to identity and this is detected latter on
321 _cmsVEC3init(off
, 0, 0, 0);
323 // If intent is abs. colorimetric,
324 if (Intent
== INTENT_ABSOLUTE_COLORIMETRIC
) {
326 cmsCIEXYZ WhitePointIn
, WhitePointOut
;
327 cmsMAT3 ChromaticAdaptationMatrixIn
, ChromaticAdaptationMatrixOut
;
329 _cmsReadMediaWhitePoint(&WhitePointIn
, hProfiles
[i
-1]);
330 _cmsReadCHAD(&ChromaticAdaptationMatrixIn
, hProfiles
[i
-1]);
332 _cmsReadMediaWhitePoint(&WhitePointOut
, hProfiles
[i
]);
333 _cmsReadCHAD(&ChromaticAdaptationMatrixOut
, hProfiles
[i
]);
335 if (!ComputeAbsoluteIntent(AdaptationState
,
336 &WhitePointIn
, &ChromaticAdaptationMatrixIn
,
337 &WhitePointOut
, &ChromaticAdaptationMatrixOut
, m
)) return FALSE
;
341 // Rest of intents may apply BPC.
345 cmsCIEXYZ BlackPointIn
, BlackPointOut
;
347 cmsDetectBlackPoint(&BlackPointIn
, hProfiles
[i
-1], Intent
, 0);
348 cmsDetectDestinationBlackPoint(&BlackPointOut
, hProfiles
[i
], Intent
, 0);
350 // If black points are equal, then do nothing
351 if (BlackPointIn
.X
!= BlackPointOut
.X
||
352 BlackPointIn
.Y
!= BlackPointOut
.Y
||
353 BlackPointIn
.Z
!= BlackPointOut
.Z
)
354 ComputeBlackPointCompensation(&BlackPointIn
, &BlackPointOut
, m
, off
);
358 // Offset should be adjusted because the encoding. We encode XYZ normalized to 0..1.0,
359 // to do that, we divide by MAX_ENCODEABLE_XZY. The conversion stage goes XYZ -> XYZ so
360 // we have first to convert from encoded to XYZ and then convert back to encoded.
364 // y = y'c; y' = y / c
365 // y' = (Mx'c + Off) /c = Mx' + (Off / c)
367 for (k
=0; k
< 3; k
++) {
368 off
->n
[k
] /= MAX_ENCODEABLE_XYZ
;
375 // Add a conversion stage if needed. If a matrix/offset m is given, it applies to XYZ space
377 cmsBool
AddConversion(cmsPipeline
* Result
, cmsColorSpaceSignature InPCS
, cmsColorSpaceSignature OutPCS
, cmsMAT3
* m
, cmsVEC3
* off
)
379 cmsFloat64Number
* m_as_dbl
= (cmsFloat64Number
*) m
;
380 cmsFloat64Number
* off_as_dbl
= (cmsFloat64Number
*) off
;
382 // Handle PCS mismatches. A specialized stage is added to the LUT in such case
385 case cmsSigXYZData
: // Input profile operates in XYZ
389 case cmsSigXYZData
: // XYZ -> XYZ
390 if (!IsEmptyLayer(m
, off
) &&
391 !cmsPipelineInsertStage(Result
, cmsAT_END
, cmsStageAllocMatrix(Result
->ContextID
, 3, 3, m_as_dbl
, off_as_dbl
)))
395 case cmsSigLabData
: // XYZ -> Lab
396 if (!IsEmptyLayer(m
, off
) &&
397 !cmsPipelineInsertStage(Result
, cmsAT_END
, cmsStageAllocMatrix(Result
->ContextID
, 3, 3, m_as_dbl
, off_as_dbl
)))
399 if (!cmsPipelineInsertStage(Result
, cmsAT_END
, _cmsStageAllocXYZ2Lab(Result
->ContextID
)))
404 return FALSE
; // Colorspace mismatch
408 case cmsSigLabData
: // Input profile operates in Lab
412 case cmsSigXYZData
: // Lab -> XYZ
414 if (!cmsPipelineInsertStage(Result
, cmsAT_END
, _cmsStageAllocLab2XYZ(Result
->ContextID
)))
416 if (!IsEmptyLayer(m
, off
) &&
417 !cmsPipelineInsertStage(Result
, cmsAT_END
, cmsStageAllocMatrix(Result
->ContextID
, 3, 3, m_as_dbl
, off_as_dbl
)))
421 case cmsSigLabData
: // Lab -> Lab
423 if (!IsEmptyLayer(m
, off
)) {
424 if (!cmsPipelineInsertStage(Result
, cmsAT_END
, _cmsStageAllocLab2XYZ(Result
->ContextID
)) ||
425 !cmsPipelineInsertStage(Result
, cmsAT_END
, cmsStageAllocMatrix(Result
->ContextID
, 3, 3, m_as_dbl
, off_as_dbl
)) ||
426 !cmsPipelineInsertStage(Result
, cmsAT_END
, _cmsStageAllocXYZ2Lab(Result
->ContextID
)))
432 return FALSE
; // Mismatch
436 // On colorspaces other than PCS, check for same space
438 if (InPCS
!= OutPCS
) return FALSE
;
446 // Is a given space compatible with another?
448 cmsBool
ColorSpaceIsCompatible(cmsColorSpaceSignature a
, cmsColorSpaceSignature b
)
450 // If they are same, they are compatible.
451 if (a
== b
) return TRUE
;
453 // Check for MCH4 substitution of CMYK
454 if ((a
== cmsSig4colorData
) && (b
== cmsSigCmykData
)) return TRUE
;
455 if ((a
== cmsSigCmykData
) && (b
== cmsSig4colorData
)) return TRUE
;
457 // Check for XYZ/Lab. Those spaces are interchangeable as they can be computed one from other.
458 if ((a
== cmsSigXYZData
) && (b
== cmsSigLabData
)) return TRUE
;
459 if ((a
== cmsSigLabData
) && (b
== cmsSigXYZData
)) return TRUE
;
465 // Default handler for ICC-style intents
467 cmsPipeline
* DefaultICCintents(cmsContext ContextID
,
468 cmsUInt32Number nProfiles
,
469 cmsUInt32Number TheIntents
[],
470 cmsHPROFILE hProfiles
[],
472 cmsFloat64Number AdaptationStates
[],
473 cmsUInt32Number dwFlags
)
475 cmsPipeline
* Lut
= NULL
;
477 cmsHPROFILE hProfile
;
480 cmsColorSpaceSignature ColorSpaceIn
, ColorSpaceOut
, CurrentColorSpace
;
481 cmsProfileClassSignature ClassSig
;
482 cmsUInt32Number i
, Intent
;
485 if (nProfiles
== 0) return NULL
;
487 // Allocate an empty LUT for holding the result. 0 as channel count means 'undefined'
488 Result
= cmsPipelineAlloc(ContextID
, 0, 0);
489 if (Result
== NULL
) return NULL
;
491 CurrentColorSpace
= cmsGetColorSpace(hProfiles
[0]);
493 for (i
=0; i
< nProfiles
; i
++) {
495 cmsBool lIsDeviceLink
, lIsInput
;
497 hProfile
= hProfiles
[i
];
498 ClassSig
= cmsGetDeviceClass(hProfile
);
499 lIsDeviceLink
= (ClassSig
== cmsSigLinkClass
|| ClassSig
== cmsSigAbstractClass
);
501 // First profile is used as input unless devicelink or abstract
502 if ((i
== 0) && !lIsDeviceLink
) {
506 // Else use profile in the input direction if current space is not PCS
507 lIsInput
= (CurrentColorSpace
!= cmsSigXYZData
) &&
508 (CurrentColorSpace
!= cmsSigLabData
);
511 Intent
= TheIntents
[i
];
513 if (lIsInput
|| lIsDeviceLink
) {
515 ColorSpaceIn
= cmsGetColorSpace(hProfile
);
516 ColorSpaceOut
= cmsGetPCS(hProfile
);
520 ColorSpaceIn
= cmsGetPCS(hProfile
);
521 ColorSpaceOut
= cmsGetColorSpace(hProfile
);
524 if (!ColorSpaceIsCompatible(ColorSpaceIn
, CurrentColorSpace
)) {
526 cmsSignalError(ContextID
, cmsERROR_COLORSPACE_CHECK
, "ColorSpace mismatch");
530 // If devicelink is found, then no custom intent is allowed and we can
531 // read the LUT to be applied. Settings don't apply here.
532 if (lIsDeviceLink
|| ((ClassSig
== cmsSigNamedColorClass
) && (nProfiles
== 1))) {
534 // Get the involved LUT from the profile
535 Lut
= _cmsReadDevicelinkLUT(hProfile
, Intent
);
536 if (Lut
== NULL
) goto Error
;
538 // What about abstract profiles?
539 if (ClassSig
== cmsSigAbstractClass
&& i
> 0) {
540 if (!ComputeConversion(i
, hProfiles
, Intent
, BPC
[i
], AdaptationStates
[i
], &m
, &off
)) goto Error
;
543 _cmsMAT3identity(&m
);
544 _cmsVEC3init(&off
, 0, 0, 0);
548 if (!AddConversion(Result
, CurrentColorSpace
, ColorSpaceIn
, &m
, &off
)) goto Error
;
554 // Input direction means non-pcs connection, so proceed like devicelinks
555 Lut
= _cmsReadInputLUT(hProfile
, Intent
);
556 if (Lut
== NULL
) goto Error
;
560 // Output direction means PCS connection. Intent may apply here
561 Lut
= _cmsReadOutputLUT(hProfile
, Intent
);
562 if (Lut
== NULL
) goto Error
;
565 if (!ComputeConversion(i
, hProfiles
, Intent
, BPC
[i
], AdaptationStates
[i
], &m
, &off
)) goto Error
;
566 if (!AddConversion(Result
, CurrentColorSpace
, ColorSpaceIn
, &m
, &off
)) goto Error
;
571 // Concatenate to the output LUT
572 if (!cmsPipelineCat(Result
, Lut
))
574 cmsPipelineFree(Lut
);
576 // Update current space
577 CurrentColorSpace
= ColorSpaceOut
;
584 cmsPipelineFree(Lut
);
585 if (Result
!= NULL
) cmsPipelineFree(Result
);
588 cmsUNUSED_PARAMETER(dwFlags
);
592 // Wrapper for DLL calling convention
593 cmsPipeline
* CMSEXPORT
_cmsDefaultICCintents(cmsContext ContextID
,
594 cmsUInt32Number nProfiles
,
595 cmsUInt32Number TheIntents
[],
596 cmsHPROFILE hProfiles
[],
598 cmsFloat64Number AdaptationStates
[],
599 cmsUInt32Number dwFlags
)
601 return DefaultICCintents(ContextID
, nProfiles
, TheIntents
, hProfiles
, BPC
, AdaptationStates
, dwFlags
);
604 // Black preserving intents ---------------------------------------------------------------------------------------------
606 // Translate black-preserving intents to ICC ones
608 int TranslateNonICCIntents(int Intent
)
611 case INTENT_PRESERVE_K_ONLY_PERCEPTUAL
:
612 case INTENT_PRESERVE_K_PLANE_PERCEPTUAL
:
613 return INTENT_PERCEPTUAL
;
615 case INTENT_PRESERVE_K_ONLY_RELATIVE_COLORIMETRIC
:
616 case INTENT_PRESERVE_K_PLANE_RELATIVE_COLORIMETRIC
:
617 return INTENT_RELATIVE_COLORIMETRIC
;
619 case INTENT_PRESERVE_K_ONLY_SATURATION
:
620 case INTENT_PRESERVE_K_PLANE_SATURATION
:
621 return INTENT_SATURATION
;
623 default: return Intent
;
627 // Sampler for Black-only preserving CMYK->CMYK transforms
630 cmsPipeline
* cmyk2cmyk
; // The original transform
631 cmsToneCurve
* KTone
; // Black-to-black tone curve
636 // Preserve black only if that is the only ink used
638 int BlackPreservingGrayOnlySampler(register const cmsUInt16Number In
[], register cmsUInt16Number Out
[], register void* Cargo
)
640 GrayOnlyParams
* bp
= (GrayOnlyParams
*) Cargo
;
642 // If going across black only, keep black only
643 if (In
[0] == 0 && In
[1] == 0 && In
[2] == 0) {
645 // TAC does not apply because it is black ink!
646 Out
[0] = Out
[1] = Out
[2] = 0;
647 Out
[3] = cmsEvalToneCurve16(bp
->KTone
, In
[3]);
651 // Keep normal transform for other colors
652 bp
->cmyk2cmyk
->Eval16Fn(In
, Out
, bp
->cmyk2cmyk
->Data
);
656 // This is the entry for black-preserving K-only intents, which are non-ICC
658 cmsPipeline
* BlackPreservingKOnlyIntents(cmsContext ContextID
,
659 cmsUInt32Number nProfiles
,
660 cmsUInt32Number TheIntents
[],
661 cmsHPROFILE hProfiles
[],
663 cmsFloat64Number AdaptationStates
[],
664 cmsUInt32Number dwFlags
)
668 cmsUInt32Number ICCIntents
[256];
670 cmsUInt32Number i
, nGridPoints
;
674 if (nProfiles
< 1 || nProfiles
> 255) return NULL
;
676 // Translate black-preserving intents to ICC ones
677 for (i
=0; i
< nProfiles
; i
++)
678 ICCIntents
[i
] = TranslateNonICCIntents(TheIntents
[i
]);
680 // Check for non-cmyk profiles
681 if (cmsGetColorSpace(hProfiles
[0]) != cmsSigCmykData
||
682 cmsGetColorSpace(hProfiles
[nProfiles
-1]) != cmsSigCmykData
)
683 return DefaultICCintents(ContextID
, nProfiles
, ICCIntents
, hProfiles
, BPC
, AdaptationStates
, dwFlags
);
685 memset(&bp
, 0, sizeof(bp
));
687 // Allocate an empty LUT for holding the result
688 Result
= cmsPipelineAlloc(ContextID
, 4, 4);
689 if (Result
== NULL
) return NULL
;
691 // Create a LUT holding normal ICC transform
692 bp
.cmyk2cmyk
= DefaultICCintents(ContextID
,
700 if (bp
.cmyk2cmyk
== NULL
) goto Error
;
702 // Now, compute the tone curve
703 bp
.KTone
= _cmsBuildKToneCurve(ContextID
,
712 if (bp
.KTone
== NULL
) goto Error
;
715 // How many gridpoints are we going to use?
716 nGridPoints
= _cmsReasonableGridpointsByColorspace(cmsSigCmykData
, dwFlags
);
718 // Create the CLUT. 16 bits
719 CLUT
= cmsStageAllocCLut16bit(ContextID
, nGridPoints
, 4, 4, NULL
);
720 if (CLUT
== NULL
) goto Error
;
722 // This is the one and only MPE in this LUT
723 if (!cmsPipelineInsertStage(Result
, cmsAT_BEGIN
, CLUT
))
726 // Sample it. We cannot afford pre/post linearization this time.
727 if (!cmsStageSampleCLut16bit(CLUT
, BlackPreservingGrayOnlySampler
, (void*) &bp
, 0))
730 // Get rid of xform and tone curve
731 cmsPipelineFree(bp
.cmyk2cmyk
);
732 cmsFreeToneCurve(bp
.KTone
);
738 if (bp
.cmyk2cmyk
!= NULL
) cmsPipelineFree(bp
.cmyk2cmyk
);
739 if (bp
.KTone
!= NULL
) cmsFreeToneCurve(bp
.KTone
);
740 if (Result
!= NULL
) cmsPipelineFree(Result
);
745 // K Plane-preserving CMYK to CMYK ------------------------------------------------------------------------------------
749 cmsPipeline
* cmyk2cmyk
; // The original transform
750 cmsHTRANSFORM hProofOutput
; // Output CMYK to Lab (last profile)
751 cmsHTRANSFORM cmyk2Lab
; // The input chain
752 cmsToneCurve
* KTone
; // Black-to-black tone curve
753 cmsPipeline
* LabK2cmyk
; // The output profile
754 cmsFloat64Number MaxError
;
756 cmsHTRANSFORM hRoundTrip
;
757 cmsFloat64Number MaxTAC
;
760 } PreserveKPlaneParams
;
763 // The CLUT will be stored at 16 bits, but calculations are performed at cmsFloat32Number precision
765 int BlackPreservingSampler(register const cmsUInt16Number In
[], register cmsUInt16Number Out
[], register void* Cargo
)
768 cmsFloat32Number Inf
[4], Outf
[4];
769 cmsFloat32Number LabK
[4];
770 cmsFloat64Number SumCMY
, SumCMYK
, Error
, Ratio
;
771 cmsCIELab ColorimetricLab
, BlackPreservingLab
;
772 PreserveKPlaneParams
* bp
= (PreserveKPlaneParams
*) Cargo
;
774 // Convert from 16 bits to floating point
775 for (i
=0; i
< 4; i
++)
776 Inf
[i
] = (cmsFloat32Number
) (In
[i
] / 65535.0);
778 // Get the K across Tone curve
779 LabK
[3] = cmsEvalToneCurveFloat(bp
->KTone
, Inf
[3]);
781 // If going across black only, keep black only
782 if (In
[0] == 0 && In
[1] == 0 && In
[2] == 0) {
784 Out
[0] = Out
[1] = Out
[2] = 0;
785 Out
[3] = _cmsQuickSaturateWord(LabK
[3] * 65535.0);
789 // Try the original transform,
790 cmsPipelineEvalFloat( Inf
, Outf
, bp
->cmyk2cmyk
);
792 // Store a copy of the floating point result into 16-bit
793 for (i
=0; i
< 4; i
++)
794 Out
[i
] = _cmsQuickSaturateWord(Outf
[i
] * 65535.0);
796 // Maybe K is already ok (mostly on K=0)
797 if ( fabs(Outf
[3] - LabK
[3]) < (3.0 / 65535.0) ) {
801 // K differ, mesure and keep Lab measurement for further usage
802 // this is done in relative colorimetric intent
803 cmsDoTransform(bp
->hProofOutput
, Out
, &ColorimetricLab
, 1);
805 // Is not black only and the transform doesn't keep black.
806 // Obtain the Lab of output CMYK. After that we have Lab + K
807 cmsDoTransform(bp
->cmyk2Lab
, Outf
, LabK
, 1);
809 // Obtain the corresponding CMY using reverse interpolation
810 // (K is fixed in LabK[3])
811 if (!cmsPipelineEvalReverseFloat(LabK
, Outf
, Outf
, bp
->LabK2cmyk
)) {
813 // Cannot find a suitable value, so use colorimetric xform
814 // which is already stored in Out[]
818 // Make sure to pass thru K (which now is fixed)
821 // Apply TAC if needed
822 SumCMY
= Outf
[0] + Outf
[1] + Outf
[2];
823 SumCMYK
= SumCMY
+ Outf
[3];
825 if (SumCMYK
> bp
->MaxTAC
) {
827 Ratio
= 1 - ((SumCMYK
- bp
->MaxTAC
) / SumCMY
);
834 Out
[0] = _cmsQuickSaturateWord(Outf
[0] * Ratio
* 65535.0); // C
835 Out
[1] = _cmsQuickSaturateWord(Outf
[1] * Ratio
* 65535.0); // M
836 Out
[2] = _cmsQuickSaturateWord(Outf
[2] * Ratio
* 65535.0); // Y
837 Out
[3] = _cmsQuickSaturateWord(Outf
[3] * 65535.0);
839 // Estimate the error (this goes 16 bits to Lab DBL)
840 cmsDoTransform(bp
->hProofOutput
, Out
, &BlackPreservingLab
, 1);
841 Error
= cmsDeltaE(&ColorimetricLab
, &BlackPreservingLab
);
842 if (Error
> bp
-> MaxError
)
843 bp
->MaxError
= Error
;
848 // This is the entry for black-plane preserving, which are non-ICC
850 cmsPipeline
* BlackPreservingKPlaneIntents(cmsContext ContextID
,
851 cmsUInt32Number nProfiles
,
852 cmsUInt32Number TheIntents
[],
853 cmsHPROFILE hProfiles
[],
855 cmsFloat64Number AdaptationStates
[],
856 cmsUInt32Number dwFlags
)
858 PreserveKPlaneParams bp
;
859 cmsPipeline
* Result
= NULL
;
860 cmsUInt32Number ICCIntents
[256];
862 cmsUInt32Number i
, nGridPoints
;
866 if (nProfiles
< 1 || nProfiles
> 255) return NULL
;
868 // Translate black-preserving intents to ICC ones
869 for (i
=0; i
< nProfiles
; i
++)
870 ICCIntents
[i
] = TranslateNonICCIntents(TheIntents
[i
]);
872 // Check for non-cmyk profiles
873 if (cmsGetColorSpace(hProfiles
[0]) != cmsSigCmykData
||
874 !(cmsGetColorSpace(hProfiles
[nProfiles
-1]) == cmsSigCmykData
||
875 cmsGetDeviceClass(hProfiles
[nProfiles
-1]) == cmsSigOutputClass
))
876 return DefaultICCintents(ContextID
, nProfiles
, ICCIntents
, hProfiles
, BPC
, AdaptationStates
, dwFlags
);
878 // Allocate an empty LUT for holding the result
879 Result
= cmsPipelineAlloc(ContextID
, 4, 4);
880 if (Result
== NULL
) return NULL
;
883 memset(&bp
, 0, sizeof(bp
));
885 // We need the input LUT of the last profile, assuming this one is responsible of
886 // black generation. This LUT will be seached in inverse order.
887 bp
.LabK2cmyk
= _cmsReadInputLUT(hProfiles
[nProfiles
-1], INTENT_RELATIVE_COLORIMETRIC
);
888 if (bp
.LabK2cmyk
== NULL
) goto Cleanup
;
890 // Get total area coverage (in 0..1 domain)
891 bp
.MaxTAC
= cmsDetectTAC(hProfiles
[nProfiles
-1]) / 100.0;
892 if (bp
.MaxTAC
<= 0) goto Cleanup
;
895 // Create a LUT holding normal ICC transform
896 bp
.cmyk2cmyk
= DefaultICCintents(ContextID
,
903 if (bp
.cmyk2cmyk
== NULL
) goto Cleanup
;
905 // Now the tone curve
906 bp
.KTone
= _cmsBuildKToneCurve(ContextID
, 4096, nProfiles
,
912 if (bp
.KTone
== NULL
) goto Cleanup
;
914 // To measure the output, Last profile to Lab
915 hLab
= cmsCreateLab4ProfileTHR(ContextID
, NULL
);
916 bp
.hProofOutput
= cmsCreateTransformTHR(ContextID
, hProfiles
[nProfiles
-1],
917 CHANNELS_SH(4)|BYTES_SH(2), hLab
, TYPE_Lab_DBL
,
918 INTENT_RELATIVE_COLORIMETRIC
,
919 cmsFLAGS_NOCACHE
|cmsFLAGS_NOOPTIMIZE
);
920 if ( bp
.hProofOutput
== NULL
) goto Cleanup
;
922 // Same as anterior, but lab in the 0..1 range
923 bp
.cmyk2Lab
= cmsCreateTransformTHR(ContextID
, hProfiles
[nProfiles
-1],
924 FLOAT_SH(1)|CHANNELS_SH(4)|BYTES_SH(4), hLab
,
925 FLOAT_SH(1)|CHANNELS_SH(3)|BYTES_SH(4),
926 INTENT_RELATIVE_COLORIMETRIC
,
927 cmsFLAGS_NOCACHE
|cmsFLAGS_NOOPTIMIZE
);
928 if (bp
.cmyk2Lab
== NULL
) goto Cleanup
;
929 cmsCloseProfile(hLab
);
931 // Error estimation (for debug only)
934 // How many gridpoints are we going to use?
935 nGridPoints
= _cmsReasonableGridpointsByColorspace(cmsSigCmykData
, dwFlags
);
938 CLUT
= cmsStageAllocCLut16bit(ContextID
, nGridPoints
, 4, 4, NULL
);
939 if (CLUT
== NULL
) goto Cleanup
;
941 if (!cmsPipelineInsertStage(Result
, cmsAT_BEGIN
, CLUT
))
944 cmsStageSampleCLut16bit(CLUT
, BlackPreservingSampler
, (void*) &bp
, 0);
948 if (bp
.cmyk2cmyk
) cmsPipelineFree(bp
.cmyk2cmyk
);
949 if (bp
.cmyk2Lab
) cmsDeleteTransform(bp
.cmyk2Lab
);
950 if (bp
.hProofOutput
) cmsDeleteTransform(bp
.hProofOutput
);
952 if (bp
.KTone
) cmsFreeToneCurve(bp
.KTone
);
953 if (bp
.LabK2cmyk
) cmsPipelineFree(bp
.LabK2cmyk
);
958 // Link routines ------------------------------------------------------------------------------------------------------
960 // Chain several profiles into a single LUT. It just checks the parameters and then calls the handler
961 // for the first intent in chain. The handler may be user-defined. Is up to the handler to deal with the
962 // rest of intents in chain. A maximum of 255 profiles at time are supported, which is pretty reasonable.
963 cmsPipeline
* _cmsLinkProfiles(cmsContext ContextID
,
964 cmsUInt32Number nProfiles
,
965 cmsUInt32Number TheIntents
[],
966 cmsHPROFILE hProfiles
[],
968 cmsFloat64Number AdaptationStates
[],
969 cmsUInt32Number dwFlags
)
972 cmsIntentsList
* Intent
;
974 // Make sure a reasonable number of profiles is provided
975 if (nProfiles
<= 0 || nProfiles
> 255) {
976 cmsSignalError(ContextID
, cmsERROR_RANGE
, "Couldn't link '%d' profiles", nProfiles
);
980 for (i
=0; i
< nProfiles
; i
++) {
982 // Check if black point is really needed or allowed. Note that
983 // following Adobe's document:
984 // BPC does not apply to devicelink profiles, nor to abs colorimetric,
985 // and applies always on V4 perceptual and saturation.
987 if (TheIntents
[i
] == INTENT_ABSOLUTE_COLORIMETRIC
)
990 if (TheIntents
[i
] == INTENT_PERCEPTUAL
|| TheIntents
[i
] == INTENT_SATURATION
) {
992 // Force BPC for V4 profiles in perceptual and saturation
993 if (cmsGetProfileVersion(hProfiles
[i
]) >= 4.0)
998 // Search for a handler. The first intent in the chain defines the handler. That would
999 // prevent using multiple custom intents in a multiintent chain, but the behaviour of
1000 // this case would present some issues if the custom intent tries to do things like
1001 // preserve primaries. This solution is not perfect, but works well on most cases.
1003 Intent
= SearchIntent(TheIntents
[0]);
1004 if (Intent
== NULL
) {
1005 cmsSignalError(ContextID
, cmsERROR_UNKNOWN_EXTENSION
, "Unsupported intent '%d'", TheIntents
[0]);
1010 return Intent
->Link(ContextID
, nProfiles
, TheIntents
, hProfiles
, BPC
, AdaptationStates
, dwFlags
);
1013 // -------------------------------------------------------------------------------------------------
1015 // Get information about available intents. nMax is the maximum space for the supplied "Codes"
1016 // and "Descriptions" the function returns the total number of intents, which may be greater
1017 // than nMax, although the matrices are not populated beyond this level.
1018 cmsUInt32Number CMSEXPORT
cmsGetSupportedIntents(cmsUInt32Number nMax
, cmsUInt32Number
* Codes
, char** Descriptions
)
1021 cmsUInt32Number nIntents
;
1023 for (nIntents
=0, pt
= Intents
; pt
!= NULL
; pt
= pt
-> Next
)
1025 if (nIntents
< nMax
) {
1027 Codes
[nIntents
] = pt
->Intent
;
1029 if (Descriptions
!= NULL
)
1030 Descriptions
[nIntents
] = pt
->Description
;
1039 // The plug-in registration. User can add new intents or override default routines
1040 cmsBool
_cmsRegisterRenderingIntentPlugin(cmsContext id
, cmsPluginBase
* Data
)
1042 cmsPluginRenderingIntent
* Plugin
= (cmsPluginRenderingIntent
*) Data
;
1045 // Do we have to reset the intents?
1048 Intents
= DefaultIntents
;
1052 fl
= SearchIntent(Plugin
->Intent
);
1055 fl
= (cmsIntentsList
*) _cmsPluginMalloc(id
, sizeof(cmsIntentsList
));
1056 if (fl
== NULL
) return FALSE
;
1059 fl
->Intent
= Plugin
->Intent
;
1060 strncpy(fl
->Description
, Plugin
->Description
, 255);
1061 fl
->Description
[255] = 0;
1063 fl
->Link
= Plugin
->Link
;
1065 fl
->Next
= Intents
;