• 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 
21 /**
22  * A color system built using CAM16 hue and chroma, and L* from L*a*b*.
23  *
24  * <p>Using L* creates a link between the color system, contrast, and thus accessibility. Contrast
25  * ratio depends on relative luminance, or Y in the XYZ color space. L*, or perceptual luminance can
26  * be calculated from Y.
27  *
28  * <p>Unlike Y, L* is linear to human perception, allowing trivial creation of accurate color tones.
29  *
30  * <p>Unlike contrast ratio, measuring contrast in L* is linear, and simple to calculate. A
31  * difference of 40 in HCT tone guarantees a contrast ratio >= 3.0, and a difference of 50
32  * guarantees a contrast ratio >= 4.5.
33  */
34 
35 /**
36  * HCT, hue, chroma, and tone. A color system that provides a perceptually accurate color
37  * measurement system that can also accurately render what colors will appear as in different
38  * lighting environments.
39  */
40 public final class Hct {
41   private double hue;
42   private double chroma;
43   private double tone;
44   private int argb;
45 
46   /**
47    * Create an HCT color from hue, chroma, and tone.
48    *
49    * @param hue 0 <= hue < 360; invalid values are corrected.
50    * @param chroma 0 <= chroma < ?; Informally, colorfulness. The color returned may be lower than
51    *     the requested chroma. Chroma has a different maximum for any given hue and tone.
52    * @param tone 0 <= tone <= 100; invalid values are corrected.
53    * @return HCT representation of a color in default viewing conditions.
54    */
from(double hue, double chroma, double tone)55   public static Hct from(double hue, double chroma, double tone) {
56     int argb = HctSolver.solveToInt(hue, chroma, tone);
57     return new Hct(argb);
58   }
59 
60   /**
61    * Create an HCT color from a color.
62    *
63    * @param argb ARGB representation of a color.
64    * @return HCT representation of a color in default viewing conditions
65    */
fromInt(int argb)66   public static Hct fromInt(int argb) {
67     return new Hct(argb);
68   }
69 
Hct(int argb)70   private Hct(int argb) {
71     setInternalState(argb);
72   }
73 
getHue()74   public double getHue() {
75     return hue;
76   }
77 
getChroma()78   public double getChroma() {
79     return chroma;
80   }
81 
getTone()82   public double getTone() {
83     return tone;
84   }
85 
toInt()86   public int toInt() {
87     return argb;
88   }
89 
90   /**
91    * Set the hue of this color. Chroma may decrease because chroma has a different maximum for any
92    * given hue and tone.
93    *
94    * @param newHue 0 <= newHue < 360; invalid values are corrected.
95    */
setHue(double newHue)96   public void setHue(double newHue) {
97     setInternalState(HctSolver.solveToInt(newHue, chroma, tone));
98   }
99 
100   /**
101    * Set the chroma of this color. Chroma may decrease because chroma has a different maximum for
102    * any given hue and tone.
103    *
104    * @param newChroma 0 <= newChroma < ?
105    */
setChroma(double newChroma)106   public void setChroma(double newChroma) {
107     setInternalState(HctSolver.solveToInt(hue, newChroma, tone));
108   }
109 
110   /**
111    * Set the tone of this color. Chroma may decrease because chroma has a different maximum for any
112    * given hue and tone.
113    *
114    * @param newTone 0 <= newTone <= 100; invalid valids are corrected.
115    */
setTone(double newTone)116   public void setTone(double newTone) {
117     setInternalState(HctSolver.solveToInt(hue, chroma, newTone));
118   }
119 
120   /**
121    * Translate a color into different ViewingConditions.
122    *
123    * <p>Colors change appearance. They look different with lights on versus off, the same color, as
124    * in hex code, on white looks different when on black. This is called color relativity, most
125    * famously explicated by Josef Albers in Interaction of Color.
126    *
127    * <p>In color science, color appearance models can account for this and calculate the appearance
128    * of a color in different settings. HCT is based on CAM16, a color appearance model, and uses it
129    * to make these calculations.
130    *
131    * <p>See ViewingConditions.make for parameters affecting color appearance.
132    */
inViewingConditions(ViewingConditions vc)133   public Hct inViewingConditions(ViewingConditions vc) {
134     // 1. Use CAM16 to find XYZ coordinates of color in specified VC.
135     Cam16 cam16 = Cam16.fromInt(toInt());
136     double[] viewedInVc = cam16.xyzInViewingConditions(vc, null);
137 
138     // 2. Create CAM16 of those XYZ coordinates in default VC.
139     Cam16 recastInVc =
140         Cam16.fromXyzInViewingConditions(
141             viewedInVc[0], viewedInVc[1], viewedInVc[2], ViewingConditions.DEFAULT);
142 
143     // 3. Create HCT from:
144     // - CAM16 using default VC with XYZ coordinates in specified VC.
145     // - L* converted from Y in XYZ coordinates in specified VC.
146     return Hct.from(
147         recastInVc.getHue(), recastInVc.getChroma(), ColorUtils.lstarFromY(viewedInVc[1]));
148   }
149 
setInternalState(int argb)150   private void setInternalState(int argb) {
151     this.argb = argb;
152     Cam16 cam = Cam16.fromInt(argb);
153     hue = cam.getHue();
154     chroma = cam.getChroma();
155     this.tone = ColorUtils.lstarFromArgb(argb);
156   }
157 }
158