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 _cmsIntentsPluginChunkType _cmsIntentsPluginChunk = { NULL };
113 // Duplicates the zone of memory used by the plug-in in the new context
115 void DupPluginIntentsList(struct _cmsContext_struct* ctx,
116 const struct _cmsContext_struct* src)
118 _cmsIntentsPluginChunkType newHead = { NULL };
119 cmsIntentsList* entry;
120 cmsIntentsList* Anterior = NULL;
121 _cmsIntentsPluginChunkType* head = (_cmsIntentsPluginChunkType*) src->chunks[IntentPlugin];
123 // Walk the list copying all nodes
124 for (entry = head->Intents;
126 entry = entry ->Next) {
128 cmsIntentsList *newEntry = ( cmsIntentsList *) _cmsSubAllocDup(ctx ->MemPool, entry, sizeof(cmsIntentsList));
130 if (newEntry == NULL)
133 // We want to keep the linked list order, so this is a little bit tricky
134 newEntry -> Next = NULL;
136 Anterior -> Next = newEntry;
140 if (newHead.Intents == NULL)
141 newHead.Intents = newEntry;
144 ctx ->chunks[IntentPlugin] = _cmsSubAllocDup(ctx->MemPool, &newHead, sizeof(_cmsIntentsPluginChunkType));
147 void _cmsAllocIntentsPluginChunk(struct _cmsContext_struct* ctx,
148 const struct _cmsContext_struct* src)
152 // Copy all linked list
153 DupPluginIntentsList(ctx, src);
156 static _cmsIntentsPluginChunkType IntentsPluginChunkType = { NULL };
157 ctx ->chunks[IntentPlugin] = _cmsSubAllocDup(ctx ->MemPool, &IntentsPluginChunkType, sizeof(_cmsIntentsPluginChunkType));
162 // Search the list for a suitable intent. Returns NULL if not found
164 cmsIntentsList* SearchIntent(cmsContext ContextID, cmsUInt32Number Intent)
166 _cmsIntentsPluginChunkType* ctx = ( _cmsIntentsPluginChunkType*) _cmsContextGetClientChunk(ContextID, IntentPlugin);
169 for (pt = ctx -> Intents; pt != NULL; pt = pt -> Next)
170 if (pt ->Intent == Intent) return pt;
172 for (pt = DefaultIntents; pt != NULL; pt = pt -> Next)
173 if (pt ->Intent == Intent) return pt;
178 // Black point compensation. Implemented as a linear scaling in XYZ. Black points
179 // should come relative to the white point. Fills an matrix/offset element m
180 // which is organized as a 4x4 matrix.
182 void ComputeBlackPointCompensation(const cmsCIEXYZ* BlackPointIn,
183 const cmsCIEXYZ* BlackPointOut,
184 cmsMAT3* m, cmsVEC3* off)
186 cmsFloat64Number ax, ay, az, bx, by, bz, tx, ty, tz;
188 // Now we need to compute a matrix plus an offset m and of such of
189 // [m]*bpin + off = bpout
190 // [m]*D50 + off = D50
192 // This is a linear scaling in the form ax+b, where
193 // a = (bpout - D50) / (bpin - D50)
194 // b = - D50* (bpout - bpin) / (bpin - D50)
196 tx = BlackPointIn->X - cmsD50_XYZ()->X;
197 ty = BlackPointIn->Y - cmsD50_XYZ()->Y;
198 tz = BlackPointIn->Z - cmsD50_XYZ()->Z;
200 ax = (BlackPointOut->X - cmsD50_XYZ()->X) / tx;
201 ay = (BlackPointOut->Y - cmsD50_XYZ()->Y) / ty;
202 az = (BlackPointOut->Z - cmsD50_XYZ()->Z) / tz;
204 bx = - cmsD50_XYZ()-> X * (BlackPointOut->X - BlackPointIn->X) / tx;
205 by = - cmsD50_XYZ()-> Y * (BlackPointOut->Y - BlackPointIn->Y) / ty;
206 bz = - cmsD50_XYZ()-> Z * (BlackPointOut->Z - BlackPointIn->Z) / tz;
208 _cmsVEC3init(&m ->v[0], ax, 0, 0);
209 _cmsVEC3init(&m ->v[1], 0, ay, 0);
210 _cmsVEC3init(&m ->v[2], 0, 0, az);
211 _cmsVEC3init(off, bx, by, bz);
216 // Approximate a blackbody illuminant based on CHAD information
218 cmsFloat64Number CHAD2Temp(const cmsMAT3* Chad)
220 // Convert D50 across inverse CHAD to get the absolute white point
223 cmsCIExyY DestChromaticity;
224 cmsFloat64Number TempK;
228 if (!_cmsMAT3inverse(&m1, &m2)) return FALSE;
230 s.n[VX] = cmsD50_XYZ() -> X;
231 s.n[VY] = cmsD50_XYZ() -> Y;
232 s.n[VZ] = cmsD50_XYZ() -> Z;
234 _cmsMAT3eval(&d, &m2, &s);
240 cmsXYZ2xyY(&DestChromaticity, &Dest);
242 if (!cmsTempFromWhitePoint(&TempK, &DestChromaticity))
248 // Compute a CHAD based on a given temperature
250 void Temp2CHAD(cmsMAT3* Chad, cmsFloat64Number Temp)
253 cmsCIExyY ChromaticityOfWhite;
255 cmsWhitePointFromTemp(&ChromaticityOfWhite, Temp);
256 cmsxyY2XYZ(&White, &ChromaticityOfWhite);
257 _cmsAdaptationMatrix(Chad, NULL, &White, cmsD50_XYZ());
260 // Join scalings to obtain relative input to absolute and then to relative output.
261 // Result is stored in a 3x3 matrix
263 cmsBool ComputeAbsoluteIntent(cmsFloat64Number AdaptationState,
264 const cmsCIEXYZ* WhitePointIn,
265 const cmsMAT3* ChromaticAdaptationMatrixIn,
266 const cmsCIEXYZ* WhitePointOut,
267 const cmsMAT3* ChromaticAdaptationMatrixOut,
270 cmsMAT3 Scale, m1, m2, m3, m4;
273 if (AdaptationState == 1.0) {
275 // Observer is fully adapted. Keep chromatic adaptation.
276 // That is the standard V4 behaviour
277 _cmsVEC3init(&m->v[0], WhitePointIn->X / WhitePointOut->X, 0, 0);
278 _cmsVEC3init(&m->v[1], 0, WhitePointIn->Y / WhitePointOut->Y, 0);
279 _cmsVEC3init(&m->v[2], 0, 0, WhitePointIn->Z / WhitePointOut->Z);
284 // Incomplete adaptation. This is an advanced feature.
285 _cmsVEC3init(&Scale.v[0], WhitePointIn->X / WhitePointOut->X, 0, 0);
286 _cmsVEC3init(&Scale.v[1], 0, WhitePointIn->Y / WhitePointOut->Y, 0);
287 _cmsVEC3init(&Scale.v[2], 0, 0, WhitePointIn->Z / WhitePointOut->Z);
290 if (AdaptationState == 0.0) {
292 m1 = *ChromaticAdaptationMatrixOut;
293 _cmsMAT3per(&m2, &m1, &Scale);
294 // m2 holds CHAD from output white to D50 times abs. col. scaling
296 // Observer is not adapted, undo the chromatic adaptation
297 _cmsMAT3per(m, &m2, ChromaticAdaptationMatrixOut);
299 m3 = *ChromaticAdaptationMatrixIn;
300 if (!_cmsMAT3inverse(&m3, &m4)) return FALSE;
301 _cmsMAT3per(m, &m2, &m4);
306 cmsFloat64Number TempSrc, TempDest, Temp;
308 m1 = *ChromaticAdaptationMatrixIn;
309 if (!_cmsMAT3inverse(&m1, &m2)) return FALSE;
310 _cmsMAT3per(&m3, &m2, &Scale);
311 // m3 holds CHAD from input white to D50 times abs. col. scaling
313 TempSrc = CHAD2Temp(ChromaticAdaptationMatrixIn);
314 TempDest = CHAD2Temp(ChromaticAdaptationMatrixOut);
316 if (TempSrc < 0.0 || TempDest < 0.0) return FALSE; // Something went wrong
318 if (_cmsMAT3isIdentity(&Scale) && fabs(TempSrc - TempDest) < 0.01) {
324 Temp = (1.0 - AdaptationState) * TempDest + AdaptationState * TempSrc;
326 // Get a CHAD from whatever output temperature to D50. This replaces output CHAD
327 Temp2CHAD(&MixedCHAD, Temp);
329 _cmsMAT3per(m, &m3, &MixedCHAD);
337 // Just to see if m matrix should be applied
339 cmsBool IsEmptyLayer(cmsMAT3* m, cmsVEC3* off)
341 cmsFloat64Number diff = 0;
345 if (m == NULL && off == NULL) return TRUE; // NULL is allowed as an empty layer
346 if (m == NULL && off != NULL) return FALSE; // This is an internal error
348 _cmsMAT3identity(&Ident);
350 for (i=0; i < 3*3; i++)
351 diff += fabs(((cmsFloat64Number*)m)[i] - ((cmsFloat64Number*)&Ident)[i]);
353 for (i=0; i < 3; i++)
354 diff += fabs(((cmsFloat64Number*)off)[i]);
357 return (diff < 0.002);
361 // Compute the conversion layer
363 cmsBool ComputeConversion(int i, cmsHPROFILE hProfiles[],
364 cmsUInt32Number Intent,
366 cmsFloat64Number AdaptationState,
367 cmsMAT3* m, cmsVEC3* off)
372 // m and off are set to identity and this is detected latter on
374 _cmsVEC3init(off, 0, 0, 0);
376 // If intent is abs. colorimetric,
377 if (Intent == INTENT_ABSOLUTE_COLORIMETRIC) {
379 cmsCIEXYZ WhitePointIn, WhitePointOut;
380 cmsMAT3 ChromaticAdaptationMatrixIn, ChromaticAdaptationMatrixOut;
382 _cmsReadMediaWhitePoint(&WhitePointIn, hProfiles[i-1]);
383 _cmsReadCHAD(&ChromaticAdaptationMatrixIn, hProfiles[i-1]);
385 _cmsReadMediaWhitePoint(&WhitePointOut, hProfiles[i]);
386 _cmsReadCHAD(&ChromaticAdaptationMatrixOut, hProfiles[i]);
388 if (!ComputeAbsoluteIntent(AdaptationState,
389 &WhitePointIn, &ChromaticAdaptationMatrixIn,
390 &WhitePointOut, &ChromaticAdaptationMatrixOut, m)) return FALSE;
394 // Rest of intents may apply BPC.
398 cmsCIEXYZ BlackPointIn, BlackPointOut;
400 cmsDetectBlackPoint(&BlackPointIn, hProfiles[i-1], Intent, 0);
401 cmsDetectDestinationBlackPoint(&BlackPointOut, hProfiles[i], Intent, 0);
403 // If black points are equal, then do nothing
404 if (BlackPointIn.X != BlackPointOut.X ||
405 BlackPointIn.Y != BlackPointOut.Y ||
406 BlackPointIn.Z != BlackPointOut.Z)
407 ComputeBlackPointCompensation(&BlackPointIn, &BlackPointOut, m, off);
411 // Offset should be adjusted because the encoding. We encode XYZ normalized to 0..1.0,
412 // to do that, we divide by MAX_ENCODEABLE_XZY. The conversion stage goes XYZ -> XYZ so
413 // we have first to convert from encoded to XYZ and then convert back to encoded.
417 // y = y'c; y' = y / c
418 // y' = (Mx'c + Off) /c = Mx' + (Off / c)
420 for (k=0; k < 3; k++) {
421 off ->n[k] /= MAX_ENCODEABLE_XYZ;
428 // Add a conversion stage if needed. If a matrix/offset m is given, it applies to XYZ space
430 cmsBool AddConversion(cmsPipeline* Result, cmsColorSpaceSignature InPCS, cmsColorSpaceSignature OutPCS, cmsMAT3* m, cmsVEC3* off)
432 cmsFloat64Number* m_as_dbl = (cmsFloat64Number*) m;
433 cmsFloat64Number* off_as_dbl = (cmsFloat64Number*) off;
435 // Handle PCS mismatches. A specialized stage is added to the LUT in such case
438 case cmsSigXYZData: // Input profile operates in XYZ
442 case cmsSigXYZData: // XYZ -> XYZ
443 if (!IsEmptyLayer(m, off) &&
444 !cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl)))
448 case cmsSigLabData: // XYZ -> Lab
449 if (!IsEmptyLayer(m, off) &&
450 !cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl)))
452 if (!cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocXYZ2Lab(Result ->ContextID)))
457 return FALSE; // Colorspace mismatch
461 case cmsSigLabData: // Input profile operates in Lab
465 case cmsSigXYZData: // Lab -> XYZ
467 if (!cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocLab2XYZ(Result ->ContextID)))
469 if (!IsEmptyLayer(m, off) &&
470 !cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl)))
474 case cmsSigLabData: // Lab -> Lab
476 if (!IsEmptyLayer(m, off)) {
477 if (!cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocLab2XYZ(Result ->ContextID)) ||
478 !cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl)) ||
479 !cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocXYZ2Lab(Result ->ContextID)))
485 return FALSE; // Mismatch
489 // On colorspaces other than PCS, check for same space
491 if (InPCS != OutPCS) return FALSE;
499 // Is a given space compatible with another?
501 cmsBool ColorSpaceIsCompatible(cmsColorSpaceSignature a, cmsColorSpaceSignature b)
503 // If they are same, they are compatible.
504 if (a == b) return TRUE;
506 // Check for MCH4 substitution of CMYK
507 if ((a == cmsSig4colorData) && (b == cmsSigCmykData)) return TRUE;
508 if ((a == cmsSigCmykData) && (b == cmsSig4colorData)) return TRUE;
510 // Check for XYZ/Lab. Those spaces are interchangeable as they can be computed one from other.
511 if ((a == cmsSigXYZData) && (b == cmsSigLabData)) return TRUE;
512 if ((a == cmsSigLabData) && (b == cmsSigXYZData)) return TRUE;
518 // Default handler for ICC-style intents
520 cmsPipeline* DefaultICCintents(cmsContext ContextID,
521 cmsUInt32Number nProfiles,
522 cmsUInt32Number TheIntents[],
523 cmsHPROFILE hProfiles[],
525 cmsFloat64Number AdaptationStates[],
526 cmsUInt32Number dwFlags)
528 cmsPipeline* Lut = NULL;
530 cmsHPROFILE hProfile;
533 cmsColorSpaceSignature ColorSpaceIn, ColorSpaceOut, CurrentColorSpace;
534 cmsProfileClassSignature ClassSig;
535 cmsUInt32Number i, Intent;
538 if (nProfiles == 0) return NULL;
540 // Allocate an empty LUT for holding the result. 0 as channel count means 'undefined'
541 Result = cmsPipelineAlloc(ContextID, 0, 0);
542 if (Result == NULL) return NULL;
544 CurrentColorSpace = cmsGetColorSpace(hProfiles[0]);
546 for (i=0; i < nProfiles; i++) {
548 cmsBool lIsDeviceLink, lIsInput;
550 hProfile = hProfiles[i];
551 ClassSig = cmsGetDeviceClass(hProfile);
552 lIsDeviceLink = (ClassSig == cmsSigLinkClass || ClassSig == cmsSigAbstractClass );
554 // First profile is used as input unless devicelink or abstract
555 if ((i == 0) && !lIsDeviceLink) {
559 // Else use profile in the input direction if current space is not PCS
560 lIsInput = (CurrentColorSpace != cmsSigXYZData) &&
561 (CurrentColorSpace != cmsSigLabData);
564 Intent = TheIntents[i];
566 if (lIsInput || lIsDeviceLink) {
568 ColorSpaceIn = cmsGetColorSpace(hProfile);
569 ColorSpaceOut = cmsGetPCS(hProfile);
573 ColorSpaceIn = cmsGetPCS(hProfile);
574 ColorSpaceOut = cmsGetColorSpace(hProfile);
577 if (!ColorSpaceIsCompatible(ColorSpaceIn, CurrentColorSpace)) {
579 cmsSignalError(ContextID, cmsERROR_COLORSPACE_CHECK, "ColorSpace mismatch");
583 // If devicelink is found, then no custom intent is allowed and we can
584 // read the LUT to be applied. Settings don't apply here.
585 if (lIsDeviceLink || ((ClassSig == cmsSigNamedColorClass) && (nProfiles == 1))) {
587 // Get the involved LUT from the profile
588 Lut = _cmsReadDevicelinkLUT(hProfile, Intent);
589 if (Lut == NULL) goto Error;
591 // What about abstract profiles?
592 if (ClassSig == cmsSigAbstractClass && i > 0) {
593 if (!ComputeConversion(i, hProfiles, Intent, BPC[i], AdaptationStates[i], &m, &off)) goto Error;
596 _cmsMAT3identity(&m);
597 _cmsVEC3init(&off, 0, 0, 0);
601 if (!AddConversion(Result, CurrentColorSpace, ColorSpaceIn, &m, &off)) goto Error;
607 // Input direction means non-pcs connection, so proceed like devicelinks
608 Lut = _cmsReadInputLUT(hProfile, Intent);
609 if (Lut == NULL) goto Error;
613 // Output direction means PCS connection. Intent may apply here
614 Lut = _cmsReadOutputLUT(hProfile, Intent);
615 if (Lut == NULL) goto Error;
618 if (!ComputeConversion(i, hProfiles, Intent, BPC[i], AdaptationStates[i], &m, &off)) goto Error;
619 if (!AddConversion(Result, CurrentColorSpace, ColorSpaceIn, &m, &off)) goto Error;
624 // Concatenate to the output LUT
625 if (!cmsPipelineCat(Result, Lut))
628 cmsPipelineFree(Lut);
631 // Update current space
632 CurrentColorSpace = ColorSpaceOut;
639 if (Lut != NULL) cmsPipelineFree(Lut);
640 if (Result != NULL) cmsPipelineFree(Result);
643 cmsUNUSED_PARAMETER(dwFlags);
647 // Wrapper for DLL calling convention
648 cmsPipeline* CMSEXPORT _cmsDefaultICCintents(cmsContext ContextID,
649 cmsUInt32Number nProfiles,
650 cmsUInt32Number TheIntents[],
651 cmsHPROFILE hProfiles[],
653 cmsFloat64Number AdaptationStates[],
654 cmsUInt32Number dwFlags)
656 return DefaultICCintents(ContextID, nProfiles, TheIntents, hProfiles, BPC, AdaptationStates, dwFlags);
659 // Black preserving intents ---------------------------------------------------------------------------------------------
661 // Translate black-preserving intents to ICC ones
663 int TranslateNonICCIntents(int Intent)
666 case INTENT_PRESERVE_K_ONLY_PERCEPTUAL:
667 case INTENT_PRESERVE_K_PLANE_PERCEPTUAL:
668 return INTENT_PERCEPTUAL;
670 case INTENT_PRESERVE_K_ONLY_RELATIVE_COLORIMETRIC:
671 case INTENT_PRESERVE_K_PLANE_RELATIVE_COLORIMETRIC:
672 return INTENT_RELATIVE_COLORIMETRIC;
674 case INTENT_PRESERVE_K_ONLY_SATURATION:
675 case INTENT_PRESERVE_K_PLANE_SATURATION:
676 return INTENT_SATURATION;
678 default: return Intent;
682 // Sampler for Black-only preserving CMYK->CMYK transforms
685 cmsPipeline* cmyk2cmyk; // The original transform
686 cmsToneCurve* KTone; // Black-to-black tone curve
691 // Preserve black only if that is the only ink used
693 int BlackPreservingGrayOnlySampler(register const cmsUInt16Number In[], register cmsUInt16Number Out[], register void* Cargo)
695 GrayOnlyParams* bp = (GrayOnlyParams*) Cargo;
697 // If going across black only, keep black only
698 if (In[0] == 0 && In[1] == 0 && In[2] == 0) {
700 // TAC does not apply because it is black ink!
701 Out[0] = Out[1] = Out[2] = 0;
702 Out[3] = cmsEvalToneCurve16(bp->KTone, In[3]);
706 // Keep normal transform for other colors
707 bp ->cmyk2cmyk ->Eval16Fn(In, Out, bp ->cmyk2cmyk->Data);
711 // This is the entry for black-preserving K-only intents, which are non-ICC
713 cmsPipeline* BlackPreservingKOnlyIntents(cmsContext ContextID,
714 cmsUInt32Number nProfiles,
715 cmsUInt32Number TheIntents[],
716 cmsHPROFILE hProfiles[],
718 cmsFloat64Number AdaptationStates[],
719 cmsUInt32Number dwFlags)
723 cmsUInt32Number ICCIntents[256];
725 cmsUInt32Number i, nGridPoints;
729 if (nProfiles < 1 || nProfiles > 255) return NULL;
731 // Translate black-preserving intents to ICC ones
732 for (i=0; i < nProfiles; i++)
733 ICCIntents[i] = TranslateNonICCIntents(TheIntents[i]);
735 // Check for non-cmyk profiles
736 if (cmsGetColorSpace(hProfiles[0]) != cmsSigCmykData ||
737 cmsGetColorSpace(hProfiles[nProfiles-1]) != cmsSigCmykData)
738 return DefaultICCintents(ContextID, nProfiles, ICCIntents, hProfiles, BPC, AdaptationStates, dwFlags);
740 memset(&bp, 0, sizeof(bp));
742 // Allocate an empty LUT for holding the result
743 Result = cmsPipelineAlloc(ContextID, 4, 4);
744 if (Result == NULL) return NULL;
746 // Create a LUT holding normal ICC transform
747 bp.cmyk2cmyk = DefaultICCintents(ContextID,
755 if (bp.cmyk2cmyk == NULL) goto Error;
757 // Now, compute the tone curve
758 bp.KTone = _cmsBuildKToneCurve(ContextID,
767 if (bp.KTone == NULL) goto Error;
770 // How many gridpoints are we going to use?
771 nGridPoints = _cmsReasonableGridpointsByColorspace(cmsSigCmykData, dwFlags);
773 // Create the CLUT. 16 bits
774 CLUT = cmsStageAllocCLut16bit(ContextID, nGridPoints, 4, 4, NULL);
775 if (CLUT == NULL) goto Error;
777 // This is the one and only MPE in this LUT
778 if (!cmsPipelineInsertStage(Result, cmsAT_BEGIN, CLUT))
781 // Sample it. We cannot afford pre/post linearization this time.
782 if (!cmsStageSampleCLut16bit(CLUT, BlackPreservingGrayOnlySampler, (void*) &bp, 0))
785 // Get rid of xform and tone curve
786 cmsPipelineFree(bp.cmyk2cmyk);
787 cmsFreeToneCurve(bp.KTone);
793 if (bp.cmyk2cmyk != NULL) cmsPipelineFree(bp.cmyk2cmyk);
794 if (bp.KTone != NULL) cmsFreeToneCurve(bp.KTone);
795 if (Result != NULL) cmsPipelineFree(Result);
800 // K Plane-preserving CMYK to CMYK ------------------------------------------------------------------------------------
804 cmsPipeline* cmyk2cmyk; // The original transform
805 cmsHTRANSFORM hProofOutput; // Output CMYK to Lab (last profile)
806 cmsHTRANSFORM cmyk2Lab; // The input chain
807 cmsToneCurve* KTone; // Black-to-black tone curve
808 cmsPipeline* LabK2cmyk; // The output profile
809 cmsFloat64Number MaxError;
811 cmsHTRANSFORM hRoundTrip;
812 cmsFloat64Number MaxTAC;
815 } PreserveKPlaneParams;
818 // The CLUT will be stored at 16 bits, but calculations are performed at cmsFloat32Number precision
820 int BlackPreservingSampler(register const cmsUInt16Number In[], register cmsUInt16Number Out[], register void* Cargo)
823 cmsFloat32Number Inf[4], Outf[4];
824 cmsFloat32Number LabK[4];
825 cmsFloat64Number SumCMY, SumCMYK, Error, Ratio;
826 cmsCIELab ColorimetricLab, BlackPreservingLab;
827 PreserveKPlaneParams* bp = (PreserveKPlaneParams*) Cargo;
829 // Convert from 16 bits to floating point
830 for (i=0; i < 4; i++)
831 Inf[i] = (cmsFloat32Number) (In[i] / 65535.0);
833 // Get the K across Tone curve
834 LabK[3] = cmsEvalToneCurveFloat(bp ->KTone, Inf[3]);
836 // If going across black only, keep black only
837 if (In[0] == 0 && In[1] == 0 && In[2] == 0) {
839 Out[0] = Out[1] = Out[2] = 0;
840 Out[3] = _cmsQuickSaturateWord(LabK[3] * 65535.0);
844 // Try the original transform,
845 cmsPipelineEvalFloat( Inf, Outf, bp ->cmyk2cmyk);
847 // Store a copy of the floating point result into 16-bit
848 for (i=0; i < 4; i++)
849 Out[i] = _cmsQuickSaturateWord(Outf[i] * 65535.0);
851 // Maybe K is already ok (mostly on K=0)
852 if ( fabs(Outf[3] - LabK[3]) < (3.0 / 65535.0) ) {
856 // K differ, mesure and keep Lab measurement for further usage
857 // this is done in relative colorimetric intent
858 cmsDoTransform(bp->hProofOutput, Out, &ColorimetricLab, 1);
860 // Is not black only and the transform doesn't keep black.
861 // Obtain the Lab of output CMYK. After that we have Lab + K
862 cmsDoTransform(bp ->cmyk2Lab, Outf, LabK, 1);
864 // Obtain the corresponding CMY using reverse interpolation
865 // (K is fixed in LabK[3])
866 if (!cmsPipelineEvalReverseFloat(LabK, Outf, Outf, bp ->LabK2cmyk)) {
868 // Cannot find a suitable value, so use colorimetric xform
869 // which is already stored in Out[]
873 // Make sure to pass thru K (which now is fixed)
876 // Apply TAC if needed
877 SumCMY = Outf[0] + Outf[1] + Outf[2];
878 SumCMYK = SumCMY + Outf[3];
880 if (SumCMYK > bp ->MaxTAC) {
882 Ratio = 1 - ((SumCMYK - bp->MaxTAC) / SumCMY);
889 Out[0] = _cmsQuickSaturateWord(Outf[0] * Ratio * 65535.0); // C
890 Out[1] = _cmsQuickSaturateWord(Outf[1] * Ratio * 65535.0); // M
891 Out[2] = _cmsQuickSaturateWord(Outf[2] * Ratio * 65535.0); // Y
892 Out[3] = _cmsQuickSaturateWord(Outf[3] * 65535.0);
894 // Estimate the error (this goes 16 bits to Lab DBL)
895 cmsDoTransform(bp->hProofOutput, Out, &BlackPreservingLab, 1);
896 Error = cmsDeltaE(&ColorimetricLab, &BlackPreservingLab);
897 if (Error > bp -> MaxError)
898 bp->MaxError = Error;
903 // This is the entry for black-plane preserving, which are non-ICC
905 cmsPipeline* BlackPreservingKPlaneIntents(cmsContext ContextID,
906 cmsUInt32Number nProfiles,
907 cmsUInt32Number TheIntents[],
908 cmsHPROFILE hProfiles[],
910 cmsFloat64Number AdaptationStates[],
911 cmsUInt32Number dwFlags)
913 PreserveKPlaneParams bp;
914 cmsPipeline* Result = NULL;
915 cmsUInt32Number ICCIntents[256];
917 cmsUInt32Number i, nGridPoints;
921 if (nProfiles < 1 || nProfiles > 255) return NULL;
923 // Translate black-preserving intents to ICC ones
924 for (i=0; i < nProfiles; i++)
925 ICCIntents[i] = TranslateNonICCIntents(TheIntents[i]);
927 // Check for non-cmyk profiles
928 if (cmsGetColorSpace(hProfiles[0]) != cmsSigCmykData ||
929 !(cmsGetColorSpace(hProfiles[nProfiles-1]) == cmsSigCmykData ||
930 cmsGetDeviceClass(hProfiles[nProfiles-1]) == cmsSigOutputClass))
931 return DefaultICCintents(ContextID, nProfiles, ICCIntents, hProfiles, BPC, AdaptationStates, dwFlags);
933 // Allocate an empty LUT for holding the result
934 Result = cmsPipelineAlloc(ContextID, 4, 4);
935 if (Result == NULL) return NULL;
938 memset(&bp, 0, sizeof(bp));
940 // We need the input LUT of the last profile, assuming this one is responsible of
941 // black generation. This LUT will be seached in inverse order.
942 bp.LabK2cmyk = _cmsReadInputLUT(hProfiles[nProfiles-1], INTENT_RELATIVE_COLORIMETRIC);
943 if (bp.LabK2cmyk == NULL) goto Cleanup;
945 // Get total area coverage (in 0..1 domain)
946 bp.MaxTAC = cmsDetectTAC(hProfiles[nProfiles-1]) / 100.0;
947 if (bp.MaxTAC <= 0) goto Cleanup;
950 // Create a LUT holding normal ICC transform
951 bp.cmyk2cmyk = DefaultICCintents(ContextID,
958 if (bp.cmyk2cmyk == NULL) goto Cleanup;
960 // Now the tone curve
961 bp.KTone = _cmsBuildKToneCurve(ContextID, 4096, nProfiles,
967 if (bp.KTone == NULL) goto Cleanup;
969 // To measure the output, Last profile to Lab
970 hLab = cmsCreateLab4ProfileTHR(ContextID, NULL);
971 bp.hProofOutput = cmsCreateTransformTHR(ContextID, hProfiles[nProfiles-1],
972 CHANNELS_SH(4)|BYTES_SH(2), hLab, TYPE_Lab_DBL,
973 INTENT_RELATIVE_COLORIMETRIC,
974 cmsFLAGS_NOCACHE|cmsFLAGS_NOOPTIMIZE);
975 if ( bp.hProofOutput == NULL) goto Cleanup;
977 // Same as anterior, but lab in the 0..1 range
978 bp.cmyk2Lab = cmsCreateTransformTHR(ContextID, hProfiles[nProfiles-1],
979 FLOAT_SH(1)|CHANNELS_SH(4)|BYTES_SH(4), hLab,
980 FLOAT_SH(1)|CHANNELS_SH(3)|BYTES_SH(4),
981 INTENT_RELATIVE_COLORIMETRIC,
982 cmsFLAGS_NOCACHE|cmsFLAGS_NOOPTIMIZE);
983 if (bp.cmyk2Lab == NULL) goto Cleanup;
984 cmsCloseProfile(hLab);
986 // Error estimation (for debug only)
989 // How many gridpoints are we going to use?
990 nGridPoints = _cmsReasonableGridpointsByColorspace(cmsSigCmykData, dwFlags);
993 CLUT = cmsStageAllocCLut16bit(ContextID, nGridPoints, 4, 4, NULL);
994 if (CLUT == NULL) goto Cleanup;
996 if (!cmsPipelineInsertStage(Result, cmsAT_BEGIN, CLUT))
999 cmsStageSampleCLut16bit(CLUT, BlackPreservingSampler, (void*) &bp, 0);
1003 if (bp.cmyk2cmyk) cmsPipelineFree(bp.cmyk2cmyk);
1004 if (bp.cmyk2Lab) cmsDeleteTransform(bp.cmyk2Lab);
1005 if (bp.hProofOutput) cmsDeleteTransform(bp.hProofOutput);
1007 if (bp.KTone) cmsFreeToneCurve(bp.KTone);
1008 if (bp.LabK2cmyk) cmsPipelineFree(bp.LabK2cmyk);
1013 // Link routines ------------------------------------------------------------------------------------------------------
1015 // Chain several profiles into a single LUT. It just checks the parameters and then calls the handler
1016 // for the first intent in chain. The handler may be user-defined. Is up to the handler to deal with the
1017 // rest of intents in chain. A maximum of 255 profiles at time are supported, which is pretty reasonable.
1018 cmsPipeline* _cmsLinkProfiles(cmsContext ContextID,
1019 cmsUInt32Number nProfiles,
1020 cmsUInt32Number TheIntents[],
1021 cmsHPROFILE hProfiles[],
1023 cmsFloat64Number AdaptationStates[],
1024 cmsUInt32Number dwFlags)
1027 cmsIntentsList* Intent;
1029 // Make sure a reasonable number of profiles is provided
1030 if (nProfiles <= 0 || nProfiles > 255) {
1031 cmsSignalError(ContextID, cmsERROR_RANGE, "Couldn't link '%d' profiles", nProfiles);
1035 for (i=0; i < nProfiles; i++) {
1037 // Check if black point is really needed or allowed. Note that
1038 // following Adobe's document:
1039 // BPC does not apply to devicelink profiles, nor to abs colorimetric,
1040 // and applies always on V4 perceptual and saturation.
1042 if (TheIntents[i] == INTENT_ABSOLUTE_COLORIMETRIC)
1045 if (TheIntents[i] == INTENT_PERCEPTUAL || TheIntents[i] == INTENT_SATURATION) {
1047 // Force BPC for V4 profiles in perceptual and saturation
1048 if (cmsGetProfileVersion(hProfiles[i]) >= 4.0)
1053 // Search for a handler. The first intent in the chain defines the handler. That would
1054 // prevent using multiple custom intents in a multiintent chain, but the behaviour of
1055 // this case would present some issues if the custom intent tries to do things like
1056 // preserve primaries. This solution is not perfect, but works well on most cases.
1058 Intent = SearchIntent(ContextID, TheIntents[0]);
1059 if (Intent == NULL) {
1060 cmsSignalError(ContextID, cmsERROR_UNKNOWN_EXTENSION, "Unsupported intent '%d'", TheIntents[0]);
1065 return Intent ->Link(ContextID, nProfiles, TheIntents, hProfiles, BPC, AdaptationStates, dwFlags);
1068 // -------------------------------------------------------------------------------------------------
1070 // Get information about available intents. nMax is the maximum space for the supplied "Codes"
1071 // and "Descriptions" the function returns the total number of intents, which may be greater
1072 // than nMax, although the matrices are not populated beyond this level.
1073 cmsUInt32Number CMSEXPORT cmsGetSupportedIntentsTHR(cmsContext ContextID, cmsUInt32Number nMax, cmsUInt32Number* Codes, char** Descriptions)
1075 _cmsIntentsPluginChunkType* ctx = ( _cmsIntentsPluginChunkType*) _cmsContextGetClientChunk(ContextID, IntentPlugin);
1077 cmsUInt32Number nIntents;
1080 for (nIntents=0, pt = ctx->Intents; pt != NULL; pt = pt -> Next)
1082 if (nIntents < nMax) {
1084 Codes[nIntents] = pt ->Intent;
1086 if (Descriptions != NULL)
1087 Descriptions[nIntents] = pt ->Description;
1093 for (nIntents=0, pt = DefaultIntents; pt != NULL; pt = pt -> Next)
1095 if (nIntents < nMax) {
1097 Codes[nIntents] = pt ->Intent;
1099 if (Descriptions != NULL)
1100 Descriptions[nIntents] = pt ->Description;
1108 cmsUInt32Number CMSEXPORT cmsGetSupportedIntents(cmsUInt32Number nMax, cmsUInt32Number* Codes, char** Descriptions)
1110 return cmsGetSupportedIntentsTHR(NULL, nMax, Codes, Descriptions);
1113 // The plug-in registration. User can add new intents or override default routines
1114 cmsBool _cmsRegisterRenderingIntentPlugin(cmsContext id, cmsPluginBase* Data)
1116 _cmsIntentsPluginChunkType* ctx = ( _cmsIntentsPluginChunkType*) _cmsContextGetClientChunk(id, IntentPlugin);
1117 cmsPluginRenderingIntent* Plugin = (cmsPluginRenderingIntent*) Data;
1120 // Do we have to reset the custom intents?
1123 ctx->Intents = NULL;
1127 fl = (cmsIntentsList*) _cmsPluginMalloc(id, sizeof(cmsIntentsList));
1128 if (fl == NULL) return FALSE;
1131 fl ->Intent = Plugin ->Intent;
1132 strncpy(fl ->Description, Plugin ->Description, sizeof(fl ->Description)-1);
1133 fl ->Description[sizeof(fl ->Description)-1] = 0;
1135 fl ->Link = Plugin ->Link;
1137 fl ->Next = ctx ->Intents;