Add ApngLoader
This commit is contained in:
parent
f7d76f26e7
commit
af21d501f6
|
@ -3,13 +3,11 @@ package oupson.apng.decoder
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.*
|
import android.graphics.*
|
||||||
import android.graphics.drawable.AnimatedImageDrawable
|
import android.graphics.drawable.AnimatedImageDrawable
|
||||||
import android.graphics.drawable.AnimationDrawable
|
|
||||||
import android.graphics.drawable.BitmapDrawable
|
import android.graphics.drawable.BitmapDrawable
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.widget.ImageView
|
|
||||||
import androidx.annotation.RawRes
|
import androidx.annotation.RawRes
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import oupson.apng.BuildConfig
|
import oupson.apng.BuildConfig
|
||||||
|
@ -30,20 +28,6 @@ import java.util.zip.CRC32
|
||||||
* Call [decodeApng]
|
* Call [decodeApng]
|
||||||
*/
|
*/
|
||||||
class ApngDecoder {
|
class ApngDecoder {
|
||||||
interface Callback {
|
|
||||||
/**
|
|
||||||
* Function called when the file was successfully decoded.
|
|
||||||
* @param drawable Can be an [ApngDrawable] if successful and an [AnimatedImageDrawable] if the image decoded is not an APNG but a gif. If it is not an animated image, it is a [Drawable].
|
|
||||||
*/
|
|
||||||
fun onSuccess(drawable: Drawable)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Function called when something gone wrong.
|
|
||||||
* @param error The problem.
|
|
||||||
*/
|
|
||||||
fun onError(error: Exception)
|
|
||||||
}
|
|
||||||
|
|
||||||
class Config(
|
class Config(
|
||||||
internal var speed: Float = 1f,
|
internal var speed: Float = 1f,
|
||||||
internal var bitmapConfig: Bitmap.Config = Bitmap.Config.ARGB_8888,
|
internal var bitmapConfig: Bitmap.Config = Bitmap.Config.ARGB_8888,
|
||||||
|
@ -645,252 +629,7 @@ class ApngDecoder {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Load Apng into an imageView, asynchronously.
|
|
||||||
* @param context Context needed for animation drawable.
|
|
||||||
* @param file File to decode.
|
|
||||||
* @param imageView Image View.
|
|
||||||
* @param callback [ApngDecoder.Callback] to handle success and error.
|
|
||||||
* @param config Decoder configuration
|
|
||||||
*/
|
|
||||||
@Suppress("unused", "BlockingMethodInNonBlockingContext")
|
|
||||||
@JvmStatic
|
|
||||||
@JvmOverloads
|
|
||||||
fun decodeApngAsyncInto(
|
|
||||||
context: Context,
|
|
||||||
file: File,
|
|
||||||
imageView: ImageView,
|
|
||||||
callback: Callback? = null,
|
|
||||||
config: Config = Config(),
|
|
||||||
scope: CoroutineScope = GlobalScope
|
|
||||||
) {
|
|
||||||
scope.launch(Dispatchers.Default) {
|
|
||||||
try {
|
|
||||||
val drawable =
|
|
||||||
decodeApng(
|
|
||||||
context,
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
FileInputStream(file)
|
|
||||||
},
|
|
||||||
config
|
|
||||||
)
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
imageView.setImageDrawable(drawable)
|
|
||||||
(drawable as? AnimationDrawable)?.start()
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
||||||
(drawable as? AnimatedImageDrawable)?.start()
|
|
||||||
}
|
|
||||||
callback?.onSuccess(drawable)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
callback?.onError(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load Apng into an imageView, asynchronously.
|
|
||||||
* @param context Context needed for animation drawable and content resolver.
|
|
||||||
* @param uri Uri to load.
|
|
||||||
* @param imageView Image View.
|
|
||||||
* @param callback [ApngDecoder.Callback] to handle success and error.
|
|
||||||
* @param config Decoder configuration
|
|
||||||
*/
|
|
||||||
@Suppress("unused")
|
|
||||||
@JvmStatic
|
|
||||||
@JvmOverloads
|
|
||||||
fun decodeApngAsyncInto(
|
|
||||||
context: Context,
|
|
||||||
uri: Uri,
|
|
||||||
imageView: ImageView,
|
|
||||||
callback: Callback? = null,
|
|
||||||
config: Config = Config(),
|
|
||||||
scope: CoroutineScope = GlobalScope
|
|
||||||
) {
|
|
||||||
val inputStream = context.contentResolver.openInputStream(uri)!!
|
|
||||||
scope.launch(Dispatchers.Default) {
|
|
||||||
try {
|
|
||||||
val drawable =
|
|
||||||
decodeApng(
|
|
||||||
context,
|
|
||||||
inputStream,
|
|
||||||
config
|
|
||||||
)
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
imageView.setImageDrawable(drawable)
|
|
||||||
(drawable as? AnimationDrawable)?.start()
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
||||||
(drawable as? AnimatedImageDrawable)?.start()
|
|
||||||
}
|
|
||||||
callback?.onSuccess(drawable)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
callback?.onError(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load Apng into an imageView, asynchronously.
|
|
||||||
* @param context Context needed to decode the resource and for the animation drawable.
|
|
||||||
* @param res Raw resource to load.
|
|
||||||
* @param imageView Image View.
|
|
||||||
* @param callback [ApngDecoder.Callback] to handle success and error.
|
|
||||||
* @param config Decoder configuration
|
|
||||||
*/
|
|
||||||
@Suppress("unused")
|
|
||||||
@JvmStatic
|
|
||||||
@JvmOverloads
|
|
||||||
fun decodeApngAsyncInto(
|
|
||||||
context: Context, @RawRes res: Int,
|
|
||||||
imageView: ImageView,
|
|
||||||
callback: Callback? = null,
|
|
||||||
config: Config = Config(),
|
|
||||||
scope: CoroutineScope = GlobalScope
|
|
||||||
) {
|
|
||||||
scope.launch(Dispatchers.Default) {
|
|
||||||
try {
|
|
||||||
val drawable =
|
|
||||||
decodeApng(
|
|
||||||
context,
|
|
||||||
context.resources.openRawResource(res),
|
|
||||||
config
|
|
||||||
)
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
imageView.setImageDrawable(drawable)
|
|
||||||
(drawable as? AnimationDrawable)?.start()
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
||||||
(drawable as? AnimatedImageDrawable)?.start()
|
|
||||||
}
|
|
||||||
callback?.onSuccess(drawable)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
callback?.onError(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load Apng into an imageView, asynchronously.
|
|
||||||
* @param context Context needed for the animation drawable.
|
|
||||||
* @param url URL to load.
|
|
||||||
* @param imageView Image View.
|
|
||||||
* @param callback [ApngDecoder.Callback] to handle success and error.
|
|
||||||
* @param config Decoder configuration
|
|
||||||
*/
|
|
||||||
@Suppress("unused", "BlockingMethodInNonBlockingContext")
|
|
||||||
@JvmStatic
|
|
||||||
@JvmOverloads
|
|
||||||
fun decodeApngAsyncInto(
|
|
||||||
context: Context,
|
|
||||||
url: URL,
|
|
||||||
imageView: ImageView,
|
|
||||||
callback: Callback? = null,
|
|
||||||
config: Config = Config(),
|
|
||||||
scope: CoroutineScope = GlobalScope
|
|
||||||
) {
|
|
||||||
scope.launch(Dispatchers.Default) {
|
|
||||||
try {
|
|
||||||
val drawable = decodeApng(
|
|
||||||
context,
|
|
||||||
ByteArrayInputStream(
|
|
||||||
Loader.load(
|
|
||||||
url
|
|
||||||
)
|
|
||||||
),
|
|
||||||
config
|
|
||||||
)
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
imageView.setImageDrawable(drawable)
|
|
||||||
(drawable as? AnimationDrawable)?.start()
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
||||||
(drawable as? AnimatedImageDrawable)?.start()
|
|
||||||
}
|
|
||||||
callback?.onSuccess(drawable)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
callback?.onError(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load Apng into an imageView, asynchronously.
|
|
||||||
* @param context Context needed for decoding the image and creating the animation drawable.
|
|
||||||
* @param string URL to load
|
|
||||||
* @param imageView Image View.
|
|
||||||
* @param callback [ApngDecoder.Callback] to handle success and error.
|
|
||||||
* @param config Decoder configuration
|
|
||||||
*/
|
|
||||||
@Suppress("unused")
|
|
||||||
@JvmStatic
|
|
||||||
@JvmOverloads
|
|
||||||
fun decodeApngAsyncInto(
|
|
||||||
context: Context,
|
|
||||||
string: String,
|
|
||||||
imageView: ImageView,
|
|
||||||
callback: Callback? = null,
|
|
||||||
config: Config = Config(),
|
|
||||||
scope: CoroutineScope = GlobalScope
|
|
||||||
) {
|
|
||||||
scope.launch(Dispatchers.Default) {
|
|
||||||
try {
|
|
||||||
if (string.startsWith("http://") || string.startsWith("https://")) {
|
|
||||||
decodeApngAsyncInto(
|
|
||||||
context,
|
|
||||||
URL(string),
|
|
||||||
imageView,
|
|
||||||
callback,
|
|
||||||
config
|
|
||||||
)
|
|
||||||
} else if (File(string).exists()) {
|
|
||||||
var pathToLoad =
|
|
||||||
if (string.startsWith("content://")) string else "file://$string"
|
|
||||||
pathToLoad = pathToLoad.replace("%", "%25").replace("#", "%23")
|
|
||||||
decodeApngAsyncInto(
|
|
||||||
context,
|
|
||||||
Uri.parse(pathToLoad),
|
|
||||||
imageView,
|
|
||||||
callback,
|
|
||||||
config
|
|
||||||
)
|
|
||||||
} else if (string.startsWith("file://android_asset/")) {
|
|
||||||
val drawable =
|
|
||||||
decodeApng(
|
|
||||||
context,
|
|
||||||
context.assets.open(string.replace("file:///android_asset/", "")),
|
|
||||||
|
|
||||||
config
|
|
||||||
)
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
imageView.setImageDrawable(drawable)
|
|
||||||
(drawable as? AnimationDrawable)?.start()
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
||||||
(drawable as? AnimatedImageDrawable)?.start()
|
|
||||||
}
|
|
||||||
callback?.onSuccess(drawable)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
callback?.onError(Exception("Cannot open string"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
callback?.onError(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a correct IHDR from the IHDR chunk of the APNG.
|
* Generate a correct IHDR from the IHDR chunk of the APNG.
|
||||||
|
|
|
@ -0,0 +1,353 @@
|
||||||
|
package oupson.apng.decoder
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.drawable.AnimatedImageDrawable
|
||||||
|
import android.graphics.drawable.AnimationDrawable
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
|
import android.widget.ImageView
|
||||||
|
import androidx.annotation.RawRes
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import oupson.apng.drawable.ApngDrawable
|
||||||
|
import oupson.apng.utils.Loader
|
||||||
|
import java.io.ByteArrayInputStream
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileInputStream
|
||||||
|
import java.io.FileNotFoundException
|
||||||
|
import java.net.URL
|
||||||
|
|
||||||
|
class ApngLoader(parent: Job? = null) {
|
||||||
|
interface Callback {
|
||||||
|
/**
|
||||||
|
* Function called when the file was successfully decoded.
|
||||||
|
* @param drawable Can be an [ApngDrawable] if successful and an [AnimatedImageDrawable] if the image decoded is not an APNG but a gif. If it is not an animated image, it is a [Drawable].
|
||||||
|
*/
|
||||||
|
fun onSuccess(drawable: Drawable)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function called when something gone wrong.
|
||||||
|
* @param error The problem.
|
||||||
|
*/
|
||||||
|
fun onError(error: Exception)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val job = SupervisorJob(parent)
|
||||||
|
private val coroutineScope: CoroutineScope = CoroutineScope(job)
|
||||||
|
|
||||||
|
fun cancelAll() {
|
||||||
|
coroutineScope.cancel(CancellationException("Loading was canceled"))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load Apng into an imageView.
|
||||||
|
* @param context Context needed for animation drawable.
|
||||||
|
* @param file File to decode.
|
||||||
|
* @param imageView Image View.
|
||||||
|
* @param config Decoder configuration
|
||||||
|
*/
|
||||||
|
suspend fun decodeApngInto(
|
||||||
|
context: Context,
|
||||||
|
file: File,
|
||||||
|
imageView: ImageView,
|
||||||
|
config: ApngDecoder.Config = ApngDecoder.Config()
|
||||||
|
): Drawable {
|
||||||
|
val drawable =
|
||||||
|
ApngDecoder.decodeApng(
|
||||||
|
context,
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
FileInputStream(file)
|
||||||
|
},
|
||||||
|
config
|
||||||
|
)
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
imageView.setImageDrawable(drawable)
|
||||||
|
(drawable as? AnimationDrawable)?.start()
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
|
(drawable as? AnimatedImageDrawable)?.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return drawable
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load Apng into an imageView.
|
||||||
|
* @param context Context needed for animation drawable and content resolver.
|
||||||
|
* @param uri Uri to load.
|
||||||
|
* @param imageView Image View.
|
||||||
|
* @param config Decoder configuration
|
||||||
|
*/
|
||||||
|
suspend fun decodeApngInto(
|
||||||
|
context: Context,
|
||||||
|
uri: Uri,
|
||||||
|
imageView: ImageView,
|
||||||
|
config: ApngDecoder.Config = ApngDecoder.Config()
|
||||||
|
): Drawable {
|
||||||
|
val inputStream =
|
||||||
|
withContext(Dispatchers.IO) { context.contentResolver.openInputStream(uri) }
|
||||||
|
?: throw FileNotFoundException("Failed to load $uri") // TODO Better err ?
|
||||||
|
val drawable =
|
||||||
|
ApngDecoder.decodeApng(
|
||||||
|
context,
|
||||||
|
inputStream,
|
||||||
|
config
|
||||||
|
)
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
imageView.setImageDrawable(drawable)
|
||||||
|
(drawable as? AnimationDrawable)?.start()
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
|
(drawable as? AnimatedImageDrawable)?.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return drawable
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load Apng into an imageView.
|
||||||
|
* @param context Context needed to decode the resource and for the animation drawable.
|
||||||
|
* @param res Raw resource to load.
|
||||||
|
* @param imageView Image View.
|
||||||
|
* @param config Decoder configuration
|
||||||
|
*/
|
||||||
|
suspend fun decodeApngInto(
|
||||||
|
context: Context, @RawRes res: Int,
|
||||||
|
imageView: ImageView,
|
||||||
|
config: ApngDecoder.Config = ApngDecoder.Config()
|
||||||
|
): Drawable {
|
||||||
|
val drawable =
|
||||||
|
ApngDecoder.decodeApng(
|
||||||
|
context,
|
||||||
|
context.resources.openRawResource(res),
|
||||||
|
config
|
||||||
|
)
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
imageView.setImageDrawable(drawable)
|
||||||
|
(drawable as? AnimationDrawable)?.start()
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
|
(drawable as? AnimatedImageDrawable)?.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return drawable
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load Apng into an imageView, asynchronously.
|
||||||
|
* @param context Context needed for the animation drawable.
|
||||||
|
* @param url URL to load.
|
||||||
|
* @param imageView Image View.
|
||||||
|
* @param config Decoder configuration
|
||||||
|
*/
|
||||||
|
suspend fun decodeApngInto(
|
||||||
|
context: Context,
|
||||||
|
url: URL,
|
||||||
|
imageView: ImageView,
|
||||||
|
config: ApngDecoder.Config = ApngDecoder.Config()
|
||||||
|
): Drawable {
|
||||||
|
|
||||||
|
val drawable = ApngDecoder.decodeApng(
|
||||||
|
context,
|
||||||
|
ByteArrayInputStream(
|
||||||
|
Loader.load(
|
||||||
|
url
|
||||||
|
)
|
||||||
|
),
|
||||||
|
config
|
||||||
|
)
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
imageView.setImageDrawable(drawable)
|
||||||
|
(drawable as? AnimationDrawable)?.start()
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
|
(drawable as? AnimatedImageDrawable)?.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return drawable
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load Apng into an imageView, asynchronously.
|
||||||
|
* @param context Context needed for decoding the image and creating the animation drawable.
|
||||||
|
* @param string URL to load
|
||||||
|
* @param imageView Image View.
|
||||||
|
* @param config Decoder configuration
|
||||||
|
*/
|
||||||
|
@Suppress("unused")
|
||||||
|
suspend fun decodeApngInto(
|
||||||
|
context: Context,
|
||||||
|
string: String,
|
||||||
|
imageView: ImageView,
|
||||||
|
config: ApngDecoder.Config = ApngDecoder.Config()
|
||||||
|
): Drawable {
|
||||||
|
return if (string.startsWith("http://") || string.startsWith("https://")) {
|
||||||
|
decodeApngInto(
|
||||||
|
context,
|
||||||
|
URL(string),
|
||||||
|
imageView,
|
||||||
|
config
|
||||||
|
)
|
||||||
|
} else if (File(string).exists()) {
|
||||||
|
var pathToLoad =
|
||||||
|
if (string.startsWith("content://")) string else "file://$string"
|
||||||
|
pathToLoad = pathToLoad.replace("%", "%25").replace("#", "%23")
|
||||||
|
decodeApngInto(
|
||||||
|
context,
|
||||||
|
Uri.parse(pathToLoad),
|
||||||
|
imageView,
|
||||||
|
config
|
||||||
|
)
|
||||||
|
} else if (string.startsWith("file://android_asset/")) {
|
||||||
|
val drawable =
|
||||||
|
ApngDecoder.decodeApng(
|
||||||
|
context,
|
||||||
|
context.assets.open(string.replace("file:///android_asset/", "")),
|
||||||
|
|
||||||
|
config
|
||||||
|
)
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
imageView.setImageDrawable(drawable)
|
||||||
|
(drawable as? AnimationDrawable)?.start()
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
|
(drawable as? AnimatedImageDrawable)?.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
drawable
|
||||||
|
} else {
|
||||||
|
throw Exception("Cannot open string")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// region with callback
|
||||||
|
/**
|
||||||
|
* Load Apng into an imageView, asynchronously.
|
||||||
|
* @param context Context needed for animation drawable.
|
||||||
|
* @param file File to decode.
|
||||||
|
* @param imageView Image View.
|
||||||
|
* @param callback [ApngLoader.Callback] to handle success and error.
|
||||||
|
* @param config Decoder configuration
|
||||||
|
*/
|
||||||
|
@Suppress("unused", "BlockingMethodInNonBlockingContext")
|
||||||
|
@JvmOverloads
|
||||||
|
fun decodeApngAsyncInto(
|
||||||
|
context: Context,
|
||||||
|
file: File,
|
||||||
|
imageView: ImageView,
|
||||||
|
callback: Callback? = null,
|
||||||
|
config: ApngDecoder.Config = ApngDecoder.Config()
|
||||||
|
) =
|
||||||
|
coroutineScope.launch(Dispatchers.Default) {
|
||||||
|
try {
|
||||||
|
val drawable = decodeApngInto(context, file, imageView, config)
|
||||||
|
callback?.onSuccess(drawable)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
callback?.onError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load Apng into an imageView, asynchronously.
|
||||||
|
* @param context Context needed for animation drawable and content resolver.
|
||||||
|
* @param uri Uri to load.
|
||||||
|
* @param imageView Image View.
|
||||||
|
* @param callback [ApngLoader.Callback] to handle success and error.
|
||||||
|
* @param config Decoder configuration
|
||||||
|
*/
|
||||||
|
@Suppress("unused")
|
||||||
|
@JvmOverloads
|
||||||
|
fun decodeApngAsyncInto(
|
||||||
|
context: Context,
|
||||||
|
uri: Uri,
|
||||||
|
imageView: ImageView,
|
||||||
|
callback: Callback? = null,
|
||||||
|
config: ApngDecoder.Config = ApngDecoder.Config()
|
||||||
|
) = coroutineScope.launch(Dispatchers.Default) {
|
||||||
|
try {
|
||||||
|
val drawable = decodeApngInto(context, uri, imageView, config)
|
||||||
|
callback?.onSuccess(drawable)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
callback?.onError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load Apng into an imageView, asynchronously.
|
||||||
|
* @param context Context needed to decode the resource and for the animation drawable.
|
||||||
|
* @param res Raw resource to load.
|
||||||
|
* @param imageView Image View.
|
||||||
|
* @param callback [ApngLoader.Callback] to handle success and error.
|
||||||
|
* @param config Decoder configuration
|
||||||
|
*/
|
||||||
|
@Suppress("unused")
|
||||||
|
@JvmOverloads
|
||||||
|
fun decodeApngAsyncInto(
|
||||||
|
context: Context, @RawRes res: Int,
|
||||||
|
imageView: ImageView,
|
||||||
|
callback: Callback? = null,
|
||||||
|
config: ApngDecoder.Config = ApngDecoder.Config()
|
||||||
|
) = coroutineScope.launch(Dispatchers.Default) {
|
||||||
|
try {
|
||||||
|
val drawable = decodeApngInto(context, res, imageView, config)
|
||||||
|
callback?.onSuccess(drawable)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
callback?.onError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load Apng into an imageView, asynchronously.
|
||||||
|
* @param context Context needed for the animation drawable.
|
||||||
|
* @param url URL to load.
|
||||||
|
* @param imageView Image View.
|
||||||
|
* @param callback [ApngLoader.Callback] to handle success and error.
|
||||||
|
* @param config Decoder configuration
|
||||||
|
*/
|
||||||
|
@Suppress("unused", "BlockingMethodInNonBlockingContext")
|
||||||
|
@JvmOverloads
|
||||||
|
fun decodeApngAsyncInto(
|
||||||
|
context: Context,
|
||||||
|
url: URL,
|
||||||
|
imageView: ImageView,
|
||||||
|
callback: Callback? = null,
|
||||||
|
config: ApngDecoder.Config = ApngDecoder.Config()
|
||||||
|
) = coroutineScope.launch(Dispatchers.Default) {
|
||||||
|
try {
|
||||||
|
val drawable = decodeApngInto(context, url, imageView, config)
|
||||||
|
callback?.onSuccess(drawable)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
callback?.onError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load Apng into an imageView, asynchronously.
|
||||||
|
* @param context Context needed for decoding the image and creating the animation drawable.
|
||||||
|
* @param string URL to load
|
||||||
|
* @param imageView Image View.
|
||||||
|
* @param callback [ApngLoader.Callback] to handle success and error.
|
||||||
|
* @param config Decoder configuration
|
||||||
|
*/
|
||||||
|
@Suppress("unused")
|
||||||
|
@JvmOverloads
|
||||||
|
fun decodeApngAsyncInto(
|
||||||
|
context: Context,
|
||||||
|
string: String,
|
||||||
|
imageView: ImageView,
|
||||||
|
callback: Callback? = null,
|
||||||
|
config: ApngDecoder.Config = ApngDecoder.Config()
|
||||||
|
) =
|
||||||
|
coroutineScope.launch(Dispatchers.Default) {
|
||||||
|
try {
|
||||||
|
val drawable = decodeApngInto(context, string, imageView, config)
|
||||||
|
callback?.onSuccess(drawable)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
callback?.onError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion with callback
|
||||||
|
}
|
|
@ -11,13 +11,17 @@ import androidx.core.app.ActivityCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import kotlinx.android.synthetic.main.activity_viewer.*
|
import kotlinx.android.synthetic.main.activity_viewer.*
|
||||||
import oupson.apng.decoder.ApngDecoder
|
import oupson.apng.decoder.ApngDecoder
|
||||||
|
import oupson.apng.decoder.ApngLoader
|
||||||
import oupson.apngcreator.R
|
import oupson.apngcreator.R
|
||||||
|
|
||||||
class ViewerActivity : AppCompatActivity() {
|
class ViewerActivity : AppCompatActivity() {
|
||||||
|
private var apngLoader: ApngLoader? = null
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_viewer)
|
setContentView(R.layout.activity_viewer)
|
||||||
|
|
||||||
|
this.apngLoader = ApngLoader()
|
||||||
|
|
||||||
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||||
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||||
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||||
|
@ -50,16 +54,24 @@ class ViewerActivity : AppCompatActivity() {
|
||||||
|
|
||||||
private fun load() {
|
private fun load() {
|
||||||
val uri = intent.data ?: return
|
val uri = intent.data ?: return
|
||||||
ApngDecoder.decodeApngAsyncInto(this, uri, viewerImageView, callback = object : ApngDecoder.Callback {
|
apngLoader?.decodeApngAsyncInto(
|
||||||
override fun onSuccess(drawable: Drawable) {}
|
this,
|
||||||
override fun onError(error: Exception) {
|
uri,
|
||||||
Log.e("ViewerActivity", "Error when loading file", error)
|
viewerImageView,
|
||||||
}
|
callback = object : ApngLoader.Callback {
|
||||||
}, ApngDecoder.Config(decodeCoverFrame = false))
|
override fun onSuccess(drawable: Drawable) {}
|
||||||
|
override fun onError(error: Exception) {
|
||||||
|
Log.e("ViewerActivity", "Error when loading file", error)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ApngDecoder.Config(decodeCoverFrame = false)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRequestPermissionsResult(requestCode: Int,
|
override fun onRequestPermissionsResult(
|
||||||
permissions: Array<String>, grantResults: IntArray) {
|
requestCode: Int,
|
||||||
|
permissions: Array<String>, grantResults: IntArray
|
||||||
|
) {
|
||||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||||
when (requestCode) {
|
when (requestCode) {
|
||||||
2 -> {
|
2 -> {
|
||||||
|
|
|
@ -11,6 +11,7 @@ import android.view.ViewGroup
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import oupson.apng.decoder.ApngDecoder
|
import oupson.apng.decoder.ApngDecoder
|
||||||
|
import oupson.apng.decoder.ApngLoader
|
||||||
import oupson.apng.drawable.ApngDrawable
|
import oupson.apng.drawable.ApngDrawable
|
||||||
import oupson.apngcreator.BuildConfig
|
import oupson.apngcreator.BuildConfig
|
||||||
import oupson.apngcreator.R
|
import oupson.apngcreator.R
|
||||||
|
@ -19,29 +20,40 @@ import java.net.URL
|
||||||
class ApngDecoderFragment : Fragment() {
|
class ApngDecoderFragment : Fragment() {
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "ApngDecoderFragment"
|
private const val TAG = "ApngDecoderFragment"
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun newInstance() =
|
fun newInstance() =
|
||||||
ApngDecoderFragment()
|
ApngDecoderFragment()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var apngLoader: ApngLoader? = null
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater, container: ViewGroup?,
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View? {
|
): View? {
|
||||||
val view = inflater.inflate(R.layout.fragment_apng_decoder, container, false)
|
val view = inflater.inflate(R.layout.fragment_apng_decoder, container, false)
|
||||||
|
|
||||||
val imageView : ImageView = view.findViewById(R.id.apngDecoderImageView) ?: return view
|
val imageView: ImageView = view.findViewById(R.id.apngDecoderImageView) ?: return view
|
||||||
|
|
||||||
|
apngLoader = ApngLoader()
|
||||||
|
|
||||||
if (context != null) {
|
if (context != null) {
|
||||||
ApngDecoder.decodeApngAsyncInto(
|
apngLoader?.decodeApngAsyncInto(
|
||||||
this.requireContext(),
|
this.requireContext(),
|
||||||
URL("https://metagif.files.wordpress.com/2015/01/bugbuckbunny.png"),
|
URL("https://metagif.files.wordpress.com/2015/01/bugbuckbunny.png"),
|
||||||
imageView,
|
imageView,
|
||||||
config = ApngDecoder.Config(bitmapConfig = Bitmap.Config.RGB_565, decodeCoverFrame = true),
|
config = ApngDecoder.Config(
|
||||||
callback = object : ApngDecoder.Callback {
|
bitmapConfig = Bitmap.Config.RGB_565,
|
||||||
|
decodeCoverFrame = true
|
||||||
|
),
|
||||||
|
callback = object : ApngLoader.Callback {
|
||||||
override fun onSuccess(drawable: Drawable) {
|
override fun onSuccess(drawable: Drawable) {
|
||||||
if (BuildConfig.DEBUG)
|
if (BuildConfig.DEBUG)
|
||||||
Log.i(TAG, "onSuccess(), has cover frame : ${(drawable as? ApngDrawable)?.coverFrame != null}")
|
Log.i(
|
||||||
|
TAG,
|
||||||
|
"onSuccess(), has cover frame : ${(drawable as? ApngDrawable)?.coverFrame != null}"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onError(error: Exception) {
|
override fun onError(error: Exception) {
|
||||||
|
|
|
@ -16,6 +16,7 @@ import androidx.fragment.app.Fragment;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import oupson.apng.decoder.ApngDecoder;
|
import oupson.apng.decoder.ApngDecoder;
|
||||||
|
import oupson.apng.decoder.ApngLoader;
|
||||||
import oupson.apngcreator.BuildConfig;
|
import oupson.apngcreator.BuildConfig;
|
||||||
import oupson.apngcreator.R;
|
import oupson.apngcreator.R;
|
||||||
|
|
||||||
|
@ -23,6 +24,8 @@ import oupson.apngcreator.R;
|
||||||
public class JavaFragment extends Fragment {
|
public class JavaFragment extends Fragment {
|
||||||
private static final String TAG = "JavaActivity";
|
private static final String TAG = "JavaActivity";
|
||||||
|
|
||||||
|
private ApngLoader apngLoader = null;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
|
@ -36,10 +39,12 @@ public class JavaFragment extends Fragment {
|
||||||
|
|
||||||
Context context = this.getContext();
|
Context context = this.getContext();
|
||||||
|
|
||||||
|
this.apngLoader = new ApngLoader();
|
||||||
|
|
||||||
if (imageView != null && context != null) {
|
if (imageView != null && context != null) {
|
||||||
if (BuildConfig.DEBUG)
|
if (BuildConfig.DEBUG)
|
||||||
Log.v(TAG, "Loading " + imageUrl);
|
Log.v(TAG, "Loading " + imageUrl);
|
||||||
ApngDecoder.decodeApngAsyncInto(context, imageUrl, imageView, new ApngDecoder.Callback() {
|
this.apngLoader.decodeApngAsyncInto(context, imageUrl, imageView, new ApngLoader.Callback() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(@NotNull Drawable drawable) {
|
public void onSuccess(@NotNull Drawable drawable) {
|
||||||
if (BuildConfig.DEBUG)
|
if (BuildConfig.DEBUG)
|
||||||
|
@ -56,4 +61,11 @@ public class JavaFragment extends Fragment {
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStop() {
|
||||||
|
super.onStop();
|
||||||
|
|
||||||
|
apngLoader.cancelAll();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ import android.widget.SeekBar
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import com.squareup.picasso.Picasso
|
import com.squareup.picasso.Picasso
|
||||||
import kotlinx.android.synthetic.main.activity_creator.*
|
import kotlinx.android.synthetic.main.activity_creator.*
|
||||||
import oupson.apng.decoder.ApngDecoder
|
import oupson.apng.decoder.ApngLoader
|
||||||
import oupson.apng.drawable.ApngDrawable
|
import oupson.apng.drawable.ApngDrawable
|
||||||
import oupson.apngcreator.BuildConfig
|
import oupson.apngcreator.BuildConfig
|
||||||
import oupson.apngcreator.R
|
import oupson.apngcreator.R
|
||||||
|
@ -22,22 +22,23 @@ import oupson.apngcreator.R
|
||||||
class KotlinFragment : Fragment() {
|
class KotlinFragment : Fragment() {
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "KotlinFragment"
|
private const val TAG = "KotlinFragment"
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun newInstance() =
|
fun newInstance() =
|
||||||
KotlinFragment()
|
KotlinFragment()
|
||||||
}
|
}
|
||||||
|
|
||||||
private var apngImageView : ImageView? = null
|
private var apngImageView: ImageView? = null
|
||||||
private var normalImageView : ImageView? = null
|
private var normalImageView: ImageView? = null
|
||||||
|
|
||||||
private var pauseButton : Button? = null
|
private var pauseButton: Button? = null
|
||||||
private var playButton : Button? = null
|
private var playButton: Button? = null
|
||||||
|
|
||||||
private var speedSeekBar : SeekBar? = null
|
private var speedSeekBar: SeekBar? = null
|
||||||
|
|
||||||
//private var animator : ApngAnimator? = null
|
//private var animator : ApngAnimator? = null
|
||||||
private var animation : ApngDrawable? = null
|
private var animation: ApngDrawable? = null
|
||||||
private var durations : IntArray? = null
|
private var durations: IntArray? = null
|
||||||
|
|
||||||
private var frameIndex = 0
|
private var frameIndex = 0
|
||||||
|
|
||||||
|
@ -51,6 +52,8 @@ class KotlinFragment : Fragment() {
|
||||||
)
|
)
|
||||||
private val selected = 4
|
private val selected = 4
|
||||||
|
|
||||||
|
private var apngLoader: ApngLoader? = null
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater, container: ViewGroup?,
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
|
@ -60,6 +63,8 @@ class KotlinFragment : Fragment() {
|
||||||
|
|
||||||
val view = inflater.inflate(R.layout.fragment_kotlin, container, false)
|
val view = inflater.inflate(R.layout.fragment_kotlin, container, false)
|
||||||
|
|
||||||
|
apngLoader = ApngLoader()
|
||||||
|
|
||||||
apngImageView = view.findViewById(R.id.ApngImageView)
|
apngImageView = view.findViewById(R.id.ApngImageView)
|
||||||
normalImageView = view.findViewById(R.id.NormalImageView)
|
normalImageView = view.findViewById(R.id.NormalImageView)
|
||||||
|
|
||||||
|
@ -126,7 +131,10 @@ class KotlinFragment : Fragment() {
|
||||||
res.coverFrame = animation.coverFrame
|
res.coverFrame = animation.coverFrame
|
||||||
|
|
||||||
for (i in 0 until animation.numberOfFrames) {
|
for (i in 0 until animation.numberOfFrames) {
|
||||||
res.addFrame(animation.getFrame(i), (durations!![i].toFloat() / speed).toInt())
|
res.addFrame(
|
||||||
|
animation.getFrame(i),
|
||||||
|
(durations!![i].toFloat() / speed).toInt()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
apngImageView?.setImageDrawable(res)
|
apngImageView?.setImageDrawable(res)
|
||||||
|
@ -138,11 +146,11 @@ class KotlinFragment : Fragment() {
|
||||||
})
|
})
|
||||||
|
|
||||||
if (animation == null) {
|
if (animation == null) {
|
||||||
ApngDecoder.decodeApngAsyncInto(
|
apngLoader?.decodeApngAsyncInto(
|
||||||
requireContext(),
|
requireContext(),
|
||||||
imageUrls[selected],
|
imageUrls[selected],
|
||||||
apngImageView!!,
|
apngImageView!!,
|
||||||
callback = object : ApngDecoder.Callback {
|
callback = object : ApngLoader.Callback {
|
||||||
override fun onSuccess(drawable: Drawable) {
|
override fun onSuccess(drawable: Drawable) {
|
||||||
animation = (drawable as? ApngDrawable)
|
animation = (drawable as? ApngDrawable)
|
||||||
durations = IntArray(animation?.numberOfFrames ?: 0) { i ->
|
durations = IntArray(animation?.numberOfFrames ?: 0) { i ->
|
||||||
|
|
Loading…
Reference in New Issue