diff --git a/apng_library/src/androidTest/assets/bugbuckbunny.png b/apng_library/src/androidTest/assets/bugbuckbunny.png new file mode 100644 index 0000000..6aaeedb Binary files /dev/null and b/apng_library/src/androidTest/assets/bugbuckbunny.png differ diff --git a/apng_library/src/androidTest/assets/sushi.png b/apng_library/src/androidTest/assets/sushi.png new file mode 100644 index 0000000..728c988 Binary files /dev/null and b/apng_library/src/androidTest/assets/sushi.png differ diff --git a/apng_library/src/androidTest/java/oupson/apng/ApngDecoderInstrumentedTest.kt b/apng_library/src/androidTest/java/oupson/apng/ApngDecoderInstrumentedTest.kt new file mode 100644 index 0000000..c8255d3 --- /dev/null +++ b/apng_library/src/androidTest/java/oupson/apng/ApngDecoderInstrumentedTest.kt @@ -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] + ) + ) + } + } +} \ No newline at end of file diff --git a/apng_library/src/androidTest/java/oupson/apng/ApngEncoderInstrumentedTest.kt b/apng_library/src/androidTest/java/oupson/apng/ApngEncoderInstrumentedTest.kt index 36430e7..5caa031 100644 --- a/apng_library/src/androidTest/java/oupson/apng/ApngEncoderInstrumentedTest.kt +++ b/apng_library/src/androidTest/java/oupson/apng/ApngEncoderInstrumentedTest.kt @@ -1,15 +1,14 @@ 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.BitmapDrawable import androidx.test.platform.app.InstrumentationRegistry import junit.framework.TestCase.assertFalse import junit.framework.TestCase.assertTrue 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.encoder.ApngEncoder import oupson.apng.utils.Utils @@ -78,7 +77,7 @@ class ApngEncoderInstrumentedTest { for (i in 0 until optimisedApng.numberOfFrames) { assertTrue( - isBitmapSimilar( + areBitmapSimilar( list[i], (optimisedApng.getFrame(i) as BitmapDrawable).bitmap ) @@ -107,7 +106,7 @@ class ApngEncoderInstrumentedTest { for (i in 0 until optimisedApng.numberOfFrames) { assertTrue( - isBitmapSimilar( + areBitmapSimilar( (optimisedApng.getFrame(i) as BitmapDrawable).bitmap, (nonOptimisedApng.getFrame(i) as BitmapDrawable).bitmap ) @@ -141,7 +140,7 @@ class ApngEncoderInstrumentedTest { for (i in 0 until optimisedApng.numberOfFrames) { assertTrue( - isBitmapSimilar( + areBitmapSimilar( list[i], (optimisedApng.getFrame(i) as BitmapDrawable).bitmap ) @@ -170,54 +169,12 @@ class ApngEncoderInstrumentedTest { for (i in 0 until optimisedApng.numberOfFrames) { assertTrue( - isBitmapSimilar( + areBitmapSimilar( (optimisedApng.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!! - } } \ No newline at end of file diff --git a/apng_library/src/androidTest/java/oupson/apng/Utils.kt b/apng_library/src/androidTest/java/oupson/apng/Utils.kt new file mode 100644 index 0000000..c8cbc66 --- /dev/null +++ b/apng_library/src/androidTest/java/oupson/apng/Utils.kt @@ -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 + } + } +} \ No newline at end of file diff --git a/apng_library/src/main/java/oupson/apng/decoder/ApngDecoder.kt b/apng_library/src/main/java/oupson/apng/decoder/ApngDecoder.kt index ed18202..28785fc 100644 --- a/apng_library/src/main/java/oupson/apng/decoder/ApngDecoder.kt +++ b/apng_library/src/main/java/oupson/apng/decoder/ApngDecoder.kt @@ -47,11 +47,31 @@ class ApngDecoder { fun onError(error: Exception) } - data class Config( - val speed: Float = 1f, - val bitmapConfig: Bitmap.Config = Bitmap.Config.ARGB_8888, - val decodeCoverFrame: Boolean = true - ) + class Config( + internal var speed: Float = 1f, + internal var bitmapConfig: Bitmap.Config = Bitmap.Config.ARGB_8888, + 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 { 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]. * @param context Context needed for the animation drawable * @param inStream Input Stream to decode. Will be closed at the end. - * @param speed Optional parameter. - * @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]. + * @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]. */ // TODO DOCUMENT CONFIG @Suppress("MemberVisibilityCanBePrivate") @@ -87,6 +106,7 @@ class ApngDecoder { val bytes = ByteArray(8) inputStream.mark(8) inputStream.read(bytes) + if (isPng(bytes)) { var png: ByteArrayOutputStream? = null var cover: ByteArrayOutputStream? = null @@ -118,6 +138,7 @@ class ApngDecoder { if (byteRead == -1) break + val length = Utils.uIntFromBytesBigEndian(lengthChunk) 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]. * @param context Context needed for animation drawable. * @param file File to decode. - * @param speed Optional parameter. - * @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 * @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") @@ -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]. * @param context Context is needed for contentResolver and animation drawable. * @param uri Uri to open. - * @param speed Optional parameter. - * @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 * @return [AnimationDrawable] if successful and an [AnimatedImageDrawable] if the image decoded is not an APNG but a gif. */ @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]. * @param context Context is needed for contentResolver and animation drawable. * @param res Resource to decode. - * @param speed Optional parameter. - * @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 * @return [AnimationDrawable] if successful and an [AnimatedImageDrawable] if the image decoded is not an APNG but a gif. */ @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]. * @param context Context is needed for contentResolver and animation drawable. * @param url URL to decode. - * @param speed Optional parameter. - * @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 * @return [AnimationDrawable] if successful and an [AnimatedImageDrawable] if the image decoded is not an APNG but a gif. */ @Suppress("unused", "BlockingMethodInNonBlockingContext") @@ -604,9 +621,8 @@ class ApngDecoder { * @param context Context needed for animation drawable. * @param file File to decode. * @param imageView Image View. - * @param speed Optional parameter. * @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") @JvmStatic @@ -647,9 +663,8 @@ class ApngDecoder { * @param context Context needed for animation drawable and content resolver. * @param uri Uri to load. * @param imageView Image View. - * @param speed Optional parameter. * @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") @JvmStatic @@ -691,9 +706,8 @@ class ApngDecoder { * @param context Context needed to decode the resource and for the animation drawable. * @param res Raw resource to load. * @param imageView Image View. - * @param speed Optional parameter. * @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") @JvmStatic @@ -734,9 +748,8 @@ class ApngDecoder { * @param context Context needed for the animation drawable. * @param url URL to load. * @param imageView Image View. - * @param speed Optional parameter. * @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") @JvmStatic @@ -780,9 +793,8 @@ class ApngDecoder { * @param context Context needed for decoding the image and creating the animation drawable. * @param string URL to load * @param imageView Image View. - * @param speed Optional parameter. * @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") @JvmStatic diff --git a/apng_library/src/main/java/oupson/apng/exceptions/customException.kt b/apng_library/src/main/java/oupson/apng/exceptions/customException.kt index adb4f5a..b4e229b 100644 --- a/apng_library/src/main/java/oupson/apng/exceptions/customException.kt +++ b/apng_library/src/main/java/oupson/apng/exceptions/customException.kt @@ -1,8 +1,5 @@ package oupson.apng.exceptions -class NoFrameException : Exception() -class NotPngException : Exception() -class NotApngException : Exception() class BadCRCException : Exception() // TODO BETTER MESSAGES diff --git a/app-test/src/main/java/oupson/apngcreator/fragments/JavaFragment.java b/app-test/src/main/java/oupson/apngcreator/fragments/JavaFragment.java index dd39744..74dd6c1 100644 --- a/app-test/src/main/java/oupson/apngcreator/fragments/JavaFragment.java +++ b/app-test/src/main/java/oupson/apngcreator/fragments/JavaFragment.java @@ -50,7 +50,7 @@ public class JavaFragment extends Fragment { public void onError(@NotNull Exception error) { Log.e(TAG, "Error : " + error.toString()); } - }); + }, new ApngDecoder.Config().setIsDecodingCoverFrame(false)); } return v;