Skip to content

Commit d287c80

Browse files
Material Design Teamraajkumars
authored andcommitted
[M3][ColorUtilities] Internal Color Utilities Library update
PiperOrigin-RevId: 543777709
1 parent ad60bbf commit d287c80

File tree

15 files changed

+247
-49
lines changed

15 files changed

+247
-49
lines changed

lib/java/com/google/android/material/color/utilities/DynamicScheme.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,16 @@ public DynamicScheme(
6666
this.errorPalette = TonalPalette.fromHueAndChroma(25.0, 84.0);
6767
}
6868

69+
/**
70+
* Given a set of hues and set of hue rotations, locate which hues the source color's hue is
71+
* between, apply the rotation at the same index as the first hue in the range, and return the
72+
* rotated hue.
73+
*
74+
* @param sourceColorHct The color whose hue should be rotated.
75+
* @param hues A set of hues.
76+
* @param rotations A set of hue rotations.
77+
* @return Color's hue with a rotation applied.
78+
*/
6979
public static double getRotatedHue(Hct sourceColorHct, double[] hues, double[] rotations) {
7080
final double sourceHue = sourceColorHct.getHue();
7181
if (rotations.length == 1) {

lib/java/com/google/android/material/color/utilities/MaterialDynamicColors.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,37 @@ public DynamicColor highestSurface(@NonNull DynamicScheme s) {
4444
return s.isDark ? surfaceBright() : surfaceDim();
4545
}
4646

47+
// Compatibility Keys Colors for Android
48+
@NonNull
49+
public DynamicColor primaryPaletteKeyColor() {
50+
return DynamicColor.fromPalette(
51+
(s) -> s.primaryPalette, (s) -> s.primaryPalette.getKeyColor().getTone());
52+
}
53+
54+
@NonNull
55+
public DynamicColor secondaryPaletteKeyColor() {
56+
return DynamicColor.fromPalette(
57+
(s) -> s.secondaryPalette, (s) -> s.secondaryPalette.getKeyColor().getTone());
58+
}
59+
60+
@NonNull
61+
public DynamicColor tertiaryPaletteKeyColor() {
62+
return DynamicColor.fromPalette(
63+
(s) -> s.tertiaryPalette, (s) -> s.tertiaryPalette.getKeyColor().getTone());
64+
}
65+
66+
@NonNull
67+
public DynamicColor neutralPaletteKeyColor() {
68+
return DynamicColor.fromPalette(
69+
(s) -> s.neutralPalette, (s) -> s.neutralPalette.getKeyColor().getTone());
70+
}
71+
72+
@NonNull
73+
public DynamicColor neutralVariantPaletteKeyColor() {
74+
return DynamicColor.fromPalette(
75+
(s) -> s.neutralVariantPalette, (s) -> s.neutralVariantPalette.getKeyColor().getTone());
76+
}
77+
4778
@NonNull
4879
public DynamicColor background() {
4980
return DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 6.0 : 98.0);

lib/java/com/google/android/material/color/utilities/SchemeExpressive.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,14 @@ public SchemeExpressive(Hct sourceColorHct, boolean isDark, double contrastLevel
3939
isDark,
4040
contrastLevel,
4141
TonalPalette.fromHueAndChroma(
42-
MathUtils.sanitizeDegreesDouble(sourceColorHct.getHue() + 120.0), 40.0),
42+
MathUtils.sanitizeDegreesDouble(sourceColorHct.getHue() + 240.0), 40.0),
4343
TonalPalette.fromHueAndChroma(
4444
DynamicScheme.getRotatedHue(sourceColorHct, HUES, SECONDARY_ROTATIONS), 24.0),
4545
TonalPalette.fromHueAndChroma(
4646
DynamicScheme.getRotatedHue(sourceColorHct, HUES, TERTIARY_ROTATIONS), 32.0),
47-
TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 8.0),
48-
TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 12.0));
47+
TonalPalette.fromHueAndChroma(
48+
MathUtils.sanitizeDegreesDouble(sourceColorHct.getHue() + 15.0), 8.0),
49+
TonalPalette.fromHueAndChroma(
50+
MathUtils.sanitizeDegreesDouble(sourceColorHct.getHue() + 15.0), 12.0));
4951
}
5052
}

lib/java/com/google/android/material/color/utilities/SchemeTonalSpot.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public SchemeTonalSpot(Hct sourceColorHct, boolean isDark, double contrastLevel)
3333
Variant.TONAL_SPOT,
3434
isDark,
3535
contrastLevel,
36-
TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 40.0),
36+
TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 36.0),
3737
TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 16.0),
3838
TonalPalette.fromHueAndChroma(
3939
MathUtils.sanitizeDegreesDouble(sourceColorHct.getHue() + 60.0), 24.0),

lib/java/com/google/android/material/color/utilities/SchemeVibrant.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public SchemeVibrant(Hct sourceColorHct, boolean isDark, double contrastLevel) {
4242
DynamicScheme.getRotatedHue(sourceColorHct, HUES, SECONDARY_ROTATIONS), 24.0),
4343
TonalPalette.fromHueAndChroma(
4444
DynamicScheme.getRotatedHue(sourceColorHct, HUES, TERTIARY_ROTATIONS), 32.0),
45-
TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 8.0),
45+
TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 10.0),
4646
TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 12.0));
4747
}
4848
}

lib/java/com/google/android/material/color/utilities/TonalPalette.java

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
@RestrictTo(LIBRARY_GROUP)
3232
public final class TonalPalette {
3333
Map<Integer, Integer> cache;
34+
Hct keyColor;
3435
double hue;
3536
double chroma;
3637

@@ -40,7 +41,7 @@ public final class TonalPalette {
4041
* @param argb ARGB representation of a color
4142
* @return Tones matching that color's hue and chroma.
4243
*/
43-
public static final TonalPalette fromInt(int argb) {
44+
public static TonalPalette fromInt(int argb) {
4445
return fromHct(Hct.fromInt(argb));
4546
}
4647

@@ -50,8 +51,8 @@ public static final TonalPalette fromInt(int argb) {
5051
* @param hct HCT representation of a color.
5152
* @return Tones matching that color's hue and chroma.
5253
*/
53-
public static final TonalPalette fromHct(Hct hct) {
54-
return TonalPalette.fromHueAndChroma(hct.getHue(), hct.getChroma());
54+
public static TonalPalette fromHct(Hct hct) {
55+
return new TonalPalette(hct.getHue(), hct.getChroma(), hct);
5556
}
5657

5758
/**
@@ -61,14 +62,53 @@ public static final TonalPalette fromHct(Hct hct) {
6162
* @param chroma HCT chroma
6263
* @return Tones matching hue and chroma.
6364
*/
64-
public static final TonalPalette fromHueAndChroma(double hue, double chroma) {
65-
return new TonalPalette(hue, chroma);
65+
public static TonalPalette fromHueAndChroma(double hue, double chroma) {
66+
return new TonalPalette(hue, chroma, createKeyColor(hue, chroma));
6667
}
6768

68-
private TonalPalette(double hue, double chroma) {
69+
private TonalPalette(double hue, double chroma, Hct keyColor) {
6970
cache = new HashMap<>();
7071
this.hue = hue;
7172
this.chroma = chroma;
73+
this.keyColor = keyColor;
74+
}
75+
76+
/** The key color is the first tone, starting from T50, matching the given hue and chroma. */
77+
private static Hct createKeyColor(double hue, double chroma) {
78+
double startTone = 50.0;
79+
Hct smallestDeltaHct = Hct.from(hue, chroma, startTone);
80+
double smallestDelta = Math.abs(smallestDeltaHct.getChroma() - chroma);
81+
// Starting from T50, check T+/-delta to see if they match the requested
82+
// chroma.
83+
//
84+
// Starts from T50 because T50 has the most chroma available, on
85+
// average. Thus it is most likely to have a direct answer and minimize
86+
// iteration.
87+
for (double delta = 1.0; delta < 50.0; delta += 1.0) {
88+
// Termination condition rounding instead of minimizing delta to avoid
89+
// case where requested chroma is 16.51, and the closest chroma is 16.49.
90+
// Error is minimized, but when rounded and displayed, requested chroma
91+
// is 17, key color's chroma is 16.
92+
if (Math.round(chroma) == Math.round(smallestDeltaHct.getChroma())) {
93+
return smallestDeltaHct;
94+
}
95+
96+
final Hct hctAdd = Hct.from(hue, chroma, startTone + delta);
97+
final double hctAddDelta = Math.abs(hctAdd.getChroma() - chroma);
98+
if (hctAddDelta < smallestDelta) {
99+
smallestDelta = hctAddDelta;
100+
smallestDeltaHct = hctAdd;
101+
}
102+
103+
final Hct hctSubtract = Hct.from(hue, chroma, startTone - delta);
104+
final double hctSubtractDelta = Math.abs(hctSubtract.getChroma() - chroma);
105+
if (hctSubtractDelta < smallestDelta) {
106+
smallestDelta = hctSubtractDelta;
107+
smallestDeltaHct = hctSubtract;
108+
}
109+
}
110+
111+
return smallestDeltaHct;
72112
}
73113

74114
/**
@@ -88,15 +128,23 @@ public int tone(int tone) {
88128
return color;
89129
}
90130

131+
/** Given a tone, use hue and chroma of palette to create a color, and return it as HCT. */
91132
public Hct getHct(double tone) {
92133
return Hct.from(this.hue, this.chroma, tone);
93134
}
94135

136+
/** The chroma of the Tonal Palette, in HCT. Ranges from 0 to ~130 (for sRGB gamut). */
95137
public double getChroma() {
96138
return this.chroma;
97139
}
98140

141+
/** The hue of the Tonal Palette, in HCT. Ranges from 0 to 360. */
99142
public double getHue() {
100143
return this.hue;
101144
}
145+
146+
/** The key color is the first tone, starting from T50, that matches the palette's chroma. */
147+
public Hct getKeyColor() {
148+
return this.keyColor;
149+
}
102150
}

lib/javatests/com/google/android/material/color/utilities/SchemeContentTest.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,18 @@ public final class SchemeContentTest {
2727

2828
private final MaterialDynamicColors dynamicColors = new MaterialDynamicColors();
2929

30+
@Test
31+
public void testKeyColors() {
32+
SchemeContent scheme = new SchemeContent(Hct.fromInt(0xff0000ff), false, 0.0);
33+
34+
assertThat(dynamicColors.primaryPaletteKeyColor().getArgb(scheme)).isSameColorAs(0xff080CFF);
35+
assertThat(dynamicColors.secondaryPaletteKeyColor().getArgb(scheme)).isSameColorAs(0xff656DD3);
36+
assertThat(dynamicColors.tertiaryPaletteKeyColor().getArgb(scheme)).isSameColorAs(0xff81009F);
37+
assertThat(dynamicColors.neutralPaletteKeyColor().getArgb(scheme)).isSameColorAs(0xff767684);
38+
assertThat(dynamicColors.neutralVariantPaletteKeyColor().getArgb(scheme))
39+
.isSameColorAs(0xff757589);
40+
}
41+
3042
@Test
3143
public void lightTheme_minContrast_primary() {
3244
SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), false, -1.0);

0 commit comments

Comments
 (0)