Skip to content

Question: What's the best way to parse animation files, reaching all frames and their durations, yet with minimal memory footprint? #249

@AndroidDeveloperLB

Description

@AndroidDeveloperLB

I've created this tiny sample, which needs the entire file read into the byte-array, to parse animation files:


fun parseAnimationWithByteArray(data: ByteArray) {
    val source = ByteBuffer.wrap(data)
    val loader = object : ByteBufferLoader() {
        override fun getByteBuffer(): ByteBuffer {
            source.position(0)
            return source
        }
    }
    var decoder: FrameSeqDecoder<*, *>? = null
    if (WebPParser.isAWebP(ByteBufferReader(source))) {
        decoder = WebPDecoder(loader, null)
    } else if (APNGParser.isAPNG(ByteBufferReader(source))) {
        decoder = APNGDecoder(loader, null)
    } else if (GifParser.isGif(ByteBufferReader(source))) {
        decoder = GifDecoder(loader, null)
    } else if (AVIFParser.isAVIF(ByteBufferReader(source))) {
        decoder = AVIFDecoder(loader, null)
    }

    if (decoder == null) return

    val width = decoder.bounds.width()
    val height = decoder.bounds.height()
    val frameCount = decoder.frameCount
    Log.d("AppLog", "animations size:${width}x$height frameCount:$frameCount")
    for (i in 0 until frameCount) {
        val frame = decoder.getFrame(i) ?: break
        Log.d("AppLog", "frame $i  delay:${frame.frameDuration} ${frame.frameWidth}x${frame.frameHeight} ${frame.frameX}x${frame.frameY}")
        try {
            val bitmap = decoder.getFrameBitmap(i) ?: break
            Log.d("AppLog", "bitmap for frame $i  delay:${frame.frameDuration} ${bitmap.width}x${bitmap.height}")
        } catch (e: IOException) {
            e.printStackTrace()
        }
    }
}

usage in background thread:

Log.d("AppLog", "testing using byte array from resources, of webp")
Utils.testWithByteArray(resources.openRawResource(R.raw.webp).readBytes())
Log.d("AppLog", "testing using byte array from resources, of gif")
Utils.testWithByteArray(resources.openRawResource(R.raw.gif).readBytes())

This has 2 issues:

  1. It needs to read the entire file, which could be an issue if it's a huge file, as it can cause a crash.
  2. It doesn't really work as it should. I failed to reach all frames for some reason. It shows the information about the files fine, but about reaching all the frames, it fails on the second frame for animated WEBP, and right on the beginning for animated GIF (no idea about the others).

I know that if I want to get the images as they are played, I could use this instead, which works well:

val bitmap = createBitmap(decoder.getBounds().width(), decoder.getBounds().height())
val frameCount = decoder.frameCount
decoder.addRenderListener(object : EmptyRenderListener() {
    override fun onRender(byteBuffer: ByteBuffer) {
        super.onRender(byteBuffer)
        byteBuffer.rewind()
        if (byteBuffer.remaining() < bitmap.byteCount) {
            return
        }
        bitmap.copyPixelsFromBuffer(byteBuffer)
        val frameIndex = decoder.frameIndex
        ...
    }
})

Any idea how to fix the function parseAnimationWithByteArray and make it better, so that it could handle content with minimal memory footprint, and yet be able to reach each of the frames, their bitmap, duration and other important information?
Maybe use one of the Loader classes here? But I'm not sure they are focused on memory usage...
On Android it's possible for example to re-create the InputStream when needed from a given Uri, and I'm sure that even for files there are better solutions...

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions