Android 在位图上组合黑色而不是擦除透明

问题描述 投票:0回答:1

我正在尝试在 Jetpack Compose 位图上实现将像素擦除为透明颜色。但是当我加载位图以在画布上显示时,已擦除的像素显示为黑色。这是我的可组合项:

@RequiresApi(Build.VERSION_CODES.Q)
@Composable
fun EraseByHandCanvasContent(
    modifier: Modifier = Modifier,
    eraseByHandViewModel: EraseByHandViewModel = koinViewModel()
) {

    val drawingByHandState = eraseByHandViewModel.drawingByHandState.collectAsState()
    val bitmap = eraseByHandViewModel.pickedImage.collectAsState()

    val paint = remember {
        derivedStateOf {
            Paint().apply {
                isAntiAlias = true
                color = Color.Transparent.toArgb()
                style = Paint.Style.STROKE
                strokeWidth = drawingByHandState.value.eraseBrushSize
                xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)

            }
        }
    }

    Box(
        modifier = modifier.fillMaxWidth(),
        contentAlignment = Alignment.TopCenter,
    ) {
        Canvas(modifier = Modifier
            .fillMaxSize()
            .onPlaced { layoutCoordinates ->
                eraseByHandViewModel.setCanvasSize(layoutCoordinates.size)
            }
            .pointerInput(Unit) {
                detectDragGestures(
                    onDrag = { change, _ ->
                        eraseByHandViewModel.onDrawingAction(
                            DrawingByHandAction.onDraw(
                                offset = change.position
                            )
                        )
                        change.consume()
                    },
                    onDragStart = { offset ->
                        eraseByHandViewModel.onDrawingAction(
                            DrawingByHandAction.onNewPathDraw(
                                offset = offset
                            )
                        )
                    },
                    onDragEnd = {
                        eraseByHandViewModel.onDrawingAction(
                            DrawingByHandAction.onPathEnd()
                        )
                    }
                )
            }) {

            val currentBitmap =
                drawingByHandState.value.tempBitmap
                    ?: bitmap.value?.copy(Bitmap.Config.ARGB_8888, true)?.apply {
                        isPremultiplied = true
                        setHasAlpha(true)
                    }

            currentBitmap?.let { baseBitmap ->
                val outputBitmap = Bitmap.createScaledBitmap(
                    baseBitmap,
                    size.width.toInt(),
                    size.height.toInt(),
                    false,
                )

                val combinedCanvas = android.graphics.Canvas(outputBitmap)

                combinedCanvas.drawPath(
                    drawingByHandState.value.currentPath.asAndroidPath(),
                    paint.value,
                )

                eraseByHandViewModel.setTempBitmap(outputBitmap)

                drawImage(
                    image = outputBitmap.asImageBitmap(),
                )

            }
        }
    }
}

同样出于调试目的,我检查了下面的位图像素,我得到的 countTransparent 值始终为 0,因此绘画过程也存在问题:

 private fun countTransparentPixels(bitmap: Bitmap) {
        var countTransparent = 0
        var countNonTransparent = 0

        val copyBitmap = bitmap.copy(Bitmap.Config.ARGB_8888,true).apply {
            setHasAlpha(true)
            isPremultiplied = true
        }

        for (x in 0 until copyBitmap.width) {
            for (y in 0 until copyBitmap.height) {
                val pixel = copyBitmap.getPixel(x, y)
                if (pixel == androidx.compose.ui.graphics.Color.Transparent.toArgb()){   
                    countTransparent++
                } else {
                    countNonTransparent++
                }
            }
        }

        Log.d("COUNTERS", "$countTransparent---$countNonTransparent")

    }

我还使用 Coil 在我的应用程序上显示位图图像,当我在图库上加载已擦除的位图时(已擦除的像素在图库中显示为白色),SubcomposeAsyncImage 也将位图已擦除的像素显示为黑色,就像我的 EraseByHandCanvasContent 可组合项一样。我无法理解,我在绘制对象和 drawImage() 方法上尝试了不同的 BlendMode 组合,但我无法获得透明位图。

val copyBitmap = selectedImageState.value?.copy(Bitmap.Config.ARGB_8888,true)
            copyBitmap?.apply {
                setHasAlpha(true)    
                isPremultiplied = true
            }

SubcomposeAsyncImage(
                        modifier = Modifier
                            .fillMaxWidth()
                            .weight(1f)
                            .clickable {
                                onPickImage()
                            },

                        model = copyBitmap,
                        contentDescription = "",
                        alignment = Alignment.TopCenter,
                        contentScale = ContentScale.Crop,
                    )
android bitmap android-jetpack-compose android-canvas eraser
1个回答
0
投票

当您从实际位图中擦除时,您需要使用

androidx.compose.ui.graphics.Canvas
而不是
androidx.compose.foundation.Canvas
,后者是可组合的。

下面的代码片段是我针对另一个问题发布的代码片段,用于检查删除了哪个百分比的图像。

结果

完整代码和演示可在存储库中找到。

@Composable
fun EraseBitmapSample(imageBitmap: ImageBitmap, modifier: Modifier) {


    var matchPercent by remember {
        mutableFloatStateOf(100f)
    }

    BoxWithConstraints(modifier) {

        // 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 imageWidth = constraints.maxWidth
        val imageHeight = constraints.maxHeight


        val drawImageBitmap: ImageBitmap = remember(imageBitmap) {
            Bitmap.createScaledBitmap(
                imageBitmap.asAndroidBitmap(),
                imageWidth,
                imageHeight,
                false
            )
                .asImageBitmap()
        }

        // Pixels of scaled bitmap, we scale it to composable size because we will erase
        // from Composable on screen
        val originalPixels: IntArray = remember {
            val buffer = IntArray(imageWidth * imageHeight)
            drawImageBitmap
                .readPixels(
                    buffer = buffer,
                    startX = 0,
                    startY = 0,
                    width = imageWidth,
                    height = imageHeight
                )

            buffer
        }

        val erasedBitmap: ImageBitmap = remember {
            Bitmap.createBitmap(imageWidth, imageHeight, Bitmap.Config.ARGB_8888).asImageBitmap()
        }

        val canvas: Canvas = remember {
            Canvas(erasedBitmap)
        }

        val paint = remember {
            Paint()
        }

        val erasePaint = remember {
            Paint().apply {
                blendMode = BlendMode.Clear
                this.style = PaintingStyle.Stroke
                strokeWidth = 50f
            }
        }

        LaunchedEffect(key1 = currentPosition) {
            snapshotFlow {
                currentPosition
            }
                .map {
                    compareBitmaps(
                        originalPixels,
                        erasedBitmap,
                        imageWidth,
                        imageHeight
                    )
                }
                .onEach {
                    matchPercent = it
                }
                .flowOn(Dispatchers.Default)
                .launchIn(this)
        }

        canvas.apply {
            val nativeCanvas = this.nativeCanvas
            val canvasWidth = nativeCanvas.width.toFloat()
            val canvasHeight = nativeCanvas.height.toFloat()

            when (motionEvent) {

                MotionEvent.Down -> {
                    erasePath.moveTo(currentPosition.x, currentPosition.y)
                    previousPosition = currentPosition

                }

                MotionEvent.Move -> {

                    erasePath.quadraticTo(
                        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
            }

            with(canvas.nativeCanvas) {
                drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)

                drawImageRect(
                    image = drawImageBitmap,
                    dstSize = IntSize(canvasWidth.toInt(), canvasHeight.toInt()),
                    paint = paint
                )

                drawPath(
                    path = erasePath,
                    paint = erasePaint
                )
            }
        }

        val canvasModifier = 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()
            },
            delayAfterDownInMillis = 20
        )

        Image(
            modifier = canvasModifier
                .clipToBounds()
                .drawBehind {
                    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) androidx.compose.ui.graphics.Color.LightGray else androidx.compose.ui.graphics.Color.White,
                                topLeft = Offset(x * checkerWidth, y * checkerHeight),
                                size = Size(checkerWidth, checkerHeight)
                            )
                        }
                    }
                }
                .matchParentSize()
                .border(2.dp, androidx.compose.ui.graphics.Color.Green),
            bitmap = erasedBitmap,
            contentDescription = null,
            contentScale = ContentScale.FillBounds
        )
    }

    Text("Original Bitmap")

    Image(
        modifier = modifier,
        bitmap = imageBitmap,
        contentDescription = null,
        contentScale = ContentScale.FillBounds
    )

    Text(
        "Bitmap match ${matchPercent.toInt()}%",
        color = androidx.compose.ui.graphics.Color.Red,
        fontSize = 22.sp
    )
}
最新问题
© www.soinside.com 2019 - 2025. All rights reserved.