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