Match latest variant spec; update hue rotation logic
- Update a couple rotations to match spec (ex. rotation occurs at 151 degrees instead of 141) - If hue rotation specs have an entry at 0 degrees, they also need an entry at 360 degrees. There's no way to determine that, for example, 321 is between 315 and 0, 360 must be listed. - Find hue rotation in a way that ensures ex. an angle isn't tested for being between 0 and 360 degrees, as that will always return true. Instead, use first element, (always 0 degrees), check if angle is between and the next element, and stop once the next element is 360 degrees (always the last element in the array) - Remove code that became unused after the above change. Test: atest ColorSchemeTest (unit test); atest SystemPaletteTest (CTS). Spent ~4 hours trying a variety of wallpapers, and seed colors, and used dumpsys to verify results match UX spec. Create spreadsheet for UX to fill out their spec, then another spreadsheet that contains the dumpsys results for blue/red/yellow/green seed colors across all theme variants, and the preset colors for Ice Cream and Fruit Salad variants. Confirmed the runtime results, as reported by dumpsys, match the spec in all cases. Bug: 213314628 Change-Id: Id35faa294bf82c0e81c2690c65c42218a87c236a
This commit is contained in:
@@ -22,7 +22,6 @@ import android.graphics.Color
|
||||
import com.android.internal.graphics.ColorUtils
|
||||
import com.android.internal.graphics.cam.Cam
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlin.math.max
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
const val TAG = "ColorScheme"
|
||||
@@ -43,13 +42,13 @@ internal interface Hue {
|
||||
* second item in the pair is a hue rotation that should be applied
|
||||
*/
|
||||
fun getHueRotation(sourceHue: Float, hueAndRotations: List<Pair<Int, Int>>): 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].second)
|
||||
val sanitizedSourceHue = (if (sourceHue < 0 || sourceHue >= 360) 0 else sourceHue).toFloat()
|
||||
for (i in 0..hueAndRotations.size - 2) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +78,7 @@ 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(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)
|
||||
}
|
||||
@@ -87,7 +86,7 @@ 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(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)
|
||||
}
|
||||
@@ -95,7 +94,7 @@ internal class HueVibrantTertiary() : Hue {
|
||||
|
||||
internal class HueExpressiveSecondary() : Hue {
|
||||
val hueToRotations = listOf(Pair(0, 45), Pair(21, 95), Pair(51, 45), Pair(121, 20),
|
||||
Pair(141, 45), Pair(191, 90), Pair(271, 45), Pair(321, 45))
|
||||
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)
|
||||
}
|
||||
@@ -103,7 +102,7 @@ internal class HueExpressiveSecondary() : Hue {
|
||||
|
||||
internal class HueExpressiveTertiary() : Hue {
|
||||
val hueToRotations = listOf(Pair(0, 120), Pair(21, 120), Pair(51, 20), Pair(121, 45),
|
||||
Pair(141, 20), Pair(191, 15), Pair(271, 20), Pair(321, 120))
|
||||
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)
|
||||
}
|
||||
@@ -111,34 +110,13 @@ internal class HueExpressiveTertiary() : Hue {
|
||||
|
||||
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<Pair<Int, Int>>): 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 ChromaMinimum(val chroma: Double) : Chroma {
|
||||
internal class ChromaMaxOut : Chroma {
|
||||
override fun get(sourceColor: Cam): Double {
|
||||
return max(sourceColor.chroma.toDouble(), chroma)
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,11 +170,11 @@ enum class Style(internal val coreSpec: CoreSpec) {
|
||||
n2 = TonalSpec(HueSource(), ChromaConstant(8.0))
|
||||
)),
|
||||
VIBRANT(CoreSpec(
|
||||
a1 = TonalSpec(HueSource(), ChromaMinimum(48.0)),
|
||||
a1 = TonalSpec(HueSource(), ChromaMaxOut()),
|
||||
a2 = TonalSpec(HueVibrantSecondary(), ChromaConstant(24.0)),
|
||||
a3 = TonalSpec(HueVibrantTertiary(), ChromaConstant(32.0)),
|
||||
n1 = TonalSpec(HueSource(), ChromaConstant(12.0)),
|
||||
n2 = TonalSpec(HueSource(), ChromaConstant(14.0))
|
||||
n1 = TonalSpec(HueSource(), ChromaConstant(8.0)),
|
||||
n2 = TonalSpec(HueSource(), ChromaConstant(12.0))
|
||||
)),
|
||||
EXPRESSIVE(CoreSpec(
|
||||
a1 = TonalSpec(HueAdd(240.0), ChromaConstant(40.0)),
|
||||
@@ -416,13 +394,6 @@ 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 -> {
|
||||
|
||||
@@ -69,7 +69,7 @@ public class ColorSchemeTest extends SysuiTestCase {
|
||||
// Expressive applies hue rotations to the theme color. The input theme color has hue
|
||||
// 117, ensuring the hue changed significantly is a strong signal styles are being applied.
|
||||
ColorScheme colorScheme = new ColorScheme(wallpaperColors, false, Style.EXPRESSIVE);
|
||||
Assert.assertEquals(Cam.fromInt(colorScheme.getAccent1().get(6)).getHue(), 357.46, 0.1);
|
||||
Assert.assertEquals(357.77, Cam.fromInt(colorScheme.getAccent1().get(6)).getHue(), 0.1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user