xref: /aosp_15_r20/external/libmonet/hct/Cam16.java (revision 970e10460f970939fd510dd6ad3e0d65908272e3)
1*970e1046SAndroid Build Coastguard Worker /*
2*970e1046SAndroid Build Coastguard Worker  * Copyright 2021 Google LLC
3*970e1046SAndroid Build Coastguard Worker  *
4*970e1046SAndroid Build Coastguard Worker  * Licensed under the Apache License, Version 2.0 (the "License");
5*970e1046SAndroid Build Coastguard Worker  * you may not use this file except in compliance with the License.
6*970e1046SAndroid Build Coastguard Worker  * You may obtain a copy of the License at
7*970e1046SAndroid Build Coastguard Worker  *
8*970e1046SAndroid Build Coastguard Worker  *      http://www.apache.org/licenses/LICENSE-2.0
9*970e1046SAndroid Build Coastguard Worker  *
10*970e1046SAndroid Build Coastguard Worker  * Unless required by applicable law or agreed to in writing, software
11*970e1046SAndroid Build Coastguard Worker  * distributed under the License is distributed on an "AS IS" BASIS,
12*970e1046SAndroid Build Coastguard Worker  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13*970e1046SAndroid Build Coastguard Worker  * See the License for the specific language governing permissions and
14*970e1046SAndroid Build Coastguard Worker  * limitations under the License.
15*970e1046SAndroid Build Coastguard Worker  */
16*970e1046SAndroid Build Coastguard Worker 
17*970e1046SAndroid Build Coastguard Worker package com.google.ux.material.libmonet.hct;
18*970e1046SAndroid Build Coastguard Worker 
19*970e1046SAndroid Build Coastguard Worker import static java.lang.Math.max;
20*970e1046SAndroid Build Coastguard Worker 
21*970e1046SAndroid Build Coastguard Worker import com.google.ux.material.libmonet.utils.ColorUtils;
22*970e1046SAndroid Build Coastguard Worker 
23*970e1046SAndroid Build Coastguard Worker /**
24*970e1046SAndroid Build Coastguard Worker  * CAM16, a color appearance model. Colors are not just defined by their hex code, but rather, a hex
25*970e1046SAndroid Build Coastguard Worker  * code and viewing conditions.
26*970e1046SAndroid Build Coastguard Worker  *
27*970e1046SAndroid Build Coastguard Worker  * <p>CAM16 instances also have coordinates in the CAM16-UCS space, called J*, a*, b*, or jstar,
28*970e1046SAndroid Build Coastguard Worker  * astar, bstar in code. CAM16-UCS is included in the CAM16 specification, and should be used when
29*970e1046SAndroid Build Coastguard Worker  * measuring distances between colors.
30*970e1046SAndroid Build Coastguard Worker  *
31*970e1046SAndroid Build Coastguard Worker  * <p>In traditional color spaces, a color can be identified solely by the observer's measurement of
32*970e1046SAndroid Build Coastguard Worker  * the color. Color appearance models such as CAM16 also use information about the environment where
33*970e1046SAndroid Build Coastguard Worker  * the color was observed, known as the viewing conditions.
34*970e1046SAndroid Build Coastguard Worker  *
35*970e1046SAndroid Build Coastguard Worker  * <p>For example, white under the traditional assumption of a midday sun white point is accurately
36*970e1046SAndroid Build Coastguard Worker  * measured as a slightly chromatic blue by CAM16. (roughly, hue 203, chroma 3, lightness 100)
37*970e1046SAndroid Build Coastguard Worker  */
38*970e1046SAndroid Build Coastguard Worker public final class Cam16 {
39*970e1046SAndroid Build Coastguard Worker   // Transforms XYZ color space coordinates to 'cone'/'RGB' responses in CAM16.
40*970e1046SAndroid Build Coastguard Worker   static final double[][] XYZ_TO_CAM16RGB = {
41*970e1046SAndroid Build Coastguard Worker     {0.401288, 0.650173, -0.051461},
42*970e1046SAndroid Build Coastguard Worker     {-0.250268, 1.204414, 0.045854},
43*970e1046SAndroid Build Coastguard Worker     {-0.002079, 0.048952, 0.953127}
44*970e1046SAndroid Build Coastguard Worker   };
45*970e1046SAndroid Build Coastguard Worker 
46*970e1046SAndroid Build Coastguard Worker   // Transforms 'cone'/'RGB' responses in CAM16 to XYZ color space coordinates.
47*970e1046SAndroid Build Coastguard Worker   static final double[][] CAM16RGB_TO_XYZ = {
48*970e1046SAndroid Build Coastguard Worker     {1.8620678, -1.0112547, 0.14918678},
49*970e1046SAndroid Build Coastguard Worker     {0.38752654, 0.62144744, -0.00897398},
50*970e1046SAndroid Build Coastguard Worker     {-0.01584150, -0.03412294, 1.0499644}
51*970e1046SAndroid Build Coastguard Worker   };
52*970e1046SAndroid Build Coastguard Worker 
53*970e1046SAndroid Build Coastguard Worker   // CAM16 color dimensions, see getters for documentation.
54*970e1046SAndroid Build Coastguard Worker   private final double hue;
55*970e1046SAndroid Build Coastguard Worker   private final double chroma;
56*970e1046SAndroid Build Coastguard Worker   private final double j;
57*970e1046SAndroid Build Coastguard Worker   private final double q;
58*970e1046SAndroid Build Coastguard Worker   private final double m;
59*970e1046SAndroid Build Coastguard Worker   private final double s;
60*970e1046SAndroid Build Coastguard Worker 
61*970e1046SAndroid Build Coastguard Worker   // Coordinates in UCS space. Used to determine color distance, like delta E equations in L*a*b*.
62*970e1046SAndroid Build Coastguard Worker   private final double jstar;
63*970e1046SAndroid Build Coastguard Worker   private final double astar;
64*970e1046SAndroid Build Coastguard Worker   private final double bstar;
65*970e1046SAndroid Build Coastguard Worker 
66*970e1046SAndroid Build Coastguard Worker   // Avoid allocations during conversion by pre-allocating an array.
67*970e1046SAndroid Build Coastguard Worker   private final double[] tempArray = new double[] {0.0, 0.0, 0.0};
68*970e1046SAndroid Build Coastguard Worker 
69*970e1046SAndroid Build Coastguard Worker   /**
70*970e1046SAndroid Build Coastguard Worker    * CAM16 instances also have coordinates in the CAM16-UCS space, called J*, a*, b*, or jstar,
71*970e1046SAndroid Build Coastguard Worker    * astar, bstar in code. CAM16-UCS is included in the CAM16 specification, and is used to measure
72*970e1046SAndroid Build Coastguard Worker    * distances between colors.
73*970e1046SAndroid Build Coastguard Worker    */
distance(Cam16 other)74*970e1046SAndroid Build Coastguard Worker   public double distance(Cam16 other) {
75*970e1046SAndroid Build Coastguard Worker     double dJ = getJstar() - other.getJstar();
76*970e1046SAndroid Build Coastguard Worker     double dA = getAstar() - other.getAstar();
77*970e1046SAndroid Build Coastguard Worker     double dB = getBstar() - other.getBstar();
78*970e1046SAndroid Build Coastguard Worker     double dEPrime = Math.sqrt(dJ * dJ + dA * dA + dB * dB);
79*970e1046SAndroid Build Coastguard Worker     double dE = 1.41 * Math.pow(dEPrime, 0.63);
80*970e1046SAndroid Build Coastguard Worker     return dE;
81*970e1046SAndroid Build Coastguard Worker   }
82*970e1046SAndroid Build Coastguard Worker 
83*970e1046SAndroid Build Coastguard Worker   /** Hue in CAM16 */
getHue()84*970e1046SAndroid Build Coastguard Worker   public double getHue() {
85*970e1046SAndroid Build Coastguard Worker     return hue;
86*970e1046SAndroid Build Coastguard Worker   }
87*970e1046SAndroid Build Coastguard Worker 
88*970e1046SAndroid Build Coastguard Worker   /** Chroma in CAM16 */
getChroma()89*970e1046SAndroid Build Coastguard Worker   public double getChroma() {
90*970e1046SAndroid Build Coastguard Worker     return chroma;
91*970e1046SAndroid Build Coastguard Worker   }
92*970e1046SAndroid Build Coastguard Worker 
93*970e1046SAndroid Build Coastguard Worker   /** Lightness in CAM16 */
getJ()94*970e1046SAndroid Build Coastguard Worker   public double getJ() {
95*970e1046SAndroid Build Coastguard Worker     return j;
96*970e1046SAndroid Build Coastguard Worker   }
97*970e1046SAndroid Build Coastguard Worker 
98*970e1046SAndroid Build Coastguard Worker   /**
99*970e1046SAndroid Build Coastguard Worker    * Brightness in CAM16.
100*970e1046SAndroid Build Coastguard Worker    *
101*970e1046SAndroid Build Coastguard Worker    * <p>Prefer lightness, brightness is an absolute quantity. For example, a sheet of white paper is
102*970e1046SAndroid Build Coastguard Worker    * much brighter viewed in sunlight than in indoor light, but it is the lightest object under any
103*970e1046SAndroid Build Coastguard Worker    * lighting.
104*970e1046SAndroid Build Coastguard Worker    */
getQ()105*970e1046SAndroid Build Coastguard Worker   public double getQ() {
106*970e1046SAndroid Build Coastguard Worker     return q;
107*970e1046SAndroid Build Coastguard Worker   }
108*970e1046SAndroid Build Coastguard Worker 
109*970e1046SAndroid Build Coastguard Worker   /**
110*970e1046SAndroid Build Coastguard Worker    * Colorfulness in CAM16.
111*970e1046SAndroid Build Coastguard Worker    *
112*970e1046SAndroid Build Coastguard Worker    * <p>Prefer chroma, colorfulness is an absolute quantity. For example, a yellow toy car is much
113*970e1046SAndroid Build Coastguard Worker    * more colorful outside than inside, but it has the same chroma in both environments.
114*970e1046SAndroid Build Coastguard Worker    */
getM()115*970e1046SAndroid Build Coastguard Worker   public double getM() {
116*970e1046SAndroid Build Coastguard Worker     return m;
117*970e1046SAndroid Build Coastguard Worker   }
118*970e1046SAndroid Build Coastguard Worker 
119*970e1046SAndroid Build Coastguard Worker   /**
120*970e1046SAndroid Build Coastguard Worker    * Saturation in CAM16.
121*970e1046SAndroid Build Coastguard Worker    *
122*970e1046SAndroid Build Coastguard Worker    * <p>Colorfulness in proportion to brightness. Prefer chroma, saturation measures colorfulness
123*970e1046SAndroid Build Coastguard Worker    * relative to the color's own brightness, where chroma is colorfulness relative to white.
124*970e1046SAndroid Build Coastguard Worker    */
getS()125*970e1046SAndroid Build Coastguard Worker   public double getS() {
126*970e1046SAndroid Build Coastguard Worker     return s;
127*970e1046SAndroid Build Coastguard Worker   }
128*970e1046SAndroid Build Coastguard Worker 
129*970e1046SAndroid Build Coastguard Worker   /** Lightness coordinate in CAM16-UCS */
getJstar()130*970e1046SAndroid Build Coastguard Worker   public double getJstar() {
131*970e1046SAndroid Build Coastguard Worker     return jstar;
132*970e1046SAndroid Build Coastguard Worker   }
133*970e1046SAndroid Build Coastguard Worker 
134*970e1046SAndroid Build Coastguard Worker   /** a* coordinate in CAM16-UCS */
getAstar()135*970e1046SAndroid Build Coastguard Worker   public double getAstar() {
136*970e1046SAndroid Build Coastguard Worker     return astar;
137*970e1046SAndroid Build Coastguard Worker   }
138*970e1046SAndroid Build Coastguard Worker 
139*970e1046SAndroid Build Coastguard Worker   /** b* coordinate in CAM16-UCS */
getBstar()140*970e1046SAndroid Build Coastguard Worker   public double getBstar() {
141*970e1046SAndroid Build Coastguard Worker     return bstar;
142*970e1046SAndroid Build Coastguard Worker   }
143*970e1046SAndroid Build Coastguard Worker 
144*970e1046SAndroid Build Coastguard Worker   /**
145*970e1046SAndroid Build Coastguard Worker    * All of the CAM16 dimensions can be calculated from 3 of the dimensions, in the following
146*970e1046SAndroid Build Coastguard Worker    * combinations: - {j or q} and {c, m, or s} and hue - jstar, astar, bstar Prefer using a static
147*970e1046SAndroid Build Coastguard Worker    * method that constructs from 3 of those dimensions. This constructor is intended for those
148*970e1046SAndroid Build Coastguard Worker    * methods to use to return all possible dimensions.
149*970e1046SAndroid Build Coastguard Worker    *
150*970e1046SAndroid Build Coastguard Worker    * @param hue for example, red, orange, yellow, green, etc.
151*970e1046SAndroid Build Coastguard Worker    * @param chroma informally, colorfulness / color intensity. like saturation in HSL, except
152*970e1046SAndroid Build Coastguard Worker    *     perceptually accurate.
153*970e1046SAndroid Build Coastguard Worker    * @param j lightness
154*970e1046SAndroid Build Coastguard Worker    * @param q brightness; ratio of lightness to white point's lightness
155*970e1046SAndroid Build Coastguard Worker    * @param m colorfulness
156*970e1046SAndroid Build Coastguard Worker    * @param s saturation; ratio of chroma to white point's chroma
157*970e1046SAndroid Build Coastguard Worker    * @param jstar CAM16-UCS J coordinate
158*970e1046SAndroid Build Coastguard Worker    * @param astar CAM16-UCS a coordinate
159*970e1046SAndroid Build Coastguard Worker    * @param bstar CAM16-UCS b coordinate
160*970e1046SAndroid Build Coastguard Worker    */
Cam16( double hue, double chroma, double j, double q, double m, double s, double jstar, double astar, double bstar)161*970e1046SAndroid Build Coastguard Worker   private Cam16(
162*970e1046SAndroid Build Coastguard Worker       double hue,
163*970e1046SAndroid Build Coastguard Worker       double chroma,
164*970e1046SAndroid Build Coastguard Worker       double j,
165*970e1046SAndroid Build Coastguard Worker       double q,
166*970e1046SAndroid Build Coastguard Worker       double m,
167*970e1046SAndroid Build Coastguard Worker       double s,
168*970e1046SAndroid Build Coastguard Worker       double jstar,
169*970e1046SAndroid Build Coastguard Worker       double astar,
170*970e1046SAndroid Build Coastguard Worker       double bstar) {
171*970e1046SAndroid Build Coastguard Worker     this.hue = hue;
172*970e1046SAndroid Build Coastguard Worker     this.chroma = chroma;
173*970e1046SAndroid Build Coastguard Worker     this.j = j;
174*970e1046SAndroid Build Coastguard Worker     this.q = q;
175*970e1046SAndroid Build Coastguard Worker     this.m = m;
176*970e1046SAndroid Build Coastguard Worker     this.s = s;
177*970e1046SAndroid Build Coastguard Worker     this.jstar = jstar;
178*970e1046SAndroid Build Coastguard Worker     this.astar = astar;
179*970e1046SAndroid Build Coastguard Worker     this.bstar = bstar;
180*970e1046SAndroid Build Coastguard Worker   }
181*970e1046SAndroid Build Coastguard Worker 
182*970e1046SAndroid Build Coastguard Worker   /**
183*970e1046SAndroid Build Coastguard Worker    * Create a CAM16 color from a color, assuming the color was viewed in default viewing conditions.
184*970e1046SAndroid Build Coastguard Worker    *
185*970e1046SAndroid Build Coastguard Worker    * @param argb ARGB representation of a color.
186*970e1046SAndroid Build Coastguard Worker    */
fromInt(int argb)187*970e1046SAndroid Build Coastguard Worker   public static Cam16 fromInt(int argb) {
188*970e1046SAndroid Build Coastguard Worker     return fromIntInViewingConditions(argb, ViewingConditions.DEFAULT);
189*970e1046SAndroid Build Coastguard Worker   }
190*970e1046SAndroid Build Coastguard Worker 
191*970e1046SAndroid Build Coastguard Worker   /**
192*970e1046SAndroid Build Coastguard Worker    * Create a CAM16 color from a color in defined viewing conditions.
193*970e1046SAndroid Build Coastguard Worker    *
194*970e1046SAndroid Build Coastguard Worker    * @param argb ARGB representation of a color.
195*970e1046SAndroid Build Coastguard Worker    * @param viewingConditions Information about the environment where the color was observed.
196*970e1046SAndroid Build Coastguard Worker    */
197*970e1046SAndroid Build Coastguard Worker   // The RGB => XYZ conversion matrix elements are derived scientific constants. While the values
198*970e1046SAndroid Build Coastguard Worker   // may differ at runtime due to floating point imprecision, keeping the values the same, and
199*970e1046SAndroid Build Coastguard Worker   // accurate, across implementations takes precedence.
200*970e1046SAndroid Build Coastguard Worker   @SuppressWarnings("FloatingPointLiteralPrecision")
fromIntInViewingConditions(int argb, ViewingConditions viewingConditions)201*970e1046SAndroid Build Coastguard Worker   static Cam16 fromIntInViewingConditions(int argb, ViewingConditions viewingConditions) {
202*970e1046SAndroid Build Coastguard Worker     // Transform ARGB int to XYZ
203*970e1046SAndroid Build Coastguard Worker     int red = (argb & 0x00ff0000) >> 16;
204*970e1046SAndroid Build Coastguard Worker     int green = (argb & 0x0000ff00) >> 8;
205*970e1046SAndroid Build Coastguard Worker     int blue = (argb & 0x000000ff);
206*970e1046SAndroid Build Coastguard Worker     double redL = ColorUtils.linearized(red);
207*970e1046SAndroid Build Coastguard Worker     double greenL = ColorUtils.linearized(green);
208*970e1046SAndroid Build Coastguard Worker     double blueL = ColorUtils.linearized(blue);
209*970e1046SAndroid Build Coastguard Worker     double x = 0.41233895 * redL + 0.35762064 * greenL + 0.18051042 * blueL;
210*970e1046SAndroid Build Coastguard Worker     double y = 0.2126 * redL + 0.7152 * greenL + 0.0722 * blueL;
211*970e1046SAndroid Build Coastguard Worker     double z = 0.01932141 * redL + 0.11916382 * greenL + 0.95034478 * blueL;
212*970e1046SAndroid Build Coastguard Worker 
213*970e1046SAndroid Build Coastguard Worker     return fromXyzInViewingConditions(x, y, z, viewingConditions);
214*970e1046SAndroid Build Coastguard Worker   }
215*970e1046SAndroid Build Coastguard Worker 
fromXyzInViewingConditions( double x, double y, double z, ViewingConditions viewingConditions)216*970e1046SAndroid Build Coastguard Worker   static Cam16 fromXyzInViewingConditions(
217*970e1046SAndroid Build Coastguard Worker       double x, double y, double z, ViewingConditions viewingConditions) {
218*970e1046SAndroid Build Coastguard Worker     // Transform XYZ to 'cone'/'rgb' responses
219*970e1046SAndroid Build Coastguard Worker     double[][] matrix = XYZ_TO_CAM16RGB;
220*970e1046SAndroid Build Coastguard Worker     double rT = (x * matrix[0][0]) + (y * matrix[0][1]) + (z * matrix[0][2]);
221*970e1046SAndroid Build Coastguard Worker     double gT = (x * matrix[1][0]) + (y * matrix[1][1]) + (z * matrix[1][2]);
222*970e1046SAndroid Build Coastguard Worker     double bT = (x * matrix[2][0]) + (y * matrix[2][1]) + (z * matrix[2][2]);
223*970e1046SAndroid Build Coastguard Worker 
224*970e1046SAndroid Build Coastguard Worker     // Discount illuminant
225*970e1046SAndroid Build Coastguard Worker     double rD = viewingConditions.getRgbD()[0] * rT;
226*970e1046SAndroid Build Coastguard Worker     double gD = viewingConditions.getRgbD()[1] * gT;
227*970e1046SAndroid Build Coastguard Worker     double bD = viewingConditions.getRgbD()[2] * bT;
228*970e1046SAndroid Build Coastguard Worker 
229*970e1046SAndroid Build Coastguard Worker     // Chromatic adaptation
230*970e1046SAndroid Build Coastguard Worker     double rAF = Math.pow(viewingConditions.getFl() * Math.abs(rD) / 100.0, 0.42);
231*970e1046SAndroid Build Coastguard Worker     double gAF = Math.pow(viewingConditions.getFl() * Math.abs(gD) / 100.0, 0.42);
232*970e1046SAndroid Build Coastguard Worker     double bAF = Math.pow(viewingConditions.getFl() * Math.abs(bD) / 100.0, 0.42);
233*970e1046SAndroid Build Coastguard Worker     double rA = Math.signum(rD) * 400.0 * rAF / (rAF + 27.13);
234*970e1046SAndroid Build Coastguard Worker     double gA = Math.signum(gD) * 400.0 * gAF / (gAF + 27.13);
235*970e1046SAndroid Build Coastguard Worker     double bA = Math.signum(bD) * 400.0 * bAF / (bAF + 27.13);
236*970e1046SAndroid Build Coastguard Worker 
237*970e1046SAndroid Build Coastguard Worker     // redness-greenness
238*970e1046SAndroid Build Coastguard Worker     double a = (11.0 * rA + -12.0 * gA + bA) / 11.0;
239*970e1046SAndroid Build Coastguard Worker     // yellowness-blueness
240*970e1046SAndroid Build Coastguard Worker     double b = (rA + gA - 2.0 * bA) / 9.0;
241*970e1046SAndroid Build Coastguard Worker 
242*970e1046SAndroid Build Coastguard Worker     // auxiliary components
243*970e1046SAndroid Build Coastguard Worker     double u = (20.0 * rA + 20.0 * gA + 21.0 * bA) / 20.0;
244*970e1046SAndroid Build Coastguard Worker     double p2 = (40.0 * rA + 20.0 * gA + bA) / 20.0;
245*970e1046SAndroid Build Coastguard Worker 
246*970e1046SAndroid Build Coastguard Worker     // hue
247*970e1046SAndroid Build Coastguard Worker     double atan2 = Math.atan2(b, a);
248*970e1046SAndroid Build Coastguard Worker     double atanDegrees = Math.toDegrees(atan2);
249*970e1046SAndroid Build Coastguard Worker     double hue =
250*970e1046SAndroid Build Coastguard Worker         atanDegrees < 0
251*970e1046SAndroid Build Coastguard Worker             ? atanDegrees + 360.0
252*970e1046SAndroid Build Coastguard Worker             : atanDegrees >= 360 ? atanDegrees - 360.0 : atanDegrees;
253*970e1046SAndroid Build Coastguard Worker     double hueRadians = Math.toRadians(hue);
254*970e1046SAndroid Build Coastguard Worker 
255*970e1046SAndroid Build Coastguard Worker     // achromatic response to color
256*970e1046SAndroid Build Coastguard Worker     double ac = p2 * viewingConditions.getNbb();
257*970e1046SAndroid Build Coastguard Worker 
258*970e1046SAndroid Build Coastguard Worker     // CAM16 lightness and brightness
259*970e1046SAndroid Build Coastguard Worker     double j =
260*970e1046SAndroid Build Coastguard Worker         100.0
261*970e1046SAndroid Build Coastguard Worker             * Math.pow(
262*970e1046SAndroid Build Coastguard Worker                 ac / viewingConditions.getAw(),
263*970e1046SAndroid Build Coastguard Worker                 viewingConditions.getC() * viewingConditions.getZ());
264*970e1046SAndroid Build Coastguard Worker     double q =
265*970e1046SAndroid Build Coastguard Worker         4.0
266*970e1046SAndroid Build Coastguard Worker             / viewingConditions.getC()
267*970e1046SAndroid Build Coastguard Worker             * Math.sqrt(j / 100.0)
268*970e1046SAndroid Build Coastguard Worker             * (viewingConditions.getAw() + 4.0)
269*970e1046SAndroid Build Coastguard Worker             * viewingConditions.getFlRoot();
270*970e1046SAndroid Build Coastguard Worker 
271*970e1046SAndroid Build Coastguard Worker     // CAM16 chroma, colorfulness, and saturation.
272*970e1046SAndroid Build Coastguard Worker     double huePrime = (hue < 20.14) ? hue + 360 : hue;
273*970e1046SAndroid Build Coastguard Worker     double eHue = 0.25 * (Math.cos(Math.toRadians(huePrime) + 2.0) + 3.8);
274*970e1046SAndroid Build Coastguard Worker     double p1 = 50000.0 / 13.0 * eHue * viewingConditions.getNc() * viewingConditions.getNcb();
275*970e1046SAndroid Build Coastguard Worker     double t = p1 * Math.hypot(a, b) / (u + 0.305);
276*970e1046SAndroid Build Coastguard Worker     double alpha =
277*970e1046SAndroid Build Coastguard Worker         Math.pow(1.64 - Math.pow(0.29, viewingConditions.getN()), 0.73) * Math.pow(t, 0.9);
278*970e1046SAndroid Build Coastguard Worker     // CAM16 chroma, colorfulness, saturation
279*970e1046SAndroid Build Coastguard Worker     double c = alpha * Math.sqrt(j / 100.0);
280*970e1046SAndroid Build Coastguard Worker     double m = c * viewingConditions.getFlRoot();
281*970e1046SAndroid Build Coastguard Worker     double s =
282*970e1046SAndroid Build Coastguard Worker         50.0 * Math.sqrt((alpha * viewingConditions.getC()) / (viewingConditions.getAw() + 4.0));
283*970e1046SAndroid Build Coastguard Worker 
284*970e1046SAndroid Build Coastguard Worker     // CAM16-UCS components
285*970e1046SAndroid Build Coastguard Worker     double jstar = (1.0 + 100.0 * 0.007) * j / (1.0 + 0.007 * j);
286*970e1046SAndroid Build Coastguard Worker     double mstar = 1.0 / 0.0228 * Math.log1p(0.0228 * m);
287*970e1046SAndroid Build Coastguard Worker     double astar = mstar * Math.cos(hueRadians);
288*970e1046SAndroid Build Coastguard Worker     double bstar = mstar * Math.sin(hueRadians);
289*970e1046SAndroid Build Coastguard Worker 
290*970e1046SAndroid Build Coastguard Worker     return new Cam16(hue, c, j, q, m, s, jstar, astar, bstar);
291*970e1046SAndroid Build Coastguard Worker   }
292*970e1046SAndroid Build Coastguard Worker 
293*970e1046SAndroid Build Coastguard Worker   /**
294*970e1046SAndroid Build Coastguard Worker    * @param j CAM16 lightness
295*970e1046SAndroid Build Coastguard Worker    * @param c CAM16 chroma
296*970e1046SAndroid Build Coastguard Worker    * @param h CAM16 hue
297*970e1046SAndroid Build Coastguard Worker    */
fromJch(double j, double c, double h)298*970e1046SAndroid Build Coastguard Worker   static Cam16 fromJch(double j, double c, double h) {
299*970e1046SAndroid Build Coastguard Worker     return fromJchInViewingConditions(j, c, h, ViewingConditions.DEFAULT);
300*970e1046SAndroid Build Coastguard Worker   }
301*970e1046SAndroid Build Coastguard Worker 
302*970e1046SAndroid Build Coastguard Worker   /**
303*970e1046SAndroid Build Coastguard Worker    * @param j CAM16 lightness
304*970e1046SAndroid Build Coastguard Worker    * @param c CAM16 chroma
305*970e1046SAndroid Build Coastguard Worker    * @param h CAM16 hue
306*970e1046SAndroid Build Coastguard Worker    * @param viewingConditions Information about the environment where the color was observed.
307*970e1046SAndroid Build Coastguard Worker    */
fromJchInViewingConditions( double j, double c, double h, ViewingConditions viewingConditions)308*970e1046SAndroid Build Coastguard Worker   private static Cam16 fromJchInViewingConditions(
309*970e1046SAndroid Build Coastguard Worker       double j, double c, double h, ViewingConditions viewingConditions) {
310*970e1046SAndroid Build Coastguard Worker     double q =
311*970e1046SAndroid Build Coastguard Worker         4.0
312*970e1046SAndroid Build Coastguard Worker             / viewingConditions.getC()
313*970e1046SAndroid Build Coastguard Worker             * Math.sqrt(j / 100.0)
314*970e1046SAndroid Build Coastguard Worker             * (viewingConditions.getAw() + 4.0)
315*970e1046SAndroid Build Coastguard Worker             * viewingConditions.getFlRoot();
316*970e1046SAndroid Build Coastguard Worker     double m = c * viewingConditions.getFlRoot();
317*970e1046SAndroid Build Coastguard Worker     double alpha = c / Math.sqrt(j / 100.0);
318*970e1046SAndroid Build Coastguard Worker     double s =
319*970e1046SAndroid Build Coastguard Worker         50.0 * Math.sqrt((alpha * viewingConditions.getC()) / (viewingConditions.getAw() + 4.0));
320*970e1046SAndroid Build Coastguard Worker 
321*970e1046SAndroid Build Coastguard Worker     double hueRadians = Math.toRadians(h);
322*970e1046SAndroid Build Coastguard Worker     double jstar = (1.0 + 100.0 * 0.007) * j / (1.0 + 0.007 * j);
323*970e1046SAndroid Build Coastguard Worker     double mstar = 1.0 / 0.0228 * Math.log1p(0.0228 * m);
324*970e1046SAndroid Build Coastguard Worker     double astar = mstar * Math.cos(hueRadians);
325*970e1046SAndroid Build Coastguard Worker     double bstar = mstar * Math.sin(hueRadians);
326*970e1046SAndroid Build Coastguard Worker     return new Cam16(h, c, j, q, m, s, jstar, astar, bstar);
327*970e1046SAndroid Build Coastguard Worker   }
328*970e1046SAndroid Build Coastguard Worker 
329*970e1046SAndroid Build Coastguard Worker   /**
330*970e1046SAndroid Build Coastguard Worker    * Create a CAM16 color from CAM16-UCS coordinates.
331*970e1046SAndroid Build Coastguard Worker    *
332*970e1046SAndroid Build Coastguard Worker    * @param jstar CAM16-UCS lightness.
333*970e1046SAndroid Build Coastguard Worker    * @param astar CAM16-UCS a dimension. Like a* in L*a*b*, it is a Cartesian coordinate on the Y
334*970e1046SAndroid Build Coastguard Worker    *     axis.
335*970e1046SAndroid Build Coastguard Worker    * @param bstar CAM16-UCS b dimension. Like a* in L*a*b*, it is a Cartesian coordinate on the X
336*970e1046SAndroid Build Coastguard Worker    *     axis.
337*970e1046SAndroid Build Coastguard Worker    */
fromUcs(double jstar, double astar, double bstar)338*970e1046SAndroid Build Coastguard Worker   public static Cam16 fromUcs(double jstar, double astar, double bstar) {
339*970e1046SAndroid Build Coastguard Worker 
340*970e1046SAndroid Build Coastguard Worker     return fromUcsInViewingConditions(jstar, astar, bstar, ViewingConditions.DEFAULT);
341*970e1046SAndroid Build Coastguard Worker   }
342*970e1046SAndroid Build Coastguard Worker 
343*970e1046SAndroid Build Coastguard Worker   /**
344*970e1046SAndroid Build Coastguard Worker    * Create a CAM16 color from CAM16-UCS coordinates in defined viewing conditions.
345*970e1046SAndroid Build Coastguard Worker    *
346*970e1046SAndroid Build Coastguard Worker    * @param jstar CAM16-UCS lightness.
347*970e1046SAndroid Build Coastguard Worker    * @param astar CAM16-UCS a dimension. Like a* in L*a*b*, it is a Cartesian coordinate on the Y
348*970e1046SAndroid Build Coastguard Worker    *     axis.
349*970e1046SAndroid Build Coastguard Worker    * @param bstar CAM16-UCS b dimension. Like a* in L*a*b*, it is a Cartesian coordinate on the X
350*970e1046SAndroid Build Coastguard Worker    *     axis.
351*970e1046SAndroid Build Coastguard Worker    * @param viewingConditions Information about the environment where the color was observed.
352*970e1046SAndroid Build Coastguard Worker    */
fromUcsInViewingConditions( double jstar, double astar, double bstar, ViewingConditions viewingConditions)353*970e1046SAndroid Build Coastguard Worker   public static Cam16 fromUcsInViewingConditions(
354*970e1046SAndroid Build Coastguard Worker       double jstar, double astar, double bstar, ViewingConditions viewingConditions) {
355*970e1046SAndroid Build Coastguard Worker 
356*970e1046SAndroid Build Coastguard Worker     double m = Math.hypot(astar, bstar);
357*970e1046SAndroid Build Coastguard Worker     double m2 = Math.expm1(m * 0.0228) / 0.0228;
358*970e1046SAndroid Build Coastguard Worker     double c = m2 / viewingConditions.getFlRoot();
359*970e1046SAndroid Build Coastguard Worker     double h = Math.atan2(bstar, astar) * (180.0 / Math.PI);
360*970e1046SAndroid Build Coastguard Worker     if (h < 0.0) {
361*970e1046SAndroid Build Coastguard Worker       h += 360.0;
362*970e1046SAndroid Build Coastguard Worker     }
363*970e1046SAndroid Build Coastguard Worker     double j = jstar / (1. - (jstar - 100.) * 0.007);
364*970e1046SAndroid Build Coastguard Worker     return fromJchInViewingConditions(j, c, h, viewingConditions);
365*970e1046SAndroid Build Coastguard Worker   }
366*970e1046SAndroid Build Coastguard Worker 
367*970e1046SAndroid Build Coastguard Worker   /**
368*970e1046SAndroid Build Coastguard Worker    * ARGB representation of the color. Assumes the color was viewed in default viewing conditions,
369*970e1046SAndroid Build Coastguard Worker    * which are near-identical to the default viewing conditions for sRGB.
370*970e1046SAndroid Build Coastguard Worker    */
toInt()371*970e1046SAndroid Build Coastguard Worker   public int toInt() {
372*970e1046SAndroid Build Coastguard Worker     return viewed(ViewingConditions.DEFAULT);
373*970e1046SAndroid Build Coastguard Worker   }
374*970e1046SAndroid Build Coastguard Worker 
375*970e1046SAndroid Build Coastguard Worker   /**
376*970e1046SAndroid Build Coastguard Worker    * ARGB representation of the color, in defined viewing conditions.
377*970e1046SAndroid Build Coastguard Worker    *
378*970e1046SAndroid Build Coastguard Worker    * @param viewingConditions Information about the environment where the color will be viewed.
379*970e1046SAndroid Build Coastguard Worker    * @return ARGB representation of color
380*970e1046SAndroid Build Coastguard Worker    */
viewed(ViewingConditions viewingConditions)381*970e1046SAndroid Build Coastguard Worker   int viewed(ViewingConditions viewingConditions) {
382*970e1046SAndroid Build Coastguard Worker     double[] xyz = xyzInViewingConditions(viewingConditions, tempArray);
383*970e1046SAndroid Build Coastguard Worker     return ColorUtils.argbFromXyz(xyz[0], xyz[1], xyz[2]);
384*970e1046SAndroid Build Coastguard Worker   }
385*970e1046SAndroid Build Coastguard Worker 
xyzInViewingConditions(ViewingConditions viewingConditions, double[] returnArray)386*970e1046SAndroid Build Coastguard Worker   double[] xyzInViewingConditions(ViewingConditions viewingConditions, double[] returnArray) {
387*970e1046SAndroid Build Coastguard Worker     double alpha =
388*970e1046SAndroid Build Coastguard Worker         (getChroma() == 0.0 || getJ() == 0.0) ? 0.0 : getChroma() / Math.sqrt(getJ() / 100.0);
389*970e1046SAndroid Build Coastguard Worker 
390*970e1046SAndroid Build Coastguard Worker     double t =
391*970e1046SAndroid Build Coastguard Worker         Math.pow(
392*970e1046SAndroid Build Coastguard Worker             alpha / Math.pow(1.64 - Math.pow(0.29, viewingConditions.getN()), 0.73), 1.0 / 0.9);
393*970e1046SAndroid Build Coastguard Worker     double hRad = Math.toRadians(getHue());
394*970e1046SAndroid Build Coastguard Worker 
395*970e1046SAndroid Build Coastguard Worker     double eHue = 0.25 * (Math.cos(hRad + 2.0) + 3.8);
396*970e1046SAndroid Build Coastguard Worker     double ac =
397*970e1046SAndroid Build Coastguard Worker         viewingConditions.getAw()
398*970e1046SAndroid Build Coastguard Worker             * Math.pow(getJ() / 100.0, 1.0 / viewingConditions.getC() / viewingConditions.getZ());
399*970e1046SAndroid Build Coastguard Worker     double p1 = eHue * (50000.0 / 13.0) * viewingConditions.getNc() * viewingConditions.getNcb();
400*970e1046SAndroid Build Coastguard Worker     double p2 = (ac / viewingConditions.getNbb());
401*970e1046SAndroid Build Coastguard Worker 
402*970e1046SAndroid Build Coastguard Worker     double hSin = Math.sin(hRad);
403*970e1046SAndroid Build Coastguard Worker     double hCos = Math.cos(hRad);
404*970e1046SAndroid Build Coastguard Worker 
405*970e1046SAndroid Build Coastguard Worker     double gamma = 23.0 * (p2 + 0.305) * t / (23.0 * p1 + 11.0 * t * hCos + 108.0 * t * hSin);
406*970e1046SAndroid Build Coastguard Worker     double a = gamma * hCos;
407*970e1046SAndroid Build Coastguard Worker     double b = gamma * hSin;
408*970e1046SAndroid Build Coastguard Worker     double rA = (460.0 * p2 + 451.0 * a + 288.0 * b) / 1403.0;
409*970e1046SAndroid Build Coastguard Worker     double gA = (460.0 * p2 - 891.0 * a - 261.0 * b) / 1403.0;
410*970e1046SAndroid Build Coastguard Worker     double bA = (460.0 * p2 - 220.0 * a - 6300.0 * b) / 1403.0;
411*970e1046SAndroid Build Coastguard Worker 
412*970e1046SAndroid Build Coastguard Worker     double rCBase = max(0, (27.13 * Math.abs(rA)) / (400.0 - Math.abs(rA)));
413*970e1046SAndroid Build Coastguard Worker     double rC =
414*970e1046SAndroid Build Coastguard Worker         Math.signum(rA) * (100.0 / viewingConditions.getFl()) * Math.pow(rCBase, 1.0 / 0.42);
415*970e1046SAndroid Build Coastguard Worker     double gCBase = max(0, (27.13 * Math.abs(gA)) / (400.0 - Math.abs(gA)));
416*970e1046SAndroid Build Coastguard Worker     double gC =
417*970e1046SAndroid Build Coastguard Worker         Math.signum(gA) * (100.0 / viewingConditions.getFl()) * Math.pow(gCBase, 1.0 / 0.42);
418*970e1046SAndroid Build Coastguard Worker     double bCBase = max(0, (27.13 * Math.abs(bA)) / (400.0 - Math.abs(bA)));
419*970e1046SAndroid Build Coastguard Worker     double bC =
420*970e1046SAndroid Build Coastguard Worker         Math.signum(bA) * (100.0 / viewingConditions.getFl()) * Math.pow(bCBase, 1.0 / 0.42);
421*970e1046SAndroid Build Coastguard Worker     double rF = rC / viewingConditions.getRgbD()[0];
422*970e1046SAndroid Build Coastguard Worker     double gF = gC / viewingConditions.getRgbD()[1];
423*970e1046SAndroid Build Coastguard Worker     double bF = bC / viewingConditions.getRgbD()[2];
424*970e1046SAndroid Build Coastguard Worker 
425*970e1046SAndroid Build Coastguard Worker     double[][] matrix = CAM16RGB_TO_XYZ;
426*970e1046SAndroid Build Coastguard Worker     double x = (rF * matrix[0][0]) + (gF * matrix[0][1]) + (bF * matrix[0][2]);
427*970e1046SAndroid Build Coastguard Worker     double y = (rF * matrix[1][0]) + (gF * matrix[1][1]) + (bF * matrix[1][2]);
428*970e1046SAndroid Build Coastguard Worker     double z = (rF * matrix[2][0]) + (gF * matrix[2][1]) + (bF * matrix[2][2]);
429*970e1046SAndroid Build Coastguard Worker 
430*970e1046SAndroid Build Coastguard Worker     if (returnArray != null) {
431*970e1046SAndroid Build Coastguard Worker       returnArray[0] = x;
432*970e1046SAndroid Build Coastguard Worker       returnArray[1] = y;
433*970e1046SAndroid Build Coastguard Worker       returnArray[2] = z;
434*970e1046SAndroid Build Coastguard Worker       return returnArray;
435*970e1046SAndroid Build Coastguard Worker     } else {
436*970e1046SAndroid Build Coastguard Worker       return new double[] {x, y, z};
437*970e1046SAndroid Build Coastguard Worker     }
438*970e1046SAndroid Build Coastguard Worker   }
439*970e1046SAndroid Build Coastguard Worker }
440