Working on optimiser functionality to reduce APNG size.

Loader now static
Disassembler now static
Optimize imports
This commit is contained in:
oupson 2018-11-15 11:25:35 +01:00
parent 0150810541
commit 66055221f1
8 changed files with 219 additions and 528 deletions

View File

@ -1,9 +1,6 @@
package oupson.apng
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.util.Log
import oupson.apng.Utils.Companion.isApng
import oupson.apng.Utils.Companion.pngSignature
import oupson.apng.Utils.Companion.to4Bytes
@ -12,240 +9,227 @@ import oupson.apng.chunks.fcTL
import oupson.apng.exceptions.NotApngException
import java.util.zip.CRC32
class APNGDisassembler(val byteArray: ByteArray) {
val pngList = ArrayList<Frame>()
var png : ArrayList<Byte>? = null
var cover : ArrayList<Byte>? = null
var delay = -1f
var yOffset= -1
var xOffset = -1
var plte : ByteArray? = null
var tnrs : ByteArray? = null
var maxWidth = 0
var maxHeight = 0
var blend_op : Utils.Companion.blend_op = Utils.Companion.blend_op.APNG_BLEND_OP_SOURCE
var dispose_op : Utils.Companion.dispose_op= Utils.Companion.dispose_op.APNG_DISPOSE_OP_NONE
class APNGDisassembler() {
companion object {
var apng : Apng
fun disassemble(byteArray: ByteArray) : Apng {
val pngList = ArrayList<Frame>()
var png: ArrayList<Byte>? = null
var cover: ArrayList<Byte>? = null
var delay = -1f
var yOffset = -1
var xOffset = -1
var plte: ByteArray? = null
var tnrs: ByteArray? = null
var maxWidth = 0
var maxHeight = 0
var blend_op: Utils.Companion.blend_op = Utils.Companion.blend_op.APNG_BLEND_OP_SOURCE
var dispose_op: Utils.Companion.dispose_op = Utils.Companion.dispose_op.APNG_DISPOSE_OP_NONE
val apng: Apng
if (isApng(byteArray)) {
apng = Apng()
val ihdr = IHDR()
ihdr.parseIHDR(byteArray)
maxWidth = ihdr.pngWidth
maxHeight = ihdr.pngHeight
for (i in 0 until byteArray.size) {
// find new Frame with fcTL
if (byteArray[i] == 0x66.toByte() && byteArray[i + 1] == 0x63.toByte() && byteArray[i + 2] == 0x54.toByte() && byteArray[i + 3] == 0x4C.toByte()) {
if (png == null) {
if (cover != null) {
cover.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)
cover.addAll(iend.toList())
cover.addAll(to4Bytes(crC32.value.toInt()).toList())
init {
if (isApng(byteArray)) {
apng = Apng()
val ihdr = IHDR()
ihdr.parseIHDR(byteArray)
maxWidth = ihdr.pngWidth
maxHeight = ihdr.pngHeight
for(i in 0 until byteArray.size) {
// find new Frame with fcTL
if (byteArray[i] == 0x66.toByte() && byteArray[i + 1] == 0x63.toByte() && byteArray[ i + 2 ] == 0x54.toByte() && byteArray[ i + 3 ] == 0x4C.toByte()) {
if (png == null) {
if (cover != null) {
cover!!.addAll(to4Bytes(0).toList())
apng.cover = BitmapFactory.decodeByteArray(cover.toByteArray(), 0, cover.size)
}
png = ArrayList()
val fcTL = fcTL(byteArray.copyOfRange(i - 4, i + 36))
delay = fcTL.delay
yOffset = fcTL.y_offset
xOffset = fcTL.x_offset
blend_op = fcTL.blend_op
dispose_op = fcTL.dispose_op
val width = fcTL.pngWidth
val height = fcTL.pngHeight
png.addAll(pngSignature.toList())
png.addAll(generate_ihdr(ihdr, width, height).toList())
if (plte != null) {
png.addAll(plte.toList())
}
if (tnrs != null) {
png.addAll(tnrs.toList())
}
} else {
// Add IEND body length : 0
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)
cover!!.addAll(iend.toList())
cover!!.addAll(to4Bytes(crC32.value.toInt()).toList())
png.addAll(iend.toList())
png.addAll(to4Bytes(crC32.value.toInt()).toList())
pngList.add(Frame(png.toByteArray(), delay, xOffset, yOffset, maxWidth, maxHeight, blend_op, dispose_op))
apng.cover = BitmapFactory.decodeByteArray(cover!!.toByteArray(), 0, cover!!.size)
}
png = ArrayList()
val fcTL = fcTL(byteArray.copyOfRange(i - 4, i + 36))
delay = fcTL.delay
yOffset = fcTL.y_offset
xOffset = fcTL.x_offset
blend_op = fcTL.blend_op
dispose_op = fcTL.dispose_op
val width = fcTL.pngWidth
val height = fcTL.pngHeight
png!!.addAll(pngSignature.toList())
png!!.addAll(generate_ihdr(ihdr, width, height).toList())
png = ArrayList()
if (plte != null) {
png!!.addAll(plte!!.toList())
}
val bodySize = {
var lengthString = ""
byteArray.copyOfRange(i - 4, i).forEach {
lengthString += String.format("%02x", it)
}
lengthString.toLong(16).toInt()
}()
val newBytes = byteArray.copyOfRange(i - 4, i + 4 + bodySize)
val fcTL = fcTL(newBytes)
delay = fcTL.delay
if (tnrs != null) {
png!!.addAll(tnrs!!.toList())
yOffset = fcTL.y_offset
xOffset = fcTL.x_offset
blend_op = fcTL.blend_op
dispose_op = fcTL.dispose_op
val width = fcTL.pngWidth
val height = fcTL.pngHeight
png.addAll(pngSignature.toList())
png.addAll(generate_ihdr(ihdr, width, height).toList())
if (plte != null) {
png.addAll(plte.toList())
}
if (tnrs != null) {
png.addAll(tnrs.toList())
}
}
} else {
// Add IEND body length : 0
} else if (i == byteArray.size - 1) {
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())
pngList.add(Frame(png!!.toByteArray(), delay, xOffset, yOffset, maxWidth, maxHeight, blend_op, dispose_op))
png = ArrayList()
val bodySize = {
png.addAll(iend.toList())
png.addAll(to4Bytes(crC32.value.toInt()).toList())
pngList.add(Frame(png.toByteArray(), delay, xOffset, yOffset, maxWidth, maxHeight, blend_op, dispose_op))
}
// Check if is IDAT
else if (byteArray[i] == 0x49.toByte() && byteArray[i + 1] == 0x44.toByte() && byteArray[i + 2] == 0x41.toByte() && byteArray[i + 3] == 0x54.toByte()) {
if (png == null) {
if (cover == null) {
cover = ArrayList()
cover.addAll(pngSignature.toList())
cover.addAll(generate_ihdr(ihdr, maxWidth, maxHeight).toList())
}
// Find the chunk length
var lengthString = ""
byteArray.copyOfRange(i - 4, i).forEach {
lengthString += String.format("%02x", it)
}
lengthString.toLong(16).toInt()
}()
val newBytes = byteArray.copyOfRange(i - 4, i + 4 + bodySize)
val fcTL = fcTL(newBytes)
delay = fcTL.delay
yOffset = fcTL.y_offset
xOffset = fcTL.x_offset
blend_op = fcTL.blend_op
dispose_op = fcTL.dispose_op
val width = fcTL.pngWidth
val height = fcTL.pngHeight
png!!.addAll(pngSignature.toList())
png!!.addAll(generate_ihdr(ihdr, width, height).toList())
if (plte != null) {
png!!.addAll(plte!!.toList())
}
if (tnrs != null) {
png!!.addAll(tnrs!!.toList())
val bodySize = lengthString.toLong(16).toInt()
cover.addAll(byteArray.copyOfRange(i - 4, i).toList())
val body = ArrayList<Byte>()
body.addAll(byteArrayOf(0x49, 0x44, 0x41, 0x54).toList())
// Get image bytes
for (j in i + 4 until i + 4 + bodySize) {
body.add(byteArray[j])
}
val crC32 = CRC32()
crC32.update(body.toByteArray(), 0, body.size)
cover.addAll(body)
cover.addAll(to4Bytes(crC32.value.toInt()).toList())
} else {
// Find the chunk length
var lengthString = ""
byteArray.copyOfRange(i - 4, i).forEach {
lengthString += String.format("%02x", it)
}
val bodySize = lengthString.toLong(16).toInt()
png.addAll(byteArray.copyOfRange(i - 4, i).toList())
val body = ArrayList<Byte>()
body.addAll(byteArrayOf(0x49, 0x44, 0x41, 0x54).toList())
// Get image bytes
for (j in i + 4 until i + 4 + bodySize) {
body.add(byteArray[j])
}
val crC32 = CRC32()
crC32.update(body.toByteArray(), 0, body.size)
png.addAll(body)
png.addAll(to4Bytes(crC32.value.toInt()).toList())
}
}
}
else if (i == byteArray.size - 1) {
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())
pngList.add(Frame(png!!.toByteArray(), delay, xOffset, yOffset, maxWidth, maxHeight, blend_op, dispose_op))
}
// Check if is IDAT
else if (byteArray[i] == 0x49.toByte() && byteArray[i + 1] == 0x44.toByte() && byteArray[ i + 2 ] == 0x41.toByte() && byteArray[ i + 3 ] == 0x54.toByte()) {
if (png == null) {
if (cover == null) {
cover = ArrayList()
cover!!.addAll(pngSignature.toList())
cover!!.addAll(generate_ihdr(ihdr, maxWidth, maxHeight).toList())
}
// Check if is fdAT
else if (byteArray[i] == 0x66.toByte() && byteArray[i + 1] == 0x64.toByte() && byteArray[i + 2] == 0x41.toByte() && byteArray[i + 3] == 0x54.toByte()) {
// Find the chunk length
var lengthString = ""
byteArray.copyOfRange( i - 4, i).forEach {
byteArray.copyOfRange(i - 4, i).forEach {
lengthString += String.format("%02x", it)
}
val bodySize = lengthString.toLong(16).toInt()
cover!!.addAll(byteArray.copyOfRange(i-4, i).toList())
png!!.addAll(to4Bytes(bodySize - 4).toList())
val body = ArrayList<Byte>()
body.addAll(byteArrayOf(0x49, 0x44, 0x41, 0x54).toList())
// Get image bytes
for (j in i +4 until i + 4 + bodySize) {
for (j in i + 8 until i + 4 + bodySize) {
body.add(byteArray[j])
}
val crC32 = CRC32()
crC32.update(body.toByteArray(), 0, body.size)
cover!!.addAll(body)
cover!!.addAll(to4Bytes(crC32.value.toInt()).toList())
} else {
// Find the chunk length
png.addAll(body)
png.addAll(to4Bytes(crC32.value.toInt()).toList())
}
// Get plte chunks if exist. The PLTE chunk contains from 1 to 256 palette entries, each a three-byte series of the form:
else if (byteArray[i] == 0x50.toByte() && byteArray[i + 1] == 0x4C.toByte() && byteArray[i + 2] == 0x54.toByte() && byteArray[i + 3] == 0x45.toByte()) {
var lengthString = ""
byteArray.copyOfRange( i - 4, i).forEach {
byteArray.copyOfRange(i - 4, i).forEach {
lengthString += String.format("%02x", it)
}
val bodySize = lengthString.toLong(16).toInt()
png!!.addAll(byteArray.copyOfRange(i-4, i).toList())
val body = ArrayList<Byte>()
body.addAll(byteArrayOf(0x49, 0x44, 0x41, 0x54).toList())
// Get image bytes
for (j in i +4 until i + 4 + bodySize) {
body.add(byteArray[j])
plte = byteArray.copyOfRange(i - 4, i + 8 + bodySize)
}
// Get tnrs chunk if exist. Used for transparency
else if (byteArray[i] == 0x74.toByte() && byteArray[i + 1] == 0x52.toByte() && byteArray[i + 2] == 0x4E.toByte() && byteArray[i + 3] == 0x53.toByte()) {
var lengthString = ""
byteArray.copyOfRange(i - 4, i).forEach {
lengthString += String.format("%02x", it)
}
val crC32 = CRC32()
crC32.update(body.toByteArray(), 0, body.size)
png!!.addAll(body)
png!!.addAll(to4Bytes(crC32.value.toInt()).toList())
val bodySize = lengthString.toLong(16).toInt()
tnrs = byteArray.copyOfRange(i - 4, i + 8 + bodySize)
}
}
// Check if is fdAT
else if (byteArray[i] == 0x66.toByte() && byteArray[i + 1] == 0x64.toByte() && byteArray[ i + 2 ] == 0x41.toByte() && byteArray[ i + 3 ] == 0x54.toByte()) {
// Find the chunk length
var lengthString = ""
byteArray.copyOfRange( i - 4, i).forEach {
lengthString += String.format("%02x", it)
}
val bodySize = lengthString.toLong(16).toInt()
png!!.addAll(to4Bytes(bodySize - 4).toList())
val body = ArrayList<Byte>()
body.addAll(byteArrayOf(0x49, 0x44, 0x41, 0x54).toList())
// Get image bytes
for (j in i + 8 until i + 4 + bodySize) {
body.add(byteArray[j])
}
val crC32 = CRC32()
crC32.update(body.toByteArray(), 0, body.size)
png!!.addAll(body)
png!!.addAll(to4Bytes(crC32.value.toInt()).toList())
}
// Get plte chunks if exist. The PLTE chunk contains from 1 to 256 palette entries, each a three-byte series of the form:
else if (byteArray[i] == 0x50.toByte() && byteArray[i + 1] == 0x4C.toByte() && byteArray[ i + 2 ] == 0x54.toByte() && byteArray[ i + 3 ] == 0x45.toByte()) {
var lengthString = ""
byteArray.copyOfRange( i - 4, i).forEach {
lengthString += String.format("%02x", it)
}
val bodySize = lengthString.toLong(16).toInt()
plte = byteArray.copyOfRange( i -4, i + 8 + bodySize)
}
// Get tnrs chunk if exist. Used for transparency
else if (byteArray[i] == 0x74.toByte() && byteArray[i + 1] == 0x52.toByte() && byteArray[ i + 2 ] == 0x4E.toByte() && byteArray[ i + 3 ] == 0x53.toByte()) {
var lengthString = ""
byteArray.copyOfRange( i - 4, i).forEach {
lengthString += String.format("%02x", it)
}
val bodySize = lengthString.toLong(16).toInt()
tnrs = byteArray.copyOfRange( i -4, i + 8 + bodySize)
}
apng.frames = pngList
return apng
} else {
throw NotApngException()
}
apng.frames = pngList
} else {
throw NotApngException()
}
}
private fun generate_ihdr(ihdrOfApng: IHDR, width : Int, height : Int) : ByteArray {
val ihdr = ArrayList<Byte>()
// We need a body var to know body length and generate crc
val ihdr_body = ArrayList<Byte>()
// Add chunk body length
ihdr.addAll(to4Bytes(ihdrOfApng.ihdrCorps.size).toList())
// Add IHDR
ihdr_body.addAll(byteArrayOf(0x49.toByte(), 0x48.toByte(), 0x44.toByte(), 0x52.toByte()).toList())
// Add the max width and height
ihdr_body.addAll(to4Bytes(width).toList())
ihdr_body.addAll(to4Bytes(height).toList())
// Add complicated stuff like depth color ...
// If you want correct png you need same parameters. Good solution is to create new png.
ihdr_body.addAll(ihdrOfApng.ihdrCorps.copyOfRange(8, 13).toList())
// Generate CRC
val crC32 = CRC32()
crC32.update(ihdr_body.toByteArray(), 0, ihdr_body.size)
ihdr.addAll(ihdr_body)
ihdr.addAll(to4Bytes(crC32.value.toInt()).toList())
return ihdr.toByteArray()
}
fun genBitmap() : ArrayList<Bitmap> {
val generatedFrame = ArrayList<Bitmap>()
pngList.forEach {
val btm = Bitmap.createBitmap(it.maxWidth!!, it.maxHeight!!, Bitmap.Config.ARGB_8888)
val canvas = Canvas(btm)
canvas.drawBitmap(BitmapFactory.decodeByteArray(it.byteArray, 0, it.byteArray.size), it.x_offsets!!.toFloat(), it.y_offsets!!.toFloat(), null)
generatedFrame.add(btm)
private fun generate_ihdr(ihdrOfApng: IHDR, width : Int, height : Int) : ByteArray {
val ihdr = ArrayList<Byte>()
// We need a body var to know body length and generate crc
val ihdr_body = ArrayList<Byte>()
// Add chunk body length
ihdr.addAll(to4Bytes(ihdrOfApng.ihdrCorps.size).toList())
// Add IHDR
ihdr_body.addAll(byteArrayOf(0x49.toByte(), 0x48.toByte(), 0x44.toByte(), 0x52.toByte()).toList())
// Add the max width and height
ihdr_body.addAll(to4Bytes(width).toList())
ihdr_body.addAll(to4Bytes(height).toList())
// Add complicated stuff like depth color ...
// If you want correct png you need same parameters. Good solution is to create new png.
ihdr_body.addAll(ihdrOfApng.ihdrCorps.copyOfRange(8, 13).toList())
// Generate CRC
val crC32 = CRC32()
crC32.update(ihdr_body.toByteArray(), 0, ihdr_body.size)
ihdr.addAll(ihdr_body)
ihdr.addAll(to4Bytes(crC32.value.toInt()).toList())
return ihdr.toByteArray()
}
return generatedFrame
}
}

View File

@ -3,7 +3,6 @@ package oupson.apng
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import oupson.apng.ImageUtils.PnnQuantizer
import oupson.apng.Utils.Companion.convertImage
import oupson.apng.Utils.Companion.getBlend_op
import oupson.apng.Utils.Companion.getDispose_op
import oupson.apng.Utils.Companion.pngSignature
@ -12,7 +11,6 @@ import oupson.apng.Utils.Companion.to4Bytes
import oupson.apng.Utils.Companion.toByteArray
import oupson.apng.chunks.IDAT
import oupson.apng.exceptions.NoFrameException
import java.io.ByteArrayOutputStream
import java.util.zip.CRC32

View File

@ -2,7 +2,6 @@ package oupson.apng
import android.content.Context
import android.graphics.*
import android.os.Handler
import android.widget.ImageView
import org.jetbrains.anko.doAsync
import org.jetbrains.anko.uiThread
@ -13,7 +12,7 @@ import java.net.URL
/**
* Class to play APNG
*/
class ApngAnimator(val context: Context) {
class ApngAnimator(private val context: Context) {
var isPlaying = true
private set(value) {
field = value
@ -54,7 +53,7 @@ class ApngAnimator(val context: Context) {
doAsync {
this@ApngAnimator.speed = speed
// Download PNG
APNGDisassembler(file.readBytes()).pngList.apply {
APNGDisassembler.disassemble(file.readBytes()).frames.apply {
draw(this)
}
setupAnimationDrawableAndStart()
@ -71,7 +70,7 @@ class ApngAnimator(val context: Context) {
doAsync(exceptionHandler = { e -> e.printStackTrace() }) {
this@ApngAnimator.speed = speed
// Download PNG
APNGDisassembler(Loader().load(context, url)).pngList.apply {
APNGDisassembler.disassemble(Loader.load(context, url)).frames.apply {
draw(this)
}
setupAnimationDrawableAndStart()
@ -87,7 +86,7 @@ class ApngAnimator(val context: Context) {
fun load(byteArray: ByteArray, speed: Float? = null) {
doAsync {
this@ApngAnimator.speed = speed
APNGDisassembler(byteArray).pngList.apply {
APNGDisassembler.disassemble(byteArray).frames.apply {
draw(this)
}
setupAnimationDrawableAndStart()

View File

@ -1,287 +0,0 @@
package oupson.apng
import oupson.apng.Utils.Companion.pngSignature
import oupson.apng.exceptions.NoFrameException
import java.util.zip.CRC32
/**
* APNG is a class for create apng
*
* Call .addFrame() to add a Frame
* Call .create to get the generated file
*
* @author oupson
*
* @throws NotPngException
* @throws NoFrameException
*
*/
class ApngFactory {
private var seq = 0
var frames = ArrayList<Frame>()
/**
* @return a byte array of the generated png
*
* @throws NoFrameException
*/
fun create(): ByteArray {
if (frames.isNotEmpty()) {
val res = ArrayList<Byte>()
// Add PNG signature
res.addAll(pngSignature.toList())
// Add Image Header
res.addAll(generate_ihdr().toList())
// Add Animation Controller
res.addAll(generateACTL())
// Get max height and max width
val maxHeight = frames.sortedByDescending { it.height }[0].height
val maxWitdh = frames.sortedByDescending { it.width }[0].width
for (i in 0 until frames.size) {
// If it's the first frame
if (i == 0) {
val framesByte = ArrayList<Byte>()
// region fcTL
// Create the fcTL
val fcTL = ArrayList<Byte>()
// Add the length of the chunk body
framesByte.addAll(byteArrayOf(0x00, 0x00, 0x00, 0x1A).toList())
// Add acTL
fcTL.addAll(byteArrayOf(0x66, 0x63, 0x54, 0x4c).toList())
// Add the frame number
fcTL.addAll(to4Bytes(seq).toList())
seq++
// Add width and height
fcTL.addAll(to4Bytes(frames[i].width).toList())
fcTL.addAll(to4Bytes(frames[i].height).toList())
// Calculate offset
if (frames[i].width < maxWitdh) {
val xOffset = (maxWitdh / 2) - (frames[i].width / 2)
fcTL.addAll(to4Bytes(xOffset).toList())
} else {
fcTL.addAll(to4Bytes(0).toList())
}
if (frames[i].height < maxHeight) {
val xOffset = (maxHeight / 2) - (frames[i].height / 2)
fcTL.addAll(to4Bytes(xOffset).toList())
} else {
fcTL.addAll(to4Bytes(0).toList())
}
// Set frame delay
fcTL.addAll(to2Bytes(frames[i].delay.toInt()).toList())
fcTL.addAll(to2Bytes(1000).toList())
fcTL.add(0x01)
fcTL.add(0x00)
val crc = CRC32()
crc.update(fcTL.toByteArray(), 0, fcTL.size)
framesByte.addAll(fcTL)
framesByte.addAll(to4Bytes(crc.value.toInt()).toList())
// endregion
// region idat
frames[i].idat.IDATBody.forEach {
val fdat = ArrayList<Byte>()
// Add the chunk body length
framesByte.addAll(to4Bytes(it.size).toList())
// Add IDAT
fdat.addAll(byteArrayOf(0x49, 0x44, 0x41, 0x54).toList())
// Add chunk body
fdat.addAll(it.toList())
// Generate CRC
val crc1 = CRC32()
crc1.update(fdat.toByteArray(), 0, fdat.size)
framesByte.addAll(fdat)
framesByte.addAll(to4Bytes(crc1.value.toInt()).toList())
}
// endregion
res.addAll(framesByte)
} else {
val framesByte = ArrayList<Byte>()
val fcTL = ArrayList<Byte>()
// region fcTL
framesByte.addAll(byteArrayOf(0x00, 0x00, 0x00, 0x1A).toList())
fcTL.addAll(byteArrayOf(0x66, 0x63, 0x54, 0x4c).toList())
// Frame number
fcTL.addAll(to4Bytes(seq).toList())
seq++
// width and height
fcTL.addAll(to4Bytes(frames[i].width).toList())
fcTL.addAll(to4Bytes(frames[i].height).toList())
if (frames[i].width < maxWitdh) {
val xOffset = (maxWitdh / 2) - (frames[i].width / 2)
fcTL.addAll(to4Bytes(xOffset).toList())
} else {
fcTL.addAll(to4Bytes(0).toList())
}
if (frames[i].height < maxHeight) {
val xOffset = (maxHeight / 2) - (frames[i].height / 2)
fcTL.addAll(to4Bytes(xOffset).toList())
} else {
fcTL.addAll(to4Bytes(0).toList())
}
// Set frame delay
fcTL.addAll(to2Bytes(frames[i].delay.toInt()).toList())
fcTL.addAll(to2Bytes(1000).toList())
fcTL.add(0x01)
fcTL.add(0x00)
val crc = CRC32()
crc.update(fcTL.toByteArray(), 0, fcTL.size)
framesByte.addAll(fcTL)
framesByte.addAll(to4Bytes(crc.value.toInt()).toList())
// endregion
// region fdAT
// Write fdAT
frames[i].idat.IDATBody.forEach {
val fdat = ArrayList<Byte>()
// Add IDAT size of frame + 4 byte of the seq
framesByte.addAll(to4Bytes(it.size + 4).toList())
// Add fdAT
fdat.addAll(byteArrayOf(0x66, 0x64, 0x41, 0x54).toList())
// Add Sequence number
// ! THIS IS NOT FRAME NUMBER
fdat.addAll(to4Bytes(seq).toList())
// Increase seq
seq++
fdat.addAll(it.toList())
// Generate CRC
val crc1 = CRC32()
crc1.update(fdat.toByteArray(), 0, fdat.size)
framesByte.addAll(fdat)
framesByte.addAll(to4Bytes(crc1.value.toInt()).toList())
}
// endregion
res.addAll(framesByte)
}
}
// Add IEND body length : 0
res.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)
res.addAll(iend.toList())
res.addAll(to4Bytes(crC32.value.toInt()).toList())
return res.toByteArray()
} else {
throw NoFrameException()
}
}
// Animation Control chunk
private fun generateACTL(): ArrayList<Byte> {
val res = ArrayList<Byte>()
val actl = ArrayList<Byte>()
// Add length bytes
res.addAll(to4Bytes(8).toList())
// Add acTL
actl.addAll(byteArrayOf(0x61, 0x63, 0x54, 0x4c).toList())
// Add number of frames
actl.addAll(to4Bytes(frames.size).toList())
// Number of repeat, 0 to infinite
actl.addAll(to4Bytes(0).toList())
res.addAll(actl)
// generate crc
val crc = CRC32()
crc.update(actl.toByteArray(), 0, actl.size)
res.addAll(to4Bytes(crc.value.toInt()).toList())
return res
}
/**
* Generate a 4 bytes array from an Int
* @param i The int
*/
fun to4Bytes(i: Int): ByteArray {
val result = ByteArray(4)
result[0] = (i shr 24).toByte()
result[1] = (i shr 16).toByte()
result[2] = (i shr 8).toByte()
result[3] = i /*>> 0*/.toByte()
return result
}
/**
* Generate a 2 bytes array from an Int
* @param i The int
*/
fun to2Bytes(i: Int): ByteArray {
val result = ByteArray(2)
result[0] = (i shr 8).toByte()
result[1] = i /*>> 0*/.toByte()
return result
}
/**
* Add a frame to the Animated PNG
*
* @param byteArray It's the byteArray of a png
* @param delay Delay in MS between this frame and the next
*/
fun addFrame(byteArray: ByteArray, delay: Float = 1000f) {
frames.add(Frame(byteArray, delay))
}
// Generate Image Header chunk
private fun generate_ihdr(): ByteArray {
val ihdr = ArrayList<Byte>()
// We need a body var to know body length and generate crc
val ihdr_body = ArrayList<Byte>()
// Get max height and max width of all the frames
val maxHeight = frames.sortedByDescending { it.height }[0].height
val maxWitdh = frames.sortedByDescending { it.width }[0].width
// Add chunk body length
ihdr.addAll(to4Bytes(frames[0].ihdr.ihdrCorps.size).toList())
// Add IHDR
ihdr_body.addAll(byteArrayOf(0x49.toByte(), 0x48.toByte(), 0x44.toByte(), 0x52.toByte()).toList())
// Add the max width and height
ihdr_body.addAll(to4Bytes(maxWitdh).toList())
ihdr_body.addAll(to4Bytes(maxHeight).toList())
// Add complicated stuff like depth color ...
// If you want correct png you need same parameters. Good solution is to create new png.
ihdr_body.addAll(frames[0].ihdr.ihdrCorps.copyOfRange(8, 13).toList())
// Generate CRC
val crC32 = CRC32()
crC32.update(ihdr_body.toByteArray(), 0, ihdr_body.size)
ihdr.addAll(ihdr_body)
ihdr.addAll(to4Bytes(crC32.value.toInt()).toList())
return ihdr.toByteArray()
}
}

View File

@ -1,7 +1,6 @@
package oupson.apng
import android.graphics.BitmapFactory
import oupson.apng.Utils.Companion.convertImage
import oupson.apng.Utils.Companion.isPng
import oupson.apng.Utils.Companion.toByteArray
import oupson.apng.chunks.IDAT

View File

@ -20,13 +20,13 @@ import java.util.Map;
import java.util.Random;
public class PnnQuantizer {
protected final short SHORT_MAX = Short.MAX_VALUE;
protected final char BYTE_MAX = -Byte.MIN_VALUE + Byte.MAX_VALUE;
protected boolean hasTransparency = false, hasSemiTransparency = false;
private final short SHORT_MAX = Short.MAX_VALUE;
private final char BYTE_MAX = -Byte.MIN_VALUE + Byte.MAX_VALUE;
private boolean hasTransparency = false, hasSemiTransparency = false;
protected int width, height;
protected int pixels[] = null;
protected Integer m_transparentColor;
protected Map<Integer, short[]> closestMap = new HashMap<>();
private Integer m_transparentColor;
private Map<Integer, short[]> closestMap = new HashMap();
public PnnQuantizer(String fname) throws IOException {
fromBitmap(fname);
@ -54,7 +54,7 @@ public class PnnQuantizer {
int nn, fw, bk, tm, mtm;
}
protected int getColorIndex(final int c)
private int getColorIndex(final int c)
{
if(hasSemiTransparency)
return (Color.alpha(c) & 0xF0) << 8 | (Color.red(c) & 0xF0) << 4 | (Color.green(c) & 0xF0) | (Color.blue(c) >> 4);
@ -63,7 +63,7 @@ public class PnnQuantizer {
return (Color.red(c) & 0xF8) << 8 | (Color.green(c) & 0xFC) << 3 | (Color.blue(c) >> 3);
}
protected double sqr(double value)
private double sqr(double value)
{
return value * value;
}
@ -412,7 +412,7 @@ public class PnnQuantizer {
return true;
}
protected Bitmap quantize_image(final int[] pixels, int[] qPixels)
private Bitmap quantize_image(final int[] pixels, int[] qPixels)
{
int pixelIndex = 0;
boolean odd_scanline = false;

View File

@ -8,31 +8,33 @@ import java.io.IOException
import java.net.URL
class Loader {
fun load( context: Context, url : URL) : ByteArray {
val currenDir = context.filesDir
val fileTXT = File(currenDir, "apngLoader.txt")
var filePNG = File(currenDir, "apngLoader.png")
if (fileTXT.exists() && url.toString() == fileTXT.readText()) {
return filePNG.readBytes()
} else {
try {
val connection = url.openConnection()
connection.connect()
val input = BufferedInputStream(connection.getInputStream())
val output = ByteArrayOutputStream()
val data = ByteArray(1024)
var count = 0
while ({ count = input.read(data); count }() != -1) {
output.write(data, 0, count)
companion object {
fun load(context: Context, url: URL): ByteArray {
val currenDir = context.filesDir
val fileTXT = File(currenDir, "apngLoader.txt")
var filePNG = File(currenDir, "apngLoader.png")
if (fileTXT.exists() && url.toString() == fileTXT.readText()) {
return filePNG.readBytes()
} else {
try {
val connection = url.openConnection()
connection.connect()
val input = BufferedInputStream(connection.getInputStream())
val output = ByteArrayOutputStream()
val data = ByteArray(1024)
var count = 0
while ({ count = input.read(data); count }() != -1) {
output.write(data, 0, count)
}
output.flush()
output.close()
input.close()
fileTXT.writeText(url.toString())
filePNG.writeBytes(output.toByteArray())
return output.toByteArray()
} catch (e: IOException) {
throw e
}
output.flush()
output.close()
input.close()
fileTXT.writeText(url.toString())
filePNG.writeBytes(output.toByteArray())
return output.toByteArray()
} catch (e: IOException) {
throw e
}
}
}

View File

@ -1,6 +1,5 @@
package oupson.apngcreator
import android.graphics.BitmapFactory
import android.os.Bundle
import android.os.Environment
import android.support.v7.app.AppCompatActivity
@ -8,14 +7,11 @@ import android.util.Log
import android.widget.SeekBar
import com.squareup.picasso.Picasso
import kotlinx.android.synthetic.main.activity_main.*
import oupson.apng.ApngAnimator
import android.widget.Toast
import org.jetbrains.anko.doAsync
import org.jetbrains.anko.toast
import oupson.apng.APNGDisassembler
import oupson.apng.Apng
import oupson.apng.ApngAnimator
import oupson.apng.Loader
import oupson.apngcreator.R.id.imageView
import java.io.File
import java.net.URL
@ -40,9 +36,9 @@ class MainActivity : AppCompatActivity() {
doAsync {
Loader().load(applicationContext, URL(imageUrl)).apply {
val a = APNGDisassembler(this).apng
a.reduceSize(100, false, 75)
Loader.load(applicationContext, URL(imageUrl)).apply {
val a = APNGDisassembler.disassemble(this)
a.reduceSize(100, false, 60)
File(File(Environment.getExternalStorageDirectory(), "Documents"), "apng.png").writeBytes(a.toByteArray())
runOnUiThread {
toast("Converted ! ")