Work on ApngDecoder.kt

This commit is contained in:
Oupson 2021-02-24 10:43:52 +01:00
parent c4e38bf54f
commit 4defb5d515
2 changed files with 140 additions and 176 deletions

View File

@ -48,7 +48,7 @@ class ApngDecoder {
companion object { companion object {
private const val TAG = "ApngDecoder" private const val TAG = "ApngDecoder"
private val zeroLength = arrayOf(0x00, 0x00, 0x00, 0x00).map { it.toByte() } private val zeroLength = byteArrayOf(0x00, 0x00, 0x00, 0x00)
// Paint used to clear the buffer // Paint used to clear the buffer
private val clearPaint: Paint by lazy { private val clearPaint: Paint by lazy {
@ -81,8 +81,8 @@ class ApngDecoder {
inputStream.mark(8) inputStream.mark(8)
inputStream.read(bytes) inputStream.read(bytes)
if (isPng(bytes)) { if (isPng(bytes)) {
var png: ArrayList<Byte>? = null var png: ByteArrayOutputStream? = null
var cover: ArrayList<Byte>? = null var cover: ByteArrayOutputStream? = null
var delay = -1f var delay = -1f
var yOffset = -1 var yOffset = -1
var xOffset = -1 var xOffset = -1
@ -117,97 +117,51 @@ class ApngDecoder {
byteRead = inputStream.read(chunk) byteRead = inputStream.read(chunk)
val byteArray = lengthChunk.plus(chunk) val byteArray = lengthChunk.plus(chunk)
val i = 4
val chunkCRC = val chunkCRC =
Utils.uIntFromBytesBigEndian(byteArray.copyOfRange(byteArray.size - 4, byteArray.size).map(Byte::toInt)) Utils.uIntFromBytesBigEndian(byteArray.copyOfRange(byteArray.size - 4, byteArray.size).map(Byte::toInt))
val crc = CRC32() val crc = CRC32()
crc.update(byteArray.copyOfRange(i, byteArray.size - 4)) crc.update(byteArray, 4, byteArray.size - 8)
if (chunkCRC == crc.value.toInt()) { if (chunkCRC == crc.value.toInt()) {
val name = byteArray.copyOfRange(i, i + 4) val name = byteArray.copyOfRange(4, 8)
when { when {
name.contentEquals(Utils.fcTL) -> { name.contentEquals(Utils.fcTL) -> {
if (png == null) { if (png == null) {
cover?.let { cover?.let {
it.addAll(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.addAll(Utils.IEND.asList()) it.write(Utils.IEND)
it.addAll(Utils.uIntToByteArray(crC32.value.toInt()).asList()) it.write(Utils.uIntToByteArray(crC32.value.toInt()))
/**APNGDisassembler.apng.cover = BitmapFactory.decodeByteArray( /**APNGDisassembler.apng.cover = BitmapFactory.decodeByteArray(
it.toByteArray(), it.toByteArray(),
0, 0,
it.size it.size
)*/ // TODO )*/ // TODO
} // This will be ignored, has this is not a frame in the anim :/
png = ArrayList() // TODO CAN BE A DRAWING THAT INHERITS FROM DRAWING ANIMATION
// PARSE FCTL
// Get the width of the png
val width = Utils.uIntFromBytesBigEndian(byteArray.copyOfRange(i + 8, i + 12).map(Byte::toInt))
// Get the height of the png
val height = Utils.uIntFromBytesBigEndian(byteArray.copyOfRange(i + 12, i + 16).map(Byte::toInt))
/*
* 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
val delayNum = Utils.uShortFromBytesBigEndian(byteArray.copyOfRange(i + 24, i + 26).map(Byte::toInt)).toFloat()
// Get delay denominator
var delayDen = Utils.uShortFromBytesBigEndian(byteArray.copyOfRange(i + 26, i + 28).map(Byte::toInt)).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 (delayDen == 0f) {
delayDen = 100f
}
delay = (delayNum / delayDen * 1000)
// Get x and y offsets
xOffset = Utils.uIntFromBytesBigEndian(byteArray.copyOfRange(i + 16, i + 20).map(Byte::toInt))
yOffset = Utils.uIntFromBytesBigEndian(byteArray.copyOfRange(i + 20, i + 24).map(Byte::toInt))
blendOp = Utils.decodeBlendOp(byteArray[33].toInt())
disposeOp = Utils.decodeDisposeOp(byteArray[32].toInt())
if (xOffset + width > maxWidth) {
throw BadApngException("`xOffset` + `width` must be <= `IHDR` width")
} else if (yOffset + height > maxHeight) {
throw BadApngException("`yOffset` + `height` must be <= `IHDR` height")
}
png.addAll(Utils.pngSignature.asList())
png.addAll(
generateIhdr(
ihdrOfApng,
width,
height
).asList()
)
plte?.let {
png?.addAll(it.asList())
}
tnrs?.let {
png?.addAll(it.asList())
} }
} else { } else {
// Add IEND body length : 0 // Add IEND body length : 0
png.addAll(zeroLength) png.write(zeroLength)
// Add IEND // Add IEND
// 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)
png.addAll(Utils.IEND.asList()) png.write(Utils.IEND)
png.addAll(Utils.uIntToByteArray(crC32.value.toInt()).asList()) png.write(Utils.uIntToByteArray(crC32.value.toInt()))
val btm = Bitmap.createBitmap( val btm = Bitmap.createBitmap(
maxWidth, maxWidth,
maxHeight, maxHeight,
Bitmap.Config.ARGB_8888 Bitmap.Config.ARGB_8888
) )
val pngBytes = png.toByteArray()
val decoded = BitmapFactory.decodeByteArray( val decoded = BitmapFactory.decodeByteArray(
png.toByteArray(), pngBytes,
0, 0,
png.size pngBytes.size
) )
val canvas = Canvas(btm) val canvas = Canvas(btm)
canvas.drawBitmap(buffer!!, 0f, 0f, null) canvas.drawBitmap(buffer!!, 0f, 0f, null)
@ -272,73 +226,93 @@ class ApngDecoder {
else -> buffer = btm else -> buffer = btm
} }
png = ArrayList()
// PARSE FCTL
// Get the width of the png
val width = Utils.uIntFromBytesBigEndian(byteArray.copyOfRange(i + 8, i + 12).map(Byte::toInt))
// Get the height of the png
val height = Utils.uIntFromBytesBigEndian(byteArray.copyOfRange(i + 12, i + 16).map(Byte::toInt))
/*
* 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
val delayNum = Utils.uShortFromBytesBigEndian(byteArray.copyOfRange(i + 24, i + 26).map(Byte::toInt)).toFloat()
// Get delay denominator
var delayDen = Utils.uShortFromBytesBigEndian(byteArray.copyOfRange(i + 26, i + 28).map(Byte::toInt)).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 (delayDen == 0f) {
delayDen = 100f
}
delay = (delayNum / delayDen * 1000)
// Get x and y offsets
xOffset = Utils.uIntFromBytesBigEndian(byteArray.copyOfRange(i + 16, i + 20).map(Byte::toInt))
yOffset = Utils.uIntFromBytesBigEndian(byteArray.copyOfRange(i + 20, i + 24).map(Byte::toInt))
blendOp = Utils.decodeBlendOp(byteArray[33].toInt())
disposeOp = Utils.decodeDisposeOp(byteArray[32].toInt())
png.addAll(Utils.pngSignature.asList())
png.addAll(
generateIhdr(
ihdrOfApng,
width,
height
).asList()
)
plte?.let {
png.addAll(it.asList())
}
tnrs?.let {
png.addAll(it.asList())
}
} }
png = ByteArrayOutputStream(4096)
// Parse Frame ConTroL chunk
// Get the width of the png
val width = Utils.uIntFromBytesBigEndian(
byteArray.copyOfRange(12, 16).map(Byte::toInt)
)
// Get the height of the png
val height = Utils.uIntFromBytesBigEndian(
byteArray.copyOfRange(16, 20).map(Byte::toInt)
)
/*
* 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
val delayNum = Utils.uShortFromBytesBigEndian(
byteArray.copyOfRange(28, 30).map(Byte::toInt)
).toFloat()
// Get delay denominator
var delayDen = Utils.uShortFromBytesBigEndian(
byteArray.copyOfRange(30, 32).map(Byte::toInt)
).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 (delayDen == 0f) {
delayDen = 100f
}
delay = (delayNum / delayDen * 1000)
// Get x and y offsets
xOffset = Utils.uIntFromBytesBigEndian(
byteArray.copyOfRange(20, 24).map(Byte::toInt)
)
yOffset = Utils.uIntFromBytesBigEndian(
byteArray.copyOfRange(24, 28).map(Byte::toInt)
)
blendOp = Utils.decodeBlendOp(byteArray[33].toInt())
disposeOp = Utils.decodeDisposeOp(byteArray[32].toInt())
if (xOffset + width > maxWidth) {
throw BadApngException("`xOffset` + `width` must be <= `IHDR` width")
} else if (yOffset + height > maxHeight) {
throw BadApngException("`yOffset` + `height` must be <= `IHDR` height")
}
png.write(Utils.pngSignature)
png.write(
generateIhdr(
ihdrOfApng,
width,
height
)
)
plte?.let {
png.write(it)
}
tnrs?.let {
png.write(it)
}
} }
name.contentEquals(Utils.IEND) -> { name.contentEquals(Utils.IEND) -> {
if (isApng && png != null) { if (isApng && png != null) {
png.addAll(zeroLength) png.write(zeroLength)
// Add IEND // Add IEND
// 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)
png.addAll(Utils.IEND.asList()) png.write(Utils.IEND)
png.addAll(Utils.uIntToByteArray(crC32.value.toInt()).asList()) png.write(Utils.uIntToByteArray(crC32.value.toInt()))
val btm = Bitmap.createBitmap( val btm = Bitmap.createBitmap(
maxWidth, maxWidth,
maxHeight, maxHeight,
Bitmap.Config.ARGB_8888 Bitmap.Config.ARGB_8888
) )
val pngBytes = png.toByteArray()
val decoded = BitmapFactory.decodeByteArray( val decoded = BitmapFactory.decodeByteArray(
png.toByteArray(), pngBytes,
0, 0,
png.size pngBytes.size
) )
val canvas = Canvas(btm) val canvas = Canvas(btm)
canvas.drawBitmap(buffer!!, 0f, 0f, null) canvas.drawBitmap(buffer!!, 0f, 0f, null)
@ -402,89 +376,80 @@ class ApngDecoder {
else -> buffer = btm else -> buffer = btm
} }
} else { } else {
// TODO Check before the end
cover?.let { cover?.let {
it.addAll(zeroLength) it.write(zeroLength)
// Add IEND // Add IEND
// 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.addAll(Utils.IEND.asList()) it.write(Utils.IEND)
it.addAll(Utils.uIntToByteArray(crC32.value.toInt()).asList()) it.write(Utils.uIntToByteArray(crC32.value.toInt()))
inputStream.close() inputStream.close()
val pngBytes = it.toByteArray()
return BitmapDrawable( return BitmapDrawable(
context.resources, context.resources,
BitmapFactory.decodeByteArray( BitmapFactory.decodeByteArray(
it.toByteArray(), pngBytes,
0, 0,
it.size pngBytes.size
) )
) )
} }
} }
} }
name.contentEquals(Utils.IDAT) -> { name.contentEquals(Utils.IDAT) -> {
if (png == null) { val w = if (png == null) {
if (cover == null) { if (cover == null) {
cover = ArrayList() cover = ByteArrayOutputStream()
cover.addAll(Utils.pngSignature.asList()) cover.write(Utils.pngSignature)
cover.addAll( cover.write(
generateIhdr( generateIhdr(
ihdrOfApng, ihdrOfApng,
maxWidth, maxWidth,
maxHeight maxHeight
).asList() )
) )
} }
// Find the chunk length cover
val bodySize =
Utils.uIntFromBytesBigEndian(byteArray.copyOfRange(i - 4, i).map(Byte::toInt))
cover.addAll(byteArray.copyOfRange(i - 4, i).asList())
val body = ArrayList<Byte>()
body.addAll(Utils.IDAT.asList())
// Get image bytes
body.addAll(
byteArray.copyOfRange(
i + 4,
i + 4 + bodySize
).asList()
)
val crC32 = CRC32()
crC32.update(body.toByteArray(), 0, body.size)
cover.addAll(body)
cover.addAll(Utils.uIntToByteArray(crC32.value.toInt()).asList())
} else { } else {
// Find the chunk length png
val bodySize =
Utils.uIntFromBytesBigEndian(byteArray.copyOfRange(i - 4, i).map(Byte::toInt))
png.addAll(byteArray.copyOfRange(i - 4, i).asList())
val body = ArrayList<Byte>()
body.addAll(Utils.IDAT.asList())
// Get image bytes
body.addAll(
byteArray.copyOfRange(
i + 4,
i + 4 + bodySize
).asList()
)
val crC32 = CRC32()
crC32.update(body.toByteArray(), 0, body.size)
png.addAll(body)
png.addAll(Utils.uIntToByteArray(crC32.value.toInt()).asList())
} }
// Find the chunk length
val bodySize =
Utils.uIntFromBytesBigEndian(
byteArray.copyOfRange(0, 4).map(Byte::toInt)
)
w.write(byteArray.copyOfRange(0, 4))
val body = ByteArray(4 + bodySize)
System.arraycopy(Utils.IDAT, 0, body, 0, 4)
// Get image bytes
System.arraycopy(byteArray, 8, body, 4, bodySize)
val crC32 = CRC32()
crC32.update(body, 0, body.size)
w.write(body)
w.write(Utils.uIntToByteArray(crC32.value.toInt()))
} }
name.contentEquals(Utils.fdAT) -> { name.contentEquals(Utils.fdAT) -> {
// Find the chunk length // Find the chunk length
val bodySize = Utils.uIntFromBytesBigEndian(byteArray.copyOfRange(i - 4, i).map(Byte::toInt)) val bodySize = Utils.uIntFromBytesBigEndian(byteArray.copyOfRange(0, 4).map(Byte::toInt))
png?.addAll(Utils.uIntToByteArray(bodySize - 4).asList()) png?.write(Utils.uIntToByteArray(bodySize - 4))
val body = ArrayList<Byte>()
body.addAll(Utils.IDAT.asList()) val body = ByteArray(bodySize)
System.arraycopy(Utils.IDAT, 0, body, 0, 4)
// Get image bytes // Get image bytes
body.addAll(byteArray.copyOfRange(i + 8, i + 4 + bodySize).asList()) System.arraycopy(byteArray, 12, body, 4, bodySize - 4)
val crC32 = CRC32() val crC32 = CRC32()
crC32.update(body.toByteArray(), 0, body.size) crC32.update(body, 0, body.size)
png?.addAll(body) png?.write(body)
png?.addAll(Utils.uIntToByteArray(crC32.value.toInt()).asList()) png?.write(Utils.uIntToByteArray(crC32.value.toInt()))
} }
name.contentEquals(Utils.plte) -> { name.contentEquals(Utils.plte) -> {
plte = byteArray plte = byteArray
@ -494,12 +459,12 @@ 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(i - 4, i).map(Byte::toInt)) val bodySize = Utils.uIntFromBytesBigEndian(byteArray.copyOfRange(4 - 4, 4).map(Byte::toInt))
// Get the width of the png // Get the width of the png
maxWidth = Utils.uIntFromBytesBigEndian(byteArray.copyOfRange(i +4, i + 8).map(Byte::toInt)) maxWidth = Utils.uIntFromBytesBigEndian(byteArray.copyOfRange(4 +4, 4 + 8).map(Byte::toInt))
// Get the height of the png // Get the height of the png
maxHeight = Utils.uIntFromBytesBigEndian(byteArray.copyOfRange(i +8, i +12).map(Byte::toInt)) maxHeight = Utils.uIntFromBytesBigEndian(byteArray.copyOfRange(4 +8, 4 + 12).map(Byte::toInt))
ihdrOfApng = byteArray.copyOfRange(i + 4, i + bodySize + 4) ihdrOfApng = byteArray.copyOfRange(4 + 4, 4 + bodySize + 4)
buffer = Bitmap.createBitmap( buffer = Bitmap.createBitmap(
maxWidth, maxWidth,
@ -507,7 +472,7 @@ class ApngDecoder {
Bitmap.Config.ARGB_8888 Bitmap.Config.ARGB_8888
) )
} }
name.contentEquals(Utils.acTL) -> { name.contentEquals(Utils.acTL) -> { // TODO GET NBR REPETITIONS
isApng = true isApng = true
} }
} }

View File

@ -128,7 +128,6 @@ class KotlinFragment : Fragment() {
} }
apngImageView?.setImageDrawable(res) apngImageView?.setImageDrawable(res)
animation.invalidateSelf()
res.start() res.start()
res res
} }