From 7732fd2401176db4b09e6c065340cc07cf995ebb Mon Sep 17 00:00:00 2001 From: James O'Leary Date: Thu, 12 May 2022 16:46:33 +0000 Subject: [PATCH] Add content color scheme for media Use content color scheme for media player, which dynamically adapts color palette chromas to the source color, and permits monochrome source colors. Use for media. This allows ex. grayscale album covers to get a grayscale treatment in UI, per design requirement. Bug: 213314628 Test: Manual inspection at runtime Change-Id: Ib8ea007e34758cd7903b3432ab88af695cb22f68 --- .../com/android/systemui/monet/ColorScheme.kt | 53 ++++++++++++++----- .../systemui/media/MediaControlPanel.java | 6 ++- .../theme/ThemeOverlayController.java | 9 ++++ .../theme/ThemeOverlayControllerTest.java | 6 ++- 4 files changed, 57 insertions(+), 17 deletions(-) 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 dd45b6f39bddc..b4f496b3e21ba 100644 --- a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt +++ b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt @@ -21,7 +21,6 @@ import android.app.WallpaperColors import android.graphics.Color import com.android.internal.graphics.ColorUtils import com.android.internal.graphics.cam.Cam -import com.android.internal.graphics.cam.CamUtils.lstarFromInt import kotlin.math.absoluteValue import kotlin.math.max import kotlin.math.roundToInt @@ -143,12 +142,24 @@ internal class ChromaMinimum(val chroma: Double) : Chroma { } } +internal class ChromaMultiple(val multiple: Double) : Chroma { + override fun get(sourceColor: Cam): Double { + return sourceColor.chroma * multiple + } +} + internal class ChromaConstant(val chroma: Double) : Chroma { override fun get(sourceColor: Cam): Double { return chroma } } +internal class ChromaSource : Chroma { + override fun get(sourceColor: Cam): Double { + return sourceColor.chroma.toDouble() + } +} + internal class TonalSpec(val hue: Hue = HueSource(), val chroma: Chroma) { fun shades(sourceColor: Cam): List { val hue = hue.get(sourceColor) @@ -208,6 +219,13 @@ enum class Style(internal val coreSpec: CoreSpec) { n1 = TonalSpec(HueSource(), ChromaConstant(10.0)), n2 = TonalSpec(HueSource(), ChromaConstant(16.0)) )), + 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)) + )), } class ColorScheme( @@ -231,7 +249,7 @@ class ColorScheme( darkTheme: Boolean, style: Style = Style.TONAL_SPOT ): - this(getSeedColor(wallpaperColors), darkTheme, style) + this(getSeedColor(wallpaperColors, style != Style.CONTENT), darkTheme, style) val allAccentColors: List get() { @@ -260,7 +278,7 @@ class ColorScheme( val proposedSeedCam = Cam.fromInt(seed) val seedArgb = if (seed == Color.TRANSPARENT) { GOOGLE_BLUE - } else if (proposedSeedCam.chroma < 5) { + } else if (style != Style.CONTENT && proposedSeedCam.chroma < 5) { GOOGLE_BLUE } else { seed @@ -289,22 +307,26 @@ class ColorScheme( * Identifies a color to create a color scheme from. * * @param wallpaperColors Colors extracted from an image via quantization. + * @param filter If false, allow colors that have low chroma, creating grayscale themes. * @return ARGB int representing the color */ @JvmStatic + @JvmOverloads @ColorInt - fun getSeedColor(wallpaperColors: WallpaperColors): Int { - return getSeedColors(wallpaperColors).first() + fun getSeedColor(wallpaperColors: WallpaperColors, filter: Boolean = true): Int { + return getSeedColors(wallpaperColors, filter).first() } /** * Filters and ranks colors from WallpaperColors. * * @param wallpaperColors Colors extracted from an image via quantization. + * @param filter If false, allow colors that have low chroma, creating grayscale themes. * @return List of ARGB ints, ordered from highest scoring to lowest. */ @JvmStatic - fun getSeedColors(wallpaperColors: WallpaperColors): List { + @JvmOverloads + fun getSeedColors(wallpaperColors: WallpaperColors, filter: Boolean = true): List { val totalPopulation = wallpaperColors.allColors.values.reduce { a, b -> a + b } .toDouble() val totalPopulationMeaningless = (totalPopulation == 0.0) @@ -317,9 +339,12 @@ class ColorScheme( val distinctColors = wallpaperColors.mainColors.map { it.toArgb() }.distinct().filter { - Cam.fromInt(it).chroma >= MIN_CHROMA + if (!filter) { + true + } else { + Cam.fromInt(it).chroma >= MIN_CHROMA + } }.toList() - if (distinctColors.isEmpty()) { return listOf(GOOGLE_BLUE) } @@ -332,7 +357,7 @@ class ColorScheme( 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) + val hueProportions = huePopulations(intToCam, intToProportion, filter) // Map each color to the percentage of the image with its hue. val intToHueProportion = wallpaperColors.allColors.mapValues { val cam = intToCam[it.key]!! @@ -346,13 +371,12 @@ 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 = intToCam.filter { + val filteredIntToCam = if (!filter) intToCam else (intToCam.filter { val cam = it.value - val lstar = lstarFromInt(it.key) 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]!!) @@ -444,7 +468,8 @@ class ColorScheme( private fun huePopulations( camByColor: Map, - populationByColor: Map + populationByColor: Map, + filter: Boolean = true ): List { val huePopulation = List(size = 360, init = { 0.0 }).toMutableList() @@ -452,7 +477,7 @@ class ColorScheme( val population = populationByColor[entry.key]!! val cam = camByColor[entry.key]!! val hue = cam.hue.roundToInt() % 360 - if (cam.chroma <= MIN_CHROMA) { + if (filter && cam.chroma <= MIN_CHROMA) { continue } huePopulation[hue] = huePopulation[hue] + population diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java index d65940172b174..53961c3b13663 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java @@ -75,6 +75,7 @@ import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.media.dialog.MediaOutputDialogFactory; import com.android.systemui.monet.ColorScheme; +import com.android.systemui.monet.Style; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.shared.system.SysUiStatsLog; @@ -589,7 +590,7 @@ public class MediaControlPanel { if (artworkIcon != null) { WallpaperColors wallpaperColors = WallpaperColors .fromBitmap(artworkIcon.getBitmap()); - mutableColorScheme = new ColorScheme(wallpaperColors, true); + mutableColorScheme = new ColorScheme(wallpaperColors, true, Style.CONTENT); artwork = getScaledBackground(artworkIcon, width, height); isArtworkBound = true; } else { @@ -599,7 +600,8 @@ public class MediaControlPanel { try { Drawable icon = mContext.getPackageManager() .getApplicationIcon(data.getPackageName()); - mutableColorScheme = new ColorScheme(WallpaperColors.fromDrawable(icon), true); + mutableColorScheme = new ColorScheme(WallpaperColors.fromDrawable(icon), true, + Style.CONTENT); } catch (PackageManager.NameNotFoundException e) { Log.w(TAG, "Cannot find icon for package " + data.getPackageName(), e); } diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java index 7920d388c6702..c6a8445d2443e 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java @@ -78,6 +78,7 @@ import org.json.JSONException; import org.json.JSONObject; import java.io.PrintWriter; +import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.List; @@ -639,6 +640,11 @@ public class ThemeOverlayController extends CoreStartable implements Dumpable { } private Style fetchThemeStyleFromSetting() { + // Allow-list of Style objects that can be created from a setting string, i.e. can be + // used as a system-wide theme. + // - Content intentionally excluded, intended for media player, not system-wide + List