New instrumented tests

This commit is contained in:
Oupson 2021-02-24 15:38:38 +01:00
parent 6b90282056
commit d5011f3ee8
8 changed files with 153 additions and 81 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

View File

@ -0,0 +1,52 @@
package oupson.apng
import android.graphics.Bitmap
import android.graphics.drawable.AnimationDrawable
import android.graphics.drawable.BitmapDrawable
import androidx.test.platform.app.InstrumentationRegistry
import junit.framework.TestCase
import org.junit.Test
import oupson.apng.Utils.Companion.areBitmapSimilar
import oupson.apng.Utils.Companion.getFrame
import oupson.apng.decoder.ApngDecoder
import oupson.apng.drawable.ApngDrawable
class ApngDecoderInstrumentedTest {
@Test
fun testBtmConfigDecoding() {
val context = InstrumentationRegistry.getInstrumentation().context
val input = context.assets.open("sushi.png")
val anim = ApngDecoder.decodeApng(
context,
input,
ApngDecoder.Config(bitmapConfig = Bitmap.Config.RGB_565)
) as AnimationDrawable
for (i in 0 until anim.numberOfFrames) {
TestCase.assertTrue((anim.getFrame(i) as BitmapDrawable).bitmap.config == Bitmap.Config.RGB_565)
}
}
@Test
fun testBigBuckBunny() {
val context = InstrumentationRegistry.getInstrumentation().context
val list = context.assets.list("bunny")?.map { getFrame(context, "bunny/$it") }!!
val input = context.assets.open("bugbuckbunny.png")
val anim = ApngDecoder.decodeApng(
context,
input,
ApngDecoder.Config(bitmapConfig = Bitmap.Config.ARGB_8888, decodeCoverFrame = true)
) as ApngDrawable
TestCase.assertTrue(areBitmapSimilar(list[0], anim.coverFrame!!))
for (i in 0 until anim.numberOfFrames) {
TestCase.assertTrue(
areBitmapSimilar(
(anim.getFrame(i) as BitmapDrawable).bitmap,
list[i + 1]
)
)
}
}
}

View File

@ -1,15 +1,14 @@
package oupson.apng package oupson.apng
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Color
import android.graphics.drawable.AnimationDrawable 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.assertFalse import junit.framework.TestCase.assertFalse
import junit.framework.TestCase.assertTrue import junit.framework.TestCase.assertTrue
import org.junit.Test import org.junit.Test
import oupson.apng.Utils.Companion.areBitmapSimilar
import oupson.apng.Utils.Companion.getFrame
import oupson.apng.Utils.Companion.isSimilar
import oupson.apng.decoder.ApngDecoder import oupson.apng.decoder.ApngDecoder
import oupson.apng.encoder.ApngEncoder import oupson.apng.encoder.ApngEncoder
import oupson.apng.utils.Utils import oupson.apng.utils.Utils
@ -78,7 +77,7 @@ class ApngEncoderInstrumentedTest {
for (i in 0 until optimisedApng.numberOfFrames) { for (i in 0 until optimisedApng.numberOfFrames) {
assertTrue( assertTrue(
isBitmapSimilar( areBitmapSimilar(
list[i], list[i],
(optimisedApng.getFrame(i) as BitmapDrawable).bitmap (optimisedApng.getFrame(i) as BitmapDrawable).bitmap
) )
@ -107,7 +106,7 @@ class ApngEncoderInstrumentedTest {
for (i in 0 until optimisedApng.numberOfFrames) { for (i in 0 until optimisedApng.numberOfFrames) {
assertTrue( assertTrue(
isBitmapSimilar( areBitmapSimilar(
(optimisedApng.getFrame(i) as BitmapDrawable).bitmap, (optimisedApng.getFrame(i) as BitmapDrawable).bitmap,
(nonOptimisedApng.getFrame(i) as BitmapDrawable).bitmap (nonOptimisedApng.getFrame(i) as BitmapDrawable).bitmap
) )
@ -141,7 +140,7 @@ class ApngEncoderInstrumentedTest {
for (i in 0 until optimisedApng.numberOfFrames) { for (i in 0 until optimisedApng.numberOfFrames) {
assertTrue( assertTrue(
isBitmapSimilar( areBitmapSimilar(
list[i], list[i],
(optimisedApng.getFrame(i) as BitmapDrawable).bitmap (optimisedApng.getFrame(i) as BitmapDrawable).bitmap
) )
@ -170,54 +169,12 @@ class ApngEncoderInstrumentedTest {
for (i in 0 until optimisedApng.numberOfFrames) { for (i in 0 until optimisedApng.numberOfFrames) {
assertTrue( assertTrue(
isBitmapSimilar( areBitmapSimilar(
(optimisedApng.getFrame(i) as BitmapDrawable).bitmap, (optimisedApng.getFrame(i) as BitmapDrawable).bitmap,
(nonOptimisedApng.getFrame(i) as BitmapDrawable).bitmap (nonOptimisedApng.getFrame(i) as BitmapDrawable).bitmap
) )
) )
}
}
private fun isSimilar(buffer : Bitmap, frame : Bitmap, diff : Utils.Companion.DiffResult) : Boolean {
val btm = buffer.copy(Bitmap.Config.ARGB_8888, true)
for (y in 0 until diff.bitmap.height) {
for (x in 0 until diff.bitmap.width) {
val p = diff.bitmap.getPixel(x, y)
if (p != Color.TRANSPARENT || p == Color.TRANSPARENT && diff.blendOp == Utils.Companion.BlendOp.APNG_BLEND_OP_SOURCE)
btm.setPixel(diff.offsetX + x, diff.offsetY + y, p)
} }
} }
for (y in 0 until buffer.height) {
for (x in 0 until buffer.width) {
if (frame.getPixel(x, y) != btm.getPixel(x, y)) {
return false
}
}
}
return true
}
fun isBitmapSimilar(btm1 : Bitmap, btm2 : Bitmap) : Boolean {
for (y in 0 until btm1.height) {
for (x in 0 until btm1.width) {
if (btm1.getPixel(x, y) != btm2.getPixel(x, y)) {
return false
}
}
}
return true
}
private fun getFrame(context: Context, name: String) : Bitmap {
val inputStream = context.assets.open(name)
val bitmap = BitmapFactory.decodeStream(inputStream, null, BitmapFactory.Options().apply {
inPreferredConfig = Bitmap.Config.ARGB_8888
})
inputStream.close()
return bitmap!!
}
} }

View File

@ -0,0 +1,54 @@
package oupson.apng
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Color
import oupson.apng.utils.Utils
class Utils {
companion object {
fun getFrame(context: Context, name: String, btmConfig : Bitmap.Config = Bitmap.Config.ARGB_8888) : Bitmap {
val inputStream = context.assets.open(name)
val bitmap = BitmapFactory.decodeStream(inputStream, null, BitmapFactory.Options().apply {
inPreferredConfig = btmConfig
})
inputStream.close()
return bitmap!!
}
fun isSimilar(buffer : Bitmap, frame : Bitmap, diff : Utils.Companion.DiffResult) : Boolean {
val btm = buffer.copy(Bitmap.Config.ARGB_8888, true)
for (y in 0 until diff.bitmap.height) {
for (x in 0 until diff.bitmap.width) {
val p = diff.bitmap.getPixel(x, y)
if (p != Color.TRANSPARENT || p == Color.TRANSPARENT && diff.blendOp == Utils.Companion.BlendOp.APNG_BLEND_OP_SOURCE)
btm.setPixel(diff.offsetX + x, diff.offsetY + y, p)
}
}
for (y in 0 until buffer.height) {
for (x in 0 until buffer.width) {
if (frame.getPixel(x, y) != btm.getPixel(x, y)) {
return false
}
}
}
return true
}
fun areBitmapSimilar(btm1 : Bitmap, btm2 : Bitmap) : Boolean {
for (y in 0 until btm1.height) {
for (x in 0 until btm1.width) {
if (btm1.getPixel(x, y) != btm2.getPixel(x, y)) {
return false
}
}
}
return true
}
}
}

View File

@ -47,11 +47,31 @@ class ApngDecoder {
fun onError(error: Exception) fun onError(error: Exception)
} }
data class Config( class Config(
val speed: Float = 1f, internal var speed: Float = 1f,
val bitmapConfig: Bitmap.Config = Bitmap.Config.ARGB_8888, internal var bitmapConfig: Bitmap.Config = Bitmap.Config.ARGB_8888,
val decodeCoverFrame: Boolean = true internal var decodeCoverFrame: Boolean = false
) ) {
fun getSpeed() : Float = this.speed
fun setSpeed(speed : Float) : Config {
this.speed = speed
return this
}
fun getBitmapConfig() : Bitmap.Config = this.bitmapConfig
fun setBitmapConfig(config : Bitmap.Config) : Config {
this.bitmapConfig = config
return this
}
fun isDecodingCoverFrame() : Boolean {
return this.decodeCoverFrame
}
fun setIsDecodingCoverFrame(decodeCoverFrame : Boolean) : Config {
this.decodeCoverFrame = decodeCoverFrame
return this
}
}
companion object { companion object {
private const val TAG = "ApngDecoder" private const val TAG = "ApngDecoder"
@ -70,9 +90,8 @@ 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]. * 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 needed for the animation drawable * @param context Context needed for the animation drawable
* @param inStream Input Stream to decode. Will be closed at the end. * @param inStream Input Stream to decode. Will be closed at the end.
* @param speed Optional parameter. * @param config Decoder configuration
* @param config Configuration applied to the bitmap added to the animation. Please note that the frame is decoded in ARGB_8888 and converted after, for the buffer. * @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 [AnimationDrawable] 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")
@ -87,6 +106,7 @@ class ApngDecoder {
val bytes = ByteArray(8) val bytes = ByteArray(8)
inputStream.mark(8) inputStream.mark(8)
inputStream.read(bytes) inputStream.read(bytes)
if (isPng(bytes)) { if (isPng(bytes)) {
var png: ByteArrayOutputStream? = null var png: ByteArrayOutputStream? = null
var cover: ByteArrayOutputStream? = null var cover: ByteArrayOutputStream? = null
@ -118,6 +138,7 @@ class ApngDecoder {
if (byteRead == -1) if (byteRead == -1)
break break
val length = Utils.uIntFromBytesBigEndian(lengthChunk) val length = Utils.uIntFromBytesBigEndian(lengthChunk)
val chunk = ByteArray(length + 8) val chunk = ByteArray(length + 8)
@ -515,8 +536,7 @@ 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]. * 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 needed for animation drawable. * @param context Context needed for animation drawable.
* @param file File to decode. * @param file File to decode.
* @param speed Optional parameter. * @param config Decoder configuration
* @param config Configuration applied to the bitmap added to the animation. Please note that the frame is decoded in ARGB_8888 and converted after, for the buffer.
* @return [AnimationDrawable] 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 [AnimationDrawable] 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")
@ -536,8 +556,7 @@ 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]. * 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 and animation drawable. * @param context Context is needed for contentResolver and animation drawable.
* @param uri Uri to open. * @param uri Uri to open.
* @param speed Optional parameter. * @param config Decoder configuration
* @param config Configuration applied to the bitmap added to the animation. Please note that the frame is decoded in ARGB_8888 and converted after, for the buffer.
* @return [AnimationDrawable] if successful and an [AnimatedImageDrawable] if the image decoded is not an APNG but a gif. * @return [AnimationDrawable] if successful and an [AnimatedImageDrawable] if the image decoded is not an APNG but a gif.
*/ */
@Suppress("unused") @Suppress("unused")
@ -559,8 +578,7 @@ 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]. * 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 and animation drawable. * @param context Context is needed for contentResolver and animation drawable.
* @param res Resource to decode. * @param res Resource to decode.
* @param speed Optional parameter. * @param config Decoder configuration
* @param config Configuration applied to the bitmap added to the animation. Please note that the frame is decoded in ARGB_8888 and converted after, for the buffer.
* @return [AnimationDrawable] if successful and an [AnimatedImageDrawable] if the image decoded is not an APNG but a gif. * @return [AnimationDrawable] if successful and an [AnimatedImageDrawable] if the image decoded is not an APNG but a gif.
*/ */
@Suppress("unused") @Suppress("unused")
@ -580,8 +598,7 @@ 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]. * 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 and animation drawable. * @param context Context is needed for contentResolver and animation drawable.
* @param url URL to decode. * @param url URL to decode.
* @param speed Optional parameter. * @param config Decoder configuration
* @param config Configuration applied to the bitmap added to the animation. Please note that the frame is decoded in ARGB_8888 and converted after, for the buffer.
* @return [AnimationDrawable] if successful and an [AnimatedImageDrawable] if the image decoded is not an APNG but a gif. * @return [AnimationDrawable] if successful and an [AnimatedImageDrawable] if the image decoded is not an APNG but a gif.
*/ */
@Suppress("unused", "BlockingMethodInNonBlockingContext") @Suppress("unused", "BlockingMethodInNonBlockingContext")
@ -604,9 +621,8 @@ class ApngDecoder {
* @param context Context needed for animation drawable. * @param context Context needed for animation drawable.
* @param file File to decode. * @param file File to decode.
* @param imageView Image View. * @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.
* @param config Configuration applied to the bitmap added to the animation. Please note that the frame is decoded in ARGB_8888 and converted after, for the buffer. * @param config Decoder configuration
*/ */
@Suppress("unused") @Suppress("unused")
@JvmStatic @JvmStatic
@ -647,9 +663,8 @@ class ApngDecoder {
* @param context Context needed for animation drawable and content resolver. * @param context Context needed for animation drawable and content resolver.
* @param uri Uri to load. * @param uri Uri to load.
* @param imageView Image View. * @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.
* @param config Configuration applied to the bitmap added to the animation. Please note that the frame is decoded in ARGB_8888 and converted after, for the buffer. * @param config Decoder configuration
*/ */
@Suppress("unused") @Suppress("unused")
@JvmStatic @JvmStatic
@ -691,9 +706,8 @@ class ApngDecoder {
* @param context Context needed to decode the resource and for the animation drawable. * @param context Context needed to decode the resource and for the animation drawable.
* @param res Raw resource to load. * @param res Raw resource to load.
* @param imageView Image View. * @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.
* @param config Configuration applied to the bitmap added to the animation. Please note that the frame is decoded in ARGB_8888 and converted after, for the buffer. * @param config Decoder configuration
*/ */
@Suppress("unused") @Suppress("unused")
@JvmStatic @JvmStatic
@ -734,9 +748,8 @@ class ApngDecoder {
* @param context Context needed for the animation drawable. * @param context Context needed for the animation drawable.
* @param url URL to load. * @param url URL to load.
* @param imageView Image View. * @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.
* @param config Configuration applied to the bitmap added to the animation. Please note that the frame is decoded in ARGB_8888 and converted after, for the buffer. * @param config Decoder configuration
*/ */
@Suppress("unused") @Suppress("unused")
@JvmStatic @JvmStatic
@ -780,9 +793,8 @@ class ApngDecoder {
* @param context Context needed for decoding the image and creating the animation drawable. * @param context Context needed for decoding the image and creating the animation drawable.
* @param string URL to load * @param string URL to load
* @param imageView Image View. * @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.
* @param config Configuration applied to the bitmap added to the animation. Please note that the frame is decoded in ARGB_8888 and converted after, for the buffer. * @param config Decoder configuration
*/ */
@Suppress("unused") @Suppress("unused")
@JvmStatic @JvmStatic

View File

@ -1,8 +1,5 @@
package oupson.apng.exceptions package oupson.apng.exceptions
class NoFrameException : Exception()
class NotPngException : Exception()
class NotApngException : Exception()
class BadCRCException : Exception() class BadCRCException : Exception()
// TODO BETTER MESSAGES // TODO BETTER MESSAGES

View File

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