androidkotlincanvasandroid-jetpack-composeandroid-jetpack-compose-canvas

How to get transparent while erasing the canvas in Jetpack Compose , now I'm getting white color?


how can I make some parts of canvas transparent? I want user to be able to erase parts of an photo like this link shows to be transparent. my canvas code:

Canvas(
    modifier = modifier
        .background(Color.Transparent)
) {
    with(drawContext.canvas.nativeCanvas) {
        val checkPoint = saveLayer(null, null)
        drawImage(
            image = bitmap,
            srcSize = IntSize(bitmap.width, bitmap.height),
            dstSize = IntSize(canvasWidth, canvasHeight)
        )
        drawPath(
            path = erasePath,
            style = Stroke(
                width = 30f,
                cap = StrokeCap.Round,
                join = StrokeJoin.Round
            ),
            blendMode = BlendMode.Clear,
            color = Color.Transparent,
        )
        restoreToCount(checkPoint)
    }
}

Solution

  • What you get as Transparent is Color(0x00000000), white you get is the color of your background, even if you Canvas has transparent background, color of your root or parent Composable is white.

    You need to draw checker layout or checker image first, inside Layer you should draw your image and path with BlendMode.Clear

    val width = this.size.width
    val height = this.size.height
    
    val checkerWidth = 10.dp.toPx()
    val checkerHeight = 10.dp.toPx()
    
    val horizontalSteps = (width / checkerWidth).toInt()
    val verticalSteps = (height / checkerHeight).toInt()
    
    for (y in 0..verticalSteps) {
        for (x in 0..horizontalSteps) {
            val isGrayTile = ((x + y) % 2 == 1)
            drawRect(
                color = if (isGrayTile) Color.LightGray else Color.White,
                topLeft = Offset(x * checkerWidth, y * checkerHeight),
                size = Size(checkerWidth, checkerHeight)
            )
        }
    }
    
    val space = 20.dp.roundToPx()
    
    with(drawContext.canvas.nativeCanvas) {
        val checkPoint = saveLayer(null, null)
    
        // Destination
        drawImage(
            image = dstBitmap,
            dstOffset = IntOffset(
                space / 2,
                space / 2
            ),
            dstSize = IntSize(
                canvasWidth - space, canvasHeight - space
            )
        )
    
        // Source
        drawPath(
            color = Color.Transparent,
            path = erasePath,
            style = Stroke(
                width = 30f,
                cap = StrokeCap.Round,
                join = StrokeJoin.Round
            ),
            blendMode = BlendMode.Clear
        )
        restoreToCount(checkPoint)
    }
    

    Full implementation

    @Composable
    private fun MyImageDrawer(modifier: Modifier) {
    
        // This is the image to draw onto
        val dstBitmap = ImageBitmap.imageResource(id = R.drawable.landscape1)
    
    
        // Path used for erasing. In this example erasing is faked by drawing with canvas color
        // above draw path.
        val erasePath = remember { Path() }
    
        var motionEvent by remember { mutableStateOf(MotionEvent.Idle) }
        // This is our motion event we get from touch motion
        var currentPosition by remember { mutableStateOf(Offset.Unspecified) }
        // This is previous motion event before next touch is saved into this current position
        var previousPosition by remember { mutableStateOf(Offset.Unspecified) }
    
    
        val drawModifier = modifier
            .pointerMotionEvents(Unit,
                onDown = { pointerInputChange ->
                    motionEvent = MotionEvent.Down
                    currentPosition = pointerInputChange.position
                    pointerInputChange.consume()
                },
                onMove = { pointerInputChange ->
                    motionEvent = MotionEvent.Move
                    currentPosition = pointerInputChange.position
                    pointerInputChange.consume()
                },
                onUp = { pointerInputChange ->
                    motionEvent = MotionEvent.Up
                    pointerInputChange.consume()
                }
            )
    
        Canvas(modifier = drawModifier) {
    
            val canvasWidth = size.width.roundToInt()
            val canvasHeight = size.height.roundToInt()
    
            // Draw or erase depending on erase mode is active or not
    
            when (motionEvent) {
    
                MotionEvent.Down -> {
                    erasePath.moveTo(currentPosition.x, currentPosition.y)
                    previousPosition = currentPosition
    
                }
                MotionEvent.Move -> {
    
                    erasePath.quadraticBezierTo(
                        previousPosition.x,
                        previousPosition.y,
                        (previousPosition.x + currentPosition.x) / 2,
                        (previousPosition.y + currentPosition.y) / 2
    
                    )
                    previousPosition = currentPosition
                }
    
                MotionEvent.Up -> {
                    erasePath.lineTo(currentPosition.x, currentPosition.y)
                    currentPosition = Offset.Unspecified
                    previousPosition = currentPosition
                    motionEvent = MotionEvent.Idle
                }
                else -> Unit
            }
    
    
            val width = this.size.width
            val height = this.size.height
    
            val checkerWidth = 10.dp.toPx()
    
            val checkerHeight = 10.dp.toPx()
    
            val horizontalSteps = (width / checkerWidth).toInt()
            val verticalSteps = (height / checkerHeight).toInt()
    
            for (y in 0..verticalSteps) {
                for (x in 0..horizontalSteps) {
                    val isGrayTile = ((x + y) % 2 == 1)
                    drawRect(
                        color = if (isGrayTile) Color.LightGray else Color.White,
                        topLeft = Offset(x * checkerWidth, y * checkerHeight),
                        size = Size(checkerWidth, checkerHeight)
                    )
                }
            }
    
            val space = 20.dp.roundToPx()
    
            with(drawContext.canvas.nativeCanvas) {
                val checkPoint = saveLayer(null, null)
    
                // Destination
                drawImage(
                    image = dstBitmap,
                    dstOffset = IntOffset(
                        space / 2,
                        space / 2
                    ),
                    dstSize = IntSize(
                        canvasWidth - space, canvasHeight - space
                    )
                )
    
                // Source
                drawPath(
                    color = Color.Transparent,
                    path = erasePath,
                    style = Stroke(
                        width = 30f,
                        cap = StrokeCap.Round,
                        join = StrokeJoin.Round
                    ),
                    blendMode = BlendMode.Clear
                )
                restoreToCount(checkPoint)
            }
        }
    }
    

    Outcome

    enter image description here