Add more user friendly loading functions

This commit is contained in:
oupson 2019-05-17 19:23:28 +02:00
parent d2d4978180
commit 887d679c4b
7 changed files with 82 additions and 143 deletions

View File

@ -76,11 +76,10 @@ class APNGDisassembler {
private fun parseChunk(byteArray: ByteArray) {
val i = 4
val chunkCRC = parseLength(byteArray.copyOfRange(byteArray.size - 4, byteArray.size))
val crc = CRC32();
val crc = CRC32()
crc.update(byteArray.copyOfRange(i, byteArray.size - 4))
if (chunkCRC == crc.value.toInt()) {
val name = Arrays.toString(byteArray.copyOfRange(i, i + 4))
when (name) {
when (Arrays.toString(byteArray.copyOfRange(i, i + 4))) {
Utils.fcTL -> {
if (png == null) {
cover?.let {
@ -114,22 +113,22 @@ class APNGDisassembler {
png?.addAll(pngSignature.toList())
png?.addAll(generateIhdr(ihdr, width, height).toList())
plte?.let {
png!!.addAll(it.toList())
png?.addAll(it.toList())
}
tnrs?.let {
png!!.addAll(it.toList())
png?.addAll(it.toList())
}
} else {
// Add IEND body length : 0
png!!.addAll(to4Bytes(0).toList())
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())
apng.frames.add(Frame(png!!.toByteArray(), delay, xOffset, yOffset, maxWidth, maxHeight, blendOp, disposeOp))
png?.addAll(iend.toList())
png?.addAll(to4Bytes(crC32.value.toInt()).toList())
apng.frames.add(Frame(png!!.toByteArray(), delay, xOffset, yOffset, blendOp, disposeOp, maxWidth, maxHeight))
png = ArrayList()
val fcTL = fcTL()
fcTL.parse(byteArray)
@ -140,71 +139,71 @@ class APNGDisassembler {
disposeOp = fcTL.disposeOp
val width = fcTL.pngWidth
val height = fcTL.pngHeight
png!!.addAll(pngSignature.toList())
png!!.addAll(generateIhdr(ihdr, width, height).toList())
png?.addAll(pngSignature.toList())
png?.addAll(generateIhdr(ihdr, width, height).toList())
plte?.let {
png!!.addAll(it.toList())
png?.addAll(it.toList())
}
tnrs?.let {
png!!.addAll(it.toList())
png?.addAll(it.toList())
}
}
}
Utils.IEND -> {
png!!.addAll(to4Bytes(0).toList())
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())
apng.frames.add(Frame(png!!.toByteArray(), delay, xOffset, yOffset, maxWidth, maxHeight, blendOp, disposeOp))
png?.addAll(iend.toList())
png?.addAll(to4Bytes(crC32.value.toInt()).toList())
apng.frames.add(Frame(png!!.toByteArray(), delay, xOffset, yOffset, blendOp, disposeOp, maxWidth, maxHeight))
}
Utils.IDAT -> {
if (png == null) {
if (cover == null) {
cover = ArrayList()
cover!!.addAll(pngSignature.toList())
cover!!.addAll(generateIhdr(ihdr, maxWidth, maxHeight).toList())
cover?.addAll(pngSignature.toList())
cover?.addAll(generateIhdr(ihdr, maxWidth, maxHeight).toList())
}
// Find the chunk length
val bodySize = parseLength(byteArray.copyOfRange(i - 4, i))
cover!!.addAll(byteArray.copyOfRange(i - 4, i).toList())
cover?.addAll(byteArray.copyOfRange(i - 4, i).toList())
val body = ArrayList<Byte>()
body.addAll(byteArrayOf(0x49, 0x44, 0x41, 0x54).toList())
// Get image bytes
body.addAll(byteArray.copyOfRange(i + 4, i + 4 + bodySize).toList())
val crC32 = CRC32()
crC32.update(body.toByteArray(), 0, body.size)
cover!!.addAll(body)
cover!!.addAll(to4Bytes(crC32.value.toInt()).toList())
cover?.addAll(body)
cover?.addAll(to4Bytes(crC32.value.toInt()).toList())
} else {
// Find the chunk length
val bodySize = parseLength(byteArray.copyOfRange(i - 4, i))
png!!.addAll(byteArray.copyOfRange(i - 4, i).toList())
png?.addAll(byteArray.copyOfRange(i - 4, i).toList())
val body = ArrayList<Byte>()
body.addAll(byteArrayOf(0x49, 0x44, 0x41, 0x54).toList())
// Get image bytes
body.addAll(byteArray.copyOfRange(i + 4, i + 4 + bodySize).toList())
val crC32 = CRC32()
crC32.update(body.toByteArray(), 0, body.size)
png!!.addAll(body)
png!!.addAll(to4Bytes(crC32.value.toInt()).toList())
png?.addAll(body)
png?.addAll(to4Bytes(crC32.value.toInt()).toList())
}
}
Utils.fdAT -> {
// Find the chunk length
val bodySize = parseLength(byteArray.copyOfRange(i - 4, i))
png!!.addAll(to4Bytes(bodySize - 4).toList())
png?.addAll(to4Bytes(bodySize - 4).toList())
val body = ArrayList<Byte>()
body.addAll(byteArrayOf(0x49, 0x44, 0x41, 0x54).toList())
// Get image bytes
body.addAll(byteArray.copyOfRange(i + 8, i + 4 + bodySize).toList())
val crC32 = CRC32()
crC32.update(body.toByteArray(), 0, body.size)
png!!.addAll(body)
png!!.addAll(to4Bytes(crC32.value.toInt()).toList())
png?.addAll(body)
png?.addAll(to4Bytes(crC32.value.toInt()).toList())
}
Utils.plte -> {
plte = byteArray

View File

@ -102,8 +102,8 @@ class Apng {
fcTL.addAll(to4Bytes(frames[0].width).toList())
fcTL.addAll(to4Bytes(frames[0].height).toList())
fcTL.addAll(to4Bytes(frames[0].x_offsets).toList())
fcTL.addAll(to4Bytes(frames[0].y_offsets).toList())
fcTL.addAll(to4Bytes(frames[0].xOffsets).toList())
fcTL.addAll(to4Bytes(frames[0].yOffsets).toList())
// Set frame delay
fcTL.addAll(to2Bytes(frames[0].delay.toInt()).toList())
@ -171,8 +171,8 @@ class Apng {
fcTL.addAll(to4Bytes(frames[0].height).toList())
fcTL.addAll(to4Bytes(frames[0].x_offsets).toList())
fcTL.addAll(to4Bytes(frames[0].y_offsets).toList())
fcTL.addAll(to4Bytes(frames[0].xOffsets).toList())
fcTL.addAll(to4Bytes(frames[0].yOffsets).toList())
// Set frame delay
fcTL.addAll(to2Bytes(frames[0].delay.toInt()).toList())
@ -226,8 +226,8 @@ class Apng {
fcTL.addAll(to4Bytes(frames[i].width).toList())
fcTL.addAll(to4Bytes(frames[i].height).toList())
fcTL.addAll(to4Bytes(frames[i].x_offsets).toList())
fcTL.addAll(to4Bytes(frames[i].y_offsets).toList())
fcTL.addAll(to4Bytes(frames[i].xOffsets).toList())
fcTL.addAll(to4Bytes(frames[i].yOffsets).toList())
// Set frame delay
fcTL.addAll(to2Bytes(frames[i].delay.toInt()).toList())
@ -391,9 +391,9 @@ class Apng {
val pnn = PnnQuantizer(btm)
val btmOptimised = pnn.convert(maxColor, false)
if (sizePercent != null) {
apng.addFrames(btmOptimised, 0, it.delay, (it.x_offsets.toFloat() * sizePercent.toFloat() / 100f).toInt(), (it.y_offsets.toFloat() * sizePercent.toFloat() / 100f).toInt(), it.disposeOp, it.blendOp)
apng.addFrames(btmOptimised, 0, it.delay, (it.xOffsets.toFloat() * sizePercent.toFloat() / 100f).toInt(), (it.yOffsets.toFloat() * sizePercent.toFloat() / 100f).toInt(), it.disposeOp, it.blendOp)
} else {
apng.addFrames(btmOptimised, 0, it.delay, it.x_offsets, it.y_offsets, it.disposeOp, it.blendOp)
apng.addFrames(btmOptimised, 0, it.delay, it.xOffsets, it.yOffsets, it.disposeOp, it.blendOp)
}
}
frames = apng.frames
@ -412,8 +412,8 @@ class Apng {
val diffCalculator = BitmapDiffCalculator(drawedFrame[i - 1], drawedFrame[i])
File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS), "frame$i.png").writeBytes(PngEncoder.encode(diffCalculator.res, true))
frames[i].byteArray = PngEncoder.encode(diffCalculator.res, true)
frames[i].x_offsets = diffCalculator.xOffset
frames[i].y_offsets = diffCalculator.yOffset
frames[i].xOffsets = diffCalculator.xOffset
frames[i].yOffsets = diffCalculator.yOffset
frames[i].blendOp = Utils.Companion.BlendOp.APNG_BLEND_OP_OVER
}
}

View File

@ -15,6 +15,26 @@ import oupson.apng.utils.Utils.Companion.isApng
import java.io.File
import java.net.URL
@Suppress("unused")
fun ImageView.loadApng(file: File, speed: Float? = null, apngAnimatorOptions: ApngAnimatorOptions? = null) = ApngAnimator(this.context).loadInto(this).apply {
load(file, speed, apngAnimatorOptions)
}
fun ImageView.loadApng(uri : Uri, speed: Float? = null, apngAnimatorOptions: ApngAnimatorOptions? = null) = ApngAnimator(this.context).loadInto(this).apply {
load(uri, speed, apngAnimatorOptions)
}
@Suppress("unused")
fun ImageView.loadApng(url: URL, speed: Float? = null, apngAnimatorOptions: ApngAnimatorOptions? = null) = ApngAnimator(this.context).loadInto(this).apply {
loadUrl(url, speed, apngAnimatorOptions)
}
@Suppress("unused")
fun ImageView.loadApng(byteArray: ByteArray, speed: Float? = null, apngAnimatorOptions: ApngAnimatorOptions? = null) = ApngAnimator(this.context).loadInto(this).apply {
load(byteArray, speed, apngAnimatorOptions)
}
fun ImageView.loadApng(string: String, speed : Float? = null, apngAnimatorOptions: ApngAnimatorOptions? = null) = ApngAnimator(this.context).loadInto(this).apply {
load(string, speed, apngAnimatorOptions)
}
/**
* Class to play APNG
*/
@ -110,8 +130,6 @@ class ApngAnimator(private val context: Context?) {
return this
}
/**
* Load an APNG file and starts playing the animation.
* @param uri The uri to load
@ -291,10 +309,10 @@ class ApngAnimator(private val context: Context?) {
// Clear current frame rect
// If `BlendOp` 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.blendOp == Utils.Companion.BlendOp.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 }())
canvas.drawRect(it.xOffsets.toFloat(), it.yOffsets.toFloat(), it.xOffsets + current.width.toFloat(), it.yOffsets + 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)
canvas.drawBitmap(current, it.xOffsets.toFloat(), it.yOffsets.toFloat(), null)
generatedFrame.add(btm)
// Don't add current frame to bitmap buffer
when {
@ -307,7 +325,7 @@ class ApngAnimator(private val context: Context?) {
val res = Bitmap.createBitmap(extractedFrame[0].maxWidth!!, extractedFrame[0].maxHeight!!, Bitmap.Config.ARGB_8888)
val can = Canvas(res)
can.drawBitmap(btm, 0f, 0f, null)
can.drawRect(it.x_offsets.toFloat(), it.y_offsets.toFloat(), it.x_offsets + it.width.toFloat(), it.y_offsets + it.height.toFloat(), { val paint = Paint(); paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR); paint }())
can.drawRect(it.xOffsets.toFloat(), it.yOffsets.toFloat(), it.xOffsets + it.width.toFloat(), it.yOffsets + it.height.toFloat(), { val paint = Paint(); paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR); paint }())
bitmapBuffer = res
}
else -> bitmapBuffer = btm

View File

@ -6,7 +6,7 @@ import android.graphics.ColorFilter
import android.graphics.PixelFormat
import android.graphics.drawable.Drawable
internal class BitmapDrawable(val bitmap: Bitmap) : Drawable() {
internal class BitmapDrawable(private val bitmap: Bitmap) : Drawable() {
override fun draw(canvas: Canvas) {
canvas.drawBitmap(bitmap, 0.0f, 0.0f, null)

View File

@ -1,6 +1,5 @@
package oupson.apng
import android.util.Log
import oupson.apng.chunks.IDAT
import oupson.apng.chunks.IHDR
import oupson.apng.exceptions.NotPngException
@ -18,7 +17,17 @@ import java.util.*
* @throws NotPngException
*/
class Frame {
class Frame// Get width and height for image
(
byteArray: ByteArray,
delay: Float = 1000f,
xOffsets: Int = 0,
yOffsets: Int = 0,
blendOp: Utils.Companion.BlendOp = Utils.Companion.BlendOp.APNG_BLEND_OP_SOURCE,
disposeOp: Utils.Companion.DisposeOp = Utils.Companion.DisposeOp.APNG_DISPOSE_OP_NONE,
maxWidth: Int? = null,
maxHeight: Int? = null
) {
var byteArray : ByteArray
@ -31,8 +40,8 @@ class Frame {
val delay : Float
var x_offsets : Int = 0
var y_offsets : Int = 0
var xOffsets : Int = 0
var yOffsets : Int = 0
var maxWidth : Int? = null
var maxHeight : Int? = null
@ -40,44 +49,7 @@ class Frame {
var blendOp: Utils.Companion.BlendOp
var disposeOp : Utils.Companion.DisposeOp
constructor(byteArray: ByteArray) {
if (isPng(byteArray)) {
this.byteArray = byteArray
Log.e("tag", byteArray.size.toString())
// Get width and height for image
delay = 1000f
blendOp = Utils.Companion.BlendOp.APNG_BLEND_OP_SOURCE
disposeOp = Utils.Companion.DisposeOp.APNG_DISPOSE_OP_NONE
var cursor = 8
while (cursor < byteArray.size) {
val chunk = byteArray.copyOfRange(cursor, cursor + Utils.parseLength(byteArray.copyOfRange(cursor, cursor + 4)) + 12)
parseChunk(chunk)
cursor += Utils.parseLength(byteArray.copyOfRange(cursor, cursor + 4)) + 12
}
} else {
throw NotPngException()
}
}
constructor(byteArray: ByteArray, delay : Float) {
if (isPng(byteArray)) {
this.byteArray = byteArray
// Get width and height for image
var cursor = 8
while (cursor < byteArray.size) {
val chunk = byteArray.copyOfRange(cursor, cursor + Utils.parseLength(byteArray.copyOfRange(cursor, cursor + 4)) + 12)
parseChunk(chunk)
cursor += Utils.parseLength(byteArray.copyOfRange(cursor, cursor + 4)) + 12
}
this.delay = delay
blendOp = Utils.Companion.BlendOp.APNG_BLEND_OP_SOURCE
disposeOp = Utils.Companion.DisposeOp.APNG_DISPOSE_OP_NONE
} else {
throw NotPngException()
}
}
constructor(byteArray: ByteArray, delay : Float, blendOp: Utils.Companion.BlendOp, disposeOp: Utils.Companion.DisposeOp) {
init {
if (isPng(byteArray)) {
this.byteArray = byteArray
// Get width and height for image
@ -90,56 +62,8 @@ class Frame {
this.delay = delay
this.maxWidth = -1
this.maxHeight = -1
this.blendOp = blendOp
this.disposeOp = disposeOp
} else {
throw NotPngException()
}
}
constructor(byteArray: ByteArray, delay : Float, xOffsets : Int, yOffsets : Int, blendOp: Utils.Companion.BlendOp, disposeOp: Utils.Companion.DisposeOp) {
if (isPng(byteArray)) {
this.byteArray = byteArray
// Get width and height for image
var cursor = 8
while (cursor < byteArray.size) {
val chunk = byteArray.copyOfRange(cursor, cursor + Utils.parseLength(byteArray.copyOfRange(cursor, cursor + 4)) + 12)
parseChunk(chunk)
cursor += Utils.parseLength(byteArray.copyOfRange(cursor, cursor + 4)) + 12
}
this.delay = delay
x_offsets = xOffsets
y_offsets = yOffsets
this.maxWidth = -1
this.maxHeight = -1
this.blendOp = blendOp
this.disposeOp = disposeOp
} else {
throw NotPngException()
}
}
constructor(byteArray: ByteArray, delay : Float, xOffsets : Int, yOffsets : Int, maxWidth : Int, maxHeight : Int, blendOp: Utils.Companion.BlendOp, disposeOp: Utils.Companion.DisposeOp) {
if (isPng(byteArray)) {
this.byteArray = byteArray
// Get width and height for image
var cursor = 8
while (cursor < byteArray.size) {
val chunk = byteArray.copyOfRange(cursor, cursor + Utils.parseLength(byteArray.copyOfRange(cursor, cursor + 4)) + 12)
parseChunk(chunk)
cursor += Utils.parseLength(byteArray.copyOfRange(cursor, cursor + 4)) + 12
}
this.delay = delay
x_offsets = xOffsets
y_offsets = yOffsets
this.xOffsets = xOffsets
this.yOffsets = yOffsets
this.maxWidth = maxWidth
this.maxHeight = maxHeight
@ -150,7 +74,7 @@ class Frame {
}
}
fun parseChunk(byteArray: ByteArray) {
private fun parseChunk(byteArray: ByteArray) {
when(Arrays.toString(byteArray.copyOfRange(4, 8))) {
IHDR -> {
ihdr = IHDR()

View File

@ -14,8 +14,7 @@ import org.jetbrains.anko.imageView
import org.jetbrains.anko.matchParent
import org.jetbrains.anko.sdk27.coroutines.onClick
import org.jetbrains.anko.verticalLayout
import oupson.apng.ApngAnimator
import oupson.apng.loadApng
class Main2Activity : AppCompatActivity() {
lateinit var imageView : ImageView
@ -52,9 +51,8 @@ class Main2Activity : AppCompatActivity() {
}
fun load() {
val animator = ApngAnimator(applicationContext).loadInto(imageView)
val uri = intent.data
animator.load(uri, null)
val animator = imageView.loadApng(uri, null)
imageView.onClick {
try {
if (animator.isPlaying) {

View File

@ -20,6 +20,7 @@ import org.jetbrains.anko.sdk27.coroutines.onClick
import org.jetbrains.anko.sdk27.coroutines.onMenuItemClick
import org.jetbrains.anko.sdk27.coroutines.onSeekBarChangeListener
import oupson.apng.ApngAnimator
import oupson.apng.loadApng
class MainActivity : AppCompatActivity() {
@ -122,8 +123,7 @@ class MainActivity : AppCompatActivity() {
)
val imageView = imageView {
id = View.generateViewId()
animator = ApngAnimator(this@MainActivity).loadInto(this).apply {
load(imageUrl)
animator = this.loadApng(imageUrl).apply {
onLoaded {
setOnAnimationLoopListener {
// Log.e("app-test", "onLoop")