Android Canvas PorterDuff 遮罩异常:新增遮罩区域显示白色像素问题排查
问题根因
核心问题有两个:
- 混合模式选择错误:你当前使用的
DST_ATOP不符合蒙版需求,该模式的计算规则是仅在目标(原图)已有不透明像素的区域,才会将源(drawingBitmap)的颜色和目标混合。当你在drawingBitmap新增白色不透明区域(要扩展蒙版)时,原图对应区域之前已经是透明状态,DST_ATOP就不会加载原图的像素,反而会透出drawingBitmap的白色像素。 - 原始素材被反复修改:你的代码直接在
imageBitmap上执行绘制操作,每次调用draw方法都会污染原始素材,后续混合操作的目标已经不是最初的鞋子原图,自然无法得到正确结果。
你填充纯色后功能正常的原因也很简单:填充纯色后整个画布的alpha都变为1,DST_ATOP可以在所有区域正常计算混合结果,填充的颜色就会替换原图内容显示出来。
修复方案
第一步:保留原始素材副本
提前保存一份不可修改的鞋子原图副本originalImageBitmap,所有混合操作都基于该副本执行,不要直接修改它。
第二步:修正混合逻辑
将混合模式替换为DST_IN,该模式正好符合「用源的alpha通道裁剪目标显示区域」的蒙版需求,公式为:最终像素 = 目标像素 * 源alpha值,完美匹配你的业务要求。
修正后的代码参考:
override fun draw(baseCanvas: Canvas) { super.draw(baseCanvas) // 原始原图,永远不修改 val originalImage = originalImageBitmap val mask = maskBitmap val drawingBitmap = drawingBitmap if (originalImage != null && mask != null && drawingBitmap != null) { // 生成蒙版的逻辑保持不变 run { val canvas = Canvas(drawingBitmap) // 1. Fill with white canvas.drawColor(Color.WHITE) // 2. Draw mask and only let non-transparent pixels through val paint = Paint(Paint.ANTI_ALIAS_FLAG) paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_IN) canvas.drawBitmap(mask, 0f, 0f, paint) // 3. Draw lines 逻辑保持不变 paint.xfermode = null paint.style = Paint.Style.STROKE paint.color = Color.WHITE paint.strokeWidth = penSize paint.isDither = true paint.strokeJoin = Paint.Join.ROUND paint.strokeCap = Paint.Cap.ROUND paint.pathEffect = CornerPathEffect(10f) paint.isAntiAlias = true lines.forEach { line -> paint.xfermode = when (line.drawMode) { DrawMode.ERASE -> PorterDuffXfermode(PorterDuff.Mode.DST_OUT) DrawMode.DRAW -> PorterDuffXfermode(PorterDuff.Mode.SRC) } val path = Path().also { path -> val points = line.points val range = points.size - 1 for (i in 1..range) { path.moveTo(points[i - 1].x, points[i - 1].y) path.lineTo(points[i].x, points[i].y) } } canvas.drawPath(path, paint) } } // 混合输出逻辑修正 run { // 先创建一张新的输出位图 val outputBitmap = Bitmap.createBitmap(originalImage.width, originalImage.height, Bitmap.Config.ARGB_8888) val canvas = Canvas(outputBitmap) // 先绘制原始原图作为目标 canvas.drawBitmap(originalImage, 0f, 0f, null) // 使用DST_IN模式绘制蒙版,用蒙版的alpha裁剪原图 val paint = Paint(Paint.ANTI_ALIAS_FLAG) paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_IN) canvas.drawBitmap(drawingBitmap, 0f, 0f, paint) // 最终把输出位图画到View的画布上 baseCanvas.drawBitmap(outputBitmap, 0f, 0f, null) } } }
额外优化
如果不需要保留中间的输出位图,可以直接在View的baseCanvas上执行混合操作,减少内存占用:
run { // 关闭硬件加速避免Xfermode兼容性问题 setLayerType(LAYER_TYPE_HARDWARE, null) // 先画原始图 baseCanvas.drawBitmap(originalImage, 0f, 0f, null) // 再用DST_IN画蒙版 val paint = Paint(Paint.ANTI_ALIAS_FLAG) paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_IN) baseCanvas.drawBitmap(drawingBitmap, 0f, 0f, paint) }
内容的提问来源于stack exchange,提问作者mrousavy
相关产品推荐
相关产品推荐

