From 919feee3e9fd6f879d385d978f55be660f9103cb Mon Sep 17 00:00:00 2001 From: Oupson Date: Sat, 6 Feb 2021 16:23:56 +0100 Subject: [PATCH] Bitmap diff with blend op --- .../apng/ApngEncoderInstrumentedTest.kt | 28 +++++--- .../java/oupson/apng/encoder/ApngEncoder.kt | 2 - .../src/main/java/oupson/apng/utils/Utils.kt | 67 +++++++++++++++---- 3 files changed, 75 insertions(+), 22 deletions(-) diff --git a/apng_library/src/androidTest/java/oupson/apng/ApngEncoderInstrumentedTest.kt b/apng_library/src/androidTest/java/oupson/apng/ApngEncoderInstrumentedTest.kt index c7989cd..78d3ab2 100644 --- a/apng_library/src/androidTest/java/oupson/apng/ApngEncoderInstrumentedTest.kt +++ b/apng_library/src/androidTest/java/oupson/apng/ApngEncoderInstrumentedTest.kt @@ -5,6 +5,7 @@ import android.graphics.Bitmap import android.graphics.BitmapFactory import android.graphics.Color import androidx.test.platform.app.InstrumentationRegistry +import junit.framework.TestCase.assertFalse import junit.framework.TestCase.assertTrue import org.junit.Test import oupson.apng.utils.Utils @@ -12,7 +13,7 @@ import oupson.apng.utils.Utils class ApngEncoderInstrumentedTest { @Test - fun testDiffBunny() { // TODO BLEND / DISPOSE OP + fun testDiffBunny() { val context = InstrumentationRegistry.getInstrumentation().context val bunnyFrame1 = getFrame(context, "bunny/frame_apngframe01.png") @@ -31,17 +32,28 @@ class ApngEncoderInstrumentedTest { val diffBall1to2 = Utils.getDiffBitmap(ballFrame1, ballFrame2) - assertTrue(isSimilar(ballFrame1, ballFrame2, diffBall1to2)) // TODO FIX THIS WITH BLEND OP / DISPOSE OP + assertTrue(isSimilar(ballFrame1, ballFrame2, diffBall1to2)) } - private fun isSimilar(buffer : Bitmap, frame : Bitmap, diff : Triple) : Boolean { + @Test + fun containTransparency() { + val context = InstrumentationRegistry.getInstrumentation().context + + val bunnyFrame1 = getFrame(context, "bunny/frame_apngframe01.png") + assertFalse(Utils.containTransparency(bunnyFrame1)) + + val ballFrame1 = getFrame(context, "ball/apngframe01.png") + assertTrue(Utils.containTransparency(ballFrame1)) + } + + 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.first.height) { - for (x in 0 until diff.first.width) { - val p = diff.first.getPixel(x, y) - if (p != Color.TRANSPARENT) - btm.setPixel(diff.second + x, diff.third + y, p) + 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) } } 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 3a7500c..49e6e40 100644 --- a/apng_library/src/main/java/oupson/apng/encoder/ApngEncoder.kt +++ b/apng_library/src/main/java/oupson/apng/encoder/ApngEncoder.kt @@ -553,6 +553,4 @@ class ApngEncoder( i++ } } - - } \ No newline at end of file diff --git a/apng_library/src/main/java/oupson/apng/utils/Utils.kt b/apng_library/src/main/java/oupson/apng/utils/Utils.kt index cf4a0d2..647fe15 100644 --- a/apng_library/src/main/java/oupson/apng/utils/Utils.kt +++ b/apng_library/src/main/java/oupson/apng/utils/Utils.kt @@ -208,16 +208,28 @@ class Utils { val IHDR: ByteArray by lazy { byteArrayOf(0x49, 0x48, 0x44, 0x52) } val acTL: ByteArray by lazy { byteArrayOf(0x61, 0x63, 0x54, 0x4c) } + /** - * + * A class that contain the difference between two bitmaps + * @property bitmap A resized [Bitmap] that contain the difference between two bitmaps + * @property offsetX the x offset + * @property offsetY the y offset + * @property blendOp a [BlendOp] */ - // TODO DOC - fun getDiffBitmap(bitmapBuffer : Bitmap, btm : Bitmap) : Triple { // TODO BLEND / DISPOSE OP - if (bitmapBuffer.width < btm.width || bitmapBuffer.height < btm.height) { + data class DiffResult(val bitmap: Bitmap, val offsetX : Int, val offsetY : Int, val blendOp: BlendOp) + + /** + * Get the difference between two bitmaps + * @param firstBitmap A [Bitmap], the first bitmap + * @param secondBitmap A [Bitmap], a second bitmap + * @return [DiffResult], the difference between the second and the first bitmap + */ + fun getDiffBitmap(firstBitmap : Bitmap, secondBitmap : Bitmap) : DiffResult { + if (firstBitmap.width < secondBitmap.width || firstBitmap.height < secondBitmap.height) { TODO("EXCEPTION BAD IMAGE SIZE") } - var resultBtm = Bitmap.createBitmap(btm.width, btm.height, Bitmap.Config.ARGB_8888) + var resultBtm = Bitmap.createBitmap(secondBitmap.width, secondBitmap.height, Bitmap.Config.ARGB_8888) var offsetX = resultBtm.width + 1 var offsetY = resultBtm.height + 1 @@ -225,12 +237,19 @@ class Utils { var lastX = 0 var lastY = 0 - for (y in 0 until btm.height) { - for (x in 0 until btm.width) { - if (bitmapBuffer.getPixel(x, y) == btm.getPixel(x, y)) { - resultBtm.setPixel(x, y, Color.TRANSPARENT) - } else { - resultBtm.setPixel(x, y, btm.getPixel(x, y)) + // Find if the image contain transparent pixels, if true, then transparent pixels must replace the pixels in the buffer + val blendOp = if(containTransparency(secondBitmap)) APNG_BLEND_OP_SOURCE else APNG_BLEND_OP_OVER + + for (y in 0 until secondBitmap.height) { + for (x in 0 until secondBitmap.width) { + val btmPixel = secondBitmap.getPixel(x, y) + if (firstBitmap.getPixel(x, y) == btmPixel) { // Similar pixels could be forgotten + if (blendOp == APNG_BLEND_OP_OVER) + resultBtm.setPixel(x, y, Color.TRANSPARENT) + else + resultBtm.setPixel(x, y, firstBitmap.getPixel(x, y)) + } else { // Otherwise, track image bounds + resultBtm.setPixel(x, y, btmPixel) if (x < offsetX) offsetX = x if (y < offsetY) @@ -249,9 +268,33 @@ class Utils { val newWidth = lastX - offsetX val newHeight = lastY - offsetY + // Resize bitmap resultBtm = Bitmap.createBitmap(resultBtm, offsetX, offsetY, newWidth, newHeight) - return Triple(resultBtm, offsetX, offsetY) + return DiffResult(resultBtm, offsetX, offsetY, blendOp) + } + + /** + * This function return true if the bitmap contain transparent pixels + * @param btm A [Bitmap] + * @return [Boolean] true if if the bitmap contain transparent pixels + */ + fun containTransparency(btm : Bitmap) : Boolean { + var result = false + var y = 0 + var x = 0 + while (y < btm.height && !result) { + if (btm.getPixel(x, y) == Color.TRANSPARENT) { + result = true + } + + x++ + if (x == btm.width) { + x = 0 + y++ + } + } + return result } } } \ No newline at end of file