Image loading optimizations

- Fix potential memory cache miss
- Reduce `SizeResolver` allocations
- Make data source type more accurate
This commit is contained in:
FooIbar committed 2025-05-19 21:36:27 +08:00
1 parent dc4ea5e96d
commit bcedbd85ef
5 files changed
+17 -30

No files matched your search

@@ -6,18 +6,19 @@ import androidx.paging.compose.LazyPagingItems
import coil3.decode.BlackholeDecoder
import coil3.request.CachePolicy
import coil3.request.ImageRequest
import coil3.size.Size
import coil3.size.SizeResolver
import com.hippo.ehviewer.client.data.GalleryInfo
import com.hippo.ehviewer.client.data.GalleryPreview
import com.hippo.ehviewer.client.data.V2GalleryPreview
import com.hippo.ehviewer.client.thumbUrl
import com.hippo.ehviewer.dao.DownloadInfo
import com.hippo.ehviewer.download.DownloadManager
import com.hippo.ehviewer.ktbuilder.execute
// Load in original size so the memory cache can be reused for preload requests
fun ImageRequest.Builder.ehUrl(info: GalleryInfo) = apply {
val key = info.thumbKey!!
data(info.thumbUrl)
size(SizeResolver.ORIGINAL)
val downloadInfo = (info as? DownloadInfo) ?: DownloadManager.getDownloadInfo(info.gid)
if (downloadInfo != null) {
downloadInfo(downloadInfo)
@@ -26,10 +27,11 @@ fun ImageRequest.Builder.ehUrl(info: GalleryInfo) = apply {
diskCacheKey(key)
}
// Load in original size so the memory cache can be reused for preload requests
fun ImageRequest.Builder.ehPreview(preview: GalleryPreview) = apply {
with(preview) {
data(url)
if (preview is V2GalleryPreview) size(Size.ORIGINAL)
size(SizeResolver.ORIGINAL)
memoryCacheKey(imageKey)
diskCacheKey(imageKey)
}
@@ -55,7 +55,9 @@ object MergeInterceptor : Interceptor {
val cached = req.context.imageLoader.memoryCache!![cacheKey] != null
val result = TightRopeTracker.use(key) { sendAndAwait { chain.proceed() } }
when (result) {
is SuccessResult if !cached -> result.copy(dataSource = DataSource.MEMORY)
is SuccessResult if !cached && result.dataSource == DataSource.MEMORY_CACHE -> {
result.copy(dataSource = DataSource.MEMORY)
}
else -> result
}
} else {
@@ -34,6 +34,8 @@ import coil3.request.SuccessResult
import coil3.request.allowHardware
import coil3.size.Dimension
import coil3.size.Precision
import coil3.size.Size
import coil3.size.SizeResolver
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.coil.BitmapImageWithExtraInfo
import com.hippo.ehviewer.coil.detectQrCode
@@ -85,12 +87,13 @@ class Image private constructor(image: CoilImage, private val src: ImageSource)
companion object {
private val targetWidth = appCtx.resources.displayMetrics.widthPixels * 3
private val sizeResolver = SizeResolver(Size(targetWidth, Dimension.Undefined))
private suspend fun Either<ByteBufferSource, PathSource>.decodeCoil(checkExtraneousAds: Boolean): CoilImage {
val request = appCtx.imageRequest {
onLeft { data(it.source) }
onRight { data(it.source.toUri()) }
size(Dimension(targetWidth), Dimension.Undefined)
size(sizeResolver)
precision(Precision.INEXACT)
allowHardware(false)
hardwareThreshold(Settings.hardwareBitmapThreshold)
@@ -17,9 +17,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import coil3.compose.AsyncImagePainter
import coil3.compose.ConstraintsSizeResolver
import coil3.compose.rememberAsyncImagePainter
import coil3.compose.rememberConstraintsSizeResolver
import coil3.request.ImageRequest
import com.hippo.ehviewer.client.data.GalleryInfo
import com.hippo.ehviewer.ktbuilder.imageRequest
@@ -33,25 +31,7 @@ import com.hippo.ehviewer.ui.tools.thenIf
@NonRestartableComposable
fun requestOf(model: GalleryInfo): ImageRequest {
val context = LocalContext.current
return remember(model) { context.imageRequest(model) }.withSizeResolver()
}
@Composable
@NonRestartableComposable
fun ImageRequest.withSizeResolver() = if (defined.sizeResolver != null) {
this
} else {
val sizeResolver = rememberConstraintsSizeResolver()
remember(this, sizeResolver) { newBuilder().size(sizeResolver).build() }
}
fun Modifier.imageRequest(request: ImageRequest): Modifier {
val sizeResolver = request.sizeResolver
return if (sizeResolver is ConstraintsSizeResolver) {
then(sizeResolver)
} else {
this
}
return remember(model) { context.imageRequest(model) }
}
context(SharedTransitionScope, TransitionsVisibilityScope, SETNodeGenerator)
@@ -73,7 +53,7 @@ fun EhAsyncCropThumb(
},
),
contentDescription = null,
modifier = modifier.imageRequest(request),
modifier = modifier,
contentScale = contentScale,
)
}
@@ -102,7 +82,7 @@ fun EhThumbCard(
modifier = Modifier.thenIf(state !is AsyncImagePainter.State.Success) {
// Keep applying this when state is `Loading` to avoid cutting off the ripple
clickable { if (state is AsyncImagePainter.State.Error) painter.restart() }
}.imageRequest(request).fillMaxSize(),
}.fillMaxSize(),
contentScale = contentScale,
)
}
@@ -34,7 +34,7 @@ import com.hippo.ehviewer.ui.tools.shouldCrop
@NonRestartableComposable
fun requestOf(model: GalleryPreview): ImageRequest {
val context = LocalContext.current
return remember(model) { context.imageRequest(model) }.withSizeResolver()
return remember(model) { context.imageRequest(model) }
}
@Composable
@@ -88,7 +88,7 @@ fun EhPreviewCard(
Image(
painter = painter,
contentDescription = null,
modifier = Modifier.imageRequest(request).fillMaxSize(),
modifier = Modifier.fillMaxSize(),
contentScale = contentScale,
)
}