From 9cf496b7c41e0a25f498feda626421f4166c293f Mon Sep 17 00:00:00 2001 From: Koitharu Date: Fri, 2 May 2025 14:38:12 +0300 Subject: [PATCH] AVIF images downsampling (cherry picked from commit 5d890cb3d0ee16a77d02102e8dd87a89ff7944e3) --- .../kotatsu/core/image/AvifImageDecoder.kt | 65 ++++++++++++++----- 1 file changed, 48 insertions(+), 17 deletions(-) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/image/AvifImageDecoder.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/image/AvifImageDecoder.kt index 4cb017e3d..29c7ee701 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/image/AvifImageDecoder.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/image/AvifImageDecoder.kt @@ -2,17 +2,21 @@ package org.koitharu.kotatsu.core.image import android.graphics.Bitmap import androidx.core.graphics.createBitmap +import androidx.core.graphics.scale import coil3.ImageLoader import coil3.asImage import coil3.decode.DecodeResult +import coil3.decode.DecodeUtils import coil3.decode.Decoder import coil3.decode.ImageSource import coil3.fetch.SourceFetchResult import coil3.request.Options +import coil3.request.maxBitmapSize +import coil3.util.component1 +import coil3.util.component2 import com.davemorrissey.labs.subscaleview.decoder.ImageDecodeException import kotlinx.coroutines.runInterruptible import org.aomedia.avif.android.AvifDecoder -import org.aomedia.avif.android.AvifDecoder.Info import org.koitharu.kotatsu.core.util.ext.readByteBuffer class AvifImageDecoder( @@ -22,24 +26,51 @@ class AvifImageDecoder( override suspend fun decode(): DecodeResult = runInterruptible { val bytes = source.source().readByteBuffer() - val info = Info() - if (!AvifDecoder.getInfo(bytes, bytes.remaining(), info)) { - throw ImageDecodeException( - null, - "avif", - "Requested to decode byte buffer which cannot be handled by AvifDecoder", + val decoder = AvifDecoder.create(bytes) ?: throw ImageDecodeException( + uri = source.fileOrNull()?.toString(), + format = "avif", + message = "Requested to decode byte buffer which cannot be handled by AvifDecoder", + ) + try { + val config = if (decoder.depth == 8 || decoder.alphaPresent) { + Bitmap.Config.ARGB_8888 + } else { + Bitmap.Config.RGB_565 + } + val bitmap = createBitmap(decoder.width, decoder.height, config) + val result = decoder.nextFrame(bitmap) + if (result != 0) { + bitmap.recycle() + throw ImageDecodeException( + uri = source.fileOrNull()?.toString(), + format = "avif", + message = AvifDecoder.resultToString(result), + ) + } + // downscaling + val (dstWidth, dstHeight) = DecodeUtils.computeDstSize( + srcWidth = bitmap.width, + srcHeight = bitmap.height, + targetSize = options.size, + scale = options.scale, + maxSize = options.maxBitmapSize, ) + if (dstWidth < bitmap.width || dstHeight < bitmap.height) { + val scaled = bitmap.scale(dstWidth, dstHeight) + bitmap.recycle() + DecodeResult( + image = scaled.asImage(), + isSampled = true, + ) + } else { + DecodeResult( + image = bitmap.asImage(), + isSampled = false, + ) + } + } finally { + decoder.release() } - val config = if (info.depth == 8 || info.alphaPresent) Bitmap.Config.ARGB_8888 else Bitmap.Config.RGB_565 - val bitmap = createBitmap(info.width, info.height, config) - if (!AvifDecoder.decode(bytes, bytes.remaining(), bitmap)) { - bitmap.recycle() - throw ImageDecodeException(null, "avif") - } - DecodeResult( - image = bitmap.asImage(), - isSampled = false, - ) } class Factory : Decoder.Factory {