| /* |
| * Copyright 2021 Google LLC |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.google.ux.material.libmonet.hct; |
| |
| import com.google.ux.material.libmonet.utils.ColorUtils; |
| |
| /** |
| * A color system built using CAM16 hue and chroma, and L* from L*a*b*. |
| * |
| * <p>Using L* creates a link between the color system, contrast, and thus accessibility. Contrast |
| * ratio depends on relative luminance, or Y in the XYZ color space. L*, or perceptual luminance can |
| * be calculated from Y. |
| * |
| * <p>Unlike Y, L* is linear to human perception, allowing trivial creation of accurate color tones. |
| * |
| * <p>Unlike contrast ratio, measuring contrast in L* is linear, and simple to calculate. A |
| * difference of 40 in HCT tone guarantees a contrast ratio >= 3.0, and a difference of 50 |
| * guarantees a contrast ratio >= 4.5. |
| */ |
| |
| /** |
| * HCT, hue, chroma, and tone. A color system that provides a perceptually accurate color |
| * measurement system that can also accurately render what colors will appear as in different |
| * lighting environments. |
| */ |
| public final class Hct { |
| private double hue; |
| private double chroma; |
| private double tone; |
| private int argb; |
| |
| /** |
| * Create an HCT color from hue, chroma, and tone. |
| * |
| * @param hue 0 <= hue < 360; invalid values are corrected. |
| * @param chroma 0 <= chroma < ?; Informally, colorfulness. The color returned may be lower than |
| * the requested chroma. Chroma has a different maximum for any given hue and tone. |
| * @param tone 0 <= tone <= 100; invalid values are corrected. |
| * @return HCT representation of a color in default viewing conditions. |
| */ |
| public static Hct from(double hue, double chroma, double tone) { |
| int argb = HctSolver.solveToInt(hue, chroma, tone); |
| return new Hct(argb); |
| } |
| |
| /** |
| * Create an HCT color from a color. |
| * |
| * @param argb ARGB representation of a color. |
| * @return HCT representation of a color in default viewing conditions |
| */ |
| public static Hct fromInt(int argb) { |
| return new Hct(argb); |
| } |
| |
| private Hct(int argb) { |
| setInternalState(argb); |
| } |
| |
| public double getHue() { |
| return hue; |
| } |
| |
| public double getChroma() { |
| return chroma; |
| } |
| |
| public double getTone() { |
| return tone; |
| } |
| |
| public int toInt() { |
| return argb; |
| } |
| |
| /** |
| * Set the hue of this color. Chroma may decrease because chroma has a different maximum for any |
| * given hue and tone. |
| * |
| * @param newHue 0 <= newHue < 360; invalid values are corrected. |
| */ |
| public void setHue(double newHue) { |
| setInternalState(HctSolver.solveToInt(newHue, chroma, tone)); |
| } |
| |
| /** |
| * Set the chroma of this color. Chroma may decrease because chroma has a different maximum for |
| * any given hue and tone. |
| * |
| * @param newChroma 0 <= newChroma < ? |
| */ |
| public void setChroma(double newChroma) { |
| setInternalState(HctSolver.solveToInt(hue, newChroma, tone)); |
| } |
| |
| /** |
| * Set the tone of this color. Chroma may decrease because chroma has a different maximum for any |
| * given hue and tone. |
| * |
| * @param newTone 0 <= newTone <= 100; invalid valids are corrected. |
| */ |
| public void setTone(double newTone) { |
| setInternalState(HctSolver.solveToInt(hue, chroma, newTone)); |
| } |
| |
| /** |
| * Translate a color into different ViewingConditions. |
| * |
| * <p>Colors change appearance. They look different with lights on versus off, the same color, as |
| * in hex code, on white looks different when on black. This is called color relativity, most |
| * famously explicated by Josef Albers in Interaction of Color. |
| * |
| * <p>In color science, color appearance models can account for this and calculate the appearance |
| * of a color in different settings. HCT is based on CAM16, a color appearance model, and uses it |
| * to make these calculations. |
| * |
| * <p>See ViewingConditions.make for parameters affecting color appearance. |
| */ |
| public Hct inViewingConditions(ViewingConditions vc) { |
| // 1. Use CAM16 to find XYZ coordinates of color in specified VC. |
| Cam16 cam16 = Cam16.fromInt(toInt()); |
| double[] viewedInVc = cam16.xyzInViewingConditions(vc, null); |
| |
| // 2. Create CAM16 of those XYZ coordinates in default VC. |
| Cam16 recastInVc = |
| Cam16.fromXyzInViewingConditions( |
| viewedInVc[0], viewedInVc[1], viewedInVc[2], ViewingConditions.DEFAULT); |
| |
| // 3. Create HCT from: |
| // - CAM16 using default VC with XYZ coordinates in specified VC. |
| // - L* converted from Y in XYZ coordinates in specified VC. |
| return Hct.from( |
| recastInVc.getHue(), recastInVc.getChroma(), ColorUtils.lstarFromY(viewedInVc[1])); |
| } |
| |
| private void setInternalState(int argb) { |
| this.argb = argb; |
| Cam16 cam = Cam16.fromInt(argb); |
| hue = cam.getHue(); |
| chroma = cam.getChroma(); |
| this.tone = ColorUtils.lstarFromArgb(argb); |
| } |
| } |