在撰写中获取可见性画布绘制

2023-12-15

我有一个画布,可以在其中绘制两个相同大小的图像,并且我已经实现了一个触摸侦听器,可以在其中“擦除”其中一个图像,我想知道是否有可能知道该图像的可见性百分比我正在“擦除”。

val overlayImageLoaded = rememberAsyncImagePainter(
        model = overlayImage,
    )
    val baseImageLoaded = rememberAsyncImagePainter(
        model = baseImage
    )
    Canvas(modifier = modifier
        .size(220.dp)
        .clip(RoundedCornerShape(size = 16.dp))
        .pointerInteropFilter {
            when (it.action) {
                MotionEvent.ACTION_DOWN -> {
                    currentPath.moveTo(it.x, it.y)
                }

                MotionEvent.ACTION_MOVE -> {
                    onMovedOffset(it.x, it.y)
                }
            }
            true
        }) {

        with(overlayImageLoaded) {
            draw(size = Size(size.width, size.height))

        }

        movedOffset?.let {
            currentPath.addOval(oval = Rect(it, currentPathThickness))
        }

        clipPath(path = currentPath, clipOp = ClipOp.Intersect) {
            with(baseImageLoaded) {
                draw(size = Size(size.width, size.height))
            }
        }
    }

我有一些想法:

因为我想要知道图像是否已被删除至少 70%,假设我考虑过存储onMovedOffset放入列表中,因为我知道画布的大小和路径厚度,所以我可以对用户所看到的内容进行微积分,但这也许有点矫枉过正。

另外,我还考虑过每次用户移动时将画布绘制为位图,然后使用一种方法将位图与位图进行比较并检查相等的百分比。

目标是了解用户是否已删除至少 70% 的图像并且图像不再可见。


这个问题的回答相当复杂,涉及很多层面。可以对线程进行一些优化以比较后台线程中的像素。

第一步创建原始 ImageBitmap 的副本以适合屏幕上的可组合项。由于我们擦除屏幕上的像素,我们应该使用 sc

// 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)
    Bitmap.createScaledBitmap(imageBitmap.asAndroidBitmap(), imageWidth, imageHeight, false)
        .asImageBitmap()
        .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()
}

第二步是创建一个androidx.compose.ui.graphics.Canvas(imageBitmap)将更改应用到 imageBitmap 上在这个答案中。查看此内容以熟悉如何操作绘制到空位图的位图

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

第三步创建要从位图中擦除的颜料

val paint = remember {
    Paint()
}

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

第四步是用手势从位图中擦除并计算原始像素与当前擦除位图的差异

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.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

            matchPercent = compareBitmaps(
                originalPixels,
                erasedBitmap,
                imageWidth,
                imageHeight
            )
        }
        else -> Unit
    }

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

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

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

第五步是将原始像素与擦除的位图进行比较,以确定它们匹配的百分比

private fun compareBitmaps(
    originalPixels: IntArray,
    erasedBitmap: ImageBitmap,
    imageWidth: Int,
    imageHeight: Int
): Float {

    var match = 0f

    val size = imageWidth * imageHeight
    val erasedBitmapPixels = IntArray(size)

    erasedBitmap.readPixels(
        buffer = erasedBitmapPixels,
        startX = 0,
        startY = 0,
        width = imageWidth,
        height = imageHeight
    )

    erasedBitmapPixels.forEachIndexed { index, pixel: Int ->
        if (originalPixels[index] == pixel) {
            match++
        }
    }

    return 100f * match / size
}

全面实施

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


    var matchPercent by remember {
        mutableStateOf(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 = remember {
            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 = 30f
            }
        }


        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.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

                    matchPercent = compareBitmaps(
                        originalPixels,
                        erasedBitmap,
                        imageWidth,
                        imageHeight
                    )
                }
                else -> Unit
            }

            with(canvas.nativeCanvas) {
                drawColor(android.graphics.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) Color.LightGray else Color.White,
                                topLeft = Offset(x * checkerWidth, y * checkerHeight),
                                size = Size(checkerWidth, checkerHeight)
                            )
                        }
                    }
                }
                .matchParentSize()
                .border(2.dp, 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", color = Color.Red, fontSize = 22.sp)

}

Result

enter image description here

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在撰写中获取可见性画布绘制 的相关文章

随机推荐

  • 为什么 IIS Express 在加载 javascript 和 CSS 时返回 HTTP 500 错误?

    我正在尝试使用 IIS Express 开发 ASP NET MVC5 解决方案进行本地调试 通常 Chrome 在尝试加载某些 JS 和 CSS 文件时会报告 HTTP500 错误 有些使用 MVC 的内置捆绑和缩小功能 有些则使用自己的
  • 错误:使用 wikixmlj 解析 xml 文件时出现 xml.sax.SAXParseException

    我正在使用解析维基百科 xml 转储wikixmlj并收到以下错误 org xml sax SAXParseException lineNumber 64243259 columnNumber 371 JAXP00010004 The ac
  • Nodejs FS 模块返回 no such file or dir 错误

    Code fs readdir commands err files gt Do something Error ENOENT 没有这样的文件或目录 scandir commands 文件夹 commands does存在 这个文件是src
  • MapKit 中的 MapTypeStyle

    我想知道是否有任何方法可以配置我们的 MapKit 地图 就像我们在 Google 地图 API 中使用 MapTypeStyle 对象一样 如果我参考Apple文档 MKMapView有一个mapType选项 它需要MKMapType常量
  • MySQL删除语句优化

    我有一些删除查询要针对一些相当大的表 100 GB 运行 并且我想尽可能地优化它们 delete from table1 where column1 lt date sub now interval 100 hour 第 1 列是datet
  • 如何使用 C# .NET 将屏幕捕获为视频?

    是否有一些库可以将屏幕捕获为压缩视频文件或可以执行此操作的某种解决方案 此代码使用 NuGet 上提供的 SharpAvi using System using System Drawing using System Drawing Ima
  • 生成唯一ID的公式?

    我想了解一些关于在不使用 GUID 的情况下生成唯一 id 的想法 最好我希望唯一值是 int32 类型 我正在寻找可用于数据库主键以及 url 友好的东西 这些可以被认为是独特的吗 int DateTime Now Ticks int D
  • 在Java中,如何检查输入是否是数字?

    我正在制作一个简单的程序 可以让您添加比赛的结果以及他们完成比赛所用的秒数 为了输入时间 我这样做了 int time Integer parseInt JOptionPane showInputDialog Enter seconds 所
  • d3.layout.histogram() 和属性在 v4 中不起作用

    我想将基于 D3 js v3 的代码 转换 为 D3 js v4 我不知道我必须在以下代码中更改什么才能不显示任何错误 var data d3 layout histogram bins resolution frequency 0 res
  • 负边距删除静态同级的背景属性

    我在底部使用负边距来将相邻元素拉到与当前元素重叠 我的目的是让它重叠 但我希望整个 div 重叠在图像上方 但是 事实证明它也删除了拉动元素的背景 有人可以解释一下吗
  • SQL Server - 重写触发器以避免基于游标的方法

    如果我有桌子Test有两列num1 and num2以及下面的触发器 它只会在插入 num1 时增加 num2 DECLARE PROC NEWNUM1 VARCHAR 10 DECLARE NEWNUM2 numeric 20 DECLA
  • MySQL 和 PHP - 如何显示字段值等于 x 的所有行?

    我有一个数据库表 ff projections 其中包含以下字段 ID Player Position Team Pass Yds Pass TDs Int Thrown Rush Yds Rush TDs Rec Yds Rec TDs
  • MAC iphone SDK中的subversion无法解析用户文件

    我是 iPhone 新手 刚刚用 Subversion 更新了我的项目 现在 如果我尝试打开该项目 我会收到以下错误 无法打开项目 Users dualg4 DEV MacStubs MacStubs xcodeproj 无法打开 因为无法
  • 使用 jQuery 解析 XML

    我有以下 xml area
  • QRunnable 尝试中止任务

    是否可以中止 QRunnable 任务 即使在文档中我也找不到任何方法 多谢 不 您不能中止 QRunnable 任务 在 Qt 中脏中止线程的唯一方法是通过QThread terminate 这是不鼓励的 QThreadPool uses
  • 制作一个函数在返回之前等待事件?

    function myFunction wait what I put there return myFunction this is an event when its triggered I want function to resum
  • 如何使用python从内网站点抓取URL数据?

    我需要一个 Python Warrior 来帮助我 我是个菜鸟 我正在尝试使用模块 urllib 从内部网站点抓取某些数据 但是 由于这是我公司的网站 仅供员工查看 而不可供公众查看 我认为这就是我得到此代码的原因 IOError http
  • WPF 绑定到样式中另一个属性的绑定

    我不确定提出这个问题的最佳方式 抱歉问题标题含糊不清 但本质上我想使用从数据上下文传递属性的值转换器在 TextBox 上设置 MaxLength 属性 以及传入属性上的属性作为转换器参数 我想以一种风格来完成这一切 而不是在逐个控制的基础
  • 在撰写中获取可见性画布绘制

    我有一个画布 可以在其中绘制两个相同大小的图像 并且我已经实现了一个触摸侦听器 可以在其中 擦除 其中一个图像 我想知道是否有可能知道该图像的可见性百分比我正在 擦除 val overlayImageLoaded rememberAsync
  • 如何在phonegap中播放swf文件?

    我正在 android 的phonegap 中开发一个Flash 应用程序 我尝试过将 flash 嵌入到 html 中 但没有成功 它显示空白 什么也没有出现 然后我尝试使用 childBrowser childbrowser 打开 但仍