Search code examples
androidandroid-jetpack-composeandroid-jetpack-compose-canvasjetpack-compose-drawscope

Compose - Remove part of background of an image


I'd like to create a component with following result, assuming that the leaf is an existing ImageVector

enter image description here

With the following code:

@Composable
fun MyIcon(
    modifier: Modifier = Modifier,
) {

    Box(modifier = modifier) {
        Image(imageVector = leafImageVector)
        Ring( // the part that need to be removed from Image
            modifier = Modifier
                .align(Alignment.BottomEnd)
                .padding(
                    end = 4.dp,
                    bottom = 8.dp
                ),
        )
        Badge(
            modifier = Modifier
                .align(Alignment.BottomEnd)
                .padding(
                    end = 8.dp,
                    bottom = 8.dp
                ),
            badgeColor = badgeColor
        )
    }
}

@Composable
private fun Ring(
    modifier: Modifier = Modifier,
) {

    Canvas(
        modifier = modifier.size(8.dp),
        onDraw = {

            val strokeWidth = 8f
            drawCircle(
                color = Color.Black, // Change this to Transparent or Unspecified does not help
                radius = 12f,
                style = Stroke(
                    width = strokeWidth
                )
            )

        }
    )
}

@Composable
fun Badge(
    modifier: Modifier = Modifier,
    badgeColor: Color,
) {
    Box(
        modifier = modifier
            .size(8.dp)
            .clip(CircleShape)
            .background(
                badgeColor
            )
    )
}

The black part is the part I need to be removed from the Image


Solution

  • You can use BlendMods inside Modifier.drawWithContent to remove some part of Composable, it doesn't necessarily need to be Image for this to work. You can clip or cut from any Composable.

    How to clip or cut a Composable?

    enter image description here

    @Preview
    @Composable
    fun ImageEraseTest() {
        Column(
            modifier = Modifier.fillMaxSize().padding(32.dp)
        ) {
            Image(
                modifier = Modifier
                    .size(80.dp)
                    .border(2.dp, Color.Red)
                    .graphicsLayer {
                        compositingStrategy = CompositingStrategy.Offscreen
                    }
                    .drawWithContent {
                        val radius = 16.dp.toPx()
                        val badgeRadius = 12.dp.toPx()
    
                        val center = Offset(
                            size.width - radius ,
                            size.height - radius
                        )
                        
                        // Destination
                        drawContent()
                        
                        // Source
                        drawCircle(
                            color = Color.Transparent,
                            radius = radius,
                            center = center,
                            blendMode = BlendMode.Clear
                        )
    
                        drawCircle(
                            color = Color.Red,
                            radius = badgeRadius,
                            center = center
                        )
                    },
                imageVector = Icons.Default.AccountCircle,
                contentDescription = null
            )
        }
    }
    

    You can also draw red circle if you wish to as in example above.