From 3953707072770e6ab2991bdb0cba8800302e6ea1 Mon Sep 17 00:00:00 2001 From: oupson Date: Fri, 5 Oct 2018 21:38:13 +0200 Subject: [PATCH] Add better support and stability --- .idea/caches/build_file_checksums.ser | Bin 596 -> 596 bytes .idea/inspectionProfiles/Project_Default.xml | 6 + apng_library/src/main/AndroidManifest.xml | 4 +- .../main/java/oupson/apng/APNGDisassembler.kt | 153 +++++++--- .../src/main/java/oupson/apng/ApngAnimator.kt | 270 ++++++++++++++---- .../src/main/java/oupson/apng/Frame.kt | 11 +- .../src/main/java/oupson/apng/fcTL.kt | 9 + .../java/oupson/apngcreator/MainActivity.kt | 2 +- 8 files changed, 362 insertions(+), 93 deletions(-) create mode 100644 .idea/inspectionProfiles/Project_Default.xml diff --git a/.idea/caches/build_file_checksums.ser b/.idea/caches/build_file_checksums.ser index 2b4ce546490e5b9293675b45990e56c7d8a48f26..b28f89c03b6282a90ed442fb55e9c6a8755fa08d 100644 GIT binary patch delta 14 Wcmcb@a)o8WOy=7Qo^717gAo8VQwKx< delta 14 Wcmcb@a)o8WOy)hSCvTjygAo8T9R}S1 diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..73ab2c8 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/apng_library/src/main/AndroidManifest.xml b/apng_library/src/main/AndroidManifest.xml index b2ea72a..a6585f3 100644 --- a/apng_library/src/main/AndroidManifest.xml +++ b/apng_library/src/main/AndroidManifest.xml @@ -1,2 +1,4 @@ + package="oupson.apng"> + + diff --git a/apng_library/src/main/java/oupson/apng/APNGDisassembler.kt b/apng_library/src/main/java/oupson/apng/APNGDisassembler.kt index 93ba2a1..532c22f 100644 --- a/apng_library/src/main/java/oupson/apng/APNGDisassembler.kt +++ b/apng_library/src/main/java/oupson/apng/APNGDisassembler.kt @@ -1,25 +1,32 @@ package oupson.apng import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.graphics.Canvas import android.util.Log -import oupson.apng.ApngFactory.Companion.isApng -import oupson.apng.ApngFactory.Companion.isApng import oupson.apng.ApngFactory.Companion.pngSignature import java.util.zip.CRC32 - class APNGDisassembler(val byteArray: ByteArray) { val pngList = ArrayList() var png : ArrayList? = null + var cover : ArrayList? = null + var delay = -1f var yOffset= -1 var xOffset = -1 + var plte : ByteArray? = null + var tnrs : ByteArray? = null + var maxWidth = 0 var maxHeight = 0 + + var blend_op : Utils.Companion.blend_op = Utils.Companion.blend_op.APNG_BLEND_OP_SOURCE + var dispose_op : Utils.Companion.dispose_op= Utils.Companion.dispose_op.APNG_DISPOSE_OP_NONE init { if (ApngFactory.isApng(byteArray)) { val ihdr = IHDR() @@ -30,17 +37,36 @@ class APNGDisassembler(val byteArray: ByteArray) { // find new Frame with fcTL if (byteArray[i] == 0x66.toByte() && byteArray[i + 1] == 0x63.toByte() && byteArray[ i + 2 ] == 0x54.toByte() && byteArray[ i + 3 ] == 0x4C.toByte() || i == byteArray.size - 1) { if (png == null) { + if (cover != null) { + cover!!.addAll(to4Bytes(0).toList()) + // Add IEND + val iend = byteArrayOf(0x49, 0x45, 0x4E, 0x44) + // Generate crc for IEND + val crC32 = CRC32() + crC32.update(iend, 0, iend.size) + cover!!.addAll(iend.toList()) + cover!!.addAll(to4Bytes(crC32.value.toInt()).toList()) + } png = ArrayList() - val fcTL = fcTL(byteArray.copyOfRange(i-4, i + 28)) + val fcTL = fcTL(byteArray.copyOfRange(i-4, i + 32)) delay = fcTL.delay yOffset = fcTL.y_offset xOffset = fcTL.x_offset - Log.e("APNG", "delay : + ${fcTL.delay}") + blend_op = fcTL.blend_op + dispose_op = fcTL.dispose_op val width = fcTL.pngWidth val height = fcTL.pngHeight png!!.addAll(pngSignature.toList()) png!!.addAll(generate_ihdr(ihdr, width, height).toList()) + + if (plte != null) { + png!!.addAll(plte!!.toList()) + } + + if (tnrs != null) { + png!!.addAll(tnrs!!.toList()) + } } else { // Add IEND body length : 0 png!!.addAll(to4Bytes(0).toList()) @@ -51,22 +77,78 @@ class APNGDisassembler(val byteArray: ByteArray) { crC32.update(iend, 0, iend.size) png!!.addAll(iend.toList()) png!!.addAll(to4Bytes(crC32.value.toInt()).toList()) - pngList.add(Frame(png!!.toByteArray(), delay, xOffset, yOffset, maxWidth, maxHeight)) + pngList.add(Frame(png!!.toByteArray(), delay, xOffset, yOffset, maxWidth, maxHeight, blend_op, dispose_op)) png = ArrayList() - val fcTL = fcTL(byteArray.copyOfRange(i-4, i + 28)) + val fcTL = fcTL(byteArray.copyOfRange(i-4, i + 32)) delay = fcTL.delay yOffset = fcTL.y_offset xOffset = fcTL.x_offset + blend_op = fcTL.blend_op + dispose_op = fcTL.dispose_op val width = fcTL.pngWidth val height = fcTL.pngHeight png!!.addAll(pngSignature.toList()) png!!.addAll(generate_ihdr(ihdr, width, height).toList()) + if (plte != null) { + png!!.addAll(plte!!.toList()) + } + + if (tnrs != null) { + png!!.addAll(tnrs!!.toList()) + } } - } else if (byteArray[i] == 0x49.toByte() && byteArray[i + 1] == 0x44.toByte() && byteArray[ i + 2 ] == 0x41.toByte() && byteArray[ i + 3 ] == 0x54.toByte()) { + } + // Check if is IDAT + else if (byteArray[i] == 0x49.toByte() && byteArray[i + 1] == 0x44.toByte() && byteArray[ i + 2 ] == 0x41.toByte() && byteArray[ i + 3 ] == 0x54.toByte()) { + if (png == null) { + if (cover == null) { + cover = ArrayList() + png!!.addAll(pngSignature.toList()) + png!!.addAll(generate_ihdr(ihdr, maxWidth, maxHeight).toList()) + } + // Find the chunk length + var lengthString = "" + byteArray.copyOfRange( i - 4, i).forEach { + lengthString += String.format("%02x", it) + } + val bodySize = lengthString.toLong(16).toInt() + cover!!.addAll(byteArray.copyOfRange(i-4, i).toList()) + val body = ArrayList() + body.addAll(byteArrayOf(0x49, 0x44, 0x41, 0x54).toList()) + // Get image bytes + for (j in i +4 until i + 4 + bodySize) { + body.add(byteArray[j]) + } + val crC32 = CRC32() + crC32.update(body.toByteArray(), 0, body.size) + cover!!.addAll(body) + cover!!.addAll(to4Bytes(crC32.value.toInt()).toList()) + } else { + // Find the chunk length + var lengthString = "" + byteArray.copyOfRange( i - 4, i).forEach { + lengthString += String.format("%02x", it) + } + val bodySize = lengthString.toLong(16).toInt() + png!!.addAll(byteArray.copyOfRange(i-4, i).toList()) + val body = ArrayList() + body.addAll(byteArrayOf(0x49, 0x44, 0x41, 0x54).toList()) + // Get image bytes + for (j in i +4 until i + 4 + bodySize) { + body.add(byteArray[j]) + } + val crC32 = CRC32() + crC32.update(body.toByteArray(), 0, body.size) + png!!.addAll(body) + png!!.addAll(to4Bytes(crC32.value.toInt()).toList()) + } + } + // Check if is fdAT + else if (byteArray[i] == 0x66.toByte() && byteArray[i + 1] == 0x64.toByte() && byteArray[ i + 2 ] == 0x41.toByte() && byteArray[ i + 3 ] == 0x54.toByte()) { // Find the chunk length var lengthString = "" byteArray.copyOfRange( i - 4, i).forEach { @@ -75,47 +157,37 @@ class APNGDisassembler(val byteArray: ByteArray) { val bodySize = lengthString.toLong(16).toInt() - png!!.addAll(byteArray.copyOfRange(i-4, i).toList()) - val body = ArrayList() - body.addAll(byteArrayOf(0x49, 0x44, 0x41, 0x54).toList()) - // Get image bytes - - for (j in i +4 until i + 4 + bodySize) { - body.add(byteArray[j]) - } - - val crC32 = CRC32() - crC32.update(body.toByteArray(), 0, body.size) - png!!.addAll(body) - png!!.addAll(to4Bytes(crC32.value.toInt()).toList()) - } else if (byteArray[i] == 0x66.toByte() && byteArray[i + 1] == 0x64.toByte() && byteArray[ i + 2 ] == 0x41.toByte() && byteArray[ i + 3 ] == 0x54.toByte()) { - // Find the chunk length - var lengthString = "" - byteArray.copyOfRange( i - 4, i).forEach { - lengthString += String.format("%02x", it) - } - - - var bodySize = lengthString.toLong(16).toInt() png!!.addAll(to4Bytes(bodySize - 4).toList()) val body = ArrayList() body.addAll(byteArrayOf(0x49, 0x44, 0x41, 0x54).toList()) // Get image bytes - for (j in i + 8 until i + 4 + bodySize) { body.add(byteArray[j]) } - val crC32 = CRC32() crC32.update(body.toByteArray(), 0, body.size) png!!.addAll(body) png!!.addAll(to4Bytes(crC32.value.toInt()).toList()) + } else if (byteArray[i] == 0x50.toByte() && byteArray[i + 1] == 0x4C.toByte() && byteArray[ i + 2 ] == 0x54.toByte() && byteArray[ i + 3 ] == 0x45.toByte()) { + var lengthString = "" + byteArray.copyOfRange( i - 4, i).forEach { + lengthString += String.format("%02x", it) + } + val bodySize = lengthString.toLong(16).toInt() + val body = ArrayList() + plte = byteArray.copyOfRange( i -4, i + 8 + bodySize) + } + else if (byteArray[i] == 0x74.toByte() && byteArray[i + 1] == 0x52.toByte() && byteArray[ i + 2 ] == 0x4E.toByte() && byteArray[ i + 3 ] == 0x53.toByte()) { + var lengthString = "" + byteArray.copyOfRange( i - 4, i).forEach { + lengthString += String.format("%02x", it) + } + val bodySize = lengthString.toLong(16).toInt() + val body = ArrayList() + tnrs = byteArray.copyOfRange( i -4, i + 8 + bodySize) } } } else { - var p = "" - p += String(byteArray.copyOfRange(0, 50)) - Log.e("TAG", "Not a apng : $p") throw NotApngException() } } @@ -169,6 +241,17 @@ class APNGDisassembler(val byteArray: ByteArray) { return result } + fun genBitmap() : ArrayList { + val generatedFrame = ArrayList() + pngList.forEach { + val btm = Bitmap.createBitmap(it.maxWidth, it.maxHeight, Bitmap.Config.ARGB_8888) + val canvas = Canvas(btm) + canvas.drawBitmap(BitmapFactory.decodeByteArray(it.byteArray, 0, it.byteArray.size), it.x_offsets.toFloat(), it.y_offsets.toFloat(), null) + generatedFrame.add(btm) + } + return generatedFrame + } + } \ No newline at end of file diff --git a/apng_library/src/main/java/oupson/apng/ApngAnimator.kt b/apng_library/src/main/java/oupson/apng/ApngAnimator.kt index b8837c1..e0d50b2 100644 --- a/apng_library/src/main/java/oupson/apng/ApngAnimator.kt +++ b/apng_library/src/main/java/oupson/apng/ApngAnimator.kt @@ -1,89 +1,247 @@ package oupson.apng -import android.graphics.Bitmap -import android.graphics.BitmapFactory -import android.graphics.Canvas +import android.graphics.* import android.os.Environment import android.os.Handler import android.widget.ImageView import org.jetbrains.anko.doAsync import org.jetbrains.anko.uiThread import java.io.File +import java.io.FileOutputStream +import java.io.IOException import java.net.URL + class ApngAnimator(val imageView : ImageView) { var play = true + var Frames = ArrayList() + var myHandler: Handler + var counter = 0 val generatedFrame = ArrayList() + var speed = 1 + + var lastFrame : Frame? = null + var bitmapBuffer : Bitmap? = null + + var background : Bitmap? = null + + var isDebug = false + init { myHandler = Handler() - - } - fun load(file: File) { - val extractedFrame = APNGDisassembler(file.readBytes()).pngList - Frames = extractedFrame - Frames.forEach { + fun load(file: File) { + // Download PNG + val extractedFrame = APNGDisassembler(file.readBytes()).pngList + + // Set last frame + lastFrame = extractedFrame[0] + + // Init image buffer + bitmapBuffer = BitmapFactory.decodeByteArray(lastFrame?.byteArray, 0, lastFrame?.byteArray!!.size) + generatedFrame.add(BitmapFactory.decodeByteArray(lastFrame?.byteArray, 0, lastFrame?.byteArray!!.size)) + Frames = extractedFrame + for (i in 1 until Frames.size) { + // Iterator + val it = Frames.get(i) + + // Current bitmap for the frame val btm = Bitmap.createBitmap(Frames[0].maxWidth, Frames[0].maxHeight, Bitmap.Config.ARGB_8888) val canvas = Canvas(btm) - canvas.drawBitmap(BitmapFactory.decodeByteArray(it.byteArray, 0, it.byteArray.size), it.x_offsets.toFloat(), it.y_offsets.toFloat(), null) - generatedFrame.add(btm) - } + val current = BitmapFactory.decodeByteArray(it.byteArray, 0, it.byteArray.size).copy(Bitmap.Config.ARGB_8888, true) + + // Write buffer to canvas + canvas.drawBitmap(bitmapBuffer, 0f, 0f, null) + + // APNG_DISPOSE_OP_BACKGROUND: the frame's region of the output buffer is to be cleared to fully transparent black before rendering the next frame. + if (lastFrame!!.dispose_op == Utils.Companion.dispose_op.APNG_DISPOSE_OP_BACKGROUND) { + canvas.drawRect(lastFrame!!.x_offsets.toFloat(), lastFrame!!.y_offsets.toFloat(), lastFrame!!.x_offsets + lastFrame!!.width.toFloat(), lastFrame!!.y_offsets + lastFrame!!.height.toFloat(), { val paint = Paint(); paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR); paint }()) + } + + // Clear current frame rect + // If `blend_op` is APNG_BLEND_OP_SOURCE all color components of the frame, including alpha, overwrite the current contents of the frame's output buffer region. + if (it.blend_op == Utils.Companion.blend_op.APNG_BLEND_OP_SOURCE) { + canvas.drawRect(it.x_offsets.toFloat(), it.y_offsets.toFloat(), it.x_offsets + current.width.toFloat(), it.y_offsets + current.height.toFloat(), { val paint = Paint(); paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR); paint }()) + } + + // Draw the bitmap + canvas.drawBitmap(current, it.x_offsets.toFloat(), it.y_offsets.toFloat(), null) + generatedFrame.add(btm) + + // Don't add current frame to bitmap buffer + if (Frames[i].dispose_op == Utils.Companion.dispose_op.APNG_DISPOSE_OP_PREVIOUS) { + //Do nothings + } + // Add current frame to bitmap buffer + else { + bitmapBuffer = btm + } + } + // DEBUG FUNCTION : WRITE RENDERED FRAME TO EXTERNAL STORAGE + if (isDebug) { + for (i in 0 until generatedFrame.size) { + try { + FileOutputStream(File(File(Environment.getExternalStorageDirectory(), "Documents"), "image_$i.png")).use { out -> + generatedFrame[i].compress(Bitmap.CompressFormat.PNG, 100, out) // bmp is your Bitmap instance + // PNG is a lossless format, the compression factor (100) is ignored + } + } catch (e: IOException) { + e.printStackTrace() + } + } + } + nextFrame() + } + + private fun loadUrl(url : URL) { + doAsync(exceptionHandler = {e -> e.printStackTrace()}) { + // Download PNG + val extractedFrame = APNGDisassembler(Loader().load(url)).pngList + + // Set last frame + lastFrame = extractedFrame[0] + + // Init image buffer + bitmapBuffer = BitmapFactory.decodeByteArray(lastFrame?.byteArray, 0, lastFrame?.byteArray!!.size) + generatedFrame.add(BitmapFactory.decodeByteArray(lastFrame?.byteArray, 0, lastFrame?.byteArray!!.size)) + Frames = extractedFrame + for (i in 1 until Frames.size) { + // Iterator + val it = Frames.get(i) + + // Current bitmap for the frame + val btm = Bitmap.createBitmap(Frames[0].maxWidth, Frames[0].maxHeight, Bitmap.Config.ARGB_8888) + val canvas = Canvas(btm) + + val current = BitmapFactory.decodeByteArray(it.byteArray, 0, it.byteArray.size).copy(Bitmap.Config.ARGB_8888, true) + + // Write buffer to canvas + canvas.drawBitmap(bitmapBuffer, 0f, 0f, null) + + // APNG_DISPOSE_OP_BACKGROUND: the frame's region of the output buffer is to be cleared to fully transparent black before rendering the next frame. + if (lastFrame!!.dispose_op == Utils.Companion.dispose_op.APNG_DISPOSE_OP_BACKGROUND) { + canvas.drawRect(lastFrame!!.x_offsets.toFloat(), lastFrame!!.y_offsets.toFloat(), lastFrame!!.x_offsets + lastFrame!!.width.toFloat(),lastFrame!!.y_offsets + lastFrame!!.height.toFloat(), {val paint = Paint(); paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR); paint}()) + } + + // Clear current frame rect + // If `blend_op` is APNG_BLEND_OP_SOURCE all color components of the frame, including alpha, overwrite the current contents of the frame's output buffer region. + if (it.blend_op == Utils.Companion.blend_op.APNG_BLEND_OP_SOURCE) { + canvas.drawRect(it.x_offsets.toFloat(), it.y_offsets.toFloat(), it.x_offsets + current.width.toFloat(),it.y_offsets + current.height.toFloat(), {val paint = Paint(); paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR); paint}()) + } + + // Draw the bitmap + canvas.drawBitmap(current, it.x_offsets.toFloat(), it.y_offsets.toFloat(), null) + generatedFrame.add(btm) + + // Don't add current frame to bitmap buffer + if (Frames[i].dispose_op == Utils.Companion.dispose_op.APNG_DISPOSE_OP_PREVIOUS) { + //Do nothings + } + // Add current frame to bitmap buffer + else { + bitmapBuffer = btm + } + } + // DEBUG FUNCTION : WRITE RENDERED FRAME TO EXTERNAL STORAGE + if (isDebug) { + for (i in 0 until generatedFrame.size) { + try { + FileOutputStream(File(File(Environment.getExternalStorageDirectory(), "Documents"), "image_$i.png")).use { out -> + generatedFrame[i].compress(Bitmap.CompressFormat.PNG, 100, out) // bmp is your Bitmap instance + // PNG is a lossless format, the compression factor (100) is ignored + } + } catch (e: IOException) { + e.printStackTrace() + } + } + } + uiThread { + nextFrame() + } + } + } + + fun load(byteArray: ByteArray) { + // Download PNG + val extractedFrame = APNGDisassembler(byteArray).pngList + + // Set last frame + lastFrame = extractedFrame[0] + + // Init image buffer + bitmapBuffer = BitmapFactory.decodeByteArray(lastFrame?.byteArray, 0, lastFrame?.byteArray!!.size) + generatedFrame.add(BitmapFactory.decodeByteArray(lastFrame?.byteArray, 0, lastFrame?.byteArray!!.size)) + Frames = extractedFrame + for (i in 1 until Frames.size) { + // Iterator + val it = Frames.get(i) + + // Current bitmap for the frame + val btm = Bitmap.createBitmap(Frames[0].maxWidth, Frames[0].maxHeight, Bitmap.Config.ARGB_8888) + val canvas = Canvas(btm) + + val current = BitmapFactory.decodeByteArray(it.byteArray, 0, it.byteArray.size).copy(Bitmap.Config.ARGB_8888, true) + + // Write buffer to canvas + canvas.drawBitmap(bitmapBuffer, 0f, 0f, null) + + // APNG_DISPOSE_OP_BACKGROUND: the frame's region of the output buffer is to be cleared to fully transparent black before rendering the next frame. + if (lastFrame!!.dispose_op == Utils.Companion.dispose_op.APNG_DISPOSE_OP_BACKGROUND) { + canvas.drawRect(lastFrame!!.x_offsets.toFloat(), lastFrame!!.y_offsets.toFloat(), lastFrame!!.x_offsets + lastFrame!!.width.toFloat(), lastFrame!!.y_offsets + lastFrame!!.height.toFloat(), { val paint = Paint(); paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR); paint }()) + } + + // Clear current frame rect + // If `blend_op` is APNG_BLEND_OP_SOURCE all color components of the frame, including alpha, overwrite the current contents of the frame's output buffer region. + if (it.blend_op == Utils.Companion.blend_op.APNG_BLEND_OP_SOURCE) { + canvas.drawRect(it.x_offsets.toFloat(), it.y_offsets.toFloat(), it.x_offsets + current.width.toFloat(), it.y_offsets + current.height.toFloat(), { val paint = Paint(); paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR); paint }()) + } + + // Draw the bitmap + canvas.drawBitmap(current, it.x_offsets.toFloat(), it.y_offsets.toFloat(), null) + generatedFrame.add(btm) + + // Don't add current frame to bitmap buffer + if (Frames[i].dispose_op == Utils.Companion.dispose_op.APNG_DISPOSE_OP_PREVIOUS) { + //Do nothings + } + // Add current frame to bitmap buffer + else { + bitmapBuffer = btm + } + } + // DEBUG FUNCTION : WRITE RENDERED FRAME TO EXTERNAL STORAGE + if (isDebug) { + for (i in 0 until generatedFrame.size) { + try { + FileOutputStream(File(File(Environment.getExternalStorageDirectory(), "Documents"), "image_$i.png")).use { out -> + generatedFrame[i].compress(Bitmap.CompressFormat.PNG, 100, out) // bmp is your Bitmap instance + // PNG is a lossless format, the compression factor (100) is ignored + } + } catch (e: IOException) { + e.printStackTrace() + } + } + } nextFrame() } fun load(string: String) { if (string.contains("http") || string.contains("https")) { val url = URL(string) - doAsync { - val extractedFrame = APNGDisassembler(Loader().load(url)).pngList - Frames = extractedFrame - - Frames.forEach { - val btm = Bitmap.createBitmap(Frames[0].maxWidth, Frames[0].maxHeight, Bitmap.Config.ARGB_8888) - val canvas = Canvas(btm) - canvas.drawBitmap(BitmapFactory.decodeByteArray(it.byteArray, 0, it.byteArray.size), it.x_offsets.toFloat(), it.y_offsets.toFloat(), null) - generatedFrame.add(btm) - } - uiThread { - nextFrame() - } - } + loadUrl(url) } else if (File(string).exists()) { - val extractedFrame = APNGDisassembler(Loader().load(File(string))).pngList - Frames = extractedFrame - - Frames.forEach { - val btm = Bitmap.createBitmap(Frames[0].maxWidth, Frames[0].maxHeight, Bitmap.Config.ARGB_8888) - val canvas = Canvas(btm) - canvas.drawBitmap(BitmapFactory.decodeByteArray(it.byteArray, 0, it.byteArray.size), it.x_offsets.toFloat(), it.y_offsets.toFloat(), null) - generatedFrame.add(btm) - } - nextFrame() + load(File(string)) } } - fun load(byteArray: ByteArray) { - val extractedFrame = APNGDisassembler(byteArray).pngList - Frames = extractedFrame - - Frames.forEach { - val btm = Bitmap.createBitmap(Frames[0].maxWidth, Frames[0].maxHeight, Bitmap.Config.ARGB_8888) - val canvas = Canvas(btm) - canvas.drawBitmap(BitmapFactory.decodeByteArray(it.byteArray, 0, it.byteArray.size), it.x_offsets.toFloat(), it.y_offsets.toFloat(), null) - generatedFrame.add(btm) - } - - nextFrame() - } - fun nextFrame() { if (counter == Frames.size) { counter = 0 @@ -92,8 +250,8 @@ class ApngAnimator(val imageView : ImageView) { imageView.setImageBitmap(generatedFrame[counter]) counter++ myHandler.postDelayed({ - mustPlay() - }, delay.toLong()) + ifmustPlay() + }, delay.toLong() * speed) } fun pause() { @@ -103,15 +261,17 @@ class ApngAnimator(val imageView : ImageView) { fun play() { if (!play) { play = true - mustPlay() + ifmustPlay() } } - private fun mustPlay() { + private fun ifmustPlay() { if (play) { nextFrame() } } - + fun setFrameSpeed(speed : Int) { + this.speed = speed + } } \ No newline at end of file diff --git a/apng_library/src/main/java/oupson/apng/Frame.kt b/apng_library/src/main/java/oupson/apng/Frame.kt index 033a506..eda9642 100644 --- a/apng_library/src/main/java/oupson/apng/Frame.kt +++ b/apng_library/src/main/java/oupson/apng/Frame.kt @@ -29,6 +29,9 @@ class Frame { val maxWidth : Int val maxHeight : Int + val blend_op: Utils.Companion.blend_op + val dispose_op : Utils.Companion.dispose_op + constructor(byteArray: ByteArray) { if (isPng(byteArray)) { this.byteArray = byteArray @@ -50,6 +53,8 @@ class Frame { maxHeight = -1 maxWidth = -1 + blend_op = Utils.Companion.blend_op.APNG_BLEND_OP_SOURCE + dispose_op = Utils.Companion.dispose_op.APNG_DISPOSE_OP_NONE } else { throw NotPngException() } @@ -75,12 +80,14 @@ class Frame { maxHeight = -1 maxWidth = -1 + blend_op = Utils.Companion.blend_op.APNG_BLEND_OP_SOURCE + dispose_op = Utils.Companion.dispose_op.APNG_DISPOSE_OP_NONE } else { throw NotPngException() } } - constructor(byteArray: ByteArray, delay : Float, xOffsets : Int, yOffsets : Int, maxWidth : Int, maxHeight : Int) { + constructor(byteArray: ByteArray, delay : Float, xOffsets : Int, yOffsets : Int, maxWidth : Int, maxHeight : Int, blend_op: Utils.Companion.blend_op, dispose_op: Utils.Companion.dispose_op) { if (isPng(byteArray)) { this.byteArray = byteArray // Get width and height for image @@ -101,6 +108,8 @@ class Frame { this.maxWidth = maxWidth this.maxHeight = maxHeight + this.blend_op = blend_op + this.dispose_op = dispose_op } else { throw NotPngException() } diff --git a/apng_library/src/main/java/oupson/apng/fcTL.kt b/apng_library/src/main/java/oupson/apng/fcTL.kt index d1eea0c..8961ae8 100644 --- a/apng_library/src/main/java/oupson/apng/fcTL.kt +++ b/apng_library/src/main/java/oupson/apng/fcTL.kt @@ -1,5 +1,9 @@ package oupson.apng +import android.util.Log +import oupson.apng.Utils.Companion.getBlend_op +import oupson.apng.Utils.Companion.getDispose_op + class fcTL(byteArray: ByteArray) { private var corpsSize = -1 @@ -16,6 +20,8 @@ class fcTL(byteArray: ByteArray) { var x_offset : Int = 0 var y_offset : Int = 0 + var blend_op : Utils.Companion.blend_op = Utils.Companion.blend_op.APNG_BLEND_OP_SOURCE + var dispose_op : Utils.Companion.dispose_op= Utils.Companion.dispose_op.APNG_DISPOSE_OP_NONE init { for (i in 0 until byteArray.size) { // Find fcTL chunk @@ -91,6 +97,9 @@ class fcTL(byteArray: ByteArray) { } fcTLBody= _fcTLBody.toByteArray() + blend_op = getBlend_op(String.format("%02x", byteArray[33]).toLong(16).toInt()) + + dispose_op = getDispose_op(String.format("%02x", byteArray[32]).toLong(16).toInt()) } } } diff --git a/app-test/src/main/java/oupson/apngcreator/MainActivity.kt b/app-test/src/main/java/oupson/apngcreator/MainActivity.kt index 891ee9e..74ec105 100644 --- a/app-test/src/main/java/oupson/apngcreator/MainActivity.kt +++ b/app-test/src/main/java/oupson/apngcreator/MainActivity.kt @@ -26,7 +26,7 @@ import java.net.URL class MainActivity : AppCompatActivity() { lateinit var animator : ApngAnimator - val imageUrl = "http://oupson.oupsman.fr/apng/image.apng" + val imageUrl = "https://cloud.githubusercontent.com/assets/13003036/24979875/e658e7c8-1fa3-11e7-908a-f1a201d38d52.png" override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState)