-//---------------------------------------------------------------------------------
-//
-// 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"
-
-
-// Auxiliar: append a Lab identity after the given sequence of profiles
-// and return the transform. Lab profile is closed, rest of profiles are kept open.
-cmsHTRANSFORM _cmsChain2Lab(cmsContext ContextID,
- cmsUInt32Number nProfiles,
- cmsUInt32Number InputFormat,
- cmsUInt32Number OutputFormat,
- const cmsUInt32Number Intents[],
- const cmsHPROFILE hProfiles[],
- const cmsBool BPC[],
- const cmsFloat64Number AdaptationStates[],
- cmsUInt32Number dwFlags)
-{
- cmsHTRANSFORM xform;
- cmsHPROFILE hLab;
- cmsHPROFILE ProfileList[256];
- cmsBool BPCList[256];
- cmsFloat64Number AdaptationList[256];
- cmsUInt32Number IntentList[256];
- cmsUInt32Number i;
-
- // This is a rather big number and there is no need of dynamic memory
- // since we are adding a profile, 254 + 1 = 255 and this is the limit
- if (nProfiles > 254) return NULL;
-
- // The output space
- hLab = cmsCreateLab4ProfileTHR(ContextID, NULL);
- if (hLab == NULL) return NULL;
-
- // Create a copy of parameters
- for (i=0; i < nProfiles; i++) {
-
- ProfileList[i] = hProfiles[i];
- BPCList[i] = BPC[i];
- AdaptationList[i] = AdaptationStates[i];
- IntentList[i] = Intents[i];
- }
-
- // Place Lab identity at chain's end.
- ProfileList[nProfiles] = hLab;
- BPCList[nProfiles] = 0;
- AdaptationList[nProfiles] = 1.0;
- IntentList[nProfiles] = INTENT_RELATIVE_COLORIMETRIC;
-
- // Create the transform
- xform = cmsCreateExtendedTransform(ContextID, nProfiles + 1, ProfileList,
- BPCList,
- IntentList,
- AdaptationList,
- NULL, 0,
- InputFormat,
- OutputFormat,
- dwFlags);
-
- cmsCloseProfile(hLab);
-
- return xform;
-}
-
-
-// Compute K -> L* relationship. Flags may include black point compensation. In this case,
-// the relationship is assumed from the profile with BPC to a black point zero.
-static
-cmsToneCurve* ComputeKToLstar(cmsContext ContextID,
- cmsUInt32Number nPoints,
- cmsUInt32Number nProfiles,
- const cmsUInt32Number Intents[],
- const cmsHPROFILE hProfiles[],
- const cmsBool BPC[],
- const cmsFloat64Number AdaptationStates[],
- cmsUInt32Number dwFlags)
-{
- cmsToneCurve* out = NULL;
- cmsUInt32Number i;
- cmsHTRANSFORM xform;
- cmsCIELab Lab;
- cmsFloat32Number cmyk[4];
- cmsFloat32Number* SampledPoints;
-
- xform = _cmsChain2Lab(ContextID, nProfiles, TYPE_CMYK_FLT, TYPE_Lab_DBL, Intents, hProfiles, BPC, AdaptationStates, dwFlags);
- if (xform == NULL) return NULL;
-
- SampledPoints = (cmsFloat32Number*) _cmsCalloc(ContextID, nPoints, sizeof(cmsFloat32Number));
- if (SampledPoints == NULL) goto Error;
-
- for (i=0; i < nPoints; i++) {
-
- cmyk[0] = 0;
- cmyk[1] = 0;
- cmyk[2] = 0;
- cmyk[3] = (cmsFloat32Number) ((i * 100.0) / (nPoints-1));
-
- cmsDoTransform(xform, cmyk, &Lab, 1);
- SampledPoints[i]= (cmsFloat32Number) (1.0 - Lab.L / 100.0); // Negate K for easier operation
- }
-
- out = cmsBuildTabulatedToneCurveFloat(ContextID, nPoints, SampledPoints);
-
-Error:
-
- cmsDeleteTransform(xform);
- if (SampledPoints) _cmsFree(ContextID, SampledPoints);
-
- return out;
-}
-
-
-// Compute Black tone curve on a CMYK -> CMYK transform. This is done by
-// using the proof direction on both profiles to find K->L* relationship
-// then joining both curves. dwFlags may include black point compensation.
-cmsToneCurve* _cmsBuildKToneCurve(cmsContext ContextID,
- cmsUInt32Number nPoints,
- cmsUInt32Number nProfiles,
- const cmsUInt32Number Intents[],
- const cmsHPROFILE hProfiles[],
- const cmsBool BPC[],
- const cmsFloat64Number AdaptationStates[],
- cmsUInt32Number dwFlags)
-{
- cmsToneCurve *in, *out, *KTone;
-
- // Make sure CMYK -> CMYK
- if (cmsGetColorSpace(hProfiles[0]) != cmsSigCmykData ||
- cmsGetColorSpace(hProfiles[nProfiles-1])!= cmsSigCmykData) return NULL;
-
-
- // Make sure last is an output profile
- if (cmsGetDeviceClass(hProfiles[nProfiles - 1]) != cmsSigOutputClass) return NULL;
-
- // Create individual curves. BPC works also as each K to L* is
- // computed as a BPC to zero black point in case of L*
- in = ComputeKToLstar(ContextID, nPoints, nProfiles - 1, Intents, hProfiles, BPC, AdaptationStates, dwFlags);
- if (in == NULL) return NULL;
-
- out = ComputeKToLstar(ContextID, nPoints, 1,
- Intents + (nProfiles - 1),
- hProfiles + (nProfiles - 1),
- BPC + (nProfiles - 1),
- AdaptationStates + (nProfiles - 1),
- dwFlags);
- if (out == NULL) {
- cmsFreeToneCurve(in);
- return NULL;
- }
-
- // Build the relationship. This effectively limits the maximum accuracy to 16 bits, but
- // since this is used on black-preserving LUTs, we are not loosing accuracy in any case
- KTone = cmsJoinToneCurve(ContextID, in, out, nPoints);
-
- // Get rid of components
- cmsFreeToneCurve(in); cmsFreeToneCurve(out);
-
- // Something went wrong...
- if (KTone == NULL) return NULL;
-
- // Make sure it is monotonic
- if (!cmsIsToneCurveMonotonic(KTone)) {
- cmsFreeToneCurve(KTone);
- return NULL;
- }
-
- return KTone;
-}
-
-
-// Gamut LUT Creation -----------------------------------------------------------------------------------------
-
-// Used by gamut & softproofing
-
-typedef struct {
-
- cmsHTRANSFORM hInput; // From whatever input color space. 16 bits to DBL
- cmsHTRANSFORM hForward, hReverse; // Transforms going from Lab to colorant and back
- cmsFloat64Number Thereshold; // The thereshold after which is considered out of gamut
-
- } GAMUTCHAIN;
-
-// This sampler does compute gamut boundaries by comparing original
-// values with a transform going back and forth. Values above ERR_THERESHOLD
-// of maximum are considered out of gamut.
-
-#define ERR_THERESHOLD 5
-
-
-static
-int GamutSampler(register const cmsUInt16Number In[], register cmsUInt16Number Out[], register void* Cargo)
-{
- GAMUTCHAIN* t = (GAMUTCHAIN* ) Cargo;
- cmsCIELab LabIn1, LabOut1;
- cmsCIELab LabIn2, LabOut2;
- cmsUInt16Number Proof[cmsMAXCHANNELS], Proof2[cmsMAXCHANNELS];
- cmsFloat64Number dE1, dE2, ErrorRatio;
-
- // Assume in-gamut by default.
- dE1 = 0.;
- dE2 = 0;
- ErrorRatio = 1.0;
-
- // Convert input to Lab
- if (t -> hInput != NULL)
- cmsDoTransform(t -> hInput, In, &LabIn1, 1);
-
- // converts from PCS to colorant. This always
- // does return in-gamut values,
- cmsDoTransform(t -> hForward, &LabIn1, Proof, 1);
-
- // Now, do the inverse, from colorant to PCS.
- cmsDoTransform(t -> hReverse, Proof, &LabOut1, 1);
-
- memmove(&LabIn2, &LabOut1, sizeof(cmsCIELab));
-
- // Try again, but this time taking Check as input
- cmsDoTransform(t -> hForward, &LabOut1, Proof2, 1);
- cmsDoTransform(t -> hReverse, Proof2, &LabOut2, 1);
-
- // Take difference of direct value
- dE1 = cmsDeltaE(&LabIn1, &LabOut1);
-
- // Take difference of converted value
- dE2 = cmsDeltaE(&LabIn2, &LabOut2);
-
-
- // if dE1 is small and dE2 is small, value is likely to be in gamut
- if (dE1 < t->Thereshold && dE2 < t->Thereshold)
- Out[0] = 0;
- else {
-
- // if dE1 is small and dE2 is big, undefined. Assume in gamut
- if (dE1 < t->Thereshold && dE2 > t->Thereshold)
- Out[0] = 0;
- else
- // dE1 is big and dE2 is small, clearly out of gamut
- if (dE1 > t->Thereshold && dE2 < t->Thereshold)
- Out[0] = (cmsUInt16Number) _cmsQuickFloor((dE1 - t->Thereshold) + .5);
- else {
-
- // dE1 is big and dE2 is also big, could be due to perceptual mapping
- // so take error ratio
- if (dE2 == 0.0)
- ErrorRatio = dE1;
- else
- ErrorRatio = dE1 / dE2;
-
- if (ErrorRatio > t->Thereshold)
- Out[0] = (cmsUInt16Number) _cmsQuickFloor((ErrorRatio - t->Thereshold) + .5);
- else
- Out[0] = 0;
- }
- }
-
-
- return TRUE;
-}
-
-// Does compute a gamut LUT going back and forth across pcs -> relativ. colorimetric intent -> pcs
-// the dE obtained is then annotated on the LUT. Values truely out of gamut are clipped to dE = 0xFFFE
-// and values changed are supposed to be handled by any gamut remapping, so, are out of gamut as well.
-//
-// **WARNING: This algorithm does assume that gamut remapping algorithms does NOT move in-gamut colors,
-// of course, many perceptual and saturation intents does not work in such way, but relativ. ones should.
-
-cmsPipeline* _cmsCreateGamutCheckPipeline(cmsContext ContextID,
- cmsHPROFILE hProfiles[],
- cmsBool BPC[],
- cmsUInt32Number Intents[],
- cmsFloat64Number AdaptationStates[],
- cmsUInt32Number nGamutPCSposition,
- cmsHPROFILE hGamut)
-{
- cmsHPROFILE hLab;
- cmsPipeline* Gamut;
- cmsStage* CLUT;
- cmsUInt32Number dwFormat;
- GAMUTCHAIN Chain;
- int nChannels, nGridpoints;
- cmsColorSpaceSignature ColorSpace;
- cmsUInt32Number i;
- cmsHPROFILE ProfileList[256];
- cmsBool BPCList[256];
- cmsFloat64Number AdaptationList[256];
- cmsUInt32Number IntentList[256];
-
- memset(&Chain, 0, sizeof(GAMUTCHAIN));
-
-
- if (nGamutPCSposition <= 0 || nGamutPCSposition > 255) {
- cmsSignalError(ContextID, cmsERROR_RANGE, "Wrong position of PCS. 1..255 expected, %d found.", nGamutPCSposition);
- return NULL;
- }
-
- hLab = cmsCreateLab4ProfileTHR(ContextID, NULL);
- if (hLab == NULL) return NULL;
-
-
- // The figure of merit. On matrix-shaper profiles, should be almost zero as
- // the conversion is pretty exact. On LUT based profiles, different resolutions
- // of input and output CLUT may result in differences.
-
- if (cmsIsMatrixShaper(hGamut)) {
-
- Chain.Thereshold = 1.0;
- }
- else {
- Chain.Thereshold = ERR_THERESHOLD;
- }
-
-
- // Create a copy of parameters
- for (i=0; i < nGamutPCSposition; i++) {
- ProfileList[i] = hProfiles[i];
- BPCList[i] = BPC[i];
- AdaptationList[i] = AdaptationStates[i];
- IntentList[i] = Intents[i];
- }
-
- // Fill Lab identity
- ProfileList[nGamutPCSposition] = hLab;
- BPCList[nGamutPCSposition] = 0;
- AdaptationList[nGamutPCSposition] = 1.0;
- Intents[nGamutPCSposition] = INTENT_RELATIVE_COLORIMETRIC;
-
-
- ColorSpace = cmsGetColorSpace(hGamut);
-
- nChannels = cmsChannelsOf(ColorSpace);
- nGridpoints = _cmsReasonableGridpointsByColorspace(ColorSpace, cmsFLAGS_HIGHRESPRECALC);
- dwFormat = (CHANNELS_SH(nChannels)|BYTES_SH(2));
-
- // 16 bits to Lab double
- Chain.hInput = cmsCreateExtendedTransform(ContextID,
- nGamutPCSposition + 1,
- ProfileList,
- BPCList,
- Intents,
- AdaptationList,
- NULL, 0,
- dwFormat, TYPE_Lab_DBL,
- cmsFLAGS_NOCACHE);
-
-
- // Does create the forward step. Lab double to device
- dwFormat = (CHANNELS_SH(nChannels)|BYTES_SH(2));
- Chain.hForward = cmsCreateTransformTHR(ContextID,
- hLab, TYPE_Lab_DBL,
- hGamut, dwFormat,
- INTENT_RELATIVE_COLORIMETRIC,
- cmsFLAGS_NOCACHE);
-
- // Does create the backwards step
- Chain.hReverse = cmsCreateTransformTHR(ContextID, hGamut, dwFormat,
- hLab, TYPE_Lab_DBL,
- INTENT_RELATIVE_COLORIMETRIC,
- cmsFLAGS_NOCACHE);
-
-
- // All ok?
- if (Chain.hForward && Chain.hReverse) {
-
- // Go on, try to compute gamut LUT from PCS. This consist on a single channel containing
- // dE when doing a transform back and forth on the colorimetric intent.
-
- Gamut = cmsPipelineAlloc(ContextID, 3, 1);
-
- if (Gamut != NULL) {
-
- CLUT = cmsStageAllocCLut16bit(ContextID, nGridpoints, nChannels, 1, NULL);
- cmsPipelineInsertStage(Gamut, cmsAT_BEGIN, CLUT);
-
- cmsStageSampleCLut16bit(CLUT, GamutSampler, (void*) &Chain, 0);
- }
- }
- else
- Gamut = NULL; // Didn't work...
-
- // Free all needed stuff.
- if (Chain.hInput) cmsDeleteTransform(Chain.hInput);
- if (Chain.hForward) cmsDeleteTransform(Chain.hForward);
- if (Chain.hReverse) cmsDeleteTransform(Chain.hReverse);
- if (hLab) cmsCloseProfile(hLab);
-
- // And return computed hull
- return Gamut;
-}
-
-// Total Area Coverage estimation ----------------------------------------------------------------
-
-typedef struct {
- cmsUInt32Number nOutputChans;
- cmsHTRANSFORM hRoundTrip;
- cmsFloat32Number MaxTAC;
- cmsFloat32Number MaxInput[cmsMAXCHANNELS];
-
-} cmsTACestimator;
-
-
-// This callback just accounts the maximum ink dropped in the given node. It does not populate any
-// memory, as the destination table is NULL. Its only purpose it to know the global maximum.
-static
-int EstimateTAC(register const cmsUInt16Number In[], register cmsUInt16Number Out[], register void * Cargo)
-{
- cmsTACestimator* bp = (cmsTACestimator*) Cargo;
- cmsFloat32Number RoundTrip[cmsMAXCHANNELS];
- cmsUInt32Number i;
- cmsFloat32Number Sum;
-
-
- // Evaluate the xform
- cmsDoTransform(bp->hRoundTrip, In, RoundTrip, 1);
-
- // All all amounts of ink
- for (Sum=0, i=0; i < bp ->nOutputChans; i++)
- Sum += RoundTrip[i];
-
- // If above maximum, keep track of input values
- if (Sum > bp ->MaxTAC) {
-
- bp ->MaxTAC = Sum;
-
- for (i=0; i < bp ->nOutputChans; i++) {
- bp ->MaxInput[i] = In[i];
- }
- }
-
- return TRUE;
-
- cmsUNUSED_PARAMETER(Out);
-}
-
-
-// Detect Total area coverage of the profile
-cmsFloat64Number CMSEXPORT cmsDetectTAC(cmsHPROFILE hProfile)
-{
- cmsTACestimator bp;
- cmsUInt32Number dwFormatter;
- cmsUInt32Number GridPoints[MAX_INPUT_DIMENSIONS];
- cmsHPROFILE hLab;
- cmsContext ContextID = cmsGetProfileContextID(hProfile);
-
- // TAC only works on output profiles
- if (cmsGetDeviceClass(hProfile) != cmsSigOutputClass) {
- return 0;
- }
-
- // Create a fake formatter for result
- dwFormatter = cmsFormatterForColorspaceOfProfile(hProfile, 4, TRUE);
-
- bp.nOutputChans = T_CHANNELS(dwFormatter);
- bp.MaxTAC = 0; // Initial TAC is 0
-
- // for safety
- if (bp.nOutputChans >= cmsMAXCHANNELS) return 0;
-
- hLab = cmsCreateLab4ProfileTHR(ContextID, NULL);
- if (hLab == NULL) return 0;
- // Setup a roundtrip on perceptual intent in output profile for TAC estimation
- bp.hRoundTrip = cmsCreateTransformTHR(ContextID, hLab, TYPE_Lab_16,
- hProfile, dwFormatter, INTENT_PERCEPTUAL, cmsFLAGS_NOOPTIMIZE|cmsFLAGS_NOCACHE);
-
- cmsCloseProfile(hLab);
- if (bp.hRoundTrip == NULL) return 0;
-
- // For L* we only need black and white. For C* we need many points
- GridPoints[0] = 6;
- GridPoints[1] = 74;
- GridPoints[2] = 74;
-
-
- if (!cmsSliceSpace16(3, GridPoints, EstimateTAC, &bp)) {
- bp.MaxTAC = 0;
- }
-
- cmsDeleteTransform(bp.hRoundTrip);
-
- // Results in %
- return bp.MaxTAC;
-}
-
-
-// Carefully, clamp on CIELab space.
-
-cmsBool CMSEXPORT cmsDesaturateLab(cmsCIELab* Lab,
- double amax, double amin,
- double bmax, double bmin)
-{
-
- // Whole Luma surface to zero
-
- if (Lab -> L < 0) {
-
- Lab-> L = Lab->a = Lab-> b = 0.0;
- return FALSE;
- }
-
- // Clamp white, DISCARD HIGHLIGHTS. This is done
- // in such way because icc spec doesn't allow the
- // use of L>100 as a highlight means.
-
- if (Lab->L > 100)
- Lab -> L = 100;
-
- // Check out gamut prism, on a, b faces
-
- if (Lab -> a < amin || Lab->a > amax||
- Lab -> b < bmin || Lab->b > bmax) {
-
- cmsCIELCh LCh;
- double h, slope;
-
- // Falls outside a, b limits. Transports to LCh space,
- // and then do the clipping
-
-
- if (Lab -> a == 0.0) { // Is hue exactly 90?
-
- // atan will not work, so clamp here
- Lab -> b = Lab->b < 0 ? bmin : bmax;
- return TRUE;
- }
-
- cmsLab2LCh(&LCh, Lab);
-
- slope = Lab -> b / Lab -> a;
- h = LCh.h;
-
- // There are 4 zones
-
- if ((h >= 0. && h < 45.) ||
- (h >= 315 && h <= 360.)) {
-
- // clip by amax
- Lab -> a = amax;
- Lab -> b = amax * slope;
- }
- else
- if (h >= 45. && h < 135.)
- {
- // clip by bmax
- Lab -> b = bmax;
- Lab -> a = bmax / slope;
- }
- else
- if (h >= 135. && h < 225.) {
- // clip by amin
- Lab -> a = amin;
- Lab -> b = amin * slope;
-
- }
- else
- if (h >= 225. && h < 315.) {
- // clip by bmin
- Lab -> b = bmin;
- Lab -> a = bmin / slope;
- }
- else {
- cmsSignalError(0, cmsERROR_RANGE, "Invalid angle");
- return FALSE;
- }
-
- }
-
- return TRUE;
-}
+//---------------------------------------------------------------------------------\r
+//\r
+// Little Color Management System\r
+// Copyright (c) 1998-2012 Marti Maria Saguer\r
+//\r
+// Permission is hereby granted, free of charge, to any person obtaining\r
+// a copy of this software and associated documentation files (the "Software"),\r
+// to deal in the Software without restriction, including without limitation\r
+// the rights to use, copy, modify, merge, publish, distribute, sublicense,\r
+// and/or sell copies of the Software, and to permit persons to whom the Software\r
+// is furnished to do so, subject to the following conditions:\r
+//\r
+// The above copyright notice and this permission notice shall be included in\r
+// all copies or substantial portions of the Software.\r
+//\r
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,\r
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\r
+// THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\r
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\r
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\r
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\r
+//\r
+//---------------------------------------------------------------------------------\r
+//\r
+\r
+#include "lcms2_internal.h"\r
+\r
+\r
+// Auxiliar: append a Lab identity after the given sequence of profiles\r
+// and return the transform. Lab profile is closed, rest of profiles are kept open.\r
+cmsHTRANSFORM _cmsChain2Lab(cmsContext ContextID,\r
+ cmsUInt32Number nProfiles,\r
+ cmsUInt32Number InputFormat,\r
+ cmsUInt32Number OutputFormat,\r
+ const cmsUInt32Number Intents[],\r
+ const cmsHPROFILE hProfiles[],\r
+ const cmsBool BPC[],\r
+ const cmsFloat64Number AdaptationStates[],\r
+ cmsUInt32Number dwFlags)\r
+{\r
+ cmsHTRANSFORM xform;\r
+ cmsHPROFILE hLab;\r
+ cmsHPROFILE ProfileList[256];\r
+ cmsBool BPCList[256];\r
+ cmsFloat64Number AdaptationList[256];\r
+ cmsUInt32Number IntentList[256];\r
+ cmsUInt32Number i;\r
+\r
+ // This is a rather big number and there is no need of dynamic memory\r
+ // since we are adding a profile, 254 + 1 = 255 and this is the limit\r
+ if (nProfiles > 254) return NULL;\r
+\r
+ // The output space\r
+ hLab = cmsCreateLab4ProfileTHR(ContextID, NULL);\r
+ if (hLab == NULL) return NULL;\r
+\r
+ // Create a copy of parameters\r
+ for (i=0; i < nProfiles; i++) {\r
+\r
+ ProfileList[i] = hProfiles[i];\r
+ BPCList[i] = BPC[i];\r
+ AdaptationList[i] = AdaptationStates[i];\r
+ IntentList[i] = Intents[i];\r
+ }\r
+\r
+ // Place Lab identity at chain's end.\r
+ ProfileList[nProfiles] = hLab;\r
+ BPCList[nProfiles] = 0;\r
+ AdaptationList[nProfiles] = 1.0;\r
+ IntentList[nProfiles] = INTENT_RELATIVE_COLORIMETRIC;\r
+\r
+ // Create the transform\r
+ xform = cmsCreateExtendedTransform(ContextID, nProfiles + 1, ProfileList,\r
+ BPCList,\r
+ IntentList,\r
+ AdaptationList,\r
+ NULL, 0,\r
+ InputFormat,\r
+ OutputFormat,\r
+ dwFlags);\r
+\r
+ cmsCloseProfile(hLab);\r
+\r
+ return xform;\r
+}\r
+\r
+\r
+// Compute K -> L* relationship. Flags may include black point compensation. In this case,\r
+// the relationship is assumed from the profile with BPC to a black point zero.\r
+static\r
+cmsToneCurve* ComputeKToLstar(cmsContext ContextID,\r
+ cmsUInt32Number nPoints,\r
+ cmsUInt32Number nProfiles,\r
+ const cmsUInt32Number Intents[],\r
+ const cmsHPROFILE hProfiles[],\r
+ const cmsBool BPC[],\r
+ const cmsFloat64Number AdaptationStates[],\r
+ cmsUInt32Number dwFlags)\r
+{\r
+ cmsToneCurve* out = NULL;\r
+ cmsUInt32Number i;\r
+ cmsHTRANSFORM xform;\r
+ cmsCIELab Lab;\r
+ cmsFloat32Number cmyk[4];\r
+ cmsFloat32Number* SampledPoints;\r
+\r
+ xform = _cmsChain2Lab(ContextID, nProfiles, TYPE_CMYK_FLT, TYPE_Lab_DBL, Intents, hProfiles, BPC, AdaptationStates, dwFlags);\r
+ if (xform == NULL) return NULL;\r
+\r
+ SampledPoints = (cmsFloat32Number*) _cmsCalloc(ContextID, nPoints, sizeof(cmsFloat32Number));\r
+ if (SampledPoints == NULL) goto Error;\r
+\r
+ for (i=0; i < nPoints; i++) {\r
+\r
+ cmyk[0] = 0;\r
+ cmyk[1] = 0;\r
+ cmyk[2] = 0;\r
+ cmyk[3] = (cmsFloat32Number) ((i * 100.0) / (nPoints-1));\r
+\r
+ cmsDoTransform(xform, cmyk, &Lab, 1);\r
+ SampledPoints[i]= (cmsFloat32Number) (1.0 - Lab.L / 100.0); // Negate K for easier operation\r
+ }\r
+\r
+ out = cmsBuildTabulatedToneCurveFloat(ContextID, nPoints, SampledPoints);\r
+\r
+Error:\r
+\r
+ cmsDeleteTransform(xform);\r
+ if (SampledPoints) _cmsFree(ContextID, SampledPoints);\r
+\r
+ return out;\r
+}\r
+\r
+\r
+// Compute Black tone curve on a CMYK -> CMYK transform. This is done by\r
+// using the proof direction on both profiles to find K->L* relationship\r
+// then joining both curves. dwFlags may include black point compensation.\r
+cmsToneCurve* _cmsBuildKToneCurve(cmsContext ContextID,\r
+ cmsUInt32Number nPoints,\r
+ cmsUInt32Number nProfiles,\r
+ const cmsUInt32Number Intents[],\r
+ const cmsHPROFILE hProfiles[],\r
+ const cmsBool BPC[],\r
+ const cmsFloat64Number AdaptationStates[],\r
+ cmsUInt32Number dwFlags)\r
+{\r
+ cmsToneCurve *in, *out, *KTone;\r
+\r
+ // Make sure CMYK -> CMYK\r
+ if (cmsGetColorSpace(hProfiles[0]) != cmsSigCmykData ||\r
+ cmsGetColorSpace(hProfiles[nProfiles-1])!= cmsSigCmykData) return NULL;\r
+\r
+\r
+ // Make sure last is an output profile\r
+ if (cmsGetDeviceClass(hProfiles[nProfiles - 1]) != cmsSigOutputClass) return NULL;\r
+\r
+ // Create individual curves. BPC works also as each K to L* is\r
+ // computed as a BPC to zero black point in case of L*\r
+ in = ComputeKToLstar(ContextID, nPoints, nProfiles - 1, Intents, hProfiles, BPC, AdaptationStates, dwFlags);\r
+ if (in == NULL) return NULL;\r
+\r
+ out = ComputeKToLstar(ContextID, nPoints, 1,\r
+ Intents + (nProfiles - 1),\r
+ hProfiles + (nProfiles - 1),\r
+ BPC + (nProfiles - 1),\r
+ AdaptationStates + (nProfiles - 1),\r
+ dwFlags);\r
+ if (out == NULL) {\r
+ cmsFreeToneCurve(in);\r
+ return NULL;\r
+ }\r
+\r
+ // Build the relationship. This effectively limits the maximum accuracy to 16 bits, but\r
+ // since this is used on black-preserving LUTs, we are not loosing accuracy in any case\r
+ KTone = cmsJoinToneCurve(ContextID, in, out, nPoints);\r
+\r
+ // Get rid of components\r
+ cmsFreeToneCurve(in); cmsFreeToneCurve(out);\r
+\r
+ // Something went wrong...\r
+ if (KTone == NULL) return NULL;\r
+\r
+ // Make sure it is monotonic\r
+ if (!cmsIsToneCurveMonotonic(KTone)) {\r
+ cmsFreeToneCurve(KTone);\r
+ return NULL;\r
+ }\r
+\r
+ return KTone;\r
+}\r
+\r
+\r
+// Gamut LUT Creation -----------------------------------------------------------------------------------------\r
+\r
+// Used by gamut & softproofing\r
+\r
+typedef struct {\r
+\r
+ cmsHTRANSFORM hInput; // From whatever input color space. 16 bits to DBL\r
+ cmsHTRANSFORM hForward, hReverse; // Transforms going from Lab to colorant and back\r
+ cmsFloat64Number Thereshold; // The thereshold after which is considered out of gamut\r
+\r
+ } GAMUTCHAIN;\r
+\r
+// This sampler does compute gamut boundaries by comparing original\r
+// values with a transform going back and forth. Values above ERR_THERESHOLD\r
+// of maximum are considered out of gamut.\r
+\r
+#define ERR_THERESHOLD 5\r
+\r
+\r
+static\r
+int GamutSampler(register const cmsUInt16Number In[], register cmsUInt16Number Out[], register void* Cargo)\r
+{\r
+ GAMUTCHAIN* t = (GAMUTCHAIN* ) Cargo;\r
+ cmsCIELab LabIn1, LabOut1;\r
+ cmsCIELab LabIn2, LabOut2;\r
+ cmsUInt16Number Proof[cmsMAXCHANNELS], Proof2[cmsMAXCHANNELS];\r
+ cmsFloat64Number dE1, dE2, ErrorRatio;\r
+\r
+ // Assume in-gamut by default.\r
+ dE1 = 0.;\r
+ dE2 = 0;\r
+ ErrorRatio = 1.0;\r
+\r
+ // Convert input to Lab\r
+ if (t -> hInput != NULL)\r
+ cmsDoTransform(t -> hInput, In, &LabIn1, 1);\r
+\r
+ // converts from PCS to colorant. This always\r
+ // does return in-gamut values,\r
+ cmsDoTransform(t -> hForward, &LabIn1, Proof, 1);\r
+\r
+ // Now, do the inverse, from colorant to PCS.\r
+ cmsDoTransform(t -> hReverse, Proof, &LabOut1, 1);\r
+\r
+ memmove(&LabIn2, &LabOut1, sizeof(cmsCIELab));\r
+\r
+ // Try again, but this time taking Check as input\r
+ cmsDoTransform(t -> hForward, &LabOut1, Proof2, 1);\r
+ cmsDoTransform(t -> hReverse, Proof2, &LabOut2, 1);\r
+\r
+ // Take difference of direct value\r
+ dE1 = cmsDeltaE(&LabIn1, &LabOut1);\r
+\r
+ // Take difference of converted value\r
+ dE2 = cmsDeltaE(&LabIn2, &LabOut2);\r
+\r
+\r
+ // if dE1 is small and dE2 is small, value is likely to be in gamut\r
+ if (dE1 < t->Thereshold && dE2 < t->Thereshold)\r
+ Out[0] = 0;\r
+ else {\r
+\r
+ // if dE1 is small and dE2 is big, undefined. Assume in gamut\r
+ if (dE1 < t->Thereshold && dE2 > t->Thereshold)\r
+ Out[0] = 0;\r
+ else\r
+ // dE1 is big and dE2 is small, clearly out of gamut\r
+ if (dE1 > t->Thereshold && dE2 < t->Thereshold)\r
+ Out[0] = (cmsUInt16Number) _cmsQuickFloor((dE1 - t->Thereshold) + .5);\r
+ else {\r
+\r
+ // dE1 is big and dE2 is also big, could be due to perceptual mapping\r
+ // so take error ratio\r
+ if (dE2 == 0.0)\r
+ ErrorRatio = dE1;\r
+ else\r
+ ErrorRatio = dE1 / dE2;\r
+\r
+ if (ErrorRatio > t->Thereshold)\r
+ Out[0] = (cmsUInt16Number) _cmsQuickFloor((ErrorRatio - t->Thereshold) + .5);\r
+ else\r
+ Out[0] = 0;\r
+ }\r
+ }\r
+\r
+\r
+ return TRUE;\r
+}\r
+\r
+// Does compute a gamut LUT going back and forth across pcs -> relativ. colorimetric intent -> pcs\r
+// the dE obtained is then annotated on the LUT. Values truely out of gamut are clipped to dE = 0xFFFE\r
+// and values changed are supposed to be handled by any gamut remapping, so, are out of gamut as well.\r
+//\r
+// **WARNING: This algorithm does assume that gamut remapping algorithms does NOT move in-gamut colors,\r
+// of course, many perceptual and saturation intents does not work in such way, but relativ. ones should.\r
+\r
+cmsPipeline* _cmsCreateGamutCheckPipeline(cmsContext ContextID,\r
+ cmsHPROFILE hProfiles[],\r
+ cmsBool BPC[],\r
+ cmsUInt32Number Intents[],\r
+ cmsFloat64Number AdaptationStates[],\r
+ cmsUInt32Number nGamutPCSposition,\r
+ cmsHPROFILE hGamut)\r
+{\r
+ cmsHPROFILE hLab;\r
+ cmsPipeline* Gamut;\r
+ cmsStage* CLUT;\r
+ cmsUInt32Number dwFormat;\r
+ GAMUTCHAIN Chain;\r
+ int nChannels, nGridpoints;\r
+ cmsColorSpaceSignature ColorSpace;\r
+ cmsUInt32Number i;\r
+ cmsHPROFILE ProfileList[256];\r
+ cmsBool BPCList[256];\r
+ cmsFloat64Number AdaptationList[256];\r
+ cmsUInt32Number IntentList[256];\r
+\r
+ memset(&Chain, 0, sizeof(GAMUTCHAIN));\r
+\r
+\r
+ if (nGamutPCSposition <= 0 || nGamutPCSposition > 255) {\r
+ cmsSignalError(ContextID, cmsERROR_RANGE, "Wrong position of PCS. 1..255 expected, %d found.", nGamutPCSposition);\r
+ return NULL;\r
+ }\r
+\r
+ hLab = cmsCreateLab4ProfileTHR(ContextID, NULL);\r
+ if (hLab == NULL) return NULL;\r
+\r
+\r
+ // The figure of merit. On matrix-shaper profiles, should be almost zero as\r
+ // the conversion is pretty exact. On LUT based profiles, different resolutions\r
+ // of input and output CLUT may result in differences.\r
+\r
+ if (cmsIsMatrixShaper(hGamut)) {\r
+\r
+ Chain.Thereshold = 1.0;\r
+ }\r
+ else {\r
+ Chain.Thereshold = ERR_THERESHOLD;\r
+ }\r
+\r
+\r
+ // Create a copy of parameters\r
+ for (i=0; i < nGamutPCSposition; i++) {\r
+ ProfileList[i] = hProfiles[i];\r
+ BPCList[i] = BPC[i];\r
+ AdaptationList[i] = AdaptationStates[i];\r
+ IntentList[i] = Intents[i];\r
+ }\r
+\r
+ // Fill Lab identity\r
+ ProfileList[nGamutPCSposition] = hLab;\r
+ BPCList[nGamutPCSposition] = 0;\r
+ AdaptationList[nGamutPCSposition] = 1.0;\r
+ Intents[nGamutPCSposition] = INTENT_RELATIVE_COLORIMETRIC;\r
+\r
+\r
+ ColorSpace = cmsGetColorSpace(hGamut);\r
+\r
+ nChannels = cmsChannelsOf(ColorSpace);\r
+ nGridpoints = _cmsReasonableGridpointsByColorspace(ColorSpace, cmsFLAGS_HIGHRESPRECALC);\r
+ dwFormat = (CHANNELS_SH(nChannels)|BYTES_SH(2));\r
+\r
+ // 16 bits to Lab double\r
+ Chain.hInput = cmsCreateExtendedTransform(ContextID,\r
+ nGamutPCSposition + 1,\r
+ ProfileList,\r
+ BPCList,\r
+ Intents,\r
+ AdaptationList,\r
+ NULL, 0,\r
+ dwFormat, TYPE_Lab_DBL,\r
+ cmsFLAGS_NOCACHE);\r
+\r
+\r
+ // Does create the forward step. Lab double to device\r
+ dwFormat = (CHANNELS_SH(nChannels)|BYTES_SH(2));\r
+ Chain.hForward = cmsCreateTransformTHR(ContextID,\r
+ hLab, TYPE_Lab_DBL,\r
+ hGamut, dwFormat,\r
+ INTENT_RELATIVE_COLORIMETRIC,\r
+ cmsFLAGS_NOCACHE);\r
+\r
+ // Does create the backwards step\r
+ Chain.hReverse = cmsCreateTransformTHR(ContextID, hGamut, dwFormat,\r
+ hLab, TYPE_Lab_DBL,\r
+ INTENT_RELATIVE_COLORIMETRIC,\r
+ cmsFLAGS_NOCACHE);\r
+\r
+\r
+ // All ok?\r
+ if (Chain.hForward && Chain.hReverse) {\r
+\r
+ // Go on, try to compute gamut LUT from PCS. This consist on a single channel containing\r
+ // dE when doing a transform back and forth on the colorimetric intent.\r
+\r
+ Gamut = cmsPipelineAlloc(ContextID, 3, 1);\r
+\r
+ if (Gamut != NULL) {\r
+\r
+ CLUT = cmsStageAllocCLut16bit(ContextID, nGridpoints, nChannels, 1, NULL);\r
+ cmsPipelineInsertStage(Gamut, cmsAT_BEGIN, CLUT);\r
+\r
+ cmsStageSampleCLut16bit(CLUT, GamutSampler, (void*) &Chain, 0);\r
+ }\r
+ }\r
+ else\r
+ Gamut = NULL; // Didn't work...\r
+\r
+ // Free all needed stuff.\r
+ if (Chain.hInput) cmsDeleteTransform(Chain.hInput);\r
+ if (Chain.hForward) cmsDeleteTransform(Chain.hForward);\r
+ if (Chain.hReverse) cmsDeleteTransform(Chain.hReverse);\r
+ if (hLab) cmsCloseProfile(hLab);\r
+\r
+ // And return computed hull\r
+ return Gamut;\r
+}\r
+\r
+// Total Area Coverage estimation ----------------------------------------------------------------\r
+\r
+typedef struct {\r
+ cmsUInt32Number nOutputChans;\r
+ cmsHTRANSFORM hRoundTrip;\r
+ cmsFloat32Number MaxTAC;\r
+ cmsFloat32Number MaxInput[cmsMAXCHANNELS];\r
+\r
+} cmsTACestimator;\r
+\r
+\r
+// This callback just accounts the maximum ink dropped in the given node. It does not populate any\r
+// memory, as the destination table is NULL. Its only purpose it to know the global maximum.\r
+static\r
+int EstimateTAC(register const cmsUInt16Number In[], register cmsUInt16Number Out[], register void * Cargo)\r
+{\r
+ cmsTACestimator* bp = (cmsTACestimator*) Cargo;\r
+ cmsFloat32Number RoundTrip[cmsMAXCHANNELS];\r
+ cmsUInt32Number i;\r
+ cmsFloat32Number Sum;\r
+\r
+\r
+ // Evaluate the xform\r
+ cmsDoTransform(bp->hRoundTrip, In, RoundTrip, 1);\r
+\r
+ // All all amounts of ink\r
+ for (Sum=0, i=0; i < bp ->nOutputChans; i++)\r
+ Sum += RoundTrip[i];\r
+\r
+ // If above maximum, keep track of input values\r
+ if (Sum > bp ->MaxTAC) {\r
+\r
+ bp ->MaxTAC = Sum;\r
+\r
+ for (i=0; i < bp ->nOutputChans; i++) {\r
+ bp ->MaxInput[i] = In[i];\r
+ }\r
+ }\r
+\r
+ return TRUE;\r
+\r
+ cmsUNUSED_PARAMETER(Out);\r
+}\r
+\r
+\r
+// Detect Total area coverage of the profile\r
+cmsFloat64Number CMSEXPORT cmsDetectTAC(cmsHPROFILE hProfile)\r
+{\r
+ cmsTACestimator bp;\r
+ cmsUInt32Number dwFormatter;\r
+ cmsUInt32Number GridPoints[MAX_INPUT_DIMENSIONS];\r
+ cmsHPROFILE hLab;\r
+ cmsContext ContextID = cmsGetProfileContextID(hProfile);\r
+\r
+ // TAC only works on output profiles\r
+ if (cmsGetDeviceClass(hProfile) != cmsSigOutputClass) {\r
+ return 0;\r
+ }\r
+\r
+ // Create a fake formatter for result\r
+ dwFormatter = cmsFormatterForColorspaceOfProfile(hProfile, 4, TRUE);\r
+\r
+ bp.nOutputChans = T_CHANNELS(dwFormatter);\r
+ bp.MaxTAC = 0; // Initial TAC is 0\r
+\r
+ // for safety\r
+ if (bp.nOutputChans >= cmsMAXCHANNELS) return 0;\r
+\r
+ hLab = cmsCreateLab4ProfileTHR(ContextID, NULL);\r
+ if (hLab == NULL) return 0;\r
+ // Setup a roundtrip on perceptual intent in output profile for TAC estimation\r
+ bp.hRoundTrip = cmsCreateTransformTHR(ContextID, hLab, TYPE_Lab_16,\r
+ hProfile, dwFormatter, INTENT_PERCEPTUAL, cmsFLAGS_NOOPTIMIZE|cmsFLAGS_NOCACHE);\r
+\r
+ cmsCloseProfile(hLab);\r
+ if (bp.hRoundTrip == NULL) return 0;\r
+\r
+ // For L* we only need black and white. For C* we need many points\r
+ GridPoints[0] = 6;\r
+ GridPoints[1] = 74;\r
+ GridPoints[2] = 74;\r
+\r
+\r
+ if (!cmsSliceSpace16(3, GridPoints, EstimateTAC, &bp)) {\r
+ bp.MaxTAC = 0;\r
+ }\r
+\r
+ cmsDeleteTransform(bp.hRoundTrip);\r
+\r
+ // Results in %\r
+ return bp.MaxTAC;\r
+}\r
+\r
+\r
+// Carefully, clamp on CIELab space.\r
+\r
+cmsBool CMSEXPORT cmsDesaturateLab(cmsCIELab* Lab,\r
+ double amax, double amin,\r
+ double bmax, double bmin)\r
+{\r
+\r
+ // Whole Luma surface to zero\r
+\r
+ if (Lab -> L < 0) {\r
+\r
+ Lab-> L = Lab->a = Lab-> b = 0.0;\r
+ return FALSE;\r
+ }\r
+\r
+ // Clamp white, DISCARD HIGHLIGHTS. This is done\r
+ // in such way because icc spec doesn't allow the\r
+ // use of L>100 as a highlight means.\r
+\r
+ if (Lab->L > 100)\r
+ Lab -> L = 100;\r
+\r
+ // Check out gamut prism, on a, b faces\r
+\r
+ if (Lab -> a < amin || Lab->a > amax||\r
+ Lab -> b < bmin || Lab->b > bmax) {\r
+\r
+ cmsCIELCh LCh;\r
+ double h, slope;\r
+\r
+ // Falls outside a, b limits. Transports to LCh space,\r
+ // and then do the clipping\r
+\r
+\r
+ if (Lab -> a == 0.0) { // Is hue exactly 90?\r
+\r
+ // atan will not work, so clamp here\r
+ Lab -> b = Lab->b < 0 ? bmin : bmax;\r
+ return TRUE;\r
+ }\r
+\r
+ cmsLab2LCh(&LCh, Lab);\r
+\r
+ slope = Lab -> b / Lab -> a;\r
+ h = LCh.h;\r
+\r
+ // There are 4 zones\r
+\r
+ if ((h >= 0. && h < 45.) ||\r
+ (h >= 315 && h <= 360.)) {\r
+\r
+ // clip by amax\r
+ Lab -> a = amax;\r
+ Lab -> b = amax * slope;\r
+ }\r
+ else\r
+ if (h >= 45. && h < 135.)\r
+ {\r
+ // clip by bmax\r
+ Lab -> b = bmax;\r
+ Lab -> a = bmax / slope;\r
+ }\r
+ else\r
+ if (h >= 135. && h < 225.) {\r
+ // clip by amin\r
+ Lab -> a = amin;\r
+ Lab -> b = amin * slope;\r
+\r
+ }\r
+ else\r
+ if (h >= 225. && h < 315.) {\r
+ // clip by bmin\r
+ Lab -> b = bmin;\r
+ Lab -> a = bmin / slope;\r
+ }\r
+ else {\r
+ cmsSignalError(0, cmsERROR_RANGE, "Invalid angle");\r
+ return FALSE;\r
+ }\r
+\r
+ }\r
+\r
+ return TRUE;\r
+}\r