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?
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()
}
}
}
}
}