Merge changes from topic "colorSchemeChanges" into tm-dev

* changes:
  Add 2 chroma to vibrant theme's specification
  Fix color scheme rotation
  Add content color scheme for media
This commit is contained in:
James O'Leary
2022-05-12 23:04:10 +00:00
committed by Android (Google) Code Review
5 changed files with 61 additions and 21 deletions

View File

@@ -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
@@ -50,7 +49,7 @@ internal interface Hue {
val previousHue = hueAndRotations[previousIndex].first
if (ColorScheme.angleIsBetween(sourceHue, thisHue, previousHue)) {
return ColorScheme.wrapDegreesDouble(sourceHue.toDouble() +
hueAndRotations[previousIndex].first)
hueAndRotations[previousIndex].second)
}
}
@@ -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)
@@ -184,8 +195,8 @@ enum class Style(internal val coreSpec: CoreSpec) {
a1 = TonalSpec(HueSource(), ChromaMinimum(48.0)),
a2 = TonalSpec(HueVibrantSecondary(), ChromaConstant(24.0)),
a3 = TonalSpec(HueVibrantTertiary(), ChromaConstant(32.0)),
n1 = TonalSpec(HueSource(), ChromaConstant(10.0)),
n2 = TonalSpec(HueSource(), ChromaConstant(12.0))
n1 = TonalSpec(HueSource(), ChromaConstant(12.0)),
n2 = TonalSpec(HueSource(), ChromaConstant(14.0))
)),
EXPRESSIVE(CoreSpec(
a1 = TonalSpec(HueAdd(240.0), ChromaConstant(40.0)),
@@ -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

View File

@@ -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;
@@ -627,7 +628,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 {
@@ -637,7 +638,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);
}

View File

@@ -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;

View File

@@ -133,7 +133,7 @@ public class ColorSchemeTest extends SysuiTestCase {
Style.VIBRANT /* style */);
int neutralMid = colorScheme.getNeutral1().get(colorScheme.getNeutral1().size() / 2);
Cam cam = Cam.fromInt(neutralMid);
Assert.assertTrue("chroma was " + cam.getChroma(), Math.floor(cam.getChroma()) <= 10.0);
Assert.assertTrue("chroma was " + cam.getChroma(), Math.floor(cam.getChroma()) <= 12.0);
}
@Test

View File

@@ -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\","