Update lcms (#544)
[openjpeg.git] / thirdparty / liblcms2 / src / cmsgmt.c
1 //---------------------------------------------------------------------------------\r
2 //\r
3 //  Little Color Management System\r
4 //  Copyright (c) 1998-2016 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     ErrorRatio = 1.0;\r
224 \r
225     // Convert input to Lab\r
226     cmsDoTransform(t -> hInput, In, &LabIn1, 1);\r
227 \r
228     // converts from PCS to colorant. This always\r
229     // does return in-gamut values,\r
230     cmsDoTransform(t -> hForward, &LabIn1, Proof, 1);\r
231 \r
232     // Now, do the inverse, from colorant to PCS.\r
233     cmsDoTransform(t -> hReverse, Proof, &LabOut1, 1);\r
234 \r
235     memmove(&LabIn2, &LabOut1, sizeof(cmsCIELab));\r
236 \r
237     // Try again, but this time taking Check as input\r
238     cmsDoTransform(t -> hForward, &LabOut1, Proof2, 1);\r
239     cmsDoTransform(t -> hReverse, Proof2, &LabOut2, 1);\r
240 \r
241     // Take difference of direct value\r
242     dE1 = cmsDeltaE(&LabIn1, &LabOut1);\r
243 \r
244     // Take difference of converted value\r
245     dE2 = cmsDeltaE(&LabIn2, &LabOut2);\r
246 \r
247 \r
248     // if dE1 is small and dE2 is small, value is likely to be in gamut\r
249     if (dE1 < t->Thereshold && dE2 < t->Thereshold)\r
250         Out[0] = 0;\r
251     else {\r
252 \r
253         // if dE1 is small and dE2 is big, undefined. Assume in gamut\r
254         if (dE1 < t->Thereshold && dE2 > t->Thereshold)\r
255             Out[0] = 0;\r
256         else\r
257             // dE1 is big and dE2 is small, clearly out of gamut\r
258             if (dE1 > t->Thereshold && dE2 < t->Thereshold)\r
259                 Out[0] = (cmsUInt16Number) _cmsQuickFloor((dE1 - t->Thereshold) + .5);\r
260             else  {\r
261 \r
262                 // dE1 is big and dE2 is also big, could be due to perceptual mapping\r
263                 // so take error ratio\r
264                 if (dE2 == 0.0)\r
265                     ErrorRatio = dE1;\r
266                 else\r
267                     ErrorRatio = dE1 / dE2;\r
268 \r
269                 if (ErrorRatio > t->Thereshold)\r
270                     Out[0] = (cmsUInt16Number)  _cmsQuickFloor((ErrorRatio - t->Thereshold) + .5);\r
271                 else\r
272                     Out[0] = 0;\r
273             }\r
274     }\r
275 \r
276 \r
277     return TRUE;\r
278 }\r
279 \r
280 // Does compute a gamut LUT going back and forth across pcs -> relativ. colorimetric intent -> pcs\r
281 // the dE obtained is then annotated on the LUT. Values truely out of gamut are clipped to dE = 0xFFFE\r
282 // and values changed are supposed to be handled by any gamut remapping, so, are out of gamut as well.\r
283 //\r
284 // **WARNING: This algorithm does assume that gamut remapping algorithms does NOT move in-gamut colors,\r
285 // of course, many perceptual and saturation intents does not work in such way, but relativ. ones should.\r
286 \r
287 cmsPipeline* _cmsCreateGamutCheckPipeline(cmsContext ContextID,\r
288                                           cmsHPROFILE hProfiles[],\r
289                                           cmsBool  BPC[],\r
290                                           cmsUInt32Number Intents[],\r
291                                           cmsFloat64Number AdaptationStates[],\r
292                                           cmsUInt32Number nGamutPCSposition,\r
293                                           cmsHPROFILE hGamut)\r
294 {\r
295     cmsHPROFILE hLab;\r
296     cmsPipeline* Gamut;\r
297     cmsStage* CLUT;\r
298     cmsUInt32Number dwFormat;\r
299     GAMUTCHAIN Chain;\r
300     int nChannels, nGridpoints;\r
301     cmsColorSpaceSignature ColorSpace;\r
302     cmsUInt32Number i;\r
303     cmsHPROFILE ProfileList[256];\r
304     cmsBool     BPCList[256];\r
305     cmsFloat64Number AdaptationList[256];\r
306     cmsUInt32Number IntentList[256];\r
307 \r
308     memset(&Chain, 0, sizeof(GAMUTCHAIN));\r
309 \r
310 \r
311     if (nGamutPCSposition <= 0 || nGamutPCSposition > 255) {\r
312         cmsSignalError(ContextID, cmsERROR_RANGE, "Wrong position of PCS. 1..255 expected, %d found.", nGamutPCSposition);\r
313         return NULL;\r
314     }\r
315 \r
316     hLab = cmsCreateLab4ProfileTHR(ContextID, NULL);\r
317     if (hLab == NULL) return NULL;\r
318 \r
319 \r
320     // The figure of merit. On matrix-shaper profiles, should be almost zero as\r
321     // the conversion is pretty exact. On LUT based profiles, different resolutions\r
322     // of input and output CLUT may result in differences.\r
323 \r
324     if (cmsIsMatrixShaper(hGamut)) {\r
325 \r
326         Chain.Thereshold = 1.0;\r
327     }\r
328     else {\r
329         Chain.Thereshold = ERR_THERESHOLD;\r
330     }\r
331 \r
332 \r
333     // Create a copy of parameters\r
334     for (i=0; i < nGamutPCSposition; i++) {\r
335         ProfileList[i]    = hProfiles[i];\r
336         BPCList[i]        = BPC[i];\r
337         AdaptationList[i] = AdaptationStates[i];\r
338         IntentList[i]     = Intents[i];\r
339     }\r
340 \r
341     // Fill Lab identity\r
342     ProfileList[nGamutPCSposition] = hLab;\r
343     BPCList[nGamutPCSposition] = 0;\r
344     AdaptationList[nGamutPCSposition] = 1.0;\r
345     IntentList[nGamutPCSposition] = INTENT_RELATIVE_COLORIMETRIC;\r
346 \r
347 \r
348     ColorSpace  = cmsGetColorSpace(hGamut);\r
349 \r
350     nChannels   = cmsChannelsOf(ColorSpace);\r
351     nGridpoints = _cmsReasonableGridpointsByColorspace(ColorSpace, cmsFLAGS_HIGHRESPRECALC);\r
352     dwFormat    = (CHANNELS_SH(nChannels)|BYTES_SH(2));\r
353 \r
354     // 16 bits to Lab double\r
355     Chain.hInput = cmsCreateExtendedTransform(ContextID,\r
356         nGamutPCSposition + 1,\r
357         ProfileList,\r
358         BPCList,\r
359         IntentList,\r
360         AdaptationList,\r
361         NULL, 0,\r
362         dwFormat, TYPE_Lab_DBL,\r
363         cmsFLAGS_NOCACHE);\r
364 \r
365 \r
366     // Does create the forward step. Lab double to device\r
367     dwFormat    = (CHANNELS_SH(nChannels)|BYTES_SH(2));\r
368     Chain.hForward = cmsCreateTransformTHR(ContextID,\r
369         hLab, TYPE_Lab_DBL,\r
370         hGamut, dwFormat,\r
371         INTENT_RELATIVE_COLORIMETRIC,\r
372         cmsFLAGS_NOCACHE);\r
373 \r
374     // Does create the backwards step\r
375     Chain.hReverse = cmsCreateTransformTHR(ContextID, hGamut, dwFormat,\r
376         hLab, TYPE_Lab_DBL,\r
377         INTENT_RELATIVE_COLORIMETRIC,\r
378         cmsFLAGS_NOCACHE);\r
379 \r
380 \r
381     // All ok?\r
382     if (Chain.hInput && Chain.hForward && Chain.hReverse) {\r
383 \r
384         // Go on, try to compute gamut LUT from PCS. This consist on a single channel containing\r
385         // dE when doing a transform back and forth on the colorimetric intent.\r
386 \r
387         Gamut = cmsPipelineAlloc(ContextID, 3, 1);\r
388         if (Gamut != NULL) {\r
389 \r
390             CLUT = cmsStageAllocCLut16bit(ContextID, nGridpoints, nChannels, 1, NULL);\r
391             if (!cmsPipelineInsertStage(Gamut, cmsAT_BEGIN, CLUT)) {\r
392                 cmsPipelineFree(Gamut);\r
393                 Gamut = NULL;\r
394             } \r
395             else {\r
396                 cmsStageSampleCLut16bit(CLUT, GamutSampler, (void*) &Chain, 0);\r
397             }\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