Add ApngLoader

This commit is contained in:
Oupson 2021-06-22 15:30:21 +02:00
parent f7d76f26e7
commit af21d501f6
6 changed files with 422 additions and 286 deletions

View File

@ -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.

View File

@ -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
}

View File

@ -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(
this,
uri,
viewerImageView,
callback = object : ApngLoader.Callback {
override fun onSuccess(drawable: Drawable) {} override fun onSuccess(drawable: Drawable) {}
override fun onError(error: Exception) { override fun onError(error: Exception) {
Log.e("ViewerActivity", "Error when loading file", error) Log.e("ViewerActivity", "Error when loading file", error)
} }
}, ApngDecoder.Config(decodeCoverFrame = false)) },
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 -> {

View File

@ -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) {

View File

@ -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();
}
} }

View File

@ -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 ->