1+ package com.github.awxkee.avifcoil.decoder.animation
2+
3+
4+ import android.annotation.SuppressLint
5+ import android.graphics.Bitmap
6+ import android.graphics.drawable.BitmapDrawable
7+ import android.graphics.drawable.Drawable
8+ import android.os.Build
9+ import coil.ImageLoader
10+ import coil.decode.DecodeResult
11+ import coil.decode.Decoder
12+ import coil.fetch.SourceResult
13+ import coil.request.Options
14+ import coil.size.Scale
15+ import coil.size.Size
16+ import coil.size.pxOrElse
17+ import com.radzivon.bartoshyk.avif.coder.AvifAnimatedDecoder
18+ import com.radzivon.bartoshyk.avif.coder.PreferredColorConfig
19+ import com.radzivon.bartoshyk.avif.coder.ScaleMode
20+ import kotlinx.coroutines.runInterruptible
21+ import okio.ByteString.Companion.encodeUtf8
22+
23+ public class AnimatedAvifDecoder (
24+ private val source : SourceResult ,
25+ private val options : Options ,
26+ private val preheatFrames : Int ,
27+ private val exceptionLogger : ((Exception ) -> Unit )? = null ,
28+ ) : Decoder {
29+
30+ override suspend fun decode (): DecodeResult ? = runInterruptible {
31+ try {
32+ // ColorSpace is preferred to be ignored due to lib is trying to handle all color profile by itself
33+ val sourceData = source.source.source().readByteArray()
34+
35+ var mPreferredColorConfig: PreferredColorConfig = when (options.config) {
36+ Bitmap .Config .ALPHA_8 -> PreferredColorConfig .RGBA_8888
37+ Bitmap .Config .RGB_565 -> if (options.allowRgb565) PreferredColorConfig .RGB_565 else PreferredColorConfig .DEFAULT
38+ Bitmap .Config .ARGB_8888 -> PreferredColorConfig .RGBA_8888
39+ else -> PreferredColorConfig .DEFAULT
40+ }
41+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .O && options.config == Bitmap .Config .RGBA_F16 ) {
42+ mPreferredColorConfig = PreferredColorConfig .RGBA_F16
43+ } else if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .Q && options.config == Bitmap .Config .HARDWARE ) {
44+ mPreferredColorConfig = PreferredColorConfig .HARDWARE
45+ } else if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .TIRAMISU && options.config == Bitmap .Config .RGBA_1010102 ) {
46+ mPreferredColorConfig = PreferredColorConfig .RGBA_1010102
47+ }
48+
49+ if (options.size == Size .ORIGINAL ) {
50+ val originalImage = AvifAnimatedDecoder (sourceData)
51+ return @runInterruptible DecodeResult (
52+ drawable = originalImage.drawable(
53+ colorConfig = mPreferredColorConfig,
54+ scaleMode = ScaleMode .FIT ,
55+ ),
56+ isSampled = false
57+ )
58+ }
59+
60+ val dstWidth = options.size.width.pxOrElse { 0 }
61+ val dstHeight = options.size.height.pxOrElse { 0 }
62+ val scaleMode = when (options.scale) {
63+ Scale .FILL -> ScaleMode .FILL
64+ Scale .FIT -> ScaleMode .FIT
65+ }
66+
67+ val originalImage = AvifAnimatedDecoder (sourceData)
68+
69+ DecodeResult (
70+ drawable = originalImage.drawable(
71+ dstWidth = dstWidth,
72+ dstHeight = dstHeight,
73+ colorConfig = mPreferredColorConfig,
74+ scaleMode = scaleMode
75+ ),
76+ isSampled = true
77+ )
78+ } catch (e: Exception ) {
79+ exceptionLogger?.invoke(e)
80+ return @runInterruptible null
81+ }
82+ }
83+
84+ private fun AvifAnimatedDecoder.drawable (
85+ dstWidth : Int = 0,
86+ dstHeight : Int = 0,
87+ colorConfig : PreferredColorConfig ,
88+ scaleMode : ScaleMode
89+ ): Drawable = if (getFramesCount() > 1 ) {
90+ AnimatedDrawable (
91+ frameStore = AvifAnimatedStore (
92+ avifAnimatedDecoder = this ,
93+ targetWidth = dstWidth,
94+ targetHeight = dstHeight,
95+ scaleMode = scaleMode,
96+ preferredColorConfig = colorConfig
97+ ),
98+ preheatFrames = preheatFrames,
99+ firstFrameAsPlaceholder = true
100+ )
101+ } else {
102+ BitmapDrawable (
103+ options.context.resources,
104+ if (dstWidth == 0 || dstHeight == 0 ) {
105+ getFrame(
106+ frame = 0 ,
107+ preferredColorConfig = colorConfig
108+ )
109+ } else {
110+ getScaledFrame(
111+ frame = 0 ,
112+ scaledWidth = dstWidth,
113+ scaledHeight = dstHeight,
114+ scaleMode = scaleMode,
115+ preferredColorConfig = colorConfig
116+ )
117+ }
118+ )
119+ }
120+
121+ public class Factory (
122+ private val preheatFrames : Int = 6 ,
123+ private val exceptionLogger : ((Exception ) -> Unit )? = null ,
124+ ) : Decoder.Factory {
125+
126+ override fun create (
127+ result : SourceResult ,
128+ options : Options ,
129+ imageLoader : ImageLoader ,
130+ ): Decoder ? {
131+ return if (
132+ AVAILABLE_BRANDS .any {
133+ result.source.source().rangeEquals(4 , it)
134+ }
135+ ) AnimatedAvifDecoder (
136+ source = result,
137+ options = options,
138+ preheatFrames = preheatFrames,
139+ exceptionLogger = exceptionLogger,
140+ )
141+ else null
142+ }
143+
144+ companion object {
145+ private val AVIF = " ftypavif" .encodeUtf8()
146+ private val AVIS = " ftypavis" .encodeUtf8()
147+
148+ private val AVAILABLE_BRANDS = listOf (AVIF , AVIS )
149+ }
150+ }
151+
152+ @SuppressLint(" ObsoleteSdkInt" )
153+ companion object {
154+ init {
155+ if (Build .VERSION .SDK_INT >= 24 ) {
156+ System .loadLibrary(" coder" )
157+ }
158+ }
159+ }
160+
161+ }
0 commit comments