diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt index d8b050aa8e2c3..46dad02ddb450 100644 --- a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt +++ b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt @@ -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>): 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>): 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 { - 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 -> { diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/Shades.java b/packages/SystemUI/monet/src/com/android/systemui/monet/Shades.java index aab3538e3c4c1..c97b9601f7435 100644 --- a/packages/SystemUI/monet/src/com/android/systemui/monet/Shades.java +++ b/packages/SystemUI/monet/src/com/android/systemui/monet/Shades.java @@ -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; diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java index 6df56e98783c1..51088b1d929ba 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java @@ -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); } /**