parent
ffb60346a7
commit
7ca74af4d4
|
@ -5,6 +5,7 @@ import android.graphics.drawable.AnimationDrawable
|
||||||
import android.graphics.drawable.BitmapDrawable
|
import android.graphics.drawable.BitmapDrawable
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
import junit.framework.TestCase
|
import junit.framework.TestCase
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import oupson.apng.Utils.Companion.areBitmapSimilar
|
import oupson.apng.Utils.Companion.areBitmapSimilar
|
||||||
import oupson.apng.Utils.Companion.getFrame
|
import oupson.apng.Utils.Companion.getFrame
|
||||||
|
@ -16,11 +17,13 @@ class ApngDecoderInstrumentedTest {
|
||||||
fun testBtmConfigDecoding() {
|
fun testBtmConfigDecoding() {
|
||||||
val context = InstrumentationRegistry.getInstrumentation().context
|
val context = InstrumentationRegistry.getInstrumentation().context
|
||||||
val input = context.assets.open("sushi.png")
|
val input = context.assets.open("sushi.png")
|
||||||
val anim = ApngDecoder.decodeApng(
|
val anim = runBlocking {
|
||||||
context,
|
ApngDecoder.decodeApng(
|
||||||
input,
|
context,
|
||||||
ApngDecoder.Config(bitmapConfig = Bitmap.Config.RGB_565)
|
input,
|
||||||
) as AnimationDrawable
|
ApngDecoder.Config(bitmapConfig = Bitmap.Config.RGB_565)
|
||||||
|
) as AnimationDrawable
|
||||||
|
}
|
||||||
|
|
||||||
for (i in 0 until anim.numberOfFrames) {
|
for (i in 0 until anim.numberOfFrames) {
|
||||||
TestCase.assertTrue((anim.getFrame(i) as BitmapDrawable).bitmap.config == Bitmap.Config.RGB_565)
|
TestCase.assertTrue((anim.getFrame(i) as BitmapDrawable).bitmap.config == Bitmap.Config.RGB_565)
|
||||||
|
@ -33,11 +36,13 @@ class ApngDecoderInstrumentedTest {
|
||||||
val list = context.assets.list("bunny")?.map { getFrame(context, "bunny/$it") }!!
|
val list = context.assets.list("bunny")?.map { getFrame(context, "bunny/$it") }!!
|
||||||
|
|
||||||
val input = context.assets.open("bugbuckbunny.png")
|
val input = context.assets.open("bugbuckbunny.png")
|
||||||
val anim = ApngDecoder.decodeApng(
|
val anim = runBlocking {
|
||||||
context,
|
ApngDecoder.decodeApng(
|
||||||
input,
|
context,
|
||||||
ApngDecoder.Config(bitmapConfig = Bitmap.Config.ARGB_8888, decodeCoverFrame = true)
|
input,
|
||||||
) as ApngDrawable
|
ApngDecoder.Config(bitmapConfig = Bitmap.Config.ARGB_8888, decodeCoverFrame = true)
|
||||||
|
) as ApngDrawable
|
||||||
|
}
|
||||||
|
|
||||||
TestCase.assertTrue(areBitmapSimilar(list[0], anim.coverFrame!!))
|
TestCase.assertTrue(areBitmapSimilar(list[0], anim.coverFrame!!))
|
||||||
for (i in 0 until anim.numberOfFrames) {
|
for (i in 0 until anim.numberOfFrames) {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import android.graphics.drawable.BitmapDrawable
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
import junit.framework.TestCase.assertFalse
|
import junit.framework.TestCase.assertFalse
|
||||||
import junit.framework.TestCase.assertTrue
|
import junit.framework.TestCase.assertTrue
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import oupson.apng.Utils.Companion.areBitmapSimilar
|
import oupson.apng.Utils.Companion.areBitmapSimilar
|
||||||
import oupson.apng.Utils.Companion.getFrame
|
import oupson.apng.Utils.Companion.getFrame
|
||||||
|
@ -57,8 +58,9 @@ class ApngEncoderInstrumentedTest {
|
||||||
val list = context.assets.list("ball")?.map { getFrame(context, "ball/$it") }!!
|
val list = context.assets.list("ball")?.map { getFrame(context, "ball/$it") }!!
|
||||||
|
|
||||||
val optimisedOutputStream = ByteArrayOutputStream()
|
val optimisedOutputStream = ByteArrayOutputStream()
|
||||||
val optimisedEncoder = ApngEncoder(optimisedOutputStream, list[0].width, list[0].height, list.size)
|
val optimisedEncoder =
|
||||||
.setOptimiseApng(true)
|
ApngEncoder(optimisedOutputStream, list[0].width, list[0].height, list.size)
|
||||||
|
.setOptimiseApng(true)
|
||||||
|
|
||||||
list.forEach {
|
list.forEach {
|
||||||
optimisedEncoder.writeFrame(it)
|
optimisedEncoder.writeFrame(it)
|
||||||
|
@ -70,8 +72,14 @@ class ApngEncoderInstrumentedTest {
|
||||||
val bytes = optimisedOutputStream.toByteArray()
|
val bytes = optimisedOutputStream.toByteArray()
|
||||||
val optimisedInputStream = ByteArrayInputStream(bytes)
|
val optimisedInputStream = ByteArrayInputStream(bytes)
|
||||||
|
|
||||||
|
|
||||||
val optimisedApng =
|
val optimisedApng =
|
||||||
ApngDecoder.decodeApng(context, optimisedInputStream) as AnimationDrawable
|
runBlocking {
|
||||||
|
ApngDecoder.decodeApng(
|
||||||
|
context,
|
||||||
|
optimisedInputStream
|
||||||
|
) as AnimationDrawable
|
||||||
|
}
|
||||||
|
|
||||||
optimisedInputStream.close()
|
optimisedInputStream.close()
|
||||||
|
|
||||||
|
@ -86,8 +94,9 @@ class ApngEncoderInstrumentedTest {
|
||||||
|
|
||||||
val nonOptimisedOutputStream = ByteArrayOutputStream()
|
val nonOptimisedOutputStream = ByteArrayOutputStream()
|
||||||
|
|
||||||
val nonOptimisedEncoder = ApngEncoder(nonOptimisedOutputStream, list[0].width, list[0].height, list.size)
|
val nonOptimisedEncoder =
|
||||||
.setOptimiseApng(false)
|
ApngEncoder(nonOptimisedOutputStream, list[0].width, list[0].height, list.size)
|
||||||
|
.setOptimiseApng(false)
|
||||||
|
|
||||||
list.forEach {
|
list.forEach {
|
||||||
nonOptimisedEncoder.writeFrame(it)
|
nonOptimisedEncoder.writeFrame(it)
|
||||||
|
@ -101,7 +110,12 @@ class ApngEncoderInstrumentedTest {
|
||||||
val nonOptimisedInputStream = ByteArrayInputStream(nonOptimisedBytes)
|
val nonOptimisedInputStream = ByteArrayInputStream(nonOptimisedBytes)
|
||||||
|
|
||||||
val nonOptimisedApng =
|
val nonOptimisedApng =
|
||||||
ApngDecoder.decodeApng(context, nonOptimisedInputStream) as AnimationDrawable
|
runBlocking {
|
||||||
|
ApngDecoder.decodeApng(
|
||||||
|
context,
|
||||||
|
nonOptimisedInputStream
|
||||||
|
) as AnimationDrawable
|
||||||
|
}
|
||||||
nonOptimisedInputStream.close()
|
nonOptimisedInputStream.close()
|
||||||
|
|
||||||
for (i in 0 until optimisedApng.numberOfFrames) {
|
for (i in 0 until optimisedApng.numberOfFrames) {
|
||||||
|
@ -120,8 +134,9 @@ class ApngEncoderInstrumentedTest {
|
||||||
val list = context.assets.list("bunny")?.map { getFrame(context, "bunny/$it") }!!
|
val list = context.assets.list("bunny")?.map { getFrame(context, "bunny/$it") }!!
|
||||||
|
|
||||||
val optimisedOutputStream = ByteArrayOutputStream()
|
val optimisedOutputStream = ByteArrayOutputStream()
|
||||||
val optimisedEncoder = ApngEncoder(optimisedOutputStream, list[0].width, list[0].height, list.size)
|
val optimisedEncoder =
|
||||||
.setOptimiseApng(true)
|
ApngEncoder(optimisedOutputStream, list[0].width, list[0].height, list.size)
|
||||||
|
.setOptimiseApng(true)
|
||||||
|
|
||||||
list.forEach {
|
list.forEach {
|
||||||
optimisedEncoder.writeFrame(it)
|
optimisedEncoder.writeFrame(it)
|
||||||
|
@ -134,7 +149,12 @@ class ApngEncoderInstrumentedTest {
|
||||||
val optimisedInputStream = ByteArrayInputStream(bytes)
|
val optimisedInputStream = ByteArrayInputStream(bytes)
|
||||||
|
|
||||||
val optimisedApng =
|
val optimisedApng =
|
||||||
ApngDecoder.decodeApng(context, optimisedInputStream) as AnimationDrawable
|
runBlocking {
|
||||||
|
ApngDecoder.decodeApng(
|
||||||
|
context,
|
||||||
|
optimisedInputStream
|
||||||
|
) as AnimationDrawable
|
||||||
|
}
|
||||||
|
|
||||||
optimisedInputStream.close()
|
optimisedInputStream.close()
|
||||||
|
|
||||||
|
@ -149,8 +169,9 @@ class ApngEncoderInstrumentedTest {
|
||||||
|
|
||||||
val nonOptimisedOutputStream = ByteArrayOutputStream()
|
val nonOptimisedOutputStream = ByteArrayOutputStream()
|
||||||
|
|
||||||
val nonOptimisedEncoder = ApngEncoder(nonOptimisedOutputStream, list[0].width, list[0].height, list.size)
|
val nonOptimisedEncoder =
|
||||||
.setOptimiseApng(false)
|
ApngEncoder(nonOptimisedOutputStream, list[0].width, list[0].height, list.size)
|
||||||
|
.setOptimiseApng(false)
|
||||||
|
|
||||||
list.forEach {
|
list.forEach {
|
||||||
nonOptimisedEncoder.writeFrame(it)
|
nonOptimisedEncoder.writeFrame(it)
|
||||||
|
@ -164,7 +185,12 @@ class ApngEncoderInstrumentedTest {
|
||||||
val nonOptimisedInputStream = ByteArrayInputStream(nonOptimisedBytes)
|
val nonOptimisedInputStream = ByteArrayInputStream(nonOptimisedBytes)
|
||||||
|
|
||||||
val nonOptimisedApng =
|
val nonOptimisedApng =
|
||||||
ApngDecoder.decodeApng(context, nonOptimisedInputStream) as AnimationDrawable
|
runBlocking {
|
||||||
|
ApngDecoder.decodeApng(
|
||||||
|
context,
|
||||||
|
nonOptimisedInputStream
|
||||||
|
) as AnimationDrawable
|
||||||
|
}
|
||||||
nonOptimisedInputStream.close()
|
nonOptimisedInputStream.close()
|
||||||
|
|
||||||
for (i in 0 until optimisedApng.numberOfFrames) {
|
for (i in 0 until optimisedApng.numberOfFrames) {
|
||||||
|
|
|
@ -49,22 +49,23 @@ class ApngDecoder {
|
||||||
internal var bitmapConfig: Bitmap.Config = Bitmap.Config.ARGB_8888,
|
internal var bitmapConfig: Bitmap.Config = Bitmap.Config.ARGB_8888,
|
||||||
internal var decodeCoverFrame: Boolean = false
|
internal var decodeCoverFrame: Boolean = false
|
||||||
) {
|
) {
|
||||||
fun getSpeed() : Float = this.speed
|
fun getSpeed(): Float = this.speed
|
||||||
fun setSpeed(speed : Float) : Config {
|
fun setSpeed(speed: Float): Config {
|
||||||
this.speed = speed
|
this.speed = speed
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getBitmapConfig() : Bitmap.Config = this.bitmapConfig
|
fun getBitmapConfig(): Bitmap.Config = this.bitmapConfig
|
||||||
fun setBitmapConfig(config : Bitmap.Config) : Config {
|
fun setBitmapConfig(config: Bitmap.Config): Config {
|
||||||
this.bitmapConfig = config
|
this.bitmapConfig = config
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isDecodingCoverFrame() : Boolean {
|
fun isDecodingCoverFrame(): Boolean {
|
||||||
return this.decodeCoverFrame
|
return this.decodeCoverFrame
|
||||||
}
|
}
|
||||||
fun setIsDecodingCoverFrame(decodeCoverFrame : Boolean) : Config {
|
|
||||||
|
fun setIsDecodingCoverFrame(decodeCoverFrame: Boolean): Config {
|
||||||
this.decodeCoverFrame = decodeCoverFrame
|
this.decodeCoverFrame = decodeCoverFrame
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
@ -91,7 +92,10 @@ class ApngDecoder {
|
||||||
* @return [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].
|
* @return [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].
|
||||||
*/
|
*/
|
||||||
// TODO DOCUMENT CONFIG
|
// TODO DOCUMENT CONFIG
|
||||||
@Suppress("MemberVisibilityCanBePrivate")
|
@Suppress(
|
||||||
|
"MemberVisibilityCanBePrivate",
|
||||||
|
"BlockingMethodInNonBlockingContext"
|
||||||
|
) // BlockingMethodInNonBlockingContext is a warning generated by java @Throws
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
suspend fun decodeApng(
|
suspend fun decodeApng(
|
||||||
|
@ -133,23 +137,23 @@ class ApngDecoder {
|
||||||
var byteRead: Int
|
var byteRead: Int
|
||||||
val lengthChunk = ByteArray(4)
|
val lengthChunk = ByteArray(4)
|
||||||
do {
|
do {
|
||||||
val length : Int
|
val length: Int
|
||||||
val chunk : ByteArray
|
val chunk: ByteArray
|
||||||
if (withContext(Dispatchers.IO) {
|
if (withContext(Dispatchers.IO) {
|
||||||
byteRead = inputStream.read(lengthChunk)
|
byteRead = inputStream.read(lengthChunk)
|
||||||
|
|
||||||
|
|
||||||
if (byteRead != -1) {
|
if (byteRead != -1) {
|
||||||
length = Utils.uIntFromBytesBigEndian(lengthChunk)
|
length = Utils.uIntFromBytesBigEndian(lengthChunk)
|
||||||
|
|
||||||
chunk = ByteArray(length + 8)
|
chunk = ByteArray(length + 8)
|
||||||
byteRead = inputStream.read(chunk)
|
byteRead = inputStream.read(chunk)
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
chunk = ByteArray(0)
|
chunk = ByteArray(0)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -534,7 +538,7 @@ class ApngDecoder {
|
||||||
inputStream.reset()
|
inputStream.reset()
|
||||||
|
|
||||||
return@withContext if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
return@withContext if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
val bytesRead : ByteArray
|
val bytesRead: ByteArray
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
bytesRead = inputStream.readBytes()
|
bytesRead = inputStream.readBytes()
|
||||||
inputStream.close()
|
inputStream.close()
|
||||||
|
@ -564,7 +568,7 @@ class ApngDecoder {
|
||||||
* @param config Decoder configuration
|
* @param config Decoder configuration
|
||||||
* @return [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].
|
* @return [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].
|
||||||
*/
|
*/
|
||||||
@Suppress("unused")
|
@Suppress("unused", "BlockingMethodInNonBlockingContext")
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
// TODO DOCUMENT
|
// TODO DOCUMENT
|
||||||
suspend fun decodeApng(
|
suspend fun decodeApng(
|
||||||
|
@ -574,7 +578,7 @@ class ApngDecoder {
|
||||||
): Drawable =
|
): Drawable =
|
||||||
decodeApng(
|
decodeApng(
|
||||||
context,
|
context,
|
||||||
FileInputStream(file), config
|
withContext(Dispatchers.IO) { FileInputStream(file) }, config
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -649,7 +653,7 @@ class ApngDecoder {
|
||||||
* @param callback [ApngDecoder.Callback] to handle success and error.
|
* @param callback [ApngDecoder.Callback] to handle success and error.
|
||||||
* @param config Decoder configuration
|
* @param config Decoder configuration
|
||||||
*/
|
*/
|
||||||
@Suppress("unused")
|
@Suppress("unused", "BlockingMethodInNonBlockingContext")
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
fun decodeApngAsyncInto(
|
fun decodeApngAsyncInto(
|
||||||
|
@ -658,7 +662,7 @@ class ApngDecoder {
|
||||||
imageView: ImageView,
|
imageView: ImageView,
|
||||||
callback: Callback? = null,
|
callback: Callback? = null,
|
||||||
config: Config = Config(),
|
config: Config = Config(),
|
||||||
scope : CoroutineScope = GlobalScope
|
scope: CoroutineScope = GlobalScope
|
||||||
) {
|
) {
|
||||||
scope.launch(Dispatchers.Default) {
|
scope.launch(Dispatchers.Default) {
|
||||||
try {
|
try {
|
||||||
|
@ -666,8 +670,8 @@ class ApngDecoder {
|
||||||
decodeApng(
|
decodeApng(
|
||||||
context,
|
context,
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
FileInputStream(file)
|
FileInputStream(file)
|
||||||
},
|
},
|
||||||
config
|
config
|
||||||
)
|
)
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
|
@ -703,7 +707,7 @@ class ApngDecoder {
|
||||||
imageView: ImageView,
|
imageView: ImageView,
|
||||||
callback: Callback? = null,
|
callback: Callback? = null,
|
||||||
config: Config = Config(),
|
config: Config = Config(),
|
||||||
scope : CoroutineScope = GlobalScope
|
scope: CoroutineScope = GlobalScope
|
||||||
) {
|
) {
|
||||||
val inputStream = context.contentResolver.openInputStream(uri)!!
|
val inputStream = context.contentResolver.openInputStream(uri)!!
|
||||||
scope.launch(Dispatchers.Default) {
|
scope.launch(Dispatchers.Default) {
|
||||||
|
@ -746,7 +750,7 @@ class ApngDecoder {
|
||||||
imageView: ImageView,
|
imageView: ImageView,
|
||||||
callback: Callback? = null,
|
callback: Callback? = null,
|
||||||
config: Config = Config(),
|
config: Config = Config(),
|
||||||
scope : CoroutineScope = GlobalScope
|
scope: CoroutineScope = GlobalScope
|
||||||
) {
|
) {
|
||||||
scope.launch(Dispatchers.Default) {
|
scope.launch(Dispatchers.Default) {
|
||||||
try {
|
try {
|
||||||
|
@ -781,7 +785,7 @@ class ApngDecoder {
|
||||||
* @param callback [ApngDecoder.Callback] to handle success and error.
|
* @param callback [ApngDecoder.Callback] to handle success and error.
|
||||||
* @param config Decoder configuration
|
* @param config Decoder configuration
|
||||||
*/
|
*/
|
||||||
@Suppress("unused")
|
@Suppress("unused", "BlockingMethodInNonBlockingContext")
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
fun decodeApngAsyncInto(
|
fun decodeApngAsyncInto(
|
||||||
|
@ -790,7 +794,7 @@ class ApngDecoder {
|
||||||
imageView: ImageView,
|
imageView: ImageView,
|
||||||
callback: Callback? = null,
|
callback: Callback? = null,
|
||||||
config: Config = Config(),
|
config: Config = Config(),
|
||||||
scope : CoroutineScope = GlobalScope
|
scope: CoroutineScope = GlobalScope
|
||||||
) {
|
) {
|
||||||
scope.launch(Dispatchers.Default) {
|
scope.launch(Dispatchers.Default) {
|
||||||
try {
|
try {
|
||||||
|
@ -836,9 +840,9 @@ class ApngDecoder {
|
||||||
imageView: ImageView,
|
imageView: ImageView,
|
||||||
callback: Callback? = null,
|
callback: Callback? = null,
|
||||||
config: Config = Config(),
|
config: Config = Config(),
|
||||||
scope : CoroutineScope = GlobalScope
|
scope: CoroutineScope = GlobalScope
|
||||||
) {
|
) {
|
||||||
scope.launch(Dispatchers.Default) {
|
scope.launch(Dispatchers.Default) {
|
||||||
try {
|
try {
|
||||||
if (string.startsWith("http://") || string.startsWith("https://")) {
|
if (string.startsWith("http://") || string.startsWith("https://")) {
|
||||||
decodeApngAsyncInto(
|
decodeApngAsyncInto(
|
||||||
|
@ -877,7 +881,7 @@ class ApngDecoder {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
callback?.onError(java.lang.Exception("Cannot open string"))
|
callback?.onError(Exception("Cannot open string"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|
Loading…
Reference in New Issue