Working on optimiser functionality to reduce APNG size.
Loader now static Disassembler now static Optimize imports
This commit is contained in:
parent
0150810541
commit
66055221f1
|
@ -1,9 +1,6 @@
|
|||
package oupson.apng
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.Canvas
|
||||
import android.util.Log
|
||||
import oupson.apng.Utils.Companion.isApng
|
||||
import oupson.apng.Utils.Companion.pngSignature
|
||||
import oupson.apng.Utils.Companion.to4Bytes
|
||||
|
@ -12,240 +9,227 @@ import oupson.apng.chunks.fcTL
|
|||
import oupson.apng.exceptions.NotApngException
|
||||
import java.util.zip.CRC32
|
||||
|
||||
class APNGDisassembler(val byteArray: ByteArray) {
|
||||
val pngList = ArrayList<Frame>()
|
||||
var png : ArrayList<Byte>? = null
|
||||
var cover : ArrayList<Byte>? = 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
|
||||
class APNGDisassembler() {
|
||||
companion object {
|
||||
|
||||
var apng : Apng
|
||||
fun disassemble(byteArray: ByteArray) : Apng {
|
||||
val pngList = ArrayList<Frame>()
|
||||
var png: ArrayList<Byte>? = null
|
||||
var cover: ArrayList<Byte>? = 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
|
||||
val apng: Apng
|
||||
if (isApng(byteArray)) {
|
||||
apng = Apng()
|
||||
val ihdr = IHDR()
|
||||
ihdr.parseIHDR(byteArray)
|
||||
maxWidth = ihdr.pngWidth
|
||||
maxHeight = ihdr.pngHeight
|
||||
for (i in 0 until byteArray.size) {
|
||||
// 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()) {
|
||||
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())
|
||||
|
||||
init {
|
||||
if (isApng(byteArray)) {
|
||||
apng = Apng()
|
||||
val ihdr = IHDR()
|
||||
ihdr.parseIHDR(byteArray)
|
||||
maxWidth = ihdr.pngWidth
|
||||
maxHeight = ihdr.pngHeight
|
||||
for(i in 0 until byteArray.size) {
|
||||
// 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()) {
|
||||
if (png == null) {
|
||||
if (cover != null) {
|
||||
cover!!.addAll(to4Bytes(0).toList())
|
||||
apng.cover = BitmapFactory.decodeByteArray(cover.toByteArray(), 0, cover.size)
|
||||
}
|
||||
png = ArrayList()
|
||||
val fcTL = fcTL(byteArray.copyOfRange(i - 4, i + 36))
|
||||
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 {
|
||||
// Add IEND body length : 0
|
||||
png.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.addAll(iend.toList())
|
||||
png.addAll(to4Bytes(crC32.value.toInt()).toList())
|
||||
pngList.add(Frame(png.toByteArray(), delay, xOffset, yOffset, maxWidth, maxHeight, blend_op, dispose_op))
|
||||
|
||||
apng.cover = BitmapFactory.decodeByteArray(cover!!.toByteArray(), 0, cover!!.size)
|
||||
}
|
||||
png = ArrayList()
|
||||
val fcTL = fcTL(byteArray.copyOfRange(i - 4, i + 36))
|
||||
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())
|
||||
png = ArrayList()
|
||||
|
||||
if (plte != null) {
|
||||
png!!.addAll(plte!!.toList())
|
||||
}
|
||||
val bodySize = {
|
||||
var lengthString = ""
|
||||
byteArray.copyOfRange(i - 4, i).forEach {
|
||||
lengthString += String.format("%02x", it)
|
||||
}
|
||||
lengthString.toLong(16).toInt()
|
||||
}()
|
||||
val newBytes = byteArray.copyOfRange(i - 4, i + 4 + bodySize)
|
||||
val fcTL = fcTL(newBytes)
|
||||
delay = fcTL.delay
|
||||
|
||||
if (tnrs != null) {
|
||||
png!!.addAll(tnrs!!.toList())
|
||||
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 {
|
||||
// Add IEND body length : 0
|
||||
} else if (i == byteArray.size - 1) {
|
||||
png!!.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)
|
||||
png!!.addAll(iend.toList())
|
||||
png!!.addAll(to4Bytes(crC32.value.toInt()).toList())
|
||||
pngList.add(Frame(png!!.toByteArray(), delay, xOffset, yOffset, maxWidth, maxHeight, blend_op, dispose_op))
|
||||
|
||||
png = ArrayList()
|
||||
|
||||
val bodySize = {
|
||||
png.addAll(iend.toList())
|
||||
png.addAll(to4Bytes(crC32.value.toInt()).toList())
|
||||
pngList.add(Frame(png.toByteArray(), delay, xOffset, yOffset, maxWidth, maxHeight, blend_op, dispose_op))
|
||||
}
|
||||
// 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()
|
||||
cover.addAll(pngSignature.toList())
|
||||
cover.addAll(generate_ihdr(ihdr, maxWidth, maxHeight).toList())
|
||||
}
|
||||
// Find the chunk length
|
||||
var lengthString = ""
|
||||
byteArray.copyOfRange(i - 4, i).forEach {
|
||||
lengthString += String.format("%02x", it)
|
||||
}
|
||||
lengthString.toLong(16).toInt()
|
||||
}()
|
||||
val newBytes = byteArray.copyOfRange(i - 4, i + 4 + bodySize)
|
||||
val fcTL = fcTL(newBytes)
|
||||
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())
|
||||
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())
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (i == byteArray.size - 1) {
|
||||
png!!.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)
|
||||
png!!.addAll(iend.toList())
|
||||
png!!.addAll(to4Bytes(crC32.value.toInt()).toList())
|
||||
pngList.add(Frame(png!!.toByteArray(), delay, xOffset, yOffset, maxWidth, maxHeight, blend_op, dispose_op))
|
||||
}
|
||||
// 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()
|
||||
cover!!.addAll(pngSignature.toList())
|
||||
cover!!.addAll(generate_ihdr(ihdr, maxWidth, maxHeight).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 {
|
||||
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())
|
||||
png!!.addAll(to4Bytes(bodySize - 4).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) {
|
||||
for (j in i + 8 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
|
||||
png.addAll(body)
|
||||
png.addAll(to4Bytes(crC32.value.toInt()).toList())
|
||||
}
|
||||
// Get plte chunks if exist. The PLTE chunk contains from 1 to 256 palette entries, each a three-byte series of the form:
|
||||
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 {
|
||||
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])
|
||||
plte = byteArray.copyOfRange(i - 4, i + 8 + bodySize)
|
||||
}
|
||||
// Get tnrs chunk if exist. Used for transparency
|
||||
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 crC32 = CRC32()
|
||||
crC32.update(body.toByteArray(), 0, body.size)
|
||||
png!!.addAll(body)
|
||||
png!!.addAll(to4Bytes(crC32.value.toInt()).toList())
|
||||
val bodySize = lengthString.toLong(16).toInt()
|
||||
tnrs = byteArray.copyOfRange(i - 4, i + 8 + bodySize)
|
||||
}
|
||||
}
|
||||
// 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 {
|
||||
lengthString += String.format("%02x", it)
|
||||
}
|
||||
val bodySize = lengthString.toLong(16).toInt()
|
||||
png!!.addAll(to4Bytes(bodySize - 4).toList())
|
||||
val body = ArrayList<Byte>()
|
||||
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())
|
||||
}
|
||||
// Get plte chunks if exist. The PLTE chunk contains from 1 to 256 palette entries, each a three-byte series of the form:
|
||||
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()
|
||||
plte = byteArray.copyOfRange( i -4, i + 8 + bodySize)
|
||||
}
|
||||
// Get tnrs chunk if exist. Used for transparency
|
||||
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()
|
||||
tnrs = byteArray.copyOfRange( i -4, i + 8 + bodySize)
|
||||
}
|
||||
apng.frames = pngList
|
||||
return apng
|
||||
} else {
|
||||
throw NotApngException()
|
||||
}
|
||||
apng.frames = pngList
|
||||
|
||||
} else {
|
||||
throw NotApngException()
|
||||
}
|
||||
}
|
||||
|
||||
private fun generate_ihdr(ihdrOfApng: IHDR, width : Int, height : Int) : ByteArray {
|
||||
val ihdr = ArrayList<Byte>()
|
||||
// We need a body var to know body length and generate crc
|
||||
val ihdr_body = ArrayList<Byte>()
|
||||
// Add chunk body length
|
||||
ihdr.addAll(to4Bytes(ihdrOfApng.ihdrCorps.size).toList())
|
||||
// Add IHDR
|
||||
ihdr_body.addAll(byteArrayOf(0x49.toByte(), 0x48.toByte(), 0x44.toByte(), 0x52.toByte()).toList())
|
||||
// Add the max width and height
|
||||
ihdr_body.addAll(to4Bytes(width).toList())
|
||||
ihdr_body.addAll(to4Bytes(height).toList())
|
||||
// Add complicated stuff like depth color ...
|
||||
// If you want correct png you need same parameters. Good solution is to create new png.
|
||||
ihdr_body.addAll(ihdrOfApng.ihdrCorps.copyOfRange(8, 13).toList())
|
||||
// Generate CRC
|
||||
val crC32 = CRC32()
|
||||
crC32.update(ihdr_body.toByteArray(), 0, ihdr_body.size)
|
||||
ihdr.addAll(ihdr_body)
|
||||
ihdr.addAll(to4Bytes(crC32.value.toInt()).toList())
|
||||
return ihdr.toByteArray()
|
||||
}
|
||||
|
||||
|
||||
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)
|
||||
private fun generate_ihdr(ihdrOfApng: IHDR, width : Int, height : Int) : ByteArray {
|
||||
val ihdr = ArrayList<Byte>()
|
||||
// We need a body var to know body length and generate crc
|
||||
val ihdr_body = ArrayList<Byte>()
|
||||
// Add chunk body length
|
||||
ihdr.addAll(to4Bytes(ihdrOfApng.ihdrCorps.size).toList())
|
||||
// Add IHDR
|
||||
ihdr_body.addAll(byteArrayOf(0x49.toByte(), 0x48.toByte(), 0x44.toByte(), 0x52.toByte()).toList())
|
||||
// Add the max width and height
|
||||
ihdr_body.addAll(to4Bytes(width).toList())
|
||||
ihdr_body.addAll(to4Bytes(height).toList())
|
||||
// Add complicated stuff like depth color ...
|
||||
// If you want correct png you need same parameters. Good solution is to create new png.
|
||||
ihdr_body.addAll(ihdrOfApng.ihdrCorps.copyOfRange(8, 13).toList())
|
||||
// Generate CRC
|
||||
val crC32 = CRC32()
|
||||
crC32.update(ihdr_body.toByteArray(), 0, ihdr_body.size)
|
||||
ihdr.addAll(ihdr_body)
|
||||
ihdr.addAll(to4Bytes(crC32.value.toInt()).toList())
|
||||
return ihdr.toByteArray()
|
||||
}
|
||||
return generatedFrame
|
||||
}
|
||||
}
|
|
@ -3,7 +3,6 @@ package oupson.apng
|
|||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import oupson.apng.ImageUtils.PnnQuantizer
|
||||
import oupson.apng.Utils.Companion.convertImage
|
||||
import oupson.apng.Utils.Companion.getBlend_op
|
||||
import oupson.apng.Utils.Companion.getDispose_op
|
||||
import oupson.apng.Utils.Companion.pngSignature
|
||||
|
@ -12,7 +11,6 @@ import oupson.apng.Utils.Companion.to4Bytes
|
|||
import oupson.apng.Utils.Companion.toByteArray
|
||||
import oupson.apng.chunks.IDAT
|
||||
import oupson.apng.exceptions.NoFrameException
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.util.zip.CRC32
|
||||
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@ package oupson.apng
|
|||
|
||||
import android.content.Context
|
||||
import android.graphics.*
|
||||
import android.os.Handler
|
||||
import android.widget.ImageView
|
||||
import org.jetbrains.anko.doAsync
|
||||
import org.jetbrains.anko.uiThread
|
||||
|
@ -13,7 +12,7 @@ import java.net.URL
|
|||
/**
|
||||
* Class to play APNG
|
||||
*/
|
||||
class ApngAnimator(val context: Context) {
|
||||
class ApngAnimator(private val context: Context) {
|
||||
var isPlaying = true
|
||||
private set(value) {
|
||||
field = value
|
||||
|
@ -54,7 +53,7 @@ class ApngAnimator(val context: Context) {
|
|||
doAsync {
|
||||
this@ApngAnimator.speed = speed
|
||||
// Download PNG
|
||||
APNGDisassembler(file.readBytes()).pngList.apply {
|
||||
APNGDisassembler.disassemble(file.readBytes()).frames.apply {
|
||||
draw(this)
|
||||
}
|
||||
setupAnimationDrawableAndStart()
|
||||
|
@ -71,7 +70,7 @@ class ApngAnimator(val context: Context) {
|
|||
doAsync(exceptionHandler = { e -> e.printStackTrace() }) {
|
||||
this@ApngAnimator.speed = speed
|
||||
// Download PNG
|
||||
APNGDisassembler(Loader().load(context, url)).pngList.apply {
|
||||
APNGDisassembler.disassemble(Loader.load(context, url)).frames.apply {
|
||||
draw(this)
|
||||
}
|
||||
setupAnimationDrawableAndStart()
|
||||
|
@ -87,7 +86,7 @@ class ApngAnimator(val context: Context) {
|
|||
fun load(byteArray: ByteArray, speed: Float? = null) {
|
||||
doAsync {
|
||||
this@ApngAnimator.speed = speed
|
||||
APNGDisassembler(byteArray).pngList.apply {
|
||||
APNGDisassembler.disassemble(byteArray).frames.apply {
|
||||
draw(this)
|
||||
}
|
||||
setupAnimationDrawableAndStart()
|
||||
|
|
|
@ -1,287 +0,0 @@
|
|||
package oupson.apng
|
||||
|
||||
import oupson.apng.Utils.Companion.pngSignature
|
||||
import oupson.apng.exceptions.NoFrameException
|
||||
import java.util.zip.CRC32
|
||||
|
||||
|
||||
/**
|
||||
* APNG is a class for create apng
|
||||
*
|
||||
* Call .addFrame() to add a Frame
|
||||
* Call .create to get the generated file
|
||||
*
|
||||
* @author oupson
|
||||
*
|
||||
* @throws NotPngException
|
||||
* @throws NoFrameException
|
||||
*
|
||||
*/
|
||||
class ApngFactory {
|
||||
|
||||
private var seq = 0
|
||||
|
||||
var frames = ArrayList<Frame>()
|
||||
|
||||
/**
|
||||
* @return a byte array of the generated png
|
||||
*
|
||||
* @throws NoFrameException
|
||||
*/
|
||||
fun create(): ByteArray {
|
||||
|
||||
if (frames.isNotEmpty()) {
|
||||
val res = ArrayList<Byte>()
|
||||
|
||||
// Add PNG signature
|
||||
res.addAll(pngSignature.toList())
|
||||
|
||||
// Add Image Header
|
||||
res.addAll(generate_ihdr().toList())
|
||||
|
||||
// Add Animation Controller
|
||||
res.addAll(generateACTL())
|
||||
|
||||
// Get max height and max width
|
||||
val maxHeight = frames.sortedByDescending { it.height }[0].height
|
||||
val maxWitdh = frames.sortedByDescending { it.width }[0].width
|
||||
|
||||
for (i in 0 until frames.size) {
|
||||
|
||||
// If it's the first frame
|
||||
if (i == 0) {
|
||||
val framesByte = ArrayList<Byte>()
|
||||
// region fcTL
|
||||
// Create the fcTL
|
||||
val fcTL = ArrayList<Byte>()
|
||||
// Add the length of the chunk body
|
||||
framesByte.addAll(byteArrayOf(0x00, 0x00, 0x00, 0x1A).toList())
|
||||
|
||||
// Add acTL
|
||||
fcTL.addAll(byteArrayOf(0x66, 0x63, 0x54, 0x4c).toList())
|
||||
|
||||
// Add the frame number
|
||||
fcTL.addAll(to4Bytes(seq).toList())
|
||||
seq++
|
||||
|
||||
// Add width and height
|
||||
fcTL.addAll(to4Bytes(frames[i].width).toList())
|
||||
fcTL.addAll(to4Bytes(frames[i].height).toList())
|
||||
|
||||
// Calculate offset
|
||||
if (frames[i].width < maxWitdh) {
|
||||
val xOffset = (maxWitdh / 2) - (frames[i].width / 2)
|
||||
fcTL.addAll(to4Bytes(xOffset).toList())
|
||||
} else {
|
||||
fcTL.addAll(to4Bytes(0).toList())
|
||||
}
|
||||
if (frames[i].height < maxHeight) {
|
||||
val xOffset = (maxHeight / 2) - (frames[i].height / 2)
|
||||
fcTL.addAll(to4Bytes(xOffset).toList())
|
||||
} else {
|
||||
fcTL.addAll(to4Bytes(0).toList())
|
||||
}
|
||||
|
||||
// Set frame delay
|
||||
fcTL.addAll(to2Bytes(frames[i].delay.toInt()).toList())
|
||||
fcTL.addAll(to2Bytes(1000).toList())
|
||||
|
||||
fcTL.add(0x01)
|
||||
fcTL.add(0x00)
|
||||
|
||||
val crc = CRC32()
|
||||
crc.update(fcTL.toByteArray(), 0, fcTL.size)
|
||||
framesByte.addAll(fcTL)
|
||||
framesByte.addAll(to4Bytes(crc.value.toInt()).toList())
|
||||
// endregion
|
||||
|
||||
// region idat
|
||||
frames[i].idat.IDATBody.forEach {
|
||||
val fdat = ArrayList<Byte>()
|
||||
// Add the chunk body length
|
||||
framesByte.addAll(to4Bytes(it.size).toList())
|
||||
// Add IDAT
|
||||
fdat.addAll(byteArrayOf(0x49, 0x44, 0x41, 0x54).toList())
|
||||
// Add chunk body
|
||||
fdat.addAll(it.toList())
|
||||
// Generate CRC
|
||||
val crc1 = CRC32()
|
||||
crc1.update(fdat.toByteArray(), 0, fdat.size)
|
||||
framesByte.addAll(fdat)
|
||||
framesByte.addAll(to4Bytes(crc1.value.toInt()).toList())
|
||||
}
|
||||
// endregion
|
||||
res.addAll(framesByte)
|
||||
} else {
|
||||
val framesByte = ArrayList<Byte>()
|
||||
val fcTL = ArrayList<Byte>()
|
||||
// region fcTL
|
||||
framesByte.addAll(byteArrayOf(0x00, 0x00, 0x00, 0x1A).toList())
|
||||
|
||||
fcTL.addAll(byteArrayOf(0x66, 0x63, 0x54, 0x4c).toList())
|
||||
|
||||
// Frame number
|
||||
fcTL.addAll(to4Bytes(seq).toList())
|
||||
seq++
|
||||
// width and height
|
||||
fcTL.addAll(to4Bytes(frames[i].width).toList())
|
||||
fcTL.addAll(to4Bytes(frames[i].height).toList())
|
||||
|
||||
if (frames[i].width < maxWitdh) {
|
||||
val xOffset = (maxWitdh / 2) - (frames[i].width / 2)
|
||||
fcTL.addAll(to4Bytes(xOffset).toList())
|
||||
} else {
|
||||
fcTL.addAll(to4Bytes(0).toList())
|
||||
}
|
||||
if (frames[i].height < maxHeight) {
|
||||
val xOffset = (maxHeight / 2) - (frames[i].height / 2)
|
||||
fcTL.addAll(to4Bytes(xOffset).toList())
|
||||
} else {
|
||||
fcTL.addAll(to4Bytes(0).toList())
|
||||
}
|
||||
|
||||
// Set frame delay
|
||||
fcTL.addAll(to2Bytes(frames[i].delay.toInt()).toList())
|
||||
fcTL.addAll(to2Bytes(1000).toList())
|
||||
|
||||
fcTL.add(0x01)
|
||||
fcTL.add(0x00)
|
||||
|
||||
val crc = CRC32()
|
||||
crc.update(fcTL.toByteArray(), 0, fcTL.size)
|
||||
framesByte.addAll(fcTL)
|
||||
framesByte.addAll(to4Bytes(crc.value.toInt()).toList())
|
||||
// endregion
|
||||
|
||||
// region fdAT
|
||||
// Write fdAT
|
||||
frames[i].idat.IDATBody.forEach {
|
||||
val fdat = ArrayList<Byte>()
|
||||
// Add IDAT size of frame + 4 byte of the seq
|
||||
framesByte.addAll(to4Bytes(it.size + 4).toList())
|
||||
// Add fdAT
|
||||
fdat.addAll(byteArrayOf(0x66, 0x64, 0x41, 0x54).toList())
|
||||
// Add Sequence number
|
||||
// ! THIS IS NOT FRAME NUMBER
|
||||
fdat.addAll(to4Bytes(seq).toList())
|
||||
// Increase seq
|
||||
seq++
|
||||
fdat.addAll(it.toList())
|
||||
// Generate CRC
|
||||
val crc1 = CRC32()
|
||||
crc1.update(fdat.toByteArray(), 0, fdat.size)
|
||||
framesByte.addAll(fdat)
|
||||
framesByte.addAll(to4Bytes(crc1.value.toInt()).toList())
|
||||
}
|
||||
// endregion
|
||||
res.addAll(framesByte)
|
||||
}
|
||||
}
|
||||
// Add IEND body length : 0
|
||||
res.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)
|
||||
res.addAll(iend.toList())
|
||||
res.addAll(to4Bytes(crC32.value.toInt()).toList())
|
||||
return res.toByteArray()
|
||||
} else {
|
||||
throw NoFrameException()
|
||||
}
|
||||
}
|
||||
|
||||
// Animation Control chunk
|
||||
private fun generateACTL(): ArrayList<Byte> {
|
||||
val res = ArrayList<Byte>()
|
||||
val actl = ArrayList<Byte>()
|
||||
|
||||
// Add length bytes
|
||||
res.addAll(to4Bytes(8).toList())
|
||||
|
||||
// Add acTL
|
||||
actl.addAll(byteArrayOf(0x61, 0x63, 0x54, 0x4c).toList())
|
||||
|
||||
// Add number of frames
|
||||
actl.addAll(to4Bytes(frames.size).toList())
|
||||
|
||||
// Number of repeat, 0 to infinite
|
||||
actl.addAll(to4Bytes(0).toList())
|
||||
res.addAll(actl)
|
||||
|
||||
// generate crc
|
||||
val crc = CRC32()
|
||||
crc.update(actl.toByteArray(), 0, actl.size)
|
||||
res.addAll(to4Bytes(crc.value.toInt()).toList())
|
||||
return res
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generate a 4 bytes array from an Int
|
||||
* @param i The int
|
||||
*/
|
||||
fun to4Bytes(i: Int): ByteArray {
|
||||
val result = ByteArray(4)
|
||||
result[0] = (i shr 24).toByte()
|
||||
result[1] = (i shr 16).toByte()
|
||||
result[2] = (i shr 8).toByte()
|
||||
result[3] = i /*>> 0*/.toByte()
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a 2 bytes array from an Int
|
||||
* @param i The int
|
||||
*/
|
||||
fun to2Bytes(i: Int): ByteArray {
|
||||
val result = ByteArray(2)
|
||||
result[0] = (i shr 8).toByte()
|
||||
result[1] = i /*>> 0*/.toByte()
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a frame to the Animated PNG
|
||||
*
|
||||
* @param byteArray It's the byteArray of a png
|
||||
* @param delay Delay in MS between this frame and the next
|
||||
*/
|
||||
fun addFrame(byteArray: ByteArray, delay: Float = 1000f) {
|
||||
frames.add(Frame(byteArray, delay))
|
||||
}
|
||||
|
||||
// Generate Image Header chunk
|
||||
private fun generate_ihdr(): ByteArray {
|
||||
val ihdr = ArrayList<Byte>()
|
||||
|
||||
// We need a body var to know body length and generate crc
|
||||
val ihdr_body = ArrayList<Byte>()
|
||||
|
||||
// Get max height and max width of all the frames
|
||||
val maxHeight = frames.sortedByDescending { it.height }[0].height
|
||||
val maxWitdh = frames.sortedByDescending { it.width }[0].width
|
||||
|
||||
// Add chunk body length
|
||||
ihdr.addAll(to4Bytes(frames[0].ihdr.ihdrCorps.size).toList())
|
||||
// Add IHDR
|
||||
ihdr_body.addAll(byteArrayOf(0x49.toByte(), 0x48.toByte(), 0x44.toByte(), 0x52.toByte()).toList())
|
||||
|
||||
// Add the max width and height
|
||||
ihdr_body.addAll(to4Bytes(maxWitdh).toList())
|
||||
ihdr_body.addAll(to4Bytes(maxHeight).toList())
|
||||
|
||||
// Add complicated stuff like depth color ...
|
||||
// If you want correct png you need same parameters. Good solution is to create new png.
|
||||
ihdr_body.addAll(frames[0].ihdr.ihdrCorps.copyOfRange(8, 13).toList())
|
||||
|
||||
// Generate CRC
|
||||
val crC32 = CRC32()
|
||||
crC32.update(ihdr_body.toByteArray(), 0, ihdr_body.size)
|
||||
ihdr.addAll(ihdr_body)
|
||||
ihdr.addAll(to4Bytes(crC32.value.toInt()).toList())
|
||||
return ihdr.toByteArray()
|
||||
}
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
package oupson.apng
|
||||
|
||||
import android.graphics.BitmapFactory
|
||||
import oupson.apng.Utils.Companion.convertImage
|
||||
import oupson.apng.Utils.Companion.isPng
|
||||
import oupson.apng.Utils.Companion.toByteArray
|
||||
import oupson.apng.chunks.IDAT
|
||||
|
|
|
@ -20,13 +20,13 @@ import java.util.Map;
|
|||
import java.util.Random;
|
||||
|
||||
public class PnnQuantizer {
|
||||
protected final short SHORT_MAX = Short.MAX_VALUE;
|
||||
protected final char BYTE_MAX = -Byte.MIN_VALUE + Byte.MAX_VALUE;
|
||||
protected boolean hasTransparency = false, hasSemiTransparency = false;
|
||||
private final short SHORT_MAX = Short.MAX_VALUE;
|
||||
private final char BYTE_MAX = -Byte.MIN_VALUE + Byte.MAX_VALUE;
|
||||
private boolean hasTransparency = false, hasSemiTransparency = false;
|
||||
protected int width, height;
|
||||
protected int pixels[] = null;
|
||||
protected Integer m_transparentColor;
|
||||
protected Map<Integer, short[]> closestMap = new HashMap<>();
|
||||
private Integer m_transparentColor;
|
||||
private Map<Integer, short[]> closestMap = new HashMap();
|
||||
|
||||
public PnnQuantizer(String fname) throws IOException {
|
||||
fromBitmap(fname);
|
||||
|
@ -54,7 +54,7 @@ public class PnnQuantizer {
|
|||
int nn, fw, bk, tm, mtm;
|
||||
}
|
||||
|
||||
protected int getColorIndex(final int c)
|
||||
private int getColorIndex(final int c)
|
||||
{
|
||||
if(hasSemiTransparency)
|
||||
return (Color.alpha(c) & 0xF0) << 8 | (Color.red(c) & 0xF0) << 4 | (Color.green(c) & 0xF0) | (Color.blue(c) >> 4);
|
||||
|
@ -63,7 +63,7 @@ public class PnnQuantizer {
|
|||
return (Color.red(c) & 0xF8) << 8 | (Color.green(c) & 0xFC) << 3 | (Color.blue(c) >> 3);
|
||||
}
|
||||
|
||||
protected double sqr(double value)
|
||||
private double sqr(double value)
|
||||
{
|
||||
return value * value;
|
||||
}
|
||||
|
@ -412,7 +412,7 @@ public class PnnQuantizer {
|
|||
return true;
|
||||
}
|
||||
|
||||
protected Bitmap quantize_image(final int[] pixels, int[] qPixels)
|
||||
private Bitmap quantize_image(final int[] pixels, int[] qPixels)
|
||||
{
|
||||
int pixelIndex = 0;
|
||||
boolean odd_scanline = false;
|
||||
|
|
|
@ -8,31 +8,33 @@ import java.io.IOException
|
|||
import java.net.URL
|
||||
|
||||
class Loader {
|
||||
fun load( context: Context, url : URL) : ByteArray {
|
||||
val currenDir = context.filesDir
|
||||
val fileTXT = File(currenDir, "apngLoader.txt")
|
||||
var filePNG = File(currenDir, "apngLoader.png")
|
||||
if (fileTXT.exists() && url.toString() == fileTXT.readText()) {
|
||||
return filePNG.readBytes()
|
||||
} else {
|
||||
try {
|
||||
val connection = url.openConnection()
|
||||
connection.connect()
|
||||
val input = BufferedInputStream(connection.getInputStream())
|
||||
val output = ByteArrayOutputStream()
|
||||
val data = ByteArray(1024)
|
||||
var count = 0
|
||||
while ({ count = input.read(data); count }() != -1) {
|
||||
output.write(data, 0, count)
|
||||
companion object {
|
||||
fun load(context: Context, url: URL): ByteArray {
|
||||
val currenDir = context.filesDir
|
||||
val fileTXT = File(currenDir, "apngLoader.txt")
|
||||
var filePNG = File(currenDir, "apngLoader.png")
|
||||
if (fileTXT.exists() && url.toString() == fileTXT.readText()) {
|
||||
return filePNG.readBytes()
|
||||
} else {
|
||||
try {
|
||||
val connection = url.openConnection()
|
||||
connection.connect()
|
||||
val input = BufferedInputStream(connection.getInputStream())
|
||||
val output = ByteArrayOutputStream()
|
||||
val data = ByteArray(1024)
|
||||
var count = 0
|
||||
while ({ count = input.read(data); count }() != -1) {
|
||||
output.write(data, 0, count)
|
||||
}
|
||||
output.flush()
|
||||
output.close()
|
||||
input.close()
|
||||
fileTXT.writeText(url.toString())
|
||||
filePNG.writeBytes(output.toByteArray())
|
||||
return output.toByteArray()
|
||||
} catch (e: IOException) {
|
||||
throw e
|
||||
}
|
||||
output.flush()
|
||||
output.close()
|
||||
input.close()
|
||||
fileTXT.writeText(url.toString())
|
||||
filePNG.writeBytes(output.toByteArray())
|
||||
return output.toByteArray()
|
||||
} catch (e: IOException) {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package oupson.apngcreator
|
||||
|
||||
import android.graphics.BitmapFactory
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
|
@ -8,14 +7,11 @@ import android.util.Log
|
|||
import android.widget.SeekBar
|
||||
import com.squareup.picasso.Picasso
|
||||
import kotlinx.android.synthetic.main.activity_main.*
|
||||
import oupson.apng.ApngAnimator
|
||||
import android.widget.Toast
|
||||
import org.jetbrains.anko.doAsync
|
||||
import org.jetbrains.anko.toast
|
||||
import oupson.apng.APNGDisassembler
|
||||
import oupson.apng.Apng
|
||||
import oupson.apng.ApngAnimator
|
||||
import oupson.apng.Loader
|
||||
import oupson.apngcreator.R.id.imageView
|
||||
import java.io.File
|
||||
import java.net.URL
|
||||
|
||||
|
@ -40,9 +36,9 @@ class MainActivity : AppCompatActivity() {
|
|||
|
||||
|
||||
doAsync {
|
||||
Loader().load(applicationContext, URL(imageUrl)).apply {
|
||||
val a = APNGDisassembler(this).apng
|
||||
a.reduceSize(100, false, 75)
|
||||
Loader.load(applicationContext, URL(imageUrl)).apply {
|
||||
val a = APNGDisassembler.disassemble(this)
|
||||
a.reduceSize(100, false, 60)
|
||||
File(File(Environment.getExternalStorageDirectory(), "Documents"), "apng.png").writeBytes(a.toByteArray())
|
||||
runOnUiThread {
|
||||
toast("Converted ! ")
|
||||
|
|
Loading…
Reference in New Issue