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