Compose Canvas中如何计算被“擦除”图像的可见百分比?
在Jetpack Compose中计算被擦除图像的可见百分比
你在Jetpack Compose中实现了双图触摸擦除效果,想要计算被擦除图像的可见百分比,判断是否已擦除至少70%。以下是针对你的需求和思路的优化方案:
现有实现代码
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)) } } }
你的思路分析
- 思路1:存储
onMovedOffset结合画布尺寸计算——核心逻辑可行,但直接存坐标列表计算繁琐,可优化为基于路径/图形几何计算 - 思路2:转Bitmap对比像素——直观但性能开销高,适合精确判断但需优化触发时机
推荐解决方案
方法一:基于路径几何计算(高效低开销)
利用你已有的currentPath记录擦除区域,通过几何公式或Path工具计算覆盖面积,快速得到擦除比例,适合近似判断70%的需求。
实现步骤
- 记录所有擦除的椭圆区域(或直接维护合并后的路径)
- 计算单个椭圆面积并累加(处理重叠可选择合并路径后计算)
- 对比画布总面积,得出擦除百分比
代码示例
// 记录所有擦除的椭圆Rect val erasedOvals = remember { mutableStateListOf<Rect>() } // 在触摸移动时更新路径和椭圆记录 MotionEvent.ACTION_MOVE -> { val ovalRect = Rect(it.x - currentPathThickness/2, it.y - currentPathThickness/2, it.x + currentPathThickness/2, it.y + currentPathThickness/2) erasedOvals.add(ovalRect) currentPath.addOval(ovalRect) onMovedOffset(it.x, it.y) } // 计算已擦除面积(近似,忽略重叠) val totalErasedArea = remember(erasedOvals) { erasedOvals.sumOf { rect -> val radius = rect.width / 2f Math.PI * radius * radius } } // 计算擦除百分比 val canvasArea = size.width * size.height val erasedPercentage = (totalErasedArea / canvasArea) * 100 val isErasedEnough = erasedPercentage >= 70f
如果需要精确处理重叠区域,可合并路径后用PathMeasure计算面积:
val mergedPath = remember(erasedOvals) { Path().apply { erasedOvals.forEach { addOval(it) } } } val pathMeasure = remember(mergedPath) { PathMeasure(mergedPath, false) } val preciseErasedArea = remember(mergedPath) { var area = 0.0 val length = pathMeasure.length val pos = FloatArray(2) var prevPos = FloatArray(2) pathMeasure.getPosTan(0f, pos, null) prevPos = pos.copyOf() var step = 0.5f while (step < length) { pathMeasure.getPosTan(step, pos, null) area += (prevPos[0] * pos[1] - pos[0] * prevPos[1]) / 2.0 prevPos = pos.copyOf() step += 0.5f } Math.abs(area) }
方法二:基于Bitmap像素对比(精确但需优化)
适合需要像素级精确判断的场景,建议在用户停止触摸后再触发计算,避免频繁操作Bitmap导致性能问题。
实现步骤
- 创建与画布尺寸一致的离屏Bitmap
- 将当前画布内容绘制到Bitmap
- 遍历像素统计擦除区域(baseImage显示区域)的像素数量
- 计算擦除百分比并判断是否达标
代码示例
// 在用户停止触摸时触发计算(例如ACTION_UP时) MotionEvent.ACTION_UP -> { val bitmap = Bitmap.createBitmap(size.width.toInt(), size.height.toInt(), Bitmap.Config.ARGB_8888) val offscreenCanvas = Canvas(bitmap) // 绘制当前画面到离屏Canvas with(overlayImageLoaded) { offscreenCanvas.draw(size = Size(size.width, size.height)) } clipPath(path = currentPath, clipOp = ClipOp.Intersect) { with(baseImageLoaded) { offscreenCanvas.draw(size = Size(size.width, size.height)) } } // 统计像素 val pixels = IntArray(bitmap.width * bitmap.height) bitmap.getPixels(pixels, 0, bitmap.width, 0, 0, bitmap.width, bitmap.height) // 统计baseImage显示的像素数(需根据你的图像调整颜色判断逻辑) var erasedPixelCount = 0 pixels.forEach { pixel -> // 示例:判断像素是否非overlay底色(需替换为你的实际判断逻辑) if (pixel != Color.WHITE.toArgb()) { erasedPixelCount++ } } val totalPixels = bitmap.width * bitmap.height val erasedPercentage = (erasedPixelCount.toFloat() / totalPixels) * 100 val isErasedEnough = erasedPercentage >= 70f }
方案选择建议
- 若只需近似判断70%的擦除比例,优先选择方法一,性能开销低、计算高效
- 若需要像素级精确判断,且图像差异明显,可使用方法二,但需注意在合适时机触发计算,避免性能问题
内容的提问来源于stack exchange,提问作者StuartDTO
相关产品推荐
相关产品推荐

