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

Commit c159bde4 authored by Lucas Dupin's avatar Lucas Dupin Committed by Android (Google) Code Review
Browse files

Merge "ColorScheme provides options for styling"

parents 9a686c07 20532d7c
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);
    }
}