AVIF images downsampling

(cherry picked from commit 5d890cb3d0)
This commit is contained in:
Koitharu
2025-05-02 14:38:12 +03:00
parent 4fb1db47ab
commit 9cf496b7c4

View File

@@ -2,17 +2,21 @@ package org.koitharu.kotatsu.core.image
import android.graphics.Bitmap import android.graphics.Bitmap
import androidx.core.graphics.createBitmap import androidx.core.graphics.createBitmap
import androidx.core.graphics.scale
import coil3.ImageLoader import coil3.ImageLoader
import coil3.asImage import coil3.asImage
import coil3.decode.DecodeResult import coil3.decode.DecodeResult
import coil3.decode.DecodeUtils
import coil3.decode.Decoder import coil3.decode.Decoder
import coil3.decode.ImageSource import coil3.decode.ImageSource
import coil3.fetch.SourceFetchResult import coil3.fetch.SourceFetchResult
import coil3.request.Options import coil3.request.Options
import coil3.request.maxBitmapSize
import coil3.util.component1
import coil3.util.component2
import com.davemorrissey.labs.subscaleview.decoder.ImageDecodeException import com.davemorrissey.labs.subscaleview.decoder.ImageDecodeException
import kotlinx.coroutines.runInterruptible import kotlinx.coroutines.runInterruptible
import org.aomedia.avif.android.AvifDecoder import org.aomedia.avif.android.AvifDecoder
import org.aomedia.avif.android.AvifDecoder.Info
import org.koitharu.kotatsu.core.util.ext.readByteBuffer import org.koitharu.kotatsu.core.util.ext.readByteBuffer
class AvifImageDecoder( class AvifImageDecoder(
@@ -22,24 +26,51 @@ class AvifImageDecoder(
override suspend fun decode(): DecodeResult = runInterruptible { override suspend fun decode(): DecodeResult = runInterruptible {
val bytes = source.source().readByteBuffer() val bytes = source.source().readByteBuffer()
val info = Info() val decoder = AvifDecoder.create(bytes) ?: throw ImageDecodeException(
if (!AvifDecoder.getInfo(bytes, bytes.remaining(), info)) { uri = source.fileOrNull()?.toString(),
throw ImageDecodeException( format = "avif",
null, message = "Requested to decode byte buffer which cannot be handled by AvifDecoder",
"avif",
"Requested to decode byte buffer which cannot be handled by AvifDecoder",
)
}
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,
) )
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()
}
} }
class Factory : Decoder.Factory { class Factory : Decoder.Factory {