Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 574
DearDhruv Avatar asked Feb 17 '26 07:02

DearDhruv


1 Answers

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.

like image 181
Philip Dukhov Avatar answered Feb 18 '26 21:02

Philip Dukhov