• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.internal.graphics.cam;
18 
19 import android.ravenwood.annotation.RavenwoodKeepWholeClass;
20 
21 /**
22  * An efficient algorithm for determining the closest sRGB color to a set of HCT coordinates,
23  * based on geometrical insights for finding intersections in linear RGB, CAM16, and L*a*b*.
24  *
25  * Algorithm identified and implemented by Tianguang Zhang.
26  * Copied from //java/com/google/ux/material/libmonet/hct on May 22 2022.
27  * ColorUtils/MathUtils functions that were required were added to CamUtils.
28  */
29 @RavenwoodKeepWholeClass
30 public class HctSolver {
HctSolver()31     private HctSolver() {}
32 
33     // Matrix used when converting from linear RGB to CAM16.
34     static final double[][] SCALED_DISCOUNT_FROM_LINRGB =
35             new double[][] {
36                     new double[] {
37                             0.001200833568784504, 0.002389694492170889, 0.0002795742885861124,
38                     },
39                     new double[] {
40                             0.0005891086651375999, 0.0029785502573438758, 0.0003270666104008398,
41                     },
42                     new double[] {
43                             0.00010146692491640572, 0.0005364214359186694, 0.0032979401770712076,
44                     },
45             };
46 
47     // Matrix used when converting from CAM16 to linear RGB.
48     static final double[][] LINRGB_FROM_SCALED_DISCOUNT =
49             new double[][] {
50                     new double[] {
51                             1373.2198709594231, -1100.4251190754821, -7.278681089101213,
52                     },
53                     new double[] {
54                             -271.815969077903, 559.6580465940733, -32.46047482791194,
55                     },
56                     new double[] {
57                             1.9622899599665666, -57.173814538844006, 308.7233197812385,
58                     },
59             };
60 
61     // Weights for transforming a set of linear RGB coordinates to Y in XYZ.
62     static final double[] Y_FROM_LINRGB = new double[] {0.2126, 0.7152, 0.0722};
63 
64     // Lookup table for plane in XYZ's Y axis (relative luminance) that corresponds to a given
65     // L* in L*a*b*. HCT's T is L*, and XYZ's Y is directly correlated to linear RGB, this table
66     // allows us to thus find the intersection between HCT and RGB, giving a solution to the
67     // RGB coordinates that correspond to a given set of HCT coordinates.
68     static final double[] CRITICAL_PLANES =
69             new double[] {
70                     0.015176349177441876,
71                     0.045529047532325624,
72                     0.07588174588720938,
73                     0.10623444424209313,
74                     0.13658714259697685,
75                     0.16693984095186062,
76                     0.19729253930674434,
77                     0.2276452376616281,
78                     0.2579979360165119,
79                     0.28835063437139563,
80                     0.3188300904430532,
81                     0.350925934958123,
82                     0.3848314933096426,
83                     0.42057480301049466,
84                     0.458183274052838,
85                     0.4976837250274023,
86                     0.5391024159806381,
87                     0.5824650784040898,
88                     0.6277969426914107,
89                     0.6751227633498623,
90                     0.7244668422128921,
91                     0.775853049866786,
92                     0.829304845476233,
93                     0.8848452951698498,
94                     0.942497089126609,
95                     1.0022825574869039,
96                     1.0642236851973577,
97                     1.1283421258858297,
98                     1.1946592148522128,
99                     1.2631959812511864,
100                     1.3339731595349034,
101                     1.407011200216447,
102                     1.4823302800086415,
103                     1.5599503113873272,
104                     1.6398909516233677,
105                     1.7221716113234105,
106                     1.8068114625156377,
107                     1.8938294463134073,
108                     1.9832442801866852,
109                     2.075074464868551,
110                     2.1693382909216234,
111                     2.2660538449872063,
112                     2.36523901573795,
113                     2.4669114995532007,
114                     2.5710888059345764,
115                     2.6777882626779785,
116                     2.7870270208169257,
117                     2.898822059350997,
118                     3.0131901897720907,
119                     3.1301480604002863,
120                     3.2497121605402226,
121                     3.3718988244681087,
122                     3.4967242352587946,
123                     3.624204428461639,
124                     3.754355295633311,
125                     3.887192587735158,
126                     4.022731918402185,
127                     4.160988767090289,
128                     4.301978482107941,
129                     4.445716283538092,
130                     4.592217266055746,
131                     4.741496401646282,
132                     4.893568542229298,
133                     5.048448422192488,
134                     5.20615066083972,
135                     5.3666897647573375,
136                     5.5300801301023865,
137                     5.696336044816294,
138                     5.865471690767354,
139                     6.037501145825082,
140                     6.212438385869475,
141                     6.390297286737924,
142                     6.571091626112461,
143                     6.7548350853498045,
144                     6.941541251256611,
145                     7.131223617812143,
146                     7.323895587840543,
147                     7.5195704746346665,
148                     7.7182615035334345,
149                     7.919981813454504,
150                     8.124744458384042,
151                     8.332562408825165,
152                     8.543448553206703,
153                     8.757415699253682,
154                     8.974476575321063,
155                     9.194643831691977,
156                     9.417930041841839,
157                     9.644347703669503,
158                     9.873909240696694,
159                     10.106627003236781,
160                     10.342513269534024,
161                     10.58158024687427,
162                     10.8238400726681,
163                     11.069304815507364,
164                     11.317986476196008,
165                     11.569896988756009,
166                     11.825048221409341,
167                     12.083451977536606,
168                     12.345119996613247,
169                     12.610063955123938,
170                     12.878295467455942,
171                     13.149826086772048,
172                     13.42466730586372,
173                     13.702830557985108,
174                     13.984327217668513,
175                     14.269168601521828,
176                     14.55736596900856,
177                     14.848930523210871,
178                     15.143873411576273,
179                     15.44220572664832,
180                     15.743938506781891,
181                     16.04908273684337,
182                     16.35764934889634,
183                     16.66964922287304,
184                     16.985093187232053,
185                     17.30399201960269,
186                     17.62635644741625,
187                     17.95219714852476,
188                     18.281524751807332,
189                     18.614349837764564,
190                     18.95068293910138,
191                     19.290534541298456,
192                     19.633915083172692,
193                     19.98083495742689,
194                     20.331304511189067,
195                     20.685334046541502,
196                     21.042933821039977,
197                     21.404114048223256,
198                     21.76888489811322,
199                     22.137256497705877,
200                     22.50923893145328,
201                     22.884842241736916,
202                     23.264076429332462,
203                     23.6469514538663,
204                     24.033477234264016,
205                     24.42366364919083,
206                     24.817520537484558,
207                     25.21505769858089,
208                     25.61628489293138,
209                     26.021211842414342,
210                     26.429848230738664,
211                     26.842203703840827,
212                     27.258287870275353,
213                     27.678110301598522,
214                     28.10168053274597,
215                     28.529008062403893,
216                     28.96010235337422,
217                     29.39497283293396,
218                     29.83362889318845,
219                     30.276079891419332,
220                     30.722335150426627,
221                     31.172403958865512,
222                     31.62629557157785,
223                     32.08401920991837,
224                     32.54558406207592,
225                     33.010999283389665,
226                     33.4802739966603,
227                     33.953417292456834,
228                     34.430438229418264,
229                     34.911345834551085,
230                     35.39614910352207,
231                     35.88485700094671,
232                     36.37747846067349,
233                     36.87402238606382,
234                     37.37449765026789,
235                     37.87891309649659,
236                     38.38727753828926,
237                     38.89959975977785,
238                     39.41588851594697,
239                     39.93615253289054,
240                     40.460400508064545,
241                     40.98864111053629,
242                     41.520882981230194,
243                     42.05713473317016,
244                     42.597404951718396,
245                     43.141702194811224,
246                     43.6900349931913,
247                     44.24241185063697,
248                     44.798841244188324,
249                     45.35933162437017,
250                     45.92389141541209,
251                     46.49252901546552,
252                     47.065252796817916,
253                     47.64207110610409,
254                     48.22299226451468,
255                     48.808024568002054,
256                     49.3971762874833,
257                     49.9904556690408,
258                     50.587870934119984,
259                     51.189430279724725,
260                     51.79514187861014,
261                     52.40501387947288,
262                     53.0190544071392,
263                     53.637271562750364,
264                     54.259673423945976,
265                     54.88626804504493,
266                     55.517063457223934,
267                     56.15206766869424,
268                     56.79128866487574,
269                     57.43473440856916,
270                     58.08241284012621,
271                     58.734331877617365,
272                     59.39049941699807,
273                     60.05092333227251,
274                     60.715611475655585,
275                     61.38457167773311,
276                     62.057811747619894,
277                     62.7353394731159,
278                     63.417162620860914,
279                     64.10328893648692,
280                     64.79372614476921,
281                     65.48848194977529,
282                     66.18756403501224,
283                     66.89098006357258,
284                     67.59873767827808,
285                     68.31084450182222,
286                     69.02730813691093,
287                     69.74813616640164,
288                     70.47333615344107,
289                     71.20291564160104,
290                     71.93688215501312,
291                     72.67524319850172,
292                     73.41800625771542,
293                     74.16517879925733,
294                     74.9167682708136,
295                     75.67278210128072,
296                     76.43322770089146,
297                     77.1981124613393,
298                     77.96744375590167,
299                     78.74122893956174,
300                     79.51947534912904,
301                     80.30219030335869,
302                     81.08938110306934,
303                     81.88105503125999,
304                     82.67721935322541,
305                     83.4778813166706,
306                     84.28304815182372,
307                     85.09272707154808,
308                     85.90692527145302,
309                     86.72564993000343,
310                     87.54890820862819,
311                     88.3767072518277,
312                     89.2090541872801,
313                     90.04595612594655,
314                     90.88742016217518,
315                     91.73345337380438,
316                     92.58406282226491,
317                     93.43925555268066,
318                     94.29903859396902,
319                     95.16341895893969,
320                     96.03240364439274,
321                     96.9059996312159,
322                     97.78421388448044,
323                     98.6670533535366,
324                     99.55452497210776,
325             };
326 
327     /**
328      * Sanitizes a small enough angle in radians.
329      *
330      * @param angle An angle in radians; must not deviate too much from 0.
331      * @return A coterminal angle between 0 and 2pi.
332      */
sanitizeRadians(double angle)333     static double sanitizeRadians(double angle) {
334         return (angle + Math.PI * 8) % (Math.PI * 2);
335     }
336 
337     /**
338      * Delinearizes an RGB component, returning a floating-point number.
339      *
340      * @param rgbComponent 0.0 <= rgb_component <= 100.0, represents linear R/G/B channel
341      * @return 0.0 <= output <= 255.0, color channel converted to regular RGB space
342      */
trueDelinearized(double rgbComponent)343     static double trueDelinearized(double rgbComponent) {
344         double normalized = rgbComponent / 100.0;
345         double delinearized;
346         if (normalized <= 0.0031308) {
347             delinearized = normalized * 12.92;
348         } else {
349             delinearized = 1.055 * Math.pow(normalized, 1.0 / 2.4) - 0.055;
350         }
351         return delinearized * 255.0;
352     }
353 
chromaticAdaptation(double component)354     static double chromaticAdaptation(double component) {
355         double af = Math.pow(Math.abs(component), 0.42);
356         return CamUtils.signum(component) * 400.0 * af / (af + 27.13);
357     }
358 
359     /**
360      * Returns the hue of a linear RGB color in CAM16.
361      *
362      * @param linrgb The linear RGB coordinates of a color.
363      * @return The hue of the color in CAM16, in radians.
364      */
hueOf(double[] linrgb)365     static double hueOf(double[] linrgb) {
366         // Calculate scaled discount components using in-lined matrix multiplication to avoid
367         // an array allocation.
368         double[][] matrix = SCALED_DISCOUNT_FROM_LINRGB;
369         double[] row = linrgb;
370         double rD = linrgb[0] * matrix[0][0] + row[1] * matrix[0][1] + row[2] * matrix[0][2];
371         double gD = linrgb[0] * matrix[1][0] + row[1] * matrix[1][1] + row[2] * matrix[1][2];
372         double bD = linrgb[0] * matrix[2][0] + row[1] * matrix[2][1] + row[2] * matrix[2][2];
373 
374         double rA = chromaticAdaptation(rD);
375         double gA = chromaticAdaptation(gD);
376         double bA = chromaticAdaptation(bD);
377         // redness-greenness
378         double a = (11.0 * rA + -12.0 * gA + bA) / 11.0;
379         // yellowness-blueness
380         double b = (rA + gA - 2.0 * bA) / 9.0;
381         return Math.atan2(b, a);
382     }
383 
384     /**
385      * Cyclic order is the idea that 330° → 5° → 200° is in order, but, 180° → 270° → 210° is not.
386      * Visually, A B and C are angles, and they are in cyclic order if travelling from A to C
387      * in a way that increases angle (ex. counter-clockwise if +x axis = 0 degrees and +y = 90)
388      * means you must cross B.
389      * @param a first angle in possibly cyclic triplet
390      * @param b second angle in possibly cyclic triplet
391      * @param c third angle in possibly cyclic triplet
392      * @return true if B is between A and C
393      */
areInCyclicOrder(double a, double b, double c)394     static boolean areInCyclicOrder(double a, double b, double c) {
395         double deltaAB = sanitizeRadians(b - a);
396         double deltaAC = sanitizeRadians(c - a);
397         return deltaAB < deltaAC;
398     }
399 
400     /**
401      * Find an intercept using linear interpolation.
402      *
403      * @param source The starting number.
404      * @param mid The number in the middle.
405      * @param target The ending number.
406      * @return A number t such that lerp(source, target, t) = mid.
407      */
intercept(double source, double mid, double target)408     static double intercept(double source, double mid, double target) {
409         if (target == source) {
410             return target;
411         }
412         return (mid - source) / (target - source);
413     }
414 
415     /**
416      * Linearly interpolate between two points in three dimensions.
417      *
418      * @param source three dimensions representing the starting point
419      * @param t the percentage to travel between source and target, from 0 to 1
420      * @param target three dimensions representing the end point
421      * @return three dimensions representing the point t percent from source to target.
422      */
lerpPoint(double[] source, double t, double[] target)423     static double[] lerpPoint(double[] source, double t, double[] target) {
424         return new double[] {
425                 source[0] + (target[0] - source[0]) * t,
426                 source[1] + (target[1] - source[1]) * t,
427                 source[2] + (target[2] - source[2]) * t,
428         };
429     }
430 
431     /**
432      * Intersects a segment with a plane.
433      *
434      * @param source The coordinates of point A.
435      * @param coordinate The R-, G-, or B-coordinate of the plane.
436      * @param target The coordinates of point B.
437      * @param axis The axis the plane is perpendicular with. (0: R, 1: G, 2: B)
438      * @return The intersection point of the segment AB with the plane R=coordinate, G=coordinate,
439      *     or B=coordinate
440      */
setCoordinate(double[] source, double coordinate, double[] target, int axis)441     static double[] setCoordinate(double[] source, double coordinate, double[] target, int axis) {
442         double t = intercept(source[axis], coordinate, target[axis]);
443         return lerpPoint(source, t, target);
444     }
445 
446     /** Ensure X is between 0 and 100. */
isBounded(double x)447     static boolean isBounded(double x) {
448         return 0.0 <= x && x <= 100.0;
449     }
450 
451     /**
452      * Returns the nth possible vertex of the polygonal intersection.
453      *
454      * @param y The Y value of the plane.
455      * @param n The zero-based index of the point. 0 <= n <= 11.
456      * @return The nth possible vertex of the polygonal intersection of the y plane and the RGB cube
457      *     in linear RGB coordinates, if it exists. If the possible vertex lies outside of the cube,
458      *     [-1.0, -1.0, -1.0] is returned.
459      */
nthVertex(double y, int n)460     static double[] nthVertex(double y, int n) {
461         double kR = Y_FROM_LINRGB[0];
462         double kG = Y_FROM_LINRGB[1];
463         double kB = Y_FROM_LINRGB[2];
464         double coordA = n % 4 <= 1 ? 0.0 : 100.0;
465         double coordB = n % 2 == 0 ? 0.0 : 100.0;
466         if (n < 4) {
467             double g = coordA;
468             double b = coordB;
469             double r = (y - g * kG - b * kB) / kR;
470             if (isBounded(r)) {
471                 return new double[] {r, g, b};
472             } else {
473                 return new double[] {-1.0, -1.0, -1.0};
474             }
475         } else if (n < 8) {
476             double b = coordA;
477             double r = coordB;
478             double g = (y - r * kR - b * kB) / kG;
479             if (isBounded(g)) {
480                 return new double[] {r, g, b};
481             } else {
482                 return new double[] {-1.0, -1.0, -1.0};
483             }
484         } else {
485             double r = coordA;
486             double g = coordB;
487             double b = (y - r * kR - g * kG) / kB;
488             if (isBounded(b)) {
489                 return new double[] {r, g, b};
490             } else {
491                 return new double[] {-1.0, -1.0, -1.0};
492             }
493         }
494     }
495 
496     /**
497      * Finds the segment containing the desired color.
498      *
499      * @param y The Y value of the color.
500      * @param targetHue The hue of the color.
501      * @return A list of two sets of linear RGB coordinates, each corresponding to an endpoint of
502      *     the segment containing the desired color.
503      */
bisectToSegment(double y, double targetHue)504     static double[][] bisectToSegment(double y, double targetHue) {
505         double[] left = new double[] {-1.0, -1.0, -1.0};
506         double[] right = left;
507         double leftHue = 0.0;
508         double rightHue = 0.0;
509         boolean initialized = false;
510         boolean uncut = true;
511         for (int n = 0; n < 12; n++) {
512             double[] mid = nthVertex(y, n);
513             if (mid[0] < 0) {
514                 continue;
515             }
516             double midHue = hueOf(mid);
517             if (!initialized) {
518                 left = mid;
519                 right = mid;
520                 leftHue = midHue;
521                 rightHue = midHue;
522                 initialized = true;
523                 continue;
524             }
525             if (uncut || areInCyclicOrder(leftHue, midHue, rightHue)) {
526                 uncut = false;
527                 if (areInCyclicOrder(leftHue, targetHue, midHue)) {
528                     right = mid;
529                     rightHue = midHue;
530                 } else {
531                     left = mid;
532                     leftHue = midHue;
533                 }
534             }
535         }
536         return new double[][] {left, right};
537     }
538 
criticalPlaneBelow(double x)539     static int criticalPlaneBelow(double x) {
540         return (int) Math.floor(x - 0.5);
541     }
542 
criticalPlaneAbove(double x)543     static int criticalPlaneAbove(double x) {
544         return (int) Math.ceil(x - 0.5);
545     }
546 
547     /**
548      * Finds a color with the given Y and hue on the boundary of the cube.
549      *
550      * @param y The Y value of the color.
551      * @param targetHue The hue of the color.
552      * @return The desired color, in linear RGB coordinates.
553      */
bisectToLimit(double y, double targetHue)554     static int bisectToLimit(double y, double targetHue) {
555         double[][] segment = bisectToSegment(y, targetHue);
556         double[] left = segment[0];
557         double leftHue = hueOf(left);
558         double[] right = segment[1];
559         for (int axis = 0; axis < 3; axis++) {
560             if (left[axis] != right[axis]) {
561                 int lPlane = -1;
562                 int rPlane = 255;
563                 if (left[axis] < right[axis]) {
564                     lPlane = criticalPlaneBelow(trueDelinearized(left[axis]));
565                     rPlane = criticalPlaneAbove(trueDelinearized(right[axis]));
566                 } else {
567                     lPlane = criticalPlaneAbove(trueDelinearized(left[axis]));
568                     rPlane = criticalPlaneBelow(trueDelinearized(right[axis]));
569                 }
570                 for (int i = 0; i < 8; i++) {
571                     if (Math.abs(rPlane - lPlane) <= 1) {
572                         break;
573                     } else {
574                         int mPlane = (int) Math.floor((lPlane + rPlane) / 2.0);
575                         double midPlaneCoordinate = CRITICAL_PLANES[mPlane];
576                         double[] mid = setCoordinate(left, midPlaneCoordinate, right, axis);
577                         double midHue = hueOf(mid);
578                         if (areInCyclicOrder(leftHue, targetHue, midHue)) {
579                             right = mid;
580                             rPlane = mPlane;
581                         } else {
582                             left = mid;
583                             leftHue = midHue;
584                             lPlane = mPlane;
585                         }
586                     }
587                 }
588             }
589         }
590         return CamUtils.argbFromLinrgbComponents((left[0] + right[0]) / 2,
591                 (left[1] + right[1]) / 2, (left[2] + right[2]) / 2);
592     }
593 
594     /** Equation used in CAM16 conversion that removes the effect of chromatic adaptation. */
inverseChromaticAdaptation(double adapted)595     static double inverseChromaticAdaptation(double adapted) {
596         double adaptedAbs = Math.abs(adapted);
597         double base = Math.max(0, 27.13 * adaptedAbs / (400.0 - adaptedAbs));
598         return CamUtils.signum(adapted) * Math.pow(base, 1.0 / 0.42);
599     }
600 
601     /**
602      * Finds a color with the given hue, chroma, and Y.
603      *
604      * @param hueRadians The desired hue in radians.
605      * @param chroma The desired chroma.
606      * @param y The desired Y.
607      * @return The desired color as a hexadecimal integer, if found; 0 otherwise.
608      */
findResultByJ(double hueRadians, double chroma, double y)609     static int findResultByJ(double hueRadians, double chroma, double y) {
610         // Initial estimate of j.
611         double j = Math.sqrt(y) * 11.0;
612         // ===========================================================
613         // Operations inlined from Cam16 to avoid repeated calculation
614         // ===========================================================
615         Frame viewingConditions = Frame.DEFAULT;
616         double tInnerCoeff = 1 / Math.pow(1.64 - Math.pow(0.29, viewingConditions.getN()), 0.73);
617         double eHue = 0.25 * (Math.cos(hueRadians + 2.0) + 3.8);
618         double p1 = eHue * (50000.0 / 13.0) * viewingConditions.getNc()
619                 * viewingConditions.getNcb();
620         double hSin = Math.sin(hueRadians);
621         double hCos = Math.cos(hueRadians);
622         for (int iterationRound = 0; iterationRound < 5; iterationRound++) {
623             // ===========================================================
624             // Operations inlined from Cam16 to avoid repeated calculation
625             // ===========================================================
626             double jNormalized = j / 100.0;
627             double alpha = chroma == 0.0 || j == 0.0 ? 0.0 : chroma / Math.sqrt(jNormalized);
628             double t = Math.pow(alpha * tInnerCoeff, 1.0 / 0.9);
629             double acExponent = 1.0 / viewingConditions.getC() / viewingConditions.getZ();
630             double ac = viewingConditions.getAw() * Math.pow(jNormalized, acExponent);
631             double p2 = ac / viewingConditions.getNbb();
632             double gamma = 23.0 * (p2 + 0.305) * t / (23.0 * p1 + 11 * t * hCos + 108.0 * t * hSin);
633             double a = gamma * hCos;
634             double b = gamma * hSin;
635             double rA = (460.0 * p2 + 451.0 * a + 288.0 * b) / 1403.0;
636             double gA = (460.0 * p2 - 891.0 * a - 261.0 * b) / 1403.0;
637             double bA = (460.0 * p2 - 220.0 * a - 6300.0 * b) / 1403.0;
638             double rCScaled = inverseChromaticAdaptation(rA);
639             double gCScaled = inverseChromaticAdaptation(gA);
640             double bCScaled = inverseChromaticAdaptation(bA);
641             double[][] matrix = LINRGB_FROM_SCALED_DISCOUNT;
642             double linrgbR = rCScaled * matrix[0][0] + gCScaled * matrix[0][1]
643                     + bCScaled * matrix[0][2];
644             double linrgbG = rCScaled * matrix[1][0] + gCScaled * matrix[1][1]
645                     + bCScaled * matrix[1][2];
646             double linrgbB = rCScaled * matrix[2][0] + gCScaled * matrix[2][1]
647                     + bCScaled * matrix[2][2];
648             // ===========================================================
649             // Operations inlined from Cam16 to avoid repeated calculation
650             // ===========================================================
651             if (linrgbR < 0 || linrgbG < 0 || linrgbB < 0) {
652                 return 0;
653             }
654             double kR = Y_FROM_LINRGB[0];
655             double kG = Y_FROM_LINRGB[1];
656             double kB = Y_FROM_LINRGB[2];
657             double fnj = kR * linrgbR + kG * linrgbG + kB * linrgbB;
658             if (fnj <= 0) {
659                 return 0;
660             }
661             if (iterationRound == 4 || Math.abs(fnj - y) < 0.002) {
662                 if (linrgbR > 100.01 || linrgbG > 100.01 || linrgbB > 100.01) {
663                     return 0;
664                 }
665                 return CamUtils.argbFromLinrgbComponents(linrgbR, linrgbG, linrgbB);
666             }
667             // Iterates with Newton method,
668             // Using 2 * fn(j) / j as the approximation of fn'(j)
669             j = j - (fnj - y) * j / (2 * fnj);
670         }
671         return 0;
672     }
673 
674     /**
675      * Finds an sRGB color with the given hue, chroma, and L*, if possible.
676      *
677      * @param hueDegrees The desired hue, in degrees.
678      * @param chroma The desired chroma.
679      * @param lstar The desired L*.
680      * @return A hexadecimal representing the sRGB color. The color has sufficiently close hue,
681      *     chroma, and L* to the desired values, if possible; otherwise, the hue and L* will be
682      *     sufficiently close, and chroma will be maximized.
683      */
solveToInt(double hueDegrees, double chroma, double lstar)684     public static int solveToInt(double hueDegrees, double chroma, double lstar) {
685         if (chroma < 0.0001 || lstar < 0.0001 || lstar > 99.9999) {
686             return CamUtils.argbFromLstar(lstar);
687         }
688         hueDegrees = sanitizeDegreesDouble(hueDegrees);
689         double hueRadians = Math.toRadians(hueDegrees);
690         double y = CamUtils.yFromLstar(lstar);
691         int exactAnswer = findResultByJ(hueRadians, chroma, y);
692         if (exactAnswer != 0) {
693             return exactAnswer;
694         }
695         return bisectToLimit(y, hueRadians);
696     }
697 
698     /**
699      * Sanitizes a degree measure as a floating-point number.
700      *
701      * @return a degree measure between 0.0 (inclusive) and 360.0 (exclusive).
702      */
sanitizeDegreesDouble(double degrees)703     public static double sanitizeDegreesDouble(double degrees) {
704         degrees = degrees % 360.0;
705         if (degrees < 0) {
706             degrees = degrees + 360.0;
707         }
708         return degrees;
709     }
710 
711     /**
712      * Finds an sRGB color with the given hue, chroma, and L*, if possible.
713      *
714      * @param hueDegrees The desired hue, in degrees.
715      * @param chroma The desired chroma.
716      * @param lstar The desired L*.
717      * @return An CAM16 object representing the sRGB color. The color has sufficiently close hue,
718      *     chroma, and L* to the desired values, if possible; otherwise, the hue and L* will be
719      *     sufficiently close, and chroma will be maximized.
720      */
solveToCam(double hueDegrees, double chroma, double lstar)721     public static Cam solveToCam(double hueDegrees, double chroma, double lstar) {
722         return Cam.fromInt(solveToInt(hueDegrees, chroma, lstar));
723     }
724 }
725