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 65ea8edd4..4cb017e3d 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 @@ -13,7 +13,7 @@ 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.toByteBuffer +import org.koitharu.kotatsu.core.util.ext.readByteBuffer class AvifImageDecoder( private val source: ImageSource, @@ -21,9 +21,7 @@ class AvifImageDecoder( ) : Decoder { override suspend fun decode(): DecodeResult = runInterruptible { - val bytes = source.source().use { - it.inputStream().toByteBuffer() - } + val bytes = source.source().readByteBuffer() val info = Info() if (!AvifDecoder.getInfo(bytes, bytes.remaining(), info)) { throw ImageDecodeException( diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/image/BitmapDecoderCompat.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/image/BitmapDecoderCompat.kt index 4bedf2f48..b84d31a77 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/image/BitmapDecoderCompat.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/image/BitmapDecoderCompat.kt @@ -9,12 +9,15 @@ import androidx.annotation.RequiresApi import androidx.core.graphics.createBitmap import com.davemorrissey.labs.subscaleview.decoder.ImageDecodeException import okio.IOException +import okio.buffer +import okio.source 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.readByteBuffer import org.koitharu.kotatsu.core.util.ext.toByteBuffer import org.koitharu.kotatsu.core.util.ext.toMimeTypeOrNull import org.koitharu.kotatsu.parsers.util.runCatchingCancellable @@ -28,7 +31,7 @@ object BitmapDecoderCompat { @Blocking fun decode(file: File): Bitmap = when (val format = probeMimeType(file)?.subtype) { - FORMAT_AVIF -> file.inputStream().use { decodeAvif(it.toByteBuffer()) } + FORMAT_AVIF -> file.source().buffer().use { decodeAvif(it.readByteBuffer()) } else -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { ImageDecoder.decodeBitmap(ImageDecoder.createSource(file)) } else { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/image/CbzFetcher.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/image/CbzFetcher.kt index c2a2561fd..d4a59ae12 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/image/CbzFetcher.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/image/CbzFetcher.kt @@ -25,7 +25,7 @@ class CbzFetcher( val entryName = requireNotNull(uri.fragment) val fs = options.fileSystem.openZip(filePath) SourceFetchResult( - source = ImageSource(entryName.toPath(), fs, closeable = fs), + source = ImageSource(entryName.toPath(), fs), mimeType = MimeTypes.getMimeTypeFromExtension(entryName)?.toString(), dataSource = DataSource.DISK, ) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/image/RegionBitmapDecoder.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/image/RegionBitmapDecoder.kt index abee08347..c1989ac61 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/image/RegionBitmapDecoder.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/image/RegionBitmapDecoder.kt @@ -23,6 +23,7 @@ import coil3.size.Scale import coil3.size.Size import coil3.size.isOriginal import coil3.size.pxOrElse +import org.koitharu.kotatsu.core.util.ext.copyWithNewSource import kotlin.math.roundToInt class RegionBitmapDecoder( @@ -34,16 +35,21 @@ class RegionBitmapDecoder( 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() + val revivedFetchResult = fetchResult.copyWithNewSource() + return try { + val fallbackDecoder = imageLoader.components.newDecoder( + result = revivedFetchResult, + options = options, + imageLoader = imageLoader, + startIndex = 0, + )?.first + if (fallbackDecoder == null || fallbackDecoder is RegionBitmapDecoder) { + null + } else { + fallbackDecoder.decode() + } + } finally { + revivedFetchResult.source.close() } } val bitmapOptions = BitmapFactory.Options() diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Coil.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Coil.kt index 4256bae0b..e1ca78ab1 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Coil.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Coil.kt @@ -6,10 +6,13 @@ import android.widget.ImageView import androidx.core.graphics.ColorUtils import androidx.core.graphics.drawable.toDrawable import androidx.lifecycle.LifecycleOwner +import androidx.annotation.CheckResult import coil3.Extras import coil3.ImageLoader import coil3.asDrawable +import coil3.decode.ImageSource import coil3.fetch.FetchResult +import coil3.fetch.SourceFetchResult import coil3.request.ErrorResult import coil3.request.ImageRequest import coil3.request.ImageResult @@ -28,6 +31,7 @@ import coil3.toBitmap import coil3.util.CoilUtils import com.google.android.material.progressindicator.BaseProgressIndicator import org.koitharu.kotatsu.R +import okio.buffer import org.koitharu.kotatsu.bookmarks.domain.Bookmark import org.koitharu.kotatsu.core.image.RegionBitmapDecoder import org.koitharu.kotatsu.core.ui.image.AnimatedPlaceholderDrawable @@ -163,3 +167,14 @@ private class CompositeImageRequestListener( val mangaKey = Extras.Key(null) val bookmarkKey = Extras.Key(null) val mangaSourceKey = Extras.Key(null) + +@CheckResult +fun SourceFetchResult.copyWithNewSource(): SourceFetchResult = SourceFetchResult( + source = ImageSource( + source = source.fileSystem.source(source.file()).buffer(), + fileSystem = source.fileSystem, + metadata = source.metadata, + ), + mimeType = mimeType, + dataSource = dataSource, +) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/IO.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/IO.kt index 8cce14a33..3882949e8 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/IO.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/IO.kt @@ -7,6 +7,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.withContext import okhttp3.ResponseBody import okio.BufferedSink +import okio.BufferedSource import okio.FileSystem import okio.IOException import okio.Path @@ -30,6 +31,14 @@ suspend fun BufferedSink.writeAllCancellable(source: Source) = withContext(Dispa writeAll(source.cancellable()) } +fun BufferedSource.readByteBuffer(): ByteBuffer { + val bytes = readByteArray() + return ByteBuffer.allocateDirect(bytes.size) + .put(bytes) + .rewind() as ByteBuffer +} + +@Deprecated("") fun InputStream.toByteBuffer(): ByteBuffer { val outStream = ByteArrayOutputStream(available()) copyTo(outStream)