https://kotlinlang.org logo
#compose
Title
# compose
z

Zoltan Demant

12/06/2023, 6:03 AM
Im rendering my composable to a
Bitmap
using
Picture::beginRecording
, but if the content is animated the
Bitmap
wont reflect the final stage of the animaion. If anyone has any ideas, please let me know. Code in 🧵
Capture logic:
Copy code
private fun Modifier.capture(
    scope: CoroutineScope,
    callback: (ImageBitmap) -> Unit,
): Modifier {
    return drawWithCache {
        val picture = Picture()
        var job: Job? = null

        val width = size.width.toInt()
        val height = size.height.toInt()

        onDrawWithContent {
            val canvas = Canvas(picture.beginRecording(width, height))

            draw(
                density = this,
                size = size,
                layoutDirection = layoutDirection,
                canvas = canvas,
                block = {
                    this@onDrawWithContent.drawContent()
                },
            )

            picture.endRecording()

            job?.cancel()
            job = scope.launch {
                val bitmap = withContext(Default) {
                    picture.toBitmap().asImageBitmap()
                }

                callback(bitmap)
            }
        }
    }
}
Usage:
Copy code
Box(
    modifier = modifier.capture(
        scope = rememberCoroutineScope(),
        callback = { bitmap ->
            // Stored in mutableState, then rendered using Image(x)
        },
    ),
    content = content,
)
Adding a bit of context to this .. if the bitmap is cached (rememberSaveable reproduces it), then the capture function doesnt reflect the latest stage of the composable 100% of the time. No caching, no problems 🤷🏽‍♂️ This doesnt make any sense to me.
z

Zach Klippenstein (he/him) [MOD]

12/07/2023, 3:38 PM
Not quite sure what you mean by cached- the code snippet above works or doesn’t? It isn’t using rememberSaveable
I’m guessing this is because the animation is invalidating a draw scope below the one where you’re recording, so your recording logic never gets invalidated? @Nader Jawad could confirm but might need to see more code
n

Nader Jawad

12/07/2023, 9:26 PM
The bitmap is static content by design, so if you want to capture the composable content at the end of the animation you would need to wait for animation completion to capture content to a bitmap
z

Zach Klippenstein (he/him) [MOD]

12/07/2023, 10:01 PM
Ah, my interpretation was they expected a capture of every frame, but I assumed
z

Zoltan Demant

12/08/2023, 7:37 AM
Im expecting a capture of every frame, and overall thats exactly what I get with the code above - but the behavior changes as soon as I put the Bitmap in some sort of cache; I can just replace remember with rememberSaveable above and it happens. My use case is that Im capturing and then blurring my composable, then I overlay it with some text and a button (kind of like a prompt for a paywall). If I tap the button, the actual paywall opens, and when navigating back from the paywall - pretty much 100% of the time, the blurred bitmap no longer reflects the final stage of my composable. The cache comes into play here to keep content consistent when navigating back. Ill look into the draw scopes, there are quite a few composables in play here. Let me know if you have any other ideas!
Just did some more testing! It seems that my capture logic simply doesnt re-run whenever the inner content (that it captures) changes. They have different recomposition scopes, so that makes sense. Any ideas on how I can work around it? My thinking currently is some form of recomposition listener for anything inside a
@Composable () -> Unit
which triggers the capture logic to recompose as well.
Not a solution, but
@NonRestartableComposable
on my capture composable makes the logic "work", but it also recomposes endlessly, so theres that.