Search code examples
androidandroid-jetpack-composeandroid-canvasandroid-jetpack-compose-canvas

Jetpack compose Canvas blendmode not working as expected


I'm attempting to apply blend mode to two shapes within Jetpack compose's canvas. Based on this blog I know roughly what the expected output should look like though I am not getting similar results.

For example, with the following simple Box + Canvas with two shapes, with the blend mode SrcIn

Box(
            contentAlignment = Alignment.Center,
            modifier = Modifier.size(290.dp)
        ) {
            val sizeInPx = with(LocalDensity.current) { 150.dp.toPx() }

            Canvas(
                modifier = Modifier.fillMaxSize()
            ) {
                    drawCircle(
                        color = Color.Red,
                        radius = sizeInPx,
                    )

                    drawRect(
                        color = Color.Blue,
                        size = Size(sizeInPx, sizeInPx),
                        blendMode = BlendMode.SrcIn
                    )

            }
}

I would expect a red circle, and a blue square clipped to the shape of the red circle. Yet the output UI is as if no blend mode has been added at all

What am I doing wrong?


Solution

  • Changing alpha less then 1f creates a layer as buffer that's why it works. Other way of achieving this is to use layer directly if you don't want to change alpha. You can see my answere about it here

    Canvas(modifier = canvasModifier) {
    
        val canvasWidth = size.width.roundToInt()
        val canvasHeight = size.height.roundToInt()
    
        with(drawContext.canvas.nativeCanvas) {
            val checkPoint = saveLayer(null, null)
    
            drawCircle(
                  color = Color.Red,
                  radius = sizeInPx,
            )
    
            drawRect(
                color = Color.Blue,
                size = Size(sizeInPx, sizeInPx),
                blendMode = BlendMode.SrcIn
            )
            restoreToCount(checkPoint)
        }
    }
    

    In painter code Android team uses it as

    private fun configureAlpha(alpha: Float) {
        if (this.alpha != alpha) {
            val consumed = applyAlpha(alpha)
            if (!consumed) {
                if (alpha == DefaultAlpha) {
                    // Only update the paint parameter if we had it allocated before
                    layerPaint?.alpha = alpha
                    useLayer = false
                } else {
                    obtainPaint().alpha = alpha
                    useLayer = true
                }
            }
            this.alpha = alpha
        }
    }
    

    And check alpha to apply layer

      fun DrawScope.draw(
            size: Size,
            alpha: Float = DefaultAlpha,
            colorFilter: ColorFilter? = null
        ) {
            configureAlpha(alpha)
            configureColorFilter(colorFilter)
            configureLayoutDirection(layoutDirection)
    
            // b/156512437 to expose saveLayer on DrawScope
            inset(
                left = 0.0f,
                top = 0.0f,
                right = this.size.width - size.width,
                bottom = this.size.height - size.height
            ) {
    
                if (alpha > 0.0f && size.width > 0 && size.height > 0) {
                    if (useLayer) {
                        val layerRect = Rect(Offset.Zero, Size(size.width, size.height))
                        // TODO (b/154550724) njawad replace with RenderNode/Layer API usage
                        drawIntoCanvas { canvas ->
                            canvas.withSaveLayer(layerRect, obtainPaint()) {
                                onDraw()
                            }
                        }
                    } else {
                        onDraw()
                    }
                }
            }
        }
    }