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

Commit 1d17199e authored by Hawkwood Glazier's avatar Hawkwood Glazier Committed by Android (Google) Code Review
Browse files

Merge "Additional Color Palettes" into udc-dev

parents 0757bded 57405bd0
Loading
Loading
Loading
Loading
+266 −128
Original line number Diff line number Diff line
@@ -23,6 +23,8 @@ import com.android.internal.graphics.ColorUtils
import com.android.internal.graphics.cam.Cam
import com.android.internal.graphics.cam.CamUtils
import kotlin.math.absoluteValue
import kotlin.math.max
import kotlin.math.min
import kotlin.math.roundToInt

const val TAG = "ColorScheme"
@@ -35,12 +37,12 @@ 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.
     * 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
     * @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 {
        val sanitizedSourceHue = (if (sourceHue < 0 || sourceHue >= 360) 0 else sourceHue).toFloat()
@@ -48,8 +50,9 @@ internal interface Hue {
            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)
                return ColorScheme.wrapDegreesDouble(
                    sanitizedSourceHue.toDouble() + hueAndRotations[i].second
                )
            }
        }

@@ -78,8 +81,18 @@ 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(360, 12))
    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(360, 12)
        )

    override fun get(sourceColor: Cam): Double {
        return getHueRotation(sourceColor.hue, hueToRotations)
@@ -87,8 +100,18 @@ 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(360, 25))
    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(360, 25)
        )

    override fun get(sourceColor: Cam): Double {
        return getHueRotation(sourceColor.hue, hueToRotations)
@@ -96,8 +119,18 @@ internal class HueVibrantTertiary() : Hue {
}

internal class HueExpressiveSecondary() : Hue {
    val hueToRotations = listOf(Pair(0, 45), Pair(21, 95), Pair(51, 45), Pair(121, 20),
            Pair(151, 45), Pair(191, 90), Pair(271, 45), Pair(321, 45), Pair(360, 45))
    val hueToRotations =
        listOf(
            Pair(0, 45),
            Pair(21, 95),
            Pair(51, 45),
            Pair(121, 20),
            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)
@@ -105,8 +138,18 @@ internal class HueExpressiveSecondary() : Hue {
}

internal class HueExpressiveTertiary() : Hue {
    val hueToRotations = listOf(Pair(0, 120), Pair(21, 120), Pair(51, 20), Pair(121, 45),
            Pair(151, 20), Pair(191, 15), Pair(271, 20), Pair(321, 120), Pair(360, 120))
    val hueToRotations =
        listOf(
            Pair(0, 120),
            Pair(21, 120),
            Pair(51, 20),
            Pair(121, 45),
            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)
@@ -115,13 +158,18 @@ internal class HueExpressiveTertiary() : Hue {

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

    companion object {
        val MAX_VALUE = 120.0
        val MIN_VALUE = 0.0
    }
}

internal class ChromaMaxOut : Chroma {
    override fun get(sourceColor: Cam): Double {
        // 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
        return Chroma.MAX_VALUE + 10.0
    }
}

@@ -131,6 +179,23 @@ internal class ChromaMultiple(val multiple: Double) : Chroma {
    }
}

internal class ChromaAdd(val amount: Double) : Chroma {
    override fun get(sourceColor: Cam): Double {
        return sourceColor.chroma + amount
    }
}

internal class ChromaBound(
    val baseChroma: Chroma,
    val minVal: Double,
    val maxVal: Double,
) : Chroma {
    override fun get(sourceColor: Cam): Double {
        val result = baseChroma.get(sourceColor)
        return min(max(result, minVal), maxVal)
    }
}

internal class ChromaConstant(val chroma: Double) : Chroma {
    override fun get(sourceColor: Cam): Double {
        return chroma
@@ -149,6 +214,12 @@ internal class TonalSpec(val hue: Hue = HueSource(), val chroma: Chroma) {
        val chroma = chroma.get(sourceColor)
        return Shades.of(hue.toFloat(), chroma.toFloat()).toList()
    }

    fun getAtTone(sourceColor: Cam, tone: Float): Int {
        val hue = hue.get(sourceColor)
        val chroma = chroma.get(sourceColor)
        return ColorUtils.CAMToColor(hue.toFloat(), chroma.toFloat(), (1000f - tone) / 10f)
    }
}

internal class CoreSpec(
@@ -160,92 +231,150 @@ internal class CoreSpec(
)

enum class Style(internal val coreSpec: CoreSpec) {
    SPRITZ(CoreSpec(
    SPRITZ(
        CoreSpec(
            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(
        )
    ),
    TONAL_SPOT(
        CoreSpec(
            a1 = TonalSpec(HueSource(), ChromaConstant(36.0)),
            a2 = TonalSpec(HueSource(), ChromaConstant(16.0)),
            a3 = TonalSpec(HueAdd(60.0), ChromaConstant(24.0)),
            n1 = TonalSpec(HueSource(), ChromaConstant(6.0)),
            n2 = TonalSpec(HueSource(), ChromaConstant(8.0))
    )),
    VIBRANT(CoreSpec(
        )
    ),
    VIBRANT(
        CoreSpec(
            a1 = TonalSpec(HueSource(), ChromaMaxOut()),
            a2 = TonalSpec(HueVibrantSecondary(), ChromaConstant(24.0)),
            a3 = TonalSpec(HueVibrantTertiary(), ChromaConstant(32.0)),
            n1 = TonalSpec(HueSource(), ChromaConstant(10.0)),
            n2 = TonalSpec(HueSource(), ChromaConstant(12.0))
    )),
    EXPRESSIVE(CoreSpec(
        )
    ),
    EXPRESSIVE(
        CoreSpec(
            a1 = TonalSpec(HueAdd(240.0), ChromaConstant(40.0)),
            a2 = TonalSpec(HueExpressiveSecondary(), ChromaConstant(24.0)),
            a3 = TonalSpec(HueExpressiveTertiary(), ChromaConstant(32.0)),
            n1 = TonalSpec(HueAdd(15.0), ChromaConstant(8.0)),
            n2 = TonalSpec(HueAdd(15.0), ChromaConstant(12.0))
    )),
    RAINBOW(CoreSpec(
        )
    ),
    RAINBOW(
        CoreSpec(
            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(
        )
    ),
    FRUIT_SALAD(
        CoreSpec(
            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))
    )),
    CONTENT(CoreSpec(
        )
    ),
    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))
    )),
    MONOCHROMATIC(CoreSpec(
        )
    ),
    MONOCHROMATIC(
        CoreSpec(
            a1 = TonalSpec(HueSource(), ChromaConstant(.0)),
            a2 = TonalSpec(HueSource(), ChromaConstant(.0)),
            a3 = TonalSpec(HueSource(), ChromaConstant(.0)),
            n1 = TonalSpec(HueSource(), ChromaConstant(.0)),
            n2 = TonalSpec(HueSource(), ChromaConstant(.0))
    )),
        )
    ),
    CLOCK(
        CoreSpec(
            a1 = TonalSpec(HueSource(), ChromaBound(ChromaSource(), 20.0, Chroma.MAX_VALUE)),
            a2 = TonalSpec(HueAdd(10.0), ChromaBound(ChromaMultiple(0.85), 17.0, 40.0)),
            a3 = TonalSpec(HueAdd(20.0), ChromaBound(ChromaAdd(20.0), 50.0, Chroma.MAX_VALUE)),

            // Not Used
            n1 = TonalSpec(HueSource(), ChromaConstant(0.0)),
            n2 = TonalSpec(HueSource(), ChromaConstant(0.0))
        )
    ),
    CLOCK_VIBRANT(
        CoreSpec(
            a1 = TonalSpec(HueSource(), ChromaBound(ChromaSource(), 30.0, Chroma.MAX_VALUE)),
            a2 = TonalSpec(HueAdd(20.0), ChromaBound(ChromaSource(), 30.0, Chroma.MAX_VALUE)),
            a3 = TonalSpec(HueAdd(60.0), ChromaBound(ChromaSource(), 30.0, Chroma.MAX_VALUE)),

            // Not Used
            n1 = TonalSpec(HueSource(), ChromaConstant(0.0)),
            n2 = TonalSpec(HueSource(), ChromaConstant(0.0))
        )
    )
}

class TonalPalette {
    val shadeKeys = listOf(10, 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000)
    val allShades: List<Int>
    val allShadesMapped: Map<Int, Int>
class TonalPalette
internal constructor(
    private val spec: TonalSpec,
    seedColor: Int,
) {
    val seedCam: Cam = Cam.fromInt(seedColor)
    val allShades: List<Int> = spec.shades(seedCam)
    val allShadesMapped: Map<Int, Int> = SHADE_KEYS.zip(allShades).toMap()
    val baseColor: Int

    internal constructor(spec: TonalSpec, seedColor: Int) {
        val seedCam = Cam.fromInt(seedColor)
        allShades = spec.shades(seedCam)
        allShadesMapped = shadeKeys.zip(allShades).toMap()

    init {
        val h = spec.hue.get(seedCam).toFloat()
        val c = spec.chroma.get(seedCam).toFloat()
        baseColor = ColorUtils.CAMToColor(h, c, CamUtils.lstarFromInt(seedColor))
    }

    val s10: Int get() = this.allShades[0]
    val s50: Int get() = this.allShades[1]
    val s100: Int get() = this.allShades[2]
    val s200: Int get() = this.allShades[3]
    val s300: Int get() = this.allShades[4]
    val s400: Int get() = this.allShades[5]
    val s500: Int get() = this.allShades[6]
    val s600: Int get() = this.allShades[7]
    val s700: Int get() = this.allShades[8]
    val s800: Int get() = this.allShades[9]
    val s900: Int get() = this.allShades[10]
    val s1000: Int get() = this.allShades[11]
    // Dynamically computed tones across the full range from 0 to 1000
    fun getAtTone(tone: Float) = spec.getAtTone(seedCam, tone)

    // Predefined & precomputed tones
    val s10: Int
        get() = this.allShades[0]
    val s50: Int
        get() = this.allShades[1]
    val s100: Int
        get() = this.allShades[2]
    val s200: Int
        get() = this.allShades[3]
    val s300: Int
        get() = this.allShades[4]
    val s400: Int
        get() = this.allShades[5]
    val s500: Int
        get() = this.allShades[6]
    val s600: Int
        get() = this.allShades[7]
    val s700: Int
        get() = this.allShades[8]
    val s800: Int
        get() = this.allShades[9]
    val s900: Int
        get() = this.allShades[10]
    val s1000: Int
        get() = this.allShades[11]

    companion object {
        val SHADE_KEYS = listOf(10, 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000)
    }
}

class ColorScheme(
@@ -260,16 +389,14 @@ class ColorScheme(
    val neutral1: TonalPalette
    val neutral2: TonalPalette

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

    @JvmOverloads
    constructor(
        wallpaperColors: WallpaperColors,
        darkTheme: Boolean,
        style: Style = Style.TONAL_SPOT
    ) :
            this(getSeedColor(wallpaperColors, style != Style.CONTENT), darkTheme, style)
    ) : this(getSeedColor(wallpaperColors, style != Style.CONTENT), darkTheme, style)

    val allHues: List<TonalPalette>
        get() {
@@ -301,7 +428,8 @@ class ColorScheme(

    init {
        val proposedSeedCam = Cam.fromInt(seed)
        val seedArgb = if (seed == Color.TRANSPARENT) {
        val seedArgb =
            if (seed == Color.TRANSPARENT) {
                GOOGLE_BLUE
            } else if (style != Style.CONTENT && proposedSeedCam.chroma < 5) {
                GOOGLE_BLUE
@@ -316,7 +444,11 @@ class ColorScheme(
        neutral2 = TonalPalette(style.coreSpec.n2, seedArgb)
    }

    val shadeCount get() = this.accent1.allShades.size
    val shadeCount
        get() = this.accent1.allShades.size

    val seedTone: Float
        get() = 1000f - CamUtils.lstarFromInt(seed) * 10f

    override fun toString(): String {
        return "ColorScheme {\n" +
@@ -356,8 +488,8 @@ class ColorScheme(
        @JvmStatic
        @JvmOverloads
        fun getSeedColors(wallpaperColors: WallpaperColors, filter: Boolean = true): List<Int> {
            val totalPopulation = wallpaperColors.allColors.values.reduce { a, b -> a + b }
                    .toDouble()
            val totalPopulation =
                wallpaperColors.allColors.values.reduce { a, b -> a + b }.toDouble()
            val totalPopulationMeaningless = (totalPopulation == 0.0)
            if (totalPopulationMeaningless) {
                // WallpaperColors with a population of 0 indicate the colors didn't come from
@@ -365,30 +497,33 @@ class ColorScheme(
                // secondary/tertiary colors.
                //
                // In this case, the colors are usually from a Live Wallpaper.
                val distinctColors = wallpaperColors.mainColors.map {
                    it.toArgb()
                }.distinct().filter {
                val distinctColors =
                    wallpaperColors.mainColors
                        .map { it.toArgb() }
                        .distinct()
                        .filter {
                            if (!filter) {
                                true
                            } else {
                                Cam.fromInt(it).chroma >= MIN_CHROMA
                            }
                }.toList()
                        }
                        .toList()
                if (distinctColors.isEmpty()) {
                    return listOf(GOOGLE_BLUE)
                }
                return distinctColors
            }

            val intToProportion = wallpaperColors.allColors.mapValues {
                it.value.toDouble() / totalPopulation
            }
            val intToProportion =
                wallpaperColors.allColors.mapValues { it.value.toDouble() / totalPopulation }
            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, filter)
            // Map each color to the percentage of the image with its hue.
            val intToHueProportion = wallpaperColors.allColors.mapValues {
            val intToHueProportion =
                wallpaperColors.allColors.mapValues {
                    val cam = intToCam[it.key]!!
                    val hue = cam.hue.roundToInt()
                    var proportion = 0.0
@@ -400,16 +535,18 @@ 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 = if (!filter) intToCam else (intToCam.filter {
            val filteredIntToCam =
                if (!filter) intToCam
                else
                    (intToCam.filter {
                        val cam = it.value
                        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]!!)
            }
            val intToScoreIntermediate =
                filteredIntToCam.mapValues { score(it.value, intToHueProportion[it.key]!!) }
            val intToScore = intToScoreIntermediate.entries.toMutableList()
            intToScore.sortByDescending { it.value }

@@ -423,7 +560,8 @@ class ColorScheme(
                seeds.clear()
                for (entry in intToScore) {
                    val int = entry.key
                    val existingSeedNearby = seeds.find {
                    val existingSeedNearby =
                        seeds.find {
                            val hueA = intToCam[int]!!.hue
                            val hueB = intToCam[it]!!.hue
                            hueDiff(hueA, hueB) < i
@@ -489,14 +627,14 @@ class ColorScheme(
        }

        private fun humanReadable(paletteName: String, colors: List<Int>): String {
            return "$paletteName\n" + colors.map {
                stringForColor(it)
            }.joinToString(separator = "\n") { it }
            return "$paletteName\n" +
                colors.map { stringForColor(it) }.joinToString(separator = "\n") { it }
        }

        private fun score(cam: Cam, proportion: Double): Double {
            val proportionScore = 0.7 * 100.0 * proportion
            val chromaScore = if (cam.chroma < ACCENT1_CHROMA) 0.1 * (cam.chroma - ACCENT1_CHROMA)
            val chromaScore =
                if (cam.chroma < ACCENT1_CHROMA) 0.1 * (cam.chroma - ACCENT1_CHROMA)
                else 0.3 * (cam.chroma - ACCENT1_CHROMA)
            return chromaScore + proportionScore
        }
+9 −2
Original line number Diff line number Diff line
@@ -109,6 +109,9 @@ interface ClockEvents {
    /** Call whenever the locale changes */
    fun onLocaleChanged(locale: Locale) {}

    val isReactiveToTone
        get() = true

    /** Call whenever the color palette should update */
    fun onColorPaletteChanged(resources: Resources) {}

@@ -164,8 +167,12 @@ interface ClockFaceEvents {
    val hasCustomWeatherDataDisplay: Boolean
        get() = false

    /** Region Darkness specific to the clock face */
    fun onRegionDarknessChanged(isDark: Boolean) {}
    /**
     * Region Darkness specific to the clock face.
     * - isRegionDark = dark theme -> clock should be light
     * - !isRegionDark = light theme -> clock should be dark
     */
    fun onRegionDarknessChanged(isRegionDark: Boolean) {}

    /**
     * Call whenever font settings change. Pass in a target font size in pixels. The specific clock