From bcedbd85efa2edd4fb83fbd7e8862bf6e13d6fbe Mon Sep 17 00:00:00 2001 From: FooIbar <118464521+FooIbar@users.noreply.github.com> Date: Mon, 19 May 2025 21:01:58 +0800 Subject: [PATCH] Image loading optimizations - Fix potential memory cache miss - Reduce `SizeResolver` allocations - Make data source type more accurate --- .../com/hippo/ehviewer/coil/Extensions.kt | 8 +++--- .../hippo/ehviewer/coil/MergeInterceptor.kt | 4 ++- .../kotlin/com/hippo/ehviewer/image/Image.kt | 5 +++- .../hippo/ehviewer/ui/main/EhAsyncThumb.kt | 26 +++---------------- .../hippo/ehviewer/ui/main/GalleryPreview.kt | 4 +-- 5 files changed, 17 insertions(+), 30 deletions(-) diff --git a/app/src/main/kotlin/com/hippo/ehviewer/coil/Extensions.kt b/app/src/main/kotlin/com/hippo/ehviewer/coil/Extensions.kt index 390c3e490..9b039aed6 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/coil/Extensions.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/coil/Extensions.kt @@ -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) } diff --git a/app/src/main/kotlin/com/hippo/ehviewer/coil/MergeInterceptor.kt b/app/src/main/kotlin/com/hippo/ehviewer/coil/MergeInterceptor.kt index 6c3635a56..6abc2009a 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/coil/MergeInterceptor.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/coil/MergeInterceptor.kt @@ -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 { diff --git a/app/src/main/kotlin/com/hippo/ehviewer/image/Image.kt b/app/src/main/kotlin/com/hippo/ehviewer/image/Image.kt index aed56e42a..8de7fb820 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/image/Image.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/image/Image.kt @@ -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.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) diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/main/EhAsyncThumb.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/main/EhAsyncThumb.kt index 2b86b47cf..48ec74c33 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/main/EhAsyncThumb.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/main/EhAsyncThumb.kt @@ -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, ) } diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/main/GalleryPreview.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/main/GalleryPreview.kt index 9dad63a16..e0185baed 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/main/GalleryPreview.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/main/GalleryPreview.kt @@ -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, ) }