prakash mohan
prakash mohan

Reputation: 43

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

Upvotes: 4

Views: 1977

Answers (1)

Thracian
Thracian

Reputation: 67208

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

Upvotes: 3

Related Questions