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