Use inputStream instead of reading bytes
This commit is contained in:
parent
2ea25cec37
commit
07a7f61fc4
Binary file not shown.
|
@ -33,7 +33,7 @@ dependencies {
|
|||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||
|
||||
implementation "org.jetbrains.anko:anko:$anko_version"
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0-M1'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0'
|
||||
}
|
||||
repositories {
|
||||
mavenCentral()
|
||||
|
|
|
@ -6,11 +6,14 @@ import oupson.apng.chunks.fcTL
|
|||
import oupson.apng.exceptions.BadApng
|
||||
import oupson.apng.exceptions.BadCRC
|
||||
import oupson.apng.exceptions.NotApngException
|
||||
import oupson.apng.exceptions.NotPngException
|
||||
import oupson.apng.utils.Utils
|
||||
import oupson.apng.utils.Utils.Companion.isApng
|
||||
import oupson.apng.utils.Utils.Companion.isPng
|
||||
import oupson.apng.utils.Utils.Companion.parseLength
|
||||
import oupson.apng.utils.Utils.Companion.pngSignature
|
||||
import oupson.apng.utils.Utils.Companion.to4Bytes
|
||||
import java.io.InputStream
|
||||
import java.util.*
|
||||
import java.util.zip.CRC32
|
||||
|
||||
|
@ -28,6 +31,7 @@ class APNGDisassembler {
|
|||
private var blendOp: Utils.Companion.BlendOp = Utils.Companion.BlendOp.APNG_BLEND_OP_SOURCE
|
||||
private var disposeOp: Utils.Companion.DisposeOp = Utils.Companion.DisposeOp.APNG_DISPOSE_OP_NONE
|
||||
private var ihdr = IHDR()
|
||||
private var isApng = false
|
||||
|
||||
var apng: Apng = Apng()
|
||||
|
||||
|
@ -52,6 +56,39 @@ class APNGDisassembler {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disassemble an Apng file
|
||||
* @param input Input Stream
|
||||
* @return [Apng] The apng decoded
|
||||
*/
|
||||
fun disassemble(input : InputStream) : Apng {
|
||||
reset()
|
||||
val buffer = ByteArray(8)
|
||||
|
||||
input.read(buffer)
|
||||
|
||||
if (!isPng(buffer))
|
||||
throw NotPngException()
|
||||
|
||||
var byteRead: Int
|
||||
|
||||
val lengthChunk = ByteArray(4)
|
||||
do {
|
||||
byteRead = input.read(lengthChunk)
|
||||
|
||||
if (byteRead == -1)
|
||||
break
|
||||
val length = parseLength(lengthChunk)
|
||||
|
||||
val chunk = ByteArray(length + 8)
|
||||
byteRead = input.read(chunk)
|
||||
|
||||
parseChunk(lengthChunk.plus(chunk))
|
||||
} while (byteRead != -1)
|
||||
|
||||
return apng
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a correct IHDR from the IHDR chunk of the APNG
|
||||
* @param ihdrOfApng The IHDR of the APNG
|
||||
|
@ -162,15 +199,41 @@ class APNGDisassembler {
|
|||
}
|
||||
}
|
||||
Utils.IEND -> {
|
||||
png?.addAll(to4Bytes(0).asList())
|
||||
// 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.asList())
|
||||
png?.addAll(to4Bytes(crC32.value.toInt()).asList())
|
||||
apng.frames.add(Frame(png!!.toByteArray(), delay, xOffset, yOffset, blendOp, disposeOp, maxWidth, maxHeight))
|
||||
if (isApng) {
|
||||
png?.addAll(to4Bytes(0).asList())
|
||||
// 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.asList())
|
||||
png?.addAll(to4Bytes(crC32.value.toInt()).asList())
|
||||
apng.frames.add(
|
||||
Frame(
|
||||
png!!.toByteArray(),
|
||||
delay,
|
||||
xOffset,
|
||||
yOffset,
|
||||
blendOp,
|
||||
disposeOp,
|
||||
maxWidth,
|
||||
maxHeight
|
||||
)
|
||||
)
|
||||
} else {
|
||||
cover?.let {
|
||||
it.addAll(to4Bytes(0).asList())
|
||||
// Add IEND
|
||||
val iend = byteArrayOf(0x49, 0x45, 0x4E, 0x44)
|
||||
// Generate crc for IEND
|
||||
val crC32 = CRC32()
|
||||
crC32.update(iend, 0, iend.size)
|
||||
it.addAll(iend.asList())
|
||||
it.addAll(to4Bytes(crC32.value.toInt()).asList())
|
||||
apng.cover = BitmapFactory.decodeByteArray(it.toByteArray(), 0, it.size)
|
||||
}
|
||||
apng.isApng = false
|
||||
}
|
||||
}
|
||||
Utils.IDAT -> {
|
||||
if (png == null) {
|
||||
|
@ -228,6 +291,9 @@ class APNGDisassembler {
|
|||
maxWidth = ihdr.pngWidth
|
||||
maxHeight = ihdr.pngHeight
|
||||
}
|
||||
Utils.acTL -> {
|
||||
isApng = true
|
||||
}
|
||||
}
|
||||
} else throw BadCRC()
|
||||
}
|
||||
|
@ -247,6 +313,7 @@ class APNGDisassembler {
|
|||
maxHeight = 0
|
||||
ihdr = IHDR()
|
||||
apng = Apng()
|
||||
isApng = false
|
||||
}
|
||||
}
|
||||
}
|
|
@ -36,6 +36,8 @@ class Apng {
|
|||
|
||||
var frames : ArrayList<Frame> = ArrayList()
|
||||
|
||||
var isApng = true
|
||||
|
||||
// region addFrames
|
||||
/**
|
||||
* Add a frame to the APNG
|
||||
|
|
|
@ -9,11 +9,12 @@ import androidx.annotation.RawRes
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
import oupson.apng.exceptions.NotApngException
|
||||
import oupson.apng.exceptions.NotPngException
|
||||
import oupson.apng.utils.ApngAnimatorOptions
|
||||
import oupson.apng.utils.Utils
|
||||
import oupson.apng.utils.Utils.Companion.isApng
|
||||
import oupson.apng.utils.Utils.Companion.isPng
|
||||
import java.io.File
|
||||
import java.net.URL
|
||||
|
||||
|
@ -167,15 +168,29 @@ class ApngAnimator(private val context: Context?) {
|
|||
@JvmOverloads
|
||||
fun load(file: File, speed: Float? = null, apngAnimatorOptions: ApngAnimatorOptions? = null) : ApngAnimator {
|
||||
GlobalScope.launch {
|
||||
val bytes = file.readBytes()
|
||||
if (isApng(bytes)) {
|
||||
val input = file.inputStream()
|
||||
val bytes = ByteArray(8)
|
||||
input.read(bytes)
|
||||
input.close()
|
||||
if (isPng(bytes)) {
|
||||
isApng = true
|
||||
this@ApngAnimator.speed = speed
|
||||
scaleType = apngAnimatorOptions?.scaleType
|
||||
// Download PNG
|
||||
APNGDisassembler.disassemble(bytes).frames.also {frames ->
|
||||
draw(frames).apply {
|
||||
setupAnimationDrawableAndStart(this)
|
||||
|
||||
val inputStream = file.inputStream()
|
||||
APNGDisassembler.disassemble(inputStream).also {
|
||||
inputStream.close()
|
||||
if (it.isApng) {
|
||||
it.frames.also {frames ->
|
||||
draw(frames).apply {
|
||||
setupAnimationDrawableAndStart(this)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
GlobalScope.launch {
|
||||
imageView?.setImageBitmap(it.cover)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -185,7 +200,7 @@ class ApngAnimator(private val context: Context?) {
|
|||
imageView?.setImageBitmap(BitmapFactory.decodeByteArray(bytes, 0, bytes.size))
|
||||
}
|
||||
} else {
|
||||
throw NotApngException()
|
||||
throw NotPngException()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -202,27 +217,40 @@ class ApngAnimator(private val context: Context?) {
|
|||
@JvmOverloads
|
||||
fun load(uri : Uri, speed: Float? = null, apngAnimatorOptions: ApngAnimatorOptions? = null) : ApngAnimator {
|
||||
GlobalScope.launch {
|
||||
context?.contentResolver?.openInputStream(uri)?.readBytes()?.also {
|
||||
if (isApng(it)) {
|
||||
isApng = true
|
||||
this@ApngAnimator.speed = speed
|
||||
scaleType = apngAnimatorOptions?.scaleType
|
||||
// Download PNG
|
||||
APNGDisassembler.disassemble(it).frames.also {frames ->
|
||||
draw(frames).apply {
|
||||
setupAnimationDrawableAndStart(this)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (loadNotApng) {
|
||||
GlobalScope.launch(Dispatchers.Main) {
|
||||
imageView?.scaleType = this@ApngAnimator.scaleType ?: ImageView.ScaleType.FIT_CENTER
|
||||
imageView?.setImageBitmap(BitmapFactory.decodeByteArray(it, 0, it.size))
|
||||
val input = context!!.contentResolver.openInputStream(uri)!!
|
||||
val bytes = ByteArray(8)
|
||||
input.read(bytes)
|
||||
input.close()
|
||||
if (isPng(bytes)) {
|
||||
isApng = true
|
||||
this@ApngAnimator.speed = speed
|
||||
scaleType = apngAnimatorOptions?.scaleType
|
||||
// Download PNG
|
||||
|
||||
val inputStream = context.contentResolver.openInputStream(uri)!!
|
||||
APNGDisassembler.disassemble(inputStream).also {
|
||||
inputStream.close()
|
||||
if (it.isApng) {
|
||||
it.frames.also {frames ->
|
||||
draw(frames).apply {
|
||||
setupAnimationDrawableAndStart(this)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw NotApngException()
|
||||
GlobalScope.launch(Dispatchers.Main) {
|
||||
imageView?.setImageBitmap(it.cover)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (loadNotApng) {
|
||||
GlobalScope.launch(Dispatchers.Main) {
|
||||
imageView?.scaleType = this@ApngAnimator.scaleType ?: ImageView.ScaleType.FIT_CENTER
|
||||
imageView?.setImageBitmap(BitmapFactory.decodeByteArray(bytes, 0, bytes.size))
|
||||
}
|
||||
} else {
|
||||
throw NotPngException()
|
||||
}
|
||||
}
|
||||
}
|
||||
return this
|
||||
|
@ -241,29 +269,20 @@ class ApngAnimator(private val context: Context?) {
|
|||
this@ApngAnimator.speed = speed
|
||||
// Download PNG
|
||||
Loader.load(context!!, url).apply {
|
||||
if (isApng(this)) {
|
||||
isApng = true
|
||||
this@ApngAnimator.speed = speed
|
||||
scaleType = apngAnimatorOptions?.scaleType
|
||||
// Download PNG
|
||||
APNGDisassembler.disassemble(this).frames.also { frames ->
|
||||
draw(frames).apply {
|
||||
setupAnimationDrawableAndStart(this)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
this@ApngAnimator.load(this, speed, apngAnimatorOptions)
|
||||
} catch (e : NotPngException) {
|
||||
if (loadNotApng) {
|
||||
val bytes = this.readBytes()
|
||||
GlobalScope.launch(Dispatchers.Main) {
|
||||
imageView?.scaleType = this@ApngAnimator.scaleType ?: ImageView.ScaleType.FIT_CENTER
|
||||
imageView?.setImageBitmap(BitmapFactory.decodeByteArray(this@apply, 0, this@apply.size))
|
||||
imageView?.setImageBitmap(BitmapFactory.decodeByteArray(bytes, 0, bytes.size))
|
||||
}
|
||||
} else {
|
||||
throw NotApngException()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
@ -320,19 +339,7 @@ class ApngAnimator(private val context: Context?) {
|
|||
} else if (File(string).exists()) {
|
||||
var pathToLoad = if (string.startsWith("content://")) string else "file://$string"
|
||||
pathToLoad = pathToLoad.replace("%", "%25").replace("#", "%23")
|
||||
val bytes = context?.contentResolver?.openInputStream(Uri.parse(pathToLoad))?.readBytes()
|
||||
bytes ?: throw Exception("File are empty")
|
||||
if (isApng(bytes)) {
|
||||
load(bytes, speed, apngAnimatorOptions)
|
||||
} else {
|
||||
if (loadNotApng) {
|
||||
GlobalScope.launch(Dispatchers.Main) {
|
||||
imageView?.setImageBitmap(BitmapFactory.decodeByteArray(bytes, 0, bytes.size))
|
||||
}
|
||||
} else {
|
||||
throw NotApngException()
|
||||
}
|
||||
}
|
||||
this@ApngAnimator.load(Uri.parse(pathToLoad), speed, apngAnimatorOptions)
|
||||
} else if (string.startsWith("file:///android_asset/")) {
|
||||
val bytes = this@ApngAnimator.context?.assets?.open(string.replace("file:///android_asset/", ""))?.readBytes()
|
||||
bytes ?: throw Exception("File are empty")
|
||||
|
|
|
@ -16,12 +16,12 @@ class Loader {
|
|||
*/
|
||||
@Suppress("RedundantSuspendModifier")
|
||||
@Throws(IOException::class)
|
||||
suspend fun load(context: Context, url: URL): ByteArray {
|
||||
suspend fun load(context: Context, url: URL): File {
|
||||
val currentDir = context.filesDir
|
||||
val fileTXT = File(currentDir, "apngLoader.txt")
|
||||
val filePNG = File(currentDir, "apngLoader.png")
|
||||
return if (fileTXT.exists() && url.toString() == fileTXT.readText()) {
|
||||
filePNG.readBytes()
|
||||
filePNG
|
||||
} else {
|
||||
val connection = url.openConnection()
|
||||
connection.connect()
|
||||
|
@ -29,8 +29,7 @@ class Loader {
|
|||
val bytes = input.readBytes()
|
||||
input.close()
|
||||
fileTXT.writeText(url.toString())
|
||||
filePNG.writeBytes(bytes)
|
||||
bytes
|
||||
filePNG.apply { writeBytes(bytes) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -144,6 +144,7 @@ class Utils {
|
|||
byteArray.forEach {
|
||||
lengthString += String.format("%02x", it)
|
||||
}
|
||||
|
||||
return lengthString.toLong(16).toInt()
|
||||
}
|
||||
|
||||
|
@ -154,5 +155,6 @@ class Utils {
|
|||
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)) }
|
||||
}
|
||||
}
|
|
@ -1,14 +1,14 @@
|
|||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.3.31'
|
||||
ext.kotlin_version = '1.3.50'
|
||||
ext.anko_version = '0.10.8'
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.4.1'
|
||||
classpath 'com.android.tools.build:gradle:3.5.1'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#Fri May 17 18:01:19 CEST 2019
|
||||
#Thu Sep 05 07:21:45 CEST 2019
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
|
||||
|
|
Loading…
Reference in New Issue