Use inputStream instead of reading bytes

This commit is contained in:
oupson 2019-10-05 20:07:19 +02:00
parent 2ea25cec37
commit 07a7f61fc4
9 changed files with 146 additions and 69 deletions

View File

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

View File

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

View File

@ -36,6 +36,8 @@ class Apng {
var frames : ArrayList<Frame> = ArrayList()
var isApng = true
// region addFrames
/**
* Add a frame to the APNG

View File

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

View File

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

View File

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

View File

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

View File

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