All Downloads are FREE. Search and download functionalities are using the official Maven repository.

palettes.TonalPalette Maven / Gradle / Ivy

Go to download

A gradle plugin that generates Material Design 3 themes for Android projects.

The newest version!
/*
 * 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 palettes;

import 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.
 *
 * 

TonalPalette is intended for use in a single thread due to its stateful caching. */ public final class TonalPalette { Map 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) { final Hct keyColor = new KeyColor(hue, chroma).create(); return new TonalPalette(hue, chroma, keyColor); } private TonalPalette(double hue, double chroma, Hct keyColor) { cache = new HashMap<>(); this.hue = hue; this.chroma = chroma; this.keyColor = keyColor; } /** * 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. */ 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; } /** Key color is a color that represents the hue and chroma of a tonal palette. */ private static final class KeyColor { private final double hue; private final double requestedChroma; // Cache that maps tone to max chroma to avoid duplicated HCT calculation. private final Map chromaCache = new HashMap<>(); private static final double MAX_CHROMA_VALUE = 200.0; /** Key color is a color that represents the hue and chroma of a tonal palette */ public KeyColor(double hue, double requestedChroma) { this.hue = hue; this.requestedChroma = requestedChroma; } /** * Creates a key color from a [hue] and a [chroma]. The key color is the first tone, starting * from T50, matching the given hue and chroma. * * @return Key color [Hct] */ public Hct create() { // Pivot around T50 because T50 has the most chroma available, on // average. Thus it is most likely to have a direct answer. final int pivotTone = 50; final int toneStepSize = 1; // Epsilon to accept values slightly higher than the requested chroma. final double epsilon = 0.01; // Binary search to find the tone that can provide a chroma that is closest // to the requested chroma. int lowerTone = 0; int upperTone = 100; while (lowerTone < upperTone) { final int midTone = (lowerTone + upperTone) / 2; boolean isAscending = maxChroma(midTone) < maxChroma(midTone + toneStepSize); boolean sufficientChroma = maxChroma(midTone) >= requestedChroma - epsilon; if (sufficientChroma) { // Either range [lowerTone, midTone] or [midTone, upperTone] has // the answer, so search in the range that is closer the pivot tone. if (Math.abs(lowerTone - pivotTone) < Math.abs(upperTone - pivotTone)) { upperTone = midTone; } else { if (lowerTone == midTone) { return Hct.from(this.hue, this.requestedChroma, lowerTone); } lowerTone = midTone; } } else { // As there is no sufficient chroma in the midTone, follow the direction to the chroma // peak. if (isAscending) { lowerTone = midTone + toneStepSize; } else { // Keep midTone for potential chroma peak. upperTone = midTone; } } } return Hct.from(this.hue, this.requestedChroma, lowerTone); } // Find the maximum chroma for a given tone private double maxChroma(int tone) { if (chromaCache.get(tone) == null) { Double newChroma = Hct.from(hue, MAX_CHROMA_VALUE, tone).getChroma(); if (newChroma != null) { chromaCache.put(tone, newChroma); } } return chromaCache.get(tone); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy