Working on ExperimentalApngDecoder
Cleaning Code Few improvements
This commit is contained in:
parent
c8e8306930
commit
d8d9945daf
Binary file not shown.
|
@ -128,7 +128,7 @@ class APNGDisassembler {
|
|||
val crc = CRC32()
|
||||
crc.update(byteArray.copyOfRange(i, byteArray.size - 4))
|
||||
if (chunkCRC == crc.value.toInt()) {
|
||||
when (Arrays.toString(byteArray.copyOfRange(i, i + 4))) {
|
||||
when (byteArray.copyOfRange(i, i + 4).contentToString()) {
|
||||
Utils.fcTL -> {
|
||||
if (png == null) {
|
||||
cover?.let {
|
||||
|
|
|
@ -92,8 +92,10 @@ fun ImageView.loadApng(@RawRes res : Int, speed : Float? = null, apngAnimatorOpt
|
|||
|
||||
/**
|
||||
* Class to play APNG
|
||||
* For better performance but lesser features use [ExperimentalApngDecoder] instead
|
||||
*/
|
||||
class ApngAnimator(private val context: Context?) {
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
var isPlaying = true
|
||||
private set
|
||||
|
||||
|
@ -165,7 +167,7 @@ class ApngAnimator(private val context: Context?) {
|
|||
*/
|
||||
@JvmOverloads
|
||||
fun load(file: File, speed: Float? = null, apngAnimatorOptions: ApngAnimatorOptions? = null) : ApngAnimator {
|
||||
GlobalScope.launch {
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
val input = file.inputStream()
|
||||
val bytes = ByteArray(8)
|
||||
input.read(bytes)
|
||||
|
@ -214,7 +216,7 @@ class ApngAnimator(private val context: Context?) {
|
|||
*/
|
||||
@JvmOverloads
|
||||
fun load(uri : Uri, speed: Float? = null, apngAnimatorOptions: ApngAnimatorOptions? = null) : ApngAnimator {
|
||||
GlobalScope.launch {
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
val input = context!!.contentResolver.openInputStream(uri)!!
|
||||
val bytes = ByteArray(8)
|
||||
input.read(bytes)
|
||||
|
@ -264,7 +266,7 @@ class ApngAnimator(private val context: Context?) {
|
|||
*/
|
||||
@JvmOverloads
|
||||
fun loadUrl(url: URL, speed: Float? = null, apngAnimatorOptions: ApngAnimatorOptions? = null) : ApngAnimator {
|
||||
GlobalScope.launch {
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
this@ApngAnimator.speed = speed
|
||||
// Download PNG
|
||||
Loader.load(context!!, url).apply {
|
||||
|
@ -330,7 +332,7 @@ class ApngAnimator(private val context: Context?) {
|
|||
*/
|
||||
@JvmOverloads
|
||||
fun load(string: String, speed : Float? = null, apngAnimatorOptions: ApngAnimatorOptions? = null) : ApngAnimator {
|
||||
GlobalScope.launch {
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
this@ApngAnimator.speed = speed
|
||||
if (string.contains("http") || string.contains("https")) {
|
||||
val url = URL(string)
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
package oupson.apng
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.*
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import androidx.annotation.RawRes
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import oupson.apng.chunks.IHDR
|
||||
import oupson.apng.chunks.fcTL
|
||||
import oupson.apng.exceptions.BadApng
|
||||
|
@ -9,11 +15,18 @@ import oupson.apng.exceptions.BadCRC
|
|||
import oupson.apng.utils.Utils
|
||||
import oupson.apng.utils.Utils.Companion.isPng
|
||||
import java.io.BufferedInputStream
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.InputStream
|
||||
import java.net.URL
|
||||
import java.util.zip.CRC32
|
||||
|
||||
// TODO DOC CODE
|
||||
class ExperimentalApngDecoder {
|
||||
companion object {
|
||||
// TODO Change TAG
|
||||
private const val TAG = "ExperimentalApngDecoder"
|
||||
|
||||
private val clearPaint : Paint by lazy {
|
||||
Paint().apply {
|
||||
xfermode = PorterDuffXfermode(
|
||||
|
@ -22,11 +35,18 @@ class ExperimentalApngDecoder {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode Apng and return a Drawable who can be an [CustomAnimationDrawable] if it end successfully.
|
||||
* @param inStream Input Stream to decode. Will be close at the end
|
||||
* @param speed Optional parameter.
|
||||
*/
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
fun decodeApng(inStream: InputStream, speed : Float = 1f) : Drawable {
|
||||
val inputStream = BufferedInputStream(inStream)
|
||||
val bytes = ByteArray(8)
|
||||
inputStream.mark(0)
|
||||
inputStream.read(bytes)
|
||||
|
||||
if (isPng(bytes)) {
|
||||
var png: ArrayList<Byte>? = null
|
||||
var cover: ArrayList<Byte>? = null
|
||||
|
@ -42,12 +62,10 @@ class ExperimentalApngDecoder {
|
|||
val ihdr = IHDR()
|
||||
var isApng = false
|
||||
|
||||
var drawable = CustomAnimationDrawable()
|
||||
val drawable = CustomAnimationDrawable()
|
||||
|
||||
var buffer : Bitmap? = null
|
||||
|
||||
// TODO REMOVE
|
||||
|
||||
var byteRead: Int
|
||||
val lengthChunk = ByteArray(4)
|
||||
do {
|
||||
|
@ -216,6 +234,7 @@ class ExperimentalApngDecoder {
|
|||
crC32.update(iend, 0, iend.size)
|
||||
it.addAll(iend.asList())
|
||||
it.addAll(Utils.to4Bytes(crC32.value.toInt()).asList())
|
||||
inputStream.close()
|
||||
return BitmapDrawable(BitmapFactory.decodeByteArray(it.toByteArray(), 0, it.size))
|
||||
}
|
||||
}
|
||||
|
@ -287,8 +306,11 @@ class ExperimentalApngDecoder {
|
|||
}
|
||||
} else throw BadCRC()
|
||||
} while (byteRead != -1)
|
||||
inputStream.close()
|
||||
return drawable
|
||||
} else {
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.i(TAG, "Decoding non APNG stream")
|
||||
inputStream.reset()
|
||||
return Drawable.createFromStream(
|
||||
inputStream,
|
||||
|
@ -297,6 +319,45 @@ class ExperimentalApngDecoder {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode Apng and return a Drawable who can be an [CustomAnimationDrawable] if it end successfully.
|
||||
* @param file File to decode.
|
||||
* @param speed Optional parameter.
|
||||
*/
|
||||
@Suppress("unused")
|
||||
fun decodeApng(file : File, speed: Float = 1f) : Drawable = decodeApng(FileInputStream(file), speed)
|
||||
|
||||
/**
|
||||
* Decode Apng and return a Drawable who can be an [CustomAnimationDrawable] if it end successfully.
|
||||
* @param context Context is needed for contentResolver
|
||||
* @param uri Uri to open.
|
||||
* @param speed Optional parameter.
|
||||
*/
|
||||
fun decodeApng(context : Context, uri : Uri, speed: Float = 1f) : Drawable {
|
||||
val inputStream = context.contentResolver.openInputStream(uri)
|
||||
?: throw Exception("Failed to open InputStream, InputStream is null")
|
||||
return decodeApng(inputStream, speed)
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode Apng and return a Drawable who can be an [CustomAnimationDrawable] if it end successfully.
|
||||
* @param context Context is needed for contentResolver
|
||||
* @param res Resource to decode.
|
||||
* @param speed Optional parameter.
|
||||
*/
|
||||
@Suppress("unused")
|
||||
fun decodeApng(context : Context, @RawRes res : Int, speed: Float = 1f) : Drawable = decodeApng(context.resources.openRawResource(res), speed)
|
||||
|
||||
/**
|
||||
* Decode Apng and return a Drawable who can be an [CustomAnimationDrawable] if it end successfully.
|
||||
* @param context Context is needed for contentResolver
|
||||
* @param url URL to decode.
|
||||
* @param speed Optional parameter.
|
||||
*/
|
||||
suspend fun decodeApng(context : Context, url : URL, speed: Float = 1f) = withContext(Dispatchers.IO) {
|
||||
decodeApng(FileInputStream(Loader.load(context, url)), speed)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a correct IHDR from the IHDR chunk of the APNG
|
||||
* @param ihdrOfApng The IHDR of the APNG
|
||||
|
|
|
@ -7,7 +7,6 @@ import oupson.apng.utils.Utils
|
|||
import oupson.apng.utils.Utils.Companion.IDAT
|
||||
import oupson.apng.utils.Utils.Companion.IHDR
|
||||
import oupson.apng.utils.Utils.Companion.isPng
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* A frame of the APNG
|
||||
|
@ -82,7 +81,7 @@ class Frame// Get width and height for image
|
|||
* @param byteArray The frame
|
||||
*/
|
||||
private fun parseChunk(byteArray: ByteArray) {
|
||||
when(Arrays.toString(byteArray.copyOfRange(4, 8))) {
|
||||
when(byteArray.copyOfRange(4, 8).contentToString()) {
|
||||
IHDR -> {
|
||||
ihdr = IHDR()
|
||||
ihdr.parse(byteArray)
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package oupson.apng
|
||||
|
||||
import android.content.Context
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.BufferedInputStream
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
@ -16,11 +18,11 @@ class Loader {
|
|||
*/
|
||||
@Suppress("RedundantSuspendModifier")
|
||||
@Throws(IOException::class)
|
||||
suspend fun load(context: Context, url: URL): File {
|
||||
suspend fun load(context: Context, url: URL): File = withContext(Dispatchers.IO) {
|
||||
val currentDir = context.filesDir
|
||||
val fileTXT = File(currentDir, "apngLoader.txt")
|
||||
val filePNG = File(currentDir, "apngLoader.png")
|
||||
return if (fileTXT.exists() && url.toString() == fileTXT.readText()) {
|
||||
if (fileTXT.exists() && url.toString() == fileTXT.readText()) {
|
||||
filePNG
|
||||
} else {
|
||||
val connection = url.openConnection()
|
||||
|
|
|
@ -12,7 +12,7 @@ class IHDR : Chunk {
|
|||
* @param byteArray The chunk with the length and the crc
|
||||
*/
|
||||
override fun parse(byteArray: ByteArray) {
|
||||
for (i in 0 until byteArray.size) {
|
||||
for (i in byteArray.indices) {
|
||||
// Find IHDR chunk
|
||||
if (byteArray[i] == 0x49.toByte() && byteArray[i + 1] == 0x48.toByte() && byteArray[ i + 2 ] == 0x44.toByte() && byteArray[ i + 3 ] == 0x52.toByte()) {
|
||||
// Get length of the body of the chunk
|
||||
|
|
|
@ -6,6 +6,8 @@ import java.io.IOException
|
|||
import java.util.zip.CRC32
|
||||
import java.util.zip.Deflater
|
||||
import java.util.zip.DeflaterOutputStream
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
/**
|
||||
* Taken from http://catcode.com/pngencoder/com/keypoint/PngEncoder.java
|
||||
|
@ -130,7 +132,7 @@ class PngEncoder {
|
|||
private fun resizeByteArray(array: ByteArray, newLength: Int): ByteArray {
|
||||
val newArray = ByteArray(newLength)
|
||||
val oldLength = array.size
|
||||
System.arraycopy(array, 0, newArray, 0, Math.min(oldLength, newLength))
|
||||
System.arraycopy(array, 0, newArray, 0, min(oldLength, newLength))
|
||||
return newArray
|
||||
}
|
||||
|
||||
|
@ -146,9 +148,9 @@ class PngEncoder {
|
|||
* @return The next place to be written to in the pngBytes array.
|
||||
*/
|
||||
private fun writeBytes(data: ByteArray, offset: Int): Int {
|
||||
maxPos = Math.max(maxPos, offset + data.size)
|
||||
maxPos = max(maxPos, offset + data.size)
|
||||
if (data.size + offset > pngBytes!!.size) {
|
||||
pngBytes = resizeByteArray(pngBytes!!, pngBytes!!.size + Math.max(1000, data.size))
|
||||
pngBytes = resizeByteArray(pngBytes!!, pngBytes!!.size + max(1000, data.size))
|
||||
}
|
||||
System.arraycopy(data, 0, pngBytes!!, offset, data.size)
|
||||
return offset + data.size
|
||||
|
@ -167,9 +169,9 @@ class PngEncoder {
|
|||
* @return The next place to be written to in the pngBytes array.
|
||||
*/
|
||||
private fun writeBytes(data: ByteArray, nBytes: Int, offset: Int): Int {
|
||||
maxPos = Math.max(maxPos, offset + nBytes)
|
||||
maxPos = max(maxPos, offset + nBytes)
|
||||
if (nBytes + offset > pngBytes!!.size) {
|
||||
pngBytes = resizeByteArray(pngBytes!!, pngBytes!!.size + Math.max(1000, nBytes))
|
||||
pngBytes = resizeByteArray(pngBytes!!, pngBytes!!.size + max(1000, nBytes))
|
||||
}
|
||||
System.arraycopy(data, 0, pngBytes!!, offset, nBytes)
|
||||
return offset + nBytes
|
||||
|
@ -317,8 +319,8 @@ class PngEncoder {
|
|||
val compBytes = DeflaterOutputStream(outBytes, scrunch)
|
||||
try {
|
||||
while (rowsLeft > 0) {
|
||||
nRows = Math.min(32767 / (width * (bytesPerPixel + 1)), rowsLeft)
|
||||
nRows = Math.max(nRows, 1)
|
||||
nRows = min(32767 / (width * (bytesPerPixel + 1)), rowsLeft)
|
||||
nRows = max(nRows, 1)
|
||||
|
||||
val pixels = IntArray(width * nRows)
|
||||
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
package oupson.apng.utils
|
||||
|
||||
import java.util.*
|
||||
|
||||
class Utils {
|
||||
companion object {
|
||||
/**
|
||||
|
@ -148,13 +146,13 @@ class Utils {
|
|||
return lengthString.toLong(16).toInt()
|
||||
}
|
||||
|
||||
val fcTL : String by lazy { Arrays.toString(byteArrayOf(0x66, 0x63, 0x54, 0x4c)) }
|
||||
val IEND : String by lazy { Arrays.toString(byteArrayOf(0x49, 0x45, 0x4e, 0x44)) }
|
||||
val IDAT : String by lazy { Arrays.toString(byteArrayOf(0x49, 0x44, 0x41, 0x54)) }
|
||||
val fdAT : String by lazy { Arrays.toString(byteArrayOf(0x66, 0x64, 0x41, 0x54)) }
|
||||
val plte : String by lazy { Arrays.toString(byteArrayOf(0x50, 0x4c, 0x54, 0x45)) }
|
||||
val tnrs : String by lazy { Arrays.toString(byteArrayOf(0x74, 0x52, 0x4e, 0x53)) }
|
||||
val IHDR : String by lazy { Arrays.toString(byteArrayOf(0x49, 0x48, 0x44, 0x52)) }
|
||||
val acTL : String by lazy { Arrays.toString(byteArrayOf(0x61, 0x63, 0x54, 0x4c)) }
|
||||
val fcTL : String by lazy { byteArrayOf(0x66, 0x63, 0x54, 0x4c).contentToString() }
|
||||
val IEND : String by lazy { byteArrayOf(0x49, 0x45, 0x4e, 0x44).contentToString() }
|
||||
val IDAT : String by lazy { byteArrayOf(0x49, 0x44, 0x41, 0x54).contentToString() }
|
||||
val fdAT : String by lazy { byteArrayOf(0x66, 0x64, 0x41, 0x54).contentToString() }
|
||||
val plte : String by lazy { byteArrayOf(0x50, 0x4c, 0x54, 0x45).contentToString() }
|
||||
val tnrs : String by lazy { byteArrayOf(0x74, 0x52, 0x4e, 0x53).contentToString() }
|
||||
val IHDR : String by lazy { byteArrayOf(0x49, 0x48, 0x44, 0x52).contentToString() }
|
||||
val acTL : String by lazy { byteArrayOf(0x61, 0x63, 0x54, 0x4c).contentToString() }
|
||||
}
|
||||
}
|
|
@ -95,6 +95,7 @@ class CreatorActivity : AppCompatActivity() {
|
|||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
when(requestCode) {
|
||||
PICK_IMAGE -> {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
|
|
|
@ -12,9 +12,9 @@ import androidx.core.content.ContextCompat
|
|||
import org.jetbrains.anko.backgroundColor
|
||||
import org.jetbrains.anko.imageView
|
||||
import org.jetbrains.anko.matchParent
|
||||
import org.jetbrains.anko.sdk27.coroutines.onClick
|
||||
import org.jetbrains.anko.verticalLayout
|
||||
import oupson.apng.loadApng
|
||||
import oupson.apng.CustomAnimationDrawable
|
||||
import oupson.apng.ExperimentalApngDecoder
|
||||
|
||||
class Main2Activity : AppCompatActivity() {
|
||||
private lateinit var imageView : ImageView
|
||||
|
@ -50,7 +50,12 @@ class Main2Activity : AppCompatActivity() {
|
|||
|
||||
private fun load() {
|
||||
val uri = intent.data ?: return
|
||||
val animator = imageView.loadApng(uri, null)
|
||||
//val animator = imageView.loadApng(uri, null)
|
||||
val drawable = ExperimentalApngDecoder.decodeApng(this, uri)
|
||||
imageView.setImageDrawable(drawable)
|
||||
if (drawable is CustomAnimationDrawable)
|
||||
drawable.start()
|
||||
/**
|
||||
imageView.onClick {
|
||||
try {
|
||||
if (animator.isApng) {
|
||||
|
@ -64,6 +69,7 @@ class Main2Activity : AppCompatActivity() {
|
|||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int,
|
||||
|
|
|
@ -24,16 +24,16 @@ import org.jetbrains.anko.sdk27.coroutines.onSeekBarChangeListener
|
|||
import oupson.apng.ApngAnimator
|
||||
import oupson.apng.CustomAnimationDrawable
|
||||
import oupson.apng.ExperimentalApngDecoder
|
||||
import oupson.apng.Loader
|
||||
import java.io.FileInputStream
|
||||
import java.net.URL
|
||||
|
||||
// TODO REMOVE ANKO
|
||||
fun ViewManager.xToolbar(init : androidx.appcompat.widget.Toolbar.() -> Unit) = ankoView({androidx.appcompat.widget.Toolbar(it)}, 0, init)
|
||||
class MainActivity : AppCompatActivity() {
|
||||
private lateinit var animator: ApngAnimator
|
||||
private lateinit var tool : androidx.appcompat.widget.Toolbar
|
||||
// val imageUrl = "http://oupson.oupsman.fr/apng/bigApng.png"
|
||||
private val imageUrl = "https://metagif.files.wordpress.com/2015/01/bugbuckbunny.png"
|
||||
//private val imageUrl = "https://upload.wikimedia.org/wikipedia/commons/3/3f/JPEG_example_flower.jpg"
|
||||
// val imageUrl = "http://orig06.deviantart.net/7812/f/2012/233/7/5/twilight_rapidash_shaded_and_animated_by_tamalesyatole-d5bz7hd.png"
|
||||
// val imageUrl = "https://raw.githubusercontent.com/tinify/iMessage-Panda-sticker/master/StickerPackExtension/Stickers.xcstickers/Sticker%20Pack.stickerpack/panda.sticker/panda.png"
|
||||
// val imageUrl = "file:///android_asset/image.png"
|
||||
|
@ -104,23 +104,16 @@ class MainActivity : AppCompatActivity() {
|
|||
)
|
||||
val imageView = imageView {
|
||||
id = View.generateViewId()
|
||||
GlobalScope.launch {
|
||||
Loader.load(this@MainActivity, URL(imageUrl)).apply {
|
||||
val drawable = ExperimentalApngDecoder.decodeApng(FileInputStream(this))
|
||||
if (drawable is CustomAnimationDrawable) {
|
||||
println("YEP")
|
||||
val a = drawable as CustomAnimationDrawable
|
||||
GlobalScope.launch(Dispatchers.Main) {
|
||||
(this@imageView).setImageDrawable(a)
|
||||
a.start()
|
||||
}
|
||||
|
||||
} else
|
||||
GlobalScope.launch(Dispatchers.Main) {
|
||||
(this@imageView).setImageDrawable(drawable)
|
||||
}
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
val drawable = ExperimentalApngDecoder.decodeApng(this@MainActivity, URL(imageUrl))
|
||||
GlobalScope.launch(Dispatchers.Main) {
|
||||
this@imageView.setImageDrawable(drawable)
|
||||
if (drawable is CustomAnimationDrawable)
|
||||
drawable.start()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
animator = this.loadApng(imageUrl).apply {
|
||||
onLoaded {
|
||||
|
@ -128,7 +121,8 @@ class MainActivity : AppCompatActivity() {
|
|||
// Log.e("app-test", "onLoop")
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
*/
|
||||
}.lparams(
|
||||
width = matchConstraint,
|
||||
height = matchConstraint
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<resources>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
|
@ -6,7 +6,7 @@
|
|||
<item name="colorAccent">@color/colorAccent</item>
|
||||
<item name="android:textColorPrimary">#000</item>
|
||||
<item name="android:textColorSecondary">#000</item>
|
||||
<item name="android:windowLightStatusBar">true</item>
|
||||
<item name="android:windowLightStatusBar" tools:targetApi="m">true</item>
|
||||
<item name="toolbarStyle">@style/AppTheme.ActionBar</item>
|
||||
<item name="colorControlNormal">@color/gray</item>
|
||||
</style>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.3.50'
|
||||
ext.kotlin_version = '1.3.61'
|
||||
ext.anko_version = '0.10.8'
|
||||
repositories {
|
||||
google()
|
||||
|
|
Loading…
Reference in New Issue