Initial commit

This commit is contained in:
oupson 2018-09-29 21:10:58 +02:00
parent 498f336466
commit 46d26d66ee
14 changed files with 204 additions and 97 deletions

View File

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

View File

@ -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) {
}

View File

@ -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,8 +101,10 @@ class ApngAnimator(val imageView : ImageView) {
}
fun play() {
play = true
mustPlay()
if (!play) {
play = true
mustPlay()
}
}
private fun mustPlay() {

View File

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

View File

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

View File

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

View File

@ -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
}
}
}
}

View File

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

View File

@ -1,7 +1,5 @@
package oupson.apng
import android.util.Log
class fcTL(byteArray: ByteArray) {
private var corpsSize = -1

View File

@ -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'
}

View File

@ -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"

View File

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

View File

@ -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"