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