derive the secondary text colour from the gradient luminance

This commit is contained in:
austinried
2026-10-04 00:29:53 +00:00
parent 4c8f3e1740
commit f725b25004
4 changed files with 44 additions and 22 deletions
@@ -310,7 +310,7 @@ private fun AlbumHeader(
Text( Text(
text = subtitle, text = subtitle,
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onBackground, color = MaterialTheme.colorScheme.onSurfaceVariant,
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
@@ -132,7 +132,7 @@ fun SongRow(
supportingContent = { supportingContent = {
Text( Text(
text = song.artist.orEmpty().ifEmpty { "\u00A0" }, text = song.artist.orEmpty().ifEmpty { "\u00A0" },
color = if (isPlaying) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onBackground, color = if (isPlaying) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
) )
@@ -26,6 +26,7 @@ private const val BLOB_ZONE = 0.62f
private const val BLOB_COUNT = 4 private const val BLOB_COUNT = 4
private const val ACCENT_MAX_LUMINANCE = 0.18f private const val ACCENT_MAX_LUMINANCE = 0.18f
private const val PRIMARY_MIN_LUMINANCE = 0.30f private const val PRIMARY_MIN_LUMINANCE = 0.30f
private const val SECONDARY_CONTRAST = 3.0f
private const val MIN_GRADIENT_SATURATION = 0.30f private const val MIN_GRADIENT_SATURATION = 0.30f
private const val HERO_DARKEN_MAX = 1.0f private const val HERO_DARKEN_MAX = 1.0f
private const val DARKEN_START_SCREENS = 0.5f private const val DARKEN_START_SCREENS = 0.5f
@@ -61,6 +62,31 @@ fun artworkColorsFromSeeds(
val background = tone(hue, (s * 0.35f).coerceAtMost(0.20f), 0.06f) val background = tone(hue, (s * 0.35f).coerceAtMost(0.20f), 0.06f)
val onBackground = tone(hue, (s * 0.10f).coerceAtMost(0.08f), 0.95f) val onBackground = tone(hue, (s * 0.10f).coerceAtMost(0.08f), 0.95f)
val secondary =
secondarySeed?.let { seed ->
val (sh, ss, sl) = Color(seed or 0xFF000000.toInt()).toHsl()
Color.hsl(
blendHue(sh, hue, 0.5f),
ss.coerceAtLeast(s * 0.7f).coerceAtMost(0.9f),
sl.coerceIn(0.34f, 0.56f),
)
} ?: tone(hue, s, 0.40f)
val gradientSat = if (s > 0.05f) s.coerceAtLeast(MIN_GRADIENT_SATURATION) else s
val gradientHigh = tone(hue, (gradientSat * 0.85f).coerceAtMost(0.70f), 0.26f)
val gradientLow = tone(hue, gradientSat * 0.55f, 0.12f)
val accents =
listOf(
tone(hue, gradientSat, 0.72f).withMaxLuminance(ACCENT_MAX_LUMINANCE),
secondary.withMaxLuminance(ACCENT_MAX_LUMINANCE),
)
val darkPrimary = tone(hue, gradientSat * 0.75f, 0.10f)
val backdrop = maxOf(gradientHigh.luminance(), gradientLow.luminance(), accents.maxOf { it.luminance() })
val onSurfaceVariant =
tone(hue, s * 0.18f, 0.78f)
.withMinLuminance(SECONDARY_CONTRAST * (backdrop + 0.05f) - 0.05f)
val scheme = val scheme =
darkColorScheme( darkColorScheme(
primary = primary, primary = primary,
@@ -81,7 +107,7 @@ fun artworkColorsFromSeeds(
onSurface = onBackground, onSurface = onBackground,
surfaceTint = primary, surfaceTint = primary,
surfaceVariant = tone(hue, s * 0.25f, 0.17f), surfaceVariant = tone(hue, s * 0.25f, 0.17f),
onSurfaceVariant = tone(hue, s * 0.18f, 0.78f), onSurfaceVariant = onSurfaceVariant,
surfaceContainerLowest = tone(hue, s * 0.25f, 0.04f), surfaceContainerLowest = tone(hue, s * 0.25f, 0.04f),
surfaceContainerLow = tone(hue, s * 0.25f, 0.08f), surfaceContainerLow = tone(hue, s * 0.25f, 0.08f),
surfaceContainer = tone(hue, s * 0.25f, 0.10f), surfaceContainer = tone(hue, s * 0.25f, 0.10f),
@@ -91,27 +117,12 @@ fun artworkColorsFromSeeds(
outlineVariant = tone(hue, s * 0.18f, 0.28f), outlineVariant = tone(hue, s * 0.18f, 0.28f),
) )
val secondary =
secondarySeed?.let { seed ->
val (sh, ss, sl) = Color(seed or 0xFF000000.toInt()).toHsl()
Color.hsl(
blendHue(sh, hue, 0.5f),
ss.coerceAtLeast(s * 0.7f).coerceAtMost(0.9f),
sl.coerceIn(0.34f, 0.56f),
)
} ?: tone(hue, s, 0.40f)
val gradientSat = if (s > 0.05f) s.coerceAtLeast(MIN_GRADIENT_SATURATION) else s
return ArtworkColors( return ArtworkColors(
scheme = scheme, scheme = scheme,
gradientHigh = tone(hue, (gradientSat * 0.85f).coerceAtMost(0.70f), 0.26f), gradientHigh = gradientHigh,
gradientLow = tone(hue, gradientSat * 0.55f, 0.12f), gradientLow = gradientLow,
accents = accents = accents,
listOf( darkPrimary = darkPrimary,
tone(hue, gradientSat, 0.72f).withMaxLuminance(ACCENT_MAX_LUMINANCE),
secondary.withMaxLuminance(ACCENT_MAX_LUMINANCE),
),
darkPrimary = tone(hue, gradientSat * 0.75f, 0.10f),
blobSeed = primarySeed, blobSeed = primarySeed,
) )
} }
@@ -48,6 +48,17 @@ class ArtworkColorsTest {
} }
} }
@Test
fun secondaryTextReadsOnGradient() {
for (seed in seeds) {
val colors = artworkColorsFromSeed(seed)
val backdrop =
(listOf(colors.gradientHigh, colors.gradientLow) + colors.accents)
.maxBy { it.luminance() }
assertTrue("onSurfaceVariant for $seed", contrast(colors.scheme.onSurfaceVariant, backdrop) >= 3f)
}
}
private fun contrast( private fun contrast(
a: Color, a: Color,
b: Color, b: Color,