Async for ApngDecoder

Improvement in app
This commit is contained in:
oupson 2020-01-25 12:10:49 +01:00
parent a307f35346
commit 4863dfb2e8
11 changed files with 332 additions and 36 deletions

View File

@ -5,8 +5,11 @@ import android.graphics.*
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.net.Uri import android.net.Uri
import android.util.Log import android.util.Log
import android.widget.ImageView
import androidx.annotation.RawRes import androidx.annotation.RawRes
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import oupson.apng.chunks.IHDR import oupson.apng.chunks.IHDR
import oupson.apng.chunks.fcTL import oupson.apng.chunks.fcTL
@ -23,6 +26,11 @@ import java.util.zip.CRC32
// TODO DOC CODE // TODO DOC CODE
class ExperimentalApngDecoder { class ExperimentalApngDecoder {
interface Callback {
fun onSuccess(drawable : Drawable)
fun onError(error : java.lang.Exception)
}
companion object { companion object {
// TODO Change TAG // TODO Change TAG
private const val TAG = "ExperimentalApngDecoder" private const val TAG = "ExperimentalApngDecoder"
@ -365,6 +373,159 @@ class ExperimentalApngDecoder {
decodeApng(FileInputStream(Loader.load(context, url)), speed) decodeApng(FileInputStream(Loader.load(context, url)), speed)
} }
/**
* Load Apng into an imageView, asynchronously
* @param file File to decode.
* @param imageView Image View.
* @param speed Optional parameter.
* @param callback [ExperimentalApngDecoder.Callback] to handle success and error
*/
@Suppress("unused")
@JvmStatic
@JvmOverloads
fun decodeApngAsyncInto(file : File, imageView : ImageView, speed: Float = 1f, callback: Callback? = null) {
GlobalScope.launch(Dispatchers.IO) {
try {
val drawable = decodeApng(FileInputStream(file), speed)
withContext(Dispatchers.Main) {
imageView.setImageDrawable(drawable)
(drawable as? CustomAnimationDrawable)?.start()
callback?.onSuccess(drawable)
}
} catch (e : java.lang.Exception) {
withContext(Dispatchers.Main) {
callback?.onError(e)
}
}
}
}
/**
* Load Apng into an imageView, asynchronously
* @param context Context
* @param uri Uri to load
* @param imageView Image View.
* @param speed Optional parameter.
* @param callback [ExperimentalApngDecoder.Callback] to handle success and error
*/
@Suppress("unused")
@JvmStatic
@JvmOverloads
fun decodeApngAsyncInto(context : Context, uri : Uri, imageView: ImageView, speed: Float = 1f, callback: Callback? = null) {
val inputStream = context.contentResolver.openInputStream(uri) ?: throw Exception("Failed to open InputStream, InputStream is null")
GlobalScope.launch(Dispatchers.IO) {
try {
val drawable = decodeApng(inputStream, speed)
withContext(Dispatchers.Main) {
imageView.setImageDrawable(drawable)
(drawable as? CustomAnimationDrawable)?.start()
callback?.onSuccess(drawable)
}
} catch (e : java.lang.Exception) {
withContext(Dispatchers.Main) {
callback?.onError(e)
}
}
}
}
/**
* Load Apng into an imageView, asynchronously
* @param context Context
* @param res Raw resource to load
* @param imageView Image View.
* @param speed Optional parameter.
* @param callback [ExperimentalApngDecoder.Callback] to handle success and error
*/
@Suppress("unused")
@JvmStatic
@JvmOverloads
fun decodeApngAsyncInto(context : Context, @RawRes res : Int, imageView: ImageView, speed: Float = 1f, callback: Callback? = null) {
GlobalScope.launch(Dispatchers.IO) {
try {
val drawable = decodeApng(context.resources.openRawResource(res), speed)
withContext(Dispatchers.Main) {
imageView.setImageDrawable(drawable)
(drawable as? CustomAnimationDrawable)?.start()
callback?.onSuccess(drawable)
}
} catch (e : java.lang.Exception) {
withContext(Dispatchers.Main) {
callback?.onError(e)
}
}
}
}
/**
* Load Apng into an imageView, asynchronously
* @param context Context
* @param url URL to load
* @param imageView Image View.
* @param speed Optional parameter.
* @param callback [ExperimentalApngDecoder.Callback] to handle success and error
*/
@Suppress("unused")
@JvmStatic
@JvmOverloads
fun decodeApngAsyncInto(context : Context, url : URL, imageView: ImageView, speed: Float = 1f, callback: Callback? = null) {
GlobalScope.launch(Dispatchers.IO) {
try {
val drawable = decodeApng(FileInputStream(Loader.load(context, url)), speed)
withContext(Dispatchers.Main) {
imageView.setImageDrawable(drawable)
(drawable as? CustomAnimationDrawable)?.start()
callback?.onSuccess(drawable)
}
} catch (e : java.lang.Exception) {
withContext(Dispatchers.Main) {
callback?.onError(e)
}
}
}
}
/**
* Load Apng into an imageView, asynchronously
* @param context Context
* @param string URL to load
* @param imageView Image View.
* @param speed Optional parameter.
* @param callback [ExperimentalApngDecoder.Callback] to handle success and error
*/
@Suppress("unused")
@JvmStatic
@JvmOverloads
fun decodeApngAsyncInto(context : Context, string: String, imageView: ImageView, speed: Float = 1f, callback: Callback? = null) {
GlobalScope.launch(Dispatchers.IO) {
try {
if (string.startsWith("http://") || string.startsWith("https://")) {
decodeApngAsyncInto(context, URL(string), imageView, speed, callback)
} 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, speed, callback)
} else if (string.startsWith("file://android_asset/")) {
val drawable = decodeApng(context.assets.open(string.replace("file:///android_asset/", "")), speed)
withContext(Dispatchers.Main) {
imageView.setImageDrawable(drawable)
(drawable as? CustomAnimationDrawable)?.start()
callback?.onSuccess(drawable)
}
} else {
withContext(Dispatchers.Main) {
callback?.onError(java.lang.Exception("Cannot open string"))
}
}
} catch (e : java.lang.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
* @param ihdrOfApng The IHDR of the APNG * @param ihdrOfApng The IHDR of the APNG

View File

@ -5,7 +5,6 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.io.BufferedInputStream import java.io.BufferedInputStream
import java.io.File import java.io.File
import java.io.IOException
import java.net.URL import java.net.URL
class Loader { class Loader {
@ -16,22 +15,23 @@ class Loader {
* @param url Url of the file to download * @param url Url of the file to download
* @return [ByteArray] of the file * @return [ByteArray] of the file
*/ */
@Throws(IOException::class) // @Throws(IOException::class, java.io.FileNotFoundException::class, java.lang.Exception::class)
suspend fun load(context: Context, url: URL): File = withContext(Dispatchers.IO) { suspend fun load(context: Context, url: URL) =
val currentDir = context.filesDir withContext(Dispatchers.IO) {
val fileTXT = File(currentDir, "apngLoader.txt") val currentDir = context.filesDir
val filePNG = File(currentDir, "apngLoader.png") val fileTXT = File(currentDir, "apngLoader.txt")
if (fileTXT.exists() && url.toString() == fileTXT.readText()) { val filePNG = File(currentDir, "apngLoader.png")
filePNG if (fileTXT.exists() && url.toString() == fileTXT.readText()) {
} else { filePNG
val connection = url.openConnection() } else {
connection.connect() val connection = url.openConnection()
val input = BufferedInputStream(connection.getInputStream()) connection.connect()
val bytes = input.readBytes() val input = BufferedInputStream(connection.getInputStream())
input.close() val bytes = input.readBytes()
fileTXT.writeText(url.toString()) input.close()
filePNG.apply { writeBytes(bytes) } fileTXT.writeText(url.toString())
filePNG.apply { writeBytes(bytes) }
}
} }
}
} }
} }

View File

@ -0,0 +1,54 @@
package oupson.apngcreator
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.fragment.app.Fragment
import oupson.apng.ExperimentalApngDecoder
import java.net.URL
class ApngDecoderFragment : Fragment() {
companion object {
private const val TAG = "ApngDecoderFragment"
@JvmStatic
fun newInstance() =
ApngDecoderFragment()
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val view = inflater.inflate(R.layout.fragment_apng_decoder, container, false)
val imageView : ImageView = view.findViewById(R.id.apngDecoderImageView) ?: return view
if (context != null) {
ExperimentalApngDecoder.decodeApngAsyncInto(
this.context!!,
URL("http://littlesvr.ca/apng/images/GenevaDrive.png"),
imageView,
callback = object : ExperimentalApngDecoder.Callback {
override fun onSuccess(drawable: Drawable) {
if (BuildConfig.DEBUG)
Log.i(TAG, "onSuccess()")
}
override fun onError(error: Exception) {
if (BuildConfig.DEBUG)
Log.e(TAG, "onError", error)
}
})
}
return view
}
}

View File

@ -1,6 +1,8 @@
package oupson.apngcreator; package oupson.apngcreator;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log; import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -10,9 +12,9 @@ import android.widget.ImageView;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import kotlin.Unit; import org.jetbrains.annotations.NotNull;
import oupson.apng.ApngAnimator;
import oupson.apng.ApngAnimatorKt; import oupson.apng.ExperimentalApngDecoder;
public class JavaFragment extends Fragment { public class JavaFragment extends Fragment {
@ -22,10 +24,16 @@ public class JavaFragment extends Fragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { Bundle savedInstanceState) {
Log.i(TAG, "onCreateView()"); Log.i(TAG, "onCreateView()");
View v = inflater.inflate(R.layout.fragment_java, container, false); View v = inflater.inflate(R.layout.fragment_java, container, false);
String imageUrl = "https://metagif.files.wordpress.com/2015/01/bugbuckbunny.png"; String imageUrl = "https://metagif.files.wordpress.com/2015/01/bugbuckbunny.png";
ImageView imageView = v.findViewById(R.id.javaImageView); ImageView imageView = v.findViewById(R.id.javaImageView);
if (imageView != null) {
Context context = this.getContext();
if (imageView != null && context != null) {
/*
ApngAnimator a = ApngAnimatorKt.loadApng(imageView, imageUrl); ApngAnimator a = ApngAnimatorKt.loadApng(imageView, imageUrl);
a.onLoaded((animator) -> { a.onLoaded((animator) -> {
animator.setOnFrameChangeLister((index) -> { animator.setOnFrameChangeLister((index) -> {
@ -36,6 +44,19 @@ public class JavaFragment extends Fragment {
}); });
return Unit.INSTANCE; return Unit.INSTANCE;
}); });
*/
ExperimentalApngDecoder.decodeApngAsyncInto(context, imageUrl, imageView, 1f, new ExperimentalApngDecoder.Callback() {
@Override
public void onSuccess(@NotNull Drawable drawable) {
Log.i(TAG, "Success");
}
@Override
public void onError(@NotNull Exception error) {
Log.e(TAG, "Error", error);
}
});
} }
return v; return v;
} }

View File

@ -16,6 +16,9 @@ import org.jetbrains.anko.startActivity
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
companion object {
private const val TAG = "MainActivity"
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -39,7 +42,7 @@ class MainActivity : AppCompatActivity() {
navigationView.setNavigationItemSelectedListener { menuItem : MenuItem -> navigationView.setNavigationItemSelectedListener { menuItem : MenuItem ->
when(menuItem.itemId) { when(menuItem.itemId) {
R.id.menu_kotlin_activity -> { R.id.menu_kotlin_fragment -> {
if (selected != 0) { if (selected != 0) {
supportFragmentManager.beginTransaction().apply { supportFragmentManager.beginTransaction().apply {
replace(R.id.fragment_container, KotlinFragment.newInstance()) replace(R.id.fragment_container, KotlinFragment.newInstance())
@ -48,7 +51,7 @@ class MainActivity : AppCompatActivity() {
selected = 0 selected = 0
} }
} }
R.id.menu_java_activity -> { R.id.menu_java_fragment -> {
if (selected != 1) { if (selected != 1) {
supportFragmentManager.beginTransaction().apply { supportFragmentManager.beginTransaction().apply {
replace(R.id.fragment_container, JavaFragment()) replace(R.id.fragment_container, JavaFragment())
@ -57,6 +60,15 @@ class MainActivity : AppCompatActivity() {
selected = 1 selected = 1
} }
} }
R.id.menu_apng_decoder_fragment -> {
if (selected != 2) {
supportFragmentManager.beginTransaction().apply {
replace(R.id.fragment_container, ApngDecoderFragment.newInstance())
addToBackStack(null)
}.commit()
selected = 2
}
}
} }
drawer_layout.closeDrawer(GravityCompat.START) drawer_layout.closeDrawer(GravityCompat.START)
@ -64,12 +76,36 @@ class MainActivity : AppCompatActivity() {
return@setNavigationItemSelectedListener true return@setNavigationItemSelectedListener true
} }
supportFragmentManager.beginTransaction().apply { if (intent.hasExtra("fragment")) {
add(R.id.fragment_container, KotlinFragment.newInstance(), "KotlinFragment") when(intent.getStringExtra("fragment")) {
}.commit() "kotlin" -> {
supportFragmentManager.beginTransaction().apply {
add(R.id.fragment_container, KotlinFragment.newInstance(), "KotlinFragment")
navigationView.setCheckedItem(R.id.menu_kotlin_activity) }.commit()
navigationView.setCheckedItem(R.id.menu_kotlin_fragment)
selected = 0
}
"java" -> {
supportFragmentManager.beginTransaction().apply {
add(R.id.fragment_container, JavaFragment())
}.commit()
navigationView.setCheckedItem(R.id.menu_java_fragment)
selected = 1
}
"apng_decoder" -> {
supportFragmentManager.beginTransaction().apply {
add(R.id.fragment_container, ApngDecoderFragment.newInstance())
}.commit()
navigationView.setCheckedItem(R.id.menu_apng_decoder_fragment)
selected = 2
}
}
} else {
supportFragmentManager.beginTransaction().apply {
add(R.id.fragment_container, KotlinFragment.newInstance(), "KotlinFragment")
}.commit()
navigationView.setCheckedItem(R.id.menu_kotlin_fragment)
}
} }
private fun setUpBottomAppBarShapeAppearance() { private fun setUpBottomAppBarShapeAppearance() {

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
</LinearLayout>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ApngDecoderFragment">
<ImageView
android:id="@+id/apngDecoderImageView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="@string/description_viewer_imageView"/>
</FrameLayout>

View File

@ -2,12 +2,16 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"> <menu xmlns:android="http://schemas.android.com/apk/res/android">
<group android:checkableBehavior="single"> <group android:checkableBehavior="single">
<item <item
android:id="@+id/menu_kotlin_activity" android:id="@+id/menu_kotlin_fragment"
android:icon="@drawable/ic_kotlin" android:icon="@drawable/ic_kotlin"
android:title="@string/menu_title_kotlin_activity" /> android:title="@string/menu_title_kotlin_fragment" />
<item <item
android:id="@+id/menu_java_activity" android:id="@+id/menu_java_fragment"
android:icon="@drawable/ic_java" android:icon="@drawable/ic_java"
android:title="@string/menu_title_java_activity" /> android:title="@string/menu_title_java_fragment" />
<item
android:id="@+id/menu_apng_decoder_fragment"
android:title="@string/menu_title_apng_decoder_fragment" />
</group> </group>
</menu> </menu>

View File

@ -16,7 +16,7 @@
<color name="gray">#999999</color> <color name="gray">#999999</color>
<color name="background">#ffffff</color> <color name="background">#FFFFFF</color>
<color name="control">#fff</color> <color name="control">#fff</color>
</resources> </resources>

View File

@ -10,8 +10,9 @@
<string name="title_playButton">Play</string> <string name="title_playButton">Play</string>
<string name="title_pauseButton">Pause</string> <string name="title_pauseButton">Pause</string>
<string name="menu_title_kotlin_activity">Kotlin Activity</string> <string name="menu_title_kotlin_fragment">Kotlin Fragment</string>
<string name="menu_title_java_activity">Java Activity</string> <string name="menu_title_java_fragment">Java Fragment</string>
<string name="menu_title_apng_decoder_fragment">ApngDecoder</string>
<string name="create">Create</string> <string name="create">Create</string>