You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.04 20:48:02