Test if optimised and non-optimised APNG are the same

This commit is contained in:
Oupson 2021-02-06 18:22:17 +01:00
parent f826afff86
commit be9e5426e0
2 changed files with 150 additions and 15 deletions

View File

@ -4,11 +4,17 @@ import android.content.Context
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.graphics.Color import android.graphics.Color
import android.graphics.drawable.AnimationDrawable
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.decoder.ApngDecoder
import oupson.apng.encoder.ApngEncoder
import oupson.apng.utils.Utils import oupson.apng.utils.Utils
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
class ApngEncoderInstrumentedTest { class ApngEncoderInstrumentedTest {
@ -38,7 +44,7 @@ class ApngEncoderInstrumentedTest {
} }
@Test @Test
fun containTransparency() { fun testContainTransparency() {
val context = InstrumentationRegistry.getInstrumentation().context val context = InstrumentationRegistry.getInstrumentation().context
val bunnyFrame1 = getFrame(context, "bunny/frame_apngframe01.png") val bunnyFrame1 = getFrame(context, "bunny/frame_apngframe01.png")
@ -48,6 +54,118 @@ class ApngEncoderInstrumentedTest {
assertTrue(Utils.containTransparency(ballFrame1)) 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 { private fun isSimilar(buffer : Bitmap, frame : Bitmap, diff : Utils.Companion.DiffResult) : Boolean {
val btm = buffer.copy(Bitmap.Config.ARGB_8888, true) val btm = buffer.copy(Bitmap.Config.ARGB_8888, true)
@ -69,6 +187,17 @@ class ApngEncoderInstrumentedTest {
return true 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 { private fun getFrame(context: Context, name: String) : Bitmap {
val inputStream = context.assets.open(name) val inputStream = context.assets.open(name)

View File

@ -284,7 +284,13 @@ class ApngEncoder(
) { ) {
if (currentFrame == 0) { if (currentFrame == 0) {
if (btm.width != width || btm.height != height) 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 var frameBtm = btm
@ -292,21 +298,21 @@ class ApngEncoder(
var frameYOffsets = yOffsets var frameYOffsets = yOffsets
var frameBlendOp = blendOp var frameBlendOp = blendOp
if (currentFrame != 0 || (currentFrame == 0 && firstFrameInAnim)) { if (optimise && currentFrame != 0 || (currentFrame == 0 && firstFrameInAnim)) {
if (bitmapBuffer == null && optimise) { if (bitmapBuffer == null) {
bitmapBuffer = btm.copy(btm.config, false) bitmapBuffer = btm.copy(btm.config, false)
} else if (optimise) { } else {
val diff = Utils.getDiffBitmap(bitmapBuffer!!, btm) val diff = Utils.getDiffBitmap(bitmapBuffer!!, btm)
frameBtm = diff.bitmap frameBtm = diff.bitmap
frameXOffsets = diff.offsetX frameXOffsets = diff.offsetX
frameYOffsets = diff.offsetY frameYOffsets = diff.offsetY
frameBlendOp = diff.blendOp frameBlendOp = diff.blendOp
bitmapBuffer = btm.copy(btm.config, false) bitmapBuffer = btm.copy(btm.config, false)
}
} }
}
if (btm.width > width || btm.height > height) if (frameBtm.width > width || frameBtm.height > height)
throw InvalidFrameSizeException(btm.width, btm.height, width, height, currentFrame == 0) throw InvalidFrameSizeException(frameBtm.width, frameBtm.height, width, height, currentFrame == 0)
if (firstFrameInAnim || currentFrame != 0) if (firstFrameInAnim || currentFrame != 0)
writeFCTL(frameBtm, delay, disposeOp, frameBlendOp, frameXOffsets, frameYOffsets) writeFCTL(frameBtm, delay, disposeOp, frameBlendOp, frameXOffsets, frameYOffsets)