Fix bad behavior with cover image

Add KDOC
This commit is contained in:
oupson 2018-10-24 11:18:07 +02:00
parent f84419a6c0
commit efbe651377
11 changed files with 167 additions and 79 deletions

View File

@ -32,7 +32,7 @@ dependencies {
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "org.jetbrains.anko:anko:0.10.6"
implementation "org.jetbrains.anko:anko:0.10.7"
}
repositories {
mavenCentral()

View File

@ -3,8 +3,8 @@ package oupson.apng
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.util.Log
import oupson.apng.ApngFactory.Companion.pngSignature
import oupson.apng.Utils.Companion.to4Bytes
import java.util.zip.CRC32
class APNGDisassembler(val byteArray: ByteArray) {
@ -105,11 +105,10 @@ class APNGDisassembler(val byteArray: ByteArray) {
// 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) {
png = ArrayList()
if (cover == null) {
cover = ArrayList()
png!!.addAll(pngSignature.toList())
png!!.addAll(generate_ihdr(ihdr, maxWidth, maxHeight).toList())
cover!!.addAll(pngSignature.toList())
cover!!.addAll(generate_ihdr(ihdr, maxWidth, maxHeight).toList())
}
// Find the chunk length
var lengthString = ""
@ -222,30 +221,11 @@ class APNGDisassembler(val byteArray: ByteArray) {
return ihdr.toByteArray()
}
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
}
fun genBitmap() : ArrayList<Bitmap> {
val generatedFrame = ArrayList<Bitmap>()
pngList.forEach {
val btm = Bitmap.createBitmap(it.maxWidth, it.maxHeight, Bitmap.Config.ARGB_8888)
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)

View File

@ -11,9 +11,11 @@ import oupson.apng.Utils.Companion.to4Bytes
import oupson.apng.Utils.Companion.toByteArray
import java.util.zip.CRC32
class Apng {
private var seq = 0
/**
* Create an APNG file
*/
class Apng {
var maxWidth : Int? = null
var maxHeight : Int? = null
@ -25,30 +27,101 @@ class Apng {
var frames : ArrayList<Frame> = ArrayList()
init {
}
// region addFrames
/**
* Add a frame to the APNG
* @param bitmap The bitamp to add
*/
fun addFrames(bitmap: Bitmap) {
frames.add(Frame(toByteArray(bitmap)))
}
/**
* Add a frame to the APNG
* @param bitmap The bitamp to add
* @param delay Delay of the frame
*/
fun addFrames(bitmap: Bitmap, delay : Float) {
frames.add(Frame(toByteArray(bitmap), delay))
}
fun addFrames(bitmap: Bitmap, delay: Float, blend_op: Utils.Companion.blend_op, dispose_op: Utils.Companion.dispose_op) {
/**
* Add a frame to the APNG
* @param bitmap The bitamp to add
* @param delay Delay of the frame
* @param dispose_op `dispose_op` specifies how the output buffer should be changed at the end of the delay (before rendering the next frame).
* @param blend_op `blend_op` specifies whether the frame is to be alpha blended into the current output buffer content, or whether it should completely replace its region in the output buffer.
*/
fun addFrames(bitmap: Bitmap, delay: Float, dispose_op: Utils.Companion.dispose_op, blend_op: Utils.Companion.blend_op) {
frames.add(Frame(toByteArray(bitmap), delay, blend_op, dispose_op))
}
fun addFrames(bitmap: Bitmap, delay: Float, xOffset : Int, yOffset : Int, blend_op: Utils.Companion.blend_op, dispose_op: Utils.Companion.dispose_op) {
/**
* Add a frame to the APNG
* @param bitmap The bitamp to add
* @param delay Delay of the frame
* @param xOffset The X offset where the frame should be rendered
* @param yOffset The Y offset where the frame should be rendered
* @param dispose_op `dispose_op` specifies how the output buffer should be changed at the end of the delay (before rendering the next frame).
* @param blend_op `blend_op` specifies whether the frame is to be alpha blended into the current output buffer content, or whether it should completely replace its region in the output buffer.
*/
fun addFrames(bitmap: Bitmap, delay: Float, xOffset : Int, yOffset : Int, dispose_op: Utils.Companion.dispose_op, blend_op: Utils.Companion.blend_op) {
frames.add(Frame(toByteArray(bitmap), delay, xOffset, yOffset, blend_op, dispose_op))
}
/**
* Add a frame to the APNG
* @param index Index where we add the frame
* @param bitmap The bitamp to add
*/
fun addFrames(index : Int, bitmap: Bitmap) {
frames.add(index, Frame(toByteArray(bitmap)))
}
/**
* Add a frame to the APNG
* @param index Index where we add the frame
* @param bitmap The bitamp to add
* @param delay Delay of the frame
*/
fun addFrames(index : Int, bitmap: Bitmap, delay : Float) {
frames.add(index, Frame(toByteArray(bitmap), delay))
}
/**
* Add a frame to the APNG
* @param index Index where we add the frame
* @param bitmap The bitamp to add
* @param delay Delay of the frame
* @param dispose_op `dispose_op` specifies how the output buffer should be changed at the end of the delay (before rendering the next frame).
* @param blend_op `blend_op` specifies whether the frame is to be alpha blended into the current output buffer content, or whether it should completely replace its region in the output buffer.
*/
fun addFrames(index: Int, bitmap: Bitmap, delay: Float, dispose_op: Utils.Companion.dispose_op, blend_op: Utils.Companion.blend_op) {
frames.add(index, Frame(toByteArray(bitmap), delay, blend_op, dispose_op))
}
/**
* Add a frame to the APNG
* @param index Index where we add the frame
* @param bitmap The bitamp to add
* @param delay Delay of the frame
* @param xOffset The X offset where the frame should be rendered
* @param yOffset The Y offset where the frame should be rendered
* @param dispose_op `dispose_op` specifies how the output buffer should be changed at the end of the delay (before rendering the next frame).
* @param blend_op `blend_op` specifies whether the frame is to be alpha blended into the current output buffer content, or whether it should completely replace its region in the output buffer.
*/
fun addFrames(index: Int, bitmap: Bitmap, delay: Float, xOffset : Int, yOffset : Int, dispose_op: Utils.Companion.dispose_op, blend_op: Utils.Companion.blend_op) {
frames.add(index, Frame(toByteArray(bitmap), delay, xOffset, yOffset, blend_op, dispose_op))
}
//endregion
fun generateAPNGByteArray() : ByteArray {
seq = 0
/**
* Generate a Bytes Array of the APNG
* @return The Bytes Array of the APNG
*/
fun toByteArray() : ByteArray {
var seq = 0
val res = ArrayList<Byte>()
// Add PNG signature
res.addAll(ApngFactory.pngSignature.toList())

View File

@ -4,9 +4,11 @@ import android.graphics.*
import android.graphics.drawable.AnimationDrawable
import android.os.Environment
import android.os.Handler
import android.util.Log
import android.widget.ImageView
import org.jetbrains.anko.doAsync
import org.jetbrains.anko.uiThread
import oupson.apng.Utils.Companion.toByteArray
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
@ -65,7 +67,7 @@ class ApngAnimator {
val it = Frames.get(i)
// Current bitmap for the frame
val btm = Bitmap.createBitmap(Frames[0].maxWidth, Frames[0].maxHeight, Bitmap.Config.ARGB_8888)
val btm = Bitmap.createBitmap(Frames[0].maxWidth!!, Frames[0].maxHeight!!, Bitmap.Config.ARGB_8888)
val canvas = Canvas(btm)
val current = BitmapFactory.decodeByteArray(it.byteArray, 0, it.byteArray.size).copy(Bitmap.Config.ARGB_8888, true)
@ -92,7 +94,7 @@ class ApngAnimator {
// Add current frame to bitmap buffer
// APNG_DISPOSE_OP_BACKGROUND: the frame's region of the output buffer is to be cleared to fully transparent black before rendering the next frame.
else if (it.dispose_op == Utils.Companion.dispose_op.APNG_DISPOSE_OP_BACKGROUND){
val res = Bitmap.createBitmap(Frames[0].maxWidth, Frames[0].maxHeight, Bitmap.Config.ARGB_8888)
val res = Bitmap.createBitmap(Frames[0].maxWidth!!, Frames[0].maxHeight!!, Bitmap.Config.ARGB_8888)
val can = Canvas(res)
can.drawBitmap(btm, 0f, 0f, null)
can.drawRect(lastFrame!!.x_offsets!!.toFloat(), lastFrame!!.y_offsets!!.toFloat(), lastFrame!!.x_offsets!! + lastFrame!!.width.toFloat(), lastFrame!!.y_offsets!! + lastFrame!!.height.toFloat(), { val paint = Paint(); paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR); paint }())
@ -123,19 +125,36 @@ class ApngAnimator {
// Download PNG
val extractedFrame = APNGDisassembler(Loader().load(url)).pngList
// DEBUG FUNCTION : WRITE RENDERED FRAME TO EXTERNAL STORAGE
if (isDebug) {
for (i in 0 until extractedFrame.size) {
try {
FileOutputStream(File(File(Environment.getExternalStorageDirectory(), "Documents"), "image_$i.png")).use { out ->
out.write(extractedFrame[i].byteArray)
// PNG is a lossless format, the compression factor (100) is ignored
}
} catch (e: IOException) {
e.printStackTrace()
}
}
}
// Set last frame
lastFrame = extractedFrame[0]
// Init image buffer
bitmapBuffer = BitmapFactory.decodeByteArray(lastFrame?.byteArray, 0, lastFrame?.byteArray!!.size)
bitmapBuffer = BitmapFactory.decodeByteArray(lastFrame?.byteArray!!, 0, lastFrame?.byteArray!!.size)
Log.e("ApngAnimator", "bitmapBuffer is null : ${bitmapBuffer == null}")
generatedFrame.add(BitmapFactory.decodeByteArray(lastFrame?.byteArray, 0, lastFrame?.byteArray!!.size))
Frames = extractedFrame
for (i in 1 until Frames.size) {
Log.e("ApngAnimator", "Render $i frame")
// Iterator
val it = Frames.get(i)
// Current bitmap for the frame
val btm = Bitmap.createBitmap(Frames[0].maxWidth, Frames[0].maxHeight, Bitmap.Config.ARGB_8888)
val btm = Bitmap.createBitmap(Frames[0].maxWidth!!, Frames[0].maxHeight!!, Bitmap.Config.ARGB_8888)
val canvas = Canvas(btm)
val current = BitmapFactory.decodeByteArray(it.byteArray, 0, it.byteArray.size).copy(Bitmap.Config.ARGB_8888, true)
@ -162,7 +181,7 @@ class ApngAnimator {
// Add current frame to bitmap buffer
// APNG_DISPOSE_OP_BACKGROUND: the frame's region of the output buffer is to be cleared to fully transparent black before rendering the next frame.
else if (it.dispose_op == Utils.Companion.dispose_op.APNG_DISPOSE_OP_BACKGROUND){
val res = Bitmap.createBitmap(Frames[0].maxWidth, Frames[0].maxHeight, Bitmap.Config.ARGB_8888)
val res = Bitmap.createBitmap(Frames[0].maxWidth!!, Frames[0].maxHeight!!, Bitmap.Config.ARGB_8888)
val can = Canvas(res)
can.drawBitmap(btm, 0f, 0f, null)
can.drawRect(lastFrame!!.x_offsets!!.toFloat(), lastFrame!!.y_offsets!!.toFloat(), lastFrame!!.x_offsets!! + lastFrame!!.width.toFloat(), lastFrame!!.y_offsets!! + lastFrame!!.height.toFloat(), { val paint = Paint(); paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR); paint }())
@ -207,7 +226,7 @@ class ApngAnimator {
val it = Frames.get(i)
// Current bitmap for the frame
val btm = Bitmap.createBitmap(Frames[0].maxWidth, Frames[0].maxHeight, Bitmap.Config.ARGB_8888)
val btm = Bitmap.createBitmap(Frames[0].maxWidth!!, Frames[0].maxHeight!!, Bitmap.Config.ARGB_8888)
val canvas = Canvas(btm)
val current = BitmapFactory.decodeByteArray(it.byteArray, 0, it.byteArray.size).copy(Bitmap.Config.ARGB_8888, true)
@ -234,7 +253,7 @@ class ApngAnimator {
// Add current frame to bitmap buffer
// APNG_DISPOSE_OP_BACKGROUND: the frame's region of the output buffer is to be cleared to fully transparent black before rendering the next frame.
else if (it.dispose_op == Utils.Companion.dispose_op.APNG_DISPOSE_OP_BACKGROUND){
val res = Bitmap.createBitmap(Frames[0].maxWidth, Frames[0].maxHeight, Bitmap.Config.ARGB_8888)
val res = Bitmap.createBitmap(Frames[0].maxWidth!!, Frames[0].maxHeight!!, Bitmap.Config.ARGB_8888)
val can = Canvas(res)
can.drawBitmap(btm, 0f, 0f, null)
can.drawRect(lastFrame!!.x_offsets!!.toFloat(), lastFrame!!.y_offsets!!.toFloat(), lastFrame!!.x_offsets!! + lastFrame!!.width.toFloat(), lastFrame!!.y_offsets!! + lastFrame!!.height.toFloat(), { val paint = Paint(); paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR); paint }())

View File

@ -12,12 +12,12 @@ import oupson.apng.ApngFactory.Companion.isPng
class Frame {
val byteArray : ByteArray
var byteArray : ByteArray
val width : Int
val height : Int
var width : Int
var height : Int
val ihdr : IHDR
var ihdr : IHDR
var idat : IDAT
@ -26,11 +26,11 @@ class Frame {
var x_offsets : Int? = null
var y_offsets : Int? = null
val maxWidth : Int
val maxHeight : Int
var maxWidth : Int? = null
var maxHeight : Int? = null
val blend_op: Utils.Companion.blend_op
val dispose_op : Utils.Companion.dispose_op
var blend_op: Utils.Companion.blend_op
var dispose_op : Utils.Companion.dispose_op
constructor(byteArray: ByteArray) {
if (isPng(byteArray)) {
@ -42,14 +42,12 @@ class Frame {
width = ihdr.pngWidth
height = ihdr.pngHeight
// Get image bytes
// Get IDAT Bytes
idat = IDAT()
idat.parseIDAT(byteArray)
delay = 1000f
maxHeight = -1
maxWidth = -1
blend_op = Utils.Companion.blend_op.APNG_BLEND_OP_SOURCE
dispose_op = Utils.Companion.dispose_op.APNG_DISPOSE_OP_NONE
} else {
@ -66,14 +64,11 @@ class Frame {
width = ihdr.pngWidth
height = ihdr.pngHeight
// Get image bytes
// Get IDAT Bytes
idat = IDAT()
idat.parseIDAT(byteArray)
this.delay = delay
maxHeight = -1
maxWidth = -1
blend_op = Utils.Companion.blend_op.APNG_BLEND_OP_SOURCE
dispose_op = Utils.Companion.dispose_op.APNG_DISPOSE_OP_NONE
} else {
@ -91,7 +86,7 @@ class Frame {
width = ihdr.pngWidth
height = ihdr.pngHeight
// Get image bytes
// Get IDAT Bytes
idat = IDAT()
idat.parseIDAT(byteArray)
@ -117,7 +112,7 @@ class Frame {
width = ihdr.pngWidth
height = ihdr.pngHeight
// Get image bytes
// Get IDAT Bytes
idat = IDAT()
idat.parseIDAT(byteArray)
@ -145,7 +140,7 @@ class Frame {
width = ihdr.pngWidth
height = ihdr.pngHeight
// Get image bytes
// Get IDAT Bytes
idat = IDAT()
idat.parseIDAT(byteArray)

View File

@ -1,11 +1,10 @@
package oupson.apng
import android.os.Bundle
import android.widget.ImageView
import java.io.*
import java.net.URI
import java.io.BufferedInputStream
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.IOException
import java.net.URL
import java.net.URLConnection
class Loader {
fun load(url : URL) : ByteArray {

View File

@ -12,6 +12,11 @@ class Utils {
APNG_DISPOSE_OP_PREVIOUS
}
/**
* Get the int equivalent to the dispose_op
* @param dispose_op The dispose_op
* @return An int equivalent to the dispose_op
*/
fun getDispose_op(dispose_op: dispose_op) : Int {
return when(dispose_op) {
Companion.dispose_op.APNG_DISPOSE_OP_NONE -> 0
@ -20,6 +25,11 @@ class Utils {
}
}
/**
* Get the dispose_op enum equivalent to the int
* @param int Int of the dispose_op
* @return A dispose_op
*/
fun getDispose_op(int: Int) : dispose_op {
return when(int) {
0 -> Companion.dispose_op.APNG_DISPOSE_OP_NONE
@ -34,6 +44,11 @@ class Utils {
APNG_BLEND_OP_OVER
}
/**
* Get the int equivalent to the blend_op
* @param blend_op The blend_op
* @return An int equivalent to the blend_op
*/
fun getBlend_op(blend_op: blend_op) : Int {
return when(blend_op) {
Companion.blend_op.APNG_BLEND_OP_SOURCE -> 0
@ -41,6 +56,11 @@ class Utils {
}
}
/**
* Get the blend_op enum equivalent to the int
* @param int Int of the blend_op
* @return A blend_op
*/
fun getBlend_op(int : Int) : blend_op{
return when(int) {
0 -> Companion.blend_op.APNG_BLEND_OP_SOURCE
@ -49,12 +69,22 @@ class Utils {
}
}
/**
* Get PNG ByteArray of Bitmap
* @param bitmap The bitmap to convert
* @return PNG ByteArray of the Bitmap
*/
fun toByteArray(bitmap: Bitmap) : ByteArray {
val bos = ByteArrayOutputStream();
convertImage(bitmap).compress(Bitmap.CompressFormat.PNG, 0 /*ignored for PNG*/, bos);
return bos.toByteArray();
}
/**
* Convert the image
* @param bitmap Bitmap to convert
* @return Bitmap converted
*/
fun convertImage(bitmap: Bitmap) : Bitmap{
val btm = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
val canvas = Canvas(btm)
@ -65,6 +95,7 @@ class Utils {
/**
* Generate a 4 bytes array from an Int
* @param i The int
* @return 2 Bytes
*/
fun to4Bytes(i: Int): ByteArray {
val result = ByteArray(4)
@ -78,6 +109,7 @@ class Utils {
/**
* Generate a 2 bytes array from an Int
* @param i The int
* @return 2 Bytes
*/
fun to2Bytes(i: Int): ByteArray {
val result = ByteArray(2)

View File

@ -1,6 +1,5 @@
package oupson.apng
import android.util.Log
import oupson.apng.Utils.Companion.getBlend_op
import oupson.apng.Utils.Companion.getDispose_op

View File

@ -30,7 +30,7 @@ android {
productFlavors {
}
}
ext.anko_version = '0.10.6'
ext.anko_version = '0.10.7'
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
//noinspection GradleCompatible

View File

@ -31,26 +31,17 @@ import java.net.URL
class MainActivity : AppCompatActivity() {
lateinit var animator : ApngAnimator
val imageUrl = "https://raw.githubusercontent.com/tinify/iMessage-Panda-sticker/master/Source/panda-original.png"
val imageUrl = "https://metagif.files.wordpress.com/2015/01/bugbuckbunny.png"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val apng = Apng()
val file1 = File(Environment.getExternalStoragePublicDirectory(DIRECTORY_PICTURES), "hopital.jpg")
val file2 = File(Environment.getExternalStoragePublicDirectory(DIRECTORY_PICTURES), "test.jpg")
apng.addFrames(BitmapFactory.decodeByteArray(file2.readBytes(), 0, file2.readBytes().size), 2000f, Utils.Companion.blend_op.APNG_BLEND_OP_OVER, Utils.Companion.dispose_op.APNG_DISPOSE_OP_NONE)
apng.addFrames(BitmapFactory.decodeByteArray(file1.readBytes(), 0, file1.readBytes().size), 1000f, Utils.Companion.blend_op.APNG_BLEND_OP_OVER, Utils.Companion.dispose_op.APNG_DISPOSE_OP_NONE)
animator = ApngAnimator(imageView)
animator.load(apng.generateAPNGByteArray())
animator.isDebug = true
animator.load(imageUrl)
Picasso.get().load(imageUrl).into(imageView2);
val out = File(Environment.getExternalStoragePublicDirectory(DIRECTORY_PICTURES), "out.png")
out.createNewFile()
out.writeBytes(apng.generateAPNGByteArray())
play.setOnClickListener {
animator.play()