Add better support and stability
This commit is contained in:
parent
9604a768ca
commit
3953707072
Binary file not shown.
|
@ -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>
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue