New config class

This commit is contained in:
Oupson 2021-02-24 12:00:21 +01:00
parent d4924627cf
commit 6b90282056
3 changed files with 46 additions and 54 deletions

View File

@ -47,6 +47,12 @@ class ApngDecoder {
fun onError(error: Exception) fun onError(error: Exception)
} }
data class Config(
val speed: Float = 1f,
val bitmapConfig: Bitmap.Config = Bitmap.Config.ARGB_8888,
val decodeCoverFrame: Boolean = true
)
companion object { companion object {
private const val TAG = "ApngDecoder" private const val TAG = "ApngDecoder"
private val zeroLength = byteArrayOf(0x00, 0x00, 0x00, 0x00) private val zeroLength = byteArrayOf(0x00, 0x00, 0x00, 0x00)
@ -68,15 +74,14 @@ class ApngDecoder {
* @param config Configuration applied to the bitmap added to the animation. Please note that the frame is decoded in ARGB_8888 and converted after, for the buffer. * @param config Configuration applied to the bitmap added to the animation. Please note that the frame is decoded in ARGB_8888 and converted after, for the buffer.
* @return [AnimationDrawable] 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 [AnimationDrawable] 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].
*/ */
// TODO BETTER CONFIG (Maybe data class with speed, config, and a settings to ignore cover frame ?) // TODO DOCUMENT CONFIG
@Suppress("MemberVisibilityCanBePrivate") @Suppress("MemberVisibilityCanBePrivate")
@JvmStatic @JvmStatic
@JvmOverloads @JvmOverloads
fun decodeApng( fun decodeApng(
context: Context, context: Context,
inStream: InputStream, inStream: InputStream,
speed: Float = 1f, config: Config = Config()
config: Bitmap.Config = Bitmap.Config.ARGB_8888
): Drawable { ): Drawable {
val inputStream = BufferedInputStream(inStream) val inputStream = BufferedInputStream(inStream)
val bytes = ByteArray(8) val bytes = ByteArray(8)
@ -127,21 +132,24 @@ class ApngDecoder {
when { when {
name.contentEquals(Utils.fcTL) -> { name.contentEquals(Utils.fcTL) -> {
if (png == null) { if (png == null) {
drawable.coverFrame = cover?.let { if (config.decodeCoverFrame) {
it.write(zeroLength) drawable.coverFrame = cover?.let {
// Generate crc for IEND it.write(zeroLength)
val crC32 = CRC32() // Generate crc for IEND
crC32.update(Utils.IEND, 0, Utils.IEND.size) val crC32 = CRC32()
it.write(Utils.IEND) crC32.update(Utils.IEND, 0, Utils.IEND.size)
it.write(Utils.uIntToByteArray(crC32.value.toInt())) it.write(Utils.IEND)
it.write(Utils.uIntToByteArray(crC32.value.toInt()))
val pngBytes = it.toByteArray() val pngBytes = it.toByteArray()
BitmapFactory.decodeByteArray( BitmapFactory.decodeByteArray(
pngBytes, pngBytes,
0, 0,
pngBytes.size pngBytes.size
) )
}
} }
cover = null
} else { } else {
// Add IEND body length : 0 // Add IEND body length : 0
png.write(zeroLength) png.write(zeroLength)
@ -187,18 +195,18 @@ class ApngDecoder {
drawable.addFrame( drawable.addFrame(
BitmapDrawable( BitmapDrawable(
context.resources, context.resources,
if (btm.config != config) { if (btm.config != config.bitmapConfig) {
if (BuildConfig.DEBUG) if (BuildConfig.DEBUG)
Log.v( Log.v(
TAG, TAG,
"Bitmap Config : ${btm.config}, Config : $config" "Bitmap Config : ${btm.config}, Config : $config"
) )
btm.copy(config, btm.isMutable) btm.copy(config.bitmapConfig, btm.isMutable)
} else { } else {
btm btm
} }
), ),
(delay / speed).toInt() (delay / config.speed).toInt()
) )
when (disposeOp) { when (disposeOp) {
@ -337,18 +345,18 @@ class ApngDecoder {
drawable.addFrame( drawable.addFrame(
BitmapDrawable( BitmapDrawable(
context.resources, context.resources,
if (btm.config != config) { if (btm.config != config.bitmapConfig) {
if (BuildConfig.DEBUG) if (BuildConfig.DEBUG)
Log.v( Log.v(
TAG, TAG,
"Bitmap Config : ${btm.config}, Config : $config" "Bitmap Config : ${btm.config}, Config : $config"
) )
btm.copy(config, btm.isMutable) btm.copy(config.bitmapConfig, btm.isMutable)
} else { } else {
btm btm
} }
), ),
(delay / speed).toInt() (delay / config.speed).toInt()
) )
when (disposeOp) { when (disposeOp) {
@ -513,15 +521,15 @@ class ApngDecoder {
*/ */
@Suppress("unused") @Suppress("unused")
@JvmStatic @JvmStatic
// TODO DOCUMENT
fun decodeApng( fun decodeApng(
context: Context, context: Context,
file: File, file: File,
speed: Float = 1f, config: Config = Config()
config: Bitmap.Config = Bitmap.Config.ARGB_8888
): Drawable = ): Drawable =
decodeApng( decodeApng(
context, context,
FileInputStream(file), speed, config FileInputStream(file), config
) )
/** /**
@ -537,14 +545,12 @@ class ApngDecoder {
fun decodeApng( fun decodeApng(
context: Context, context: Context,
uri: Uri, uri: Uri,
speed: Float = 1f, config: Config = Config()
config: Bitmap.Config = Bitmap.Config.ARGB_8888
): Drawable { ): Drawable {
val inputStream = context.contentResolver.openInputStream(uri)!! val inputStream = context.contentResolver.openInputStream(uri)!!
return decodeApng( return decodeApng(
context, context,
inputStream, inputStream,
speed,
config config
) )
} }
@ -562,13 +568,11 @@ class ApngDecoder {
fun decodeApng( fun decodeApng(
context: Context, context: Context,
@RawRes res: Int, @RawRes res: Int,
speed: Float = 1f, config: Config = Config()
config: Bitmap.Config = Bitmap.Config.ARGB_8888
): Drawable = ): Drawable =
decodeApng( decodeApng(
context, context,
context.resources.openRawResource(res), context.resources.openRawResource(res),
speed,
config config
) )
@ -585,14 +589,12 @@ class ApngDecoder {
suspend fun decodeApng( suspend fun decodeApng(
context: Context, context: Context,
url: URL, url: URL,
speed: Float = 1f, config: Config = Config()
config: Bitmap.Config = Bitmap.Config.ARGB_8888
) = ) =
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
decodeApng( decodeApng(
context, context,
ByteArrayInputStream(Loader.load(url)), ByteArrayInputStream(Loader.load(url)),
speed,
config config
) )
} }
@ -613,9 +615,8 @@ class ApngDecoder {
context: Context, context: Context,
file: File, file: File,
imageView: ImageView, imageView: ImageView,
speed: Float = 1f,
callback: Callback? = null, callback: Callback? = null,
config: Bitmap.Config = Bitmap.Config.ARGB_8888 config: Config = Config()
) { ) {
GlobalScope.launch(Dispatchers.IO) { GlobalScope.launch(Dispatchers.IO) {
try { try {
@ -623,7 +624,6 @@ class ApngDecoder {
decodeApng( decodeApng(
context, context,
FileInputStream(file), FileInputStream(file),
speed,
config config
) )
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
@ -658,9 +658,8 @@ class ApngDecoder {
context: Context, context: Context,
uri: Uri, uri: Uri,
imageView: ImageView, imageView: ImageView,
speed: Float = 1f,
callback: Callback? = null, callback: Callback? = null,
config: Bitmap.Config = Bitmap.Config.ARGB_8888 config: Config = Config()
) { ) {
val inputStream = context.contentResolver.openInputStream(uri)!! val inputStream = context.contentResolver.openInputStream(uri)!!
GlobalScope.launch(Dispatchers.IO) { GlobalScope.launch(Dispatchers.IO) {
@ -669,7 +668,6 @@ class ApngDecoder {
decodeApng( decodeApng(
context, context,
inputStream, inputStream,
speed,
config config
) )
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
@ -703,9 +701,8 @@ class ApngDecoder {
fun decodeApngAsyncInto( fun decodeApngAsyncInto(
context: Context, @RawRes res: Int, context: Context, @RawRes res: Int,
imageView: ImageView, imageView: ImageView,
speed: Float = 1f,
callback: Callback? = null, callback: Callback? = null,
config: Bitmap.Config = Bitmap.Config.ARGB_8888 config: Config = Config()
) { ) {
GlobalScope.launch(Dispatchers.IO) { GlobalScope.launch(Dispatchers.IO) {
try { try {
@ -713,7 +710,6 @@ class ApngDecoder {
decodeApng( decodeApng(
context, context,
context.resources.openRawResource(res), context.resources.openRawResource(res),
speed,
config config
) )
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
@ -749,9 +745,8 @@ class ApngDecoder {
context: Context, context: Context,
url: URL, url: URL,
imageView: ImageView, imageView: ImageView,
speed: Float = 1f,
callback: Callback? = null, callback: Callback? = null,
config: Bitmap.Config = Bitmap.Config.ARGB_8888 config: Config = Config()
) { ) {
GlobalScope.launch(Dispatchers.IO) { GlobalScope.launch(Dispatchers.IO) {
try { try {
@ -762,7 +757,6 @@ class ApngDecoder {
url url
) )
), ),
speed,
config config
) )
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
@ -797,9 +791,8 @@ class ApngDecoder {
context: Context, context: Context,
string: String, string: String,
imageView: ImageView, imageView: ImageView,
speed: Float = 1f,
callback: Callback? = null, callback: Callback? = null,
config: Bitmap.Config = Bitmap.Config.ARGB_8888 config: Config = Config()
) { ) {
GlobalScope.launch(Dispatchers.IO) { GlobalScope.launch(Dispatchers.IO) {
try { try {
@ -808,7 +801,6 @@ class ApngDecoder {
context, context,
URL(string), URL(string),
imageView, imageView,
speed,
callback, callback,
config config
) )
@ -820,7 +812,6 @@ class ApngDecoder {
context, context,
Uri.parse(pathToLoad), Uri.parse(pathToLoad),
imageView, imageView,
speed,
callback, callback,
config config
) )
@ -829,7 +820,7 @@ class ApngDecoder {
decodeApng( decodeApng(
context, context,
context.assets.open(string.replace("file:///android_asset/", "")), context.assets.open(string.replace("file:///android_asset/", "")),
speed,
config config
) )
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
@ -861,7 +852,8 @@ class ApngDecoder {
* @return [ByteArray] The generated IHDR. * @return [ByteArray] The generated IHDR.
*/ */
private fun generateIhdr(ihdrOfApng: ByteArray, width: Int, height: Int): ByteArray { 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 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 // Add chunk body length
System.arraycopy(Utils.uIntToByteArray(0xD), 0, ihdr, 0, 4) System.arraycopy(Utils.uIntToByteArray(0xD), 0, ihdr, 0, 4)

View File

@ -37,7 +37,7 @@ class ApngDecoderFragment : Fragment() {
this.context!!, this.context!!,
URL("https://metagif.files.wordpress.com/2015/01/bugbuckbunny.png"), URL("https://metagif.files.wordpress.com/2015/01/bugbuckbunny.png"),
imageView, imageView,
config = Bitmap.Config.RGB_565, config = ApngDecoder.Config(bitmapConfig = Bitmap.Config.RGB_565, decodeCoverFrame = false),
callback = object : ApngDecoder.Callback { callback = object : ApngDecoder.Callback {
override fun onSuccess(drawable: Drawable) { override fun onSuccess(drawable: Drawable) {
if (BuildConfig.DEBUG) if (BuildConfig.DEBUG)

View File

@ -39,7 +39,7 @@ public class JavaFragment extends Fragment {
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, 1f, new ApngDecoder.Callback() { ApngDecoder.decodeApngAsyncInto(context, imageUrl, imageView, new ApngDecoder.Callback() {
@Override @Override
public void onSuccess(@NotNull Drawable drawable) { public void onSuccess(@NotNull Drawable drawable) {
if (BuildConfig.DEBUG) if (BuildConfig.DEBUG)