'Android Compose: draw transparent circle on image

I have an image and I want to draw dark rectangle over it with a transparent circle, so the result will be something like this: https://www.google.com/url?sa=i&url=https%3A%2F%2Fstackoverflow.com%2Fquestions%2F36763696%2Fhow-to-create-a-transparent-circle-inside-rectangle-shape-in-xml-in-android&psig=AOvVaw18ZjvY-j8QyYzFw1xsmOdJ&ust=1630768957499000&source=images&cd=vfe&ved=0CAsQjRxqFwoTCPD1mveN4_ICFQAAAAAdAAAAABAE

I have ended up with this code:

Box(modifier = Modifier
        .clip(RectangleShape)
        .fillMaxSize()
        .background(Color.Black)
        .pointerInput(Unit) {
            detectTransformGestures { centroid, pan, zoom, rotation ->
                scale *= zoom
            }
        }) {
        Image(
            modifier = Modifier
                .align(Alignment.Center)
                .graphicsLayer(
                    scaleX = maxOf(.2f, minOf(5f, scale)),
                    scaleY = maxOf(.2f, minOf(5f, scale))
                ),
            bitmap = bitmap.asImageBitmap(),
            contentDescription = null
        )
        Canvas(modifier = Modifier.fillMaxSize(), onDraw = {
            drawRect(Color.Black.copy(alpha = 0.8f))
            drawCircle(
                Color.Transparent,
                style = Fill,
                blendMode = BlendMode.Clear
            )
        })
    }

But it seems like it just draws a dark circle on top of the image instead of clearing darken rectangle...

It would be also super handy if you would suggest how to crop image based on this circle coordinates.



Solution 1:[1]

You need to use clipPath in this case.

Canvas(modifier = Modifier.fillMaxSize(), onDraw = {
    val circlePath = Path().apply {
        addOval(Rect(center, size.minDimension / 2))
    }
    clipPath(circlePath, clipOp = ClipOp.Difference) {
        drawRect(SolidColor(Color.Black.copy(alpha = 0.8f)))
    }
})

Solution 2:[2]

You don't need to necessarily use clipPath, you can use Porterduff(BlendMode) modes too, which is actually preferred way when you check sample codes or questions for clearing or removing some pixels or manipulating pixels.

It only requires small modifications to your code to work, you can check my answer how to apply Porterduff modes here.

1- adding graphics layer with alpha less than 1f

.graphicsLayer {
    alpha = .99f
}

will fix the issue.

Full implementation

Box(
    modifier = Modifier
        .background(Color.Black)
        .fillMaxSize()
) {

    Image(
        modifier = Modifier.fillMaxSize(),
        painter = painterResource(id = R.drawable.landscape),
        contentScale = ContentScale.Crop,
        contentDescription = null
    )
    Canvas(
        modifier = Modifier
            .fillMaxSize()
            // ONLY ADD THIS
            .graphicsLayer {
                alpha = .99f
            }
    ) {

        // Destination
        drawRect(Color.Black.copy(alpha = 0.8f))

        // Source
        drawCircle(
            color = Color.Transparent,
            blendMode = BlendMode.Clear
        )

    }
}

2- Saving to a layer

with(drawContext.canvas.nativeCanvas) {
    val checkPoint = saveLayer(null, null)

    // Destination
    drawRect(Color.Black.copy(alpha = 0.8f))

    // Source
    drawCircle(
        color = Color.Transparent,
        blendMode = BlendMode.Clear
    )
    restoreToCount(checkPoint)
}

Full Implementation

    Canvas(modifier = Modifier
        .fillMaxSize()
    ) {

        with(drawContext.canvas.nativeCanvas) {
            val checkPoint = saveLayer(null, null)

            // Destination
            drawRect(Color.Black.copy(alpha = 0.8f))

            // Source
            drawCircle(
                color = Color.Transparent,
                blendMode = BlendMode.Clear
            )
            restoreToCount(checkPoint)
        }
    }
}

Result

enter image description here

Also you can check here to get familiar with BlendModes, path operations and more about canvas.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Pylyp Dukhov
Solution 2