You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
145 lines
4.7 KiB
145 lines
4.7 KiB
/*
|
|
* 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.palettes;
|
|
|
|
import com.google.ux.material.libmonet.hct.Hct;
|
|
import java.util.HashMap;
|
|
import java.util.Map;
|
|
|
|
/**
|
|
* A convenience class for retrieving colors that are constant in hue and chroma, but vary in tone.
|
|
*/
|
|
public final class TonalPalette {
|
|
Map<Integer, Integer> cache;
|
|
Hct keyColor;
|
|
double hue;
|
|
double chroma;
|
|
|
|
/**
|
|
* Create tones using the HCT hue and chroma from a color.
|
|
*
|
|
* @param argb ARGB representation of a color
|
|
* @return Tones matching that color's hue and chroma.
|
|
*/
|
|
public static TonalPalette fromInt(int argb) {
|
|
return fromHct(Hct.fromInt(argb));
|
|
}
|
|
|
|
/**
|
|
* Create tones using a HCT color.
|
|
*
|
|
* @param hct HCT representation of a color.
|
|
* @return Tones matching that color's hue and chroma.
|
|
*/
|
|
public static TonalPalette fromHct(Hct hct) {
|
|
return new TonalPalette(hct.getHue(), hct.getChroma(), hct);
|
|
}
|
|
|
|
/**
|
|
* Create tones from a defined HCT hue and chroma.
|
|
*
|
|
* @param hue HCT hue
|
|
* @param chroma HCT chroma
|
|
* @return Tones matching hue and chroma.
|
|
*/
|
|
public static TonalPalette fromHueAndChroma(double hue, double chroma) {
|
|
return new TonalPalette(hue, chroma, createKeyColor(hue, chroma));
|
|
}
|
|
|
|
private TonalPalette(double hue, double chroma, Hct keyColor) {
|
|
cache = new HashMap<>();
|
|
this.hue = hue;
|
|
this.chroma = chroma;
|
|
this.keyColor = keyColor;
|
|
}
|
|
|
|
/** The key color is the first tone, starting from T50, matching the given hue and chroma. */
|
|
private static Hct createKeyColor(double hue, double chroma) {
|
|
double startTone = 50.0;
|
|
Hct smallestDeltaHct = Hct.from(hue, chroma, startTone);
|
|
double smallestDelta = Math.abs(smallestDeltaHct.getChroma() - chroma);
|
|
// Starting from T50, check T+/-delta to see if they match the requested
|
|
// chroma.
|
|
//
|
|
// Starts from T50 because T50 has the most chroma available, on
|
|
// average. Thus it is most likely to have a direct answer and minimize
|
|
// iteration.
|
|
for (double delta = 1.0; delta < 50.0; delta += 1.0) {
|
|
// Termination condition rounding instead of minimizing delta to avoid
|
|
// case where requested chroma is 16.51, and the closest chroma is 16.49.
|
|
// Error is minimized, but when rounded and displayed, requested chroma
|
|
// is 17, key color's chroma is 16.
|
|
if (Math.round(chroma) == Math.round(smallestDeltaHct.getChroma())) {
|
|
return smallestDeltaHct;
|
|
}
|
|
|
|
final Hct hctAdd = Hct.from(hue, chroma, startTone + delta);
|
|
final double hctAddDelta = Math.abs(hctAdd.getChroma() - chroma);
|
|
if (hctAddDelta < smallestDelta) {
|
|
smallestDelta = hctAddDelta;
|
|
smallestDeltaHct = hctAdd;
|
|
}
|
|
|
|
final Hct hctSubtract = Hct.from(hue, chroma, startTone - delta);
|
|
final double hctSubtractDelta = Math.abs(hctSubtract.getChroma() - chroma);
|
|
if (hctSubtractDelta < smallestDelta) {
|
|
smallestDelta = hctSubtractDelta;
|
|
smallestDeltaHct = hctSubtract;
|
|
}
|
|
}
|
|
|
|
return smallestDeltaHct;
|
|
}
|
|
|
|
/**
|
|
* Create an ARGB color with HCT hue and chroma of this Tones instance, and the provided HCT tone.
|
|
*
|
|
* @param tone HCT tone, measured from 0 to 100.
|
|
* @return ARGB representation of a color with that tone.
|
|
*/
|
|
// AndroidJdkLibsChecker is higher priority than ComputeIfAbsentUseValue (b/119581923)
|
|
@SuppressWarnings("ComputeIfAbsentUseValue")
|
|
public int tone(int tone) {
|
|
Integer color = cache.get(tone);
|
|
if (color == null) {
|
|
color = Hct.from(this.hue, this.chroma, tone).toInt();
|
|
cache.put(tone, color);
|
|
}
|
|
return color;
|
|
}
|
|
|
|
/** Given a tone, use hue and chroma of palette to create a color, and return it as HCT. */
|
|
public Hct getHct(double tone) {
|
|
return Hct.from(this.hue, this.chroma, tone);
|
|
}
|
|
|
|
/** The chroma of the Tonal Palette, in HCT. Ranges from 0 to ~130 (for sRGB gamut). */
|
|
public double getChroma() {
|
|
return this.chroma;
|
|
}
|
|
|
|
/** The hue of the Tonal Palette, in HCT. Ranges from 0 to 360. */
|
|
public double getHue() {
|
|
return this.hue;
|
|
}
|
|
|
|
/** The key color is the first tone, starting from T50, that matches the palette's chroma. */
|
|
public Hct getKeyColor() {
|
|
return this.keyColor;
|
|
}
|
|
}
|