diff --git a/image-loader/src/androidMain/kotlin/com/seiko/imageloader/component/decoder/GifDecoder.kt b/image-loader/src/androidMain/kotlin/com/seiko/imageloader/component/decoder/GifDecoder.kt index 31b14f78..c50b56d2 100644 --- a/image-loader/src/androidMain/kotlin/com/seiko/imageloader/component/decoder/GifDecoder.kt +++ b/image-loader/src/androidMain/kotlin/com/seiko/imageloader/component/decoder/GifDecoder.kt @@ -3,7 +3,9 @@ package com.seiko.imageloader.component.decoder import android.graphics.Bitmap +import android.graphics.Canvas import android.graphics.Movie +import androidx.core.graphics.createBitmap import com.seiko.imageloader.option.Options import com.seiko.imageloader.toImage import com.seiko.imageloader.util.FrameDelayRewritingSource @@ -36,20 +38,30 @@ class GifDecoder private constructor( } else { bufferSource } - val movie: Movie? = bufferedSource.use { Movie.decodeStream(it.inputStream()) } + val movie: Movie? = bufferedSource.use { Movie.decodeStream(it.inputStream()) } check(movie != null && movie.width() > 0 && movie.height() > 0) { "Failed to decode GIF." } val config = options.config.toBitmapConfig() + val movieConfig = when { + // movie.isOpaque && options.allowRgb565 -> Bitmap.Config.RGB_565 + config.isHardware -> Bitmap.Config.ARGB_8888 + else -> config + } + + if (!options.playAnimate) { + val bitmap = createBitmap(movie.width(), movie.height(), movieConfig) + val canvas = Canvas(bitmap) + movie.draw(canvas, 0f, 0f) + return@runInterruptible DecodeResult.Bitmap( + bitmap = bitmap, + ) + } + val drawable = MovieDrawable( movie = movie, - config = when { - // movie.isOpaque && options.allowRgb565 -> Bitmap.Config.RGB_565 - config.isHardware -> Bitmap.Config.ARGB_8888 - else -> config - }, + config = movieConfig, scale = options.scale, - playAnimate = options.playAnimate, ) drawable.setRepeatCount(options.repeatCount) diff --git a/image-loader/src/androidMain/kotlin/com/seiko/imageloader/component/decoder/ImageDecoderDecoder.kt b/image-loader/src/androidMain/kotlin/com/seiko/imageloader/component/decoder/ImageDecoderDecoder.kt index ebaf080d..6946a8bf 100644 --- a/image-loader/src/androidMain/kotlin/com/seiko/imageloader/component/decoder/ImageDecoderDecoder.kt +++ b/image-loader/src/androidMain/kotlin/com/seiko/imageloader/component/decoder/ImageDecoderDecoder.kt @@ -6,6 +6,7 @@ import android.graphics.drawable.AnimatedImageDrawable import android.graphics.drawable.Drawable import android.os.Build.VERSION.SDK_INT import androidx.annotation.RequiresApi +import androidx.core.graphics.decodeBitmap import androidx.core.graphics.decodeDrawable import com.seiko.imageloader.component.fetcher.AssetUriFetcher import com.seiko.imageloader.component.fetcher.ContentUriFetcher @@ -45,20 +46,34 @@ class ImageDecoderDecoder private constructor( override suspend fun decode(): DecodeResult { var imageDecoder: ImageDecoder? = null - val drawable = try { - source.toImageDecoderSource().decodeDrawable { _, _ -> - // Capture the image decoder to manually close it later. - imageDecoder = this + val decoder = source.toImageDecoderSource() + return if (!options.playAnimate) { + val bitmap = try { + decoder.decodeBitmap { _, _ -> + imageDecoder = this + } + } finally { + imageDecoder?.close() + } + DecodeResult.Bitmap( + bitmap = bitmap, + ) + } else { + val drawable = try { + decoder.decodeDrawable { _, _ -> + // Capture the image decoder to manually close it later. + imageDecoder = this - // Configure any other attributes. - configureImageDecoderProperties() + // Configure any other attributes. + configureImageDecoderProperties() + } + } finally { + imageDecoder?.close() } - } finally { - imageDecoder?.close() + DecodeResult.Image( + image = wrapDrawable(drawable).toImage(), + ) } - return DecodeResult.Image( - image = wrapDrawable(drawable).toImage(), - ) } private fun wrapBufferedSource(channel: BufferedSource): BufferedSource { @@ -139,7 +154,7 @@ class ImageDecoderDecoder private constructor( // } // Wrap AnimatedImageDrawable in a ScaleDrawable so it always scales to fill its bounds. - return ScaleDrawable(baseDrawable, options.scale, options.playAnimate) + return ScaleDrawable(baseDrawable, options.scale) } class Factory @JvmOverloads constructor( diff --git a/image-loader/src/androidMain/kotlin/com/seiko/imageloader/util/MovieDrawable.kt b/image-loader/src/androidMain/kotlin/com/seiko/imageloader/util/MovieDrawable.kt index 820ada58..d194fc7f 100644 --- a/image-loader/src/androidMain/kotlin/com/seiko/imageloader/util/MovieDrawable.kt +++ b/image-loader/src/androidMain/kotlin/com/seiko/imageloader/util/MovieDrawable.kt @@ -33,7 +33,6 @@ internal class MovieDrawable @JvmOverloads constructor( private val movie: Movie, val config: Bitmap.Config = Bitmap.Config.ARGB_8888, val scale: Scale = Scale.FIT, - private val playAnimate: Boolean = false, ) : Drawable(), Animatable2Compat { private val paint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.FILTER_BITMAP_FLAG) @@ -250,8 +249,6 @@ internal class MovieDrawable @JvmOverloads constructor( override fun isRunning() = isRunning override fun start() { - if (!playAnimate) return - if (isRunning) return isRunning = true @@ -263,8 +260,6 @@ internal class MovieDrawable @JvmOverloads constructor( } override fun stop() { - if (!playAnimate) return - if (!isRunning) return isRunning = false diff --git a/image-loader/src/androidMain/kotlin/com/seiko/imageloader/util/ScaleDrawable.kt b/image-loader/src/androidMain/kotlin/com/seiko/imageloader/util/ScaleDrawable.kt index cf529926..0968ef3a 100644 --- a/image-loader/src/androidMain/kotlin/com/seiko/imageloader/util/ScaleDrawable.kt +++ b/image-loader/src/androidMain/kotlin/com/seiko/imageloader/util/ScaleDrawable.kt @@ -23,7 +23,6 @@ import kotlin.math.roundToInt internal class ScaleDrawable @JvmOverloads constructor( val child: Drawable, val scale: Scale = Scale.FIT, - private val playAnimate: Boolean = false, ) : Drawable(), Drawable.Callback, Animatable { private var childDx = 0f @@ -110,10 +109,10 @@ internal class ScaleDrawable @JvmOverloads constructor( override fun isRunning() = child is Animatable && child.isRunning override fun start() { - if (child is Animatable && playAnimate) child.start() + if (child is Animatable) child.start() } override fun stop() { - if (child is Animatable && playAnimate) child.stop() + if (child is Animatable) child.stop() } } diff --git a/image-loader/src/skiaMain/kotlin/com/seiko/imageloader/component/decoder/GifDecoder.kt b/image-loader/src/skiaMain/kotlin/com/seiko/imageloader/component/decoder/GifDecoder.kt index 271dbe7e..c85251e1 100644 --- a/image-loader/src/skiaMain/kotlin/com/seiko/imageloader/component/decoder/GifDecoder.kt +++ b/image-loader/src/skiaMain/kotlin/com/seiko/imageloader/component/decoder/GifDecoder.kt @@ -6,8 +6,10 @@ import com.seiko.imageloader.util.isGif import kotlinx.coroutines.CoroutineScope import okio.BufferedSource import okio.use +import org.jetbrains.skia.Bitmap import org.jetbrains.skia.Codec import org.jetbrains.skia.Data +import org.jetbrains.skia.Image class GifDecoder private constructor( private val source: BufferedSource, @@ -15,6 +17,14 @@ class GifDecoder private constructor( private val options: Options, ) : Decoder { override suspend fun decode(): DecodeResult { + if (!options.playAnimate) { + val image = source.use { + Image.makeFromEncoded(it.readByteArray()) + } + return DecodeResult.Bitmap( + bitmap = Bitmap.makeFromImage(image), + ) + } val codec = source.use { Codec.makeFromData(Data.makeFromBytes(it.readByteArray())) } @@ -22,7 +32,6 @@ class GifDecoder private constructor( painter = GifPainter( codec = codec, imageScope = imageScope, - playAnimate = options.playAnimate, repeatCount = options.repeatCount, ), ) diff --git a/image-loader/src/skiaMain/kotlin/com/seiko/imageloader/util/GifPainter.kt b/image-loader/src/skiaMain/kotlin/com/seiko/imageloader/util/GifPainter.kt index 0a8a119f..19a9cf12 100644 --- a/image-loader/src/skiaMain/kotlin/com/seiko/imageloader/util/GifPainter.kt +++ b/image-loader/src/skiaMain/kotlin/com/seiko/imageloader/util/GifPainter.kt @@ -23,7 +23,6 @@ import kotlin.time.Duration.Companion.milliseconds internal class GifPainter( private val codec: Codec, private val imageScope: CoroutineScope, - private val playAnimate: Boolean = true, private val repeatCount: Int = Options.REPEAT_INFINITE, ) : Painter(), RememberObserver { @@ -50,7 +49,7 @@ internal class GifPainter( yield() when { codec.framesInfo.isEmpty() -> Unit - codec.framesInfo.size == 1 || !playAnimate -> { + codec.framesInfo.size == 1 -> { emit(getImageBitmap(codec, 0)) } else -> {