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