Initial commit
This commit is contained in:
parent
498f336466
commit
46d26d66ee
Binary file not shown.
|
@ -32,6 +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"
|
||||
}
|
||||
repositories {
|
||||
mavenCentral()
|
||||
|
|
|
@ -1,26 +1,27 @@
|
|||
package oupson.apng
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import oupson.apng.APNG.Companion.isApng
|
||||
import java.util.zip.CRC32
|
||||
import android.graphics.BitmapFactory
|
||||
import android.util.Log
|
||||
import oupson.apng.ApngFactory.Companion.isApng
|
||||
import oupson.apng.ApngFactory.Companion.isApng
|
||||
import oupson.apng.ApngFactory.Companion.pngSignature
|
||||
import java.util.zip.CRC32
|
||||
|
||||
|
||||
class APNGDisassembler(val byteArray: ByteArray) {
|
||||
val pngList = ArrayList<ByteArray>()
|
||||
val pngList = ArrayList<Frame>()
|
||||
var png : ArrayList<Byte>? = null
|
||||
|
||||
var delayList = ArrayList<Float>()
|
||||
var delay = -1f
|
||||
|
||||
var yOffset= ArrayList<Int>()
|
||||
var yOffset= -1
|
||||
|
||||
var xOffset = ArrayList<Int>()
|
||||
var xOffset = -1
|
||||
|
||||
var maxWidth = 0
|
||||
var maxHeight = 0
|
||||
init {
|
||||
if (isApng(byteArray)) {
|
||||
if (ApngFactory.isApng(byteArray)) {
|
||||
val ihdr = IHDR()
|
||||
ihdr.parseIHDR(byteArray)
|
||||
maxWidth = ihdr.pngWidth
|
||||
|
@ -32,14 +33,13 @@ class APNGDisassembler(val byteArray: ByteArray) {
|
|||
png = ArrayList()
|
||||
|
||||
val fcTL = fcTL(byteArray.copyOfRange(i-4, i + 28))
|
||||
delayList.add(fcTL.delay)
|
||||
|
||||
yOffset.add(fcTL.y_offset)
|
||||
xOffset.add(fcTL.x_offset)
|
||||
delay = fcTL.delay
|
||||
yOffset = fcTL.y_offset
|
||||
xOffset = fcTL.x_offset
|
||||
Log.e("APNG", "delay : + ${fcTL.delay}")
|
||||
val width = fcTL.pngWidth
|
||||
val height = fcTL.pngHeight
|
||||
png!!.addAll(APNG.pngSignature.toList())
|
||||
png!!.addAll(pngSignature.toList())
|
||||
png!!.addAll(generate_ihdr(ihdr, width, height).toList())
|
||||
} else {
|
||||
// Add IEND body length : 0
|
||||
|
@ -51,20 +51,19 @@ class APNGDisassembler(val byteArray: ByteArray) {
|
|||
crC32.update(iend, 0, iend.size)
|
||||
png!!.addAll(iend.toList())
|
||||
png!!.addAll(to4Bytes(crC32.value.toInt()).toList())
|
||||
|
||||
pngList.add(png!!.toByteArray())
|
||||
pngList.add(Frame(png!!.toByteArray(), delay, xOffset, yOffset, maxWidth, maxHeight))
|
||||
|
||||
png = ArrayList()
|
||||
|
||||
val fcTL = fcTL(byteArray.copyOfRange(i-4, i + 28))
|
||||
delayList.add(fcTL.delay)
|
||||
delay = fcTL.delay
|
||||
|
||||
yOffset.add(fcTL.y_offset)
|
||||
xOffset.add(fcTL.x_offset)
|
||||
yOffset = fcTL.y_offset
|
||||
xOffset = fcTL.x_offset
|
||||
|
||||
val width = fcTL.pngWidth
|
||||
val height = fcTL.pngHeight
|
||||
png!!.addAll(APNG.pngSignature.toList())
|
||||
png!!.addAll(pngSignature.toList())
|
||||
png!!.addAll(generate_ihdr(ihdr, width, height).toList())
|
||||
}
|
||||
} else if (byteArray[i] == 0x49.toByte() && byteArray[i + 1] == 0x44.toByte() && byteArray[ i + 2 ] == 0x41.toByte() && byteArray[ i + 3 ] == 0x54.toByte()) {
|
||||
|
@ -75,7 +74,7 @@ class APNGDisassembler(val byteArray: ByteArray) {
|
|||
}
|
||||
|
||||
|
||||
var bodySize = lengthString.toLong(16).toInt()
|
||||
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())
|
||||
|
@ -114,20 +113,13 @@ class APNGDisassembler(val byteArray: ByteArray) {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
var p = ""
|
||||
p += String(byteArray.copyOfRange(0, 50))
|
||||
Log.e("TAG", "Not a apng : $p")
|
||||
throw NotApngException()
|
||||
}
|
||||
}
|
||||
|
||||
fun getbitmapList() : ArrayList<Frame>
|
||||
{
|
||||
var res = ArrayList<Frame>()
|
||||
Log.e("APNG", "pngList : ${pngList.size}, delayList : ${delayList.size}")
|
||||
for (i in 0 until pngList.size) {
|
||||
res.add(Frame(pngList[i], delayList[i], xOffset[i], yOffset[i], maxWidth, maxHeight))
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
private fun generate_ihdr(ihdrOfApng: IHDR, width : Int, height : Int) : ByteArray {
|
||||
val ihdr = ArrayList<Byte>()
|
||||
|
||||
|
@ -179,6 +171,4 @@ class APNGDisassembler(val byteArray: ByteArray) {
|
|||
|
||||
|
||||
|
||||
}
|
||||
class extractedFrame(val bitmap: Bitmap, val delay : Float, val xoffset : Int, val yoffset : Int, val maxWidth : Int, val maxHeight : Int) {
|
||||
}
|
|
@ -6,7 +6,10 @@ import android.graphics.Canvas
|
|||
import android.os.Environment
|
||||
import android.os.Handler
|
||||
import android.widget.ImageView
|
||||
import org.jetbrains.anko.doAsync
|
||||
import org.jetbrains.anko.uiThread
|
||||
import java.io.File
|
||||
import java.net.URL
|
||||
|
||||
class ApngAnimator(val imageView : ImageView) {
|
||||
var play = true
|
||||
|
@ -23,7 +26,52 @@ class ApngAnimator(val imageView : ImageView) {
|
|||
}
|
||||
|
||||
fun load(file: File) {
|
||||
val extractedFrame = APNGDisassembler(file.readBytes()).getbitmapList()
|
||||
val extractedFrame = APNGDisassembler(file.readBytes()).pngList
|
||||
Frames = extractedFrame
|
||||
|
||||
Frames.forEach {
|
||||
val btm = Bitmap.createBitmap(Frames[0].maxWidth, Frames[0].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)
|
||||
}
|
||||
|
||||
nextFrame()
|
||||
}
|
||||
|
||||
fun load(string: String) {
|
||||
if (string.contains("http") || string.contains("https")) {
|
||||
val url = URL(string)
|
||||
doAsync {
|
||||
val extractedFrame = APNGDisassembler(Loader().load(url)).pngList
|
||||
Frames = extractedFrame
|
||||
|
||||
Frames.forEach {
|
||||
val btm = Bitmap.createBitmap(Frames[0].maxWidth, Frames[0].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)
|
||||
}
|
||||
uiThread {
|
||||
nextFrame()
|
||||
}
|
||||
}
|
||||
} else if (File(string).exists()) {
|
||||
val extractedFrame = APNGDisassembler(Loader().load(File(string))).pngList
|
||||
Frames = extractedFrame
|
||||
|
||||
Frames.forEach {
|
||||
val btm = Bitmap.createBitmap(Frames[0].maxWidth, Frames[0].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)
|
||||
}
|
||||
nextFrame()
|
||||
}
|
||||
}
|
||||
|
||||
fun load(byteArray: ByteArray) {
|
||||
val extractedFrame = APNGDisassembler(byteArray).pngList
|
||||
Frames = extractedFrame
|
||||
|
||||
Frames.forEach {
|
||||
|
@ -53,9 +101,11 @@ class ApngAnimator(val imageView : ImageView) {
|
|||
|
||||
}
|
||||
fun play() {
|
||||
if (!play) {
|
||||
play = true
|
||||
mustPlay()
|
||||
}
|
||||
}
|
||||
|
||||
private fun mustPlay() {
|
||||
if (play) {
|
||||
|
|
|
@ -15,7 +15,7 @@ import java.util.zip.CRC32
|
|||
* @throws NoFrameException
|
||||
*
|
||||
*/
|
||||
class APNG {
|
||||
class ApngFactory {
|
||||
|
||||
private var seq = 0
|
||||
|
||||
|
@ -26,7 +26,7 @@ class APNG {
|
|||
*
|
||||
* @throws NoFrameException
|
||||
*/
|
||||
fun create() : ByteArray {
|
||||
fun create(): ByteArray {
|
||||
|
||||
if (frames.isNotEmpty()) {
|
||||
val res = ArrayList<Byte>()
|
||||
|
@ -191,7 +191,7 @@ class APNG {
|
|||
}
|
||||
|
||||
// Animation Control chunk
|
||||
private fun generateACTL() : ArrayList<Byte> {
|
||||
private fun generateACTL(): ArrayList<Byte> {
|
||||
val res = ArrayList<Byte>()
|
||||
val actl = ArrayList<Byte>()
|
||||
|
||||
|
@ -242,14 +242,14 @@ class APNG {
|
|||
|
||||
companion object {
|
||||
// Return true if png
|
||||
fun isPng(byteArray: ByteArray) : Boolean {
|
||||
fun isPng(byteArray: ByteArray): Boolean {
|
||||
return byteArray.copyOfRange(0, 8).contentToString() == pngSignature.contentToString()
|
||||
}
|
||||
|
||||
fun isApng(byteArray: ByteArray) : Boolean {
|
||||
fun isApng(byteArray: ByteArray): Boolean {
|
||||
for (i in 0 until byteArray.size) {
|
||||
// if byteArray contain acTL
|
||||
if (byteArray[i] == 0x66.toByte() && byteArray[i + 1] == 0x63.toByte() && byteArray[ i + 2] == 0x54.toByte() && byteArray[ i + 3] == 0x4c.toByte()) {
|
||||
if (byteArray[i] == 0x66.toByte() && byteArray[i + 1] == 0x63.toByte() && byteArray[i + 2] == 0x54.toByte() && byteArray[i + 3] == 0x4c.toByte()) {
|
||||
// It's an apng
|
||||
return true
|
||||
}
|
||||
|
@ -259,7 +259,7 @@ class APNG {
|
|||
}
|
||||
|
||||
// Signature for png
|
||||
val pngSignature : ByteArray = byteArrayOf(0x89.toByte(), 0x50.toByte(), 0x4E.toByte(), 0x47.toByte(), 0x0D.toByte(), 0x0A.toByte(), 0x1A.toByte(), 0x0A.toByte())
|
||||
val pngSignature: ByteArray = byteArrayOf(0x89.toByte(), 0x50.toByte(), 0x4E.toByte(), 0x47.toByte(), 0x0D.toByte(), 0x0A.toByte(), 0x1A.toByte(), 0x0A.toByte())
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -273,7 +273,7 @@ class APNG {
|
|||
}
|
||||
|
||||
// Generate Image Header chunk
|
||||
private fun generate_ihdr() : ByteArray {
|
||||
private fun generate_ihdr(): ByteArray {
|
||||
val ihdr = ArrayList<Byte>()
|
||||
|
||||
// We need a body var to know body length and generate crc
|
|
@ -1,6 +1,6 @@
|
|||
package oupson.apng
|
||||
|
||||
import oupson.apng.APNG.Companion.isPng
|
||||
import oupson.apng.ApngFactory.Companion.isPng
|
||||
|
||||
/**
|
||||
* A frame for an animated png
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
package oupson.apng
|
||||
|
||||
import android.os.Bundle
|
||||
import android.widget.ImageView
|
||||
import java.io.*
|
||||
import java.net.URI
|
||||
import java.net.URL
|
||||
import java.net.URLConnection
|
||||
|
||||
class Loader {
|
||||
fun load(url : URL) : ByteArray {
|
||||
try {
|
||||
val connection = url.openConnection()
|
||||
connection.connect()
|
||||
// this will be useful so that you can show a typical 0-100% progress bar
|
||||
val fileLength = connection.contentLength
|
||||
// download the file
|
||||
val input = BufferedInputStream(connection.getInputStream())
|
||||
val output = ByteArrayOutputStream()
|
||||
|
||||
val data = ByteArray(1024)
|
||||
var count: Int = 0
|
||||
while ({count = input.read(data); count}() != -1) {
|
||||
output.write(data, 0, count)
|
||||
}
|
||||
output.flush()
|
||||
output.close()
|
||||
input.close()
|
||||
|
||||
return output.toByteArray()
|
||||
} catch (e: IOException) {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
fun load(file : File) : ByteArray {
|
||||
return file.readBytes()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
package oupson.apng
|
||||
|
||||
class Utils {
|
||||
companion object {
|
||||
enum class dispose_op {
|
||||
APNG_DISPOSE_OP_NONE,
|
||||
APNG_DISPOSE_OP_BACKGROUND,
|
||||
APNG_DISPOSE_OP_PREVIOUS
|
||||
}
|
||||
|
||||
fun getDispose_op(dispose_op: dispose_op) : Int {
|
||||
return when(dispose_op) {
|
||||
Companion.dispose_op.APNG_DISPOSE_OP_NONE -> 0
|
||||
Companion.dispose_op.APNG_DISPOSE_OP_BACKGROUND -> 1
|
||||
Companion.dispose_op.APNG_DISPOSE_OP_PREVIOUS -> 2
|
||||
}
|
||||
}
|
||||
|
||||
fun getDispose_op(int: Int) : dispose_op {
|
||||
return when(int) {
|
||||
0 -> Companion.dispose_op.APNG_DISPOSE_OP_NONE
|
||||
1 -> Companion.dispose_op.APNG_DISPOSE_OP_BACKGROUND
|
||||
2 -> Companion.dispose_op.APNG_DISPOSE_OP_PREVIOUS
|
||||
else -> dispose_op.APNG_DISPOSE_OP_NONE
|
||||
}
|
||||
}
|
||||
|
||||
enum class blend_op() {
|
||||
APNG_BLEND_OP_SOURCE,
|
||||
APNG_BLEND_OP_OVER
|
||||
}
|
||||
|
||||
fun getBlend_op(blend_op: blend_op) : Int {
|
||||
return when(blend_op) {
|
||||
Companion.blend_op.APNG_BLEND_OP_SOURCE -> 0
|
||||
Companion.blend_op.APNG_BLEND_OP_OVER -> 1
|
||||
}
|
||||
}
|
||||
|
||||
fun getBlend_op(int : Int) : blend_op{
|
||||
return when(int) {
|
||||
0 -> Companion.blend_op.APNG_BLEND_OP_SOURCE
|
||||
1 -> Companion.blend_op.APNG_BLEND_OP_OVER
|
||||
else -> blend_op.APNG_BLEND_OP_SOURCE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
package oupson.apng.customView
|
||||
|
||||
import android.content.Context
|
||||
import android.widget.ImageView
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.os.Handler
|
||||
import android.util.AttributeSet
|
||||
import oupson.apng.extractedFrame
|
||||
|
||||
|
||||
class ApngImageView(context: Context, attrs: AttributeSet) : ImageView(context, attrs) {
|
||||
var Frames = ArrayList<extractedFrame>()
|
||||
var myHandler: Handler
|
||||
var counter = 0
|
||||
|
||||
val generatedFrame = ArrayList<Bitmap>()
|
||||
|
||||
init {
|
||||
myHandler = Handler()
|
||||
}
|
||||
fun load(frames : ArrayList<extractedFrame>) {
|
||||
Frames = frames
|
||||
|
||||
Frames.forEach {
|
||||
val btm = Bitmap.createBitmap(Frames[0].maxWidth, Frames[0].maxHeight, Bitmap.Config.ARGB_8888)
|
||||
val canvas = Canvas(btm)
|
||||
canvas.drawBitmap(it.bitmap, it.xoffset.toFloat(), it.yoffset.toFloat(), null)
|
||||
generatedFrame.add(btm)
|
||||
}
|
||||
|
||||
nextFrame()
|
||||
}
|
||||
|
||||
fun nextFrame() {
|
||||
if (counter == Frames.size) {
|
||||
counter = 0
|
||||
}
|
||||
val delay = Frames[counter].delay
|
||||
this.setImageBitmap(generatedFrame[counter])
|
||||
counter++
|
||||
myHandler.postDelayed({
|
||||
nextFrame()
|
||||
}, delay.toLong())
|
||||
}
|
||||
}
|
|
@ -1,7 +1,5 @@
|
|||
package oupson.apng
|
||||
|
||||
import android.util.Log
|
||||
|
||||
class fcTL(byteArray: ByteArray) {
|
||||
|
||||
private var corpsSize = -1
|
||||
|
|
|
@ -30,7 +30,7 @@ android {
|
|||
productFlavors {
|
||||
}
|
||||
}
|
||||
|
||||
ext.anko_version='0.10.6'
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
implementation 'com.android.support:appcompat-v7:27.1.1'
|
||||
|
@ -39,4 +39,6 @@ dependencies {
|
|||
androidTestImplementation 'com.android.support.test:runner:1.0.2'
|
||||
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
|
||||
implementation project(':apng_library')
|
||||
implementation "org.jetbrains.anko:anko:$anko_version"
|
||||
implementation 'com.squareup.picasso:picasso:2.71828'
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="oupson.apngcreator">
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
|
|
|
@ -10,18 +10,32 @@ import oupson.apng.APNGDisassembler
|
|||
import java.io.File
|
||||
import android.os.Environment.getExternalStorageDirectory
|
||||
import kotlinx.android.synthetic.main.activity_main.*
|
||||
import org.jetbrains.anko.doAsync
|
||||
import oupson.apng.ApngAnimator
|
||||
import java.nio.file.Files.exists
|
||||
import android.system.Os.mkdir
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import com.squareup.picasso.Picasso
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.HttpURLConnection.HTTP_OK
|
||||
import java.net.URL
|
||||
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
lateinit var animator : ApngAnimator
|
||||
|
||||
val imageUrl = "http://oupson.oupsman.fr/apng/image.apng"
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_main)
|
||||
|
||||
animator = ApngAnimator(imageView)
|
||||
animator.load(File(File(Environment.getExternalStorageDirectory(), "documents"), "image_3.png"))
|
||||
animator.load(imageUrl)
|
||||
|
||||
Picasso.get().load(imageUrl).into(imageView2);
|
||||
|
||||
|
||||
play.setOnClickListener {
|
||||
animator.play()
|
||||
|
|
|
@ -26,6 +26,18 @@
|
|||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView2"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/play"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/imageView"
|
||||
app:srcCompat="@android:color/transparent" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView"
|
||||
android:layout_width="0dp"
|
||||
|
@ -33,8 +45,7 @@
|
|||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/play"
|
||||
app:layout_constraintBottom_toTopOf="@+id/imageView2"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
|
|
Loading…
Reference in New Issue