diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e45d547c06..cdd30f2053 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -89,7 +89,10 @@ android { dependencies { // Modified dependencies - implementation("com.github.jays2kings:subsampling-scale-image-view:dfd3e43") + implementation("com.github.jays2kings:subsampling-scale-image-view:dfd3e43") { + exclude(module = "image-decoder") + } + implementation("com.github.tachiyomiorg:image-decoder:0e91111") // Source models and interfaces from Tachiyomi 1.x implementation("tachiyomi.sourceapi:source-api:1.1") diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt index 148e26818e..c323cc5f6f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt @@ -349,8 +349,8 @@ class PagerPageHolder( val stream2 = if (extraPage != null) streamFn2?.invoke()?.buffered(16) else null openStream = this@PagerPageHolder.mergePages(stream, stream2) - ImageUtil.findImageType(stream) == ImageUtil.ImageType.GIF || - if (stream2 != null) ImageUtil.findImageType(stream2) == ImageUtil.ImageType.GIF else false + ImageUtil.isAnimatedAndSupported(stream) || + if (stream2 != null) ImageUtil.isAnimatedAndSupported(stream2) else false } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) @@ -654,11 +654,11 @@ class PagerPageHolder( private fun mergePages(imageStream: InputStream, imageStream2: InputStream?): InputStream { imageStream2 ?: return imageStream if (page.fullPage) return imageStream - if (ImageUtil.findImageType(imageStream) == ImageUtil.ImageType.GIF) { + if (ImageUtil.isAnimatedAndSupported(imageStream)) { page.fullPage = true skipExtra = true return imageStream - } else if (ImageUtil.findImageType(imageStream2) == ImageUtil.ImageType.GIF) { + } else if (ImageUtil.isAnimatedAndSupported(imageStream)) { page.isolatedPage = true extraPage?.fullPage = true skipExtra = true diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonPageHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonPageHolder.kt index 55aa834e52..cd7beb375d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonPageHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonPageHolder.kt @@ -280,7 +280,7 @@ class WebtoonPageHolder( val stream = streamFn().buffered(16) openStream = stream - ImageUtil.findImageType(stream) == ImageUtil.ImageType.GIF + ImageUtil.isAnimatedAndSupported(stream) } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/ImageUtil.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/ImageUtil.kt index a9759e96cb..93c5781a31 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/system/ImageUtil.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/ImageUtil.kt @@ -9,8 +9,11 @@ import android.graphics.Rect import android.graphics.drawable.ColorDrawable import android.graphics.drawable.Drawable import android.graphics.drawable.GradientDrawable +import android.os.Build import androidx.annotation.ColorInt import eu.kanade.tachiyomi.R +import tachiyomi.decoder.Format +import tachiyomi.decoder.ImageDecoder import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream import java.io.InputStream @@ -35,36 +38,61 @@ object ImageUtil { fun findImageType(stream: InputStream): ImageType? { try { - val bytes = ByteArray(8) - - val length = if (stream.markSupported()) { - stream.mark(bytes.size) - stream.read(bytes, 0, bytes.size).also { stream.reset() } - } else { - stream.read(bytes, 0, bytes.size) - } - - if (length == -1) { - return null - } - - if (bytes.compareWith(charByteArrayOf(0xFF, 0xD8, 0xFF))) { - return ImageType.JPG - } - if (bytes.compareWith(charByteArrayOf(0x89, 0x50, 0x4E, 0x47))) { - return ImageType.PNG - } - if (bytes.compareWith("GIF8".toByteArray())) { - return ImageType.GIF - } - if (bytes.compareWith("RIFF".toByteArray())) { - return ImageType.WEBP + return when (getImageType(stream)?.format) { + Format.Avif -> ImageType.AVIF + Format.Gif -> ImageType.GIF + Format.Heif -> ImageType.HEIF + Format.Jpeg -> ImageType.JPEG + Format.Png -> ImageType.PNG + Format.Webp -> ImageType.WEBP + else -> null } } catch (e: Exception) { } return null } + fun isAnimatedAndSupported(stream: InputStream): Boolean { + try { + val type = getImageType(stream) ?: return false + return when (type.format) { + Format.Gif -> true + // Coil supports animated WebP on Android 9.0+ + // https://coil-kt.github.io/coil/getting_started/#supported-image-formats + Format.Webp -> type.isAnimated && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P + else -> false + } + } catch (e: Exception) { + } + return false + } + + enum class ImageType(val mime: String, val extension: String) { + AVIF("image/avif", "avif"), + GIF("image/gif", "gif"), + HEIF("image/heif", "heif"), + JPEG("image/jpeg", "jpg"), + PNG("image/png", "png"), + WEBP("image/webp", "webp"), + } + + private fun getImageType(stream: InputStream): tachiyomi.decoder.ImageType? { + val bytes = ByteArray(32) + + val length = if (stream.markSupported()) { + stream.mark(bytes.size) + stream.read(bytes, 0, bytes.size).also { stream.reset() } + } else { + stream.read(bytes, 0, bytes.size) + } + + if (length == -1) { + return null + } + + return ImageDecoder.findType(bytes) + } + fun autoSetBackground(image: Bitmap?, alwaysUseWhite: Boolean, context: Context): Drawable { val backgroundColor = if (alwaysUseWhite) Color.WHITE else { context.getResourceColor(R.attr.readerBackground) @@ -325,11 +353,4 @@ object ImageUtil { } } } - - enum class ImageType(val mime: String, val extension: String) { - JPG("image/jpeg", "jpg"), - PNG("image/png", "png"), - GIF("image/gif", "gif"), - WEBP("image/webp", "webp") - } }