Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 20532d7c authored by James O'Leary's avatar James O'Leary Committed by Lucas Dupin
Browse files

ColorScheme provides options for styling

- Add 5(!) lightweight classes for storing a design spec for a style.
- Add parameter to ColorScheme constructor for Style
- Add constructor replicating previous constructor for backwards
compatibility with existing code, uses Tonal Spot as tyle
- Reconfigure ColorScheme to use style to generate palettes

Test: atest ColorSchemeTest passes. Change default style to
style #1, make sure it works as intended, and matches what we've seen in
mocks. Continue through all 4 styles.
Bug: 213314628
Bug: 195969565

Change-Id: Iae2b4fc4ffd630bb4275f827798ae4643b230ae1
parent c2dc611a
Loading
Loading
Loading
Loading
+100 −16
Original line number Diff line number Diff line
@@ -28,18 +28,88 @@ import kotlin.math.roundToInt
const val TAG = "ColorScheme"

const val ACCENT1_CHROMA = 48.0f
const val ACCENT2_CHROMA = 16.0f
const val ACCENT3_CHROMA = 32.0f
const val ACCENT3_HUE_SHIFT = 60.0f
const val GOOGLE_BLUE = 0xFF1b6ef3.toInt()
const val MIN_CHROMA = 5

const val NEUTRAL1_CHROMA = 4.0f
const val NEUTRAL2_CHROMA = 8.0f
enum class ChromaStrategy {
    EQ, GTE
}

const val GOOGLE_BLUE = 0xFF1b6ef3.toInt()
enum class HueStrategy {
    SOURCE, ADD, SUBTRACT
}

const val MIN_CHROMA = 5
class Chroma(val strategy: ChromaStrategy, val value: Double) {
    fun get(sourceChroma: Double): Double {
        return when (strategy) {
            ChromaStrategy.EQ -> value
            ChromaStrategy.GTE -> sourceChroma.coerceAtLeast(value)
        }
    }
}

public class ColorScheme(@ColorInt seed: Int, val darkTheme: Boolean) {
class Hue(val strategy: HueStrategy = HueStrategy.SOURCE, val value: Double = 0.0) {
    fun get(sourceHue: Double): Double {
        return when (strategy) {
            HueStrategy.SOURCE -> sourceHue
            HueStrategy.ADD -> ColorScheme.wrapDegreesDouble(sourceHue + value)
            HueStrategy.SUBTRACT -> ColorScheme.wrapDegreesDouble(sourceHue - value)
        }
    }
}

class TonalSpec(val hue: Hue = Hue(), val chroma: Chroma) {
    fun shades(sourceColor: Cam): List<Int> {
        val hue = hue.get(sourceColor.hue.toDouble())
        val chroma = chroma.get(sourceColor.chroma.toDouble())
        return Shades.of(hue.toFloat(), chroma.toFloat()).toList()
    }
}

class CoreSpec(
    val a1: TonalSpec,
    val a2: TonalSpec,
    val a3: TonalSpec,
    val n1: TonalSpec,
    val n2: TonalSpec
)

enum class Style(val coreSpec: CoreSpec) {
    SPRITZ(CoreSpec(
            a1 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 4.0)),
            a2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 4.0)),
            a3 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 4.0)),
            n1 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 4.0)),
            n2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 4.0))
    )),
    TONAL_SPOT(CoreSpec(
            a1 = TonalSpec(chroma = Chroma(ChromaStrategy.GTE, 48.0)),
            a2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 16.0)),
            a3 = TonalSpec(Hue(HueStrategy.ADD, 60.0), Chroma(ChromaStrategy.EQ, 24.0)),
            n1 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 4.0)),
            n2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 8.0))
    )),
    VIBRANT(CoreSpec(
            a1 = TonalSpec(chroma = Chroma(ChromaStrategy.GTE, 48.0)),
            a2 = TonalSpec(Hue(HueStrategy.ADD, 10.0), Chroma(ChromaStrategy.EQ, 24.0)),
            a3 = TonalSpec(Hue(HueStrategy.ADD, 20.0), Chroma(ChromaStrategy.GTE, 32.0)),
            n1 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 8.0)),
            n2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 16.0))
    )),
    EXPRESSIVE(CoreSpec(
            a1 = TonalSpec(Hue(HueStrategy.SUBTRACT, 40.0), Chroma(ChromaStrategy.GTE, 64.0)),
            a2 = TonalSpec(Hue(HueStrategy.ADD, 20.0), Chroma(ChromaStrategy.EQ, 24.0)),
            a3 = TonalSpec(Hue(HueStrategy.SUBTRACT, 80.0), Chroma(ChromaStrategy.GTE, 64.0)),
            n1 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 16.0)),
            n2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 32.0))
    )),
}

public class ColorScheme(
    @ColorInt seed: Int,
    val darkTheme: Boolean,
    val style: Style = Style.TONAL_SPOT
) {

    val accent1: List<Int>
    val accent2: List<Int>
@@ -47,6 +117,9 @@ public class ColorScheme(@ColorInt seed: Int, val darkTheme: Boolean) {
    val neutral1: List<Int>
    val neutral2: List<Int>

    constructor(@ColorInt seed: Int, darkTheme: Boolean):
            this(seed, darkTheme, Style.TONAL_SPOT)

    constructor(wallpaperColors: WallpaperColors, darkTheme: Boolean):
            this(getSeedColor(wallpaperColors), darkTheme)

@@ -83,14 +156,11 @@ public class ColorScheme(@ColorInt seed: Int, val darkTheme: Boolean) {
            seed
        }
        val camSeed = Cam.fromInt(seedArgb)
        val hue = camSeed.hue
        val chroma = camSeed.chroma.coerceAtLeast(ACCENT1_CHROMA)
        val tertiaryHue = wrapDegrees((hue + ACCENT3_HUE_SHIFT).toInt())
        accent1 = Shades.of(hue, chroma).toList()
        accent2 = Shades.of(hue, ACCENT2_CHROMA).toList()
        accent3 = Shades.of(tertiaryHue.toFloat(), ACCENT3_CHROMA).toList()
        neutral1 = Shades.of(hue, NEUTRAL1_CHROMA).toList()
        neutral2 = Shades.of(hue, NEUTRAL2_CHROMA).toList()
        accent1 = style.coreSpec.a1.shades(camSeed)
        accent2 = style.coreSpec.a2.shades(camSeed)
        accent3 = style.coreSpec.a3.shades(camSeed)
        neutral1 = style.coreSpec.n1.shades(camSeed)
        neutral2 = style.coreSpec.n2.shades(camSeed)
    }

    override fun toString(): String {
@@ -225,6 +295,20 @@ public class ColorScheme(@ColorInt seed: Int, val darkTheme: Boolean) {
            }
        }

        public fun wrapDegreesDouble(degrees: Double): Double {
            return when {
                degrees < 0 -> {
                    (degrees % 360) + 360
                }
                degrees >= 360 -> {
                    degrees % 360
                }
                else -> {
                    degrees
                }
            }
        }

        private fun hueDiff(a: Float, b: Float): Float {
            return 180f - ((a - b).absoluteValue - 180f).absoluteValue
        }
+30 −0
Original line number Diff line number Diff line
@@ -100,4 +100,34 @@ public class ColorSchemeTest extends SysuiTestCase {
        Cam cam = Cam.fromInt(tertiaryMid);
        Assert.assertEquals(cam.getHue(), 50.0, 10.0);
    }

    @Test
    public void testSpritz() {
        int colorInt = 0xffB3588A; // H350 C50 T50
        ColorScheme colorScheme = new ColorScheme(colorInt, false /* darkTheme */,
                Style.SPRITZ /* style */);
        int primaryMid = colorScheme.getAccent1().get(colorScheme.getAccent1().size() / 2);
        Cam cam = Cam.fromInt(primaryMid);
        Assert.assertEquals(cam.getChroma(), 4.0, 1.0);
    }

    @Test
    public void testVibrant() {
        int colorInt = 0xffB3588A; // H350 C50 T50
        ColorScheme colorScheme = new ColorScheme(colorInt, false /* darkTheme */,
                Style.VIBRANT /* style */);
        int neutralMid = colorScheme.getNeutral1().get(colorScheme.getNeutral1().size() / 2);
        Cam cam = Cam.fromInt(neutralMid);
        Assert.assertEquals(cam.getChroma(), 8.0, 1.0);
    }

    @Test
    public void testExpressive() {
        int colorInt = 0xffB3588A; // H350 C50 T50
        ColorScheme colorScheme = new ColorScheme(colorInt, false /* darkTheme */,
                Style.EXPRESSIVE /* style */);
        int neutralMid = colorScheme.getNeutral1().get(colorScheme.getNeutral1().size() / 2);
        Cam cam = Cam.fromInt(neutralMid);
        Assert.assertEquals(cam.getChroma(), 16.0, 1.0);
    }
}