Fix avif image decoding
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package org.koitharu.kotatsu.core.image
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import androidx.core.graphics.createBitmap
|
||||
import coil3.ImageLoader
|
||||
import coil3.asImage
|
||||
import coil3.decode.DecodeResult
|
||||
@@ -32,7 +33,7 @@ class AvifImageDecoder(
|
||||
)
|
||||
}
|
||||
val config = if (info.depth == 8 || info.alphaPresent) Bitmap.Config.ARGB_8888 else Bitmap.Config.RGB_565
|
||||
val bitmap = Bitmap.createBitmap(info.width, info.height, config)
|
||||
val bitmap = createBitmap(info.width, info.height, config)
|
||||
if (!AvifDecoder.decode(bytes, bytes.remaining(), bitmap)) {
|
||||
bitmap.recycle()
|
||||
throw ImageDecodeException(null, "avif")
|
||||
|
||||
@@ -2,15 +2,19 @@ package org.koitharu.kotatsu.core.image
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.BitmapRegionDecoder
|
||||
import android.graphics.ImageDecoder
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.graphics.createBitmap
|
||||
import com.davemorrissey.labs.subscaleview.decoder.ImageDecodeException
|
||||
import okio.IOException
|
||||
import org.aomedia.avif.android.AvifDecoder
|
||||
import org.aomedia.avif.android.AvifDecoder.Info
|
||||
import org.jetbrains.annotations.Blocking
|
||||
import org.koitharu.kotatsu.core.util.MimeTypes
|
||||
import org.koitharu.kotatsu.core.util.ext.MimeType
|
||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.core.util.ext.toByteBuffer
|
||||
import org.koitharu.kotatsu.core.util.ext.toMimeTypeOrNull
|
||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||
@@ -51,6 +55,19 @@ object BitmapDecoderCompat {
|
||||
}
|
||||
}
|
||||
|
||||
@Blocking
|
||||
fun createRegionDecoder(inoutStream: InputStream): BitmapRegionDecoder? = try {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
BitmapRegionDecoder.newInstance(inoutStream)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
BitmapRegionDecoder.newInstance(inoutStream, false)
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
e.printStackTraceDebug()
|
||||
null
|
||||
}
|
||||
|
||||
@Blocking
|
||||
fun probeMimeType(file: File): MimeType? {
|
||||
return MimeTypes.probeMimeType(file) ?: detectBitmapType(file)
|
||||
@@ -62,7 +79,7 @@ object BitmapDecoderCompat {
|
||||
inJustDecodeBounds = true
|
||||
}
|
||||
BitmapFactory.decodeFile(file.path, options)?.recycle()
|
||||
return options.outMimeType?.toMimeTypeOrNull()
|
||||
options.outMimeType?.toMimeTypeOrNull()
|
||||
}.getOrNull()
|
||||
|
||||
private fun checkBitmapNotNull(bitmap: Bitmap?, format: String?): Bitmap =
|
||||
@@ -78,7 +95,7 @@ object BitmapDecoderCompat {
|
||||
)
|
||||
}
|
||||
val config = if (info.depth == 8 || info.alphaPresent) Bitmap.Config.ARGB_8888 else Bitmap.Config.RGB_565
|
||||
val bitmap = Bitmap.createBitmap(info.width, info.height, config)
|
||||
val bitmap = createBitmap(info.width, info.height, config)
|
||||
if (!AvifDecoder.decode(bytes, bytes.remaining(), bitmap)) {
|
||||
bitmap.recycle()
|
||||
throw ImageDecodeException(null, FORMAT_AVIF)
|
||||
|
||||
@@ -2,7 +2,6 @@ package org.koitharu.kotatsu.core.image
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.BitmapRegionDecoder
|
||||
import android.graphics.Rect
|
||||
import android.os.Build
|
||||
import coil3.Extras
|
||||
@@ -11,7 +10,6 @@ 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.getExtra
|
||||
import coil3.request.Options
|
||||
@@ -25,24 +23,31 @@ import coil3.size.Scale
|
||||
import coil3.size.Size
|
||||
import coil3.size.isOriginal
|
||||
import coil3.size.pxOrElse
|
||||
import kotlinx.coroutines.runInterruptible
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class RegionBitmapDecoder(
|
||||
private val source: ImageSource,
|
||||
private val fetchResult: SourceFetchResult,
|
||||
private val options: Options,
|
||||
private val imageLoader: ImageLoader,
|
||||
) : Decoder {
|
||||
|
||||
override suspend fun decode(): DecodeResult = runInterruptible {
|
||||
val regionDecoder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
BitmapRegionDecoder.newInstance(source.source().inputStream())
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
BitmapRegionDecoder.newInstance(source.source().inputStream(), false)
|
||||
override suspend fun decode(): DecodeResult? {
|
||||
val regionDecoder = BitmapDecoderCompat.createRegionDecoder(fetchResult.source.source().inputStream())
|
||||
if (regionDecoder == null) {
|
||||
val fallbackDecoder = imageLoader.components.newDecoder(
|
||||
result = fetchResult,
|
||||
options = options,
|
||||
imageLoader = imageLoader,
|
||||
startIndex = 0,
|
||||
)?.first
|
||||
return if (fallbackDecoder == null || fallbackDecoder is RegionBitmapDecoder) {
|
||||
null
|
||||
} else {
|
||||
fallbackDecoder.decode()
|
||||
}
|
||||
}
|
||||
checkNotNull(regionDecoder)
|
||||
val bitmapOptions = BitmapFactory.Options()
|
||||
try {
|
||||
return try {
|
||||
val rect = bitmapOptions.configureScale(regionDecoder.width, regionDecoder.height)
|
||||
bitmapOptions.configureConfig()
|
||||
val bitmap = regionDecoder.decodeRegion(rect, bitmapOptions)
|
||||
@@ -149,7 +154,7 @@ class RegionBitmapDecoder(
|
||||
result: SourceFetchResult,
|
||||
options: Options,
|
||||
imageLoader: ImageLoader
|
||||
): Decoder = RegionBitmapDecoder(result.source, options)
|
||||
): Decoder = RegionBitmapDecoder(result, options, imageLoader)
|
||||
|
||||
override fun equals(other: Any?) = other is Factory
|
||||
|
||||
|
||||
@@ -3,7 +3,9 @@ package org.koitharu.kotatsu.reader.domain
|
||||
import android.content.Context
|
||||
import android.graphics.Rect
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import androidx.annotation.AnyThread
|
||||
import androidx.annotation.CheckResult
|
||||
import androidx.collection.LongSparseArray
|
||||
import androidx.collection.set
|
||||
import androidx.core.net.toFile
|
||||
@@ -30,7 +32,6 @@ import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.Semaphore
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.sync.withPermit
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okio.use
|
||||
@@ -184,9 +185,10 @@ class PageLoader @Inject constructor(
|
||||
return loadPageAsync(page, force).await()
|
||||
}
|
||||
|
||||
@CheckResult
|
||||
suspend fun convertBimap(uri: Uri): Uri = convertLock.withLock {
|
||||
if (uri.isZipUri()) {
|
||||
val bitmap = runInterruptible(Dispatchers.IO) {
|
||||
runInterruptible(Dispatchers.IO) {
|
||||
ZipFile(uri.schemeSpecificPart).use { zip ->
|
||||
val entry = zip.getEntry(uri.fragment)
|
||||
context.ensureRamAtLeast(entry.size * 2)
|
||||
@@ -194,8 +196,9 @@ class PageLoader @Inject constructor(
|
||||
BitmapDecoderCompat.decode(it, MimeTypes.getMimeTypeFromExtension(entry.name))
|
||||
}
|
||||
}
|
||||
}.use { image ->
|
||||
cache.put(uri.toString(), image).toUri()
|
||||
}
|
||||
cache.put(uri.toString(), bitmap).toUri()
|
||||
} else {
|
||||
val file = uri.toFile()
|
||||
runInterruptible(Dispatchers.IO) {
|
||||
|
||||
@@ -119,7 +119,7 @@ class PageViewModel(
|
||||
} else {
|
||||
null
|
||||
}
|
||||
state.value = PageState.Loaded(uri.toImageSource(cachedBounds), isConverted = true)
|
||||
state.value = PageState.Loaded(newUri.toImageSource(cachedBounds), isConverted = true)
|
||||
} catch (ce: CancellationException) {
|
||||
throw ce
|
||||
} catch (e2: Throwable) {
|
||||
|
||||
Reference in New Issue
Block a user