Use Coil pipeline instead of SSIV for image decode (#692)

(cherry picked from commit c3e7bb12f4)
This commit is contained in:
FooIbar 2024-05-01 14:07:30 +07:00 committed by Ahmad Ansori Palembani
parent 008a906055
commit d8f812a474
Signed by: null2264
GPG key ID: BA64F8B60AF3EFB6
4 changed files with 123 additions and 17 deletions

View file

@ -1,14 +1,13 @@
package eu.kanade.tachiyomi.data.image.coil
import android.graphics.Bitmap
import android.os.Build
import androidx.core.graphics.drawable.toDrawable
import coil.ImageLoader
import coil.decode.DecodeResult
import coil.decode.Decoder
import coil.decode.ImageDecoderDecoder
import coil.decode.ImageSource
import coil.decode.*
import coil.fetch.SourceResult
import coil.request.Options
import eu.kanade.tachiyomi.util.system.GLUtil
import eu.kanade.tachiyomi.util.system.ImageUtil
import okio.BufferedSource
import tachiyomi.decoder.ImageDecoder
@ -20,26 +19,52 @@ class TachiyomiImageDecoder(private val resources: ImageSource, private val opti
override suspend fun decode(): DecodeResult {
val decoder = resources.sourceOrNull()?.use {
ImageDecoder.newInstance(it.inputStream())
ImageDecoder.newInstance(it.inputStream(), options.cropBorders, displayProfile)
}
check(decoder != null && decoder.width > 0 && decoder.height > 0) { "Failed to initialize decoder." }
val bitmap = decoder.decode()
val srcWidth = decoder.width
val srcHeight = decoder.height
val dstWidth = options.size.widthPx(options.scale) { srcWidth }
val dstHeight = options.size.heightPx(options.scale) { srcHeight }
val sampleSize = DecodeUtils.calculateInSampleSize(
srcWidth = srcWidth,
srcHeight = srcHeight,
dstWidth = dstWidth,
dstHeight = dstHeight,
scale = options.scale,
)
var bitmap = decoder.decode(sampleSize = sampleSize)
decoder.recycle()
check(bitmap != null) { "Failed to decode image." }
if (
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
options.bitmapConfig == Bitmap.Config.HARDWARE &&
maxOf(bitmap.width, bitmap.height) <= GLUtil.maxTextureSize
) {
val hwBitmap = bitmap.copy(Bitmap.Config.HARDWARE, false)
if (hwBitmap != null) {
bitmap.recycle()
bitmap = hwBitmap
}
}
return DecodeResult(
drawable = bitmap.toDrawable(options.context.resources),
isSampled = false,
isSampled = sampleSize > 1,
)
}
class Factory : Decoder.Factory {
override fun create(result: SourceResult, options: Options, imageLoader: ImageLoader): Decoder? {
if (!isApplicable(result.source.source())) return null
if (!isApplicable(result.source.source()) || !options.customDecoder) return null
return TachiyomiImageDecoder(result.source, options)
}
@ -54,8 +79,12 @@ class TachiyomiImageDecoder(private val resources: ImageSource, private val opti
}
}
override fun equals(other: Any?) = other is ImageDecoderDecoder.Factory
override fun equals(other: Any?) = other is Factory
override fun hashCode() = javaClass.hashCode()
}
companion object {
var displayProfile: ByteArray? = null
}
}

View file

@ -0,0 +1,49 @@
package eu.kanade.tachiyomi.data.image.coil
import android.graphics.Bitmap
import android.os.Build
import coil.request.ImageRequest
import coil.request.Options
import coil.size.Dimension
import coil.size.Scale
import coil.size.Size
import coil.size.isOriginal
import coil.size.pxOrElse
internal inline fun Size.widthPx(scale: Scale, original: () -> Int): Int {
return if (isOriginal) original() else width.toPx(scale)
}
internal inline fun Size.heightPx(scale: Scale, original: () -> Int): Int {
return if (isOriginal) original() else height.toPx(scale)
}
internal fun Dimension.toPx(scale: Scale): Int = pxOrElse {
when (scale) {
Scale.FILL -> Int.MIN_VALUE
Scale.FIT -> Int.MAX_VALUE
}
}
fun ImageRequest.Builder.cropBorders(enable: Boolean) = apply {
setParameter(cropBordersKey, enable)
}
val Options.cropBorders: Boolean
get() = parameters.value(cropBordersKey) ?: false
private val cropBordersKey = "crop_borders"
fun ImageRequest.Builder.customDecoder(enable: Boolean) = apply {
setParameter(customDecoderKey, enable)
}
val Options.customDecoder: Boolean
get() = parameters.value(customDecoderKey) ?: false
private val customDecoderKey = "custom_decoder"
val Options.bitmapConfig: Bitmap.Config
get() = parameters.value(bitmapConfigKey) ?: if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) Bitmap.Config.HARDWARE else Bitmap.Config.RGB_565
private val bitmapConfigKey = "bitmap_config"

View file

@ -81,6 +81,7 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.core.preference.toggle
import eu.kanade.tachiyomi.data.image.coil.TachiyomiImageDecoder
import eu.kanade.tachiyomi.data.preference.changesIn
import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.databinding.ReaderActivityBinding
@ -2018,7 +2019,9 @@ class ReaderActivity : BaseActivity<ReaderActivityBinding>() {
input.copyTo(output)
}
}
SubsamplingScaleImageView.setDisplayProfile(outputStream.toByteArray())
val data = outputStream.toByteArray()
SubsamplingScaleImageView.setDisplayProfile(data)
TachiyomiImageDecoder.displayProfile = data
}
}

View file

@ -22,11 +22,15 @@ import coil.dispose
import coil.imageLoader
import coil.request.CachePolicy
import coil.request.ImageRequest
import coil.size.Precision
import coil.size.ViewSizeResolver
import com.davemorrissey.labs.subscaleview.ImageSource
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView.SCALE_TYPE_CENTER_INSIDE
import com.github.chrisbanes.photoview.PhotoView
import dev.yokai.domain.ui.settings.ReaderPreferences.CutoutBehaviour
import eu.kanade.tachiyomi.data.image.coil.cropBorders
import eu.kanade.tachiyomi.data.image.coil.customDecoder
import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerConfig
import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonSubsamplingImageView
import eu.kanade.tachiyomi.util.system.DeviceUtil
@ -219,15 +223,36 @@ open class ReaderPageImageView @JvmOverloads constructor(
},
)
when (image) {
is Drawable -> {
val bitmap = (image as BitmapDrawable).bitmap
setImage(ImageSource.bitmap(bitmap))
if (isWebtoon) {
val request = ImageRequest.Builder(context)
.data(image)
.memoryCachePolicy(CachePolicy.DISABLED)
.diskCachePolicy(CachePolicy.DISABLED)
.target(
onSuccess = { result ->
val drawable = result as BitmapDrawable
setImage(ImageSource.bitmap(drawable.bitmap))
isVisible = true
},
onError = {
this@ReaderPageImageView.onImageLoadError()
},
)
.size(ViewSizeResolver(this@ReaderPageImageView))
.precision(Precision.INEXACT)
.cropBorders(config.cropBorders)
.customDecoder(true)
.crossfade(false)
.build()
context.imageLoader.enqueue(request)
} else {
when (image) {
is BitmapDrawable -> setImage(ImageSource.bitmap(image.bitmap))
is InputStream -> setImage(ImageSource.inputStream(image))
else -> throw IllegalArgumentException("Not implemented for class ${image::class.simpleName}")
}
is InputStream -> setImage(ImageSource.inputStream(image))
else -> throw IllegalArgumentException("Not implemented for class ${image::class.simpleName}")
isVisible = true
}
isVisible = true
}
private fun prepareAnimatedImageView() {