Merge "Update styles to match latest specs" into tm-dev

This commit is contained in:
James O'Leary
2022-03-21 20:34:01 +00:00
committed by Android (Google) Code Review
3 changed files with 158 additions and 57 deletions

View File

@@ -31,37 +31,134 @@ const val ACCENT1_CHROMA = 48.0f
const val GOOGLE_BLUE = 0xFF1b6ef3.toInt()
const val MIN_CHROMA = 5
internal enum class ChromaStrategy {
EQ, GTE
}
internal interface Hue {
fun get(sourceColor: Cam): Double
internal enum class HueStrategy {
SOURCE, ADD, SUBTRACT
}
internal class Chroma(val strategy: ChromaStrategy, val value: Double) {
fun get(sourceChroma: Double): Double {
return when (strategy) {
ChromaStrategy.EQ -> value
ChromaStrategy.GTE -> sourceChroma.coerceAtLeast(value)
/**
* Given a hue, and a mapping of hues to hue rotations, find which hues in the mapping the
* hue fall betweens, and use the hue rotation of the lower hue.
*
* @param sourceHue hue of source color
* @param hueAndRotations list of pairs, where the first item in a pair is a hue, and the
* second item in the pair is a hue rotation that should be applied
*/
fun getHueRotation(sourceHue: Float, hueAndRotations: List<Pair<Int, Int>>): Double {
for (i in 0..hueAndRotations.size) {
val previousIndex = if (i == 0) hueAndRotations.size - 1 else i - 1
val thisHue = hueAndRotations[i].first
val previousHue = hueAndRotations[previousIndex].first
if (ColorScheme.angleIsBetween(sourceHue, thisHue, previousHue)) {
return ColorScheme.wrapDegreesDouble(sourceHue.toDouble() +
hueAndRotations[previousIndex].first)
}
}
// If this statement executes, something is wrong, there should have been a rotation
// found using the arrays.
return sourceHue.toDouble()
}
}
internal class Hue(val strategy: HueStrategy = HueStrategy.SOURCE, val value: Double = 0.0) {
fun get(sourceHue: Double): Double {
return when (strategy) {
HueStrategy.SOURCE -> sourceHue
HueStrategy.ADD -> ColorScheme.wrapDegreesDouble(sourceHue + value)
HueStrategy.SUBTRACT -> ColorScheme.wrapDegreesDouble(sourceHue - value)
}
internal class HueSource : Hue {
override fun get(sourceColor: Cam): Double {
return sourceColor.hue.toDouble()
}
}
internal class TonalSpec(val hue: Hue = Hue(), val chroma: Chroma) {
internal class HueAdd(val amountDegrees: Double) : Hue {
override fun get(sourceColor: Cam): Double {
return ColorScheme.wrapDegreesDouble(sourceColor.hue.toDouble() + amountDegrees)
}
}
internal class HueSubtract(val amountDegrees: Double) : Hue {
override fun get(sourceColor: Cam): Double {
return ColorScheme.wrapDegreesDouble(sourceColor.hue.toDouble() - amountDegrees)
}
}
internal class HueVibrantSecondary() : Hue {
val hueToRotations = listOf(Pair(24, 15), Pair(53, 15), Pair(91, 15), Pair(123, 15),
Pair(141, 15), Pair(172, 15), Pair(198, 15), Pair(234, 18), Pair(272, 18),
Pair(302, 18), Pair(329, 30), Pair(354, 15))
override fun get(sourceColor: Cam): Double {
return getHueRotation(sourceColor.hue, hueToRotations)
}
}
internal class HueVibrantTertiary() : Hue {
val hueToRotations = listOf(Pair(24, 30), Pair(53, 30), Pair(91, 15), Pair(123, 30),
Pair(141, 27), Pair(172, 27), Pair(198, 30), Pair(234, 35), Pair(272, 30),
Pair(302, 30), Pair(329, 60), Pair(354, 30))
override fun get(sourceColor: Cam): Double {
return getHueRotation(sourceColor.hue, hueToRotations)
}
}
internal class HueExpressiveSecondary() : Hue {
val hueToRotations = listOf(Pair(24, 95), Pair(53, 45), Pair(91, 45), Pair(123, 20),
Pair(141, 45), Pair(172, 45), Pair(198, 15), Pair(234, 15),
Pair(272, 45), Pair(302, 45), Pair(329, 45), Pair(354, 45))
override fun get(sourceColor: Cam): Double {
return getHueRotation(sourceColor.hue, hueToRotations)
}
}
internal class HueExpressiveTertiary() : Hue {
val hueToRotations = listOf(Pair(24, 20), Pair(53, 20), Pair(91, 20), Pair(123, 45),
Pair(141, 20), Pair(172, 20), Pair(198, 90), Pair(234, 90), Pair(272, 20),
Pair(302, 20), Pair(329, 120), Pair(354, 120))
override fun get(sourceColor: Cam): Double {
return getHueRotation(sourceColor.hue, hueToRotations)
}
}
internal interface Chroma {
fun get(sourceColor: Cam): Double
/**
* Given a hue, and a mapping of hues to hue rotations, find which hues in the mapping the
* hue fall betweens, and use the hue rotation of the lower hue.
*
* @param sourceHue hue of source color
* @param hueAndChromas list of pairs, where the first item in a pair is a hue, and the
* second item in the pair is a chroma that should be applied
*/
fun getSpecifiedChroma(sourceHue: Float, hueAndChromas: List<Pair<Int, Int>>): Double {
for (i in 0..hueAndChromas.size) {
val previousIndex = if (i == 0) hueAndChromas.size - 1 else i - 1
val thisHue = hueAndChromas[i].first
val previousHue = hueAndChromas[previousIndex].first
if (ColorScheme.angleIsBetween(sourceHue, thisHue, previousHue)) {
return hueAndChromas[i].second.toDouble()
}
}
// If this statement executes, something is wrong, there should have been a rotation
// found using the arrays.
return sourceHue.toDouble()
}
}
internal class ChromaConstant(val chroma: Double) : Chroma {
override fun get(sourceColor: Cam): Double {
return chroma
}
}
internal class ChromaExpressiveNeutral() : Chroma {
val hueToChromas = listOf(Pair(24, 8), Pair(53, 8), Pair(91, 8), Pair(123, 8),
Pair(141, 6), Pair(172, 6), Pair(198, 8), Pair(234, 8), Pair(272, 8),
Pair(302, 8), Pair(329, 8), Pair(354, 8))
override fun get(sourceColor: Cam): Double {
return getSpecifiedChroma(sourceColor.hue, hueToChromas)
}
}
internal class TonalSpec(val hue: Hue = HueSource(), val chroma: Chroma) {
fun shades(sourceColor: Cam): List<Int> {
val hue = hue.get(sourceColor.hue.toDouble())
val chroma = chroma.get(sourceColor.chroma.toDouble())
val hue = hue.get(sourceColor)
val chroma = chroma.get(sourceColor)
return Shades.of(hue.toFloat(), chroma.toFloat()).toList()
}
}
@@ -76,46 +173,46 @@ internal class CoreSpec(
enum class Style(internal val coreSpec: CoreSpec) {
SPRITZ(CoreSpec(
a1 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 12.0)),
a2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 8.0)),
a3 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 16.0)),
n1 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 4.0)),
n2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 8.0))
a1 = TonalSpec(HueSource(), ChromaConstant(12.0)),
a2 = TonalSpec(HueSource(), ChromaConstant(8.0)),
a3 = TonalSpec(HueSource(), ChromaConstant(16.0)),
n1 = TonalSpec(HueSource(), ChromaConstant(2.0)),
n2 = TonalSpec(HueSource(), ChromaConstant(2.0))
)),
TONAL_SPOT(CoreSpec(
a1 = TonalSpec(chroma = Chroma(ChromaStrategy.GTE, 32.0)),
a2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 16.0)),
a3 = TonalSpec(Hue(HueStrategy.ADD, 60.0), Chroma(ChromaStrategy.EQ, 24.0)),
n1 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 4.0)),
n2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 8.0))
a1 = TonalSpec(HueSource(), ChromaConstant(36.0)),
a2 = TonalSpec(HueSource(), ChromaConstant(16.0)),
a3 = TonalSpec(HueAdd(60.0), ChromaConstant(24.0)),
n1 = TonalSpec(HueSource(), ChromaConstant(4.0)),
n2 = TonalSpec(HueSource(), ChromaConstant(8.0))
)),
VIBRANT(CoreSpec(
a1 = TonalSpec(chroma = Chroma(ChromaStrategy.GTE, 48.0)),
a2 = TonalSpec(Hue(HueStrategy.ADD, 15.0), Chroma(ChromaStrategy.EQ, 24.0)),
a3 = TonalSpec(Hue(HueStrategy.ADD, 30.0), Chroma(ChromaStrategy.GTE, 32.0)),
n1 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 8.0)),
n2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 16.0))
a1 = TonalSpec(HueSource(), ChromaConstant(48.0)),
a2 = TonalSpec(HueVibrantSecondary(), ChromaConstant(24.0)),
a3 = TonalSpec(HueVibrantTertiary(), ChromaConstant(32.0)),
n1 = TonalSpec(HueSource(), ChromaConstant(6.0)),
n2 = TonalSpec(HueSource(), ChromaConstant(12.0))
)),
EXPRESSIVE(CoreSpec(
a1 = TonalSpec(Hue(HueStrategy.SUBTRACT, 60.0), Chroma(ChromaStrategy.GTE, 64.0)),
a2 = TonalSpec(Hue(HueStrategy.SUBTRACT, 30.0), Chroma(ChromaStrategy.EQ, 24.0)),
a3 = TonalSpec(chroma = Chroma(ChromaStrategy.GTE, 48.0)),
n1 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 12.0)),
n2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 16.0))
a1 = TonalSpec(HueAdd(240.0), ChromaConstant(40.0)),
a2 = TonalSpec(HueExpressiveSecondary(), ChromaConstant(24.0)),
a3 = TonalSpec(HueExpressiveTertiary(), ChromaConstant(40.0)),
n1 = TonalSpec(HueAdd(15.0), ChromaExpressiveNeutral()),
n2 = TonalSpec(HueAdd(15.0), ChromaConstant(12.0))
)),
RAINBOW(CoreSpec(
a1 = TonalSpec(chroma = Chroma(ChromaStrategy.GTE, 48.0)),
a2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 16.0)),
a3 = TonalSpec(Hue(HueStrategy.ADD, 60.0), Chroma(ChromaStrategy.EQ, 24.0)),
n1 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 0.0)),
n2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 0.0))
a1 = TonalSpec(HueSource(), ChromaConstant(48.0)),
a2 = TonalSpec(HueSource(), ChromaConstant(16.0)),
a3 = TonalSpec(HueAdd(60.0), ChromaConstant(24.0)),
n1 = TonalSpec(HueSource(), ChromaConstant(0.0)),
n2 = TonalSpec(HueSource(), ChromaConstant(0.0))
)),
FRUIT_SALAD(CoreSpec(
a1 = TonalSpec(Hue(HueStrategy.SUBTRACT, 50.0), Chroma(ChromaStrategy.GTE, 48.0)),
a2 = TonalSpec(Hue(HueStrategy.SUBTRACT, 50.0), Chroma(ChromaStrategy.EQ, 36.0)),
a3 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 36.0)),
n1 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 10.0)),
n2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 16.0))
a1 = TonalSpec(HueSubtract(50.0), ChromaConstant(48.0)),
a2 = TonalSpec(HueSubtract(50.0), ChromaConstant(36.0)),
a3 = TonalSpec(HueSource(), ChromaConstant(36.0)),
n1 = TonalSpec(HueSource(), ChromaConstant(10.0)),
n2 = TonalSpec(HueSource(), ChromaConstant(16.0))
)),
}
@@ -296,6 +393,13 @@ class ColorScheme(
return seeds
}
internal fun angleIsBetween(angle: Float, a: Int, b: Int): Boolean {
if (a < b) {
return a <= angle && angle <= b
}
return a <= angle || angle <= b
}
private fun wrapDegrees(degrees: Int): Int {
return when {
degrees < 0 -> {

View File

@@ -59,9 +59,6 @@ public class Shades {
shades[1] = ColorUtils.CAMToColor(hue, Math.min(40f, chroma), 95);
for (int i = 2; i < 12; i++) {
float lStar = (i == 6) ? MIDDLE_LSTAR : 100 - 10 * (i - 1);
if (lStar >= 90) {
chroma = Math.min(40f, chroma);
}
shades[i] = ColorUtils.CAMToColor(hue, chroma, lStar);
}
return shades;

View File

@@ -123,7 +123,7 @@ public class ColorSchemeTest extends SysuiTestCase {
Style.VIBRANT /* style */);
int neutralMid = colorScheme.getNeutral1().get(colorScheme.getNeutral1().size() / 2);
Cam cam = Cam.fromInt(neutralMid);
Assert.assertEquals(cam.getChroma(), 8.0, 1.0);
Assert.assertTrue(cam.getChroma() <= 8.0);
}
@Test
@@ -133,7 +133,7 @@ public class ColorSchemeTest extends SysuiTestCase {
Style.EXPRESSIVE /* style */);
int neutralMid = colorScheme.getNeutral1().get(colorScheme.getNeutral1().size() / 2);
Cam cam = Cam.fromInt(neutralMid);
Assert.assertEquals(cam.getChroma(), 12.0, 1.0);
Assert.assertTrue(cam.getChroma() <= 8.0);
}
/**