'Update bitmap in Image in jetpack compose

Task: I am trying the blurring of the view after setting up the grid with the cells having diff colours (which works nicely in android SDK 31+)

Situation: Since the blur in compose is not available below android SDK 31, ( I am using the Renderscript to blur the captured view bitmap)

Problem: I am not sure how to update the bitmap in Image after the processing is done or add/remove the Image in compose

What I have done so far:

Blur in android SDK 31+

@Composable
fun PlaceHolder(viewModel: PlaceHolderViewModel) {

    val viewState by rememberFlowWithLifecycle(viewModel.state)
        .collectAsState(initial = PlaceHolderViewState.Empty)

    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
        PlaceHolderBlurBelowSDK31(viewState = viewState)
    } else {
        PlaceHolder(viewState = viewState)
    }

}

/**
 * blur for compose is only available for android 31 and above devices.
 */
@Composable
private fun PlaceHolder(viewState: PlaceHolderViewState) = Box(
    modifier = Modifier.blur(radius = 60.dp)
) {
    PlaceHolderGrid(viewState = viewState)
}

@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun PlaceHolderGrid(viewState: PlaceHolderViewState) {
    val size = max(viewState.placeHolderInfo.colors.size, 3)
    val cellHeight = viewState.placeHolderInfo.maxHeight / (size / 3)

    LazyVerticalGrid(cells = GridCells.Fixed(3)) {
        itemsIndexed(items = viewState.placeHolderInfo.colors) { i: Int, color: String ->
            Box(
                modifier = Modifier
                    .fillMaxWidth()
                    .height(cellHeight.dp)
                    .background(color = color.getColor())
            )
        }
    }

}

Here is what I am trying for android SDK below 31

@Composable
fun PlaceHolderBlurBelowSDK31(viewState: PlaceHolderViewState) {

    CaptureBitmap(content = {
        PlaceHolderGrid(viewState = viewState)
    }, laidOut = { bitmap ->

        if (bitmap != null && bitmap.width != 0 && bitmap.height != 0) {
            val painter = rememberAsyncImagePainter(ImageRequest.Builder(LocalContext.current)
                .data(data = bitmap)
                .apply(
                    block = {
                        scale(Scale.FILL)
                    }
                ).build())
            Logger.e("placeholder", "Image - " + System.currentTimeMillis())
            Image(
                painter = painter,
                modifier = Modifier
                    .wrapContentSize()
                    .padding(16.dp),
                colorFilter = ColorFilter.tint(Color.Red),
                contentScale = ContentScale.Crop,
                contentDescription = "logo"
            )
        }
    })

}

@Composable
fun CaptureBitmap(
    content: @Composable () -> Unit,
    laidOut: @Composable (Bitmap?) -> Unit
) {

    val context = LocalContext.current

    /**
     * ComposeView that would take composable as its content
     * Kept in remember so recomposition doesn't re-initialize it
     **/
    val composeView = remember { ComposeView(context) }

    /**
     * Callback function which could get latest image bitmap
     **/
    fun captureBitmap(): Bitmap? {
        return try {
            composeView.drawToBitmap()
        } catch (e: Exception) {
            null
        }
    }


    /** Use Native View inside Composable **/
    AndroidView(modifier = Modifier
        .onGloballyPositioned {
            Logger.e("placeholder", "onGloballyPositioned - " + System.currentTimeMillis())
            laidOut.invoke(captureBitmap())
        },
        factory = {
            ComposeView(it).apply {
                setContent {
                    Logger.e("placeholder", "setContent - " + System.currentTimeMillis())
                    content.invoke()
                }
            }
        }
    )

}


Need help with bitmap to get updated in Image. I know that it needs to be called from the @Composable scope or function.

laidOut.invoke(captureBitmap())

I have tried the

val bitmap: MutableState<Bitmap?> = mutableStateOf(null)
bitmap.value = captureBitmap()

However, I am not getting anywhere. Kindly help.

Grid: Gird cells with diff colurs

Blur: Grid cells after bllur



Solution 1:[1]

In Compose any such cases when you need to update the view are solved with states. Create a mutable state of optional bitmap type, update it, and display it it's not null:

fun captureBitmap(): Bitmap? {
    return try {
        composeView.drawToBitmap()
    } catch (e: Exception) {
        null
    }
}

val (bitmap, updateBitmap) = remember { mutableStateOf<Bitmap?>(null) }

/** Use Native View inside Composable **/
AndroidView(modifier = Modifier
    .onGloballyPositioned {
        Logger.e("placeholder", "onGloballyPositioned - " + System.currentTimeMillis())
        updateBitmap(captureBitmap())
    },
    // ..
)


if (bitmap != null) {
    laidOut(bitmap)
}

I suggest you check out with state in Compose documentation, including this youtube video which explains the basic principles.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Pylyp Dukhov