xref: /aosp_15_r20/external/libmonet/hct/ViewingConditions.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 com.google.ux.material.libmonet.utils.ColorUtils;
20*970e1046SAndroid Build Coastguard Worker import com.google.ux.material.libmonet.utils.MathUtils;
21*970e1046SAndroid Build Coastguard Worker 
22*970e1046SAndroid Build Coastguard Worker /**
23*970e1046SAndroid Build Coastguard Worker  * In traditional color spaces, a color can be identified solely by the observer's measurement of
24*970e1046SAndroid Build Coastguard Worker  * the color. Color appearance models such as CAM16 also use information about the environment where
25*970e1046SAndroid Build Coastguard Worker  * the color was observed, known as the viewing conditions.
26*970e1046SAndroid Build Coastguard Worker  *
27*970e1046SAndroid Build Coastguard Worker  * <p>For example, white under the traditional assumption of a midday sun white point is accurately
28*970e1046SAndroid Build Coastguard Worker  * measured as a slightly chromatic blue by CAM16. (roughly, hue 203, chroma 3, lightness 100)
29*970e1046SAndroid Build Coastguard Worker  *
30*970e1046SAndroid Build Coastguard Worker  * <p>This class caches intermediate values of the CAM16 conversion process that depend only on
31*970e1046SAndroid Build Coastguard Worker  * viewing conditions, enabling speed ups.
32*970e1046SAndroid Build Coastguard Worker  */
33*970e1046SAndroid Build Coastguard Worker public final class ViewingConditions {
34*970e1046SAndroid Build Coastguard Worker   /** sRGB-like viewing conditions. */
35*970e1046SAndroid Build Coastguard Worker   public static final ViewingConditions DEFAULT =
36*970e1046SAndroid Build Coastguard Worker       ViewingConditions.defaultWithBackgroundLstar(50.0);
37*970e1046SAndroid Build Coastguard Worker 
38*970e1046SAndroid Build Coastguard Worker   private final double aw;
39*970e1046SAndroid Build Coastguard Worker   private final double nbb;
40*970e1046SAndroid Build Coastguard Worker   private final double ncb;
41*970e1046SAndroid Build Coastguard Worker   private final double c;
42*970e1046SAndroid Build Coastguard Worker   private final double nc;
43*970e1046SAndroid Build Coastguard Worker   private final double n;
44*970e1046SAndroid Build Coastguard Worker   private final double[] rgbD;
45*970e1046SAndroid Build Coastguard Worker   private final double fl;
46*970e1046SAndroid Build Coastguard Worker   private final double flRoot;
47*970e1046SAndroid Build Coastguard Worker   private final double z;
48*970e1046SAndroid Build Coastguard Worker 
getAw()49*970e1046SAndroid Build Coastguard Worker   public double getAw() {
50*970e1046SAndroid Build Coastguard Worker     return aw;
51*970e1046SAndroid Build Coastguard Worker   }
52*970e1046SAndroid Build Coastguard Worker 
getN()53*970e1046SAndroid Build Coastguard Worker   public double getN() {
54*970e1046SAndroid Build Coastguard Worker     return n;
55*970e1046SAndroid Build Coastguard Worker   }
56*970e1046SAndroid Build Coastguard Worker 
getNbb()57*970e1046SAndroid Build Coastguard Worker   public double getNbb() {
58*970e1046SAndroid Build Coastguard Worker     return nbb;
59*970e1046SAndroid Build Coastguard Worker   }
60*970e1046SAndroid Build Coastguard Worker 
getNcb()61*970e1046SAndroid Build Coastguard Worker   double getNcb() {
62*970e1046SAndroid Build Coastguard Worker     return ncb;
63*970e1046SAndroid Build Coastguard Worker   }
64*970e1046SAndroid Build Coastguard Worker 
getC()65*970e1046SAndroid Build Coastguard Worker   double getC() {
66*970e1046SAndroid Build Coastguard Worker     return c;
67*970e1046SAndroid Build Coastguard Worker   }
68*970e1046SAndroid Build Coastguard Worker 
getNc()69*970e1046SAndroid Build Coastguard Worker   double getNc() {
70*970e1046SAndroid Build Coastguard Worker     return nc;
71*970e1046SAndroid Build Coastguard Worker   }
72*970e1046SAndroid Build Coastguard Worker 
getRgbD()73*970e1046SAndroid Build Coastguard Worker   public double[] getRgbD() {
74*970e1046SAndroid Build Coastguard Worker     return rgbD;
75*970e1046SAndroid Build Coastguard Worker   }
76*970e1046SAndroid Build Coastguard Worker 
getFl()77*970e1046SAndroid Build Coastguard Worker   double getFl() {
78*970e1046SAndroid Build Coastguard Worker     return fl;
79*970e1046SAndroid Build Coastguard Worker   }
80*970e1046SAndroid Build Coastguard Worker 
getFlRoot()81*970e1046SAndroid Build Coastguard Worker   public double getFlRoot() {
82*970e1046SAndroid Build Coastguard Worker     return flRoot;
83*970e1046SAndroid Build Coastguard Worker   }
84*970e1046SAndroid Build Coastguard Worker 
getZ()85*970e1046SAndroid Build Coastguard Worker   double getZ() {
86*970e1046SAndroid Build Coastguard Worker     return z;
87*970e1046SAndroid Build Coastguard Worker   }
88*970e1046SAndroid Build Coastguard Worker 
89*970e1046SAndroid Build Coastguard Worker   /**
90*970e1046SAndroid Build Coastguard Worker    * Create ViewingConditions from a simple, physically relevant, set of parameters.
91*970e1046SAndroid Build Coastguard Worker    *
92*970e1046SAndroid Build Coastguard Worker    * @param whitePoint White point, measured in the XYZ color space. default = D65, or sunny day
93*970e1046SAndroid Build Coastguard Worker    *     afternoon
94*970e1046SAndroid Build Coastguard Worker    * @param adaptingLuminance The luminance of the adapting field. Informally, how bright it is in
95*970e1046SAndroid Build Coastguard Worker    *     the room where the color is viewed. Can be calculated from lux by multiplying lux by
96*970e1046SAndroid Build Coastguard Worker    *     0.0586. default = 11.72, or 200 lux.
97*970e1046SAndroid Build Coastguard Worker    * @param backgroundLstar The lightness of the area surrounding the color. measured by L* in
98*970e1046SAndroid Build Coastguard Worker    *     L*a*b*. default = 50.0
99*970e1046SAndroid Build Coastguard Worker    * @param surround A general description of the lighting surrounding the color. 0 is pitch dark,
100*970e1046SAndroid Build Coastguard Worker    *     like watching a movie in a theater. 1.0 is a dimly light room, like watching TV at home at
101*970e1046SAndroid Build Coastguard Worker    *     night. 2.0 means there is no difference between the lighting on the color and around it.
102*970e1046SAndroid Build Coastguard Worker    *     default = 2.0
103*970e1046SAndroid Build Coastguard Worker    * @param discountingIlluminant Whether the eye accounts for the tint of the ambient lighting,
104*970e1046SAndroid Build Coastguard Worker    *     such as knowing an apple is still red in green light. default = false, the eye does not
105*970e1046SAndroid Build Coastguard Worker    *     perform this process on self-luminous objects like displays.
106*970e1046SAndroid Build Coastguard Worker    */
make( double[] whitePoint, double adaptingLuminance, double backgroundLstar, double surround, boolean discountingIlluminant)107*970e1046SAndroid Build Coastguard Worker   public static ViewingConditions make(
108*970e1046SAndroid Build Coastguard Worker       double[] whitePoint,
109*970e1046SAndroid Build Coastguard Worker       double adaptingLuminance,
110*970e1046SAndroid Build Coastguard Worker       double backgroundLstar,
111*970e1046SAndroid Build Coastguard Worker       double surround,
112*970e1046SAndroid Build Coastguard Worker       boolean discountingIlluminant) {
113*970e1046SAndroid Build Coastguard Worker     // A background of pure black is non-physical and leads to infinities that represent the idea
114*970e1046SAndroid Build Coastguard Worker     // that any color viewed in pure black can't be seen.
115*970e1046SAndroid Build Coastguard Worker     backgroundLstar = Math.max(0.1, backgroundLstar);
116*970e1046SAndroid Build Coastguard Worker     // Transform white point XYZ to 'cone'/'rgb' responses
117*970e1046SAndroid Build Coastguard Worker     double[][] matrix = Cam16.XYZ_TO_CAM16RGB;
118*970e1046SAndroid Build Coastguard Worker     double[] xyz = whitePoint;
119*970e1046SAndroid Build Coastguard Worker     double rW = (xyz[0] * matrix[0][0]) + (xyz[1] * matrix[0][1]) + (xyz[2] * matrix[0][2]);
120*970e1046SAndroid Build Coastguard Worker     double gW = (xyz[0] * matrix[1][0]) + (xyz[1] * matrix[1][1]) + (xyz[2] * matrix[1][2]);
121*970e1046SAndroid Build Coastguard Worker     double bW = (xyz[0] * matrix[2][0]) + (xyz[1] * matrix[2][1]) + (xyz[2] * matrix[2][2]);
122*970e1046SAndroid Build Coastguard Worker     double f = 0.8 + (surround / 10.0);
123*970e1046SAndroid Build Coastguard Worker     double c =
124*970e1046SAndroid Build Coastguard Worker         (f >= 0.9)
125*970e1046SAndroid Build Coastguard Worker             ? MathUtils.lerp(0.59, 0.69, ((f - 0.9) * 10.0))
126*970e1046SAndroid Build Coastguard Worker             : MathUtils.lerp(0.525, 0.59, ((f - 0.8) * 10.0));
127*970e1046SAndroid Build Coastguard Worker     double d =
128*970e1046SAndroid Build Coastguard Worker         discountingIlluminant
129*970e1046SAndroid Build Coastguard Worker             ? 1.0
130*970e1046SAndroid Build Coastguard Worker             : f * (1.0 - ((1.0 / 3.6) * Math.exp((-adaptingLuminance - 42.0) / 92.0)));
131*970e1046SAndroid Build Coastguard Worker     d = MathUtils.clampDouble(0.0, 1.0, d);
132*970e1046SAndroid Build Coastguard Worker     double nc = f;
133*970e1046SAndroid Build Coastguard Worker     double[] rgbD =
134*970e1046SAndroid Build Coastguard Worker         new double[] {
135*970e1046SAndroid Build Coastguard Worker           d * (100.0 / rW) + 1.0 - d, d * (100.0 / gW) + 1.0 - d, d * (100.0 / bW) + 1.0 - d
136*970e1046SAndroid Build Coastguard Worker         };
137*970e1046SAndroid Build Coastguard Worker     double k = 1.0 / (5.0 * adaptingLuminance + 1.0);
138*970e1046SAndroid Build Coastguard Worker     double k4 = k * k * k * k;
139*970e1046SAndroid Build Coastguard Worker     double k4F = 1.0 - k4;
140*970e1046SAndroid Build Coastguard Worker     double fl = (k4 * adaptingLuminance) + (0.1 * k4F * k4F * Math.cbrt(5.0 * adaptingLuminance));
141*970e1046SAndroid Build Coastguard Worker     double n = (ColorUtils.yFromLstar(backgroundLstar) / whitePoint[1]);
142*970e1046SAndroid Build Coastguard Worker     double z = 1.48 + Math.sqrt(n);
143*970e1046SAndroid Build Coastguard Worker     double nbb = 0.725 / Math.pow(n, 0.2);
144*970e1046SAndroid Build Coastguard Worker     double ncb = nbb;
145*970e1046SAndroid Build Coastguard Worker     double[] rgbAFactors =
146*970e1046SAndroid Build Coastguard Worker         new double[] {
147*970e1046SAndroid Build Coastguard Worker           Math.pow(fl * rgbD[0] * rW / 100.0, 0.42),
148*970e1046SAndroid Build Coastguard Worker           Math.pow(fl * rgbD[1] * gW / 100.0, 0.42),
149*970e1046SAndroid Build Coastguard Worker           Math.pow(fl * rgbD[2] * bW / 100.0, 0.42)
150*970e1046SAndroid Build Coastguard Worker         };
151*970e1046SAndroid Build Coastguard Worker 
152*970e1046SAndroid Build Coastguard Worker     double[] rgbA =
153*970e1046SAndroid Build Coastguard Worker         new double[] {
154*970e1046SAndroid Build Coastguard Worker           (400.0 * rgbAFactors[0]) / (rgbAFactors[0] + 27.13),
155*970e1046SAndroid Build Coastguard Worker           (400.0 * rgbAFactors[1]) / (rgbAFactors[1] + 27.13),
156*970e1046SAndroid Build Coastguard Worker           (400.0 * rgbAFactors[2]) / (rgbAFactors[2] + 27.13)
157*970e1046SAndroid Build Coastguard Worker         };
158*970e1046SAndroid Build Coastguard Worker 
159*970e1046SAndroid Build Coastguard Worker     double aw = ((2.0 * rgbA[0]) + rgbA[1] + (0.05 * rgbA[2])) * nbb;
160*970e1046SAndroid Build Coastguard Worker     return new ViewingConditions(n, aw, nbb, ncb, c, nc, rgbD, fl, Math.pow(fl, 0.25), z);
161*970e1046SAndroid Build Coastguard Worker   }
162*970e1046SAndroid Build Coastguard Worker 
163*970e1046SAndroid Build Coastguard Worker   /**
164*970e1046SAndroid Build Coastguard Worker    * Create sRGB-like viewing conditions with a custom background lstar.
165*970e1046SAndroid Build Coastguard Worker    *
166*970e1046SAndroid Build Coastguard Worker    * <p>Default viewing conditions have a lstar of 50, midgray.
167*970e1046SAndroid Build Coastguard Worker    */
defaultWithBackgroundLstar(double lstar)168*970e1046SAndroid Build Coastguard Worker   public static ViewingConditions defaultWithBackgroundLstar(double lstar) {
169*970e1046SAndroid Build Coastguard Worker     return ViewingConditions.make(
170*970e1046SAndroid Build Coastguard Worker         ColorUtils.whitePointD65(),
171*970e1046SAndroid Build Coastguard Worker         (200.0 / Math.PI * ColorUtils.yFromLstar(50.0) / 100.f),
172*970e1046SAndroid Build Coastguard Worker         lstar,
173*970e1046SAndroid Build Coastguard Worker         2.0,
174*970e1046SAndroid Build Coastguard Worker         false);
175*970e1046SAndroid Build Coastguard Worker   }
176*970e1046SAndroid Build Coastguard Worker 
177*970e1046SAndroid Build Coastguard Worker   /**
178*970e1046SAndroid Build Coastguard Worker    * Parameters are intermediate values of the CAM16 conversion process. Their names are shorthand
179*970e1046SAndroid Build Coastguard Worker    * for technical color science terminology, this class would not benefit from documenting them
180*970e1046SAndroid Build Coastguard Worker    * individually. A brief overview is available in the CAM16 specification, and a complete overview
181*970e1046SAndroid Build Coastguard Worker    * requires a color science textbook, such as Fairchild's Color Appearance Models.
182*970e1046SAndroid Build Coastguard Worker    */
ViewingConditions( double n, double aw, double nbb, double ncb, double c, double nc, double[] rgbD, double fl, double flRoot, double z)183*970e1046SAndroid Build Coastguard Worker   private ViewingConditions(
184*970e1046SAndroid Build Coastguard Worker       double n,
185*970e1046SAndroid Build Coastguard Worker       double aw,
186*970e1046SAndroid Build Coastguard Worker       double nbb,
187*970e1046SAndroid Build Coastguard Worker       double ncb,
188*970e1046SAndroid Build Coastguard Worker       double c,
189*970e1046SAndroid Build Coastguard Worker       double nc,
190*970e1046SAndroid Build Coastguard Worker       double[] rgbD,
191*970e1046SAndroid Build Coastguard Worker       double fl,
192*970e1046SAndroid Build Coastguard Worker       double flRoot,
193*970e1046SAndroid Build Coastguard Worker       double z) {
194*970e1046SAndroid Build Coastguard Worker     this.n = n;
195*970e1046SAndroid Build Coastguard Worker     this.aw = aw;
196*970e1046SAndroid Build Coastguard Worker     this.nbb = nbb;
197*970e1046SAndroid Build Coastguard Worker     this.ncb = ncb;
198*970e1046SAndroid Build Coastguard Worker     this.c = c;
199*970e1046SAndroid Build Coastguard Worker     this.nc = nc;
200*970e1046SAndroid Build Coastguard Worker     this.rgbD = rgbD;
201*970e1046SAndroid Build Coastguard Worker     this.fl = fl;
202*970e1046SAndroid Build Coastguard Worker     this.flRoot = flRoot;
203*970e1046SAndroid Build Coastguard Worker     this.z = z;
204*970e1046SAndroid Build Coastguard Worker   }
205*970e1046SAndroid Build Coastguard Worker }
206