Add better support and stability

This commit is contained in:
oupson 2018-10-05 21:38:13 +02:00
parent 9604a768ca
commit 3953707072
8 changed files with 362 additions and 93 deletions

View File

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="UnusedSymbol" enabled="false" level="WARNING" enabled_by_default="false" />
</profile>
</component>

View File

@ -1,2 +1,4 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="oupson.apng" /> package="oupson.apng">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
</manifest>

View File

@ -1,25 +1,32 @@
package oupson.apng package oupson.apng
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.util.Log import android.util.Log
import oupson.apng.ApngFactory.Companion.isApng
import oupson.apng.ApngFactory.Companion.isApng
import oupson.apng.ApngFactory.Companion.pngSignature import oupson.apng.ApngFactory.Companion.pngSignature
import java.util.zip.CRC32 import java.util.zip.CRC32
class APNGDisassembler(val byteArray: ByteArray) { class APNGDisassembler(val byteArray: ByteArray) {
val pngList = ArrayList<Frame>() val pngList = ArrayList<Frame>()
var png : ArrayList<Byte>? = null var png : ArrayList<Byte>? = null
var cover : ArrayList<Byte>? = null
var delay = -1f var delay = -1f
var yOffset= -1 var yOffset= -1
var xOffset = -1 var xOffset = -1
var plte : ByteArray? = null
var tnrs : ByteArray? = null
var maxWidth = 0 var maxWidth = 0
var maxHeight = 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 { init {
if (ApngFactory.isApng(byteArray)) { if (ApngFactory.isApng(byteArray)) {
val ihdr = IHDR() val ihdr = IHDR()
@ -30,17 +37,36 @@ class APNGDisassembler(val byteArray: ByteArray) {
// find new Frame with fcTL // 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 (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 (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() png = ArrayList()
val fcTL = fcTL(byteArray.copyOfRange(i-4, i + 28)) val fcTL = fcTL(byteArray.copyOfRange(i-4, i + 32))
delay = fcTL.delay delay = fcTL.delay
yOffset = fcTL.y_offset yOffset = fcTL.y_offset
xOffset = fcTL.x_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 width = fcTL.pngWidth
val height = fcTL.pngHeight val height = fcTL.pngHeight
png!!.addAll(pngSignature.toList()) png!!.addAll(pngSignature.toList())
png!!.addAll(generate_ihdr(ihdr, width, height).toList()) png!!.addAll(generate_ihdr(ihdr, width, height).toList())
if (plte != null) {
png!!.addAll(plte!!.toList())
}
if (tnrs != null) {
png!!.addAll(tnrs!!.toList())
}
} else { } else {
// Add IEND body length : 0 // Add IEND body length : 0
png!!.addAll(to4Bytes(0).toList()) png!!.addAll(to4Bytes(0).toList())
@ -51,22 +77,78 @@ class APNGDisassembler(val byteArray: ByteArray) {
crC32.update(iend, 0, iend.size) crC32.update(iend, 0, iend.size)
png!!.addAll(iend.toList()) png!!.addAll(iend.toList())
png!!.addAll(to4Bytes(crC32.value.toInt()).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() png = ArrayList()
val fcTL = fcTL(byteArray.copyOfRange(i-4, i + 28)) val fcTL = fcTL(byteArray.copyOfRange(i-4, i + 32))
delay = fcTL.delay delay = fcTL.delay
yOffset = fcTL.y_offset yOffset = fcTL.y_offset
xOffset = fcTL.x_offset xOffset = fcTL.x_offset
blend_op = fcTL.blend_op
dispose_op = fcTL.dispose_op
val width = fcTL.pngWidth val width = fcTL.pngWidth
val height = fcTL.pngHeight val height = fcTL.pngHeight
png!!.addAll(pngSignature.toList()) png!!.addAll(pngSignature.toList())
png!!.addAll(generate_ihdr(ihdr, width, height).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<Byte>()
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<Byte>()
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 // Find the chunk length
var lengthString = "" var lengthString = ""
byteArray.copyOfRange( i - 4, i).forEach { byteArray.copyOfRange( i - 4, i).forEach {
@ -75,47 +157,37 @@ class APNGDisassembler(val byteArray: ByteArray) {
val bodySize = lengthString.toLong(16).toInt() val bodySize = lengthString.toLong(16).toInt()
png!!.addAll(byteArray.copyOfRange(i-4, i).toList())
val body = ArrayList<Byte>()
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()) png!!.addAll(to4Bytes(bodySize - 4).toList())
val body = ArrayList<Byte>() val body = ArrayList<Byte>()
body.addAll(byteArrayOf(0x49, 0x44, 0x41, 0x54).toList()) body.addAll(byteArrayOf(0x49, 0x44, 0x41, 0x54).toList())
// Get image bytes // Get image bytes
for (j in i + 8 until i + 4 + bodySize) { for (j in i + 8 until i + 4 + bodySize) {
body.add(byteArray[j]) body.add(byteArray[j])
} }
val crC32 = CRC32() val crC32 = CRC32()
crC32.update(body.toByteArray(), 0, body.size) crC32.update(body.toByteArray(), 0, body.size)
png!!.addAll(body) png!!.addAll(body)
png!!.addAll(to4Bytes(crC32.value.toInt()).toList()) 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<Byte>()
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<Byte>()
tnrs = byteArray.copyOfRange( i -4, i + 8 + bodySize)
} }
} }
} else { } else {
var p = ""
p += String(byteArray.copyOfRange(0, 50))
Log.e("TAG", "Not a apng : $p")
throw NotApngException() throw NotApngException()
} }
} }
@ -169,6 +241,17 @@ class APNGDisassembler(val byteArray: ByteArray) {
return result return result
} }
fun genBitmap() : ArrayList<Bitmap> {
val generatedFrame = ArrayList<Bitmap>()
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
}
} }

View File

@ -1,89 +1,247 @@
package oupson.apng package oupson.apng
import android.graphics.Bitmap import android.graphics.*
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.os.Environment import android.os.Environment
import android.os.Handler import android.os.Handler
import android.widget.ImageView import android.widget.ImageView
import org.jetbrains.anko.doAsync import org.jetbrains.anko.doAsync
import org.jetbrains.anko.uiThread import org.jetbrains.anko.uiThread
import java.io.File import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.net.URL import java.net.URL
class ApngAnimator(val imageView : ImageView) { class ApngAnimator(val imageView : ImageView) {
var play = true var play = true
var Frames = ArrayList<Frame>() var Frames = ArrayList<Frame>()
var myHandler: Handler var myHandler: Handler
var counter = 0 var counter = 0
val generatedFrame = ArrayList<Bitmap>() val generatedFrame = ArrayList<Bitmap>()
var speed = 1
var lastFrame : Frame? = null
var bitmapBuffer : Bitmap? = null
var background : Bitmap? = null
var isDebug = false
init { init {
myHandler = Handler() 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 btm = Bitmap.createBitmap(Frames[0].maxWidth, Frames[0].maxHeight, Bitmap.Config.ARGB_8888)
val canvas = Canvas(btm) 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() nextFrame()
} }
fun load(string: String) { fun load(string: String) {
if (string.contains("http") || string.contains("https")) { if (string.contains("http") || string.contains("https")) {
val url = URL(string) val url = URL(string)
doAsync { loadUrl(url)
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()
}
}
} else if (File(string).exists()) { } else if (File(string).exists()) {
val extractedFrame = APNGDisassembler(Loader().load(File(string))).pngList load(File(string))
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 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() { fun nextFrame() {
if (counter == Frames.size) { if (counter == Frames.size) {
counter = 0 counter = 0
@ -92,8 +250,8 @@ class ApngAnimator(val imageView : ImageView) {
imageView.setImageBitmap(generatedFrame[counter]) imageView.setImageBitmap(generatedFrame[counter])
counter++ counter++
myHandler.postDelayed({ myHandler.postDelayed({
mustPlay() ifmustPlay()
}, delay.toLong()) }, delay.toLong() * speed)
} }
fun pause() { fun pause() {
@ -103,15 +261,17 @@ class ApngAnimator(val imageView : ImageView) {
fun play() { fun play() {
if (!play) { if (!play) {
play = true play = true
mustPlay() ifmustPlay()
} }
} }
private fun mustPlay() { private fun ifmustPlay() {
if (play) { if (play) {
nextFrame() nextFrame()
} }
} }
fun setFrameSpeed(speed : Int) {
this.speed = speed
}
} }

View File

@ -29,6 +29,9 @@ class Frame {
val maxWidth : Int val maxWidth : Int
val maxHeight : Int val maxHeight : Int
val blend_op: Utils.Companion.blend_op
val dispose_op : Utils.Companion.dispose_op
constructor(byteArray: ByteArray) { constructor(byteArray: ByteArray) {
if (isPng(byteArray)) { if (isPng(byteArray)) {
this.byteArray = byteArray this.byteArray = byteArray
@ -50,6 +53,8 @@ class Frame {
maxHeight = -1 maxHeight = -1
maxWidth = -1 maxWidth = -1
blend_op = Utils.Companion.blend_op.APNG_BLEND_OP_SOURCE
dispose_op = Utils.Companion.dispose_op.APNG_DISPOSE_OP_NONE
} else { } else {
throw NotPngException() throw NotPngException()
} }
@ -75,12 +80,14 @@ class Frame {
maxHeight = -1 maxHeight = -1
maxWidth = -1 maxWidth = -1
blend_op = Utils.Companion.blend_op.APNG_BLEND_OP_SOURCE
dispose_op = Utils.Companion.dispose_op.APNG_DISPOSE_OP_NONE
} else { } else {
throw NotPngException() 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)) { if (isPng(byteArray)) {
this.byteArray = byteArray this.byteArray = byteArray
// Get width and height for image // Get width and height for image
@ -101,6 +108,8 @@ class Frame {
this.maxWidth = maxWidth this.maxWidth = maxWidth
this.maxHeight = maxHeight this.maxHeight = maxHeight
this.blend_op = blend_op
this.dispose_op = dispose_op
} else { } else {
throw NotPngException() throw NotPngException()
} }

View File

@ -1,5 +1,9 @@
package oupson.apng 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) { class fcTL(byteArray: ByteArray) {
private var corpsSize = -1 private var corpsSize = -1
@ -16,6 +20,8 @@ class fcTL(byteArray: ByteArray) {
var x_offset : Int = 0 var x_offset : Int = 0
var y_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 { init {
for (i in 0 until byteArray.size) { for (i in 0 until byteArray.size) {
// Find fcTL chunk // Find fcTL chunk
@ -91,6 +97,9 @@ class fcTL(byteArray: ByteArray) {
} }
fcTLBody= _fcTLBody.toByteArray() 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())
} }
} }
} }

View File

@ -26,7 +26,7 @@ import java.net.URL
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
lateinit var animator : ApngAnimator 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?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)