Loading packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt +266 −128 Original line number Diff line number Diff line Loading @@ -23,6 +23,8 @@ import com.android.internal.graphics.ColorUtils import com.android.internal.graphics.cam.Cam import com.android.internal.graphics.cam.CamUtils import kotlin.math.absoluteValue import kotlin.math.max import kotlin.math.min import kotlin.math.roundToInt const val TAG = "ColorScheme" Loading @@ -35,12 +37,12 @@ internal interface Hue { 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. * 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 * @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 { val sanitizedSourceHue = (if (sourceHue < 0 || sourceHue >= 360) 0 else sourceHue).toFloat() Loading @@ -48,8 +50,9 @@ internal interface Hue { val thisHue = hueAndRotations[i].first.toFloat() val nextHue = hueAndRotations[i + 1].first.toFloat() if (thisHue <= sanitizedSourceHue && sanitizedSourceHue < nextHue) { return ColorScheme.wrapDegreesDouble(sanitizedSourceHue.toDouble() + hueAndRotations[i].second) return ColorScheme.wrapDegreesDouble( sanitizedSourceHue.toDouble() + hueAndRotations[i].second ) } } Loading Loading @@ -78,8 +81,18 @@ internal class HueSubtract(val amountDegrees: Double) : Hue { } internal class HueVibrantSecondary() : Hue { val hueToRotations = listOf(Pair(0, 18), Pair(41, 15), Pair(61, 10), Pair(101, 12), Pair(131, 15), Pair(181, 18), Pair(251, 15), Pair(301, 12), Pair(360, 12)) val hueToRotations = listOf( Pair(0, 18), Pair(41, 15), Pair(61, 10), Pair(101, 12), Pair(131, 15), Pair(181, 18), Pair(251, 15), Pair(301, 12), Pair(360, 12) ) override fun get(sourceColor: Cam): Double { return getHueRotation(sourceColor.hue, hueToRotations) Loading @@ -87,8 +100,18 @@ internal class HueVibrantSecondary() : Hue { } internal class HueVibrantTertiary() : Hue { val hueToRotations = listOf(Pair(0, 35), Pair(41, 30), Pair(61, 20), Pair(101, 25), Pair(131, 30), Pair(181, 35), Pair(251, 30), Pair(301, 25), Pair(360, 25)) val hueToRotations = listOf( Pair(0, 35), Pair(41, 30), Pair(61, 20), Pair(101, 25), Pair(131, 30), Pair(181, 35), Pair(251, 30), Pair(301, 25), Pair(360, 25) ) override fun get(sourceColor: Cam): Double { return getHueRotation(sourceColor.hue, hueToRotations) Loading @@ -96,8 +119,18 @@ internal class HueVibrantTertiary() : Hue { } internal class HueExpressiveSecondary() : Hue { val hueToRotations = listOf(Pair(0, 45), Pair(21, 95), Pair(51, 45), Pair(121, 20), Pair(151, 45), Pair(191, 90), Pair(271, 45), Pair(321, 45), Pair(360, 45)) val hueToRotations = listOf( Pair(0, 45), Pair(21, 95), Pair(51, 45), Pair(121, 20), Pair(151, 45), Pair(191, 90), Pair(271, 45), Pair(321, 45), Pair(360, 45) ) override fun get(sourceColor: Cam): Double { return getHueRotation(sourceColor.hue, hueToRotations) Loading @@ -105,8 +138,18 @@ internal class HueExpressiveSecondary() : Hue { } internal class HueExpressiveTertiary() : Hue { val hueToRotations = listOf(Pair(0, 120), Pair(21, 120), Pair(51, 20), Pair(121, 45), Pair(151, 20), Pair(191, 15), Pair(271, 20), Pair(321, 120), Pair(360, 120)) val hueToRotations = listOf( Pair(0, 120), Pair(21, 120), Pair(51, 20), Pair(121, 45), Pair(151, 20), Pair(191, 15), Pair(271, 20), Pair(321, 120), Pair(360, 120) ) override fun get(sourceColor: Cam): Double { return getHueRotation(sourceColor.hue, hueToRotations) Loading @@ -115,13 +158,18 @@ internal class HueExpressiveTertiary() : Hue { internal interface Chroma { fun get(sourceColor: Cam): Double companion object { val MAX_VALUE = 120.0 val MIN_VALUE = 0.0 } } internal class ChromaMaxOut : Chroma { override fun get(sourceColor: Cam): Double { // Intentionally high. Gamut mapping from impossible HCT to sRGB will ensure that // the maximum chroma is reached, even if lower than this constant. return 130.0 return Chroma.MAX_VALUE + 10.0 } } Loading @@ -131,6 +179,23 @@ internal class ChromaMultiple(val multiple: Double) : Chroma { } } internal class ChromaAdd(val amount: Double) : Chroma { override fun get(sourceColor: Cam): Double { return sourceColor.chroma + amount } } internal class ChromaBound( val baseChroma: Chroma, val minVal: Double, val maxVal: Double, ) : Chroma { override fun get(sourceColor: Cam): Double { val result = baseChroma.get(sourceColor) return min(max(result, minVal), maxVal) } } internal class ChromaConstant(val chroma: Double) : Chroma { override fun get(sourceColor: Cam): Double { return chroma Loading @@ -149,6 +214,12 @@ internal class TonalSpec(val hue: Hue = HueSource(), val chroma: Chroma) { val chroma = chroma.get(sourceColor) return Shades.of(hue.toFloat(), chroma.toFloat()).toList() } fun getAtTone(sourceColor: Cam, tone: Float): Int { val hue = hue.get(sourceColor) val chroma = chroma.get(sourceColor) return ColorUtils.CAMToColor(hue.toFloat(), chroma.toFloat(), (1000f - tone) / 10f) } } internal class CoreSpec( Loading @@ -160,92 +231,150 @@ internal class CoreSpec( ) enum class Style(internal val coreSpec: CoreSpec) { SPRITZ(CoreSpec( SPRITZ( CoreSpec( 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( ) ), TONAL_SPOT( CoreSpec( a1 = TonalSpec(HueSource(), ChromaConstant(36.0)), a2 = TonalSpec(HueSource(), ChromaConstant(16.0)), a3 = TonalSpec(HueAdd(60.0), ChromaConstant(24.0)), n1 = TonalSpec(HueSource(), ChromaConstant(6.0)), n2 = TonalSpec(HueSource(), ChromaConstant(8.0)) )), VIBRANT(CoreSpec( ) ), VIBRANT( CoreSpec( a1 = TonalSpec(HueSource(), ChromaMaxOut()), a2 = TonalSpec(HueVibrantSecondary(), ChromaConstant(24.0)), a3 = TonalSpec(HueVibrantTertiary(), ChromaConstant(32.0)), n1 = TonalSpec(HueSource(), ChromaConstant(10.0)), n2 = TonalSpec(HueSource(), ChromaConstant(12.0)) )), EXPRESSIVE(CoreSpec( ) ), EXPRESSIVE( CoreSpec( a1 = TonalSpec(HueAdd(240.0), ChromaConstant(40.0)), a2 = TonalSpec(HueExpressiveSecondary(), ChromaConstant(24.0)), a3 = TonalSpec(HueExpressiveTertiary(), ChromaConstant(32.0)), n1 = TonalSpec(HueAdd(15.0), ChromaConstant(8.0)), n2 = TonalSpec(HueAdd(15.0), ChromaConstant(12.0)) )), RAINBOW(CoreSpec( ) ), RAINBOW( CoreSpec( 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( ) ), FRUIT_SALAD( CoreSpec( 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)) )), CONTENT(CoreSpec( ) ), CONTENT( CoreSpec( a1 = TonalSpec(HueSource(), ChromaSource()), a2 = TonalSpec(HueSource(), ChromaMultiple(0.33)), a3 = TonalSpec(HueSource(), ChromaMultiple(0.66)), n1 = TonalSpec(HueSource(), ChromaMultiple(0.0833)), n2 = TonalSpec(HueSource(), ChromaMultiple(0.1666)) )), MONOCHROMATIC(CoreSpec( ) ), MONOCHROMATIC( CoreSpec( a1 = TonalSpec(HueSource(), ChromaConstant(.0)), a2 = TonalSpec(HueSource(), ChromaConstant(.0)), a3 = TonalSpec(HueSource(), ChromaConstant(.0)), n1 = TonalSpec(HueSource(), ChromaConstant(.0)), n2 = TonalSpec(HueSource(), ChromaConstant(.0)) )), ) ), CLOCK( CoreSpec( a1 = TonalSpec(HueSource(), ChromaBound(ChromaSource(), 20.0, Chroma.MAX_VALUE)), a2 = TonalSpec(HueAdd(10.0), ChromaBound(ChromaMultiple(0.85), 17.0, 40.0)), a3 = TonalSpec(HueAdd(20.0), ChromaBound(ChromaAdd(20.0), 50.0, Chroma.MAX_VALUE)), // Not Used n1 = TonalSpec(HueSource(), ChromaConstant(0.0)), n2 = TonalSpec(HueSource(), ChromaConstant(0.0)) ) ), CLOCK_VIBRANT( CoreSpec( a1 = TonalSpec(HueSource(), ChromaBound(ChromaSource(), 30.0, Chroma.MAX_VALUE)), a2 = TonalSpec(HueAdd(20.0), ChromaBound(ChromaSource(), 30.0, Chroma.MAX_VALUE)), a3 = TonalSpec(HueAdd(60.0), ChromaBound(ChromaSource(), 30.0, Chroma.MAX_VALUE)), // Not Used n1 = TonalSpec(HueSource(), ChromaConstant(0.0)), n2 = TonalSpec(HueSource(), ChromaConstant(0.0)) ) ) } class TonalPalette { val shadeKeys = listOf(10, 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000) val allShades: List<Int> val allShadesMapped: Map<Int, Int> class TonalPalette internal constructor( private val spec: TonalSpec, seedColor: Int, ) { val seedCam: Cam = Cam.fromInt(seedColor) val allShades: List<Int> = spec.shades(seedCam) val allShadesMapped: Map<Int, Int> = SHADE_KEYS.zip(allShades).toMap() val baseColor: Int internal constructor(spec: TonalSpec, seedColor: Int) { val seedCam = Cam.fromInt(seedColor) allShades = spec.shades(seedCam) allShadesMapped = shadeKeys.zip(allShades).toMap() init { val h = spec.hue.get(seedCam).toFloat() val c = spec.chroma.get(seedCam).toFloat() baseColor = ColorUtils.CAMToColor(h, c, CamUtils.lstarFromInt(seedColor)) } val s10: Int get() = this.allShades[0] val s50: Int get() = this.allShades[1] val s100: Int get() = this.allShades[2] val s200: Int get() = this.allShades[3] val s300: Int get() = this.allShades[4] val s400: Int get() = this.allShades[5] val s500: Int get() = this.allShades[6] val s600: Int get() = this.allShades[7] val s700: Int get() = this.allShades[8] val s800: Int get() = this.allShades[9] val s900: Int get() = this.allShades[10] val s1000: Int get() = this.allShades[11] // Dynamically computed tones across the full range from 0 to 1000 fun getAtTone(tone: Float) = spec.getAtTone(seedCam, tone) // Predefined & precomputed tones val s10: Int get() = this.allShades[0] val s50: Int get() = this.allShades[1] val s100: Int get() = this.allShades[2] val s200: Int get() = this.allShades[3] val s300: Int get() = this.allShades[4] val s400: Int get() = this.allShades[5] val s500: Int get() = this.allShades[6] val s600: Int get() = this.allShades[7] val s700: Int get() = this.allShades[8] val s800: Int get() = this.allShades[9] val s900: Int get() = this.allShades[10] val s1000: Int get() = this.allShades[11] companion object { val SHADE_KEYS = listOf(10, 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000) } } class ColorScheme( Loading @@ -260,16 +389,14 @@ class ColorScheme( val neutral1: TonalPalette val neutral2: TonalPalette constructor(@ColorInt seed: Int, darkTheme: Boolean) : this(seed, darkTheme, Style.TONAL_SPOT) constructor(@ColorInt seed: Int, darkTheme: Boolean) : this(seed, darkTheme, Style.TONAL_SPOT) @JvmOverloads constructor( wallpaperColors: WallpaperColors, darkTheme: Boolean, style: Style = Style.TONAL_SPOT ) : this(getSeedColor(wallpaperColors, style != Style.CONTENT), darkTheme, style) ) : this(getSeedColor(wallpaperColors, style != Style.CONTENT), darkTheme, style) val allHues: List<TonalPalette> get() { Loading Loading @@ -301,7 +428,8 @@ class ColorScheme( init { val proposedSeedCam = Cam.fromInt(seed) val seedArgb = if (seed == Color.TRANSPARENT) { val seedArgb = if (seed == Color.TRANSPARENT) { GOOGLE_BLUE } else if (style != Style.CONTENT && proposedSeedCam.chroma < 5) { GOOGLE_BLUE Loading @@ -316,7 +444,11 @@ class ColorScheme( neutral2 = TonalPalette(style.coreSpec.n2, seedArgb) } val shadeCount get() = this.accent1.allShades.size val shadeCount get() = this.accent1.allShades.size val seedTone: Float get() = 1000f - CamUtils.lstarFromInt(seed) * 10f override fun toString(): String { return "ColorScheme {\n" + Loading Loading @@ -356,8 +488,8 @@ class ColorScheme( @JvmStatic @JvmOverloads fun getSeedColors(wallpaperColors: WallpaperColors, filter: Boolean = true): List<Int> { val totalPopulation = wallpaperColors.allColors.values.reduce { a, b -> a + b } .toDouble() val totalPopulation = wallpaperColors.allColors.values.reduce { a, b -> a + b }.toDouble() val totalPopulationMeaningless = (totalPopulation == 0.0) if (totalPopulationMeaningless) { // WallpaperColors with a population of 0 indicate the colors didn't come from Loading @@ -365,30 +497,33 @@ class ColorScheme( // secondary/tertiary colors. // // In this case, the colors are usually from a Live Wallpaper. val distinctColors = wallpaperColors.mainColors.map { it.toArgb() }.distinct().filter { val distinctColors = wallpaperColors.mainColors .map { it.toArgb() } .distinct() .filter { if (!filter) { true } else { Cam.fromInt(it).chroma >= MIN_CHROMA } }.toList() } .toList() if (distinctColors.isEmpty()) { return listOf(GOOGLE_BLUE) } return distinctColors } val intToProportion = wallpaperColors.allColors.mapValues { it.value.toDouble() / totalPopulation } val intToProportion = wallpaperColors.allColors.mapValues { it.value.toDouble() / totalPopulation } val intToCam = wallpaperColors.allColors.mapValues { Cam.fromInt(it.key) } // Get an array with 360 slots. A slot contains the percentage of colors with that hue. val hueProportions = huePopulations(intToCam, intToProportion, filter) // Map each color to the percentage of the image with its hue. val intToHueProportion = wallpaperColors.allColors.mapValues { val intToHueProportion = wallpaperColors.allColors.mapValues { val cam = intToCam[it.key]!! val hue = cam.hue.roundToInt() var proportion = 0.0 Loading @@ -400,16 +535,18 @@ class ColorScheme( // Remove any inappropriate seed colors. For example, low chroma colors look grayscale // raising their chroma will turn them to a much louder color that may not have been // in the image. val filteredIntToCam = if (!filter) intToCam else (intToCam.filter { val filteredIntToCam = if (!filter) intToCam else (intToCam.filter { val cam = it.value val proportion = intToHueProportion[it.key]!! cam.chroma >= MIN_CHROMA && (totalPopulationMeaningless || proportion > 0.01) }) // Sort the colors by score, from high to low. val intToScoreIntermediate = filteredIntToCam.mapValues { score(it.value, intToHueProportion[it.key]!!) } val intToScoreIntermediate = filteredIntToCam.mapValues { score(it.value, intToHueProportion[it.key]!!) } val intToScore = intToScoreIntermediate.entries.toMutableList() intToScore.sortByDescending { it.value } Loading @@ -423,7 +560,8 @@ class ColorScheme( seeds.clear() for (entry in intToScore) { val int = entry.key val existingSeedNearby = seeds.find { val existingSeedNearby = seeds.find { val hueA = intToCam[int]!!.hue val hueB = intToCam[it]!!.hue hueDiff(hueA, hueB) < i Loading Loading @@ -489,14 +627,14 @@ class ColorScheme( } private fun humanReadable(paletteName: String, colors: List<Int>): String { return "$paletteName\n" + colors.map { stringForColor(it) }.joinToString(separator = "\n") { it } return "$paletteName\n" + colors.map { stringForColor(it) }.joinToString(separator = "\n") { it } } private fun score(cam: Cam, proportion: Double): Double { val proportionScore = 0.7 * 100.0 * proportion val chromaScore = if (cam.chroma < ACCENT1_CHROMA) 0.1 * (cam.chroma - ACCENT1_CHROMA) val chromaScore = if (cam.chroma < ACCENT1_CHROMA) 0.1 * (cam.chroma - ACCENT1_CHROMA) else 0.3 * (cam.chroma - ACCENT1_CHROMA) return chromaScore + proportionScore } Loading packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt +9 −2 Original line number Diff line number Diff line Loading @@ -109,6 +109,9 @@ interface ClockEvents { /** Call whenever the locale changes */ fun onLocaleChanged(locale: Locale) {} val isReactiveToTone get() = true /** Call whenever the color palette should update */ fun onColorPaletteChanged(resources: Resources) {} Loading Loading @@ -164,8 +167,12 @@ interface ClockFaceEvents { val hasCustomWeatherDataDisplay: Boolean get() = false /** Region Darkness specific to the clock face */ fun onRegionDarknessChanged(isDark: Boolean) {} /** * Region Darkness specific to the clock face. * - isRegionDark = dark theme -> clock should be light * - !isRegionDark = light theme -> clock should be dark */ fun onRegionDarknessChanged(isRegionDark: Boolean) {} /** * Call whenever font settings change. Pass in a target font size in pixels. The specific clock Loading Loading
packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt +266 −128 Original line number Diff line number Diff line Loading @@ -23,6 +23,8 @@ import com.android.internal.graphics.ColorUtils import com.android.internal.graphics.cam.Cam import com.android.internal.graphics.cam.CamUtils import kotlin.math.absoluteValue import kotlin.math.max import kotlin.math.min import kotlin.math.roundToInt const val TAG = "ColorScheme" Loading @@ -35,12 +37,12 @@ internal interface Hue { 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. * 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 * @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 { val sanitizedSourceHue = (if (sourceHue < 0 || sourceHue >= 360) 0 else sourceHue).toFloat() Loading @@ -48,8 +50,9 @@ internal interface Hue { val thisHue = hueAndRotations[i].first.toFloat() val nextHue = hueAndRotations[i + 1].first.toFloat() if (thisHue <= sanitizedSourceHue && sanitizedSourceHue < nextHue) { return ColorScheme.wrapDegreesDouble(sanitizedSourceHue.toDouble() + hueAndRotations[i].second) return ColorScheme.wrapDegreesDouble( sanitizedSourceHue.toDouble() + hueAndRotations[i].second ) } } Loading Loading @@ -78,8 +81,18 @@ internal class HueSubtract(val amountDegrees: Double) : Hue { } internal class HueVibrantSecondary() : Hue { val hueToRotations = listOf(Pair(0, 18), Pair(41, 15), Pair(61, 10), Pair(101, 12), Pair(131, 15), Pair(181, 18), Pair(251, 15), Pair(301, 12), Pair(360, 12)) val hueToRotations = listOf( Pair(0, 18), Pair(41, 15), Pair(61, 10), Pair(101, 12), Pair(131, 15), Pair(181, 18), Pair(251, 15), Pair(301, 12), Pair(360, 12) ) override fun get(sourceColor: Cam): Double { return getHueRotation(sourceColor.hue, hueToRotations) Loading @@ -87,8 +100,18 @@ internal class HueVibrantSecondary() : Hue { } internal class HueVibrantTertiary() : Hue { val hueToRotations = listOf(Pair(0, 35), Pair(41, 30), Pair(61, 20), Pair(101, 25), Pair(131, 30), Pair(181, 35), Pair(251, 30), Pair(301, 25), Pair(360, 25)) val hueToRotations = listOf( Pair(0, 35), Pair(41, 30), Pair(61, 20), Pair(101, 25), Pair(131, 30), Pair(181, 35), Pair(251, 30), Pair(301, 25), Pair(360, 25) ) override fun get(sourceColor: Cam): Double { return getHueRotation(sourceColor.hue, hueToRotations) Loading @@ -96,8 +119,18 @@ internal class HueVibrantTertiary() : Hue { } internal class HueExpressiveSecondary() : Hue { val hueToRotations = listOf(Pair(0, 45), Pair(21, 95), Pair(51, 45), Pair(121, 20), Pair(151, 45), Pair(191, 90), Pair(271, 45), Pair(321, 45), Pair(360, 45)) val hueToRotations = listOf( Pair(0, 45), Pair(21, 95), Pair(51, 45), Pair(121, 20), Pair(151, 45), Pair(191, 90), Pair(271, 45), Pair(321, 45), Pair(360, 45) ) override fun get(sourceColor: Cam): Double { return getHueRotation(sourceColor.hue, hueToRotations) Loading @@ -105,8 +138,18 @@ internal class HueExpressiveSecondary() : Hue { } internal class HueExpressiveTertiary() : Hue { val hueToRotations = listOf(Pair(0, 120), Pair(21, 120), Pair(51, 20), Pair(121, 45), Pair(151, 20), Pair(191, 15), Pair(271, 20), Pair(321, 120), Pair(360, 120)) val hueToRotations = listOf( Pair(0, 120), Pair(21, 120), Pair(51, 20), Pair(121, 45), Pair(151, 20), Pair(191, 15), Pair(271, 20), Pair(321, 120), Pair(360, 120) ) override fun get(sourceColor: Cam): Double { return getHueRotation(sourceColor.hue, hueToRotations) Loading @@ -115,13 +158,18 @@ internal class HueExpressiveTertiary() : Hue { internal interface Chroma { fun get(sourceColor: Cam): Double companion object { val MAX_VALUE = 120.0 val MIN_VALUE = 0.0 } } internal class ChromaMaxOut : Chroma { override fun get(sourceColor: Cam): Double { // Intentionally high. Gamut mapping from impossible HCT to sRGB will ensure that // the maximum chroma is reached, even if lower than this constant. return 130.0 return Chroma.MAX_VALUE + 10.0 } } Loading @@ -131,6 +179,23 @@ internal class ChromaMultiple(val multiple: Double) : Chroma { } } internal class ChromaAdd(val amount: Double) : Chroma { override fun get(sourceColor: Cam): Double { return sourceColor.chroma + amount } } internal class ChromaBound( val baseChroma: Chroma, val minVal: Double, val maxVal: Double, ) : Chroma { override fun get(sourceColor: Cam): Double { val result = baseChroma.get(sourceColor) return min(max(result, minVal), maxVal) } } internal class ChromaConstant(val chroma: Double) : Chroma { override fun get(sourceColor: Cam): Double { return chroma Loading @@ -149,6 +214,12 @@ internal class TonalSpec(val hue: Hue = HueSource(), val chroma: Chroma) { val chroma = chroma.get(sourceColor) return Shades.of(hue.toFloat(), chroma.toFloat()).toList() } fun getAtTone(sourceColor: Cam, tone: Float): Int { val hue = hue.get(sourceColor) val chroma = chroma.get(sourceColor) return ColorUtils.CAMToColor(hue.toFloat(), chroma.toFloat(), (1000f - tone) / 10f) } } internal class CoreSpec( Loading @@ -160,92 +231,150 @@ internal class CoreSpec( ) enum class Style(internal val coreSpec: CoreSpec) { SPRITZ(CoreSpec( SPRITZ( CoreSpec( 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( ) ), TONAL_SPOT( CoreSpec( a1 = TonalSpec(HueSource(), ChromaConstant(36.0)), a2 = TonalSpec(HueSource(), ChromaConstant(16.0)), a3 = TonalSpec(HueAdd(60.0), ChromaConstant(24.0)), n1 = TonalSpec(HueSource(), ChromaConstant(6.0)), n2 = TonalSpec(HueSource(), ChromaConstant(8.0)) )), VIBRANT(CoreSpec( ) ), VIBRANT( CoreSpec( a1 = TonalSpec(HueSource(), ChromaMaxOut()), a2 = TonalSpec(HueVibrantSecondary(), ChromaConstant(24.0)), a3 = TonalSpec(HueVibrantTertiary(), ChromaConstant(32.0)), n1 = TonalSpec(HueSource(), ChromaConstant(10.0)), n2 = TonalSpec(HueSource(), ChromaConstant(12.0)) )), EXPRESSIVE(CoreSpec( ) ), EXPRESSIVE( CoreSpec( a1 = TonalSpec(HueAdd(240.0), ChromaConstant(40.0)), a2 = TonalSpec(HueExpressiveSecondary(), ChromaConstant(24.0)), a3 = TonalSpec(HueExpressiveTertiary(), ChromaConstant(32.0)), n1 = TonalSpec(HueAdd(15.0), ChromaConstant(8.0)), n2 = TonalSpec(HueAdd(15.0), ChromaConstant(12.0)) )), RAINBOW(CoreSpec( ) ), RAINBOW( CoreSpec( 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( ) ), FRUIT_SALAD( CoreSpec( 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)) )), CONTENT(CoreSpec( ) ), CONTENT( CoreSpec( a1 = TonalSpec(HueSource(), ChromaSource()), a2 = TonalSpec(HueSource(), ChromaMultiple(0.33)), a3 = TonalSpec(HueSource(), ChromaMultiple(0.66)), n1 = TonalSpec(HueSource(), ChromaMultiple(0.0833)), n2 = TonalSpec(HueSource(), ChromaMultiple(0.1666)) )), MONOCHROMATIC(CoreSpec( ) ), MONOCHROMATIC( CoreSpec( a1 = TonalSpec(HueSource(), ChromaConstant(.0)), a2 = TonalSpec(HueSource(), ChromaConstant(.0)), a3 = TonalSpec(HueSource(), ChromaConstant(.0)), n1 = TonalSpec(HueSource(), ChromaConstant(.0)), n2 = TonalSpec(HueSource(), ChromaConstant(.0)) )), ) ), CLOCK( CoreSpec( a1 = TonalSpec(HueSource(), ChromaBound(ChromaSource(), 20.0, Chroma.MAX_VALUE)), a2 = TonalSpec(HueAdd(10.0), ChromaBound(ChromaMultiple(0.85), 17.0, 40.0)), a3 = TonalSpec(HueAdd(20.0), ChromaBound(ChromaAdd(20.0), 50.0, Chroma.MAX_VALUE)), // Not Used n1 = TonalSpec(HueSource(), ChromaConstant(0.0)), n2 = TonalSpec(HueSource(), ChromaConstant(0.0)) ) ), CLOCK_VIBRANT( CoreSpec( a1 = TonalSpec(HueSource(), ChromaBound(ChromaSource(), 30.0, Chroma.MAX_VALUE)), a2 = TonalSpec(HueAdd(20.0), ChromaBound(ChromaSource(), 30.0, Chroma.MAX_VALUE)), a3 = TonalSpec(HueAdd(60.0), ChromaBound(ChromaSource(), 30.0, Chroma.MAX_VALUE)), // Not Used n1 = TonalSpec(HueSource(), ChromaConstant(0.0)), n2 = TonalSpec(HueSource(), ChromaConstant(0.0)) ) ) } class TonalPalette { val shadeKeys = listOf(10, 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000) val allShades: List<Int> val allShadesMapped: Map<Int, Int> class TonalPalette internal constructor( private val spec: TonalSpec, seedColor: Int, ) { val seedCam: Cam = Cam.fromInt(seedColor) val allShades: List<Int> = spec.shades(seedCam) val allShadesMapped: Map<Int, Int> = SHADE_KEYS.zip(allShades).toMap() val baseColor: Int internal constructor(spec: TonalSpec, seedColor: Int) { val seedCam = Cam.fromInt(seedColor) allShades = spec.shades(seedCam) allShadesMapped = shadeKeys.zip(allShades).toMap() init { val h = spec.hue.get(seedCam).toFloat() val c = spec.chroma.get(seedCam).toFloat() baseColor = ColorUtils.CAMToColor(h, c, CamUtils.lstarFromInt(seedColor)) } val s10: Int get() = this.allShades[0] val s50: Int get() = this.allShades[1] val s100: Int get() = this.allShades[2] val s200: Int get() = this.allShades[3] val s300: Int get() = this.allShades[4] val s400: Int get() = this.allShades[5] val s500: Int get() = this.allShades[6] val s600: Int get() = this.allShades[7] val s700: Int get() = this.allShades[8] val s800: Int get() = this.allShades[9] val s900: Int get() = this.allShades[10] val s1000: Int get() = this.allShades[11] // Dynamically computed tones across the full range from 0 to 1000 fun getAtTone(tone: Float) = spec.getAtTone(seedCam, tone) // Predefined & precomputed tones val s10: Int get() = this.allShades[0] val s50: Int get() = this.allShades[1] val s100: Int get() = this.allShades[2] val s200: Int get() = this.allShades[3] val s300: Int get() = this.allShades[4] val s400: Int get() = this.allShades[5] val s500: Int get() = this.allShades[6] val s600: Int get() = this.allShades[7] val s700: Int get() = this.allShades[8] val s800: Int get() = this.allShades[9] val s900: Int get() = this.allShades[10] val s1000: Int get() = this.allShades[11] companion object { val SHADE_KEYS = listOf(10, 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000) } } class ColorScheme( Loading @@ -260,16 +389,14 @@ class ColorScheme( val neutral1: TonalPalette val neutral2: TonalPalette constructor(@ColorInt seed: Int, darkTheme: Boolean) : this(seed, darkTheme, Style.TONAL_SPOT) constructor(@ColorInt seed: Int, darkTheme: Boolean) : this(seed, darkTheme, Style.TONAL_SPOT) @JvmOverloads constructor( wallpaperColors: WallpaperColors, darkTheme: Boolean, style: Style = Style.TONAL_SPOT ) : this(getSeedColor(wallpaperColors, style != Style.CONTENT), darkTheme, style) ) : this(getSeedColor(wallpaperColors, style != Style.CONTENT), darkTheme, style) val allHues: List<TonalPalette> get() { Loading Loading @@ -301,7 +428,8 @@ class ColorScheme( init { val proposedSeedCam = Cam.fromInt(seed) val seedArgb = if (seed == Color.TRANSPARENT) { val seedArgb = if (seed == Color.TRANSPARENT) { GOOGLE_BLUE } else if (style != Style.CONTENT && proposedSeedCam.chroma < 5) { GOOGLE_BLUE Loading @@ -316,7 +444,11 @@ class ColorScheme( neutral2 = TonalPalette(style.coreSpec.n2, seedArgb) } val shadeCount get() = this.accent1.allShades.size val shadeCount get() = this.accent1.allShades.size val seedTone: Float get() = 1000f - CamUtils.lstarFromInt(seed) * 10f override fun toString(): String { return "ColorScheme {\n" + Loading Loading @@ -356,8 +488,8 @@ class ColorScheme( @JvmStatic @JvmOverloads fun getSeedColors(wallpaperColors: WallpaperColors, filter: Boolean = true): List<Int> { val totalPopulation = wallpaperColors.allColors.values.reduce { a, b -> a + b } .toDouble() val totalPopulation = wallpaperColors.allColors.values.reduce { a, b -> a + b }.toDouble() val totalPopulationMeaningless = (totalPopulation == 0.0) if (totalPopulationMeaningless) { // WallpaperColors with a population of 0 indicate the colors didn't come from Loading @@ -365,30 +497,33 @@ class ColorScheme( // secondary/tertiary colors. // // In this case, the colors are usually from a Live Wallpaper. val distinctColors = wallpaperColors.mainColors.map { it.toArgb() }.distinct().filter { val distinctColors = wallpaperColors.mainColors .map { it.toArgb() } .distinct() .filter { if (!filter) { true } else { Cam.fromInt(it).chroma >= MIN_CHROMA } }.toList() } .toList() if (distinctColors.isEmpty()) { return listOf(GOOGLE_BLUE) } return distinctColors } val intToProportion = wallpaperColors.allColors.mapValues { it.value.toDouble() / totalPopulation } val intToProportion = wallpaperColors.allColors.mapValues { it.value.toDouble() / totalPopulation } val intToCam = wallpaperColors.allColors.mapValues { Cam.fromInt(it.key) } // Get an array with 360 slots. A slot contains the percentage of colors with that hue. val hueProportions = huePopulations(intToCam, intToProportion, filter) // Map each color to the percentage of the image with its hue. val intToHueProportion = wallpaperColors.allColors.mapValues { val intToHueProportion = wallpaperColors.allColors.mapValues { val cam = intToCam[it.key]!! val hue = cam.hue.roundToInt() var proportion = 0.0 Loading @@ -400,16 +535,18 @@ class ColorScheme( // Remove any inappropriate seed colors. For example, low chroma colors look grayscale // raising their chroma will turn them to a much louder color that may not have been // in the image. val filteredIntToCam = if (!filter) intToCam else (intToCam.filter { val filteredIntToCam = if (!filter) intToCam else (intToCam.filter { val cam = it.value val proportion = intToHueProportion[it.key]!! cam.chroma >= MIN_CHROMA && (totalPopulationMeaningless || proportion > 0.01) }) // Sort the colors by score, from high to low. val intToScoreIntermediate = filteredIntToCam.mapValues { score(it.value, intToHueProportion[it.key]!!) } val intToScoreIntermediate = filteredIntToCam.mapValues { score(it.value, intToHueProportion[it.key]!!) } val intToScore = intToScoreIntermediate.entries.toMutableList() intToScore.sortByDescending { it.value } Loading @@ -423,7 +560,8 @@ class ColorScheme( seeds.clear() for (entry in intToScore) { val int = entry.key val existingSeedNearby = seeds.find { val existingSeedNearby = seeds.find { val hueA = intToCam[int]!!.hue val hueB = intToCam[it]!!.hue hueDiff(hueA, hueB) < i Loading Loading @@ -489,14 +627,14 @@ class ColorScheme( } private fun humanReadable(paletteName: String, colors: List<Int>): String { return "$paletteName\n" + colors.map { stringForColor(it) }.joinToString(separator = "\n") { it } return "$paletteName\n" + colors.map { stringForColor(it) }.joinToString(separator = "\n") { it } } private fun score(cam: Cam, proportion: Double): Double { val proportionScore = 0.7 * 100.0 * proportion val chromaScore = if (cam.chroma < ACCENT1_CHROMA) 0.1 * (cam.chroma - ACCENT1_CHROMA) val chromaScore = if (cam.chroma < ACCENT1_CHROMA) 0.1 * (cam.chroma - ACCENT1_CHROMA) else 0.3 * (cam.chroma - ACCENT1_CHROMA) return chromaScore + proportionScore } Loading
packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt +9 −2 Original line number Diff line number Diff line Loading @@ -109,6 +109,9 @@ interface ClockEvents { /** Call whenever the locale changes */ fun onLocaleChanged(locale: Locale) {} val isReactiveToTone get() = true /** Call whenever the color palette should update */ fun onColorPaletteChanged(resources: Resources) {} Loading Loading @@ -164,8 +167,12 @@ interface ClockFaceEvents { val hasCustomWeatherDataDisplay: Boolean get() = false /** Region Darkness specific to the clock face */ fun onRegionDarknessChanged(isDark: Boolean) {} /** * Region Darkness specific to the clock face. * - isRegionDark = dark theme -> clock should be light * - !isRegionDark = light theme -> clock should be dark */ fun onRegionDarknessChanged(isRegionDark: Boolean) {} /** * Call whenever font settings change. Pass in a target font size in pixels. The specific clock Loading