Partial fix of a bug with ApngAnimator and system style change

Working on documentation
This commit is contained in:
oupson 2020-05-09 20:14:22 +02:00
parent 323623b5fa
commit f52b9e4563
5 changed files with 356 additions and 289 deletions

View File

@ -17,8 +17,8 @@ import java.io.InputStream
import java.util.*
import java.util.zip.CRC32
// TODO REWRITE
class APNGDisassembler {
companion object {
private var png: ArrayList<Byte>? = null
private var cover: ArrayList<Byte>? = null
private var delay = -1f
@ -29,7 +29,8 @@ class APNGDisassembler {
private var maxWidth = 0
private var maxHeight = 0
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 disposeOp: Utils.Companion.DisposeOp =
Utils.Companion.DisposeOp.APNG_DISPOSE_OP_NONE
private var ihdr = IHDR()
private var isApng = false
@ -40,7 +41,7 @@ class APNGDisassembler {
* @param byteArray The Byte Array of the file
* @return [Apng] The apng decoded
*/
fun disassemble(byteArray: ByteArray) : Apng {
fun disassemble(byteArray: ByteArray): Apng {
reset()
if (isApng(byteArray)) {
var cursor = 8
@ -61,7 +62,7 @@ class APNGDisassembler {
* @param input Input Stream
* @return [Apng] The apng decoded
*/
fun disassemble(input : InputStream) : Apng {
fun disassemble(input: InputStream): Apng {
reset()
val buffer = ByteArray(8)
@ -96,14 +97,21 @@ class APNGDisassembler {
* @param height The height of the frame
* @return [ByteArray] The generated IHDR
*/
private fun generateIhdr(ihdrOfApng: IHDR, width : Int, height : Int) : ByteArray {
private fun generateIhdr(ihdrOfApng: IHDR, width: Int, height: Int): ByteArray {
val ihdr = ArrayList<Byte>()
// We need a body var to know body length and generate crc
val ihdrBody = ArrayList<Byte>()
// Add chunk body length
ihdr.addAll(to4Bytes(ihdrOfApng.body.size).asList())
// Add IHDR
ihdrBody.addAll(byteArrayOf(0x49.toByte(), 0x48.toByte(), 0x44.toByte(), 0x52.toByte()).asList())
ihdrBody.addAll(
byteArrayOf(
0x49.toByte(),
0x48.toByte(),
0x44.toByte(),
0x52.toByte()
).asList()
)
// Add the max width and height
ihdrBody.addAll(to4Bytes(width).asList())
ihdrBody.addAll(to4Bytes(height).asList())
@ -128,7 +136,7 @@ class APNGDisassembler {
val crc = CRC32()
crc.update(byteArray.copyOfRange(i, byteArray.size - 4))
if (chunkCRC == crc.value.toInt()) {
val name= byteArray.copyOfRange(i, i + 4)
val name = byteArray.copyOfRange(i, i + 4)
when {
name.contentEquals(Utils.fcTL) -> {
if (png == null) {
@ -178,7 +186,18 @@ class APNGDisassembler {
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))
apng.frames.add(
Frame(
png!!.toByteArray(),
delay,
xOffset,
yOffset,
blendOp,
disposeOp,
maxWidth,
maxHeight
)
)
png = ArrayList()
val fcTL = fcTL()
fcTL.parse(byteArray)
@ -316,5 +335,4 @@ class APNGDisassembler {
apng = Apng()
isApng = false
}
}
}

View File

@ -183,7 +183,7 @@ class ApngAnimator(private val context: Context?) {
// Download PNG
val inputStream = file.inputStream()
APNGDisassembler.disassemble(inputStream).also {
APNGDisassembler().disassemble(inputStream).also {
inputStream.close()
if (it.isApng) {
it.frames.also {frames ->
@ -231,7 +231,7 @@ class ApngAnimator(private val context: Context?) {
// Download PNG
val inputStream = context.contentResolver.openInputStream(uri)!!
APNGDisassembler.disassemble(inputStream).also {
APNGDisassembler().disassemble(inputStream).also {
inputStream.close()
if (it.isApng) {
isApng = true
@ -319,7 +319,8 @@ class ApngAnimator(private val context: Context?) {
this@ApngAnimator.speed = speed
scaleType = apngAnimatorOptions?.scaleType
// Download PNG
APNGDisassembler.disassemble(byteArray).frames.also { frames ->
println(byteArray.size)
APNGDisassembler().disassemble(byteArray).frames.also { frames ->
draw(frames).apply {
setupAnimationDrawableAndStart(this)
}
@ -391,7 +392,7 @@ class ApngAnimator(private val context: Context?) {
this@ApngAnimator.speed = speed
scaleType = apngAnimatorOptions?.scaleType
// Download PNG
APNGDisassembler.disassemble(byteArray).frames.also { frames ->
APNGDisassembler().disassemble(byteArray).frames.also { frames ->
draw(frames).apply {
setupAnimationDrawableAndStart(this)
}

View File

@ -8,12 +8,15 @@ import java.io.IOException
import java.net.HttpURLConnection
import java.net.URL
// TODO DOCUMENTATION (MAYBE WIKI) FOR THE CACHE
class Loader {
companion object {
/**
* Download file from given url.
* Download file from given url on the [Dispatchers.IO] scope.
* @param url Url of the file to download.
* @return [ByteArray] of the file.
* @throws IOException thrown when retrieving the file.
* @throws Exception when returned code of the [HttpURLConnection] is not 200 (OK).
*/
@Throws(IOException::class, Exception::class)
suspend fun load(url: URL): ByteArray =

View File

@ -15,7 +15,6 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import oupson.apng.APNGDisassembler
import oupson.apng.BuildConfig
import oupson.apng.Loader
import oupson.apng.chunks.IHDR
@ -31,13 +30,23 @@ import java.util.zip.CRC32
class ApngDecoder {
interface Callback {
/**
* Function called when the file was successfully decoded.
* @param drawable Can be an [AnimationDrawable] if successful and an [AnimatedImageDrawable] if the image decoded is not an APNG but a gif. If it is not an animated image, it is a [Drawable].
*/
fun onSuccess(drawable: Drawable)
/**
* Function called when something gone wrong.
* @param error The problem.
*/
fun onError(error: java.lang.Exception)
}
companion object {
private const val TAG = "ApngDecoder"
// Paint used to clear the buffer
private val clearPaint: Paint by lazy {
Paint().apply {
xfermode = PorterDuffXfermode(
@ -52,11 +61,17 @@ class ApngDecoder {
* @param inStream Input Stream to decode. Will be close at the end.
* @param speed Optional parameter.
* @param config Configuration applied to the bitmap added to the animation. Please note that the frame is decoded in ARGB_8888 and converted after, for the buffer.
* @return [AnimationDrawable] if successful and an [AnimatedImageDrawable] if the image decoded is not an APNG but a gif. If it is not an animated image, it is a [Drawable].
*/
@Suppress("MemberVisibilityCanBePrivate")
@JvmStatic
@JvmOverloads
fun decodeApng(context: Context, inStream: InputStream, speed: Float = 1f, config : Bitmap.Config = Bitmap.Config.ARGB_8888): Drawable {
fun decodeApng(
context: Context,
inStream: InputStream,
speed: Float = 1f,
config: Bitmap.Config = Bitmap.Config.ARGB_8888
): Drawable {
val inputStream = BufferedInputStream(inStream)
val bytes = ByteArray(8)
inputStream.mark(8)
@ -115,11 +130,11 @@ class ApngDecoder {
crC32.update(iend, 0, iend.size)
it.addAll(iend.asList())
it.addAll(Utils.to4Bytes(crC32.value.toInt()).asList())
APNGDisassembler.apng.cover = BitmapFactory.decodeByteArray(
/**APNGDisassembler.apng.cover = BitmapFactory.decodeByteArray(
it.toByteArray(),
0,
it.size
)
)*/ // TODO
}
png = ArrayList()
val fcTL = fcTL()
@ -198,7 +213,10 @@ class ApngDecoder {
context.resources,
if (btm.config != config) {
if (BuildConfig.DEBUG)
Log.v(TAG, "Bitmap Config : ${btm.config}, Config : $config")
Log.v(
TAG,
"Bitmap Config : ${btm.config}, Config : $config"
)
btm.copy(config, btm.isMutable)
} else {
btm
@ -306,7 +324,10 @@ class ApngDecoder {
context.resources,
if (btm.config != config) {
if (BuildConfig.DEBUG)
Log.v(TAG, "Bitmap Config : ${btm.config}, Config : $config")
Log.v(
TAG,
"Bitmap Config : ${btm.config}, Config : $config"
)
btm.copy(config, btm.isMutable)
} else {
btm
@ -478,10 +499,16 @@ class ApngDecoder {
* @param file File to decode.
* @param speed Optional parameter.
* @param config Configuration applied to the bitmap added to the animation. Please note that the frame is decoded in ARGB_8888 and converted after, for the buffer.
* @return [AnimationDrawable] if successful and an [AnimatedImageDrawable] if the image decoded is not an APNG but a gif. If it is not an animated image, it is a [Drawable].
*/
@Suppress("unused")
@JvmStatic
fun decodeApng(context: Context, file: File, speed: Float = 1f, config : Bitmap.Config = Bitmap.Config.ARGB_8888): Drawable =
fun decodeApng(
context: Context,
file: File,
speed: Float = 1f,
config: Bitmap.Config = Bitmap.Config.ARGB_8888
): Drawable =
decodeApng(
context,
FileInputStream(file), speed, config
@ -493,12 +520,17 @@ class ApngDecoder {
* @param uri Uri to open.
* @param speed Optional parameter.
* @param config Configuration applied to the bitmap added to the animation. Please note that the frame is decoded in ARGB_8888 and converted after, for the buffer.
* @return [AnimationDrawable] if successful and an [AnimatedImageDrawable] if the image decoded is not an APNG but a gif.
*/
@Suppress("unused")
@JvmStatic
fun decodeApng(context: Context, uri: Uri, speed: Float = 1f, config : Bitmap.Config = Bitmap.Config.ARGB_8888): Drawable {
val inputStream = context.contentResolver.openInputStream(uri)
?: throw Exception("Failed to open InputStream, InputStream is null")
fun decodeApng(
context: Context,
uri: Uri,
speed: Float = 1f,
config: Bitmap.Config = Bitmap.Config.ARGB_8888
): Drawable {
val inputStream = context.contentResolver.openInputStream(uri)!!
return decodeApng(
context,
inputStream,
@ -513,10 +545,16 @@ class ApngDecoder {
* @param res Resource to decode.
* @param speed Optional parameter.
* @param config Configuration applied to the bitmap added to the animation. Please note that the frame is decoded in ARGB_8888 and converted after, for the buffer.
* @return [AnimationDrawable] if successful and an [AnimatedImageDrawable] if the image decoded is not an APNG but a gif.
*/
@Suppress("unused")
@JvmStatic
fun decodeApng(context: Context, @RawRes res: Int, speed: Float = 1f, config : Bitmap.Config = Bitmap.Config.ARGB_8888): Drawable =
fun decodeApng(
context: Context,
@RawRes res: Int,
speed: Float = 1f,
config: Bitmap.Config = Bitmap.Config.ARGB_8888
): Drawable =
decodeApng(
context,
context.resources.openRawResource(res),
@ -530,10 +568,16 @@ class ApngDecoder {
* @param url URL to decode.
* @param speed Optional parameter.
* @param config Configuration applied to the bitmap added to the animation. Please note that the frame is decoded in ARGB_8888 and converted after, for the buffer.
* @return [AnimationDrawable] if successful and an [AnimatedImageDrawable] if the image decoded is not an APNG but a gif.
*/
@Suppress("unused")
@JvmStatic
suspend fun decodeApng(context: Context, url: URL, speed: Float = 1f, config : Bitmap.Config = Bitmap.Config.ARGB_8888) =
suspend fun decodeApng(
context: Context,
url: URL,
speed: Float = 1f,
config: Bitmap.Config = Bitmap.Config.ARGB_8888
) =
withContext(Dispatchers.IO) {
decodeApng(
context,
@ -561,7 +605,7 @@ class ApngDecoder {
imageView: ImageView,
speed: Float = 1f,
callback: Callback? = null,
config : Bitmap.Config = Bitmap.Config.ARGB_8888
config: Bitmap.Config = Bitmap.Config.ARGB_8888
) {
GlobalScope.launch(Dispatchers.IO) {
try {
@ -606,10 +650,9 @@ class ApngDecoder {
imageView: ImageView,
speed: Float = 1f,
callback: Callback? = null,
config : Bitmap.Config = Bitmap.Config.ARGB_8888
config: Bitmap.Config = Bitmap.Config.ARGB_8888
) {
val inputStream = context.contentResolver.openInputStream(uri)
?: throw Exception("Failed to open InputStream, InputStream is null")
val inputStream = context.contentResolver.openInputStream(uri)!!
GlobalScope.launch(Dispatchers.IO) {
try {
val drawable =
@ -652,7 +695,7 @@ class ApngDecoder {
imageView: ImageView,
speed: Float = 1f,
callback: Callback? = null,
config : Bitmap.Config = Bitmap.Config.ARGB_8888
config: Bitmap.Config = Bitmap.Config.ARGB_8888
) {
GlobalScope.launch(Dispatchers.IO) {
try {
@ -698,7 +741,7 @@ class ApngDecoder {
imageView: ImageView,
speed: Float = 1f,
callback: Callback? = null,
config : Bitmap.Config = Bitmap.Config.ARGB_8888
config: Bitmap.Config = Bitmap.Config.ARGB_8888
) {
GlobalScope.launch(Dispatchers.IO) {
try {
@ -747,7 +790,7 @@ class ApngDecoder {
imageView: ImageView,
speed: Float = 1f,
callback: Callback? = null,
config : Bitmap.Config = Bitmap.Config.ARGB_8888
config: Bitmap.Config = Bitmap.Config.ARGB_8888
) {
GlobalScope.launch(Dispatchers.IO) {
try {

View File

@ -11,6 +11,8 @@ import java.io.OutputStream
import java.util.zip.CRC32
// TODO DOCUMENTATION
// TODO BITMAP ENCODING
// TODO BUFFER AND BUFFER DEACTIVATION WHEN BITMAP CONFIG DOES NOT CONTAIN AN ALPHA CHANNEL
class ApngEncoder(
private val outputStream: OutputStream,
private val width : Int,