1e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov//--------------------------------------------------------------------------------- 2e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov// 3e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov// Little Color Management System 4e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov// Copyright (c) 1998-2012 Marti Maria Saguer 5e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov// 6e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov// Permission is hereby granted, free of charge, to any person obtaining 7e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov// a copy of this software and associated documentation files (the "Software"), 8e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov// to deal in the Software without restriction, including without limitation 9e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov// the rights to use, copy, modify, merge, publish, distribute, sublicense, 10e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov// and/or sell copies of the Software, and to permit persons to whom the Software 11e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov// is furnished to do so, subject to the following conditions: 12e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov// 13e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov// The above copyright notice and this permission notice shall be included in 14e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov// all copies or substantial portions of the Software. 15e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov// 16e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 18e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov// THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov// 24e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov//--------------------------------------------------------------------------------- 25e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov// 26e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 27e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov#include "lcms2_internal.h" 28e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 29e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 30e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov// Auxiliar: append a Lab identity after the given sequence of profiles 31e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov// and return the transform. Lab profile is closed, rest of profiles are kept open. 32e6986e1e8d4a57987f47c215490cb080a65ee29aSvet GanovcmsHTRANSFORM _cmsChain2Lab(cmsContext ContextID, 33e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsUInt32Number nProfiles, 34e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsUInt32Number InputFormat, 35e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsUInt32Number OutputFormat, 36e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov const cmsUInt32Number Intents[], 37e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov const cmsHPROFILE hProfiles[], 38e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov const cmsBool BPC[], 39e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov const cmsFloat64Number AdaptationStates[], 40e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsUInt32Number dwFlags) 41e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov{ 42e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsHTRANSFORM xform; 43e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsHPROFILE hLab; 44e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsHPROFILE ProfileList[256]; 45e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsBool BPCList[256]; 46e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsFloat64Number AdaptationList[256]; 47e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsUInt32Number IntentList[256]; 48e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsUInt32Number i; 49e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 50e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // This is a rather big number and there is no need of dynamic memory 51e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // since we are adding a profile, 254 + 1 = 255 and this is the limit 52e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov if (nProfiles > 254) return NULL; 53e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 54e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // The output space 55e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov hLab = cmsCreateLab4ProfileTHR(ContextID, NULL); 56e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov if (hLab == NULL) return NULL; 57e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 58e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // Create a copy of parameters 59e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov for (i=0; i < nProfiles; i++) { 60e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 61e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov ProfileList[i] = hProfiles[i]; 62e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov BPCList[i] = BPC[i]; 63e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov AdaptationList[i] = AdaptationStates[i]; 64e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov IntentList[i] = Intents[i]; 65e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov } 66e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 67e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // Place Lab identity at chain's end. 68e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov ProfileList[nProfiles] = hLab; 69e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov BPCList[nProfiles] = 0; 70e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov AdaptationList[nProfiles] = 1.0; 71e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov IntentList[nProfiles] = INTENT_RELATIVE_COLORIMETRIC; 72e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 73e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // Create the transform 74e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov xform = cmsCreateExtendedTransform(ContextID, nProfiles + 1, ProfileList, 75e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov BPCList, 76e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov IntentList, 77e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov AdaptationList, 78e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov NULL, 0, 79e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov InputFormat, 80e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov OutputFormat, 81e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov dwFlags); 82e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 83e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsCloseProfile(hLab); 84e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 85e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov return xform; 86e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov} 87e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 88e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 89e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov// Compute K -> L* relationship. Flags may include black point compensation. In this case, 90e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov// the relationship is assumed from the profile with BPC to a black point zero. 91e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganovstatic 92e6986e1e8d4a57987f47c215490cb080a65ee29aSvet GanovcmsToneCurve* ComputeKToLstar(cmsContext ContextID, 93e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsUInt32Number nPoints, 94e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsUInt32Number nProfiles, 95e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov const cmsUInt32Number Intents[], 96e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov const cmsHPROFILE hProfiles[], 97e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov const cmsBool BPC[], 98e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov const cmsFloat64Number AdaptationStates[], 99e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsUInt32Number dwFlags) 100e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov{ 101e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsToneCurve* out = NULL; 102e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsUInt32Number i; 103e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsHTRANSFORM xform; 104e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsCIELab Lab; 105e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsFloat32Number cmyk[4]; 106e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsFloat32Number* SampledPoints; 107e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 108e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov xform = _cmsChain2Lab(ContextID, nProfiles, TYPE_CMYK_FLT, TYPE_Lab_DBL, Intents, hProfiles, BPC, AdaptationStates, dwFlags); 109e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov if (xform == NULL) return NULL; 110e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 111e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov SampledPoints = (cmsFloat32Number*) _cmsCalloc(ContextID, nPoints, sizeof(cmsFloat32Number)); 112e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov if (SampledPoints == NULL) goto Error; 113e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 114e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov for (i=0; i < nPoints; i++) { 115e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 116e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmyk[0] = 0; 117e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmyk[1] = 0; 118e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmyk[2] = 0; 119e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmyk[3] = (cmsFloat32Number) ((i * 100.0) / (nPoints-1)); 120e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 121e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsDoTransform(xform, cmyk, &Lab, 1); 122e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov SampledPoints[i]= (cmsFloat32Number) (1.0 - Lab.L / 100.0); // Negate K for easier operation 123e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov } 124e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 125e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov out = cmsBuildTabulatedToneCurveFloat(ContextID, nPoints, SampledPoints); 126e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 127e6986e1e8d4a57987f47c215490cb080a65ee29aSvet GanovError: 128e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 129e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsDeleteTransform(xform); 130e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov if (SampledPoints) _cmsFree(ContextID, SampledPoints); 131e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 132e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov return out; 133e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov} 134e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 135e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 136e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov// Compute Black tone curve on a CMYK -> CMYK transform. This is done by 137e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov// using the proof direction on both profiles to find K->L* relationship 138e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov// then joining both curves. dwFlags may include black point compensation. 139e6986e1e8d4a57987f47c215490cb080a65ee29aSvet GanovcmsToneCurve* _cmsBuildKToneCurve(cmsContext ContextID, 140e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsUInt32Number nPoints, 141e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsUInt32Number nProfiles, 142e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov const cmsUInt32Number Intents[], 143e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov const cmsHPROFILE hProfiles[], 144e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov const cmsBool BPC[], 145e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov const cmsFloat64Number AdaptationStates[], 146e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsUInt32Number dwFlags) 147e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov{ 148e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsToneCurve *in, *out, *KTone; 149e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 150e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // Make sure CMYK -> CMYK 151e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov if (cmsGetColorSpace(hProfiles[0]) != cmsSigCmykData || 152e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsGetColorSpace(hProfiles[nProfiles-1])!= cmsSigCmykData) return NULL; 153e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 154e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 155e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // Make sure last is an output profile 156e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov if (cmsGetDeviceClass(hProfiles[nProfiles - 1]) != cmsSigOutputClass) return NULL; 157e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 158e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // Create individual curves. BPC works also as each K to L* is 159e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // computed as a BPC to zero black point in case of L* 160e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov in = ComputeKToLstar(ContextID, nPoints, nProfiles - 1, Intents, hProfiles, BPC, AdaptationStates, dwFlags); 161e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov if (in == NULL) return NULL; 162e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 163e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov out = ComputeKToLstar(ContextID, nPoints, 1, 164e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov Intents + (nProfiles - 1), 165e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov &hProfiles [nProfiles - 1], 166e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov BPC + (nProfiles - 1), 167e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov AdaptationStates + (nProfiles - 1), 168e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov dwFlags); 169e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov if (out == NULL) { 170e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsFreeToneCurve(in); 171e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov return NULL; 172e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov } 173e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 174e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // Build the relationship. This effectively limits the maximum accuracy to 16 bits, but 175e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // since this is used on black-preserving LUTs, we are not loosing accuracy in any case 176e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov KTone = cmsJoinToneCurve(ContextID, in, out, nPoints); 177e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 178e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // Get rid of components 179e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsFreeToneCurve(in); cmsFreeToneCurve(out); 180e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 181e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // Something went wrong... 182e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov if (KTone == NULL) return NULL; 183e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 184e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // Make sure it is monotonic 185e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov if (!cmsIsToneCurveMonotonic(KTone)) { 186e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsFreeToneCurve(KTone); 187e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov return NULL; 188e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov } 189e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 190e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov return KTone; 191e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov} 192e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 193e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 194e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov// Gamut LUT Creation ----------------------------------------------------------------------------------------- 195e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 196e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov// Used by gamut & softproofing 197e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 198e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganovtypedef struct { 199e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 200e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsHTRANSFORM hInput; // From whatever input color space. 16 bits to DBL 201e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsHTRANSFORM hForward, hReverse; // Transforms going from Lab to colorant and back 202e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsFloat64Number Thereshold; // The thereshold after which is considered out of gamut 203e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 204e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov } GAMUTCHAIN; 205e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 206e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov// This sampler does compute gamut boundaries by comparing original 207e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov// values with a transform going back and forth. Values above ERR_THERESHOLD 208e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov// of maximum are considered out of gamut. 209e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 210e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov#define ERR_THERESHOLD 5 211e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 212e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 213e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganovstatic 214e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganovint GamutSampler(register const cmsUInt16Number In[], register cmsUInt16Number Out[], register void* Cargo) 215e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov{ 216e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov GAMUTCHAIN* t = (GAMUTCHAIN* ) Cargo; 217e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsCIELab LabIn1, LabOut1; 218e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsCIELab LabIn2, LabOut2; 219e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsUInt16Number Proof[cmsMAXCHANNELS], Proof2[cmsMAXCHANNELS]; 220e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsFloat64Number dE1, dE2, ErrorRatio; 221e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 222e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // Assume in-gamut by default. 223e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov ErrorRatio = 1.0; 224e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 225e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // Convert input to Lab 226e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsDoTransform(t -> hInput, In, &LabIn1, 1); 227e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 228e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // converts from PCS to colorant. This always 229e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // does return in-gamut values, 230e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsDoTransform(t -> hForward, &LabIn1, Proof, 1); 231e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 232e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // Now, do the inverse, from colorant to PCS. 233e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsDoTransform(t -> hReverse, Proof, &LabOut1, 1); 234e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 235e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov memmove(&LabIn2, &LabOut1, sizeof(cmsCIELab)); 236e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 237e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // Try again, but this time taking Check as input 238e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsDoTransform(t -> hForward, &LabOut1, Proof2, 1); 239e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsDoTransform(t -> hReverse, Proof2, &LabOut2, 1); 240e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 241e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // Take difference of direct value 242e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov dE1 = cmsDeltaE(&LabIn1, &LabOut1); 243e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 244e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // Take difference of converted value 245e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov dE2 = cmsDeltaE(&LabIn2, &LabOut2); 246e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 247e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 248e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // if dE1 is small and dE2 is small, value is likely to be in gamut 249e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov if (dE1 < t->Thereshold && dE2 < t->Thereshold) 250e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov Out[0] = 0; 251e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov else { 252e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 253e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // if dE1 is small and dE2 is big, undefined. Assume in gamut 254e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov if (dE1 < t->Thereshold && dE2 > t->Thereshold) 255e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov Out[0] = 0; 256e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov else 257e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // dE1 is big and dE2 is small, clearly out of gamut 258e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov if (dE1 > t->Thereshold && dE2 < t->Thereshold) 259e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov Out[0] = (cmsUInt16Number) _cmsQuickFloor((dE1 - t->Thereshold) + .5); 260e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov else { 261e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 262e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // dE1 is big and dE2 is also big, could be due to perceptual mapping 263e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // so take error ratio 264e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov if (dE2 == 0.0) 265e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov ErrorRatio = dE1; 266e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov else 267e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov ErrorRatio = dE1 / dE2; 268e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 269e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov if (ErrorRatio > t->Thereshold) 270e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov Out[0] = (cmsUInt16Number) _cmsQuickFloor((ErrorRatio - t->Thereshold) + .5); 271e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov else 272e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov Out[0] = 0; 273e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov } 274e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov } 275e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 276e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 277e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov return TRUE; 278e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov} 279e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 280e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov// Does compute a gamut LUT going back and forth across pcs -> relativ. colorimetric intent -> pcs 281e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov// the dE obtained is then annotated on the LUT. Values truely out of gamut are clipped to dE = 0xFFFE 282e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov// and values changed are supposed to be handled by any gamut remapping, so, are out of gamut as well. 283e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov// 284e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov// **WARNING: This algorithm does assume that gamut remapping algorithms does NOT move in-gamut colors, 285e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov// of course, many perceptual and saturation intents does not work in such way, but relativ. ones should. 286e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 287e6986e1e8d4a57987f47c215490cb080a65ee29aSvet GanovcmsPipeline* _cmsCreateGamutCheckPipeline(cmsContext ContextID, 288e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsHPROFILE hProfiles[], 289e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsBool BPC[], 290e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsUInt32Number Intents[], 291e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsFloat64Number AdaptationStates[], 292e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsUInt32Number nGamutPCSposition, 293e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsHPROFILE hGamut) 294e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov{ 295e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsHPROFILE hLab; 296e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsPipeline* Gamut; 297e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsStage* CLUT; 298e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsUInt32Number dwFormat; 299e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov GAMUTCHAIN Chain; 300e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov int nChannels, nGridpoints; 301e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsColorSpaceSignature ColorSpace; 302e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsUInt32Number i; 303e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsHPROFILE ProfileList[256]; 304e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsBool BPCList[256]; 305e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsFloat64Number AdaptationList[256]; 306e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsUInt32Number IntentList[256]; 307e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 308e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov memset(&Chain, 0, sizeof(GAMUTCHAIN)); 309e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 310e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 311e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov if (nGamutPCSposition <= 0 || nGamutPCSposition > 255) { 312e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsSignalError(ContextID, cmsERROR_RANGE, "Wrong position of PCS. 1..255 expected, %d found.", nGamutPCSposition); 313e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov return NULL; 314e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov } 315e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 316e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov hLab = cmsCreateLab4ProfileTHR(ContextID, NULL); 317e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov if (hLab == NULL) return NULL; 318e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 319e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 320e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // The figure of merit. On matrix-shaper profiles, should be almost zero as 321e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // the conversion is pretty exact. On LUT based profiles, different resolutions 322e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // of input and output CLUT may result in differences. 323e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 324e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov if (cmsIsMatrixShaper(hGamut)) { 325e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 326e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov Chain.Thereshold = 1.0; 327e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov } 328e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov else { 329e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov Chain.Thereshold = ERR_THERESHOLD; 330e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov } 331e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 332e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 333e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // Create a copy of parameters 334e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov for (i=0; i < nGamutPCSposition; i++) { 335e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov ProfileList[i] = hProfiles[i]; 336e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov BPCList[i] = BPC[i]; 337e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov AdaptationList[i] = AdaptationStates[i]; 338e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov IntentList[i] = Intents[i]; 339e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov } 340e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 341e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // Fill Lab identity 342e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov ProfileList[nGamutPCSposition] = hLab; 343e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov BPCList[nGamutPCSposition] = 0; 344e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov AdaptationList[nGamutPCSposition] = 1.0; 345e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov IntentList[nGamutPCSposition] = INTENT_RELATIVE_COLORIMETRIC; 346e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 347e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 348e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov ColorSpace = cmsGetColorSpace(hGamut); 349e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 350e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov nChannels = cmsChannelsOf(ColorSpace); 351e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov nGridpoints = _cmsReasonableGridpointsByColorspace(ColorSpace, cmsFLAGS_HIGHRESPRECALC); 352e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov dwFormat = (CHANNELS_SH(nChannels)|BYTES_SH(2)); 353e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 354e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // 16 bits to Lab double 355e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov Chain.hInput = cmsCreateExtendedTransform(ContextID, 356e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov nGamutPCSposition + 1, 357e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov ProfileList, 358e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov BPCList, 359e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov IntentList, 360e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov AdaptationList, 361e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov NULL, 0, 362e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov dwFormat, TYPE_Lab_DBL, 363e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsFLAGS_NOCACHE); 364e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 365e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 366e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // Does create the forward step. Lab double to device 367e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov dwFormat = (CHANNELS_SH(nChannels)|BYTES_SH(2)); 368e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov Chain.hForward = cmsCreateTransformTHR(ContextID, 369e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov hLab, TYPE_Lab_DBL, 370e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov hGamut, dwFormat, 371e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov INTENT_RELATIVE_COLORIMETRIC, 372e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsFLAGS_NOCACHE); 373e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 374e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // Does create the backwards step 375e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov Chain.hReverse = cmsCreateTransformTHR(ContextID, hGamut, dwFormat, 376e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov hLab, TYPE_Lab_DBL, 377e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov INTENT_RELATIVE_COLORIMETRIC, 378e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsFLAGS_NOCACHE); 379e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 380e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 381e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // All ok? 382e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov if (Chain.hInput && Chain.hForward && Chain.hReverse) { 383e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 384e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // Go on, try to compute gamut LUT from PCS. This consist on a single channel containing 385e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // dE when doing a transform back and forth on the colorimetric intent. 386e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 387e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov Gamut = cmsPipelineAlloc(ContextID, 3, 1); 388e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov if (Gamut != NULL) { 389e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 390e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov CLUT = cmsStageAllocCLut16bit(ContextID, nGridpoints, nChannels, 1, NULL); 391e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov if (!cmsPipelineInsertStage(Gamut, cmsAT_BEGIN, CLUT)) { 392e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsPipelineFree(Gamut); 393e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov Gamut = NULL; 394e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov } 395e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov else { 396e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsStageSampleCLut16bit(CLUT, GamutSampler, (void*) &Chain, 0); 397e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov } 398e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov } 399e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov } 400e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov else 401e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov Gamut = NULL; // Didn't work... 402e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 403e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // Free all needed stuff. 404e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov if (Chain.hInput) cmsDeleteTransform(Chain.hInput); 405e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov if (Chain.hForward) cmsDeleteTransform(Chain.hForward); 406e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov if (Chain.hReverse) cmsDeleteTransform(Chain.hReverse); 407e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov if (hLab) cmsCloseProfile(hLab); 408e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 409e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // And return computed hull 410e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov return Gamut; 411e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov} 412e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 413e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov// Total Area Coverage estimation ---------------------------------------------------------------- 414e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 415e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganovtypedef struct { 416e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsUInt32Number nOutputChans; 417e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsHTRANSFORM hRoundTrip; 418e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsFloat32Number MaxTAC; 419e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsFloat32Number MaxInput[cmsMAXCHANNELS]; 420e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 421e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov} cmsTACestimator; 422e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 423e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 424e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov// This callback just accounts the maximum ink dropped in the given node. It does not populate any 425e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov// memory, as the destination table is NULL. Its only purpose it to know the global maximum. 426e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganovstatic 427e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganovint EstimateTAC(register const cmsUInt16Number In[], register cmsUInt16Number Out[], register void * Cargo) 428e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov{ 429e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsTACestimator* bp = (cmsTACestimator*) Cargo; 430e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsFloat32Number RoundTrip[cmsMAXCHANNELS]; 431e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsUInt32Number i; 432e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsFloat32Number Sum; 433e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 434e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 435e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // Evaluate the xform 436e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsDoTransform(bp->hRoundTrip, In, RoundTrip, 1); 437e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 438e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // All all amounts of ink 439e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov for (Sum=0, i=0; i < bp ->nOutputChans; i++) 440e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov Sum += RoundTrip[i]; 441e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 442e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // If above maximum, keep track of input values 443e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov if (Sum > bp ->MaxTAC) { 444e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 445e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov bp ->MaxTAC = Sum; 446e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 447e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov for (i=0; i < bp ->nOutputChans; i++) { 448e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov bp ->MaxInput[i] = In[i]; 449e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov } 450e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov } 451e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 452e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov return TRUE; 453e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 454e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsUNUSED_PARAMETER(Out); 455e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov} 456e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 457e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 458e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov// Detect Total area coverage of the profile 459e6986e1e8d4a57987f47c215490cb080a65ee29aSvet GanovcmsFloat64Number CMSEXPORT cmsDetectTAC(cmsHPROFILE hProfile) 460e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov{ 461e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsTACestimator bp; 462e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsUInt32Number dwFormatter; 463e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsUInt32Number GridPoints[MAX_INPUT_DIMENSIONS]; 464e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsHPROFILE hLab; 465e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsContext ContextID = cmsGetProfileContextID(hProfile); 466e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 467e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // TAC only works on output profiles 468e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov if (cmsGetDeviceClass(hProfile) != cmsSigOutputClass) { 469e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov return 0; 470e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov } 471e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 472e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // Create a fake formatter for result 473e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov dwFormatter = cmsFormatterForColorspaceOfProfile(hProfile, 4, TRUE); 474e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 475e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov bp.nOutputChans = T_CHANNELS(dwFormatter); 476e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov bp.MaxTAC = 0; // Initial TAC is 0 477e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 478e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // for safety 479e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov if (bp.nOutputChans >= cmsMAXCHANNELS) return 0; 480e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 481e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov hLab = cmsCreateLab4ProfileTHR(ContextID, NULL); 482e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov if (hLab == NULL) return 0; 483e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // Setup a roundtrip on perceptual intent in output profile for TAC estimation 484e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov bp.hRoundTrip = cmsCreateTransformTHR(ContextID, hLab, TYPE_Lab_16, 485e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov hProfile, dwFormatter, INTENT_PERCEPTUAL, cmsFLAGS_NOOPTIMIZE|cmsFLAGS_NOCACHE); 486e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 487e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsCloseProfile(hLab); 488e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov if (bp.hRoundTrip == NULL) return 0; 489e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 490e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // For L* we only need black and white. For C* we need many points 491e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov GridPoints[0] = 6; 492e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov GridPoints[1] = 74; 493e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov GridPoints[2] = 74; 494e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 495e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 496e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov if (!cmsSliceSpace16(3, GridPoints, EstimateTAC, &bp)) { 497e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov bp.MaxTAC = 0; 498e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov } 499e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 500e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsDeleteTransform(bp.hRoundTrip); 501e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 502e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // Results in % 503e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov return bp.MaxTAC; 504e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov} 505e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 506e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 507e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov// Carefully, clamp on CIELab space. 508e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 509e6986e1e8d4a57987f47c215490cb080a65ee29aSvet GanovcmsBool CMSEXPORT cmsDesaturateLab(cmsCIELab* Lab, 510e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov double amax, double amin, 511e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov double bmax, double bmin) 512e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov{ 513e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 514e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // Whole Luma surface to zero 515e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 516e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov if (Lab -> L < 0) { 517e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 518e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov Lab-> L = Lab->a = Lab-> b = 0.0; 519e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov return FALSE; 520e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov } 521e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 522e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // Clamp white, DISCARD HIGHLIGHTS. This is done 523e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // in such way because icc spec doesn't allow the 524e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // use of L>100 as a highlight means. 525e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 526e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov if (Lab->L > 100) 527e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov Lab -> L = 100; 528e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 529e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // Check out gamut prism, on a, b faces 530e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 531e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov if (Lab -> a < amin || Lab->a > amax|| 532e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov Lab -> b < bmin || Lab->b > bmax) { 533e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 534e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsCIELCh LCh; 535e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov double h, slope; 536e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 537e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // Falls outside a, b limits. Transports to LCh space, 538e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // and then do the clipping 539e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 540e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 541e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov if (Lab -> a == 0.0) { // Is hue exactly 90? 542e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 543e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // atan will not work, so clamp here 544e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov Lab -> b = Lab->b < 0 ? bmin : bmax; 545e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov return TRUE; 546e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov } 547e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 548e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsLab2LCh(&LCh, Lab); 549e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 550e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov slope = Lab -> b / Lab -> a; 551e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov h = LCh.h; 552e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 553e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // There are 4 zones 554e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 555e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov if ((h >= 0. && h < 45.) || 556e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov (h >= 315 && h <= 360.)) { 557e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 558e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // clip by amax 559e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov Lab -> a = amax; 560e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov Lab -> b = amax * slope; 561e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov } 562e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov else 563e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov if (h >= 45. && h < 135.) 564e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov { 565e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // clip by bmax 566e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov Lab -> b = bmax; 567e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov Lab -> a = bmax / slope; 568e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov } 569e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov else 570e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov if (h >= 135. && h < 225.) { 571e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // clip by amin 572e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov Lab -> a = amin; 573e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov Lab -> b = amin * slope; 574e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 575e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov } 576e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov else 577e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov if (h >= 225. && h < 315.) { 578e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov // clip by bmin 579e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov Lab -> b = bmin; 580e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov Lab -> a = bmin / slope; 581e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov } 582e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov else { 583e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov cmsSignalError(0, cmsERROR_RANGE, "Invalid angle"); 584e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov return FALSE; 585e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov } 586e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 587e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov } 588e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov 589e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov return TRUE; 590e6986e1e8d4a57987f47c215490cb080a65ee29aSvet Ganov} 591