mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-11-20 04:59:17 +01:00
Display animated webp whenever possible
Support AVIF and HEIF images Handle HEIF images Using new decoder Co-Authored-By: arkon <4098258+arkon@users.noreply.github.com>
This commit is contained in:
parent
a8a23c153d
commit
756f285675
@ -89,7 +89,10 @@ android {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// Modified 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
|
// Source models and interfaces from Tachiyomi 1.x
|
||||||
implementation("tachiyomi.sourceapi:source-api:1.1")
|
implementation("tachiyomi.sourceapi:source-api:1.1")
|
||||||
|
@ -349,8 +349,8 @@ class PagerPageHolder(
|
|||||||
|
|
||||||
val stream2 = if (extraPage != null) streamFn2?.invoke()?.buffered(16) else null
|
val stream2 = if (extraPage != null) streamFn2?.invoke()?.buffered(16) else null
|
||||||
openStream = this@PagerPageHolder.mergePages(stream, stream2)
|
openStream = this@PagerPageHolder.mergePages(stream, stream2)
|
||||||
ImageUtil.findImageType(stream) == ImageUtil.ImageType.GIF ||
|
ImageUtil.isAnimatedAndSupported(stream) ||
|
||||||
if (stream2 != null) ImageUtil.findImageType(stream2) == ImageUtil.ImageType.GIF else false
|
if (stream2 != null) ImageUtil.isAnimatedAndSupported(stream2) else false
|
||||||
}
|
}
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
@ -654,11 +654,11 @@ class PagerPageHolder(
|
|||||||
private fun mergePages(imageStream: InputStream, imageStream2: InputStream?): InputStream {
|
private fun mergePages(imageStream: InputStream, imageStream2: InputStream?): InputStream {
|
||||||
imageStream2 ?: return imageStream
|
imageStream2 ?: return imageStream
|
||||||
if (page.fullPage) return imageStream
|
if (page.fullPage) return imageStream
|
||||||
if (ImageUtil.findImageType(imageStream) == ImageUtil.ImageType.GIF) {
|
if (ImageUtil.isAnimatedAndSupported(imageStream)) {
|
||||||
page.fullPage = true
|
page.fullPage = true
|
||||||
skipExtra = true
|
skipExtra = true
|
||||||
return imageStream
|
return imageStream
|
||||||
} else if (ImageUtil.findImageType(imageStream2) == ImageUtil.ImageType.GIF) {
|
} else if (ImageUtil.isAnimatedAndSupported(imageStream)) {
|
||||||
page.isolatedPage = true
|
page.isolatedPage = true
|
||||||
extraPage?.fullPage = true
|
extraPage?.fullPage = true
|
||||||
skipExtra = true
|
skipExtra = true
|
||||||
|
@ -280,7 +280,7 @@ class WebtoonPageHolder(
|
|||||||
val stream = streamFn().buffered(16)
|
val stream = streamFn().buffered(16)
|
||||||
openStream = stream
|
openStream = stream
|
||||||
|
|
||||||
ImageUtil.findImageType(stream) == ImageUtil.ImageType.GIF
|
ImageUtil.isAnimatedAndSupported(stream)
|
||||||
}
|
}
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
@ -9,8 +9,11 @@ import android.graphics.Rect
|
|||||||
import android.graphics.drawable.ColorDrawable
|
import android.graphics.drawable.ColorDrawable
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.graphics.drawable.GradientDrawable
|
import android.graphics.drawable.GradientDrawable
|
||||||
|
import android.os.Build
|
||||||
import androidx.annotation.ColorInt
|
import androidx.annotation.ColorInt
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
import tachiyomi.decoder.Format
|
||||||
|
import tachiyomi.decoder.ImageDecoder
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
@ -35,36 +38,61 @@ object ImageUtil {
|
|||||||
|
|
||||||
fun findImageType(stream: InputStream): ImageType? {
|
fun findImageType(stream: InputStream): ImageType? {
|
||||||
try {
|
try {
|
||||||
val bytes = ByteArray(8)
|
return when (getImageType(stream)?.format) {
|
||||||
|
Format.Avif -> ImageType.AVIF
|
||||||
val length = if (stream.markSupported()) {
|
Format.Gif -> ImageType.GIF
|
||||||
stream.mark(bytes.size)
|
Format.Heif -> ImageType.HEIF
|
||||||
stream.read(bytes, 0, bytes.size).also { stream.reset() }
|
Format.Jpeg -> ImageType.JPEG
|
||||||
} else {
|
Format.Png -> ImageType.PNG
|
||||||
stream.read(bytes, 0, bytes.size)
|
Format.Webp -> ImageType.WEBP
|
||||||
}
|
else -> null
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
}
|
}
|
||||||
return null
|
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 {
|
fun autoSetBackground(image: Bitmap?, alwaysUseWhite: Boolean, context: Context): Drawable {
|
||||||
val backgroundColor = if (alwaysUseWhite) Color.WHITE else {
|
val backgroundColor = if (alwaysUseWhite) Color.WHITE else {
|
||||||
context.getResourceColor(R.attr.readerBackground)
|
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")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user