//---------------------------------------------------------------------------------
//
// Little Color Management System
-// Copyright (c) 1998-2010 Marti Maria Saguer
+// Copyright (c) 1998-2012 Marti Maria Saguer
//
-// Permission is hereby granted, free of charge, to any person obtaining
-// a copy of this software and associated documentation files (the "Software"),
-// to deal in the Software without restriction, including without limitation
-// the rights to use, copy, modify, merge, publish, distribute, sublicense,
-// and/or sell copies of the Software, and to permit persons to whom the Software
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the "Software"),
+// to deal in the Software without restriction, including without limitation
+// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+// and/or sell copies of the Software, and to permit persons to whom the Software
// is furnished to do so, subject to the following conditions:
//
-// The above copyright notice and this permission notice shall be included in
+// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
-// THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+// THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
//---------------------------------------------------------------------------------
#include "lcms2_internal.h"
-// Link several profiles to obtain a single LUT modelling the whole color transform. Intents, Black point
+// Link several profiles to obtain a single LUT modelling the whole color transform. Intents, Black point
// compensation and Adaptation parameters may vary across profiles. BPC and Adaptation refers to the PCS
// after the profile. I.e, BPC[0] refers to connexion between profile(0) and profile(1)
-cmsPipeline* _cmsLinkProfiles(cmsContext ContextID,
+cmsPipeline* _cmsLinkProfiles(cmsContext ContextID,
cmsUInt32Number nProfiles,
- cmsUInt32Number Intents[],
- cmsHPROFILE hProfiles[],
+ cmsUInt32Number Intents[],
+ cmsHPROFILE hProfiles[],
cmsBool BPC[],
cmsFloat64Number AdaptationStates[],
cmsUInt32Number dwFlags);
-
+
//---------------------------------------------------------------------------------
-// This is the default routine for ICC-style intents. A user may decide to override it by using a plugin.
+// This is the default routine for ICC-style intents. A user may decide to override it by using a plugin.
// Supported intents are perceptual, relative colorimetric, saturation and ICC-absolute colorimetric
-static
-cmsPipeline* DefaultICCintents(cmsContext ContextID,
+static
+cmsPipeline* DefaultICCintents(cmsContext ContextID,
cmsUInt32Number nProfiles,
- cmsUInt32Number Intents[],
- cmsHPROFILE hProfiles[],
+ cmsUInt32Number Intents[],
+ cmsHPROFILE hProfiles[],
cmsBool BPC[],
cmsFloat64Number AdaptationStates[],
cmsUInt32Number dwFlags);
// This is the entry for black-preserving K-only intents, which are non-ICC. Last profile have to be a output profile
// to do the trick (no devicelinks allowed at that position)
static
-cmsPipeline* BlackPreservingKOnlyIntents(cmsContext ContextID,
+cmsPipeline* BlackPreservingKOnlyIntents(cmsContext ContextID,
cmsUInt32Number nProfiles,
- cmsUInt32Number Intents[],
- cmsHPROFILE hProfiles[],
+ cmsUInt32Number Intents[],
+ cmsHPROFILE hProfiles[],
cmsBool BPC[],
cmsFloat64Number AdaptationStates[],
cmsUInt32Number dwFlags);
// This is the entry for black-plane preserving, which are non-ICC. Again, Last profile have to be a output profile
// to do the trick (no devicelinks allowed at that position)
static
-cmsPipeline* BlackPreservingKPlaneIntents(cmsContext ContextID,
+cmsPipeline* BlackPreservingKPlaneIntents(cmsContext ContextID,
cmsUInt32Number nProfiles,
- cmsUInt32Number Intents[],
- cmsHPROFILE hProfiles[],
+ cmsUInt32Number Intents[],
+ cmsHPROFILE hProfiles[],
cmsBool BPC[],
cmsFloat64Number AdaptationStates[],
cmsUInt32Number dwFlags);
// Built-in intents
-static cmsIntentsList DefaultIntents[] = {
+static cmsIntentsList DefaultIntents[] = {
{ INTENT_PERCEPTUAL, "Perceptual", DefaultICCintents, &DefaultIntents[1] },
{ INTENT_RELATIVE_COLORIMETRIC, "Relative colorimetric", DefaultICCintents, &DefaultIntents[2] },
{ INTENT_PRESERVE_K_ONLY_SATURATION, "Saturation preserving black ink", BlackPreservingKOnlyIntents, &DefaultIntents[7] },
{ INTENT_PRESERVE_K_PLANE_PERCEPTUAL, "Perceptual preserving black plane", BlackPreservingKPlaneIntents, &DefaultIntents[8] },
{ INTENT_PRESERVE_K_PLANE_RELATIVE_COLORIMETRIC,"Relative colorimetric preserving black plane", BlackPreservingKPlaneIntents, &DefaultIntents[9] },
- { INTENT_PRESERVE_K_PLANE_SATURATION, "Saturation preserving black plane", BlackPreservingKPlaneIntents, NULL }
+ { INTENT_PRESERVE_K_PLANE_SATURATION, "Saturation preserving black plane", BlackPreservingKPlaneIntents, NULL }
};
// A pointer to the begining of the list
-static cmsIntentsList *Intents = DefaultIntents;
+_cmsIntentsPluginChunkType _cmsIntentsPluginChunk = { NULL };
+
+// Duplicates the zone of memory used by the plug-in in the new context
+static
+void DupPluginIntentsList(struct _cmsContext_struct* ctx,
+ const struct _cmsContext_struct* src)
+{
+ _cmsIntentsPluginChunkType newHead = { NULL };
+ cmsIntentsList* entry;
+ cmsIntentsList* Anterior = NULL;
+ _cmsIntentsPluginChunkType* head = (_cmsIntentsPluginChunkType*) src->chunks[IntentPlugin];
+
+ // Walk the list copying all nodes
+ for (entry = head->Intents;
+ entry != NULL;
+ entry = entry ->Next) {
+
+ cmsIntentsList *newEntry = ( cmsIntentsList *) _cmsSubAllocDup(ctx ->MemPool, entry, sizeof(cmsIntentsList));
+
+ if (newEntry == NULL)
+ return;
+
+ // We want to keep the linked list order, so this is a little bit tricky
+ newEntry -> Next = NULL;
+ if (Anterior)
+ Anterior -> Next = newEntry;
+
+ Anterior = newEntry;
+
+ if (newHead.Intents == NULL)
+ newHead.Intents = newEntry;
+ }
+
+ ctx ->chunks[IntentPlugin] = _cmsSubAllocDup(ctx->MemPool, &newHead, sizeof(_cmsIntentsPluginChunkType));
+}
+
+void _cmsAllocIntentsPluginChunk(struct _cmsContext_struct* ctx,
+ const struct _cmsContext_struct* src)
+{
+ if (src != NULL) {
+
+ // Copy all linked list
+ DupPluginIntentsList(ctx, src);
+ }
+ else {
+ static _cmsIntentsPluginChunkType IntentsPluginChunkType = { NULL };
+ ctx ->chunks[IntentPlugin] = _cmsSubAllocDup(ctx ->MemPool, &IntentsPluginChunkType, sizeof(_cmsIntentsPluginChunkType));
+ }
+}
+
// Search the list for a suitable intent. Returns NULL if not found
-static
-cmsIntentsList* SearchIntent(cmsUInt32Number Intent)
+static
+cmsIntentsList* SearchIntent(cmsContext ContextID, cmsUInt32Number Intent)
{
+ _cmsIntentsPluginChunkType* ctx = ( _cmsIntentsPluginChunkType*) _cmsContextGetClientChunk(ContextID, IntentPlugin);
cmsIntentsList* pt;
- for (pt = Intents; pt != NULL; pt = pt -> Next)
+ for (pt = ctx -> Intents; pt != NULL; pt = pt -> Next)
+ if (pt ->Intent == Intent) return pt;
+
+ for (pt = DefaultIntents; pt != NULL; pt = pt -> Next)
if (pt ->Intent == Intent) return pt;
return NULL;
}
-// Black point compensation. Implemented as a linear scaling in XYZ. Black points
+// Black point compensation. Implemented as a linear scaling in XYZ. Black points
// should come relative to the white point. Fills an matrix/offset element m
// which is organized as a 4x4 matrix.
static
-void ComputeBlackPointCompensation(const cmsCIEXYZ* BlackPointIn,
+void ComputeBlackPointCompensation(const cmsCIEXYZ* BlackPointIn,
const cmsCIEXYZ* BlackPointOut,
cmsMAT3* m, cmsVEC3* off)
-{
+{
cmsFloat64Number ax, ay, az, bx, by, bz, tx, ty, tz;
-
+
// Now we need to compute a matrix plus an offset m and of such of
// [m]*bpin + off = bpout
// [m]*D50 + off = D50
static
cmsFloat64Number CHAD2Temp(const cmsMAT3* Chad)
{
- // Convert D50 across CHAD to get the absolute white point
- cmsVEC3 d, s;
- cmsCIEXYZ Dest;
- cmsCIExyY DestChromaticity;
- cmsFloat64Number TempK;
+ // Convert D50 across inverse CHAD to get the absolute white point
+ cmsVEC3 d, s;
+ cmsCIEXYZ Dest;
+ cmsCIExyY DestChromaticity;
+ cmsFloat64Number TempK;
+ cmsMAT3 m1, m2;
+
+ m1 = *Chad;
+ if (!_cmsMAT3inverse(&m1, &m2)) return FALSE;
s.n[VX] = cmsD50_XYZ() -> X;
s.n[VY] = cmsD50_XYZ() -> Y;
s.n[VZ] = cmsD50_XYZ() -> Z;
- _cmsMAT3eval(&d, Chad, &s);
+ _cmsMAT3eval(&d, &m2, &s);
Dest.X = d.n[VX];
Dest.Y = d.n[VY];
// Compute a CHAD based on a given temperature
static
-void Temp2CHAD(cmsMAT3* Chad, cmsFloat64Number Temp)
+ void Temp2CHAD(cmsMAT3* Chad, cmsFloat64Number Temp)
{
cmsCIEXYZ White;
cmsCIExyY ChromaticityOfWhite;
-
- cmsWhitePointFromTemp(&ChromaticityOfWhite, Temp);
- cmsxyY2XYZ(&White, &ChromaticityOfWhite);
- _cmsAdaptationMatrix(Chad, NULL, cmsD50_XYZ(), &White);
+ cmsWhitePointFromTemp(&ChromaticityOfWhite, Temp);
+ cmsxyY2XYZ(&White, &ChromaticityOfWhite);
+ _cmsAdaptationMatrix(Chad, NULL, &White, cmsD50_XYZ());
}
// Join scalings to obtain relative input to absolute and then to relative output.
// Result is stored in a 3x3 matrix
static
cmsBool ComputeAbsoluteIntent(cmsFloat64Number AdaptationState,
- const cmsCIEXYZ* WhitePointIn,
+ const cmsCIEXYZ* WhitePointIn,
const cmsMAT3* ChromaticAdaptationMatrixIn,
const cmsCIEXYZ* WhitePointOut,
const cmsMAT3* ChromaticAdaptationMatrixOut,
cmsMAT3* m)
{
- cmsMAT3 Scale, m1, m2, m3;
+ cmsMAT3 Scale, m1, m2, m3, m4;
// Adaptation state
if (AdaptationState == 1.0) {
- // Observer is fully adapted. Keep chromatic adaptation.
+ // Observer is fully adapted. Keep chromatic adaptation.
// That is the standard V4 behaviour
_cmsVEC3init(&m->v[0], WhitePointIn->X / WhitePointOut->X, 0, 0);
_cmsVEC3init(&m->v[1], 0, WhitePointIn->Y / WhitePointOut->Y, 0);
_cmsVEC3init(&Scale.v[1], 0, WhitePointIn->Y / WhitePointOut->Y, 0);
_cmsVEC3init(&Scale.v[2], 0, 0, WhitePointIn->Z / WhitePointOut->Z);
- m1 = *ChromaticAdaptationMatrixIn;
- if (!_cmsMAT3inverse(&m1, &m2)) return FALSE;
- _cmsMAT3per(&m3, &m2, &Scale);
- // m3 holds CHAD from input white to D50 times abs. col. scaling
if (AdaptationState == 0.0) {
+ m1 = *ChromaticAdaptationMatrixOut;
+ _cmsMAT3per(&m2, &m1, &Scale);
+ // m2 holds CHAD from output white to D50 times abs. col. scaling
+
// Observer is not adapted, undo the chromatic adaptation
- _cmsMAT3per(m, &m3, ChromaticAdaptationMatrixOut);
+ _cmsMAT3per(m, &m2, ChromaticAdaptationMatrixOut);
+
+ m3 = *ChromaticAdaptationMatrixIn;
+ if (!_cmsMAT3inverse(&m3, &m4)) return FALSE;
+ _cmsMAT3per(m, &m2, &m4);
} else {
cmsMAT3 MixedCHAD;
cmsFloat64Number TempSrc, TempDest, Temp;
- TempSrc = CHAD2Temp(ChromaticAdaptationMatrixIn); // K for source white
- TempDest = CHAD2Temp(ChromaticAdaptationMatrixOut); // K for dest white
+ m1 = *ChromaticAdaptationMatrixIn;
+ if (!_cmsMAT3inverse(&m1, &m2)) return FALSE;
+ _cmsMAT3per(&m3, &m2, &Scale);
+ // m3 holds CHAD from input white to D50 times abs. col. scaling
+
+ TempSrc = CHAD2Temp(ChromaticAdaptationMatrixIn);
+ TempDest = CHAD2Temp(ChromaticAdaptationMatrixOut);
if (TempSrc < 0.0 || TempDest < 0.0) return FALSE; // Something went wrong
return TRUE;
}
- Temp = AdaptationState * TempSrc + (1.0 - AdaptationState) * TempDest;
+ Temp = (1.0 - AdaptationState) * TempDest + AdaptationState * TempSrc;
- // Get a CHAD from D50 to whatever output temperature. This replaces output CHAD
+ // Get a CHAD from whatever output temperature to D50. This replaces output CHAD
Temp2CHAD(&MixedCHAD, Temp);
_cmsMAT3per(m, &m3, &MixedCHAD);
cmsFloat64Number diff = 0;
cmsMAT3 Ident;
int i;
-
+
if (m == NULL && off == NULL) return TRUE; // NULL is allowed as an empty layer
if (m == NULL && off != NULL) return FALSE; // This is an internal error
_cmsMAT3identity(&Ident);
-
+
for (i=0; i < 3*3; i++)
diff += fabs(((cmsFloat64Number*)m)[i] - ((cmsFloat64Number*)&Ident)[i]);
// Compute the conversion layer
static
-cmsBool ComputeConversion(int i, cmsHPROFILE hProfiles[],
- cmsUInt32Number Intent,
- cmsBool BPC,
- cmsFloat64Number AdaptationState,
+cmsBool ComputeConversion(int i, cmsHPROFILE hProfiles[],
+ cmsUInt32Number Intent,
+ cmsBool BPC,
+ cmsFloat64Number AdaptationState,
cmsMAT3* m, cmsVEC3* off)
{
if (Intent == INTENT_ABSOLUTE_COLORIMETRIC) {
cmsCIEXYZ WhitePointIn, WhitePointOut;
- cmsMAT3 ChromaticAdaptationMatrixIn, ChromaticAdaptationMatrixOut;
+ cmsMAT3 ChromaticAdaptationMatrixIn, ChromaticAdaptationMatrixOut;
_cmsReadMediaWhitePoint(&WhitePointIn, hProfiles[i-1]);
_cmsReadCHAD(&ChromaticAdaptationMatrixIn, hProfiles[i-1]);
_cmsReadMediaWhitePoint(&WhitePointOut, hProfiles[i]);
_cmsReadCHAD(&ChromaticAdaptationMatrixOut, hProfiles[i]);
- if (!ComputeAbsoluteIntent(AdaptationState,
- &WhitePointIn, &ChromaticAdaptationMatrixIn,
+ if (!ComputeAbsoluteIntent(AdaptationState,
+ &WhitePointIn, &ChromaticAdaptationMatrixIn,
&WhitePointOut, &ChromaticAdaptationMatrixOut, m)) return FALSE;
}
cmsCIEXYZ BlackPointIn, BlackPointOut;
cmsDetectBlackPoint(&BlackPointIn, hProfiles[i-1], Intent, 0);
- cmsDetectBlackPoint(&BlackPointOut, hProfiles[i], Intent, 0);
+ cmsDetectDestinationBlackPoint(&BlackPointOut, hProfiles[i], Intent, 0);
// If black points are equal, then do nothing
if (BlackPointIn.X != BlackPointOut.X ||
BlackPointIn.Y != BlackPointOut.Y ||
- BlackPointIn.Z != BlackPointOut.Z)
+ BlackPointIn.Z != BlackPointOut.Z)
ComputeBlackPointCompensation(&BlackPointIn, &BlackPointOut, m, off);
}
}
// Offset should be adjusted because the encoding. We encode XYZ normalized to 0..1.0,
// to do that, we divide by MAX_ENCODEABLE_XZY. The conversion stage goes XYZ -> XYZ so
- // we have first to convert from encoded to XYZ and then convert back to encoded.
+ // we have first to convert from encoded to XYZ and then convert back to encoded.
// y = Mx + Off
// x = x'c
// y = M x'c + Off
// y = y'c; y' = y / c
- // y' = (Mx'c + Off) /c = Mx' + (Off / c)
+ // y' = (Mx'c + Off) /c = Mx' + (Off / c)
for (k=0; k < 3; k++) {
off ->n[k] /= MAX_ENCODEABLE_XYZ;
}
-// Add a conversion stage if needed. If a matrix/offset m is given, it applies to XYZ space
+// Add a conversion stage if needed. If a matrix/offset m is given, it applies to XYZ space
static
cmsBool AddConversion(cmsPipeline* Result, cmsColorSpaceSignature InPCS, cmsColorSpaceSignature OutPCS, cmsMAT3* m, cmsVEC3* off)
{
// Handle PCS mismatches. A specialized stage is added to the LUT in such case
switch (InPCS) {
- case cmsSigXYZData: // Input profile operates in XYZ
+ case cmsSigXYZData: // Input profile operates in XYZ
- switch (OutPCS) {
+ switch (OutPCS) {
- case cmsSigXYZData: // XYZ -> XYZ
- if (!IsEmptyLayer(m, off))
- cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl));
- break;
-
- case cmsSigLabData: // XYZ -> Lab
- if (!IsEmptyLayer(m, off))
- cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl));
- cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocXYZ2Lab(Result ->ContextID));
- break;
-
- default:
- return FALSE; // Colorspace mismatch
- }
- break;
+ case cmsSigXYZData: // XYZ -> XYZ
+ if (!IsEmptyLayer(m, off) &&
+ !cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl)))
+ return FALSE;
+ break;
+ case cmsSigLabData: // XYZ -> Lab
+ if (!IsEmptyLayer(m, off) &&
+ !cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl)))
+ return FALSE;
+ if (!cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocXYZ2Lab(Result ->ContextID)))
+ return FALSE;
+ break;
- case cmsSigLabData: // Input profile operates in Lab
+ default:
+ return FALSE; // Colorspace mismatch
+ }
+ break;
- switch (OutPCS) {
+ case cmsSigLabData: // Input profile operates in Lab
- case cmsSigXYZData: // Lab -> XYZ
+ switch (OutPCS) {
- cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocLab2XYZ(Result ->ContextID));
- if (!IsEmptyLayer(m, off))
- cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl));
- break;
+ case cmsSigXYZData: // Lab -> XYZ
- case cmsSigLabData: // Lab -> Lab
+ if (!cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocLab2XYZ(Result ->ContextID)))
+ return FALSE;
+ if (!IsEmptyLayer(m, off) &&
+ !cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl)))
+ return FALSE;
+ break;
- if (!IsEmptyLayer(m, off)) {
- cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocLab2XYZ(Result ->ContextID));
- cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl));
- cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocXYZ2Lab(Result ->ContextID));
- }
- break;
+ case cmsSigLabData: // Lab -> Lab
- default:
- return FALSE; // Mismatch
+ if (!IsEmptyLayer(m, off)) {
+ if (!cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocLab2XYZ(Result ->ContextID)) ||
+ !cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl)) ||
+ !cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocXYZ2Lab(Result ->ContextID)))
+ return FALSE;
}
break;
-
- // On colorspaces other than PCS, check for same space
default:
- if (InPCS != OutPCS) return FALSE;
- break;
+ return FALSE; // Mismatch
+ }
+ break;
+
+ // On colorspaces other than PCS, check for same space
+ default:
+ if (InPCS != OutPCS) return FALSE;
+ break;
}
return TRUE;
// If they are same, they are compatible.
if (a == b) return TRUE;
+ // Check for MCH4 substitution of CMYK
+ if ((a == cmsSig4colorData) && (b == cmsSigCmykData)) return TRUE;
+ if ((a == cmsSigCmykData) && (b == cmsSig4colorData)) return TRUE;
+
// Check for XYZ/Lab. Those spaces are interchangeable as they can be computed one from other.
if ((a == cmsSigXYZData) && (b == cmsSigLabData)) return TRUE;
if ((a == cmsSigLabData) && (b == cmsSigXYZData)) return TRUE;
// Default handler for ICC-style intents
static
-cmsPipeline* DefaultICCintents(cmsContext ContextID,
+cmsPipeline* DefaultICCintents(cmsContext ContextID,
cmsUInt32Number nProfiles,
- cmsUInt32Number TheIntents[],
- cmsHPROFILE hProfiles[],
+ cmsUInt32Number TheIntents[],
+ cmsHPROFILE hProfiles[],
cmsBool BPC[],
cmsFloat64Number AdaptationStates[],
cmsUInt32Number dwFlags)
{
- cmsPipeline* Lut, *Result;
+ cmsPipeline* Lut = NULL;
+ cmsPipeline* Result;
cmsHPROFILE hProfile;
cmsMAT3 m;
cmsVEC3 off;
- cmsColorSpaceSignature ColorSpaceIn, ColorSpaceOut, CurrentColorSpace;
+ cmsColorSpaceSignature ColorSpaceIn, ColorSpaceOut, CurrentColorSpace;
cmsProfileClassSignature ClassSig;
cmsUInt32Number i, Intent;
- // For safety
- if (nProfiles == 0) return NULL;
+ // For safety
+ if (nProfiles == 0) return NULL;
// Allocate an empty LUT for holding the result. 0 as channel count means 'undefined'
Result = cmsPipelineAlloc(ContextID, 0, 0);
if (Result == NULL) return NULL;
- CurrentColorSpace = cmsGetColorSpace(hProfiles[0]);
+ CurrentColorSpace = cmsGetColorSpace(hProfiles[0]);
for (i=0; i < nProfiles; i++) {
hProfile = hProfiles[i];
ClassSig = cmsGetDeviceClass(hProfile);
lIsDeviceLink = (ClassSig == cmsSigLinkClass || ClassSig == cmsSigAbstractClass );
-
+
// First profile is used as input unless devicelink or abstract
- if ((i == 0) && !lIsDeviceLink) {
- lIsInput = TRUE;
- }
- else {
- // Else use profile in the input direction if current space is not PCS
+ if ((i == 0) && !lIsDeviceLink) {
+ lIsInput = TRUE;
+ }
+ else {
+ // Else use profile in the input direction if current space is not PCS
lIsInput = (CurrentColorSpace != cmsSigXYZData) &&
(CurrentColorSpace != cmsSigLabData);
- }
+ }
Intent = TheIntents[i];
goto Error;
}
- // If devicelink is found, then no custom intent is allowed and we can
- // read the LUT to be applied. Settings don't apply here.
- if (lIsDeviceLink) {
+ // If devicelink is found, then no custom intent is allowed and we can
+ // read the LUT to be applied. Settings don't apply here.
+ if (lIsDeviceLink || ((ClassSig == cmsSigNamedColorClass) && (nProfiles == 1))) {
// Get the involved LUT from the profile
Lut = _cmsReadDevicelinkLUT(hProfile, Intent);
_cmsMAT3identity(&m);
_cmsVEC3init(&off, 0, 0, 0);
}
-
+
if (!AddConversion(Result, CurrentColorSpace, ColorSpaceIn, &m, &off)) goto Error;
if (lIsInput) {
// Input direction means non-pcs connection, so proceed like devicelinks
- Lut = _cmsReadInputLUT(hProfile, Intent);
+ Lut = _cmsReadInputLUT(hProfile, Intent);
if (Lut == NULL) goto Error;
}
else {
// Output direction means PCS connection. Intent may apply here
- Lut = _cmsReadOutputLUT(hProfile, Intent);
+ Lut = _cmsReadOutputLUT(hProfile, Intent);
if (Lut == NULL) goto Error;
}
// Concatenate to the output LUT
- cmsPipelineCat(Result, Lut);
- cmsPipelineFree(Lut);
+ if (!cmsPipelineCat(Result, Lut))
+ goto Error;
+
+ cmsPipelineFree(Lut);
+ Lut = NULL;
// Update current space
- CurrentColorSpace = ColorSpaceOut;
+ CurrentColorSpace = ColorSpaceOut;
}
return Result;
Error:
+ if (Lut != NULL) cmsPipelineFree(Lut);
if (Result != NULL) cmsPipelineFree(Result);
return NULL;
// Wrapper for DLL calling convention
-cmsPipeline* CMSEXPORT _cmsDefaultICCintents(cmsContext ContextID,
+cmsPipeline* CMSEXPORT _cmsDefaultICCintents(cmsContext ContextID,
cmsUInt32Number nProfiles,
- cmsUInt32Number TheIntents[],
- cmsHPROFILE hProfiles[],
+ cmsUInt32Number TheIntents[],
+ cmsHPROFILE hProfiles[],
cmsBool BPC[],
cmsFloat64Number AdaptationStates[],
cmsUInt32Number dwFlags)
switch (Intent) {
case INTENT_PRESERVE_K_ONLY_PERCEPTUAL:
case INTENT_PRESERVE_K_PLANE_PERCEPTUAL:
- return INTENT_PERCEPTUAL;
+ return INTENT_PERCEPTUAL;
case INTENT_PRESERVE_K_ONLY_RELATIVE_COLORIMETRIC:
case INTENT_PRESERVE_K_PLANE_RELATIVE_COLORIMETRIC:
// This is the entry for black-preserving K-only intents, which are non-ICC
static
-cmsPipeline* BlackPreservingKOnlyIntents(cmsContext ContextID,
+cmsPipeline* BlackPreservingKOnlyIntents(cmsContext ContextID,
cmsUInt32Number nProfiles,
- cmsUInt32Number TheIntents[],
- cmsHPROFILE hProfiles[],
+ cmsUInt32Number TheIntents[],
+ cmsHPROFILE hProfiles[],
cmsBool BPC[],
cmsFloat64Number AdaptationStates[],
cmsUInt32Number dwFlags)
if (nProfiles < 1 || nProfiles > 255) return NULL;
// Translate black-preserving intents to ICC ones
- for (i=0; i < nProfiles; i++)
- ICCIntents[i] = TranslateNonICCIntents(TheIntents[i]);
+ for (i=0; i < nProfiles; i++)
+ ICCIntents[i] = TranslateNonICCIntents(TheIntents[i]);
// Check for non-cmyk profiles
if (cmsGetColorSpace(hProfiles[0]) != cmsSigCmykData ||
- cmsGetColorSpace(hProfiles[nProfiles-1]) != cmsSigCmykData)
+ cmsGetColorSpace(hProfiles[nProfiles-1]) != cmsSigCmykData)
return DefaultICCintents(ContextID, nProfiles, ICCIntents, hProfiles, BPC, AdaptationStates, dwFlags);
memset(&bp, 0, sizeof(bp));
if (Result == NULL) return NULL;
// Create a LUT holding normal ICC transform
- bp.cmyk2cmyk = DefaultICCintents(ContextID,
+ bp.cmyk2cmyk = DefaultICCintents(ContextID,
nProfiles,
- ICCIntents,
- hProfiles,
+ ICCIntents,
+ hProfiles,
BPC,
AdaptationStates,
dwFlags);
if (bp.cmyk2cmyk == NULL) goto Error;
-
+
// Now, compute the tone curve
- bp.KTone = _cmsBuildKToneCurve(ContextID,
- 4096,
+ bp.KTone = _cmsBuildKToneCurve(ContextID,
+ 4096,
nProfiles,
- ICCIntents,
- hProfiles,
+ ICCIntents,
+ hProfiles,
BPC,
AdaptationStates,
dwFlags);
-
+
if (bp.KTone == NULL) goto Error;
-
+
// How many gridpoints are we going to use?
nGridPoints = _cmsReasonableGridpointsByColorspace(cmsSigCmykData, dwFlags);
-
+
// Create the CLUT. 16 bits
CLUT = cmsStageAllocCLut16bit(ContextID, nGridPoints, 4, 4, NULL);
if (CLUT == NULL) goto Error;
// This is the one and only MPE in this LUT
- cmsPipelineInsertStage(Result, cmsAT_BEGIN, CLUT);
+ if (!cmsPipelineInsertStage(Result, cmsAT_BEGIN, CLUT))
+ goto Error;
// Sample it. We cannot afford pre/post linearization this time.
- if (!cmsStageSampleCLut16bit(CLUT, BlackPreservingGrayOnlySampler, (void*) &bp, 0))
+ if (!cmsStageSampleCLut16bit(CLUT, BlackPreservingGrayOnlySampler, (void*) &bp, 0))
goto Error;
-
+
// Get rid of xform and tone curve
cmsPipelineFree(bp.cmyk2cmyk);
cmsFreeToneCurve(bp.KTone);
cmsPipeline* LabK2cmyk; // The output profile
cmsFloat64Number MaxError;
- cmsHTRANSFORM hRoundTrip;
+ cmsHTRANSFORM hRoundTrip;
cmsFloat64Number MaxTAC;
-
+
} PreserveKPlaneParams;
{
int i;
cmsFloat32Number Inf[4], Outf[4];
- cmsFloat32Number LabK[4];
+ cmsFloat32Number LabK[4];
cmsFloat64Number SumCMY, SumCMYK, Error, Ratio;
cmsCIELab ColorimetricLab, BlackPreservingLab;
PreserveKPlaneParams* bp = (PreserveKPlaneParams*) Cargo;
-
+
// Convert from 16 bits to floating point
- for (i=0; i < 4; i++)
+ for (i=0; i < 4; i++)
Inf[i] = (cmsFloat32Number) (In[i] / 65535.0);
// Get the K across Tone curve
LabK[3] = cmsEvalToneCurveFloat(bp ->KTone, Inf[3]);
-
+
// If going across black only, keep black only
if (In[0] == 0 && In[1] == 0 && In[2] == 0) {
Out[3] = _cmsQuickSaturateWord(LabK[3] * 65535.0);
return TRUE;
}
-
- // Try the original transform,
- cmsPipelineEvalFloat( Inf, Outf, bp ->cmyk2cmyk);
-
+
+ // Try the original transform,
+ cmsPipelineEvalFloat( Inf, Outf, bp ->cmyk2cmyk);
+
// Store a copy of the floating point result into 16-bit
- for (i=0; i < 4; i++)
+ for (i=0; i < 4; i++)
Out[i] = _cmsQuickSaturateWord(Outf[i] * 65535.0);
// Maybe K is already ok (mostly on K=0)
if ( fabs(Outf[3] - LabK[3]) < (3.0 / 65535.0) ) {
return TRUE;
}
-
+
// K differ, mesure and keep Lab measurement for further usage
// this is done in relative colorimetric intent
cmsDoTransform(bp->hProofOutput, Out, &ColorimetricLab, 1);
-
+
// Is not black only and the transform doesn't keep black.
// Obtain the Lab of output CMYK. After that we have Lab + K
cmsDoTransform(bp ->cmyk2Lab, Outf, LabK, 1);
- // Obtain the corresponding CMY using reverse interpolation
+ // Obtain the corresponding CMY using reverse interpolation
// (K is fixed in LabK[3])
if (!cmsPipelineEvalReverseFloat(LabK, Outf, Outf, bp ->LabK2cmyk)) {
// Make sure to pass thru K (which now is fixed)
Outf[3] = LabK[3];
-
- // Apply TAC if needed
+
+ // Apply TAC if needed
SumCMY = Outf[0] + Outf[1] + Outf[2];
- SumCMYK = SumCMY + Outf[3];
+ SumCMYK = SumCMY + Outf[3];
if (SumCMYK > bp ->MaxTAC) {
Out[3] = _cmsQuickSaturateWord(Outf[3] * 65535.0);
// Estimate the error (this goes 16 bits to Lab DBL)
- cmsDoTransform(bp->hProofOutput, Out, &BlackPreservingLab, 1);
- Error = cmsDeltaE(&ColorimetricLab, &BlackPreservingLab);
- if (Error > bp -> MaxError)
+ cmsDoTransform(bp->hProofOutput, Out, &BlackPreservingLab, 1);
+ Error = cmsDeltaE(&ColorimetricLab, &BlackPreservingLab);
+ if (Error > bp -> MaxError)
bp->MaxError = Error;
return TRUE;
// This is the entry for black-plane preserving, which are non-ICC
static
-cmsPipeline* BlackPreservingKPlaneIntents(cmsContext ContextID,
+cmsPipeline* BlackPreservingKPlaneIntents(cmsContext ContextID,
cmsUInt32Number nProfiles,
- cmsUInt32Number TheIntents[],
- cmsHPROFILE hProfiles[],
+ cmsUInt32Number TheIntents[],
+ cmsHPROFILE hProfiles[],
cmsBool BPC[],
cmsFloat64Number AdaptationStates[],
cmsUInt32Number dwFlags)
cmsPipeline* Result = NULL;
cmsUInt32Number ICCIntents[256];
cmsStage* CLUT;
- cmsUInt32Number i, nGridPoints;
+ cmsUInt32Number i, nGridPoints;
cmsHPROFILE hLab;
// Sanity check
if (nProfiles < 1 || nProfiles > 255) return NULL;
// Translate black-preserving intents to ICC ones
- for (i=0; i < nProfiles; i++)
- ICCIntents[i] = TranslateNonICCIntents(TheIntents[i]);
+ for (i=0; i < nProfiles; i++)
+ ICCIntents[i] = TranslateNonICCIntents(TheIntents[i]);
// Check for non-cmyk profiles
if (cmsGetColorSpace(hProfiles[0]) != cmsSigCmykData ||
- cmsGetColorSpace(hProfiles[nProfiles-1]) != cmsSigCmykData)
+ !(cmsGetColorSpace(hProfiles[nProfiles-1]) == cmsSigCmykData ||
+ cmsGetDeviceClass(hProfiles[nProfiles-1]) == cmsSigOutputClass))
return DefaultICCintents(ContextID, nProfiles, ICCIntents, hProfiles, BPC, AdaptationStates, dwFlags);
// Allocate an empty LUT for holding the result
Result = cmsPipelineAlloc(ContextID, 4, 4);
if (Result == NULL) return NULL;
-
+
memset(&bp, 0, sizeof(bp));
// We need the input LUT of the last profile, assuming this one is responsible of
// Get total area coverage (in 0..1 domain)
bp.MaxTAC = cmsDetectTAC(hProfiles[nProfiles-1]) / 100.0;
+ if (bp.MaxTAC <= 0) goto Cleanup;
+
// Create a LUT holding normal ICC transform
bp.cmyk2cmyk = DefaultICCintents(ContextID,
nProfiles,
- ICCIntents,
- hProfiles,
+ ICCIntents,
+ hProfiles,
BPC,
AdaptationStates,
dwFlags);
+ if (bp.cmyk2cmyk == NULL) goto Cleanup;
// Now the tone curve
bp.KTone = _cmsBuildKToneCurve(ContextID, 4096, nProfiles,
- ICCIntents,
- hProfiles,
- BPC,
+ ICCIntents,
+ hProfiles,
+ BPC,
AdaptationStates,
dwFlags);
-
+ if (bp.KTone == NULL) goto Cleanup;
// To measure the output, Last profile to Lab
hLab = cmsCreateLab4ProfileTHR(ContextID, NULL);
- bp.hProofOutput = cmsCreateTransformTHR(ContextID, hProfiles[nProfiles-1],
- CHANNELS_SH(4)|BYTES_SH(2), hLab, TYPE_Lab_DBL,
- INTENT_RELATIVE_COLORIMETRIC,
+ bp.hProofOutput = cmsCreateTransformTHR(ContextID, hProfiles[nProfiles-1],
+ CHANNELS_SH(4)|BYTES_SH(2), hLab, TYPE_Lab_DBL,
+ INTENT_RELATIVE_COLORIMETRIC,
cmsFLAGS_NOCACHE|cmsFLAGS_NOOPTIMIZE);
+ if ( bp.hProofOutput == NULL) goto Cleanup;
// Same as anterior, but lab in the 0..1 range
- bp.cmyk2Lab = cmsCreateTransformTHR(ContextID, hProfiles[nProfiles-1],
- FLOAT_SH(1)|CHANNELS_SH(4)|BYTES_SH(4), hLab,
- FLOAT_SH(1)|CHANNELS_SH(3)|BYTES_SH(4),
- INTENT_RELATIVE_COLORIMETRIC,
+ bp.cmyk2Lab = cmsCreateTransformTHR(ContextID, hProfiles[nProfiles-1],
+ FLOAT_SH(1)|CHANNELS_SH(4)|BYTES_SH(4), hLab,
+ FLOAT_SH(1)|CHANNELS_SH(3)|BYTES_SH(4),
+ INTENT_RELATIVE_COLORIMETRIC,
cmsFLAGS_NOCACHE|cmsFLAGS_NOOPTIMIZE);
+ if (bp.cmyk2Lab == NULL) goto Cleanup;
cmsCloseProfile(hLab);
// Error estimation (for debug only)
// How many gridpoints are we going to use?
nGridPoints = _cmsReasonableGridpointsByColorspace(cmsSigCmykData, dwFlags);
-
+
CLUT = cmsStageAllocCLut16bit(ContextID, nGridPoints, 4, 4, NULL);
if (CLUT == NULL) goto Cleanup;
- cmsPipelineInsertStage(Result, cmsAT_BEGIN, CLUT);
+ if (!cmsPipelineInsertStage(Result, cmsAT_BEGIN, CLUT))
+ goto Cleanup;
cmsStageSampleCLut16bit(CLUT, BlackPreservingSampler, (void*) &bp, 0);
Cleanup:
if (bp.cmyk2cmyk) cmsPipelineFree(bp.cmyk2cmyk);
- if (bp.cmyk2Lab) cmsDeleteTransform(bp.cmyk2Lab);
+ if (bp.cmyk2Lab) cmsDeleteTransform(bp.cmyk2Lab);
if (bp.hProofOutput) cmsDeleteTransform(bp.hProofOutput);
-
- if (bp.KTone) cmsFreeToneCurve(bp.KTone);
+
+ if (bp.KTone) cmsFreeToneCurve(bp.KTone);
if (bp.LabK2cmyk) cmsPipelineFree(bp.LabK2cmyk);
return Result;
// Link routines ------------------------------------------------------------------------------------------------------
// Chain several profiles into a single LUT. It just checks the parameters and then calls the handler
-// for the first intent in chain. The handler may be user-defined. Is up to the handler to deal with the
+// for the first intent in chain. The handler may be user-defined. Is up to the handler to deal with the
// rest of intents in chain. A maximum of 255 profiles at time are supported, which is pretty reasonable.
-cmsPipeline* _cmsLinkProfiles(cmsContext ContextID,
+cmsPipeline* _cmsLinkProfiles(cmsContext ContextID,
cmsUInt32Number nProfiles,
- cmsUInt32Number TheIntents[],
- cmsHPROFILE hProfiles[],
+ cmsUInt32Number TheIntents[],
+ cmsHPROFILE hProfiles[],
cmsBool BPC[],
cmsFloat64Number AdaptationStates[],
cmsUInt32Number dwFlags)
for (i=0; i < nProfiles; i++) {
- // Check if black point is really needed or allowed. Note that
+ // Check if black point is really needed or allowed. Note that
// following Adobe's document:
- // BPC does not apply to devicelink profiles, nor to abs colorimetric,
+ // BPC does not apply to devicelink profiles, nor to abs colorimetric,
// and applies always on V4 perceptual and saturation.
if (TheIntents[i] == INTENT_ABSOLUTE_COLORIMETRIC)
// Force BPC for V4 profiles in perceptual and saturation
if (cmsGetProfileVersion(hProfiles[i]) >= 4.0)
BPC[i] = TRUE;
- }
+ }
}
// Search for a handler. The first intent in the chain defines the handler. That would
// this case would present some issues if the custom intent tries to do things like
// preserve primaries. This solution is not perfect, but works well on most cases.
- Intent = SearchIntent(TheIntents[0]);
- if (Intent == NULL) {
- cmsSignalError(ContextID, cmsERROR_UNKNOWN_EXTENSION, "Unsupported intent '%d'", TheIntents[0]);
- return NULL;
- }
+ Intent = SearchIntent(ContextID, TheIntents[0]);
+ if (Intent == NULL) {
+ cmsSignalError(ContextID, cmsERROR_UNKNOWN_EXTENSION, "Unsupported intent '%d'", TheIntents[0]);
+ return NULL;
+ }
// Call the handler
return Intent ->Link(ContextID, nProfiles, TheIntents, hProfiles, BPC, AdaptationStates, dwFlags);
// -------------------------------------------------------------------------------------------------
-// Get information about available intents. nMax is the maximum space for the supplied "Codes"
-// and "Descriptions" the function returns the total number of intents, which may be greater
+// Get information about available intents. nMax is the maximum space for the supplied "Codes"
+// and "Descriptions" the function returns the total number of intents, which may be greater
// than nMax, although the matrices are not populated beyond this level.
-cmsUInt32Number CMSEXPORT cmsGetSupportedIntents(cmsUInt32Number nMax, cmsUInt32Number* Codes, char** Descriptions)
+cmsUInt32Number CMSEXPORT cmsGetSupportedIntentsTHR(cmsContext ContextID, cmsUInt32Number nMax, cmsUInt32Number* Codes, char** Descriptions)
{
+ _cmsIntentsPluginChunkType* ctx = ( _cmsIntentsPluginChunkType*) _cmsContextGetClientChunk(ContextID, IntentPlugin);
cmsIntentsList* pt;
cmsUInt32Number nIntents;
- for (nIntents=0, pt = Intents; pt != NULL; pt = pt -> Next)
+
+ for (nIntents=0, pt = ctx->Intents; pt != NULL; pt = pt -> Next)
{
if (nIntents < nMax) {
- if (Codes != NULL)
+ if (Codes != NULL)
Codes[nIntents] = pt ->Intent;
- if (Descriptions != NULL)
+ if (Descriptions != NULL)
Descriptions[nIntents] = pt ->Description;
}
nIntents++;
}
+ for (nIntents=0, pt = DefaultIntents; pt != NULL; pt = pt -> Next)
+ {
+ if (nIntents < nMax) {
+ if (Codes != NULL)
+ Codes[nIntents] = pt ->Intent;
+
+ if (Descriptions != NULL)
+ Descriptions[nIntents] = pt ->Description;
+ }
+
+ nIntents++;
+ }
return nIntents;
}
+cmsUInt32Number CMSEXPORT cmsGetSupportedIntents(cmsUInt32Number nMax, cmsUInt32Number* Codes, char** Descriptions)
+{
+ return cmsGetSupportedIntentsTHR(NULL, nMax, Codes, Descriptions);
+}
+
// The plug-in registration. User can add new intents or override default routines
-cmsBool _cmsRegisterRenderingIntentPlugin(cmsPluginBase* Data)
+cmsBool _cmsRegisterRenderingIntentPlugin(cmsContext id, cmsPluginBase* Data)
{
+ _cmsIntentsPluginChunkType* ctx = ( _cmsIntentsPluginChunkType*) _cmsContextGetClientChunk(id, IntentPlugin);
cmsPluginRenderingIntent* Plugin = (cmsPluginRenderingIntent*) Data;
cmsIntentsList* fl;
- // Do we have to reset the intents?
+ // Do we have to reset the custom intents?
if (Data == NULL) {
-
- Intents = DefaultIntents;
- return TRUE;
+
+ ctx->Intents = NULL;
+ return TRUE;
}
- fl = SearchIntent(Plugin ->Intent);
+ fl = (cmsIntentsList*) _cmsPluginMalloc(id, sizeof(cmsIntentsList));
+ if (fl == NULL) return FALSE;
- if (fl == NULL) {
- fl = (cmsIntentsList*) _cmsPluginMalloc(sizeof(cmsIntentsList));
- if (fl == NULL) return FALSE;
- }
fl ->Intent = Plugin ->Intent;
- strncpy(fl ->Description, Plugin ->Description, 255);
- fl ->Description[255] = 0;
+ strncpy(fl ->Description, Plugin ->Description, sizeof(fl ->Description)-1);
+ fl ->Description[sizeof(fl ->Description)-1] = 0;
fl ->Link = Plugin ->Link;
- fl ->Next = Intents;
- Intents = fl;
+ fl ->Next = ctx ->Intents;
+ ctx ->Intents = fl;
return TRUE;
}