Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reproducer for: Coil does not apply contentScale to placeholder/error #275

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package app.playground

import androidx.compose.runtime.Composable
import androidx.compose.runtime.RememberObserver
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.DefaultAlpha
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.graphics.painter.ColorPainter
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.dp
import app.playground.BlurHashModel
import com.vanniktech.blurhash.BlurHash
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlin.math.roundToInt

@Composable
actual fun rememberBlurHashPainter(blurHash: BlurHashModel): Painter {
// todo: can i read the density from DrawScope instead?
val density = LocalDensity.current
val isInPreview = LocalInspectionMode.current
return remember(blurHash, density) {
AsyncBlurHashPainter(blurHash, density, isInPreview)
}
}

private class AsyncBlurHashPainter(
private val blurHash: BlurHashModel,
private val density: Density,
private val isInPreview: Boolean,
) : Painter(),
RememberObserver {

private var painter: Painter? by mutableStateOf(null)
private var scope: CoroutineScope? = null
private var alpha: Float = DefaultAlpha
private var colorFilter: ColorFilter? = null

override val intrinsicSize: Size
get() = painter?.intrinsicSize ?: Size.Unspecified

override fun onRemembered() {
if (isInPreview) {
painter = blurHash.createPainter(density)
} else {
scope = CoroutineScope(Dispatchers.Main.immediate).apply {
launch {
painter = withContext(Dispatchers.IO) {
blurHash.createPainter(density)
}
}
}
}
}

override fun onForgotten() {
scope?.cancel()
}

override fun onAbandoned() = Unit

override fun applyAlpha(alpha: Float): Boolean {
this.alpha = alpha
return true
}

override fun applyColorFilter(colorFilter: ColorFilter?): Boolean {
this.colorFilter = colorFilter
return true
}

override fun DrawScope.onDraw() {
painter?.let { painter ->
with(painter) {
draw(size, alpha, colorFilter)
}
}
}

private fun BlurHashModel.createPainter(density: Density): Painter {
check(width > 0 && height > 0)

// Smaller bitmaps are significantly cheaper to generate with little reduction in blur quality.
val maxWidthPx = density.run { 80.dp.roundToPx() }
val aspectRatio = width.toFloat() / height
val constrainedWidth = (width / 4).coerceAtMost(maxWidthPx)

val bitmap = BlurHash.decode(
blurHash = hash,
width = constrainedWidth,
height = (constrainedWidth / aspectRatio).roundToInt(),
)

return when (bitmap) {
null -> ColorPainter(Color.Transparent)
else -> BitmapPainter(bitmap.asImageBitmap())
}
}
}
13 changes: 13 additions & 0 deletions composeApp/src/commonMain/kotlin/app/playground/BlurHashPainter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package app.playground

import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.painter.Painter

data class BlurHashModel(
val hash: String,
val width: Int,
val height: Int,
)

@Composable
expect fun rememberBlurHashPainter(blurHash: BlurHashModel): Painter
38 changes: 38 additions & 0 deletions composeApp/src/commonMain/kotlin/app/playground/MainScreen.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,50 @@
package app.playground

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.RememberObserver
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.DefaultAlpha
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.Density
import androidx.navigation.NavHostController
import coil3.compose.AsyncImage
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlin.math.roundToInt

@Composable internal fun MainScreen(
navHostController: NavHostController,
) = Column {
Text("Hello World")

val placeholder = rememberBlurHashPainter(
BlurHashModel(
hash = "LEHLk~WB2yk8pyo0adR*.7kCMdnj",
width = 100,
height = 100,
),
)

AsyncImage(
model = "https://www.google.com/url?sa=i&url=https%3A%2F%2Fcommons.wikimedia.org%2Fwiki%2FFile%3ATest.svg&psig=AOvVaw0YE5gD2JedL-Twca2x2_us&ust=1732595398709000&source=images&cd=vfe&opi=89978449&ved=0CBEQjRxqFwoTCKiigv7S9okDFQAAAAAdAAAAABAE",
contentScale = ContentScale.Fit,
contentDescription = null,
placeholder = placeholder,
error = placeholder,
modifier = Modifier.fillMaxWidth(fraction = 1f).aspectRatio(1f),
)
}
Loading
Loading