Applying a Blur Effect to the Entire Screen Except for a Specific Rectangle in Jetpack Compose

997 Views Asked by At

I’m working on a project using Jetpack Compose in Kotlin and I’m trying to apply a blur effect to the entire screen, excluding a specific rectangle. The goal is to blur the background, regardless of its content (it could be a photo or other UI elements), and have a clear, non-blurred rectangle within this blurred background.

Here’s an example of what I’m trying to achieve: {MY PHOTO}

I found a similar example where an overlay of another transparent color is used instead of blurring. Here’s the code:

@Preview(showBackground = true)
@Composable
fun BlurExample()
{
    Image(
        painter = painterResource(id = R.drawable.delete1),
        contentDescription = null,
        contentScale = ContentScale.FillBounds,
        modifier = Modifier.fillMaxSize().blur(20.dp)
    )
    TransparentClipLayout(
        modifier = Modifier.fillMaxSize(),
        width = 300.dp,
        height = 200.dp,
        offsetY = 150.dp
    )
}

@Composable
fun TransparentClipLayout(
    modifier: Modifier,
    width: Dp,
    height: Dp,
    offsetY: Dp
) {
    val offsetInPx: Float
    val widthInPx: Float
    val heightInPx: Float

    with(LocalDensity.current) {
        offsetInPx = offsetY.toPx()
        widthInPx = width.toPx()
        heightInPx = height.toPx()
    }

    Canvas(modifier = modifier) {
    with(drawContext.canvas.nativeCanvas) {
            val checkPoint = saveLayer(null, null)

            // Destination
            drawRect(Color(0x77FF0000))

            // Source
            drawRoundRect(
                topLeft = Offset(
                    x = (size.width - widthInPx) / 2,
                    y = offsetInPx
                ),
                size = Size(widthInPx, heightInPx),
                cornerRadius = CornerRadius(30f,30f),
                color = Color.Transparent,
                blendMode = BlendMode.Clear
            )
            restoreToCount(checkPoint)
        }
    }
}

However, I haven’t been able to find a solution on how to apply a blur effect to the background. My question is: How can I achieve a clear, non-blurred rectangle within a blurred background, similar to the example above, using Jetpack Compose in Kotlin?

Note: I understand that Modifier.blur can be used to apply a blur effect, but it seems to blur the entire background, including the rectangle that I want to keep clear.

1

There are 1 best solutions below

0
On BEST ANSWER

Somehow blur doesn't draw when there is no content. If it's ok to draw image twice you can do it like this

@Preview(showBackground = true)
@Composable
fun BlurExample() {

    val offsetInPx: Float
    val widthInPx: Float
    val heightInPx: Float

    with(LocalDensity.current) {
        offsetInPx = 150.dp.toPx()
        widthInPx = 300.dp.toPx()
        heightInPx = 200.dp.toPx()
    }


    Box {
        Image(
            painter = painterResource(id = R.drawable.landscape11),
            contentDescription = null,
            contentScale = ContentScale.FillBounds,
            modifier = Modifier.fillMaxSize()
        )

        Box(modifier = Modifier
            .matchParentSize()
            .blur(20.dp, edgeTreatment = BlurredEdgeTreatment.Unbounded)
            .drawWithContent {
                with(drawContext.canvas.nativeCanvas) {
                    val checkPoint = saveLayer(null, null)

                    // Destination
                    drawContent()

                    // Source
                    drawRoundRect(
                        topLeft = Offset(
                            x = (size.width - widthInPx) / 2,
                            y = offsetInPx
                        ),
                        size = Size(widthInPx, heightInPx),
                        cornerRadius = CornerRadius(30f, 30f),
                        color = Color.Transparent,
                        blendMode = BlendMode.Clear
                    )
                    restoreToCount(checkPoint)
                }
            }

        ) {
            Image(
                painter = painterResource(id = R.drawable.landscape11),
                contentDescription = null,
                contentScale = ContentScale.FillBounds,
                modifier = Modifier.fillMaxSize()
            )
        }
    }
}

Or like this

@Preview(showBackground = true)
@Composable
fun BlurExample2() {

    val offsetInPx: Float
    val widthInPx: Float
    val heightInPx: Float

    with(LocalDensity.current) {
        offsetInPx = 150.dp.toPx()
        widthInPx = 300.dp.toPx()
        heightInPx = 200.dp.toPx()
    }

    val painter = painterResource(R.drawable.landscape11)

    Box(modifier = Modifier.fillMaxSize()
        .drawBehind {
            with(painter){
                draw(size)
            }
        }
        .drawWithContent {
            with(drawContext.canvas.nativeCanvas) {
                val checkPoint = saveLayer(null, null)

                // Destination
                drawContent()

                // Source
                drawRoundRect(
                    topLeft = Offset(
                        x = (size.width - widthInPx) / 2,
                        y = offsetInPx
                    ),
                    size = Size(widthInPx, heightInPx),
                    cornerRadius = CornerRadius(30f, 30f),
                    color = Color.Transparent,
                    blendMode = BlendMode.Clear
                )
                restoreToCount(checkPoint)
            }
        }
        .blur(20.dp)
        .drawBehind {
            with(painter){
                draw(size)
            }
        }
    )
}