Working on ExperimentalApngEncoder.kt
This commit is contained in:
parent
2fd2d0e684
commit
a20d9ee7d9
|
@ -1,5 +1,3 @@
|
|||
@file:Suppress("CascadeIf")
|
||||
|
||||
package oupson.apng.encoder
|
||||
|
||||
import android.graphics.Bitmap
|
||||
|
@ -16,13 +14,32 @@ import kotlin.math.max
|
|||
import kotlin.math.min
|
||||
|
||||
// TODO DOCUMENTATION
|
||||
// TODO BUFFER AND BUFFER DEACTIVATION WHEN BITMAP CONFIG DOES NOT CONTAIN AN ALPHA CHANNEL
|
||||
// TODO JAVA OVERLOADS
|
||||
// TODO ADD SUPPORT FOR FIRST FRAME NOT IN ANIM
|
||||
// TODO OPTIMISE APNG
|
||||
class ExperimentalApngEncoder(
|
||||
private val outputStream: OutputStream,
|
||||
private val width : Int,
|
||||
private val height : Int,
|
||||
numberOfFrames : Int, private val encodeAlpha: Boolean = true, filter: Int = 0, compressionLevel: Int = 0) {
|
||||
private val width: Int,
|
||||
private val height: Int,
|
||||
numberOfFrames: Int,
|
||||
private val encodeAlpha: Boolean = true,
|
||||
filter: Int = 0,
|
||||
compressionLevel: Int = 0
|
||||
) {
|
||||
companion object {
|
||||
/** Constants for filter (NONE) */
|
||||
private const val FILTER_NONE = 0
|
||||
|
||||
/** Constants for filter (SUB) */
|
||||
private const val FILTER_SUB = 1
|
||||
|
||||
/** Constants for filter (UP) */
|
||||
private const val FILTER_UP = 2
|
||||
|
||||
/** Constants for filter (LAST) */
|
||||
private const val FILTER_LAST = 2
|
||||
}
|
||||
|
||||
private var frameIndex = 0
|
||||
private var seq = 0
|
||||
|
||||
|
@ -32,8 +49,6 @@ class ExperimentalApngEncoder(
|
|||
/** The CRC value. */
|
||||
private var crcValue: Long = 0
|
||||
|
||||
//private var encodeAlpha = true
|
||||
|
||||
/** The bytes-per-pixel. */
|
||||
private var bytesPerPixel: Int = 0
|
||||
|
||||
|
@ -49,21 +64,6 @@ class ExperimentalApngEncoder(
|
|||
/** The left bytes. */
|
||||
private var leftBytes: ByteArray? = null
|
||||
|
||||
companion object {
|
||||
/** Constants for filter (NONE) */
|
||||
private const val FILTER_NONE = 0
|
||||
|
||||
/** Constants for filter (SUB) */
|
||||
private const val FILTER_SUB = 1
|
||||
|
||||
/** Constants for filter (UP) */
|
||||
private const val FILTER_UP = 2
|
||||
|
||||
/** Constants for filter (LAST) */
|
||||
private const val FILTER_LAST = 2
|
||||
}
|
||||
|
||||
|
||||
init {
|
||||
this.filter = FILTER_NONE
|
||||
if (filter <= FILTER_LAST) {
|
||||
|
@ -76,11 +76,9 @@ class ExperimentalApngEncoder(
|
|||
|
||||
outputStream.write(Utils.pngSignature)
|
||||
writeHeader()
|
||||
outputStream.write(generateACTL(numberOfFrames))
|
||||
writeACTL(numberOfFrames)
|
||||
}
|
||||
|
||||
// TODO ADD SUPPORT FOR FIRST FRAME NOT IN ANIM
|
||||
// TODO OPTIMISE APNG
|
||||
@JvmOverloads
|
||||
fun writeFrame(
|
||||
inputStream: InputStream,
|
||||
|
@ -88,27 +86,22 @@ class ExperimentalApngEncoder(
|
|||
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,
|
||||
usePngEncoder: Boolean = true
|
||||
disposeOp: Utils.Companion.DisposeOp = Utils.Companion.DisposeOp.APNG_DISPOSE_OP_NONE
|
||||
) {
|
||||
val btm = BitmapFactory.decodeStream(inputStream)
|
||||
inputStream.close()
|
||||
|
||||
writeFrame(btm, delay, xOffsets, yOffsets, blendOp, disposeOp, usePngEncoder)
|
||||
writeFrame(btm, delay, xOffsets, yOffsets, blendOp, disposeOp)
|
||||
btm.recycle()
|
||||
}
|
||||
|
||||
// TODO ADD SUPPORT FOR FIRST FRAME NOT IN ANIM
|
||||
// TODO OPTIMISE APNG
|
||||
@JvmOverloads
|
||||
fun writeFrame(
|
||||
btm : Bitmap,
|
||||
btm: Bitmap,
|
||||
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,
|
||||
usePngEncoder: Boolean = false
|
||||
disposeOp: Utils.Companion.DisposeOp = Utils.Companion.DisposeOp.APNG_DISPOSE_OP_NONE
|
||||
) {
|
||||
if (frameIndex == 0) {
|
||||
if (btm.width != width)
|
||||
|
@ -120,74 +113,6 @@ class ExperimentalApngEncoder(
|
|||
writeFCTL(btm, delay, disposeOp, blendOp, xOffsets, yOffsets)
|
||||
writeImageData(btm)
|
||||
frameIndex++
|
||||
/** val idat = IDAT().apply {
|
||||
val byteArray = if (usePngEncoder) {
|
||||
val m = ByteArrayOutputStream()
|
||||
val e = FrameEncoder(btm.width, btm.height, true, outputStream = m)
|
||||
e.encode(btm)
|
||||
//PngEncoder().encode(btm, true)
|
||||
m.close()
|
||||
val b = m.toByteArray()
|
||||
File(context.filesDir, "image$frameIndex.png").apply {
|
||||
println("Path is ${this.path}")
|
||||
}.writeBytes(b)
|
||||
b
|
||||
} else {
|
||||
val outputStream = ByteArrayOutputStream()
|
||||
btm.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
|
||||
outputStream.close()
|
||||
outputStream.toByteArray()
|
||||
}
|
||||
var cursor = 8
|
||||
while (cursor < byteArray.size) {
|
||||
val chunk = byteArray.copyOfRange(cursor, cursor + Utils.parseLength(byteArray.copyOfRange(cursor, cursor + 4)) + 12)
|
||||
parse(chunk)
|
||||
|
||||
cursor += Utils.parseLength(byteArray.copyOfRange(cursor, cursor + 4)) + 12
|
||||
}
|
||||
}
|
||||
/**
|
||||
idat.IDATBody.forEach { idatBody ->
|
||||
if (frameIndex == 0) {
|
||||
val idatChunk = ArrayList<Byte>().let { i ->
|
||||
// Add IDAT
|
||||
i.addAll(idatName)
|
||||
// Add chunk body
|
||||
i.addAll(idatBody.asList())
|
||||
i.toByteArray()
|
||||
}
|
||||
// Add the chunk body length
|
||||
outputStream.write(Utils.to4BytesArray(idatBody.size))
|
||||
|
||||
// Generate CRC
|
||||
val crc1 = CRC32()
|
||||
crc1.update(idatChunk, 0, idatChunk.size)
|
||||
outputStream.write(idatChunk)
|
||||
outputStream.write(Utils.to4BytesArray(crc1.value.toInt()))
|
||||
} else {
|
||||
val fdat = ArrayList<Byte>().let { fdat ->
|
||||
fdat.addAll(byteArrayOf(0x66, 0x64, 0x41, 0x54).asList())
|
||||
// Add fdat
|
||||
fdat.addAll(Utils.to4Bytes(seq++).asList())
|
||||
// Add chunk body
|
||||
fdat.addAll(idatBody.asList())
|
||||
fdat.toByteArray()
|
||||
}
|
||||
// Add the chunk body length
|
||||
outputStream.write(Utils.to4BytesArray(idatBody.size + 4))
|
||||
|
||||
// Generate CRC
|
||||
val crc1 = CRC32()
|
||||
crc1.update(fdat, 0, fdat.size)
|
||||
outputStream.write(fdat)
|
||||
outputStream.write(Utils.to4BytesArray(crc1.value.toInt()))
|
||||
}
|
||||
}
|
||||
*/
|
||||
frameIndex++
|
||||
/**if (usePngEncoder) {
|
||||
PngEncoder.release()
|
||||
}*/*/
|
||||
}
|
||||
|
||||
fun writeEnd() {
|
||||
|
@ -196,10 +121,10 @@ class ExperimentalApngEncoder(
|
|||
// Add IEND
|
||||
val iend = byteArrayOf(0x49, 0x45, 0x4E, 0x44)
|
||||
// Generate crc for IEND
|
||||
val crC32 = CRC32()
|
||||
crC32.update(iend, 0, iend.size)
|
||||
crc.reset()
|
||||
crc.update(iend, 0, iend.size)
|
||||
outputStream.write(iend)
|
||||
outputStream.write(Utils.to4BytesArray(crC32.value.toInt()))
|
||||
outputStream.write(Utils.to4BytesArray(crc.value.toInt()))
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -227,11 +152,8 @@ class ExperimentalApngEncoder(
|
|||
}
|
||||
|
||||
/**
|
||||
* Write a two-byte integer into the pngBytes array at a given position.
|
||||
* Write a two-byte integer into the outputStream.
|
||||
*
|
||||
* @param n The integer to be written into pngBytes.
|
||||
* @param offset The starting point to write to.
|
||||
* @return The next place to be written to in the pngBytes array.
|
||||
*/
|
||||
@Suppress("unused")
|
||||
private fun writeInt2(n: Int) {
|
||||
|
@ -240,11 +162,8 @@ class ExperimentalApngEncoder(
|
|||
}
|
||||
|
||||
/**
|
||||
* Write a four-byte integer into the pngBytes array at a given position.
|
||||
* Write a four-byte integer into the outputStream.
|
||||
*
|
||||
* @param n The integer to be written into pngBytes.
|
||||
* @param offset The starting point to write to.
|
||||
* @return The next place to be written to in the pngBytes array.
|
||||
*/
|
||||
private fun writeInt4(n: Int) {
|
||||
val temp = byteArrayOf(
|
||||
|
@ -257,15 +176,13 @@ class ExperimentalApngEncoder(
|
|||
}
|
||||
|
||||
/**
|
||||
* Generate the animation control chunk
|
||||
* @return [ArrayList] The byteArray generated
|
||||
* Write the animation control chunk into the outputStream.
|
||||
*/
|
||||
private fun generateACTL(num: Int): ByteArray {
|
||||
val res = ArrayList<Byte>()
|
||||
private fun writeACTL(num: Int) {
|
||||
val actl = ArrayList<Byte>()
|
||||
|
||||
// Add length bytes
|
||||
res.addAll(arrayListOf(0, 0, 0, 0x08))
|
||||
outputStream.write(byteArrayOf(0, 0, 0, 0x08))
|
||||
|
||||
// Add acTL
|
||||
actl.addAll(byteArrayOf(0x61, 0x63, 0x54, 0x4c).asList())
|
||||
|
@ -275,34 +192,43 @@ class ExperimentalApngEncoder(
|
|||
|
||||
// Number of repeat, 0 to infinite
|
||||
actl.addAll(Utils.to4Bytes(0).asList())
|
||||
res.addAll(actl)
|
||||
outputStream.write(actl.toByteArray())
|
||||
|
||||
// generate crc
|
||||
val crc = CRC32()
|
||||
crc.reset()
|
||||
crc.update(actl.toByteArray(), 0, actl.size)
|
||||
res.addAll(Utils.to4Bytes(crc.value.toInt()).asList())
|
||||
return res.toByteArray()
|
||||
outputStream.write(Utils.to4BytesArray(crc.value.toInt()))
|
||||
}
|
||||
|
||||
private fun writeFCTL(btm : Bitmap, delay: Float, disposeOp: Utils.Companion.DisposeOp, blendOp: Utils.Companion.BlendOp, xOffsets: Int, yOffsets: Int) {
|
||||
/**
|
||||
* Write the frame control chunk into the outputStream.
|
||||
*/
|
||||
private fun writeFCTL(
|
||||
btm: Bitmap,
|
||||
delay: Float,
|
||||
disposeOp: Utils.Companion.DisposeOp,
|
||||
blendOp: Utils.Companion.BlendOp,
|
||||
xOffsets: Int,
|
||||
yOffsets: Int
|
||||
) {
|
||||
val fcTL = ArrayList<Byte>()
|
||||
|
||||
// Add the length of the chunk body
|
||||
outputStream.write(byteArrayOf(0x00, 0x00, 0x00, 0x1A))
|
||||
|
||||
// Add fcTL
|
||||
fcTL.addAll(byteArrayOf(0x66, 0x63, 0x54, 0x4c).asList())
|
||||
fcTL.addAll(Utils.fcTL.asList())
|
||||
|
||||
// Add the frame number
|
||||
fcTL.addAll(Utils.to4Bytes(seq++).asList())
|
||||
fcTL.addAll(Utils.to4Bytes(seq++))
|
||||
|
||||
// Add width and height
|
||||
fcTL.addAll(Utils.to4Bytes(btm.width).asList())
|
||||
fcTL.addAll(Utils.to4Bytes(btm.height).asList())
|
||||
fcTL.addAll(Utils.to4Bytes(btm.width))
|
||||
fcTL.addAll(Utils.to4Bytes(btm.height))
|
||||
|
||||
// Add offsets
|
||||
fcTL.addAll(Utils.to4Bytes(xOffsets).asList())
|
||||
fcTL.addAll(Utils.to4Bytes(yOffsets).asList())
|
||||
fcTL.addAll(Utils.to4Bytes(xOffsets))
|
||||
fcTL.addAll(Utils.to4Bytes(yOffsets))
|
||||
|
||||
// Set frame delay
|
||||
fcTL.addAll(Utils.to2Bytes(delay.toInt()).asList())
|
||||
|
@ -313,7 +239,7 @@ class ExperimentalApngEncoder(
|
|||
fcTL.add(Utils.getBlendOp(blendOp).toByte())
|
||||
|
||||
// Create CRC
|
||||
val crc = CRC32()
|
||||
crc.reset()
|
||||
crc.update(fcTL.toByteArray(), 0, fcTL.size)
|
||||
outputStream.write(fcTL.toByteArray())
|
||||
outputStream.write(Utils.to4BytesArray(crc.value.toInt()))
|
||||
|
|
|
@ -145,7 +145,6 @@ class CreatorActivity : AppCompatActivity() {
|
|||
else
|
||||
btm,
|
||||
delay = uri.second.toFloat(),
|
||||
usePngEncoder = true,
|
||||
disposeOp = Utils.Companion.DisposeOp.APNG_DISPOSE_OP_NONE
|
||||
)
|
||||
//input.close()
|
||||
|
@ -153,7 +152,6 @@ class CreatorActivity : AppCompatActivity() {
|
|||
encoder.writeFrame(
|
||||
str,
|
||||
delay = uri.second.toFloat(),
|
||||
usePngEncoder = true
|
||||
)
|
||||
}
|
||||
str.close()
|
||||
|
|
Loading…
Reference in New Issue