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