Add chunk interface

Prepare for release
This commit is contained in:
oupson 2018-12-08 21:18:32 +01:00
parent cefb4ca9f4
commit 08d5d307c7
14 changed files with 89 additions and 202 deletions

View File

@ -5,7 +5,7 @@ android {
compileSdkVersion 27
defaultConfig {
minSdkVersion 21
targetSdkVersion 26
targetSdkVersion 27
versionCode 1
versionName "1.0.8"

View File

@ -6,6 +6,7 @@ import oupson.apng.chunks.fcTL
import oupson.apng.exceptions.NotApngException
import oupson.apng.utils.Utils
import oupson.apng.utils.Utils.Companion.isApng
import oupson.apng.utils.Utils.Companion.parseLength
import oupson.apng.utils.Utils.Companion.pngSignature
import oupson.apng.utils.Utils.Companion.to4Bytes
import java.util.*
@ -24,12 +25,17 @@ class APNGDisassembler {
private var maxHeight = 0
private var blend_op: Utils.Companion.blend_op = Utils.Companion.blend_op.APNG_BLEND_OP_SOURCE
private var dispose_op: Utils.Companion.dispose_op = Utils.Companion.dispose_op.APNG_DISPOSE_OP_NONE
var apng: Apng = Apng()
private val ihdr = IHDR()
var apng: Apng = Apng()
/**
* Disassemble an Apng file
* @param byteArray The Byte Array of the file
* @return The apng decoded
*/
fun disassemble(byteArray: ByteArray) : Apng {
if (isApng(byteArray)) {
apng = Apng()
ihdr.parseIHDR(byteArray)
ihdr.parse(byteArray)
maxWidth = ihdr.pngWidth
maxHeight = ihdr.pngHeight
var cursor = 8
@ -44,20 +50,12 @@ class APNGDisassembler {
}
}
private fun parseLength(byteArray: ByteArray) : Int {
var lengthString = ""
byteArray.forEach {
lengthString += String.format("%02x", it)
}
return lengthString.toLong(16).toInt()
}
private fun generateIhdr(ihdrOfApng: IHDR, width : Int, height : Int) : ByteArray {
val ihdr = ArrayList<Byte>()
// We need a body var to know body length and generate crc
val ihdrBody = ArrayList<Byte>()
// Add chunk body length
ihdr.addAll(to4Bytes(ihdrOfApng.ihdrCorps.size).toList())
ihdr.addAll(to4Bytes(ihdrOfApng.body.size).toList())
// Add IHDR
ihdrBody.addAll(byteArrayOf(0x49.toByte(), 0x48.toByte(), 0x44.toByte(), 0x52.toByte()).toList())
// Add the max width and height
@ -65,7 +63,7 @@ class APNGDisassembler {
ihdrBody.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.
ihdrBody.addAll(ihdrOfApng.ihdrCorps.copyOfRange(8, 13).toList())
ihdrBody.addAll(ihdrOfApng.body.copyOfRange(8, 13).toList())
// Generate CRC
val crC32 = CRC32()
crC32.update(ihdrBody.toByteArray(), 0, ihdrBody.size)
@ -91,9 +89,9 @@ class APNGDisassembler {
it.addAll(to4Bytes(crC32.value.toInt()).toList())
apng.cover = BitmapFactory.decodeByteArray(it.toByteArray(), 0, it.size)
}
png = ArrayList()
val fcTL = fcTL(byteArray)
val fcTL = fcTL()
fcTL.parse(byteArray)
delay = fcTL.delay
yOffset = fcTL.y_offset
xOffset = fcTL.x_offset
@ -103,11 +101,9 @@ class APNGDisassembler {
val height = fcTL.pngHeight
png!!.addAll(pngSignature.toList())
png!!.addAll(generateIhdr(ihdr, width, height).toList())
plte?.let {
png!!.addAll(it.toList())
}
tnrs?.let {
png!!.addAll(it.toList())
}
@ -122,15 +118,12 @@ class APNGDisassembler {
png!!.addAll(iend.toList())
png!!.addAll(to4Bytes(crC32.value.toInt()).toList())
apng.frames.add(Frame(png!!.toByteArray(), delay, xOffset, yOffset, maxWidth, maxHeight, blend_op, dispose_op))
png = ArrayList()
val fcTL = fcTL(byteArray)
val fcTL = fcTL()
fcTL.parse(byteArray)
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
@ -140,7 +133,6 @@ class APNGDisassembler {
plte?.let {
png!!.addAll(it.toList())
}
tnrs?.let {
png!!.addAll(it.toList())
}

View File

@ -14,12 +14,10 @@ import oupson.apng.utils.Utils.Companion.to4Bytes
import oupson.apng.utils.Utils.Companion.toByteArray
import java.util.zip.CRC32
/**
* Create an APNG file
*/
class Apng {
var maxWidth : Int? = null
var maxHeight : Int? = null
@ -226,7 +224,7 @@ class Apng {
// Add cover image : Not part of animation
// region IDAT
val idat = IDAT()
idat.parseIDAT(toByteArray(cover!!))
idat.parse(toByteArray(cover!!))
idat.IDATBody.forEach {
val idatByteArray = ArrayList<Byte>()
framesByte.addAll(to4Bytes(it.size).toList())
@ -427,7 +425,7 @@ class Apng {
}
// Add chunk body length
ihdr.addAll(to4Bytes(frames[0].ihdr.ihdrCorps.size).toList())
ihdr.addAll(to4Bytes(frames[0].ihdr.body.size).toList())
// Add IHDR
ihdr_body.addAll(byteArrayOf(0x49.toByte(), 0x48.toByte(), 0x44.toByte(), 0x52.toByte()).toList())
@ -437,7 +435,7 @@ class Apng {
// 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())
ihdr_body.addAll(frames[0].ihdr.body.copyOfRange(8, 13).toList())
// Generate CRC
val crC32 = CRC32()

View File

@ -39,12 +39,14 @@ class ApngAnimator(private val context: Context) {
private var anim: CustomAnimationDrawable? = null
private var activeAnimation: CustomAnimationDrawable? = null
private var doOnLoaded : (ApngAnimator) -> Unit = {}
@SuppressWarnings("WeakerAccess")
private var AnimationLoopListener : () -> Unit = {}
private var duration : ArrayList<Float>? = null
private var scaleType : ImageView.ScaleType? = null
var isApng = false
@SuppressWarnings("WeakerAccess")
var loadNotApng = true
private val sharedPreferences : SharedPreferences = context.getSharedPreferences("apngAnimator", Context.MODE_PRIVATE)
init {
@ -54,6 +56,7 @@ class ApngAnimator(private val context: Context) {
/**
* Specify if the library could load non apng file
*/
@SuppressWarnings("WeakerAccess")
fun loadNotApng(boolean: Boolean) {
val editor = sharedPreferences.edit()
editor.putBoolean("loadNotApng", boolean)
@ -75,7 +78,7 @@ class ApngAnimator(private val context: Context) {
* @param speed The speed
* @throws NotApngException
*/
@JvmOverloads
@SuppressWarnings("WeakerAccess")
fun load(file: File, speed: Float? = null, apngAnimatorOptions: ApngAnimatorOptions? = null) {
doAsync {
val bytes = file.readBytes()
@ -140,6 +143,7 @@ class ApngAnimator(private val context: Context) {
* @param speed The speed
* @throws NotApngException
*/
@SuppressWarnings("WeakerAccess")
fun loadUrl(url: URL, speed: Float? = null, apngAnimatorOptions: ApngAnimatorOptions? = null) {
doAsync(exceptionHandler = { e -> e.printStackTrace() }) {
this@ApngAnimator.speed = speed
@ -171,13 +175,13 @@ class ApngAnimator(private val context: Context) {
}
}
/**
* Load an APNG file and starts playing the animation.
* @param byteArray ByteArray of the file
* @param speed The speed
* @throws NotApngException
*/
@SuppressWarnings("WeakerAccess")
fun load(byteArray: ByteArray, speed: Float? = null, apngAnimatorOptions: ApngAnimatorOptions? = null) {
doAsync {
this@ApngAnimator.speed = speed

View File

@ -45,14 +45,14 @@ class Frame {
this.byteArray = bytes
// Get width and height for image
ihdr = IHDR()
ihdr.parseIHDR(bytes)
ihdr.parse(bytes)
width = ihdr.pngWidth
height = ihdr.pngHeight
// Get IDAT Bytes
idat = IDAT()
idat.parseIDAT(bytes)
idat.parse(bytes)
delay = 1000f
@ -69,14 +69,14 @@ class Frame {
this.byteArray = bytes
// Get width and height for image
ihdr = IHDR()
ihdr.parseIHDR(bytes)
ihdr.parse(bytes)
width = ihdr.pngWidth
height = ihdr.pngHeight
// Get IDAT Bytes
idat = IDAT()
idat.parseIDAT(bytes)
idat.parse(bytes)
this.delay = delay
blend_op = Utils.Companion.blend_op.APNG_BLEND_OP_SOURCE
@ -93,14 +93,14 @@ class Frame {
this.byteArray = bytes
// Get width and height for image
ihdr = IHDR()
ihdr.parseIHDR(bytes)
ihdr.parse(bytes)
width = ihdr.pngWidth
height = ihdr.pngHeight
// Get IDAT Bytes
idat = IDAT()
idat.parseIDAT(bytes)
idat.parse(bytes)
this.delay = delay
@ -121,14 +121,14 @@ class Frame {
this.byteArray = bytes
// Get width and height for image
ihdr = IHDR()
ihdr.parseIHDR(bytes)
ihdr.parse(bytes)
width = ihdr.pngWidth
height = ihdr.pngHeight
// Get IDAT Bytes
idat = IDAT()
idat.parseIDAT(bytes)
idat.parse(bytes)
this.delay = delay
@ -149,14 +149,14 @@ class Frame {
this.byteArray = byteArray
// Get width and height for image
ihdr = IHDR()
ihdr.parseIHDR(byteArray)
ihdr.parse(byteArray)
width = ihdr.pngWidth
height = ihdr.pngHeight
// Get IDAT Bytes
idat = IDAT()
idat.parseIDAT(byteArray)
idat.parse(byteArray)
this.delay = delay

View File

@ -8,6 +8,12 @@ import java.net.URL
class Loader {
companion object {
/**
* Download file from given url
* @param context Context of app
* @param url Url of the file to download
* @return [ByteArray] of the file
*/
@Throws(IOException::class)
fun load(context: Context, url: URL): ByteArray {
val currentDir = context.filesDir

View File

@ -0,0 +1,6 @@
package oupson.apng.chunks
interface Chunk {
var body : ByteArray
fun parse(byteArray: ByteArray)
}

View File

@ -1,30 +1,20 @@
package oupson.apng.chunks
class IDAT {
private var bodySize = -1
var IDATBody: ArrayList<ByteArray> = ArrayList()
import oupson.apng.utils.Utils.Companion.parseLength
fun parseIDAT(byteArray: ByteArray) {
class IDAT : Chunk {
var IDATBody: ArrayList<ByteArray> = ArrayList()
override var body = byteArrayOf()
override fun parse(byteArray: ByteArray) {
for (i in 0 until byteArray.size) {
// Find IDAT chunk
if (byteArray[i] == 0x49.toByte() && byteArray[i + 1] == 0x44.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)
}
bodySize = lengthString.toLong(16).toInt()
val bodySize = parseLength(byteArray.copyOfRange(i - 4, i))
// Get image bytes
val _IDATbody = ArrayList<Byte>()
for (j in i +4 until i + 4 + bodySize) {
_IDATbody.add(byteArray[j])
}
IDATBody.add(_IDATbody.toByteArray())
IDATBody.add(byteArray.copyOfRange(i + 4, i + 4 + bodySize))
}
}
}
}

View File

@ -1,44 +1,22 @@
package oupson.apng.chunks
class IHDR {
private var corpsSize = -1
var ihdrCorps = byteArrayOf()
import oupson.apng.utils.Utils.Companion.parseLength
class IHDR : Chunk {
override var body = byteArrayOf()
var pngWidth = -1
var pngHeight = -1
fun parseIHDR(byteArray: ByteArray) {
override fun parse(byteArray: ByteArray) {
for (i in 0 until byteArray.size) {
// Find IHDR chunk
if (byteArray[i] == 0x49.toByte() && byteArray[i + 1] == 0x48.toByte() && byteArray[ i + 2 ] == 0x44.toByte() && byteArray[ i + 3 ] == 0x52.toByte()) {
// Get length of the corps of the chunk
var lengthString = ""
byteArray.copyOfRange(i - 4, i).forEach {
lengthString += String.format("%02x", it)
}
corpsSize = lengthString.toLong(16).toInt()
// Get length of the body of the chunk
val bodySize = parseLength(byteArray.copyOfRange(i - 4, i))
// Get the width of the png
var pngwidth = ""
byteArray.copyOfRange(i + 4, i + 8).forEach {
pngwidth += String.format("%02x", it)
}
pngWidth = pngwidth.toLong(16).toInt()
pngWidth = parseLength(byteArray.copyOfRange(i +4, i + 8))
// Get the height of the png
var pngheight = ""
byteArray.copyOfRange(i + 8, i + 12).forEach {
pngheight += String.format("%02x", it)
}
pngHeight = pngheight.toLong(16).toInt()
val _ihdrCorps = ArrayList<Byte>()
byteArray.copyOfRange(i + 4, i + corpsSize + 4).forEach {
_ihdrCorps.add(it)
}
ihdrCorps = _ihdrCorps.toByteArray()
pngHeight = parseLength(byteArray.copyOfRange(i +8, i +12))
body = byteArray.copyOfRange(i + 4, i + bodySize + 4)
}
}
}

View File

@ -3,11 +3,10 @@ package oupson.apng.chunks
import oupson.apng.utils.Utils
import oupson.apng.utils.Utils.Companion.getBlend_op
import oupson.apng.utils.Utils.Companion.getDispose_op
import oupson.apng.utils.Utils.Companion.parseLength
class fcTL(byteArray: ByteArray) {
private var corpsSize = -1
lateinit var fcTLBody : ByteArray
class fcTL : Chunk {
override var body : ByteArray = byteArrayOf()
// Height and width of frame
var pngWidth = -1
@ -22,83 +21,36 @@ class fcTL(byteArray: ByteArray) {
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 {
override fun parse(byteArray: ByteArray) {
for (i in 0 until byteArray.size) {
// Find fcTL chunk
if (byteArray[i] == 0x66.toByte() && byteArray[i + 1] == 0x63.toByte() && byteArray[ i + 2 ] == 0x54.toByte() && byteArray[ i + 3 ] == 0x4C.toByte()) {
// Get length of the body of the chunk
var lengthString = ""
byteArray.copyOfRange(i - 4, i).forEach {
lengthString += String.format("%02x", it)
}
corpsSize = lengthString.toLong(16).toInt()
var bodySize = parseLength(byteArray.copyOfRange(i - 4, 1))
// Get the width of the png
var pngwidth = ""
byteArray.copyOfRange(i + 8, i + 12).forEach {
pngwidth += String.format("%02x", it)
}
pngWidth = pngwidth.toLong(16).toInt()
pngWidth = parseLength(byteArray.copyOfRange(i + 8, i + 12))
// Get the height of the png
var pngheight = ""
byteArray.copyOfRange(i + 12, i + 16).forEach {
pngheight += String.format("%02x", it)
}
pngHeight = pngheight.toLong(16).toInt()
/**
pngHeight = parseLength(byteArray.copyOfRange(i + 12, i + 16))
/*
* The `delay_num` and `delay_den` parameters together specify a fraction indicating the time to display the current frame, in seconds.
* If the the value of the numerator is 0 the decoder should render the next frame as quickly as possible, though viewers may impose a reasonable lower bound.
*/
// Get delay numerator
var delayNum = ""
byteArray.copyOfRange(i + 24, i+ 26).forEach {
delayNum += String.format("%02x", it)
}
val delay_num = delayNum.toLong(16).toFloat()
val delay_num = parseLength(byteArray.copyOfRange(i + 24, i + 26)).toFloat()
// Get delay denominator
var delayDen = ""
byteArray.copyOfRange(i + 26, i+ 28).forEach {
delayDen += String.format("%02x", it)
}
var delay_den = delayDen.toLong(16).toFloat()
var delay_den = parseLength(byteArray.copyOfRange(i + 26, i + 28)).toFloat()
/**
* If the denominator is 0, it is to be treated as if it were 100 (that is, `delay_num` then specifies 1/100ths of a second).
*/
// If the denominator is 0, it is to be treated as if it were 100 (that is, `delay_num` then specifies 1/100ths of a second).
if (delay_den == 0f) {
delay_den = 100f
}
delay = (delay_num / delay_den * 1000)
// Get x and y offsets
var xOffset = ""
byteArray.copyOfRange(i + 16, i+ 20).forEach {
xOffset += String.format("%02x", it)
}
x_offset = xOffset.toLong(16).toInt()
var yOffset = ""
byteArray.copyOfRange(i + 20, i+ 24).forEach {
yOffset += String.format("%02x", it)
}
y_offset = yOffset.toLong(16).toInt()
val _fcTLBody = ArrayList<Byte>()
byteArray.copyOfRange(i + 4, i + corpsSize + 3 ).forEach {
_fcTLBody.add(it)
}
fcTLBody= _fcTLBody.toByteArray()
x_offset = parseLength(byteArray.copyOfRange(i + 16, i+ 20))
y_offset = parseLength( byteArray.copyOfRange(i + 20, i+ 24))
body = byteArray.copyOfRange(i +4, i + bodySize + 4)
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

@ -1,45 +0,0 @@
package oupson.apng.chunks
class fdAT {
private var bodySize = -1
var fdATBody: ArrayList<ByteArray> = ArrayList()
fun parsefdAT(byteArray: ByteArray, position: Int) {
for (i in position until byteArray.size) {
// Find fdAT chunk
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)
}
bodySize = lengthString.toLong(16).toInt()
// Get image bytes
val _fdATbody = ArrayList<Byte>()
for (j in i +4 until i + 4 + bodySize) {
_fdATbody.add(byteArray[j])
}
fdATBody.add(_fdATbody.toByteArray())
}
// Find idat chunk
else if (byteArray[i] == 0x49.toByte() && byteArray[i + 1] == 0x44.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)
}
bodySize = lengthString.toLong(16).toInt()
// Get image bytes
val _fdATbody = ArrayList<Byte>()
for (j in i +4 until i + 4 + bodySize) {
_fdATbody.add(byteArray[j])
}
fdATBody.add(_fdATbody.toByteArray())
}
}
}
}

View File

@ -157,6 +157,14 @@ class Utils {
return result
}
fun parseLength(byteArray: ByteArray) : Int {
var lengthString = ""
byteArray.forEach {
lengthString += String.format("%02x", it)
}
return lengthString.toLong(16).toInt()
}
val fcTL = Arrays.toString(byteArrayOf(0x66, 0x63, 0x54, 0x4c))
val IEND = Arrays.toString(byteArrayOf(0x49, 0x45, 0x4e, 0x44))
val IDAT = Arrays.toString(byteArrayOf(0x49, 0x44, 0x41, 0x54))

View File

@ -7,11 +7,9 @@ import android.support.v4.app.ActivityCompat
import android.support.v4.content.ContextCompat
import android.support.v7.app.AppCompatActivity
import android.view.View
import android.widget.ImageView
import kotlinx.android.synthetic.main.activity_main2.*
import org.jetbrains.anko.sdk27.coroutines.onClick
import oupson.apng.ApngAnimator
import oupson.apng.utils.ApngAnimatorOptions
class Main2Activity : AppCompatActivity() {
@ -42,7 +40,7 @@ class Main2Activity : AppCompatActivity() {
fun load() {
val animator = ApngAnimator(applicationContext).loadInto(imageView3)
val uri = intent.data
animator.load(uri, null, ApngAnimatorOptions(ImageView.ScaleType.CENTER_CROP))
animator.load(uri, null)
imageView3.onClick {
try {
if (animator.isPlaying) {