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
This commit is contained in:
@@ -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<Int> {
|
||||
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<Int>
|
||||
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<Int> {
|
||||
@JvmOverloads
|
||||
fun getSeedColors(wallpaperColors: WallpaperColors, filter: Boolean = true): List<Int> {
|
||||
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<Int, Cam>,
|
||||
populationByColor: Map<Int, Double>
|
||||
populationByColor: Map<Int, Double>,
|
||||
filter: Boolean = true
|
||||
): List<Double> {
|
||||
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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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<Style> validStyles = Arrays.asList(Style.EXPRESSIVE, Style.SPRITZ, Style.TONAL_SPOT,
|
||||
Style.FRUIT_SALAD, Style.RAINBOW, Style.VIBRANT);
|
||||
Style style = mThemeStyle;
|
||||
final String overlayPackageJson = mSecureSettings.getStringForUser(
|
||||
Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
|
||||
@@ -648,6 +654,9 @@ public class ThemeOverlayController extends CoreStartable implements Dumpable {
|
||||
JSONObject object = new JSONObject(overlayPackageJson);
|
||||
style = Style.valueOf(
|
||||
object.getString(ThemeOverlayApplier.OVERLAY_CATEGORY_THEME_STYLE));
|
||||
if (!validStyles.contains(style)) {
|
||||
style = Style.TONAL_SPOT;
|
||||
}
|
||||
} catch (JSONException | IllegalArgumentException e) {
|
||||
Log.i(TAG, "Failed to parse THEME_CUSTOMIZATION_OVERLAY_PACKAGES.", e);
|
||||
style = Style.TONAL_SPOT;
|
||||
|
||||
@@ -74,6 +74,8 @@ import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
@@ -345,7 +347,9 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
|
||||
@Test
|
||||
public void onSettingChanged_honorThemeStyle() {
|
||||
when(mDeviceProvisionedController.isUserSetup(anyInt())).thenReturn(true);
|
||||
for (Style style : Style.values()) {
|
||||
List<Style> validStyles = Arrays.asList(Style.EXPRESSIVE, Style.SPRITZ, Style.TONAL_SPOT,
|
||||
Style.FRUIT_SALAD, Style.RAINBOW, Style.VIBRANT);
|
||||
for (Style style : validStyles) {
|
||||
reset(mSecureSettings);
|
||||
|
||||
String jsonString = "{\"android.theme.customization.system_palette\":\"A16B00\","
|
||||
|
||||
Reference in New Issue
Block a user