summaryrefslogtreecommitdiff
path: root/thirdparty/liblcms2/src/cmscnvrt.c
diff options
context:
space:
mode:
authorAntonin Descampe <antonin@gmail.com>2011-03-20 22:45:24 +0000
committerAntonin Descampe <antonin@gmail.com>2011-03-20 22:45:24 +0000
commit19f9147e1076d83dd1111609ca93a01085dbfb4f (patch)
tree8ba9fe2ac562b474f627c3ae8c90eefb7d0435a3 /thirdparty/liblcms2/src/cmscnvrt.c
parent6bda73eeb2134963f64c3d67fdd11c1304cb14f9 (diff)
Removed the libs directory containing win32 compiled versions of libpng, libtiff and liblcms. Added a thirdparty directory to include main source files of libtiff, libpng, libz and liblcms to enable support of these formats in the codec executables. CMake will try to statically build these libraries if they are not found on the system. Note that these third party libraries are not required to build libopenjpeg (which has no dependencies).
Diffstat (limited to 'thirdparty/liblcms2/src/cmscnvrt.c')
-rw-r--r--thirdparty/liblcms2/src/cmscnvrt.c1039
1 files changed, 1039 insertions, 0 deletions
diff --git a/thirdparty/liblcms2/src/cmscnvrt.c b/thirdparty/liblcms2/src/cmscnvrt.c
new file mode 100644
index 00000000..8dadc875
--- /dev/null
+++ b/thirdparty/liblcms2/src/cmscnvrt.c
@@ -0,0 +1,1039 @@
+//---------------------------------------------------------------------------------
+//
+// Little Color Management System
+// Copyright (c) 1998-2010 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
+// is furnished to do so, subject to the following conditions:
+//
+// 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
+// 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
+// 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,
+ cmsUInt32Number nProfiles,
+ 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.
+// Supported intents are perceptual, relative colorimetric, saturation and ICC-absolute colorimetric
+static
+cmsPipeline* DefaultICCintents(cmsContext ContextID,
+ cmsUInt32Number nProfiles,
+ 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,
+ cmsUInt32Number nProfiles,
+ 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,
+ cmsUInt32Number nProfiles,
+ cmsUInt32Number Intents[],
+ cmsHPROFILE hProfiles[],
+ cmsBool BPC[],
+ cmsFloat64Number AdaptationStates[],
+ cmsUInt32Number dwFlags);
+
+//---------------------------------------------------------------------------------
+
+
+// This is a structure holding implementations for all supported intents.
+typedef struct _cms_intents_list {
+
+ cmsUInt32Number Intent;
+ char Description[256];
+ cmsIntentFn Link;
+ struct _cms_intents_list* Next;
+
+} cmsIntentsList;
+
+
+// Built-in intents
+static cmsIntentsList DefaultIntents[] = {
+
+ { INTENT_PERCEPTUAL, "Perceptual", DefaultICCintents, &DefaultIntents[1] },
+ { INTENT_RELATIVE_COLORIMETRIC, "Relative colorimetric", DefaultICCintents, &DefaultIntents[2] },
+ { INTENT_SATURATION, "Saturation", DefaultICCintents, &DefaultIntents[3] },
+ { INTENT_ABSOLUTE_COLORIMETRIC, "Absolute colorimetric", DefaultICCintents, &DefaultIntents[4] },
+ { INTENT_PRESERVE_K_ONLY_PERCEPTUAL, "Perceptual preserving black ink", BlackPreservingKOnlyIntents, &DefaultIntents[5] },
+ { INTENT_PRESERVE_K_ONLY_RELATIVE_COLORIMETRIC, "Relative colorimetric preserving black ink", BlackPreservingKOnlyIntents, &DefaultIntents[6] },
+ { 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 }
+};
+
+
+// A pointer to the begining of the list
+static cmsIntentsList *Intents = DefaultIntents;
+
+// Search the list for a suitable intent. Returns NULL if not found
+static
+cmsIntentsList* SearchIntent(cmsUInt32Number Intent)
+{
+ cmsIntentsList* pt;
+
+ for (pt = Intents; 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
+// 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,
+ 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
+ //
+ // This is a linear scaling in the form ax+b, where
+ // a = (bpout - D50) / (bpin - D50)
+ // b = - D50* (bpout - bpin) / (bpin - D50)
+
+ tx = BlackPointIn->X - cmsD50_XYZ()->X;
+ ty = BlackPointIn->Y - cmsD50_XYZ()->Y;
+ tz = BlackPointIn->Z - cmsD50_XYZ()->Z;
+
+ ax = (BlackPointOut->X - cmsD50_XYZ()->X) / tx;
+ ay = (BlackPointOut->Y - cmsD50_XYZ()->Y) / ty;
+ az = (BlackPointOut->Z - cmsD50_XYZ()->Z) / tz;
+
+ bx = - cmsD50_XYZ()-> X * (BlackPointOut->X - BlackPointIn->X) / tx;
+ by = - cmsD50_XYZ()-> Y * (BlackPointOut->Y - BlackPointIn->Y) / ty;
+ bz = - cmsD50_XYZ()-> Z * (BlackPointOut->Z - BlackPointIn->Z) / tz;
+
+ _cmsVEC3init(&m ->v[0], ax, 0, 0);
+ _cmsVEC3init(&m ->v[1], 0, ay, 0);
+ _cmsVEC3init(&m ->v[2], 0, 0, az);
+ _cmsVEC3init(off, bx, by, bz);
+
+}
+
+
+// Approximate a blackbody illuminant based on CHAD information
+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;
+
+ s.n[VX] = cmsD50_XYZ() -> X;
+ s.n[VY] = cmsD50_XYZ() -> Y;
+ s.n[VZ] = cmsD50_XYZ() -> Z;
+
+ _cmsMAT3eval(&d, Chad, &s);
+
+ Dest.X = d.n[VX];
+ Dest.Y = d.n[VY];
+ Dest.Z = d.n[VZ];
+
+ cmsXYZ2xyY(&DestChromaticity, &Dest);
+
+ if (!cmsTempFromWhitePoint(&TempK, &DestChromaticity))
+ return -1.0;
+
+ return TempK;
+}
+
+// Compute a CHAD based on a given temperature
+static
+void Temp2CHAD(cmsMAT3* Chad, cmsFloat64Number Temp)
+{
+ cmsCIEXYZ White;
+ cmsCIExyY ChromaticityOfWhite;
+
+ cmsWhitePointFromTemp(&ChromaticityOfWhite, Temp);
+ cmsxyY2XYZ(&White, &ChromaticityOfWhite);
+ _cmsAdaptationMatrix(Chad, NULL, cmsD50_XYZ(), &White);
+
+}
+
+// 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 cmsMAT3* ChromaticAdaptationMatrixIn,
+ const cmsCIEXYZ* WhitePointOut,
+ const cmsMAT3* ChromaticAdaptationMatrixOut,
+ cmsMAT3* m)
+{
+ cmsMAT3 Scale, m1, m2, m3;
+
+ // Adaptation state
+ if (AdaptationState == 1.0) {
+
+ // 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(&m->v[2], 0, 0, WhitePointIn->Z / WhitePointOut->Z);
+
+ }
+ else {
+
+ // Incomplete adaptation. This is an advanced feature.
+ _cmsVEC3init(&Scale.v[0], WhitePointIn->X / WhitePointOut->X, 0, 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) {
+
+ // Observer is not adapted, undo the chromatic adaptation
+ _cmsMAT3per(m, &m3, ChromaticAdaptationMatrixOut);
+
+ } else {
+
+ cmsMAT3 MixedCHAD;
+ cmsFloat64Number TempSrc, TempDest, Temp;
+
+ TempSrc = CHAD2Temp(ChromaticAdaptationMatrixIn); // K for source white
+ TempDest = CHAD2Temp(ChromaticAdaptationMatrixOut); // K for dest white
+
+ if (TempSrc < 0.0 || TempDest < 0.0) return FALSE; // Something went wrong
+
+ if (_cmsMAT3isIdentity(&Scale) && fabs(TempSrc - TempDest) < 0.01) {
+
+ _cmsMAT3identity(m);
+ return TRUE;
+ }
+
+ Temp = AdaptationState * TempSrc + (1.0 - AdaptationState) * TempDest;
+
+ // Get a CHAD from D50 to whatever output temperature. This replaces output CHAD
+ Temp2CHAD(&MixedCHAD, Temp);
+
+ _cmsMAT3per(m, &m3, &MixedCHAD);
+ }
+
+ }
+ return TRUE;
+
+}
+
+// Just to see if m matrix should be applied
+static
+cmsBool IsEmptyLayer(cmsMAT3* m, cmsVEC3* off)
+{
+ 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]);
+
+ for (i=0; i < 3; i++)
+ diff += fabs(((cmsFloat64Number*)off)[i]);
+
+
+ return (diff < 0.002);
+}
+
+
+// Compute the conversion layer
+static
+cmsBool ComputeConversion(int i, cmsHPROFILE hProfiles[],
+ cmsUInt32Number Intent,
+ cmsBool BPC,
+ cmsFloat64Number AdaptationState,
+ cmsMAT3* m, cmsVEC3* off)
+{
+
+ int k;
+
+ // m and off are set to identity and this is detected latter on
+ _cmsMAT3identity(m);
+ _cmsVEC3init(off, 0, 0, 0);
+
+ // If intent is abs. colorimetric,
+ if (Intent == INTENT_ABSOLUTE_COLORIMETRIC) {
+
+ cmsCIEXYZ WhitePointIn, WhitePointOut;
+ 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,
+ &WhitePointOut, &ChromaticAdaptationMatrixOut, m)) return FALSE;
+
+ }
+ else {
+ // Rest of intents may apply BPC.
+
+ if (BPC) {
+
+ cmsCIEXYZ BlackPointIn, BlackPointOut;
+
+ cmsDetectBlackPoint(&BlackPointIn, hProfiles[i-1], Intent, 0);
+ cmsDetectBlackPoint(&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)
+ 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.
+ // 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)
+
+ for (k=0; k < 3; k++) {
+ off ->n[k] /= MAX_ENCODEABLE_XYZ;
+ }
+
+ return TRUE;
+}
+
+
+// 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)
+{
+ cmsFloat64Number* m_as_dbl = (cmsFloat64Number*) m;
+ cmsFloat64Number* off_as_dbl = (cmsFloat64Number*) off;
+
+ // Handle PCS mismatches. A specialized stage is added to the LUT in such case
+ switch (InPCS) {
+
+ case cmsSigXYZData: // Input profile operates in XYZ
+
+ 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 cmsSigLabData: // Input profile operates in Lab
+
+ switch (OutPCS) {
+
+ case cmsSigXYZData: // Lab -> XYZ
+
+ 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 cmsSigLabData: // Lab -> Lab
+
+ 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;
+
+ default:
+ return FALSE; // Mismatch
+ }
+ break;
+
+
+ // On colorspaces other than PCS, check for same space
+ default:
+ if (InPCS != OutPCS) return FALSE;
+ break;
+ }
+
+ return TRUE;
+}
+
+
+// Is a given space compatible with another?
+static
+cmsBool ColorSpaceIsCompatible(cmsColorSpaceSignature a, cmsColorSpaceSignature b)
+{
+ // If they are same, they are compatible.
+ if (a == b) 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;
+
+ return FALSE;
+}
+
+
+// Default handler for ICC-style intents
+static
+cmsPipeline* DefaultICCintents(cmsContext ContextID,
+ cmsUInt32Number nProfiles,
+ cmsUInt32Number TheIntents[],
+ cmsHPROFILE hProfiles[],
+ cmsBool BPC[],
+ cmsFloat64Number AdaptationStates[],
+ cmsUInt32Number dwFlags)
+{
+ cmsPipeline* Lut, *Result;
+ cmsHPROFILE hProfile;
+ cmsMAT3 m;
+ cmsVEC3 off;
+ cmsColorSpaceSignature ColorSpaceIn, ColorSpaceOut, CurrentColorSpace;
+ cmsProfileClassSignature ClassSig;
+ cmsUInt32Number i, Intent;
+
+ // 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]);
+
+ for (i=0; i < nProfiles; i++) {
+
+ cmsBool lIsDeviceLink, lIsInput;
+
+ 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
+ lIsInput = (CurrentColorSpace != cmsSigXYZData) &&
+ (CurrentColorSpace != cmsSigLabData);
+ }
+
+ Intent = TheIntents[i];
+
+ if (lIsInput || lIsDeviceLink) {
+
+ ColorSpaceIn = cmsGetColorSpace(hProfile);
+ ColorSpaceOut = cmsGetPCS(hProfile);
+ }
+ else {
+
+ ColorSpaceIn = cmsGetPCS(hProfile);
+ ColorSpaceOut = cmsGetColorSpace(hProfile);
+ }
+
+ if (!ColorSpaceIsCompatible(ColorSpaceIn, CurrentColorSpace)) {
+
+ cmsSignalError(ContextID, cmsERROR_COLORSPACE_CHECK, "ColorSpace mismatch");
+ 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) {
+
+ // Get the involved LUT from the profile
+ Lut = _cmsReadDevicelinkLUT(hProfile, Intent);
+ if (Lut == NULL) goto Error;
+
+ // What about abstract profiles?
+ if (ClassSig == cmsSigAbstractClass && i > 0) {
+ if (!ComputeConversion(i, hProfiles, Intent, BPC[i], AdaptationStates[i], &m, &off)) goto Error;
+ }
+ else {
+ _cmsMAT3identity(&m);
+ _cmsVEC3init(&off, 0, 0, 0);
+ }
+
+
+ if (!AddConversion(Result, CurrentColorSpace, ColorSpaceIn, &m, &off)) goto Error;
+
+ }
+ else {
+
+ if (lIsInput) {
+ // Input direction means non-pcs connection, so proceed like devicelinks
+ Lut = _cmsReadInputLUT(hProfile, Intent);
+ if (Lut == NULL) goto Error;
+ }
+ else {
+
+ // Output direction means PCS connection. Intent may apply here
+ Lut = _cmsReadOutputLUT(hProfile, Intent);
+ if (Lut == NULL) goto Error;
+
+
+ if (!ComputeConversion(i, hProfiles, Intent, BPC[i], AdaptationStates[i], &m, &off)) goto Error;
+ if (!AddConversion(Result, CurrentColorSpace, ColorSpaceIn, &m, &off)) goto Error;
+
+ }
+ }
+
+ // Concatenate to the output LUT
+ cmsPipelineCat(Result, Lut);
+ cmsPipelineFree(Lut);
+
+ // Update current space
+ CurrentColorSpace = ColorSpaceOut;
+ }
+
+ return Result;
+
+Error:
+
+ if (Result != NULL) cmsPipelineFree(Result);
+ return NULL;
+
+ cmsUNUSED_PARAMETER(dwFlags);
+}
+
+
+// Wrapper for DLL calling convention
+cmsPipeline* CMSEXPORT _cmsDefaultICCintents(cmsContext ContextID,
+ cmsUInt32Number nProfiles,
+ cmsUInt32Number TheIntents[],
+ cmsHPROFILE hProfiles[],
+ cmsBool BPC[],
+ cmsFloat64Number AdaptationStates[],
+ cmsUInt32Number dwFlags)
+{
+ return DefaultICCintents(ContextID, nProfiles, TheIntents, hProfiles, BPC, AdaptationStates, dwFlags);
+}
+
+// Black preserving intents ---------------------------------------------------------------------------------------------
+
+// Translate black-preserving intents to ICC ones
+static
+int TranslateNonICCIntents(int Intent)
+{
+ switch (Intent) {
+ case INTENT_PRESERVE_K_ONLY_PERCEPTUAL:
+ case INTENT_PRESERVE_K_PLANE_PERCEPTUAL:
+ return INTENT_PERCEPTUAL;
+
+ case INTENT_PRESERVE_K_ONLY_RELATIVE_COLORIMETRIC:
+ case INTENT_PRESERVE_K_PLANE_RELATIVE_COLORIMETRIC:
+ return INTENT_RELATIVE_COLORIMETRIC;
+
+ case INTENT_PRESERVE_K_ONLY_SATURATION:
+ case INTENT_PRESERVE_K_PLANE_SATURATION:
+ return INTENT_SATURATION;
+
+ default: return Intent;
+ }
+}
+
+// Sampler for Black-only preserving CMYK->CMYK transforms
+
+typedef struct {
+ cmsPipeline* cmyk2cmyk; // The original transform
+ cmsToneCurve* KTone; // Black-to-black tone curve
+
+} GrayOnlyParams;
+
+
+// Preserve black only if that is the only ink used
+static
+int BlackPreservingGrayOnlySampler(register const cmsUInt16Number In[], register cmsUInt16Number Out[], register void* Cargo)
+{
+ GrayOnlyParams* bp = (GrayOnlyParams*) Cargo;
+
+ // If going across black only, keep black only
+ if (In[0] == 0 && In[1] == 0 && In[2] == 0) {
+
+ // TAC does not apply because it is black ink!
+ Out[0] = Out[1] = Out[2] = 0;
+ Out[3] = cmsEvalToneCurve16(bp->KTone, In[3]);
+ return TRUE;
+ }
+
+ // Keep normal transform for other colors
+ bp ->cmyk2cmyk ->Eval16Fn(In, Out, bp ->cmyk2cmyk->Data);
+ return TRUE;
+}
+
+// This is the entry for black-preserving K-only intents, which are non-ICC
+static
+cmsPipeline* BlackPreservingKOnlyIntents(cmsContext ContextID,
+ cmsUInt32Number nProfiles,
+ cmsUInt32Number TheIntents[],
+ cmsHPROFILE hProfiles[],
+ cmsBool BPC[],
+ cmsFloat64Number AdaptationStates[],
+ cmsUInt32Number dwFlags)
+{
+ GrayOnlyParams bp;
+ cmsPipeline* Result;
+ cmsUInt32Number ICCIntents[256];
+ cmsStage* CLUT;
+ cmsUInt32Number i, nGridPoints;
+
+
+ // 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]);
+
+ // Check for non-cmyk profiles
+ if (cmsGetColorSpace(hProfiles[0]) != cmsSigCmykData ||
+ cmsGetColorSpace(hProfiles[nProfiles-1]) != cmsSigCmykData)
+ return DefaultICCintents(ContextID, nProfiles, ICCIntents, hProfiles, BPC, AdaptationStates, dwFlags);
+
+ memset(&bp, 0, sizeof(bp));
+
+ // Allocate an empty LUT for holding the result
+ Result = cmsPipelineAlloc(ContextID, 4, 4);
+ if (Result == NULL) return NULL;
+
+ // Create a LUT holding normal ICC transform
+ bp.cmyk2cmyk = DefaultICCintents(ContextID,
+ nProfiles,
+ ICCIntents,
+ hProfiles,
+ BPC,
+ AdaptationStates,
+ dwFlags);
+
+ if (bp.cmyk2cmyk == NULL) goto Error;
+
+ // Now, compute the tone curve
+ bp.KTone = _cmsBuildKToneCurve(ContextID,
+ 4096,
+ nProfiles,
+ 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);
+
+ // Sample it. We cannot afford pre/post linearization this time.
+ if (!cmsStageSampleCLut16bit(CLUT, BlackPreservingGrayOnlySampler, (void*) &bp, 0))
+ goto Error;
+
+ // Get rid of xform and tone curve
+ cmsPipelineFree(bp.cmyk2cmyk);
+ cmsFreeToneCurve(bp.KTone);
+
+ return Result;
+
+Error:
+
+ if (bp.cmyk2cmyk != NULL) cmsPipelineFree(bp.cmyk2cmyk);
+ if (bp.KTone != NULL) cmsFreeToneCurve(bp.KTone);
+ if (Result != NULL) cmsPipelineFree(Result);
+ return NULL;
+
+}
+
+// K Plane-preserving CMYK to CMYK ------------------------------------------------------------------------------------
+
+typedef struct {
+
+ cmsPipeline* cmyk2cmyk; // The original transform
+ cmsHTRANSFORM hProofOutput; // Output CMYK to Lab (last profile)
+ cmsHTRANSFORM cmyk2Lab; // The input chain
+ cmsToneCurve* KTone; // Black-to-black tone curve
+ cmsPipeline* LabK2cmyk; // The output profile
+ cmsFloat64Number MaxError;
+
+ cmsHTRANSFORM hRoundTrip;
+ cmsFloat64Number MaxTAC;
+
+
+} PreserveKPlaneParams;
+
+
+// The CLUT will be stored at 16 bits, but calculations are performed at cmsFloat32Number precision
+static
+int BlackPreservingSampler(register const cmsUInt16Number In[], register cmsUInt16Number Out[], register void* Cargo)
+{
+ int i;
+ cmsFloat32Number Inf[4], Outf[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++)
+ 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[0] = Out[1] = Out[2] = 0;
+ Out[3] = _cmsQuickSaturateWord(LabK[3] * 65535.0);
+ return TRUE;
+ }
+
+ // 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++)
+ 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
+ // (K is fixed in LabK[3])
+ if (!cmsPipelineEvalReverseFloat(LabK, Outf, Outf, bp ->LabK2cmyk)) {
+
+ // Cannot find a suitable value, so use colorimetric xform
+ // which is already stored in Out[]
+ return TRUE;
+ }
+
+ // Make sure to pass thru K (which now is fixed)
+ Outf[3] = LabK[3];
+
+ // Apply TAC if needed
+ SumCMY = Outf[0] + Outf[1] + Outf[2];
+ SumCMYK = SumCMY + Outf[3];
+
+ if (SumCMYK > bp ->MaxTAC) {
+
+ Ratio = 1 - ((SumCMYK - bp->MaxTAC) / SumCMY);
+ if (Ratio < 0)
+ Ratio = 0;
+ }
+ else
+ Ratio = 1.0;
+
+ Out[0] = _cmsQuickSaturateWord(Outf[0] * Ratio * 65535.0); // C
+ Out[1] = _cmsQuickSaturateWord(Outf[1] * Ratio * 65535.0); // M
+ Out[2] = _cmsQuickSaturateWord(Outf[2] * Ratio * 65535.0); // Y
+ 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)
+ bp->MaxError = Error;
+
+ return TRUE;
+}
+
+// This is the entry for black-plane preserving, which are non-ICC
+static
+cmsPipeline* BlackPreservingKPlaneIntents(cmsContext ContextID,
+ cmsUInt32Number nProfiles,
+ cmsUInt32Number TheIntents[],
+ cmsHPROFILE hProfiles[],
+ cmsBool BPC[],
+ cmsFloat64Number AdaptationStates[],
+ cmsUInt32Number dwFlags)
+{
+ PreserveKPlaneParams bp;
+ cmsPipeline* Result = NULL;
+ cmsUInt32Number ICCIntents[256];
+ cmsStage* CLUT;
+ 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]);
+
+ // Check for non-cmyk profiles
+ if (cmsGetColorSpace(hProfiles[0]) != cmsSigCmykData ||
+ cmsGetColorSpace(hProfiles[nProfiles-1]) != cmsSigCmykData)
+ 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
+ // black generation. This LUT will be seached in inverse order.
+ bp.LabK2cmyk = _cmsReadInputLUT(hProfiles[nProfiles-1], INTENT_RELATIVE_COLORIMETRIC);
+ if (bp.LabK2cmyk == NULL) goto Cleanup;
+
+ // Get total area coverage (in 0..1 domain)
+ bp.MaxTAC = cmsDetectTAC(hProfiles[nProfiles-1]) / 100.0;
+
+ // Create a LUT holding normal ICC transform
+ bp.cmyk2cmyk = DefaultICCintents(ContextID,
+ nProfiles,
+ ICCIntents,
+ hProfiles,
+ BPC,
+ AdaptationStates,
+ dwFlags);
+
+ // Now the tone curve
+ bp.KTone = _cmsBuildKToneCurve(ContextID, 4096, nProfiles,
+ ICCIntents,
+ hProfiles,
+ BPC,
+ AdaptationStates,
+ dwFlags);
+
+
+ // 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,
+ cmsFLAGS_NOCACHE|cmsFLAGS_NOOPTIMIZE);
+
+ // 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,
+ cmsFLAGS_NOCACHE|cmsFLAGS_NOOPTIMIZE);
+ cmsCloseProfile(hLab);
+
+ // Error estimation (for debug only)
+ bp.MaxError = 0;
+
+ // 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);
+
+ cmsStageSampleCLut16bit(CLUT, BlackPreservingSampler, (void*) &bp, 0);
+
+Cleanup:
+
+ if (bp.cmyk2cmyk) cmsPipelineFree(bp.cmyk2cmyk);
+ if (bp.cmyk2Lab) cmsDeleteTransform(bp.cmyk2Lab);
+ if (bp.hProofOutput) cmsDeleteTransform(bp.hProofOutput);
+
+ 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
+// rest of intents in chain. A maximum of 255 profiles at time are supported, which is pretty reasonable.
+cmsPipeline* _cmsLinkProfiles(cmsContext ContextID,
+ cmsUInt32Number nProfiles,
+ cmsUInt32Number TheIntents[],
+ cmsHPROFILE hProfiles[],
+ cmsBool BPC[],
+ cmsFloat64Number AdaptationStates[],
+ cmsUInt32Number dwFlags)
+{
+ cmsUInt32Number i;
+ cmsIntentsList* Intent;
+
+ // Make sure a reasonable number of profiles is provided
+ if (nProfiles <= 0 || nProfiles > 255) {
+ cmsSignalError(ContextID, cmsERROR_RANGE, "Couldn't link '%d' profiles", nProfiles);
+ return NULL;
+ }
+
+ for (i=0; i < nProfiles; i++) {
+
+ // 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,
+ // and applies always on V4 perceptual and saturation.
+
+ if (TheIntents[i] == INTENT_ABSOLUTE_COLORIMETRIC)
+ BPC[i] = FALSE;
+
+ if (TheIntents[i] == INTENT_PERCEPTUAL || TheIntents[i] == INTENT_SATURATION) {
+
+ // 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
+ // prevent using multiple custom intents in a multiintent chain, but the behaviour of
+ // 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;
+ }
+
+ // 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
+// than nMax, although the matrices are not populated beyond this level.
+cmsUInt32Number CMSEXPORT cmsGetSupportedIntents(cmsUInt32Number nMax, cmsUInt32Number* Codes, char** Descriptions)
+{
+ cmsIntentsList* pt;
+ cmsUInt32Number nIntents;
+
+ for (nIntents=0, pt = Intents; 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;
+}
+
+// The plug-in registration. User can add new intents or override default routines
+cmsBool _cmsRegisterRenderingIntentPlugin(cmsPluginBase* Data)
+{
+ cmsPluginRenderingIntent* Plugin = (cmsPluginRenderingIntent*) Data;
+ cmsIntentsList* fl;
+
+ // Do we have to reset the intents?
+ if (Data == NULL) {
+
+ Intents = DefaultIntents;
+ return TRUE;
+ }
+
+ fl = SearchIntent(Plugin ->Intent);
+
+ 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;
+
+ fl ->Link = Plugin ->Link;
+
+ fl ->Next = Intents;
+ Intents = fl;
+
+ return TRUE;
+}
+