New class : ApngDrawable, to get the cover frame
Remove useless array copy
This commit is contained in:
parent
b584d6d013
commit
d4924627cf
|
@ -17,6 +17,7 @@ import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import oupson.apng.BuildConfig
|
import oupson.apng.BuildConfig
|
||||||
import oupson.apng.decoder.ApngDecoder.Companion.decodeApng
|
import oupson.apng.decoder.ApngDecoder.Companion.decodeApng
|
||||||
|
import oupson.apng.drawable.ApngDrawable
|
||||||
import oupson.apng.exceptions.BadApngException
|
import oupson.apng.exceptions.BadApngException
|
||||||
import oupson.apng.exceptions.BadCRCException
|
import oupson.apng.exceptions.BadCRCException
|
||||||
import oupson.apng.utils.Loader
|
import oupson.apng.utils.Loader
|
||||||
|
@ -67,6 +68,7 @@ class ApngDecoder {
|
||||||
* @param config Configuration applied to the bitmap added to the animation. Please note that the frame is decoded in ARGB_8888 and converted after, for the buffer.
|
* @param config Configuration applied to the bitmap added to the animation. Please note that the frame is decoded in ARGB_8888 and converted after, for the buffer.
|
||||||
* @return [AnimationDrawable] if successful and an [AnimatedImageDrawable] if the image decoded is not an APNG but a gif. If it is not an animated image, it is a [Drawable].
|
* @return [AnimationDrawable] if successful and an [AnimatedImageDrawable] if the image decoded is not an APNG but a gif. If it is not an animated image, it is a [Drawable].
|
||||||
*/
|
*/
|
||||||
|
// TODO BETTER CONFIG (Maybe data class with speed, config, and a settings to ignore cover frame ?)
|
||||||
@Suppress("MemberVisibilityCanBePrivate")
|
@Suppress("MemberVisibilityCanBePrivate")
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
|
@ -98,7 +100,7 @@ class ApngDecoder {
|
||||||
|
|
||||||
var isApng = false
|
var isApng = false
|
||||||
|
|
||||||
val drawable = AnimationDrawable().apply {
|
val drawable = ApngDrawable().apply {
|
||||||
isOneShot = false
|
isOneShot = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,14 +113,13 @@ class ApngDecoder {
|
||||||
|
|
||||||
if (byteRead == -1)
|
if (byteRead == -1)
|
||||||
break
|
break
|
||||||
val length = Utils.uIntFromBytesBigEndian(lengthChunk.map(Byte::toInt))
|
val length = Utils.uIntFromBytesBigEndian(lengthChunk)
|
||||||
|
|
||||||
val chunk = ByteArray(length + 8)
|
val chunk = ByteArray(length + 8)
|
||||||
byteRead = inputStream.read(chunk)
|
byteRead = inputStream.read(chunk)
|
||||||
|
|
||||||
val byteArray = lengthChunk.plus(chunk)
|
val byteArray = lengthChunk.plus(chunk)
|
||||||
val chunkCRC =
|
val chunkCRC = Utils.uIntFromBytesBigEndian(byteArray, byteArray.size - 4)
|
||||||
Utils.uIntFromBytesBigEndian(byteArray.copyOfRange(byteArray.size - 4, byteArray.size).map(Byte::toInt))
|
|
||||||
val crc = CRC32()
|
val crc = CRC32()
|
||||||
crc.update(byteArray, 4, byteArray.size - 8)
|
crc.update(byteArray, 4, byteArray.size - 8)
|
||||||
if (chunkCRC == crc.value.toInt()) {
|
if (chunkCRC == crc.value.toInt()) {
|
||||||
|
@ -126,20 +127,20 @@ class ApngDecoder {
|
||||||
when {
|
when {
|
||||||
name.contentEquals(Utils.fcTL) -> {
|
name.contentEquals(Utils.fcTL) -> {
|
||||||
if (png == null) {
|
if (png == null) {
|
||||||
cover?.let {
|
drawable.coverFrame = cover?.let {
|
||||||
it.write(zeroLength)
|
it.write(zeroLength)
|
||||||
// Generate crc for IEND
|
// Generate crc for IEND
|
||||||
val crC32 = CRC32()
|
val crC32 = CRC32()
|
||||||
crC32.update(Utils.IEND, 0, Utils.IEND.size)
|
crC32.update(Utils.IEND, 0, Utils.IEND.size)
|
||||||
it.write(Utils.IEND)
|
it.write(Utils.IEND)
|
||||||
it.write(Utils.uIntToByteArray(crC32.value.toInt()))
|
it.write(Utils.uIntToByteArray(crC32.value.toInt()))
|
||||||
/**APNGDisassembler.apng.cover = BitmapFactory.decodeByteArray(
|
|
||||||
it.toByteArray(),
|
val pngBytes = it.toByteArray()
|
||||||
|
BitmapFactory.decodeByteArray(
|
||||||
|
pngBytes,
|
||||||
0,
|
0,
|
||||||
it.size
|
pngBytes.size
|
||||||
)*/ // TODO
|
)
|
||||||
// This will be ignored, has this is not a frame in the anim :/
|
|
||||||
// TODO CAN BE A DRAWING THAT INHERITS FROM DRAWING ANIMATION
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Add IEND body length : 0
|
// Add IEND body length : 0
|
||||||
|
@ -233,11 +234,11 @@ class ApngDecoder {
|
||||||
// Parse Frame ConTroL chunk
|
// Parse Frame ConTroL chunk
|
||||||
// Get the width of the png
|
// Get the width of the png
|
||||||
val width = Utils.uIntFromBytesBigEndian(
|
val width = Utils.uIntFromBytesBigEndian(
|
||||||
byteArray.copyOfRange(12, 16).map(Byte::toInt)
|
byteArray, 12
|
||||||
)
|
)
|
||||||
// Get the height of the png
|
// Get the height of the png
|
||||||
val height = Utils.uIntFromBytesBigEndian(
|
val height = Utils.uIntFromBytesBigEndian(
|
||||||
byteArray.copyOfRange(16, 20).map(Byte::toInt)
|
byteArray, 16
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -246,11 +247,11 @@ class ApngDecoder {
|
||||||
*/
|
*/
|
||||||
// Get delay numerator
|
// Get delay numerator
|
||||||
val delayNum = Utils.uShortFromBytesBigEndian(
|
val delayNum = Utils.uShortFromBytesBigEndian(
|
||||||
byteArray.copyOfRange(28, 30).map(Byte::toInt)
|
byteArray, 28
|
||||||
).toFloat()
|
).toFloat()
|
||||||
// Get delay denominator
|
// Get delay denominator
|
||||||
var delayDen = Utils.uShortFromBytesBigEndian(
|
var delayDen = Utils.uShortFromBytesBigEndian(
|
||||||
byteArray.copyOfRange(30, 32).map(Byte::toInt)
|
byteArray, 30
|
||||||
).toFloat()
|
).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).
|
||||||
|
@ -262,10 +263,10 @@ class ApngDecoder {
|
||||||
|
|
||||||
// Get x and y offsets
|
// Get x and y offsets
|
||||||
xOffset = Utils.uIntFromBytesBigEndian(
|
xOffset = Utils.uIntFromBytesBigEndian(
|
||||||
byteArray.copyOfRange(20, 24).map(Byte::toInt)
|
byteArray, 20
|
||||||
)
|
)
|
||||||
yOffset = Utils.uIntFromBytesBigEndian(
|
yOffset = Utils.uIntFromBytesBigEndian(
|
||||||
byteArray.copyOfRange(24, 28).map(Byte::toInt)
|
byteArray, 24
|
||||||
)
|
)
|
||||||
blendOp = Utils.decodeBlendOp(byteArray[33].toInt())
|
blendOp = Utils.decodeBlendOp(byteArray[33].toInt())
|
||||||
disposeOp = Utils.decodeDisposeOp(byteArray[32].toInt())
|
disposeOp = Utils.decodeDisposeOp(byteArray[32].toInt())
|
||||||
|
@ -419,7 +420,7 @@ class ApngDecoder {
|
||||||
// Find the chunk length
|
// Find the chunk length
|
||||||
val bodySize =
|
val bodySize =
|
||||||
Utils.uIntFromBytesBigEndian(
|
Utils.uIntFromBytesBigEndian(
|
||||||
byteArray.copyOfRange(0, 4).map(Byte::toInt)
|
byteArray, 0
|
||||||
)
|
)
|
||||||
w.write(byteArray.copyOfRange(0, 4))
|
w.write(byteArray.copyOfRange(0, 4))
|
||||||
|
|
||||||
|
@ -437,7 +438,7 @@ class ApngDecoder {
|
||||||
}
|
}
|
||||||
name.contentEquals(Utils.fdAT) -> {
|
name.contentEquals(Utils.fdAT) -> {
|
||||||
// Find the chunk length
|
// Find the chunk length
|
||||||
val bodySize = Utils.uIntFromBytesBigEndian(byteArray.copyOfRange(0, 4).map(Byte::toInt))
|
val bodySize = Utils.uIntFromBytesBigEndian(byteArray, 0)
|
||||||
png?.write(Utils.uIntToByteArray(bodySize - 4))
|
png?.write(Utils.uIntToByteArray(bodySize - 4))
|
||||||
|
|
||||||
val body = ByteArray(bodySize)
|
val body = ByteArray(bodySize)
|
||||||
|
@ -459,11 +460,11 @@ class ApngDecoder {
|
||||||
}
|
}
|
||||||
name.contentEquals(Utils.IHDR) -> {
|
name.contentEquals(Utils.IHDR) -> {
|
||||||
// Get length of the body of the chunk
|
// Get length of the body of the chunk
|
||||||
val bodySize = Utils.uIntFromBytesBigEndian(byteArray.copyOfRange(4 - 4, 4).map(Byte::toInt))
|
val bodySize = Utils.uIntFromBytesBigEndian(byteArray, 0)
|
||||||
// Get the width of the png
|
// Get the width of the png
|
||||||
maxWidth = Utils.uIntFromBytesBigEndian(byteArray.copyOfRange(4 +4, 4 + 8).map(Byte::toInt))
|
maxWidth = Utils.uIntFromBytesBigEndian(byteArray, 8)
|
||||||
// Get the height of the png
|
// Get the height of the png
|
||||||
maxHeight = Utils.uIntFromBytesBigEndian(byteArray.copyOfRange(4 +8, 4 + 12).map(Byte::toInt))
|
maxHeight = Utils.uIntFromBytesBigEndian(byteArray, 12)
|
||||||
ihdrOfApng = byteArray.copyOfRange(4 + 4, 4 + bodySize + 4)
|
ihdrOfApng = byteArray.copyOfRange(4 + 4, 4 + bodySize + 4)
|
||||||
|
|
||||||
buffer = Bitmap.createBitmap(
|
buffer = Bitmap.createBitmap(
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
package oupson.apng.drawable
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.drawable.AnimationDrawable
|
||||||
|
|
||||||
|
// TODO DOCUMENT
|
||||||
|
class ApngDrawable : AnimationDrawable() {
|
||||||
|
var coverFrame : Bitmap? = null
|
||||||
|
}
|
|
@ -190,6 +190,13 @@ class Utils {
|
||||||
((bytes[2] and 0xFF) shl 8) or
|
((bytes[2] and 0xFF) shl 8) or
|
||||||
(bytes[3] and 0xFF)
|
(bytes[3] and 0xFF)
|
||||||
|
|
||||||
|
// TODO DOCUMENT AND TEST
|
||||||
|
fun uIntFromBytesBigEndian(bytes: ByteArray, offset: Int = 0): Int =
|
||||||
|
((bytes[offset + 0].toInt() and 0xFF) shl 24) or
|
||||||
|
((bytes[offset + 1].toInt() and 0xFF) shl 16) or
|
||||||
|
((bytes[offset + 2].toInt() and 0xFF) shl 8) or
|
||||||
|
(bytes[offset + 3].toInt() and 0xFF)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse an unsigned short
|
* Parse an unsigned short
|
||||||
* [bytes] The bytes, in big endian order
|
* [bytes] The bytes, in big endian order
|
||||||
|
@ -200,6 +207,11 @@ class Utils {
|
||||||
(((bytes[0] and 0xFF) shl 8) or
|
(((bytes[0] and 0xFF) shl 8) or
|
||||||
(bytes[1] and 0xFF))
|
(bytes[1] and 0xFF))
|
||||||
|
|
||||||
|
// TODO DOCUMENT AND TEST
|
||||||
|
fun uShortFromBytesBigEndian(bytes: ByteArray, offset : Int = 0): Int =
|
||||||
|
(((bytes[offset].toInt() and 0xFF) shl 8) or
|
||||||
|
(bytes[offset + 1].toInt() and 0xFF))
|
||||||
|
|
||||||
val fcTL: ByteArray by lazy { byteArrayOf(0x66, 0x63, 0x54, 0x4c) }
|
val fcTL: ByteArray by lazy { byteArrayOf(0x66, 0x63, 0x54, 0x4c) }
|
||||||
val IEND: ByteArray by lazy { byteArrayOf(0x49, 0x45, 0x4e, 0x44) }
|
val IEND: ByteArray by lazy { byteArrayOf(0x49, 0x45, 0x4e, 0x44) }
|
||||||
val IDAT: ByteArray by lazy { byteArrayOf(0x49, 0x44, 0x41, 0x54) }
|
val IDAT: ByteArray by lazy { byteArrayOf(0x49, 0x44, 0x41, 0x54) }
|
||||||
|
|
|
@ -11,6 +11,7 @@ import android.view.ViewGroup
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import oupson.apng.decoder.ApngDecoder
|
import oupson.apng.decoder.ApngDecoder
|
||||||
|
import oupson.apng.drawable.ApngDrawable
|
||||||
import oupson.apngcreator.BuildConfig
|
import oupson.apngcreator.BuildConfig
|
||||||
import oupson.apngcreator.R
|
import oupson.apngcreator.R
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
@ -40,7 +41,7 @@ class ApngDecoderFragment : Fragment() {
|
||||||
callback = object : ApngDecoder.Callback {
|
callback = object : ApngDecoder.Callback {
|
||||||
override fun onSuccess(drawable: Drawable) {
|
override fun onSuccess(drawable: Drawable) {
|
||||||
if (BuildConfig.DEBUG)
|
if (BuildConfig.DEBUG)
|
||||||
Log.i(TAG, "onSuccess()")
|
Log.i(TAG, "onSuccess(), has cover frame : ${(drawable as? ApngDrawable)?.coverFrame != null}")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onError(error: Exception) {
|
override fun onError(error: Exception) {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package oupson.apngcreator.fragments
|
package oupson.apngcreator.fragments
|
||||||
|
|
||||||
|
|
||||||
import android.graphics.drawable.AnimationDrawable
|
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
@ -15,6 +14,7 @@ import androidx.fragment.app.Fragment
|
||||||
import com.squareup.picasso.Picasso
|
import com.squareup.picasso.Picasso
|
||||||
import kotlinx.android.synthetic.main.activity_creator.*
|
import kotlinx.android.synthetic.main.activity_creator.*
|
||||||
import oupson.apng.decoder.ApngDecoder
|
import oupson.apng.decoder.ApngDecoder
|
||||||
|
import oupson.apng.drawable.ApngDrawable
|
||||||
import oupson.apngcreator.BuildConfig
|
import oupson.apngcreator.BuildConfig
|
||||||
import oupson.apngcreator.R
|
import oupson.apngcreator.R
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ class KotlinFragment : Fragment() {
|
||||||
private var speedSeekBar : SeekBar? = null
|
private var speedSeekBar : SeekBar? = null
|
||||||
|
|
||||||
//private var animator : ApngAnimator? = null
|
//private var animator : ApngAnimator? = null
|
||||||
private var animation : AnimationDrawable? = null
|
private var animation : ApngDrawable? = null
|
||||||
private var durations : IntArray? = null
|
private var durations : IntArray? = null
|
||||||
|
|
||||||
private var frameIndex = 0
|
private var frameIndex = 0
|
||||||
|
@ -82,8 +82,9 @@ class KotlinFragment : Fragment() {
|
||||||
|
|
||||||
pauseButton?.setOnClickListener {
|
pauseButton?.setOnClickListener {
|
||||||
animation = animation?.let { animation ->
|
animation = animation?.let { animation ->
|
||||||
val res = AnimationDrawable()
|
val res = ApngDrawable()
|
||||||
animation.stop()
|
animation.stop()
|
||||||
|
res.coverFrame = animation.coverFrame
|
||||||
val currentFrame = animation.current
|
val currentFrame = animation.current
|
||||||
|
|
||||||
frameLoop@ for (i in 0 until animation.numberOfFrames) {
|
frameLoop@ for (i in 0 until animation.numberOfFrames) {
|
||||||
|
@ -120,8 +121,9 @@ class KotlinFragment : Fragment() {
|
||||||
if (seekBar != null && durations != null) {
|
if (seekBar != null && durations != null) {
|
||||||
val speed = seekBar.progress.toFloat() / 100f
|
val speed = seekBar.progress.toFloat() / 100f
|
||||||
animation = animation?.let { animation ->
|
animation = animation?.let { animation ->
|
||||||
val res = AnimationDrawable()
|
val res = ApngDrawable()
|
||||||
animation.stop()
|
animation.stop()
|
||||||
|
res.coverFrame = animation.coverFrame
|
||||||
|
|
||||||
for (i in 0 until animation.numberOfFrames) {
|
for (i in 0 until animation.numberOfFrames) {
|
||||||
res.addFrame(animation.getFrame(i), (durations!![i].toFloat() / speed).toInt())
|
res.addFrame(animation.getFrame(i), (durations!![i].toFloat() / speed).toInt())
|
||||||
|
@ -142,7 +144,7 @@ class KotlinFragment : Fragment() {
|
||||||
apngImageView!!,
|
apngImageView!!,
|
||||||
callback = object : ApngDecoder.Callback {
|
callback = object : ApngDecoder.Callback {
|
||||||
override fun onSuccess(drawable: Drawable) {
|
override fun onSuccess(drawable: Drawable) {
|
||||||
animation = (drawable as? AnimationDrawable)
|
animation = (drawable as? ApngDrawable)
|
||||||
durations = IntArray(animation?.numberOfFrames ?: 0) { i ->
|
durations = IntArray(animation?.numberOfFrames ?: 0) { i ->
|
||||||
animation?.getDuration(i) ?: 0
|
animation?.getDuration(i) ?: 0
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue