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

Commit d6dbd50a authored by James O'Leary's avatar James O'Leary
Browse files

Update styles to match latest specs

A) Refactor hue/chroma specification to support specification of
hue rotation and chroma depending on source color hue. Update vibrant &
expressive specs to use granular specification of hue rotations for
secondary and tertiary palettes for both, and expressive's neutral uses
specification of chroma for the neutral palette.
B) Use chroma equal to seed color for all palettes, replacing some
usage of >=
C) Remove chroma 40 cap at tone 90

Test: atest ColorSchemeTest
Bug: N/A
Change-Id: Ib9fff483443ccae888faf636d4388b9658ff3dfe
parent 7fe1c138
Loading
Loading
Loading
Loading
+153 −49
Original line number Diff line number Diff line
@@ -31,37 +31,134 @@ const val ACCENT1_CHROMA = 48.0f
const val GOOGLE_BLUE = 0xFF1b6ef3.toInt()
const val MIN_CHROMA = 5

internal enum class ChromaStrategy {
    EQ, GTE
internal interface Hue {
    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 hueAndRotations list of pairs, where the first item in a pair is a hue, and the
     *    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].first)
            }
        }

        // If this statement executes, something is wrong, there should have been a rotation
        // found using the arrays.
        return sourceHue.toDouble()
    }
}

internal class HueSource : Hue {
    override fun get(sourceColor: Cam): Double {
        return sourceColor.hue.toDouble()
    }
}

internal class HueAdd(val amountDegrees: Double) : Hue {
    override fun get(sourceColor: Cam): Double {
        return ColorScheme.wrapDegreesDouble(sourceColor.hue.toDouble() + amountDegrees)
    }
}

internal class HueSubtract(val amountDegrees: Double) : Hue {
    override fun get(sourceColor: Cam): Double {
        return ColorScheme.wrapDegreesDouble(sourceColor.hue.toDouble() - amountDegrees)
    }
}

internal class HueVibrantSecondary() : Hue {
    val hueToRotations = listOf(Pair(24, 15), Pair(53, 15), Pair(91, 15), Pair(123, 15),
            Pair(141, 15), Pair(172, 15), Pair(198, 15), Pair(234, 18), Pair(272, 18),
            Pair(302, 18), Pair(329, 30), Pair(354, 15))
    override fun get(sourceColor: Cam): Double {
        return getHueRotation(sourceColor.hue, hueToRotations)
    }
}

internal enum class HueStrategy {
    SOURCE, ADD, SUBTRACT
internal class HueVibrantTertiary() : Hue {
    val hueToRotations = listOf(Pair(24, 30), Pair(53, 30), Pair(91, 15), Pair(123, 30),
            Pair(141, 27), Pair(172, 27), Pair(198, 30), Pair(234, 35), Pair(272, 30),
            Pair(302, 30), Pair(329, 60), Pair(354, 30))
    override fun get(sourceColor: Cam): Double {
        return getHueRotation(sourceColor.hue, hueToRotations)
    }
}

internal class Chroma(val strategy: ChromaStrategy, val value: Double) {
    fun get(sourceChroma: Double): Double {
        return when (strategy) {
            ChromaStrategy.EQ -> value
            ChromaStrategy.GTE -> sourceChroma.coerceAtLeast(value)
internal class HueExpressiveSecondary() : Hue {
    val hueToRotations = listOf(Pair(24, 95), Pair(53, 45), Pair(91, 45), Pair(123, 20),
            Pair(141, 45), Pair(172, 45), Pair(198, 15), Pair(234, 15),
            Pair(272, 45), Pair(302, 45), Pair(329, 45), Pair(354, 45))
    override fun get(sourceColor: Cam): Double {
        return getHueRotation(sourceColor.hue, hueToRotations)
    }
}

internal class HueExpressiveTertiary() : Hue {
    val hueToRotations = listOf(Pair(24, 20), Pair(53, 20), Pair(91, 20), Pair(123, 45),
            Pair(141, 20), Pair(172, 20), Pair(198, 90), Pair(234, 90), Pair(272, 20),
            Pair(302, 20), Pair(329, 120), Pair(354, 120))
    override fun get(sourceColor: Cam): Double {
        return getHueRotation(sourceColor.hue, hueToRotations)
    }
}

internal interface Chroma {
    fun get(sourceColor: Cam): Double

internal 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)
    /**
     * 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 TonalSpec(val hue: Hue = Hue(), val chroma: Chroma) {
internal class ChromaConstant(val chroma: Double) : Chroma {
    override fun get(sourceColor: Cam): Double {
        return chroma
    }
}

internal class ChromaExpressiveNeutral() : Chroma {
    val hueToChromas = listOf(Pair(24, 8), Pair(53, 8), Pair(91, 8), Pair(123, 8),
            Pair(141, 6), Pair(172, 6), Pair(198, 8), Pair(234, 8), Pair(272, 8),
            Pair(302, 8), Pair(329, 8), Pair(354, 8))
    override fun get(sourceColor: Cam): Double {
        return getSpecifiedChroma(sourceColor.hue, hueToChromas)
    }
}

internal class TonalSpec(val hue: Hue = HueSource(), val chroma: Chroma) {
    fun shades(sourceColor: Cam): List<Int> {
        val hue = hue.get(sourceColor.hue.toDouble())
        val chroma = chroma.get(sourceColor.chroma.toDouble())
        val hue = hue.get(sourceColor)
        val chroma = chroma.get(sourceColor)
        return Shades.of(hue.toFloat(), chroma.toFloat()).toList()
    }
}
@@ -76,46 +173,46 @@ internal class CoreSpec(

enum class Style(internal val coreSpec: CoreSpec) {
    SPRITZ(CoreSpec(
            a1 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 12.0)),
            a2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 8.0)),
            a3 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 16.0)),
            n1 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 4.0)),
            n2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 8.0))
            a1 = TonalSpec(HueSource(), ChromaConstant(12.0)),
            a2 = TonalSpec(HueSource(), ChromaConstant(8.0)),
            a3 = TonalSpec(HueSource(), ChromaConstant(16.0)),
            n1 = TonalSpec(HueSource(), ChromaConstant(2.0)),
            n2 = TonalSpec(HueSource(), ChromaConstant(2.0))
    )),
    TONAL_SPOT(CoreSpec(
            a1 = TonalSpec(chroma = Chroma(ChromaStrategy.GTE, 32.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))
            a1 = TonalSpec(HueSource(), ChromaConstant(36.0)),
            a2 = TonalSpec(HueSource(), ChromaConstant(16.0)),
            a3 = TonalSpec(HueAdd(60.0), ChromaConstant(24.0)),
            n1 = TonalSpec(HueSource(), ChromaConstant(4.0)),
            n2 = TonalSpec(HueSource(), ChromaConstant(8.0))
    )),
    VIBRANT(CoreSpec(
            a1 = TonalSpec(chroma = Chroma(ChromaStrategy.GTE, 48.0)),
            a2 = TonalSpec(Hue(HueStrategy.ADD, 15.0), Chroma(ChromaStrategy.EQ, 24.0)),
            a3 = TonalSpec(Hue(HueStrategy.ADD, 30.0), Chroma(ChromaStrategy.GTE, 32.0)),
            n1 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 8.0)),
            n2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 16.0))
            a1 = TonalSpec(HueSource(), ChromaConstant(48.0)),
            a2 = TonalSpec(HueVibrantSecondary(), ChromaConstant(24.0)),
            a3 = TonalSpec(HueVibrantTertiary(), ChromaConstant(32.0)),
            n1 = TonalSpec(HueSource(), ChromaConstant(6.0)),
            n2 = TonalSpec(HueSource(), ChromaConstant(12.0))
    )),
    EXPRESSIVE(CoreSpec(
            a1 = TonalSpec(Hue(HueStrategy.SUBTRACT, 60.0), Chroma(ChromaStrategy.GTE, 64.0)),
            a2 = TonalSpec(Hue(HueStrategy.SUBTRACT, 30.0), Chroma(ChromaStrategy.EQ, 24.0)),
            a3 = TonalSpec(chroma = Chroma(ChromaStrategy.GTE, 48.0)),
            n1 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 12.0)),
            n2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 16.0))
            a1 = TonalSpec(HueAdd(240.0), ChromaConstant(40.0)),
            a2 = TonalSpec(HueExpressiveSecondary(), ChromaConstant(24.0)),
            a3 = TonalSpec(HueExpressiveTertiary(), ChromaConstant(40.0)),
            n1 = TonalSpec(HueAdd(15.0), ChromaExpressiveNeutral()),
            n2 = TonalSpec(HueAdd(15.0), ChromaConstant(12.0))
    )),
    RAINBOW(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, 0.0)),
            n2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 0.0))
            a1 = TonalSpec(HueSource(), ChromaConstant(48.0)),
            a2 = TonalSpec(HueSource(), ChromaConstant(16.0)),
            a3 = TonalSpec(HueAdd(60.0), ChromaConstant(24.0)),
            n1 = TonalSpec(HueSource(), ChromaConstant(0.0)),
            n2 = TonalSpec(HueSource(), ChromaConstant(0.0))
    )),
    FRUIT_SALAD(CoreSpec(
            a1 = TonalSpec(Hue(HueStrategy.SUBTRACT, 50.0), Chroma(ChromaStrategy.GTE, 48.0)),
            a2 = TonalSpec(Hue(HueStrategy.SUBTRACT, 50.0), Chroma(ChromaStrategy.EQ, 36.0)),
            a3 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 36.0)),
            n1 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 10.0)),
            n2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 16.0))
            a1 = TonalSpec(HueSubtract(50.0), ChromaConstant(48.0)),
            a2 = TonalSpec(HueSubtract(50.0), ChromaConstant(36.0)),
            a3 = TonalSpec(HueSource(), ChromaConstant(36.0)),
            n1 = TonalSpec(HueSource(), ChromaConstant(10.0)),
            n2 = TonalSpec(HueSource(), ChromaConstant(16.0))
    )),
}

@@ -296,6 +393,13 @@ 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 -> {
+0 −3
Original line number Diff line number Diff line
@@ -59,9 +59,6 @@ public class Shades {
        shades[1] = ColorUtils.CAMToColor(hue, Math.min(40f, chroma), 95);
        for (int i = 2; i < 12; i++) {
            float lStar = (i == 6) ? MIDDLE_LSTAR : 100 - 10 * (i - 1);
            if (lStar >= 90) {
                chroma = Math.min(40f, chroma);
            }
            shades[i] = ColorUtils.CAMToColor(hue, chroma, lStar);
        }
        return shades;
+2 −2
Original line number Diff line number Diff line
@@ -123,7 +123,7 @@ public class ColorSchemeTest extends SysuiTestCase {
                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);
        Assert.assertTrue(cam.getChroma() <= 8.0);
    }

    @Test
@@ -133,7 +133,7 @@ public class ColorSchemeTest extends SysuiTestCase {
                Style.EXPRESSIVE /* style */);
        int neutralMid = colorScheme.getNeutral1().get(colorScheme.getNeutral1().size() / 2);
        Cam cam = Cam.fromInt(neutralMid);
        Assert.assertEquals(cam.getChroma(), 12.0, 1.0);
        Assert.assertTrue(cam.getChroma() <= 8.0);
    }

    /**