Revert
This commit is contained in:
parent
a086e06811
commit
7d7b02f2b9
|
@ -13,6 +13,7 @@ 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
|
||||||
|
import oupson.apng.decoder.ApngDecoder.Companion.decodeApng
|
||||||
import oupson.apng.drawable.ApngDrawable
|
import oupson.apng.drawable.ApngDrawable
|
||||||
import oupson.apng.exceptions.BadApngException
|
import oupson.apng.exceptions.BadApngException
|
||||||
import oupson.apng.exceptions.BadCRCException
|
import oupson.apng.exceptions.BadCRCException
|
||||||
|
@ -60,7 +61,9 @@ class ApngDecoder {
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isDecodingCoverFrame(): Boolean = this.decodeCoverFrame
|
fun isDecodingCoverFrame() : Boolean {
|
||||||
|
return this.decodeCoverFrame
|
||||||
|
}
|
||||||
fun setIsDecodingCoverFrame(decodeCoverFrame : Boolean) : Config {
|
fun setIsDecodingCoverFrame(decodeCoverFrame : Boolean) : Config {
|
||||||
this.decodeCoverFrame = decodeCoverFrame
|
this.decodeCoverFrame = decodeCoverFrame
|
||||||
return this
|
return this
|
||||||
|
@ -80,289 +83,6 @@ 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")
|
|
||||||
@JvmStatic
|
|
||||||
@JvmOverloads
|
|
||||||
fun decodeApngAsyncInto(
|
|
||||||
context: Context,
|
|
||||||
file: File,
|
|
||||||
imageView: ImageView,
|
|
||||||
callback: Callback? = null,
|
|
||||||
config: Config = Config(),
|
|
||||||
scope: CoroutineScope = GlobalScope
|
|
||||||
) {
|
|
||||||
scope.launch(Dispatchers.IO) {
|
|
||||||
try {
|
|
||||||
val drawable =
|
|
||||||
ApngDecoder().decodeApng(
|
|
||||||
context,
|
|
||||||
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.IO) {
|
|
||||||
try {
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
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.IO) {
|
|
||||||
try {
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
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")
|
|
||||||
@JvmStatic
|
|
||||||
@JvmOverloads
|
|
||||||
fun decodeApngAsyncInto(
|
|
||||||
context: Context,
|
|
||||||
url: URL,
|
|
||||||
imageView: ImageView,
|
|
||||||
callback: Callback? = null,
|
|
||||||
config: Config = Config(),
|
|
||||||
scope: CoroutineScope = GlobalScope
|
|
||||||
) {
|
|
||||||
scope.launch(Dispatchers.IO) {
|
|
||||||
try {
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
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.IO) {
|
|
||||||
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 =
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
callback?.onSuccess(drawable)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
callback?.onError(java.lang.Exception("Cannot open string"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
callback?.onError(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a correct IHDR from the IHDR chunk of the APNG.
|
|
||||||
* @param ihdrOfApng The IHDR of the APNG.
|
|
||||||
* @param width The width of the frame.
|
|
||||||
* @param height The height of the frame.
|
|
||||||
* @return [ByteArray] The generated IHDR.
|
|
||||||
*/
|
|
||||||
private fun generateIhdr(ihdrOfApng: ByteArray, width: Int, height: Int): ByteArray {
|
|
||||||
val ihdr =
|
|
||||||
ByteArray(0xD + 4 + 4 + 4) // 0xD (IHDR body length) + 4 (0x0, 0x0, 0x0, 0xD : the chunk length) + 4 : IHDR + 4 : CRC
|
|
||||||
|
|
||||||
// Add chunk body length
|
|
||||||
System.arraycopy(Utils.uIntToByteArray(0xD), 0, ihdr, 0, 4)
|
|
||||||
|
|
||||||
// We need a body var to know body length and generate crc
|
|
||||||
val ihdrBody = ByteArray(0xD + 4) // 0xD (IHDR body length) + 4 : IHDR
|
|
||||||
|
|
||||||
// Add IHDR
|
|
||||||
System.arraycopy(Utils.IHDR, 0, ihdrBody, 0, 4)
|
|
||||||
|
|
||||||
// Add the max width and height
|
|
||||||
System.arraycopy(Utils.uIntToByteArray(width), 0, ihdrBody, 4, 4)
|
|
||||||
System.arraycopy(Utils.uIntToByteArray(height), 0, ihdrBody, 8, 4)
|
|
||||||
|
|
||||||
// Add complicated stuff like depth color ...
|
|
||||||
// If you want correct png you need same parameters.
|
|
||||||
System.arraycopy(ihdrOfApng, 8, ihdrBody, 12, 5)
|
|
||||||
|
|
||||||
// Generate CRC
|
|
||||||
val crC32 = CRC32()
|
|
||||||
crC32.update(ihdrBody, 0, 0xD + 4)
|
|
||||||
|
|
||||||
System.arraycopy(ihdrBody, 0, ihdr, 4, 0xD + 4)
|
|
||||||
System.arraycopy(Utils.uIntToByteArray(crC32.value.toInt()), 0, ihdr, 0xD + 4 + 4, 4)
|
|
||||||
return ihdr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decode Apng and return a Drawable who can be an [ApngDrawable] if it end successfully. Can also be an [android.graphics.drawable.AnimatedImageDrawable].
|
* Decode Apng and return a Drawable who can be an [ApngDrawable] if it end successfully. Can also be an [android.graphics.drawable.AnimatedImageDrawable].
|
||||||
* @param context Context needed for the animation drawable
|
* @param context Context needed for the animation drawable
|
||||||
|
@ -372,6 +92,7 @@ class ApngDecoder {
|
||||||
*/
|
*/
|
||||||
// TODO DOCUMENT CONFIG
|
// TODO DOCUMENT CONFIG
|
||||||
@Suppress("MemberVisibilityCanBePrivate")
|
@Suppress("MemberVisibilityCanBePrivate")
|
||||||
|
@JvmStatic
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
fun decodeApng(
|
fun decodeApng(
|
||||||
context: Context,
|
context: Context,
|
||||||
|
@ -821,6 +542,7 @@ class ApngDecoder {
|
||||||
* @return [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].
|
* @return [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].
|
||||||
*/
|
*/
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
|
@JvmStatic
|
||||||
// TODO DOCUMENT
|
// TODO DOCUMENT
|
||||||
fun decodeApng(
|
fun decodeApng(
|
||||||
context: Context,
|
context: Context,
|
||||||
|
@ -840,6 +562,7 @@ class ApngDecoder {
|
||||||
* @return [ApngDrawable] if successful and an [AnimatedImageDrawable] if the image decoded is not an APNG but a gif.
|
* @return [ApngDrawable] if successful and an [AnimatedImageDrawable] if the image decoded is not an APNG but a gif.
|
||||||
*/
|
*/
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
|
@JvmStatic
|
||||||
fun decodeApng(
|
fun decodeApng(
|
||||||
context: Context,
|
context: Context,
|
||||||
uri: Uri,
|
uri: Uri,
|
||||||
|
@ -861,6 +584,7 @@ class ApngDecoder {
|
||||||
* @return [ApngDrawable] if successful and an [AnimatedImageDrawable] if the image decoded is not an APNG but a gif.
|
* @return [ApngDrawable] if successful and an [AnimatedImageDrawable] if the image decoded is not an APNG but a gif.
|
||||||
*/
|
*/
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
|
@JvmStatic
|
||||||
fun decodeApng(
|
fun decodeApng(
|
||||||
context: Context,
|
context: Context,
|
||||||
@RawRes res: Int,
|
@RawRes res: Int,
|
||||||
|
@ -880,6 +604,7 @@ class ApngDecoder {
|
||||||
* @return [ApngDrawable] if successful and an [AnimatedImageDrawable] if the image decoded is not an APNG but a gif.
|
* @return [ApngDrawable] if successful and an [AnimatedImageDrawable] if the image decoded is not an APNG but a gif.
|
||||||
*/
|
*/
|
||||||
@Suppress("unused", "BlockingMethodInNonBlockingContext")
|
@Suppress("unused", "BlockingMethodInNonBlockingContext")
|
||||||
|
@JvmStatic
|
||||||
suspend fun decodeApng(
|
suspend fun decodeApng(
|
||||||
context: Context,
|
context: Context,
|
||||||
url: URL,
|
url: URL,
|
||||||
|
@ -892,4 +617,287 @@ class ApngDecoder {
|
||||||
config
|
config
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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")
|
||||||
|
@JvmStatic
|
||||||
|
@JvmOverloads
|
||||||
|
fun decodeApngAsyncInto(
|
||||||
|
context: Context,
|
||||||
|
file: File,
|
||||||
|
imageView: ImageView,
|
||||||
|
callback: Callback? = null,
|
||||||
|
config: Config = Config(),
|
||||||
|
scope : CoroutineScope = GlobalScope
|
||||||
|
) {
|
||||||
|
scope.launch(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
val drawable =
|
||||||
|
decodeApng(
|
||||||
|
context,
|
||||||
|
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.IO) {
|
||||||
|
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.IO) {
|
||||||
|
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")
|
||||||
|
@JvmStatic
|
||||||
|
@JvmOverloads
|
||||||
|
fun decodeApngAsyncInto(
|
||||||
|
context: Context,
|
||||||
|
url: URL,
|
||||||
|
imageView: ImageView,
|
||||||
|
callback: Callback? = null,
|
||||||
|
config: Config = Config(),
|
||||||
|
scope : CoroutineScope = GlobalScope
|
||||||
|
) {
|
||||||
|
scope.launch(Dispatchers.IO) {
|
||||||
|
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.IO) {
|
||||||
|
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(java.lang.Exception("Cannot open string"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
callback?.onError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a correct IHDR from the IHDR chunk of the APNG.
|
||||||
|
* @param ihdrOfApng The IHDR of the APNG.
|
||||||
|
* @param width The width of the frame.
|
||||||
|
* @param height The height of the frame.
|
||||||
|
* @return [ByteArray] The generated IHDR.
|
||||||
|
*/
|
||||||
|
private fun generateIhdr(ihdrOfApng: ByteArray, width: Int, height: Int): ByteArray {
|
||||||
|
val ihdr =
|
||||||
|
ByteArray(0xD + 4 + 4 + 4) // 0xD (IHDR body length) + 4 (0x0, 0x0, 0x0, 0xD : the chunk length) + 4 : IHDR + 4 : CRC
|
||||||
|
|
||||||
|
// Add chunk body length
|
||||||
|
System.arraycopy(Utils.uIntToByteArray(0xD), 0, ihdr, 0, 4)
|
||||||
|
|
||||||
|
// We need a body var to know body length and generate crc
|
||||||
|
val ihdrBody = ByteArray(0xD + 4) // 0xD (IHDR body length) + 4 : IHDR
|
||||||
|
|
||||||
|
// Add IHDR
|
||||||
|
System.arraycopy(Utils.IHDR, 0, ihdrBody, 0, 4)
|
||||||
|
|
||||||
|
// Add the max width and height
|
||||||
|
System.arraycopy(Utils.uIntToByteArray(width), 0, ihdrBody, 4, 4)
|
||||||
|
System.arraycopy(Utils.uIntToByteArray(height), 0, ihdrBody, 8, 4)
|
||||||
|
|
||||||
|
// Add complicated stuff like depth color ...
|
||||||
|
// If you want correct png you need same parameters.
|
||||||
|
System.arraycopy(ihdrOfApng, 8, ihdrBody, 12, 5)
|
||||||
|
|
||||||
|
// Generate CRC
|
||||||
|
val crC32 = CRC32()
|
||||||
|
crC32.update(ihdrBody, 0, 0xD + 4)
|
||||||
|
|
||||||
|
System.arraycopy(ihdrBody, 0, ihdr, 4, 0xD + 4)
|
||||||
|
System.arraycopy(Utils.uIntToByteArray(crC32.value.toInt()), 0, ihdr, 0xD + 4 + 4, 4)
|
||||||
|
return ihdr
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue