extract album colours from the thumbnail off the main thread

This commit is contained in:
austinried
2026-10-04 00:29:52 +00:00
parent fe943af2b6
commit 3d59379907
3 changed files with 87 additions and 11 deletions
@@ -74,7 +74,7 @@ fun AlbumDetailRoute(
album = album,
songs = songs,
coverArt = viewModel::coverArt,
artwork = rememberArtworkColors(viewModel.coverArt(album?.coverArt, false)),
artwork = rememberArtworkColors(viewModel.coverArt(album?.coverArt, true)),
onBack = onBack,
onSongClick = viewModel::play,
playingSongId = playback.item?.id.takeIf { context?.kind == QueueKind.Album && context.refId == albumId },
@@ -19,11 +19,15 @@ import coil3.SingletonImageLoader
import coil3.compose.LocalPlatformContext
import coil3.request.ImageRequest
import coil3.request.SuccessResult
import coil3.request.allowHardware
import coil3.toBitmap
import com.subtracks.data.model.CoverArtRef
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
private const val SEED_ART_SIZE_PX = 128
data class ArtworkColors(
val scheme: ColorScheme,
val gradientHigh: Color,
@@ -97,16 +101,24 @@ private fun rememberArtworkSeed(ref: CoverArtRef?): State<Int?> {
value = null
val art = ref ?: return@produceState
value =
withContext(Dispatchers.Default) {
val request =
ImageRequest
.Builder(context)
.data(art.url)
.memoryCacheKey(art.cacheKey)
.diskCacheKey(art.cacheKey)
.build()
val image = (SingletonImageLoader.get(context).execute(request) as? SuccessResult)?.image
image?.let { seedFrom(it.toBitmap()) }
withContext(Dispatchers.IO) {
try {
val request =
ImageRequest
.Builder(context)
.data(art.url)
.memoryCacheKey(art.cacheKey)
.diskCacheKey(art.cacheKey)
.size(SEED_ART_SIZE_PX)
.allowHardware(false)
.build()
val image = (SingletonImageLoader.get(context).execute(request) as? SuccessResult)?.image
image?.let { seedFrom(it.toBitmap()) }
} catch (e: CancellationException) {
throw e
} catch (_: Exception) {
null
}
}
}
}
@@ -0,0 +1,64 @@
package com.subtracks.ui.theme
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Color
import androidx.activity.ComponentActivity
import androidx.compose.ui.graphics.luminance
import androidx.compose.ui.test.junit4.v2.createAndroidComposeRule
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import coil3.ImageLoader
import coil3.SingletonImageLoader
import coil3.annotation.DelicateCoilApi
import coil3.asImage
import coil3.test.FakeImageLoaderEngine
import com.subtracks.data.model.CoverArtRef
import org.junit.After
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.annotation.GraphicsMode
@OptIn(DelicateCoilApi::class)
@RunWith(AndroidJUnit4::class)
@GraphicsMode(GraphicsMode.Mode.NATIVE)
class ArtworkExtractionTest {
@get:Rule
val composeRule = createAndroidComposeRule<ComponentActivity>()
private val bitmap =
Bitmap.createBitmap(64, 64, Bitmap.Config.ARGB_8888).apply {
eraseColor(Color.rgb(30, 120, 200))
}
@Before
fun installImageLoader() {
SingletonImageLoader.reset()
SingletonImageLoader.setUnsafe(
ImageLoader
.Builder(ApplicationProvider.getApplicationContext<Context>())
.components { add(FakeImageLoaderEngine.Builder().intercept("art", bitmap.asImage()).build()) }
.build(),
)
}
@After
fun resetImageLoader() {
SingletonImageLoader.reset()
}
@Test
fun resolvesColorsFromCoverArt() {
var colors: ArtworkColors? = null
composeRule.setContent {
colors = rememberArtworkColors(CoverArtRef(url = "art", cacheKey = "art"))
}
composeRule.waitUntil(timeoutMillis = 5_000) { colors != null }
assertNotNull(colors)
assertTrue(colors!!.scheme.background.luminance() < 0.1f)
}
}