androidandroid-jetpack-composeandroid-canvasandroid-jetpack-compose-canvas

Android Compose Canvas producing unexpected BlendMode results


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?


Solution

  • 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