3

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)
    }
}
mrzbn
  • 497
  • 1
  • 3
  • 15

1 Answers1

3

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

Thracian
  • 43,021
  • 16
  • 133
  • 222
  • Thanks for answering, Do you know how to save this as transparent ? – prakash mohan Jul 19 '22 at 06:54
  • ou can take a screenshot of Canvas using this library for instance, it will be converted to Bitmap, then you need to save it into physical folder. github.com/SmartToolFactory/Compose-Screenshot – Thracian Jul 19 '22 at 07:13
  • Thank you ,it worked but you know the grey part also saving along with the image, I want the image part only, Can you help me ? – prakash mohan Jul 19 '22 at 08:34
  • every screenshot library out there does this by capturing view on screen. You can stop drawing grey area before screenshot but then you will have white spaces as in your question. If you exactly want image to be saved. you should ask it as another question because it's not easy to answer in comments and out of scope of this question. You need to use androidx.compose.ui.graphics.Canvas which accepts Bitmap as parameter and path and blend modes – Thracian Jul 19 '22 at 08:39
  • You can check my question here to get the idea https://stackoverflow.com/questions/72168588/jetpack-compose-androidx-compose-ui-graphics-canvas-not-refreshing-correctly-for – Thracian Jul 19 '22 at 08:40
  • You need to use `val canvas: androidx.compose.ui.graphics.Canvas = Canvas(imageBitmap)` but if you check question it doesn't refresh correctly to draw a path on it. If it's solved you can get bitmap that is cleared – Thracian Jul 19 '22 at 08:43
  • @prakashmohan i finally figured out how to erase Bitmap. It's in this answer https://stackoverflow.com/questions/74496059/get-visibility-canvas-draw-in-compose?noredirect=1&lq=1 and simple version of it https://stackoverflow.com/questions/72168588/jetpack-compose-watermark-or-write-on-bitmap-with-androidx-compose-foundation-ca – Thracian Nov 29 '22 at 15:19