1ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov//---------------------------------------------------------------------------------
2ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov//
3ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov//  Little Color Management System
4ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov//  Copyright (c) 1998-2010 Marti Maria Saguer
5ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov//
6ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// Permission is hereby granted, free of charge, to any person obtaining
7ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// a copy of this software and associated documentation files (the "Software"),
8ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// to deal in the Software without restriction, including without limitation
9ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// the rights to use, copy, modify, merge, publish, distribute, sublicense,
10ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// and/or sell copies of the Software, and to permit persons to whom the Software
11ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// is furnished to do so, subject to the following conditions:
12ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov//
13ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// The above copyright notice and this permission notice shall be included in
14ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// all copies or substantial portions of the Software.
15ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov//
16ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
18ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov//
24ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov//---------------------------------------------------------------------------------
25ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov//
26ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
27ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov#include "lcms2_internal.h"
28ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
29ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
30ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov#define cmsmin(a, b) (((a) < (b)) ? (a) : (b))
31ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov#define cmsmax(a, b) (((a) > (b)) ? (a) : (b))
32ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
33ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// This file contains routines for resampling and LUT optimization, black point detection
34ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// and black preservation.
35ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
36ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// Black point detection -------------------------------------------------------------------------
37ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
38ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
39ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// PCS -> PCS round trip transform, always uses relative intent on the device -> pcs
40ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganovstatic
41ee451cb395940862dad63c85adfe8f2fd55e864cSvet GanovcmsHTRANSFORM CreateRoundtripXForm(cmsHPROFILE hProfile, cmsUInt32Number nIntent)
42ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov{
43ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsContext ContextID = cmsGetProfileContextID(hProfile);
44ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsHPROFILE hLab = cmsCreateLab4ProfileTHR(ContextID, NULL);
45ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsHTRANSFORM xform;
46ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsBool BPC[4] = { FALSE, FALSE, FALSE, FALSE };
47ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsFloat64Number States[4] = { 1.0, 1.0, 1.0, 1.0 };
48ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsHPROFILE hProfiles[4];
49ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsUInt32Number Intents[4];
50ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
51ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    hProfiles[0] = hLab; hProfiles[1] = hProfile; hProfiles[2] = hProfile; hProfiles[3] = hLab;
52ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    Intents[0]   = INTENT_RELATIVE_COLORIMETRIC; Intents[1] = nIntent; Intents[2] = INTENT_RELATIVE_COLORIMETRIC; Intents[3] = INTENT_RELATIVE_COLORIMETRIC;
53ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
54ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    xform =  cmsCreateExtendedTransform(ContextID, 4, hProfiles, BPC, Intents,
55ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        States, NULL, 0, TYPE_Lab_DBL, TYPE_Lab_DBL, cmsFLAGS_NOCACHE|cmsFLAGS_NOOPTIMIZE);
56ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
57ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsCloseProfile(hLab);
58ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    return xform;
59ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov}
60ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
61ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// Use darker colorants to obtain black point. This works in the relative colorimetric intent and
62ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// assumes more ink results in darker colors. No ink limit is assumed.
63ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganovstatic
64ee451cb395940862dad63c85adfe8f2fd55e864cSvet GanovcmsBool  BlackPointAsDarkerColorant(cmsHPROFILE    hInput,
65ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                    cmsUInt32Number Intent,
66ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                    cmsCIEXYZ* BlackPoint,
67ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                    cmsUInt32Number dwFlags)
68ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov{
69ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsUInt16Number *Black;
70ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsHTRANSFORM xform;
71ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsColorSpaceSignature Space;
72ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsUInt32Number nChannels;
73ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsUInt32Number dwFormat;
74ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsHPROFILE hLab;
75ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsCIELab  Lab;
76ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsCIEXYZ  BlackXYZ;
77ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsContext ContextID = cmsGetProfileContextID(hInput);
78ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
79ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // If the profile does not support input direction, assume Black point 0
80ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (!cmsIsIntentSupported(hInput, Intent, LCMS_USED_AS_INPUT)) {
81ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
82ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        BlackPoint -> X = BlackPoint ->Y = BlackPoint -> Z = 0.0;
83ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        return FALSE;
84ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    }
85ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
86ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Create a formatter which has n channels and floating point
87ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    dwFormat = cmsFormatterForColorspaceOfProfile(hInput, 2, FALSE);
88ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
89ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov   // Try to get black by using black colorant
90ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    Space = cmsGetColorSpace(hInput);
91ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
92ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // This function returns darker colorant in 16 bits for several spaces
93ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (!_cmsEndPointsBySpace(Space, NULL, &Black, &nChannels)) {
94ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
95ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        BlackPoint -> X = BlackPoint ->Y = BlackPoint -> Z = 0.0;
96ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        return FALSE;
97ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    }
98ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
99ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (nChannels != T_CHANNELS(dwFormat)) {
100ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov       BlackPoint -> X = BlackPoint ->Y = BlackPoint -> Z = 0.0;
101ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov       return FALSE;
102ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    }
103ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
104ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Lab will be used as the output space, but lab2 will avoid recursion
105ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    hLab = cmsCreateLab2ProfileTHR(ContextID, NULL);
106ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (hLab == NULL) {
107ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov       BlackPoint -> X = BlackPoint ->Y = BlackPoint -> Z = 0.0;
108ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov       return FALSE;
109ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    }
110ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
111ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Create the transform
112ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    xform = cmsCreateTransformTHR(ContextID, hInput, dwFormat,
113ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                hLab, TYPE_Lab_DBL, Intent, cmsFLAGS_NOOPTIMIZE|cmsFLAGS_NOCACHE);
114ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsCloseProfile(hLab);
115ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
116ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (xform == NULL) {
117ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
118ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        // Something went wrong. Get rid of open resources and return zero as black
119ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        BlackPoint -> X = BlackPoint ->Y = BlackPoint -> Z = 0.0;
120ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        return FALSE;
121ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    }
122ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
123ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Convert black to Lab
124ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsDoTransform(xform, Black, &Lab, 1);
125ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
126ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Force it to be neutral, clip to max. L* of 50
127ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    Lab.a = Lab.b = 0;
128ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (Lab.L > 50) Lab.L = 50;
129ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
130ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Free the resources
131ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsDeleteTransform(xform);
132ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
133ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Convert from Lab (which is now clipped) to XYZ.
134ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsLab2XYZ(NULL, &BlackXYZ, &Lab);
135ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
136ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (BlackPoint != NULL)
137ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        *BlackPoint = BlackXYZ;
138ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
139ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    return TRUE;
140ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
141ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsUNUSED_PARAMETER(dwFlags);
142ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov}
143ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
144ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// Get a black point of output CMYK profile, discounting any ink-limiting embedded
145ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// in the profile. For doing that, we use perceptual intent in input direction:
146ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// Lab (0, 0, 0) -> [Perceptual] Profile -> CMYK -> [Rel. colorimetric] Profile -> Lab
147ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganovstatic
148ee451cb395940862dad63c85adfe8f2fd55e864cSvet GanovcmsBool BlackPointUsingPerceptualBlack(cmsCIEXYZ* BlackPoint, cmsHPROFILE hProfile)
149ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov{
150ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsHTRANSFORM hRoundTrip;
151ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsCIELab LabIn, LabOut;
152ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsCIEXYZ  BlackXYZ;
153ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
154ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov     // Is the intent supported by the profile?
155ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (!cmsIsIntentSupported(hProfile, INTENT_PERCEPTUAL, LCMS_USED_AS_INPUT)) {
156ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
157ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        BlackPoint -> X = BlackPoint ->Y = BlackPoint -> Z = 0.0;
158ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        return TRUE;
159ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    }
160ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
161ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    hRoundTrip = CreateRoundtripXForm(hProfile, INTENT_PERCEPTUAL);
162ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (hRoundTrip == NULL) {
163ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        BlackPoint -> X = BlackPoint ->Y = BlackPoint -> Z = 0.0;
164ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        return FALSE;
165ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    }
166ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
167ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    LabIn.L = LabIn.a = LabIn.b = 0;
168ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsDoTransform(hRoundTrip, &LabIn, &LabOut, 1);
169ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
170ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Clip Lab to reasonable limits
171ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (LabOut.L > 50) LabOut.L = 50;
172ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    LabOut.a = LabOut.b = 0;
173ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
174ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsDeleteTransform(hRoundTrip);
175ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
176ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Convert it to XYZ
177ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsLab2XYZ(NULL, &BlackXYZ, &LabOut);
178ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
179ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (BlackPoint != NULL)
180ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        *BlackPoint = BlackXYZ;
181ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
182ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    return TRUE;
183ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov}
184ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
185ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// This function shouldn't exist at all -- there is such quantity of broken
186ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// profiles on black point tag, that we must somehow fix chromaticity to
187ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// avoid huge tint when doing Black point compensation. This function does
188ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// just that. There is a special flag for using black point tag, but turned
189ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// off by default because it is bogus on most profiles. The detection algorithm
190ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// involves to turn BP to neutral and to use only L component.
191ee451cb395940862dad63c85adfe8f2fd55e864cSvet GanovcmsBool CMSEXPORT cmsDetectBlackPoint(cmsCIEXYZ* BlackPoint, cmsHPROFILE hProfile, cmsUInt32Number Intent, cmsUInt32Number dwFlags)
192ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov{
193ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsProfileClassSignature devClass;
194ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
195ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Make sure the device class is adequate
196ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    devClass = cmsGetDeviceClass(hProfile);
197ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (devClass == cmsSigLinkClass ||
198ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        devClass == cmsSigAbstractClass ||
199ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        devClass == cmsSigNamedColorClass) {
200ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            BlackPoint -> X = BlackPoint ->Y = BlackPoint -> Z = 0.0;
201ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            return FALSE;
202ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    }
203ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
204ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Make sure intent is adequate
205ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (Intent != INTENT_PERCEPTUAL &&
206ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        Intent != INTENT_RELATIVE_COLORIMETRIC &&
207ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        Intent != INTENT_SATURATION) {
208ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            BlackPoint -> X = BlackPoint ->Y = BlackPoint -> Z = 0.0;
209ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            return FALSE;
210ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    }
211ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
212ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // v4 + perceptual & saturation intents does have its own black point, and it is
213ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // well specified enough to use it. Black point tag is deprecated in V4.
214ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if ((cmsGetEncodedICCversion(hProfile) >= 0x4000000) &&
215ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        (Intent == INTENT_PERCEPTUAL || Intent == INTENT_SATURATION)) {
216ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
217ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            // Matrix shaper share MRC & perceptual intents
218ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            if (cmsIsMatrixShaper(hProfile))
219ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                return BlackPointAsDarkerColorant(hProfile, INTENT_RELATIVE_COLORIMETRIC, BlackPoint, 0);
220ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
221ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            // Get Perceptual black out of v4 profiles. That is fixed for perceptual & saturation intents
222ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            BlackPoint -> X = cmsPERCEPTUAL_BLACK_X;
223ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            BlackPoint -> Y = cmsPERCEPTUAL_BLACK_Y;
224ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            BlackPoint -> Z = cmsPERCEPTUAL_BLACK_Z;
225ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
226ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            return TRUE;
227ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    }
228ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
229ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
230ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov#ifdef CMS_USE_PROFILE_BLACK_POINT_TAG
231ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
232ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // v2, v4 rel/abs colorimetric
233ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (cmsIsTag(hProfile, cmsSigMediaBlackPointTag) &&
234ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        Intent == INTENT_RELATIVE_COLORIMETRIC) {
235ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
236ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            cmsCIEXYZ *BlackPtr, BlackXYZ, UntrustedBlackPoint, TrustedBlackPoint, MediaWhite;
237ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            cmsCIELab Lab;
238ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
239ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            // If black point is specified, then use it,
240ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
241ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            BlackPtr = cmsReadTag(hProfile, cmsSigMediaBlackPointTag);
242ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            if (BlackPtr != NULL) {
243ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
244ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                BlackXYZ = *BlackPtr;
245ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                _cmsReadMediaWhitePoint(&MediaWhite, hProfile);
246ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
247ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                // Black point is absolute XYZ, so adapt to D50 to get PCS value
248ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                cmsAdaptToIlluminant(&UntrustedBlackPoint, &MediaWhite, cmsD50_XYZ(), &BlackXYZ);
249ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
250ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                // Force a=b=0 to get rid of any chroma
251ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                cmsXYZ2Lab(NULL, &Lab, &UntrustedBlackPoint);
252ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                Lab.a = Lab.b = 0;
253ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                if (Lab.L > 50) Lab.L = 50; // Clip to L* <= 50
254ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                cmsLab2XYZ(NULL, &TrustedBlackPoint, &Lab);
255ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
256ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                if (BlackPoint != NULL)
257ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                    *BlackPoint = TrustedBlackPoint;
258ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
259ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                return TRUE;
260ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            }
261ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    }
262ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov#endif
263ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
264ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // That is about v2 profiles.
265ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
266ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // If output profile, discount ink-limiting and that's all
267ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (Intent == INTENT_RELATIVE_COLORIMETRIC &&
268ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        (cmsGetDeviceClass(hProfile) == cmsSigOutputClass) &&
269ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        (cmsGetColorSpace(hProfile)  == cmsSigCmykData))
270ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        return BlackPointUsingPerceptualBlack(BlackPoint, hProfile);
271ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
272ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Nope, compute BP using current intent.
273ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    return BlackPointAsDarkerColorant(hProfile, Intent, BlackPoint, dwFlags);
274ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov}
275ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
276ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
277ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
278ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// ---------------------------------------------------------------------------------------------------------
279ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
280ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// Least Squares Fit of a Quadratic Curve to Data
281ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// http://www.personal.psu.edu/jhm/f90/lectures/lsq2.html
282ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
283ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganovstatic
284ee451cb395940862dad63c85adfe8f2fd55e864cSvet GanovcmsFloat64Number RootOfLeastSquaresFitQuadraticCurve(int n, cmsFloat64Number x[], cmsFloat64Number y[])
285ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov{
286ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    double sum_x = 0, sum_x2 = 0, sum_x3 = 0, sum_x4 = 0;
287ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    double sum_y = 0, sum_yx = 0, sum_yx2 = 0;
288ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    double d, a, b, c;
289ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    int i;
290ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsMAT3 m;
291ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsVEC3 v, res;
292ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
293ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (n < 4) return 0;
294ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
295ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    for (i=0; i < n; i++) {
296ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
297ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        double xn = x[i];
298ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        double yn = y[i];
299ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
300ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        sum_x  += xn;
301ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        sum_x2 += xn*xn;
302ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        sum_x3 += xn*xn*xn;
303ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        sum_x4 += xn*xn*xn*xn;
304ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
305ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        sum_y += yn;
306ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        sum_yx += yn*xn;
307ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        sum_yx2 += yn*xn*xn;
308ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    }
309ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
310ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    _cmsVEC3init(&m.v[0], n,      sum_x,  sum_x2);
311ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    _cmsVEC3init(&m.v[1], sum_x,  sum_x2, sum_x3);
312ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    _cmsVEC3init(&m.v[2], sum_x2, sum_x3, sum_x4);
313ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
314ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    _cmsVEC3init(&v, sum_y, sum_yx, sum_yx2);
315ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
316ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (!_cmsMAT3solve(&res, &m, &v)) return 0;
317ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
318ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
319ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    a = res.n[2];
320ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    b = res.n[1];
321ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    c = res.n[0];
322ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
323ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (fabs(a) < 1.0E-10) {
324ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
325ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        return cmsmin(0, cmsmax(50, -c/b ));
326ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    }
327ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    else {
328ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
329ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov         d = b*b - 4.0 * a * c;
330ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov         if (d <= 0) {
331ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov             return 0;
332ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov         }
333ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov         else {
334ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
335ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov             double rt = (-b + sqrt(d)) / (2.0 * a);
336ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
337ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov             return cmsmax(0, cmsmin(50, rt));
338ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov         }
339ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov   }
340ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
341ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov}
342ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
343ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov/*
344ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganovstatic
345ee451cb395940862dad63c85adfe8f2fd55e864cSvet GanovcmsBool IsMonotonic(int n, const cmsFloat64Number Table[])
346ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov{
347ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    int i;
348ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsFloat64Number last;
349ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
350ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    last = Table[n-1];
351ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
352ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    for (i = n-2; i >= 0; --i) {
353ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
354ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        if (Table[i] > last)
355ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
356ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            return FALSE;
357ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        else
358ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            last = Table[i];
359ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
360ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    }
361ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
362ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    return TRUE;
363ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov}
364ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov*/
365ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
366ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// Calculates the black point of a destination profile.
367ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// This algorithm comes from the Adobe paper disclosing its black point compensation method.
368ee451cb395940862dad63c85adfe8f2fd55e864cSvet GanovcmsBool CMSEXPORT cmsDetectDestinationBlackPoint(cmsCIEXYZ* BlackPoint, cmsHPROFILE hProfile, cmsUInt32Number Intent, cmsUInt32Number dwFlags)
369ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov{
370ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsColorSpaceSignature ColorSpace;
371ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsHTRANSFORM hRoundTrip = NULL;
372ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsCIELab InitialLab, destLab, Lab;
373ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsFloat64Number inRamp[256], outRamp[256];
374ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsFloat64Number MinL, MaxL;
375ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsBool NearlyStraightMidrange = TRUE;
376ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsFloat64Number yRamp[256];
377ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsFloat64Number x[256], y[256];
378ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsFloat64Number lo, hi;
379ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    int n, l;
380ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsProfileClassSignature devClass;
381ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
382ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Make sure the device class is adequate
383ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    devClass = cmsGetDeviceClass(hProfile);
384ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (devClass == cmsSigLinkClass ||
385ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        devClass == cmsSigAbstractClass ||
386ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        devClass == cmsSigNamedColorClass) {
387ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            BlackPoint -> X = BlackPoint ->Y = BlackPoint -> Z = 0.0;
388ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            return FALSE;
389ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    }
390ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
391ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Make sure intent is adequate
392ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (Intent != INTENT_PERCEPTUAL &&
393ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        Intent != INTENT_RELATIVE_COLORIMETRIC &&
394ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        Intent != INTENT_SATURATION) {
395ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            BlackPoint -> X = BlackPoint ->Y = BlackPoint -> Z = 0.0;
396ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            return FALSE;
397ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    }
398ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
399ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
400ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // v4 + perceptual & saturation intents does have its own black point, and it is
401ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // well specified enough to use it. Black point tag is deprecated in V4.
402ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if ((cmsGetEncodedICCversion(hProfile) >= 0x4000000) &&
403ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        (Intent == INTENT_PERCEPTUAL || Intent == INTENT_SATURATION)) {
404ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
405ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            // Matrix shaper share MRC & perceptual intents
406ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            if (cmsIsMatrixShaper(hProfile))
407ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                return BlackPointAsDarkerColorant(hProfile, INTENT_RELATIVE_COLORIMETRIC, BlackPoint, 0);
408ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
409ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            // Get Perceptual black out of v4 profiles. That is fixed for perceptual & saturation intents
410ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            BlackPoint -> X = cmsPERCEPTUAL_BLACK_X;
411ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            BlackPoint -> Y = cmsPERCEPTUAL_BLACK_Y;
412ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            BlackPoint -> Z = cmsPERCEPTUAL_BLACK_Z;
413ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            return TRUE;
414ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    }
415ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
416ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
417ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Check if the profile is lut based and gray, rgb or cmyk (7.2 in Adobe's document)
418ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    ColorSpace = cmsGetColorSpace(hProfile);
419ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (!cmsIsCLUT(hProfile, Intent, LCMS_USED_AS_OUTPUT ) ||
420ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        (ColorSpace != cmsSigGrayData &&
421ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov         ColorSpace != cmsSigRgbData  &&
422ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov         ColorSpace != cmsSigCmykData)) {
423ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
424ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        // In this case, handle as input case
425ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        return cmsDetectBlackPoint(BlackPoint, hProfile, Intent, dwFlags);
426ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    }
427ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
428ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // It is one of the valid cases!, use Adobe algorithm
429ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
430ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
431ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Set a first guess, that should work on good profiles.
432ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (Intent == INTENT_RELATIVE_COLORIMETRIC) {
433ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
434ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        cmsCIEXYZ IniXYZ;
435ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
436ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        // calculate initial Lab as source black point
437ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        if (!cmsDetectBlackPoint(&IniXYZ, hProfile, Intent, dwFlags)) {
438ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            return FALSE;
439ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        }
440ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
441ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        // convert the XYZ to lab
442ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        cmsXYZ2Lab(NULL, &InitialLab, &IniXYZ);
443ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
444ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    } else {
445ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
446ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        // set the initial Lab to zero, that should be the black point for perceptual and saturation
447ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        InitialLab.L = 0;
448ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        InitialLab.a = 0;
449ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        InitialLab.b = 0;
450ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    }
451ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
452ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
453ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Step 2
454ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // ======
455ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
456ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Create a roundtrip. Define a Transform BT for all x in L*a*b*
457ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    hRoundTrip = CreateRoundtripXForm(hProfile, Intent);
458ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (hRoundTrip == NULL)  return FALSE;
459ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
460ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Compute ramps
461ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
462ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    for (l=0; l < 256; l++) {
463ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
464ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        Lab.L = (cmsFloat64Number) (l * 100.0) / 255.0;
465ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        Lab.a = cmsmin(50, cmsmax(-50, InitialLab.a));
466ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        Lab.b = cmsmin(50, cmsmax(-50, InitialLab.b));
467ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
468ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        cmsDoTransform(hRoundTrip, &Lab, &destLab, 1);
469ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
470ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        inRamp[l]  = Lab.L;
471ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        outRamp[l] = destLab.L;
472ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    }
473ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
474ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Make monotonic
475ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    for (l = 254; l > 0; --l) {
476ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        outRamp[l] = cmsmin(outRamp[l], outRamp[l+1]);
477ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    }
478ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
479ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Check
480ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (! (outRamp[0] < outRamp[255])) {
481ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
482ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        cmsDeleteTransform(hRoundTrip);
483ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        BlackPoint -> X = BlackPoint ->Y = BlackPoint -> Z = 0.0;
484ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        return FALSE;
485ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    }
486ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
487ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
488ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Test for mid range straight (only on relative colorimetric)
489ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
490ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    NearlyStraightMidrange = TRUE;
491ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    MinL = outRamp[0]; MaxL = outRamp[255];
492ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (Intent == INTENT_RELATIVE_COLORIMETRIC) {
493ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
494ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        for (l=0; l < 256; l++) {
495ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
496ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            if (! ((inRamp[l] <= MinL + 0.2 * (MaxL - MinL) ) ||
497ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                (fabs(inRamp[l] - outRamp[l]) < 4.0 )))
498ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                NearlyStraightMidrange = FALSE;
499ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        }
500ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
501ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        // If the mid range is straight (as determined above) then the
502ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        // DestinationBlackPoint shall be the same as initialLab.
503ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        // Otherwise, the DestinationBlackPoint shall be determined
504ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        // using curve fitting.
505ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
506ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        if (NearlyStraightMidrange) {
507ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
508ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            cmsLab2XYZ(NULL, BlackPoint, &InitialLab);
509ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            cmsDeleteTransform(hRoundTrip);
510ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            return TRUE;
511ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        }
512ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    }
513ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
514ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
515ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // curve fitting: The round-trip curve normally looks like a nearly constant section at the black point,
516ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // with a corner and a nearly straight line to the white point.
517ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
518ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    for (l=0; l < 256; l++) {
519ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
520ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        yRamp[l] = (outRamp[l] - MinL) / (MaxL - MinL);
521ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    }
522ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
523ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // find the black point using the least squares error quadratic curve fitting
524ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
525ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (Intent == INTENT_RELATIVE_COLORIMETRIC) {
526ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        lo = 0.1;
527ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        hi = 0.5;
528ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    }
529ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    else {
530ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
531ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        // Perceptual and saturation
532ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        lo = 0.03;
533ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        hi = 0.25;
534ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    }
535ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
536ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Capture shadow points for the fitting.
537ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    n = 0;
538ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    for (l=0; l < 256; l++) {
539ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
540ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        cmsFloat64Number ff = yRamp[l];
541ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
542ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        if (ff >= lo && ff < hi) {
543ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            x[n] = inRamp[l];
544ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            y[n] = yRamp[l];
545ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            n++;
546ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        }
547ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    }
548ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
549ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
550ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // No suitable points
551ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (n < 3 ) {
552ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        cmsDeleteTransform(hRoundTrip);
553ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        BlackPoint -> X = BlackPoint ->Y = BlackPoint -> Z = 0.0;
554ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        return FALSE;
555ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    }
556ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
557ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
558ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // fit and get the vertex of quadratic curve
559ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    Lab.L = RootOfLeastSquaresFitQuadraticCurve(n, x, y);
560ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
561ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (Lab.L < 0.0) { // clip to zero L* if the vertex is negative
562ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        Lab.L = 0;
563ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    }
564ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
565ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    Lab.a = InitialLab.a;
566ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    Lab.b = InitialLab.b;
567ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
568ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsLab2XYZ(NULL, BlackPoint, &Lab);
569ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
570ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsDeleteTransform(hRoundTrip);
571ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    return TRUE;
572ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov}
573