Working on ExperimentalApngEncoder.kt

This commit is contained in:
Oupson 2020-09-20 00:14:54 +02:00
parent 2fd2d0e684
commit a20d9ee7d9
2 changed files with 57 additions and 133 deletions

View File

@ -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()))

View File

@ -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()