diff --git a/apng_library/src/androidTest/java/oupson/apng/ApngEncoderInstrumentedTest.kt b/apng_library/src/androidTest/java/oupson/apng/ApngEncoderInstrumentedTest.kt index 81b88b2..f7a4fcc 100644 --- a/apng_library/src/androidTest/java/oupson/apng/ApngEncoderInstrumentedTest.kt +++ b/apng_library/src/androidTest/java/oupson/apng/ApngEncoderInstrumentedTest.kt @@ -4,11 +4,17 @@ 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.decoder.ApngDecoder +import oupson.apng.encoder.ApngEncoder import oupson.apng.utils.Utils +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream class ApngEncoderInstrumentedTest { @@ -38,7 +44,7 @@ class ApngEncoderInstrumentedTest { } @Test - fun containTransparency() { + fun testContainTransparency() { val context = InstrumentationRegistry.getInstrumentation().context val bunnyFrame1 = getFrame(context, "bunny/frame_apngframe01.png") @@ -48,6 +54,118 @@ class ApngEncoderInstrumentedTest { assertTrue(Utils.containTransparency(ballFrame1)) } + @Test + fun testOptimiseBall() { + val context = InstrumentationRegistry.getInstrumentation().context + val list = context.assets.list("ball")?.map { getFrame(context, "ball/$it") }!! + + val optimisedOutputStream = ByteArrayOutputStream() + val optimisedEncoder = ApngEncoder(optimisedOutputStream, list[0].width, list[0].height, list.size) + .setOptimiseApng(true) + + list.forEach { + optimisedEncoder.writeFrame(it) + } + + optimisedEncoder.writeEnd() + optimisedOutputStream.close() + + val bytes = optimisedOutputStream.toByteArray() + val optimisedInputStream = ByteArrayInputStream(bytes) + + val optimisedApng = + ApngDecoder.decodeApng(context, optimisedInputStream) as AnimationDrawable + + optimisedInputStream.close() + + + + val nonOptimisedOutputStream = ByteArrayOutputStream() + + val nonOptimisedEncoder = ApngEncoder(nonOptimisedOutputStream, list[0].width, list[0].height, list.size) + .setOptimiseApng(false) + + list.forEach { + nonOptimisedEncoder.writeFrame(it) + } + + nonOptimisedEncoder.writeEnd() + + nonOptimisedOutputStream.close() + + val nonOptimisedBytes = nonOptimisedOutputStream.toByteArray() + val nonOptimisedInputStream = ByteArrayInputStream(nonOptimisedBytes) + + val nonOptimisedApng = + ApngDecoder.decodeApng(context, nonOptimisedInputStream) as AnimationDrawable + nonOptimisedInputStream.close() + + for (i in 0 until optimisedApng.numberOfFrames) { + assertTrue( + isBitmapSimilar( + (optimisedApng.getFrame(i) as BitmapDrawable).bitmap, + (nonOptimisedApng.getFrame(i) as BitmapDrawable).bitmap + ) + ) + } + } + + @Test + fun testOptimiseBunny() { + val context = InstrumentationRegistry.getInstrumentation().context + val list = context.assets.list("bunny")?.map { getFrame(context, "bunny/$it") }!! + + val optimisedOutputStream = ByteArrayOutputStream() + val optimisedEncoder = ApngEncoder(optimisedOutputStream, list[0].width, list[0].height, list.size) + .setOptimiseApng(true) + + list.forEach { + optimisedEncoder.writeFrame(it) + } + + optimisedEncoder.writeEnd() + optimisedOutputStream.close() + + val bytes = optimisedOutputStream.toByteArray() + val optimisedInputStream = ByteArrayInputStream(bytes) + + val optimisedApng = + ApngDecoder.decodeApng(context, optimisedInputStream) as AnimationDrawable + + optimisedInputStream.close() + + + + val nonOptimisedOutputStream = ByteArrayOutputStream() + + val nonOptimisedEncoder = ApngEncoder(nonOptimisedOutputStream, list[0].width, list[0].height, list.size) + .setOptimiseApng(false) + + list.forEach { + nonOptimisedEncoder.writeFrame(it) + } + + nonOptimisedEncoder.writeEnd() + + nonOptimisedOutputStream.close() + + val nonOptimisedBytes = nonOptimisedOutputStream.toByteArray() + val nonOptimisedInputStream = ByteArrayInputStream(nonOptimisedBytes) + + val nonOptimisedApng = + ApngDecoder.decodeApng(context, nonOptimisedInputStream) as AnimationDrawable + nonOptimisedInputStream.close() + + for (i in 0 until optimisedApng.numberOfFrames) { + assertTrue( + isBitmapSimilar( + (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) @@ -69,6 +187,17 @@ class ApngEncoderInstrumentedTest { 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) diff --git a/apng_library/src/main/java/oupson/apng/encoder/ApngEncoder.kt b/apng_library/src/main/java/oupson/apng/encoder/ApngEncoder.kt index 75e593d..886c358 100644 --- a/apng_library/src/main/java/oupson/apng/encoder/ApngEncoder.kt +++ b/apng_library/src/main/java/oupson/apng/encoder/ApngEncoder.kt @@ -284,7 +284,13 @@ class ApngEncoder( ) { if (currentFrame == 0) { if (btm.width != width || btm.height != height) - throw InvalidFrameSizeException(btm.width, btm.height, width, height, currentFrame == 0) + throw InvalidFrameSizeException( + btm.width, + btm.height, + width, + height, + currentFrame == 0 + ) } var frameBtm = btm @@ -292,21 +298,21 @@ class ApngEncoder( var frameYOffsets = yOffsets var frameBlendOp = blendOp - if (currentFrame != 0 || (currentFrame == 0 && firstFrameInAnim)) { - if (bitmapBuffer == null && optimise) { - bitmapBuffer = btm.copy(btm.config, false) - } else if (optimise) { - val diff = Utils.getDiffBitmap(bitmapBuffer!!, btm) - frameBtm = diff.bitmap - frameXOffsets = diff.offsetX - frameYOffsets = diff.offsetY - frameBlendOp = diff.blendOp - bitmapBuffer = btm.copy(btm.config, false) - } + if (optimise && currentFrame != 0 || (currentFrame == 0 && firstFrameInAnim)) { + if (bitmapBuffer == null) { + bitmapBuffer = btm.copy(btm.config, false) + } else { + val diff = Utils.getDiffBitmap(bitmapBuffer!!, btm) + frameBtm = diff.bitmap + frameXOffsets = diff.offsetX + frameYOffsets = diff.offsetY + frameBlendOp = diff.blendOp + bitmapBuffer = btm.copy(btm.config, false) } + } - if (btm.width > width || btm.height > height) - throw InvalidFrameSizeException(btm.width, btm.height, width, height, currentFrame == 0) + if (frameBtm.width > width || frameBtm.height > height) + throw InvalidFrameSizeException(frameBtm.width, frameBtm.height, width, height, currentFrame == 0) if (firstFrameInAnim || currentFrame != 0) writeFCTL(frameBtm, delay, disposeOp, frameBlendOp, frameXOffsets, frameYOffsets)