Android Compose Canvas producing unexpected BlendMode results

213 Views Asked by At

I'm looking to clip an image by approximately a quadrant of a circle to get the following affect:

Goal

My initial thought was to use BlendMode in a Canvas using drawWithContent. However, I'm finding that all the BlendModes that I've tried either just show the square image or show a square image with an opaque circle drawn over it. For both dstIn and srcIn, for example, I see:

Result

Source code:

GlideImage(
    modifier = Modifier
        .requiredSize(158.dp)
        .graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen)
        .drawWithContent {
            val radius = size.width - 28.dp.toPx()
            drawContent()
            drawCircle(
                blendMode = BlendMode.DstIn,
                color = Color.Transparent,
                radius = radius,
                center = Offset(radius, radius),
            )
        },
    contentScale = ContentScale.Crop,
    model = model.imageUrl,
    contentDescription = null,
)

Any ideas on what I'm missing?

1

There are 1 best solutions below

1
On BEST ANSWER

The reason it doesn't wok you apply BlendMode to region only available to circle not all of the pixels in Composable. Because of that it won't be applied intersection or section where is not intersection of both.

If you are able draw image with Painter or ImageBitmap you can do it like this

@Preview
@Composable
private fun BlendModeSample() {
    Column(
        modifier = Modifier.padding(20.dp)
    ) {

        val image = ImageBitmap.imageResource(R.drawable.landscape1)

        Canvas(
            modifier = Modifier
                .size(135.dp)
                .graphicsLayer {
                    compositingStrategy = CompositingStrategy.Offscreen
                }
        ) {
            val radius = size.width - 28.dp.toPx()

            drawCircle(
                color = Color.Red,
                radius = radius,
                center = Offset(radius, radius),
            )

            drawImage(
                image,
                dstSize = IntSize(size.width.toInt(), size.height.toInt()),
                blendMode = BlendMode.SrcIn)
        }
    }
}

enter image description here

If that's not an option you can get difference of a rectangle and circle with paths and use that shape as mask using BlendMode.

@Preview
@Composable
private fun BlendModeTest() {
    Column(
        modifier = Modifier.padding(20.dp)
    ) {
        Image(
            modifier = Modifier
                .size(135.dp)
                .graphicsLayer {
                    compositingStrategy = CompositingStrategy.Offscreen
                }
                .drawWithCache {

                    val radius = size.width - 28.dp.toPx()
                    val path = Path()
                    val pathCircle = Path()

                    if (path.isEmpty) {
                        path.addRect(
                            Rect(
                                offset = Offset.Zero,
                                size = size
                            )
                        )

                    }

                    if (pathCircle.isEmpty) {
                        pathCircle.addOval(
                            Rect(
                                center = Offset(
                                    radius, radius
                                ),
                                radius = radius
                            )
                        )
                    }

                    path.op(path, pathCircle, PathOperation.Difference)

                    onDrawWithContent {
                        drawContent()
                        drawPath(path, Color.Transparent, blendMode = BlendMode.DstIn)

                    }
                },
            painter = painterResource(R.drawable.landscape2),
            contentScale = ContentScale.Crop,
            contentDescription = null
        )
    }
}

enter image description here