Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jetpack Compose: Drawing outside of Composable bounds While using Transparent Colors

I'm creating a custom component in Jetpack Compose that needs to be drawn beyond the component's bounds, Which might use transparent color in BlendMode. I'm using CompositingStrategy for handling this but facing some issues.

Below is the example recreation of the issue.

@Composable
@Preview(widthDp = 600, heightDp = 600)
private fun Preview() {
    Column(
        Modifier.fillMaxSize().background(Color.Blue),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Canvas(
            modifier = Modifier.size(250.dp).graphicsLayer(
                compositingStrategy = CompositingStrategy.Offscreen,
//                compositingStrategy = CompositingStrategy.ModulateAlpha
            ).border(1.dp, color = Color.Black)
        ) {
            drawCircle(
                Color.Red,
                (size.minDimension/2f)*1.5f
            )
            drawCircle(
                Color.Transparent,
                (size.minDimension/2f)*1f,
                blendMode = BlendMode.Src
            )
        }
    }
}

On using CompositingStrategy.Offscreen: Handles alpha correctly but clips the drawing to the component's size.

Result with CompositingStrategy.Offscreen

On using CompositingStrategy.ModulateAlpha: Solves the clipping issue, but transparent colors Inner Circle are not working.

Result with CompositingStrategy.ModulateAlpha

So is there any way of achieving both of these at the same time?

Below is the Result i am expecting, where the inner circle is transparent, So the blue background is visible:

Expected result

like image 585
Rahim .H Avatar asked Jan 21 '26 22:01

Rahim .H


2 Answers

compositingStrategy = CompositingStrategy.Offscreen

clips content in Composable and Modifiers after it.

By default even if you can draw anything to any Composable even if their size is zero.

To apply Porter-Duff or blend modes correctly you can create a layer with

private fun DrawScope.drawWithLayer(block: DrawScope.() -> Unit) {
    with(drawContext.canvas.nativeCanvas) {
        val checkPoint = saveLayer(null, null)
        block()
        restoreToCount(checkPoint)
    }
}

And draw inside it with

@Composable
@Preview(widthDp = 600, heightDp = 600)
private fun Preview() {
    Column(
        Modifier.fillMaxSize().background(Color.Blue),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Canvas(
            modifier = Modifier.size(250.dp)
                .border(1.dp, color = Color.Black)
        ) {

            drawWithLayer {
                drawCircle(
                    Color.Red,
                    (size.minDimension/2f)*1.5f
                )
                drawCircle(
                    Color.Transparent,
                    (size.minDimension/2f)*1f,
                    blendMode = BlendMode.Src
                )
            }
        }
    }
}

enter image description here

Jetpack Compose Applying PorterDuffMode to Image

How to clip or cut a Composable?

like image 52
Thracian Avatar answered Jan 24 '26 22:01

Thracian


You can create the shape you mention using "difference" path operation: create a path for the outer circle and a path for the inner circle, then take the difference between the two paths, which gives the shape you mention.

Code:

@Composable
@Preview(widthDp = 600, heightDp = 600)
fun App() {
    Column(
        Modifier
            .fillMaxSize()
            .background(Color.Blue),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Canvas(
            modifier = Modifier
                .size(250.dp)
                .border(1.dp, color = Color.Black)
        ) {

            val cx = size.width/2f
            val cy = size.height/2f

            val c = Offset(cx, cy)

            val r = (size.minDimension/2f)

            val outerPath = Path().apply {
                addOval(
                    oval = Rect(
                        center = c,
                        radius = r*1.5f
                    )
                )
            }

            val innerPath = Path().apply {
                addOval(
                    oval = Rect(
                        center = c,
                        radius = r
                    )
                )
            }

            val path = Path().apply {
                op(outerPath, innerPath, PathOperation.Difference)
            }

            drawPath(
                path = path,
                color = Color.Red
            )
        }
    }
}

Result:

Demo

like image 39
Abdo21 Avatar answered Jan 24 '26 22:01

Abdo21



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!