Update styles to match latest specs
A) Refactor hue/chroma specification to support specification of hue rotation and chroma depending on source color hue. Update vibrant & expressive specs to use granular specification of hue rotations for secondary and tertiary palettes for both, and expressive's neutral uses specification of chroma for the neutral palette. B) Use chroma equal to seed color for all palettes, replacing some usage of >= C) Remove chroma 40 cap at tone 90 Test: atest ColorSchemeTest Bug: N/A Change-Id: Ib9fff483443ccae888faf636d4388b9658ff3dfe
This commit is contained in:
@@ -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 -> {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user