Refine and enable AnimatedWebPDrawable by default (#2442)

This commit is contained in:
FooIbar authored and GitHub committed 2025-06-28 16:10:29 +08:00
1 parent 924d3c4ea3
commit a10f9f2ce8
2 files changed
+37 -31

No files matched your search

@@ -127,7 +127,7 @@ object Settings : DataStorePreferences(null) {
val animateItems = boolPref("animate_items", true)
val enableCronet = boolPref("enable_cronet", true)
val enableQuic = boolPref("enable_quic", true)
val webpDecoder = boolPref("webp_decoder", false)
val webpDecoder = boolPref("webp_decoder", true)
val desktopSite = boolPref("desktop_site", true)
val displayName = stringOrNullPref("display_name", null)
val avatar = stringOrNullPref("avatar", null)
@@ -7,14 +7,15 @@ import android.graphics.Paint
import android.graphics.PixelFormat
import android.graphics.drawable.Animatable
import android.graphics.drawable.Drawable
import android.os.SystemClock
import androidx.core.graphics.createBitmap
import eu.kanade.tachiyomi.util.system.logcat
import java.nio.ByteBuffer
import kotlin.time.TimeSource
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.launch
// Hold a reference to the buffer as it's used by the decoder
@@ -29,7 +30,7 @@ class AnimatedWebPDrawable(private val source: ByteBuffer) : Drawable(), Animata
private var decodeJob: Job? = null
private var loopsCompleted = 0
private var frameDuration: Int
private var timeToShowNextFrame = 0L
private var currentFrame: Frame
private var nextFrame: Frame
@@ -45,7 +46,6 @@ class AnimatedWebPDrawable(private val source: ByteBuffer) : Drawable(), Animata
nativeDestroyDecoder(decoder)
"Failed to decode first frame"
}
frameDuration = timestamp
currentFrame = Frame(bitmap, timestamp)
nextFrame = Frame(createBitmap(width, height), 0)
}
@@ -54,30 +54,42 @@ class AnimatedWebPDrawable(private val source: ByteBuffer) : Drawable(), Animata
override fun getIntrinsicHeight() = height
private fun decodeNextFrame(reset: Boolean) {
nextFrame.timestamp = nativeDecodeNextFrame(decoder, reset, nextFrame.bitmap)
if (nextFrame.timestamp == 0) {
throw CancellationException("Failed to decode next frame")
private val runnable = Runnable {
timeToShowNextFrame = SystemClock.uptimeMillis() + nextFrame.timestamp - currentFrame.timestamp
invalidateSelf()
}
private fun decodeNextFrame(reset: Boolean) = decodeScope.launch {
val timestamp = nativeDecodeNextFrame(decoder, reset, nextFrame.bitmap)
ensureActive()
check(timestamp != 0) {
decodeJob = null
"Failed to decode next frame"
}
if (timestamp <= currentFrame.timestamp) {
if (reset) loopsCompleted = 0 else loopsCompleted++
currentFrame.timestamp = 0
}
nextFrame.timestamp = timestamp
nextFrame.bitmap.prepareToDraw()
}.apply {
invokeOnCompletion { cause ->
when (cause) {
null -> if (reset) {
runnable.run()
} else {
scheduleSelf(runnable, timeToShowNextFrame)
}
!is CancellationException -> logcat(cause)
}
}
}
override fun draw(canvas: Canvas) {
if (decodeJob?.isCompleted == true && nextFrame.timestamp != 0) {
val timeMark = TimeSource.Monotonic.markNow()
frameDuration = if (nextFrame.timestamp > currentFrame.timestamp) {
nextFrame.timestamp - currentFrame.timestamp
} else {
loopsCompleted++
nextFrame.timestamp
}
currentFrame = nextFrame.also { nextFrame = currentFrame }
if (decodeJob?.isCompleted == true) {
decodeJob = if (loopCount == 0 || loopsCompleted < loopCount) {
decodeScope.launch {
decodeNextFrame(false)
delay(frameDuration - timeMark.elapsedNow().inWholeMilliseconds)
invalidateSelf()
}
currentFrame = nextFrame.also { nextFrame = currentFrame }
decodeNextFrame(false)
} else {
null
}
@@ -110,20 +122,14 @@ class AnimatedWebPDrawable(private val source: ByteBuffer) : Drawable(), Animata
override fun start() {
if (decodeJob == null) {
val timeMark = TimeSource.Monotonic.markNow()
loopsCompleted = 0
decodeJob = decodeScope.launch {
val isFirstFrame = currentFrame.timestamp == frameDuration
decodeNextFrame(!isFirstFrame)
delay(frameDuration - timeMark.elapsedNow().inWholeMilliseconds)
invalidateSelf()
}
decodeJob = decodeNextFrame(true)
}
}
override fun stop() {
decodeJob?.cancel()
decodeJob = null
unscheduleSelf(runnable)
}
fun dispose() {