Initial coroutine support for decodeApng

This commit is contained in:
Oupson 2021-03-04 13:15:49 +01:00
parent 7d7b02f2b9
commit ffb60346a7
1 changed files with 54 additions and 29 deletions

View File

@ -94,15 +94,17 @@ class ApngDecoder {
@Suppress("MemberVisibilityCanBePrivate") @Suppress("MemberVisibilityCanBePrivate")
@JvmStatic @JvmStatic
@JvmOverloads @JvmOverloads
fun decodeApng( suspend fun decodeApng(
context: Context, context: Context,
inStream: InputStream, inStream: InputStream,
config: Config = Config() config: Config = Config()
): Drawable { ): Drawable = withContext(Dispatchers.Default) {
val inputStream = BufferedInputStream(inStream) val inputStream = BufferedInputStream(inStream)
val bytes = ByteArray(8) val bytes = ByteArray(8)
inputStream.mark(8) inputStream.mark(8)
inputStream.read(bytes) withContext(Dispatchers.IO) {
inputStream.read(bytes)
}
if (isPng(bytes)) { if (isPng(bytes)) {
var png: ByteArrayOutputStream? = null var png: ByteArrayOutputStream? = null
@ -131,15 +133,25 @@ class ApngDecoder {
var byteRead: Int var byteRead: Int
val lengthChunk = ByteArray(4) val lengthChunk = ByteArray(4)
do { do {
byteRead = inputStream.read(lengthChunk) val length : Int
val chunk : ByteArray
if (withContext(Dispatchers.IO) {
byteRead = inputStream.read(lengthChunk)
if (byteRead == -1)
if (byteRead != -1) {
length = Utils.uIntFromBytesBigEndian(lengthChunk)
chunk = ByteArray(length + 8)
byteRead = inputStream.read(chunk)
false
} else {
chunk = ByteArray(0)
true
}
}) {
break break
}
val length = Utils.uIntFromBytesBigEndian(lengthChunk)
val chunk = ByteArray(length + 8)
byteRead = inputStream.read(chunk)
val byteArray = lengthChunk.plus(chunk) val byteArray = lengthChunk.plus(chunk)
val chunkCRC = Utils.uIntFromBytesBigEndian(byteArray, byteArray.size - 4) val chunkCRC = Utils.uIntFromBytesBigEndian(byteArray, byteArray.size - 4)
@ -411,10 +423,12 @@ class ApngDecoder {
crC32.update(Utils.IEND, 0, Utils.IEND.size) crC32.update(Utils.IEND, 0, Utils.IEND.size)
it.write(Utils.IEND) it.write(Utils.IEND)
it.write(Utils.uIntToByteArray(crC32.value.toInt())) it.write(Utils.uIntToByteArray(crC32.value.toInt()))
inputStream.close() withContext(Dispatchers.IO) {
inputStream.close()
}
val pngBytes = it.toByteArray() val pngBytes = it.toByteArray()
return BitmapDrawable( return@withContext BitmapDrawable(
context.resources, context.resources,
BitmapFactory.decodeByteArray( BitmapFactory.decodeByteArray(
pngBytes, pngBytes,
@ -509,26 +523,35 @@ class ApngDecoder {
} }
} }
} else throw BadCRCException() } else throw BadCRCException()
} while (byteRead != -1) } while (byteRead != -1 && isActive)
inputStream.close() withContext(Dispatchers.IO) {
return drawable inputStream.close()
}
return@withContext drawable
} else { } else {
if (BuildConfig.DEBUG) if (BuildConfig.DEBUG)
Log.i(TAG, "Decoding non APNG stream") Log.i(TAG, "Decoding non APNG stream")
inputStream.reset() inputStream.reset()
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { return@withContext if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
val bytesRead = inputStream.readBytes() val bytesRead : ByteArray
inputStream.close() withContext(Dispatchers.IO) {
bytesRead = inputStream.readBytes()
inputStream.close()
}
val buf = ByteBuffer.wrap(bytesRead) val buf = ByteBuffer.wrap(bytesRead)
val source = ImageDecoder.createSource(buf) val source = ImageDecoder.createSource(buf)
ImageDecoder.decodeDrawable(source) withContext(Dispatchers.IO) {
ImageDecoder.decodeDrawable(source)
}
} else { } else {
val drawable = Drawable.createFromStream( val drawable = Drawable.createFromStream(
inputStream, inputStream,
null null
) )
inputStream.close() withContext(Dispatchers.IO) {
inputStream.close()
}
drawable drawable
} }
} }
@ -544,7 +567,7 @@ class ApngDecoder {
@Suppress("unused") @Suppress("unused")
@JvmStatic @JvmStatic
// TODO DOCUMENT // TODO DOCUMENT
fun decodeApng( suspend fun decodeApng(
context: Context, context: Context,
file: File, file: File,
config: Config = Config() config: Config = Config()
@ -563,7 +586,7 @@ class ApngDecoder {
*/ */
@Suppress("unused") @Suppress("unused")
@JvmStatic @JvmStatic
fun decodeApng( suspend fun decodeApng(
context: Context, context: Context,
uri: Uri, uri: Uri,
config: Config = Config() config: Config = Config()
@ -585,7 +608,7 @@ class ApngDecoder {
*/ */
@Suppress("unused") @Suppress("unused")
@JvmStatic @JvmStatic
fun decodeApng( suspend fun decodeApng(
context: Context, context: Context,
@RawRes res: Int, @RawRes res: Int,
config: Config = Config() config: Config = Config()
@ -637,12 +660,14 @@ class ApngDecoder {
config: Config = Config(), config: Config = Config(),
scope : CoroutineScope = GlobalScope scope : CoroutineScope = GlobalScope
) { ) {
scope.launch(Dispatchers.IO) { scope.launch(Dispatchers.Default) {
try { try {
val drawable = val drawable =
decodeApng( decodeApng(
context, context,
FileInputStream(file), withContext(Dispatchers.IO) {
FileInputStream(file)
},
config config
) )
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
@ -681,7 +706,7 @@ class ApngDecoder {
scope : CoroutineScope = GlobalScope scope : CoroutineScope = GlobalScope
) { ) {
val inputStream = context.contentResolver.openInputStream(uri)!! val inputStream = context.contentResolver.openInputStream(uri)!!
scope.launch(Dispatchers.IO) { scope.launch(Dispatchers.Default) {
try { try {
val drawable = val drawable =
decodeApng( decodeApng(
@ -723,7 +748,7 @@ class ApngDecoder {
config: Config = Config(), config: Config = Config(),
scope : CoroutineScope = GlobalScope scope : CoroutineScope = GlobalScope
) { ) {
scope.launch(Dispatchers.IO) { scope.launch(Dispatchers.Default) {
try { try {
val drawable = val drawable =
decodeApng( decodeApng(
@ -767,7 +792,7 @@ class ApngDecoder {
config: Config = Config(), config: Config = Config(),
scope : CoroutineScope = GlobalScope scope : CoroutineScope = GlobalScope
) { ) {
scope.launch(Dispatchers.IO) { scope.launch(Dispatchers.Default) {
try { try {
val drawable = decodeApng( val drawable = decodeApng(
context, context,
@ -813,7 +838,7 @@ class ApngDecoder {
config: Config = Config(), config: Config = Config(),
scope : CoroutineScope = GlobalScope scope : CoroutineScope = GlobalScope
) { ) {
scope.launch(Dispatchers.IO) { scope.launch(Dispatchers.Default) {
try { try {
if (string.startsWith("http://") || string.startsWith("https://")) { if (string.startsWith("http://") || string.startsWith("https://")) {
decodeApngAsyncInto( decodeApngAsyncInto(