Working on beta
This commit is contained in:
parent
8ffdd2d998
commit
a273e7da09
Binary file not shown.
|
@ -8,7 +8,7 @@ android {
|
|||
minSdkVersion 21
|
||||
targetSdkVersion 28
|
||||
versionCode 1
|
||||
versionName "1.0.10"
|
||||
versionName "1.0.10-beta1"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import android.content.Context
|
|||
import android.content.SharedPreferences
|
||||
import android.graphics.*
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import android.widget.ImageView
|
||||
import androidx.annotation.RawRes
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
@ -18,6 +19,8 @@ import oupson.apng.utils.Utils.Companion.isPng
|
|||
import java.io.File
|
||||
import java.net.URL
|
||||
|
||||
// TODO REWRITE WITH CALLBACKS
|
||||
|
||||
/**
|
||||
* @param file The APNG to load
|
||||
* @param speed The speed of the APNG
|
||||
|
@ -270,20 +273,32 @@ class ApngAnimator(private val context: Context?) {
|
|||
GlobalScope.launch(Dispatchers.IO) {
|
||||
this@ApngAnimator.speed = speed
|
||||
// Download PNG
|
||||
Loader.load(context!!, url).apply {
|
||||
try {
|
||||
this@ApngAnimator.load(this, speed, apngAnimatorOptions)
|
||||
} catch (e : NotPngException) {
|
||||
if (loadNotApng) {
|
||||
val bytes = this.readBytes()
|
||||
GlobalScope.launch(Dispatchers.Main) {
|
||||
imageView?.scaleType = this@ApngAnimator.scaleType ?: ImageView.ScaleType.FIT_CENTER
|
||||
imageView?.setImageBitmap(BitmapFactory.decodeByteArray(bytes, 0, bytes.size))
|
||||
try {
|
||||
Loader.load(context!!, url).apply {
|
||||
try {
|
||||
this@ApngAnimator.load(this, speed, apngAnimatorOptions)
|
||||
} catch (e: NotPngException) {
|
||||
if (loadNotApng) {
|
||||
val bytes = this.readBytes()
|
||||
GlobalScope.launch(Dispatchers.Main) {
|
||||
imageView?.scaleType =
|
||||
this@ApngAnimator.scaleType ?: ImageView.ScaleType.FIT_CENTER
|
||||
imageView?.setImageBitmap(
|
||||
BitmapFactory.decodeByteArray(
|
||||
bytes,
|
||||
0,
|
||||
bytes.size
|
||||
)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
throw NotApngException()
|
||||
}
|
||||
} else {
|
||||
throw NotApngException()
|
||||
}
|
||||
}
|
||||
} catch (e : java.lang.Exception) {
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.e("ApngAnimator", "Error : $e")
|
||||
}
|
||||
}
|
||||
return this
|
||||
|
@ -535,6 +550,7 @@ class ApngAnimator(private val context: Context?) {
|
|||
private fun toAnimationDrawable( generatedFrame : ArrayList<Bitmap> ): CustomAnimationDrawable {
|
||||
if (isApng) {
|
||||
return CustomAnimationDrawable().apply {
|
||||
isOneShot = false
|
||||
for (i in 0 until generatedFrame.size) {
|
||||
addFrame(BitmapDrawable(generatedFrame[i]), ((duration!![i]) / (speed ?: 1f)).toInt())
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ import oupson.apng.utils.Utils.Companion.isPng
|
|||
* @param maxWidth The max width of the APNG
|
||||
* @param maxHeight The max height of the APNG
|
||||
*/
|
||||
class Frame// Get width and height for image
|
||||
class Frame // Get width and height for image
|
||||
(
|
||||
byteArray: ByteArray,
|
||||
delay: Float = 1000f,
|
||||
|
|
|
@ -12,10 +12,10 @@ import java.net.URL
|
|||
class Loader {
|
||||
companion object {
|
||||
/**
|
||||
* Download file from given url
|
||||
* @param context Context of app
|
||||
* @param url Url of the file to download
|
||||
* @return [ByteArray] of the file
|
||||
* Download file from given url.
|
||||
* @param context Context of app.
|
||||
* @param url Url of the file to download.
|
||||
* @return [ByteArray] of the file.
|
||||
*/
|
||||
@Throws(IOException::class, java.io.FileNotFoundException::class)
|
||||
suspend fun load(context: Context, url: URL) =
|
||||
|
|
|
@ -4,6 +4,7 @@ import android.content.Context
|
|||
import android.graphics.*
|
||||
import android.graphics.drawable.AnimatedImageDrawable
|
||||
import android.graphics.drawable.AnimationDrawable
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
|
@ -15,7 +16,6 @@ import kotlinx.coroutines.GlobalScope
|
|||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import oupson.apng.APNGDisassembler
|
||||
import oupson.apng.BitmapDrawable
|
||||
import oupson.apng.BuildConfig
|
||||
import oupson.apng.Loader
|
||||
import oupson.apng.chunks.IHDR
|
||||
|
@ -34,14 +34,14 @@ import java.util.zip.CRC32
|
|||
|
||||
class ApngDecoder {
|
||||
interface Callback {
|
||||
fun onSuccess(drawable : Drawable)
|
||||
fun onError(error : java.lang.Exception)
|
||||
fun onSuccess(drawable: Drawable)
|
||||
fun onError(error: java.lang.Exception)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "ApngDecoder"
|
||||
|
||||
private val clearPaint : Paint by lazy {
|
||||
private val clearPaint: Paint by lazy {
|
||||
Paint().apply {
|
||||
xfermode = PorterDuffXfermode(
|
||||
PorterDuff.Mode.CLEAR
|
||||
|
@ -50,13 +50,13 @@ class ApngDecoder {
|
|||
}
|
||||
|
||||
/**
|
||||
* Decode Apng and return a Drawable who can be an [AnimationDrawable] if it end successfully. Can also be an [android.graphics.drawable.AnimatedImageDrawable]
|
||||
* @param inStream Input Stream to decode. Will be close at the end
|
||||
* Decode Apng and return a Drawable who can be an [AnimationDrawable] if it end successfully. Can also be an [android.graphics.drawable.AnimatedImageDrawable].
|
||||
* @param inStream Input Stream to decode. Will be close at the end.
|
||||
* @param speed Optional parameter.
|
||||
*/
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
@JvmStatic
|
||||
fun decodeApng(inStream: InputStream, speed : Float = 1f) : Drawable {
|
||||
fun decodeApng(context: Context, inStream: InputStream, speed: Float = 1f): Drawable {
|
||||
val inputStream = BufferedInputStream(inStream)
|
||||
val bytes = ByteArray(8)
|
||||
inputStream.mark(0)
|
||||
|
@ -72,7 +72,8 @@ class ApngDecoder {
|
|||
var maxWidth = 0
|
||||
var maxHeight = 0
|
||||
var blendOp: Utils.Companion.BlendOp = Utils.Companion.BlendOp.APNG_BLEND_OP_SOURCE
|
||||
var disposeOp: Utils.Companion.DisposeOp = Utils.Companion.DisposeOp.APNG_DISPOSE_OP_NONE
|
||||
var disposeOp: Utils.Companion.DisposeOp =
|
||||
Utils.Companion.DisposeOp.APNG_DISPOSE_OP_NONE
|
||||
val ihdr = IHDR()
|
||||
var isApng = false
|
||||
|
||||
|
@ -80,7 +81,7 @@ class ApngDecoder {
|
|||
isOneShot = false
|
||||
}
|
||||
|
||||
var buffer : Bitmap? = null
|
||||
var buffer: Bitmap? = null
|
||||
|
||||
var byteRead: Int
|
||||
val lengthChunk = ByteArray(4)
|
||||
|
@ -114,7 +115,11 @@ class ApngDecoder {
|
|||
crC32.update(iend, 0, iend.size)
|
||||
it.addAll(iend.asList())
|
||||
it.addAll(Utils.to4Bytes(crC32.value.toInt()).asList())
|
||||
APNGDisassembler.apng.cover = BitmapFactory.decodeByteArray(it.toByteArray(), 0, it.size)
|
||||
APNGDisassembler.apng.cover = BitmapFactory.decodeByteArray(
|
||||
it.toByteArray(),
|
||||
0,
|
||||
it.size
|
||||
)
|
||||
}
|
||||
png = ArrayList()
|
||||
val fcTL = fcTL()
|
||||
|
@ -139,7 +144,8 @@ class ApngDecoder {
|
|||
ihdr,
|
||||
width,
|
||||
height
|
||||
).asList())
|
||||
).asList()
|
||||
)
|
||||
plte?.let {
|
||||
png?.addAll(it.asList())
|
||||
}
|
||||
|
@ -157,34 +163,63 @@ class ApngDecoder {
|
|||
png.addAll(iend.asList())
|
||||
png.addAll(Utils.to4Bytes(crC32.value.toInt()).asList())
|
||||
|
||||
val btm = Bitmap.createBitmap(maxWidth, maxHeight, Bitmap.Config.ARGB_8888)
|
||||
val decoded = BitmapFactory.decodeByteArray(png.toByteArray(), 0, png.size)
|
||||
val btm = Bitmap.createBitmap(
|
||||
maxWidth,
|
||||
maxHeight,
|
||||
Bitmap.Config.ARGB_8888
|
||||
)
|
||||
val decoded = BitmapFactory.decodeByteArray(
|
||||
png.toByteArray(),
|
||||
0,
|
||||
png.size
|
||||
)
|
||||
val canvas = Canvas(btm)
|
||||
canvas.drawBitmap(buffer!!, 0f, 0f, null)
|
||||
|
||||
if (blendOp == Utils.Companion.BlendOp.APNG_BLEND_OP_SOURCE) {
|
||||
canvas.drawRect(xOffset.toFloat(), yOffset.toFloat(), xOffset+ decoded.width.toFloat(), yOffset + decoded.height.toFloat(),
|
||||
canvas.drawRect(
|
||||
xOffset.toFloat(),
|
||||
yOffset.toFloat(),
|
||||
xOffset + decoded.width.toFloat(),
|
||||
yOffset + decoded.height.toFloat(),
|
||||
clearPaint
|
||||
)
|
||||
}
|
||||
|
||||
canvas.drawBitmap(decoded, xOffset.toFloat(), yOffset.toFloat(), null)
|
||||
canvas.drawBitmap(
|
||||
decoded,
|
||||
xOffset.toFloat(),
|
||||
yOffset.toFloat(),
|
||||
null
|
||||
)
|
||||
|
||||
drawable.addFrame(
|
||||
BitmapDrawable(
|
||||
context.resources,
|
||||
btm
|
||||
), (delay / speed).toInt())
|
||||
),
|
||||
(delay / speed).toInt()
|
||||
)
|
||||
|
||||
when(disposeOp) {
|
||||
when (disposeOp) {
|
||||
Utils.Companion.DisposeOp.APNG_DISPOSE_OP_PREVIOUS -> {
|
||||
//Do nothings
|
||||
}
|
||||
// Add current frame to bitmap buffer
|
||||
// APNG_DISPOSE_OP_BACKGROUND: the frame's region of the output buffer is to be cleared to fully transparent black before rendering the next frame.
|
||||
Utils.Companion.DisposeOp.APNG_DISPOSE_OP_BACKGROUND -> {
|
||||
val res = Bitmap.createBitmap(maxWidth, maxHeight, Bitmap.Config.ARGB_8888)
|
||||
val res = Bitmap.createBitmap(
|
||||
maxWidth,
|
||||
maxHeight,
|
||||
Bitmap.Config.ARGB_8888
|
||||
)
|
||||
val can = Canvas(res)
|
||||
can.drawBitmap(btm, 0f, 0f, null)
|
||||
can.drawRect(xOffset.toFloat(), yOffset.toFloat(), xOffset + decoded.width.toFloat(), yOffset + decoded.height.toFloat(),
|
||||
can.drawRect(
|
||||
xOffset.toFloat(),
|
||||
yOffset.toFloat(),
|
||||
xOffset + decoded.width.toFloat(),
|
||||
yOffset + decoded.height.toFloat(),
|
||||
clearPaint
|
||||
)
|
||||
buffer = res
|
||||
|
@ -209,7 +244,8 @@ class ApngDecoder {
|
|||
ihdr,
|
||||
width,
|
||||
height
|
||||
).asList())
|
||||
).asList()
|
||||
)
|
||||
plte?.let {
|
||||
png.addAll(it.asList())
|
||||
}
|
||||
|
@ -230,34 +266,62 @@ class ApngDecoder {
|
|||
png.addAll(Utils.to4Bytes(crC32.value.toInt()).asList())
|
||||
|
||||
|
||||
val btm = Bitmap.createBitmap(maxWidth, maxHeight, Bitmap.Config.ARGB_8888)
|
||||
val decoded = BitmapFactory.decodeByteArray(png.toByteArray(), 0, png.size)
|
||||
val btm = Bitmap.createBitmap(
|
||||
maxWidth,
|
||||
maxHeight,
|
||||
Bitmap.Config.ARGB_8888
|
||||
)
|
||||
val decoded = BitmapFactory.decodeByteArray(
|
||||
png.toByteArray(),
|
||||
0,
|
||||
png.size
|
||||
)
|
||||
val canvas = Canvas(btm)
|
||||
canvas.drawBitmap(buffer!!, 0f, 0f, null)
|
||||
|
||||
if (blendOp == Utils.Companion.BlendOp.APNG_BLEND_OP_SOURCE) {
|
||||
canvas.drawRect(xOffset.toFloat(), yOffset.toFloat(), xOffset+ decoded.width.toFloat(), yOffset + decoded.height.toFloat(),
|
||||
canvas.drawRect(
|
||||
xOffset.toFloat(),
|
||||
yOffset.toFloat(),
|
||||
xOffset + decoded.width.toFloat(),
|
||||
yOffset + decoded.height.toFloat(),
|
||||
clearPaint
|
||||
)
|
||||
}
|
||||
|
||||
canvas.drawBitmap(decoded, xOffset.toFloat(), yOffset.toFloat(), null)
|
||||
canvas.drawBitmap(
|
||||
decoded,
|
||||
xOffset.toFloat(),
|
||||
yOffset.toFloat(),
|
||||
null
|
||||
)
|
||||
drawable.addFrame(
|
||||
BitmapDrawable(
|
||||
context.resources,
|
||||
btm
|
||||
), (delay / speed).toInt())
|
||||
),
|
||||
(delay / speed).toInt()
|
||||
)
|
||||
|
||||
when(disposeOp) {
|
||||
when (disposeOp) {
|
||||
Utils.Companion.DisposeOp.APNG_DISPOSE_OP_PREVIOUS -> {
|
||||
//Do nothings
|
||||
}
|
||||
// Add current frame to bitmap buffer
|
||||
// APNG_DISPOSE_OP_BACKGROUND: the frame's region of the output buffer is to be cleared to fully transparent black before rendering the next frame.
|
||||
Utils.Companion.DisposeOp.APNG_DISPOSE_OP_BACKGROUND -> {
|
||||
val res = Bitmap.createBitmap(maxWidth, maxHeight, Bitmap.Config.ARGB_8888)
|
||||
val res = Bitmap.createBitmap(
|
||||
maxWidth,
|
||||
maxHeight,
|
||||
Bitmap.Config.ARGB_8888
|
||||
)
|
||||
val can = Canvas(res)
|
||||
can.drawBitmap(btm, 0f, 0f, null)
|
||||
can.drawRect(xOffset.toFloat(), yOffset.toFloat(), xOffset + decoded.width.toFloat(), yOffset + decoded.height.toFloat(),
|
||||
can.drawRect(
|
||||
xOffset.toFloat(),
|
||||
yOffset.toFloat(),
|
||||
xOffset + decoded.width.toFloat(),
|
||||
yOffset + decoded.height.toFloat(),
|
||||
clearPaint
|
||||
)
|
||||
buffer = res
|
||||
|
@ -277,6 +341,7 @@ class ApngDecoder {
|
|||
it.addAll(Utils.to4Bytes(crC32.value.toInt()).asList())
|
||||
inputStream.close()
|
||||
return BitmapDrawable(
|
||||
context.resources,
|
||||
BitmapFactory.decodeByteArray(
|
||||
it.toByteArray(),
|
||||
0,
|
||||
|
@ -296,27 +361,40 @@ class ApngDecoder {
|
|||
ihdr,
|
||||
maxWidth,
|
||||
maxHeight
|
||||
).asList())
|
||||
).asList()
|
||||
)
|
||||
}
|
||||
// Find the chunk length
|
||||
val bodySize = Utils.parseLength(byteArray.copyOfRange(i - 4, i))
|
||||
val bodySize =
|
||||
Utils.parseLength(byteArray.copyOfRange(i - 4, i))
|
||||
cover.addAll(byteArray.copyOfRange(i - 4, i).asList())
|
||||
val body = ArrayList<Byte>()
|
||||
body.addAll(byteArrayOf(0x49, 0x44, 0x41, 0x54).asList())
|
||||
// Get image bytes
|
||||
body.addAll(byteArray.copyOfRange(i + 4, i + 4 + bodySize).asList())
|
||||
body.addAll(
|
||||
byteArray.copyOfRange(
|
||||
i + 4,
|
||||
i + 4 + bodySize
|
||||
).asList()
|
||||
)
|
||||
val crC32 = CRC32()
|
||||
crC32.update(body.toByteArray(), 0, body.size)
|
||||
cover.addAll(body)
|
||||
cover.addAll(Utils.to4Bytes(crC32.value.toInt()).asList())
|
||||
} else {
|
||||
// Find the chunk length
|
||||
val bodySize = Utils.parseLength(byteArray.copyOfRange(i - 4, i))
|
||||
val bodySize =
|
||||
Utils.parseLength(byteArray.copyOfRange(i - 4, i))
|
||||
png.addAll(byteArray.copyOfRange(i - 4, i).asList())
|
||||
val body = ArrayList<Byte>()
|
||||
body.addAll(byteArrayOf(0x49, 0x44, 0x41, 0x54).asList())
|
||||
// Get image bytes
|
||||
body.addAll(byteArray.copyOfRange(i + 4, i + 4 + bodySize).asList())
|
||||
body.addAll(
|
||||
byteArray.copyOfRange(
|
||||
i + 4,
|
||||
i + 4 + bodySize
|
||||
).asList()
|
||||
)
|
||||
val crC32 = CRC32()
|
||||
crC32.update(body.toByteArray(), 0, body.size)
|
||||
png.addAll(body)
|
||||
|
@ -345,8 +423,12 @@ class ApngDecoder {
|
|||
name.contentEquals(Utils.IHDR) -> {
|
||||
ihdr.parse(byteArray)
|
||||
maxWidth = ihdr.pngWidth
|
||||
maxHeight =ihdr.pngHeight
|
||||
buffer = Bitmap.createBitmap(maxWidth, maxHeight, Bitmap.Config.ARGB_8888)
|
||||
maxHeight = ihdr.pngHeight
|
||||
buffer = Bitmap.createBitmap(
|
||||
maxWidth,
|
||||
maxHeight,
|
||||
Bitmap.Config.ARGB_8888
|
||||
)
|
||||
}
|
||||
name.contentEquals(Utils.acTL) -> {
|
||||
isApng = true
|
||||
|
@ -378,28 +460,31 @@ class ApngDecoder {
|
|||
|
||||
/**
|
||||
* Decode Apng and return a Drawable who can be an [AnimationDrawable] if it end successfully. Can also be an [android.graphics.drawable.AnimatedImageDrawable].
|
||||
* @param context Context.
|
||||
* @param file File to decode.
|
||||
* @param speed Optional parameter.
|
||||
*/
|
||||
@Suppress("unused")
|
||||
@JvmStatic
|
||||
fun decodeApng(file : File, speed: Float = 1f) : Drawable =
|
||||
fun decodeApng(context: Context, file: File, speed: Float = 1f): Drawable =
|
||||
decodeApng(
|
||||
context,
|
||||
FileInputStream(file), speed
|
||||
)
|
||||
|
||||
/**
|
||||
* Decode Apng and return a Drawable who can be an [AnimationDrawable] if it end successfully. Can also be an [android.graphics.drawable.AnimatedImageDrawable].
|
||||
* @param context Context is needed for contentResolver
|
||||
* @param context Context is needed for contentResolver.
|
||||
* @param uri Uri to open.
|
||||
* @param speed Optional parameter.
|
||||
*/
|
||||
@Suppress("unused")
|
||||
@JvmStatic
|
||||
fun decodeApng(context : Context, uri : Uri, speed: Float = 1f) : Drawable {
|
||||
fun decodeApng(context: Context, uri: Uri, speed: Float = 1f): Drawable {
|
||||
val inputStream = context.contentResolver.openInputStream(uri)
|
||||
?: throw Exception("Failed to open InputStream, InputStream is null")
|
||||
return decodeApng(
|
||||
context,
|
||||
inputStream,
|
||||
speed
|
||||
)
|
||||
|
@ -407,48 +492,61 @@ class ApngDecoder {
|
|||
|
||||
/**
|
||||
* Decode Apng and return a Drawable who can be an [AnimationDrawable] if it end successfully. Can also be an [android.graphics.drawable.AnimatedImageDrawable].
|
||||
* @param context Context is needed for contentResolver
|
||||
* @param context Context is needed for contentResolver.
|
||||
* @param res Resource to decode.
|
||||
* @param speed Optional parameter.
|
||||
*/
|
||||
@Suppress("unused")
|
||||
@JvmStatic
|
||||
fun decodeApng(context : Context, @RawRes res : Int, speed: Float = 1f) : Drawable =
|
||||
fun decodeApng(context: Context, @RawRes res: Int, speed: Float = 1f): Drawable =
|
||||
decodeApng(
|
||||
context.resources.openRawResource(res), speed
|
||||
context,
|
||||
context.resources.openRawResource(res),
|
||||
speed
|
||||
)
|
||||
|
||||
/**
|
||||
* Decode Apng and return a Drawable who can be an [AnimationDrawable] if it end successfully. Can also be an [android.graphics.drawable.AnimatedImageDrawable].
|
||||
* @param context Context is needed for contentResolver
|
||||
* @param context Context is needed for contentResolver.
|
||||
* @param url URL to decode.
|
||||
* @param speed Optional parameter.
|
||||
*/
|
||||
@Suppress("unused")
|
||||
@JvmStatic
|
||||
suspend fun decodeApng(context : Context, url : URL, speed: Float = 1f) = withContext(Dispatchers.IO) {
|
||||
decodeApng(
|
||||
FileInputStream(
|
||||
Loader.load(context, url)
|
||||
), speed
|
||||
)
|
||||
}
|
||||
suspend fun decodeApng(context: Context, url: URL, speed: Float = 1f) =
|
||||
withContext(Dispatchers.IO) {
|
||||
decodeApng(
|
||||
context,
|
||||
FileInputStream(
|
||||
Loader.load(context, url)
|
||||
),
|
||||
speed
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Load Apng into an imageView, asynchronously
|
||||
* Load Apng into an imageView, asynchronously.
|
||||
* @param context Context.
|
||||
* @param file File to decode.
|
||||
* @param imageView Image View.
|
||||
* @param speed Optional parameter.
|
||||
* @param callback [ApngDecoder.Callback] to handle success and error
|
||||
* @param callback [ApngDecoder.Callback] to handle success and error.
|
||||
*/
|
||||
@Suppress("unused")
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun decodeApngAsyncInto(file : File, imageView : ImageView, speed: Float = 1f, callback: Callback? = null) {
|
||||
fun decodeApngAsyncInto(
|
||||
context: Context,
|
||||
file: File,
|
||||
imageView: ImageView,
|
||||
speed: Float = 1f,
|
||||
callback: Callback? = null
|
||||
) {
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
val drawable =
|
||||
decodeApng(
|
||||
context,
|
||||
FileInputStream(file),
|
||||
speed
|
||||
)
|
||||
|
@ -460,7 +558,7 @@ class ApngDecoder {
|
|||
}
|
||||
callback?.onSuccess(drawable)
|
||||
}
|
||||
} catch (e : java.lang.Exception) {
|
||||
} catch (e: Exception) {
|
||||
withContext(Dispatchers.Main) {
|
||||
callback?.onError(e)
|
||||
}
|
||||
|
@ -469,22 +567,30 @@ class ApngDecoder {
|
|||
}
|
||||
|
||||
/**
|
||||
* Load Apng into an imageView, asynchronously
|
||||
* @param context Context
|
||||
* @param uri Uri to load
|
||||
* Load Apng into an imageView, asynchronously.
|
||||
* @param context Context.
|
||||
* @param uri Uri to load.
|
||||
* @param imageView Image View.
|
||||
* @param speed Optional parameter.
|
||||
* @param callback [ApngDecoder.Callback] to handle success and error
|
||||
* @param callback [ApngDecoder.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")
|
||||
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(
|
||||
context,
|
||||
inputStream,
|
||||
speed
|
||||
)
|
||||
|
@ -496,7 +602,7 @@ class ApngDecoder {
|
|||
}
|
||||
callback?.onSuccess(drawable)
|
||||
}
|
||||
} catch (e : java.lang.Exception) {
|
||||
} catch (e: Exception) {
|
||||
withContext(Dispatchers.Main) {
|
||||
callback?.onError(e)
|
||||
}
|
||||
|
@ -505,21 +611,27 @@ class ApngDecoder {
|
|||
}
|
||||
|
||||
/**
|
||||
* Load Apng into an imageView, asynchronously
|
||||
* @param context Context
|
||||
* @param res Raw resource to load
|
||||
* 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 [ApngDecoder.Callback] to handle success and error
|
||||
* @param callback [ApngDecoder.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) {
|
||||
fun decodeApngAsyncInto(
|
||||
context: Context, @RawRes res: Int,
|
||||
imageView: ImageView,
|
||||
speed: Float = 1f,
|
||||
callback: Callback? = null
|
||||
) {
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
val drawable =
|
||||
decodeApng(
|
||||
context,
|
||||
context.resources.openRawResource(res),
|
||||
speed
|
||||
)
|
||||
|
@ -531,7 +643,7 @@ class ApngDecoder {
|
|||
}
|
||||
callback?.onSuccess(drawable)
|
||||
}
|
||||
} catch (e : java.lang.Exception) {
|
||||
} catch (e: Exception) {
|
||||
withContext(Dispatchers.Main) {
|
||||
callback?.onError(e)
|
||||
}
|
||||
|
@ -541,21 +653,28 @@ class ApngDecoder {
|
|||
}
|
||||
|
||||
/**
|
||||
* Load Apng into an imageView, asynchronously
|
||||
* @param context Context
|
||||
* @param url URL to load
|
||||
* Load Apng into an imageView, asynchronously.
|
||||
* @param context Context.
|
||||
* @param url URL to load.
|
||||
* @param imageView Image View.
|
||||
* @param speed Optional parameter.
|
||||
* @param callback [ApngDecoder.Callback] to handle success and error
|
||||
* @param callback [ApngDecoder.Callback] to handle success and error.
|
||||
*/
|
||||
@Suppress("unused")
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun decodeApngAsyncInto(context : Context, url : URL, imageView: ImageView, speed: Float = 1f, callback: Callback? = null) {
|
||||
fun decodeApngAsyncInto(
|
||||
context: Context,
|
||||
url: URL,
|
||||
imageView: ImageView,
|
||||
speed: Float = 1f,
|
||||
callback: Callback? = null
|
||||
) {
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
val drawable =
|
||||
decodeApng(
|
||||
context,
|
||||
FileInputStream(
|
||||
Loader.load(
|
||||
context,
|
||||
|
@ -572,7 +691,7 @@ class ApngDecoder {
|
|||
}
|
||||
callback?.onSuccess(drawable)
|
||||
}
|
||||
} catch (e : java.lang.Exception) {
|
||||
} catch (e: Exception) {
|
||||
withContext(Dispatchers.Main) {
|
||||
callback?.onError(e)
|
||||
}
|
||||
|
@ -581,17 +700,23 @@ class ApngDecoder {
|
|||
}
|
||||
|
||||
/**
|
||||
* Load Apng into an imageView, asynchronously
|
||||
* @param context Context
|
||||
* Load Apng into an imageView, asynchronously.
|
||||
* @param context Context.
|
||||
* @param string URL to load
|
||||
* @param imageView Image View.
|
||||
* @param speed Optional parameter.
|
||||
* @param callback [ApngDecoder.Callback] to handle success and error
|
||||
* @param callback [ApngDecoder.Callback] to handle success and error.
|
||||
*/
|
||||
@Suppress("unused")
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun decodeApngAsyncInto(context : Context, string: String, imageView: ImageView, speed: Float = 1f, callback: Callback? = null) {
|
||||
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://")) {
|
||||
|
@ -602,8 +727,9 @@ class ApngDecoder {
|
|||
speed,
|
||||
callback
|
||||
)
|
||||
} else if(File(string).exists()) {
|
||||
var pathToLoad = if (string.startsWith("content://")) string else "file://$string"
|
||||
} else if (File(string).exists()) {
|
||||
var pathToLoad =
|
||||
if (string.startsWith("content://")) string else "file://$string"
|
||||
pathToLoad = pathToLoad.replace("%", "%25").replace("#", "%23")
|
||||
decodeApngAsyncInto(
|
||||
context,
|
||||
|
@ -615,6 +741,7 @@ class ApngDecoder {
|
|||
} else if (string.startsWith("file://android_asset/")) {
|
||||
val drawable =
|
||||
decodeApng(
|
||||
context,
|
||||
context.assets.open(string.replace("file:///android_asset/", "")),
|
||||
speed
|
||||
)
|
||||
|
@ -631,7 +758,7 @@ class ApngDecoder {
|
|||
callback?.onError(java.lang.Exception("Cannot open string"))
|
||||
}
|
||||
}
|
||||
} catch (e : java.lang.Exception) {
|
||||
} catch (e: Exception) {
|
||||
withContext(Dispatchers.Main) {
|
||||
callback?.onError(e)
|
||||
}
|
||||
|
@ -640,20 +767,27 @@ class ApngDecoder {
|
|||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* 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: IHDR, width : Int, height : Int) : ByteArray {
|
||||
private fun generateIhdr(ihdrOfApng: IHDR, width: Int, height: Int): ByteArray {
|
||||
val ihdr = ArrayList<Byte>()
|
||||
// We need a body var to know body length and generate crc
|
||||
val ihdrBody = ArrayList<Byte>()
|
||||
// Add chunk body length
|
||||
ihdr.addAll(Utils.to4Bytes(ihdrOfApng.body.size).asList())
|
||||
// Add IHDR
|
||||
ihdrBody.addAll(byteArrayOf(0x49.toByte(), 0x48.toByte(), 0x44.toByte(), 0x52.toByte()).asList())
|
||||
ihdrBody.addAll(
|
||||
byteArrayOf(
|
||||
0x49.toByte(),
|
||||
0x48.toByte(),
|
||||
0x44.toByte(),
|
||||
0x52.toByte()
|
||||
).asList()
|
||||
)
|
||||
// Add the max width and height
|
||||
ihdrBody.addAll(Utils.to4Bytes(width).asList())
|
||||
ihdrBody.addAll(Utils.to4Bytes(height).asList())
|
||||
|
|
|
@ -44,7 +44,7 @@ class ApngDecoderFragment : Fragment() {
|
|||
|
||||
override fun onError(error: Exception) {
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.e(TAG, "onError", error)
|
||||
Log.e(TAG, "onError : $error")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ import androidx.fragment.app.Fragment;
|
|||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import oupson.apng.decoder.ApngDecoder;
|
||||
import oupson.apngcreator.BuildConfig;
|
||||
import oupson.apngcreator.R;
|
||||
|
||||
|
||||
|
@ -24,7 +25,8 @@ public class JavaFragment extends Fragment {
|
|||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
Log.i(TAG, "onCreateView()");
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.i(TAG, "onCreateView()");
|
||||
|
||||
View v = inflater.inflate(R.layout.fragment_java, container, false);
|
||||
|
||||
|
@ -49,12 +51,14 @@ public class JavaFragment extends Fragment {
|
|||
ApngDecoder.decodeApngAsyncInto(context, imageUrl, imageView, 1f, new ApngDecoder.Callback() {
|
||||
@Override
|
||||
public void onSuccess(@NotNull Drawable drawable) {
|
||||
Log.i(TAG, "Success");
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.i(TAG, "Success");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@NotNull Exception error) {
|
||||
Log.e(TAG, "Error", error);
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.e(TAG, "Error : " + error.toString());
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -87,12 +87,17 @@ class KotlinFragment : Fragment() {
|
|||
})
|
||||
|
||||
if (animator == null) {
|
||||
animator = apngImageView?.loadApng(imageUrls[selected])?.apply {
|
||||
onLoaded {
|
||||
setOnFrameChangeLister {
|
||||
// Log.e("app-test", "onLoop")
|
||||
try {
|
||||
animator = apngImageView?.loadApng(imageUrls[selected])?.apply {
|
||||
onLoaded {
|
||||
setOnFrameChangeLister {
|
||||
// Log.e("app-test", "onLoop")
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e : Exception) {
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.e(TAG, "Error : $e")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ImageView
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/viewerImageView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ImageView
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/ApngImageView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
|
@ -22,7 +22,7 @@
|
|||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:contentDescription="@string/description_viewer_imageView"/>
|
||||
|
||||
<ImageView
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/NormalImageView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
|
@ -36,7 +36,7 @@
|
|||
app:layout_constraintTop_toBottomOf="@id/ApngImageView"
|
||||
android:contentDescription="@string/description_viewer_imageView" />
|
||||
|
||||
<SeekBar
|
||||
<androidx.appcompat.widget.AppCompatSeekBar
|
||||
android:id="@+id/SpeedSeekBar"
|
||||
style="@style/Widget.AppCompat.SeekBar"
|
||||
android:layout_width="0dp"
|
||||
|
@ -49,24 +49,26 @@
|
|||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<Button
|
||||
<androidx.appcompat.widget.AppCompatButton
|
||||
android:id="@+id/PlayButton"
|
||||
style="@android:style/Widget.Material.Light.Button.Borderless.Colored"
|
||||
android:theme="@style/Widget.AppCompat.Button.Borderless.Colored"
|
||||
android:backgroundTint="@color/colorPrimary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:background="@android:color/white"
|
||||
android:textColor="@color/control"
|
||||
android:text="@string/title_playButton"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<Button
|
||||
<androidx.appcompat.widget.AppCompatButton
|
||||
android:id="@+id/PauseButton"
|
||||
style="@style/Widget.AppCompat.Button.Borderless.Colored"
|
||||
android:theme="@style/Widget.AppCompat.Button.Borderless.Colored"
|
||||
android:backgroundTint="@color/colorPrimary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:background="@android:color/white"
|
||||
android:textColor="@color/control"
|
||||
android:text="@string/title_pauseButton"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
<item name="android:windowLightStatusBar" tools:targetApi="m">@bool/is_theme_light</item>
|
||||
<item name="android:navigationBarColor">@color/colorPrimary</item>
|
||||
<item name="android:windowLightNavigationBar" tools:targetApi="o_mr1">@bool/nav_bar_light</item>
|
||||
<item name="colorControlHighlight">@color/colorPrimary</item>
|
||||
</style>
|
||||
|
||||
<style name="AppShapeAppearance.MediumComponent" parent="ShapeAppearance.MaterialComponents.MediumComponent">
|
||||
|
|
Loading…
Reference in New Issue