Work on ApngDecoder

This commit is contained in:
Oupson 2021-06-25 13:31:35 +02:00
parent af21d501f6
commit 4ef644632a
5 changed files with 683 additions and 685 deletions

File diff suppressed because it is too large Load Diff

View File

@ -10,8 +10,6 @@ import android.widget.ImageView
import androidx.annotation.RawRes import androidx.annotation.RawRes
import kotlinx.coroutines.* import kotlinx.coroutines.*
import oupson.apng.drawable.ApngDrawable import oupson.apng.drawable.ApngDrawable
import oupson.apng.utils.Loader
import java.io.ByteArrayInputStream
import java.io.File import java.io.File
import java.io.FileInputStream import java.io.FileInputStream
import java.io.FileNotFoundException import java.io.FileNotFoundException
@ -29,7 +27,7 @@ class ApngLoader(parent: Job? = null) {
* Function called when something gone wrong. * Function called when something gone wrong.
* @param error The problem. * @param error The problem.
*/ */
fun onError(error: Exception) fun onError(error: Throwable)
} }
private val job = SupervisorJob(parent) private val job = SupervisorJob(parent)
@ -52,23 +50,26 @@ class ApngLoader(parent: Job? = null) {
file: File, file: File,
imageView: ImageView, imageView: ImageView,
config: ApngDecoder.Config = ApngDecoder.Config() config: ApngDecoder.Config = ApngDecoder.Config()
): Drawable { ): Result<Drawable> {
val drawable = val result =
ApngDecoder.decodeApng( ApngDecoder(
context,
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
FileInputStream(file) FileInputStream(file)
}, },
config config
) ).getDecoded(context)
withContext(Dispatchers.Main) {
imageView.setImageDrawable(drawable) if (result.isSuccess) {
(drawable as? AnimationDrawable)?.start() withContext(Dispatchers.Main) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { val drawable = result.getOrNull()
(drawable as? AnimatedImageDrawable)?.start() imageView.setImageDrawable(drawable)
(drawable as? AnimationDrawable)?.start()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
(drawable as? AnimatedImageDrawable)?.start()
}
} }
} }
return drawable return result
} }
/** /**
@ -83,25 +84,27 @@ class ApngLoader(parent: Job? = null) {
uri: Uri, uri: Uri,
imageView: ImageView, imageView: ImageView,
config: ApngDecoder.Config = ApngDecoder.Config() config: ApngDecoder.Config = ApngDecoder.Config()
): Drawable { ): Result<Drawable> {
val inputStream = val inputStream =
withContext(Dispatchers.IO) { context.contentResolver.openInputStream(uri) } withContext(Dispatchers.IO) { context.contentResolver.openInputStream(uri) }
?: throw FileNotFoundException("Failed to load $uri") // TODO Better err ? ?: throw FileNotFoundException("Failed to load $uri") // TODO Result
val drawable = val result =
ApngDecoder.decodeApng( ApngDecoder(
context,
inputStream, inputStream,
config config
) ).getDecoded(context)
withContext(Dispatchers.Main) {
imageView.setImageDrawable(drawable) if (result.isSuccess) {
(drawable as? AnimationDrawable)?.start() withContext(Dispatchers.Main) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { val drawable = result.getOrNull()
(drawable as? AnimatedImageDrawable)?.start() imageView.setImageDrawable(drawable)
(drawable as? AnimationDrawable)?.start()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
(drawable as? AnimatedImageDrawable)?.start()
}
} }
} }
return result
return drawable
} }
/** /**
@ -115,21 +118,26 @@ class ApngLoader(parent: Job? = null) {
context: Context, @RawRes res: Int, context: Context, @RawRes res: Int,
imageView: ImageView, imageView: ImageView,
config: ApngDecoder.Config = ApngDecoder.Config() config: ApngDecoder.Config = ApngDecoder.Config()
): Drawable { ): Result<Drawable> {
val drawable = val result =
ApngDecoder.decodeApng( ApngDecoder(
context, withContext(Dispatchers.IO) {
context.resources.openRawResource(res), context.resources.openRawResource(res)
},
config config
) ).getDecoded(context)
withContext(Dispatchers.Main) {
imageView.setImageDrawable(drawable) if (result.isSuccess) {
(drawable as? AnimationDrawable)?.start() withContext(Dispatchers.Main) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { val drawable = result.getOrNull()
(drawable as? AnimatedImageDrawable)?.start() imageView.setImageDrawable(drawable)
(drawable as? AnimationDrawable)?.start()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
(drawable as? AnimatedImageDrawable)?.start()
}
} }
} }
return drawable return result
} }
/** /**
@ -144,26 +152,20 @@ class ApngLoader(parent: Job? = null) {
url: URL, url: URL,
imageView: ImageView, imageView: ImageView,
config: ApngDecoder.Config = ApngDecoder.Config() config: ApngDecoder.Config = ApngDecoder.Config()
): Drawable { ): Result<Drawable> {
val result = ApngDecoder.constructFromUrl(url, config).getDecoded(context)
val drawable = ApngDecoder.decodeApng( if (result.isSuccess) {
context, withContext(Dispatchers.Main) {
ByteArrayInputStream( val drawable = result.getOrNull()
Loader.load( imageView.setImageDrawable(drawable)
url (drawable as? AnimationDrawable)?.start()
) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
), (drawable as? AnimatedImageDrawable)?.start()
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 return result
} }
/** /**
@ -179,7 +181,7 @@ class ApngLoader(parent: Job? = null) {
string: String, string: String,
imageView: ImageView, imageView: ImageView,
config: ApngDecoder.Config = ApngDecoder.Config() config: ApngDecoder.Config = ApngDecoder.Config()
): Drawable { ): Result<Drawable> {
return if (string.startsWith("http://") || string.startsWith("https://")) { return if (string.startsWith("http://") || string.startsWith("https://")) {
decodeApngInto( decodeApngInto(
context, context,
@ -198,21 +200,25 @@ class ApngLoader(parent: Job? = null) {
config config
) )
} else if (string.startsWith("file://android_asset/")) { } else if (string.startsWith("file://android_asset/")) {
val drawable = val inputStream = kotlin.runCatching {
ApngDecoder.decodeApng( withContext(Dispatchers.IO) {
context, context.assets.open(string.replace("file:///android_asset/", ""))
context.assets.open(string.replace("file:///android_asset/", "")), }
}.onFailure {
config return Result.failure(it)
) }
withContext(Dispatchers.Main) { val result = ApngDecoder(inputStream.getOrThrow(), config).getDecoded(context)
imageView.setImageDrawable(drawable) if (result.isSuccess) {
(drawable as? AnimationDrawable)?.start() withContext(Dispatchers.Main) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { val drawable = result.getOrNull()
(drawable as? AnimatedImageDrawable)?.start() imageView.setImageDrawable(drawable)
(drawable as? AnimationDrawable)?.start()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
(drawable as? AnimatedImageDrawable)?.start()
}
} }
} }
drawable result
} else { } else {
throw Exception("Cannot open string") throw Exception("Cannot open string")
} }
@ -228,7 +234,7 @@ class ApngLoader(parent: Job? = null) {
* @param callback [ApngLoader.Callback] to handle success and error. * @param callback [ApngLoader.Callback] to handle success and error.
* @param config Decoder configuration * @param config Decoder configuration
*/ */
@Suppress("unused", "BlockingMethodInNonBlockingContext") @Suppress("unused")
@JvmOverloads @JvmOverloads
fun decodeApngAsyncInto( fun decodeApngAsyncInto(
context: Context, context: Context,
@ -238,11 +244,13 @@ class ApngLoader(parent: Job? = null) {
config: ApngDecoder.Config = ApngDecoder.Config() config: ApngDecoder.Config = ApngDecoder.Config()
) = ) =
coroutineScope.launch(Dispatchers.Default) { coroutineScope.launch(Dispatchers.Default) {
try { val drawable = decodeApngInto(context, file, imageView, config)
val drawable = decodeApngInto(context, file, imageView, config) withContext(Dispatchers.Main) {
callback?.onSuccess(drawable) if (drawable.isSuccess) {
} catch (e: Exception) { callback?.onSuccess(drawable.getOrNull()!!)
callback?.onError(e) } else {
callback?.onError(drawable.exceptionOrNull()!!)
}
} }
} }
@ -264,11 +272,13 @@ class ApngLoader(parent: Job? = null) {
callback: Callback? = null, callback: Callback? = null,
config: ApngDecoder.Config = ApngDecoder.Config() config: ApngDecoder.Config = ApngDecoder.Config()
) = coroutineScope.launch(Dispatchers.Default) { ) = coroutineScope.launch(Dispatchers.Default) {
try { val drawable = decodeApngInto(context, uri, imageView, config)
val drawable = decodeApngInto(context, uri, imageView, config) withContext(Dispatchers.Main) {
callback?.onSuccess(drawable) if (drawable.isSuccess) {
} catch (e: Exception) { callback?.onSuccess(drawable.getOrNull()!!)
callback?.onError(e) } else {
callback?.onError(drawable.exceptionOrNull()!!)
}
} }
} }
@ -288,11 +298,13 @@ class ApngLoader(parent: Job? = null) {
callback: Callback? = null, callback: Callback? = null,
config: ApngDecoder.Config = ApngDecoder.Config() config: ApngDecoder.Config = ApngDecoder.Config()
) = coroutineScope.launch(Dispatchers.Default) { ) = coroutineScope.launch(Dispatchers.Default) {
try { val drawable = decodeApngInto(context, res, imageView, config)
val drawable = decodeApngInto(context, res, imageView, config) withContext(Dispatchers.Main) {
callback?.onSuccess(drawable) if (drawable.isSuccess) {
} catch (e: Exception) { callback?.onSuccess(drawable.getOrNull()!!)
callback?.onError(e) } else {
callback?.onError(drawable.exceptionOrNull()!!)
}
} }
} }
@ -304,7 +316,7 @@ class ApngLoader(parent: Job? = null) {
* @param callback [ApngLoader.Callback] to handle success and error. * @param callback [ApngLoader.Callback] to handle success and error.
* @param config Decoder configuration * @param config Decoder configuration
*/ */
@Suppress("unused", "BlockingMethodInNonBlockingContext") @Suppress("unused")
@JvmOverloads @JvmOverloads
fun decodeApngAsyncInto( fun decodeApngAsyncInto(
context: Context, context: Context,
@ -313,11 +325,13 @@ class ApngLoader(parent: Job? = null) {
callback: Callback? = null, callback: Callback? = null,
config: ApngDecoder.Config = ApngDecoder.Config() config: ApngDecoder.Config = ApngDecoder.Config()
) = coroutineScope.launch(Dispatchers.Default) { ) = coroutineScope.launch(Dispatchers.Default) {
try { val drawable = decodeApngInto(context, url, imageView, config)
val drawable = decodeApngInto(context, url, imageView, config) withContext(Dispatchers.Main) {
callback?.onSuccess(drawable) if (drawable.isSuccess) {
} catch (e: Exception) { callback?.onSuccess(drawable.getOrNull()!!)
callback?.onError(e) } else {
callback?.onError(drawable.exceptionOrNull()!!)
}
} }
} }
@ -339,12 +353,12 @@ class ApngLoader(parent: Job? = null) {
config: ApngDecoder.Config = ApngDecoder.Config() config: ApngDecoder.Config = ApngDecoder.Config()
) = ) =
coroutineScope.launch(Dispatchers.Default) { coroutineScope.launch(Dispatchers.Default) {
try { val drawable = decodeApngInto(context, string, imageView, config)
val drawable = decodeApngInto(context, string, imageView, config) withContext(Dispatchers.Main) {
callback?.onSuccess(drawable) if (drawable.isSuccess) {
} catch (e: Exception) { callback?.onSuccess(drawable.getOrNull()!!)
withContext(Dispatchers.Main) { } else {
callback?.onError(e) callback?.onError(drawable.exceptionOrNull()!!)
} }
} }
} }

View File

@ -60,7 +60,7 @@ class ViewerActivity : AppCompatActivity() {
viewerImageView, viewerImageView,
callback = object : ApngLoader.Callback { callback = object : ApngLoader.Callback {
override fun onSuccess(drawable: Drawable) {} override fun onSuccess(drawable: Drawable) {}
override fun onError(error: Exception) { override fun onError(error: Throwable) {
Log.e("ViewerActivity", "Error when loading file", error) Log.e("ViewerActivity", "Error when loading file", error)
} }
}, },

View File

@ -56,7 +56,7 @@ class ApngDecoderFragment : Fragment() {
) )
} }
override fun onError(error: Exception) { override fun onError(error: Throwable) {
Log.e(TAG, "onError : $error") Log.e(TAG, "onError : $error")
} }
}) })

View File

@ -52,7 +52,7 @@ public class JavaFragment extends Fragment {
} }
@Override @Override
public void onError(@NotNull Exception error) { public void onError(@NotNull Throwable error) {
Log.e(TAG, "Error : " + error.toString()); Log.e(TAG, "Error : " + error.toString());
} }
}, new ApngDecoder.Config().setIsDecodingCoverFrame(false)); }, new ApngDecoder.Config().setIsDecodingCoverFrame(false));