aboutsummaryrefslogtreecommitdiff
path: root/palettes/TonalPalette.java
diff options
context:
space:
mode:
Diffstat (limited to 'palettes/TonalPalette.java')
-rw-r--r--palettes/TonalPalette.java144
1 files changed, 144 insertions, 0 deletions
diff --git a/palettes/TonalPalette.java b/palettes/TonalPalette.java
new file mode 100644
index 0000000..618d324
--- /dev/null
+++ b/palettes/TonalPalette.java
@@ -0,0 +1,144 @@
+/*
+ * 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;
+ }
+}