android - Jetpack 撰写水印或使用 androidx.compose.ui.graphics.Canvas 在位图上书写?

标签 android android-jetpack-compose android-jetpack-compose-canvas

使用 androidx.compose.foundation.Canvas、Jetpack Compose 的默认 Canvas 或带有 Modifier.drawBehind 的 Spacer{}

@Composable
fun Canvas(modifier: Modifier, onDraw: DrawScope.() -> Unit) =
    Spacer(modifier.drawBehind(onDraw

当 mutableState Offset 更改时正确刷新 Canvas 上的绘图

var offset by remember {
    mutableStateOf(Offset(bitmapWidth / 2f, bitmapHeight / 2f))
}  

Canvas(modifier = canvasModifier.fillMaxSize()) {
        val canvasWidth = size.width.roundToInt()
        val canvasHeight = size.height.roundToInt()
    
        drawImage(
            image = dstBitmap,
            srcSize = IntSize(dstBitmap.width, dstBitmap.height),
            dstSize = IntSize(canvasWidth, canvasHeight)
        )
    
        drawCircle(
            center = offset,
            color = Color.Red,
            radius = canvasHeight.coerceAtMost(canvasWidth) / 8f,
        )
    }

使用androidx.compose.ui.graphics.Canvas,Canvas接受ImageBitmap作为参数并按照其描述进行绘制

Create a new Canvas instance that targets its drawing commands to the provided ImageBitmap

我添加了完整的实现来轻松测试这一点,如果您能提出解决方案,我将不胜感激。

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

        val imageWidth = constraints.maxWidth
        val imageHeight = constraints.maxHeight

        val bitmapWidth = imageBitmap.width
        val bitmapHeight = imageBitmap.height

        var offset by remember {
            mutableStateOf(Offset(bitmapWidth / 2f, bitmapHeight / 2f))
        }


        val canvasModifier = Modifier.pointerMotionEvents(
            Unit,
            onDown = {
                val position = it.position
                val offsetX = position.x * bitmapWidth / imageWidth
                val offsetY = position.y * bitmapHeight / imageHeight
                offset = Offset(offsetX, offsetY)
                it.consumeDownChange()
            },
            onMove = {
                val position = it.position
                val offsetX = position.x * bitmapWidth / imageWidth
                val offsetY = position.y * bitmapHeight / imageHeight
                offset = Offset(offsetX, offsetY)
                it.consumePositionChange()
            },
            delayAfterDownInMillis = 20
        )

        val canvas: androidx.compose.ui.graphics.Canvas = Canvas(imageBitmap)
        

        val paint1 = remember {
            Paint().apply {
                color = Color.Red
            }
        }
        canvas.apply {
            val nativeCanvas = this.nativeCanvas
            val canvasWidth = nativeCanvas.width.toFloat()
            val canvasHeight = nativeCanvas.height.toFloat()

            drawCircle(
                center = offset,
                radius = canvasHeight.coerceAtMost(canvasWidth) / 8,
                paint = paint1
            )
        }


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

        Text(
            "Offset: $offset",
            modifier = Modifier.align(Alignment.BottomEnd),
            color = Color.White,
            fontSize = 16.sp
        )
    }
}

第一期,如果没有Text或其他读取Offset的内容,它永远不会刷新Canvas

第二期如下图所示。它没有清除之前在图像上的绘图,我尝试了 this question thread 中的所有可能的解决方案但它们都不起作用。

enter image description here

我尝试使用 BlendMode、drawColor(Color.TRANSPARENT,Mode.Multiply) 和 native Canvas 绘制图像,但许多组合仍然无法使用 Jetpack Compose Canvas 获得相同的结果。

    val erasePaint = remember {
        Paint().apply {
            color = Color.Transparent
            blendMode = BlendMode.Clear
        }
    }

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

    drawImage(imageBitmap, topLeftOffset = Offset.Zero, erasePaint)
    drawCircle(
        center = offset,
        radius = canvasHeight.coerceAtMost(canvasWidth) / 8,
        paint = paint1
    )
    
    restoreToCount(checkPoint)
}

我需要使用androidx.compose.ui.graphics.Canvas,因为你可以看到Canvas上的操作反射(reflect)到位图上,我计划使用它为裁剪位图创建基础

enter image description here

最佳答案

我终于在 6 个月后弄清楚了如何做到这一点,以及如何使用 androidx.compose.ui.graphics.Canvas 修改 Bitmap 实例

首先创建一个与原始位图尺寸相同的空可变位图。这就是我们要借鉴的。这里的技巧不是发送真实的位图,而是发送空的位图

val bitmapWidth = imageBitmap.width
val bitmapHeight = imageBitmap.height

val bmp: Bitmap = remember {
    Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888)
}

由于我们在底部没有绘制任何内容,因此我们可以使用drawColor(android.graphics.Color.TRANSPARENT, PorterDuff.Mode.CLEAR)

在每次绘制时清除,然后绘制图像并使用 Paint 应用任何混合模式

val paint = remember {
    Paint()
}

val erasePaint = remember {
    Paint().apply {
        color = Color.Red
        blendMode = BlendMode.SrcIn
    }
}

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

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

        drawCircle(
            center = offset,
            radius = 400f,
            paint = paint
        )

        drawImageRect(
            image = imageBitmap,
            dstSize = IntSize(canvasWidth.toInt(), canvasHeight.toInt()),
            paint = erasePaint
        )
    }
}

最后使用 Canvas to Image Composable 绘制我们使用的位图

Image(
    modifier = canvasModifier.border(2.dp, Color.Green),
    bitmap = bmp.asImageBitmap(),
    contentDescription = null,
    contentScale = ContentScale.FillBounds
)

或者您可以保存此修改后的带有水印的 ImageBitmap 或您在 Canvas 上绘制的任何叠加层

全面实现

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

    BoxWithConstraints(modifier) {

        val imageWidth = constraints.maxWidth
        val imageHeight = constraints.maxHeight

        val bitmapWidth = imageBitmap.width
        val bitmapHeight = imageBitmap.height

        var offset by remember {
            mutableStateOf(Offset(bitmapWidth / 2f, bitmapHeight / 2f))
        }

        val bmp: Bitmap = remember {
            Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888)
        }

        val canvas: Canvas = remember {
            Canvas(bmp.asImageBitmap())
        }

        val paint = remember {
            Paint()
        }

        val erasePaint = remember {
            Paint().apply {
                color = Color.Red
                blendMode = BlendMode.SrcIn
            }
        }

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

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

                drawCircle(
                    center = offset,
                    radius = 400f,
                    paint = paint
                )

                drawImageRect(
                    image = imageBitmap,
                    dstSize = IntSize(canvasWidth.toInt(), canvasHeight.toInt()),
                    paint = erasePaint
                )
            }
        }
        
        val canvasModifier = Modifier.pointerMotionEvents(
            Unit,
            onDown = {
                val position = it.position
                val offsetX = position.x * bitmapWidth / imageWidth
                val offsetY = position.y * bitmapHeight / imageHeight
                offset = Offset(offsetX, offsetY)
                it.consume()
            },
            onMove = {
                val position = it.position
                val offsetX = position.x * bitmapWidth / imageWidth
                val offsetY = position.y * bitmapHeight / imageHeight
                offset = Offset(offsetX, offsetY)
                it.consume()
            },
            delayAfterDownInMillis = 20
        )

        Image(
            modifier = canvasModifier.border(2.dp, Color.Green),
            bitmap = bmp.asImageBitmap(),
            contentDescription = null,
            contentScale = ContentScale.FillBounds
        )
    }
}

结果

enter image description here

关于android - Jetpack 撰写水印或使用 androidx.compose.ui.graphics.Canvas 在位图上书写?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/72168588/

相关文章:

android - Jetpack compose Canvas 混合模式未按预期工作

android - 带代理的 Flutter WebView

android - 识别 Android 中的 RTL 语言

Android 相当于多环境下的 web/app.config

android - 如何在 Jetpack Compose 中处理一次性操作?

kotlin - 在 Compose 中使用 Text() 时如何获取文本的字体大小?

android - 如何在不使用@Composable 注释的情况下为撰写功能创建扩展?

android - 如何在不启动模拟器的情况下在 Eclipse 中将 Android 项目编译为 .apk 文件?

android - 如何使用 Modifier 镜像 canvas 制作的可组合函数?