awxkee / jxl-coder-coil

JPEG XL Decoder for Coil Android
Apache License 2.0
10 stars 1 forks source link

Update AnimatedJxlDecoder.kt #2

Closed T8RIN closed 8 months ago

T8RIN commented 8 months ago

import android.content.Context import android.graphics.Bitmap import android.graphics.drawable.Drawable import android.os.Build import coil.ImageLoader import coil.decode.DecodeResult import coil.decode.Decoder import coil.fetch.SourceResult import coil.request.Options import coil.size.Scale import coil.size.Size import coil.size.pxOrElse import com.awxkee.jxlcoder.JxlAnimatedImage import com.awxkee.jxlcoder.JxlResizeFilter import com.awxkee.jxlcoder.PreferredColorConfig import com.awxkee.jxlcoder.ScaleMode import com.awxkee.jxlcoder.animation.AnimatedDrawable import com.awxkee.jxlcoder.animation.JxlAnimatedStore import kotlinx.coroutines.runInterruptible import okio.BufferedSource import okio.ByteString.Companion.toByteString

class AnimatedJxlDecoder( private val source: SourceResult, private val options: Options, private val context: Context, private val preheatFrames: Int ) : Decoder {

override suspend fun decode(): DecodeResult = runInterruptible {
    // ColorSpace is preferred to be ignored due to lib is trying to handle all color profile by itself
    val sourceData = source.source.source().readByteArray()

    var mPreferredColorConfig: PreferredColorConfig = when (options.config) {
        Bitmap.Config.ALPHA_8 -> PreferredColorConfig.RGBA_8888
        Bitmap.Config.RGB_565 -> if (options.allowRgb565) PreferredColorConfig.RGB_565 else PreferredColorConfig.DEFAULT
        Bitmap.Config.ARGB_8888 -> PreferredColorConfig.RGBA_8888
        else -> PreferredColorConfig.DEFAULT
    }
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && options.config == Bitmap.Config.RGBA_F16) {
        mPreferredColorConfig = PreferredColorConfig.RGBA_F16
    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && options.config == Bitmap.Config.HARDWARE) {
        mPreferredColorConfig = PreferredColorConfig.HARDWARE
    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && options.config == Bitmap.Config.RGBA_1010102) {
        mPreferredColorConfig = PreferredColorConfig.RGBA_1010102
    }

    if (options.size == Size.ORIGINAL) {
        val originalImage = JxlAnimatedImage(
            byteArray = sourceData,
            preferredColorConfig = mPreferredColorConfig
        )
        return@runInterruptible DecodeResult(
            drawable = originalImage.animatedDrawable(),
            isSampled = false
        )
    }

    val dstWidth = options.size.width.pxOrElse { 0 }
    val dstHeight = options.size.height.pxOrElse { 0 }
    val scaleMode = when (options.scale) {
        Scale.FILL -> ScaleMode.FILL
        Scale.FIT -> ScaleMode.FIT
    }

    val originalImage = JxlAnimatedImage(
        byteArray = sourceData,
        preferredColorConfig = mPreferredColorConfig,
        scaleMode = scaleMode,
        jxlResizeFilter = JxlResizeFilter.BILINEAR
    )
    return@runInterruptible DecodeResult(
        drawable = originalImage.animatedDrawable(
            dstWidth = dstWidth,
            dstHeight = dstHeight
        ),
        isSampled = true
    )
}

private fun JxlAnimatedImage.animatedDrawable(
    dstWidth: Int = 0,
    dstHeight: Int = 0
): Drawable = AnimatedDrawable(
    frameStore = JxlAnimatedStore(
        jxlAnimatedImage = this,
        targetWidth = dstWidth,
        targetHeight = dstHeight
    ),
    preheatFrames = preheatFrames,
    firstFrameAsPlaceholder = true
)

class Factory(
    private val context: Context,
    private val preheatFrames: Int = 6
) : Decoder.Factory {
    override fun create(
        result: SourceResult,
        options: Options,
        imageLoader: ImageLoader
    ) = if (isJXL(result.source.source())) {
        AnimatedJxlDecoder(
            source = result,
            options = options,
            context = context,
            preheatFrames = preheatFrames
        )
    } else null

    companion object {
        private val MAGIC_1 = byteArrayOf(0xFF.toByte(), 0x0A).toByteString()
        private val MAGIC_2 = byteArrayOf(
            0x0.toByte(),
            0x0.toByte(),
            0x0.toByte(),
            0x0C.toByte(),
            0x4A,
            0x58,
            0x4C,
            0x20,
            0x0D,
            0x0A,
            0x87.toByte(),
            0x0A
        ).toByteString()

        private fun isJXL(source: BufferedSource): Boolean {
            return source.rangeEquals(0, MAGIC_1) || source.rangeEquals(
                0,
                MAGIC_2
            )
        }
    }
}

}