真马赛克必须用ctx.getImageData+ctx.putImageData修改像素,因fillRect仅覆盖绘制层、不改原始像素,导出时无效;需读取原图数据→按块采样均值→重写像素→导出,且App端不支持该API,跨平台应选服务端处理。

uni-app 里给图片加真马赛克,必须用 ctx.getImageData + ctx.putImageData 改像素,不能用 fillRect 画色块。后者只是视觉欺骗,导出时完全消失。
为什么 fillRect 不行,而 getImageData 才是真马赛克
很多初学者直接在 canvas 上用 fillRect 覆盖一块区域,看着像马赛克,但本质没动原图数据——uni.canvasToTempFilePath 导出的是 canvas 当前像素状态,而 fillRect 绘制的内容不参与像素数据重写,导出结果里那块区域仍是原始图像。
真马赛克需要:读取原始像素 → 按块采样均值 → 把均值写回该块所有像素 → 再导出。这个流程绕不开 getImageData 和 putImageData。
-
getImageData返回的是Uint8ClampedArray,每个像素占 4 字节(r,g,b,a) - 必须确保 canvas 尺寸和图片宽高一致,否则采样坐标错位;推荐先用
uni.getImageInfo获取真实尺寸 - H5 端可用原生
HTMLCanvasElement,但 App 和小程序端必须用uni.createCanvasContext,且需指定canvas-id
如何实现涂鸦式马赛克(touchmove 连续涂抹)
用户手指滑动时,不能只对起始点和终点做马赛克——中间会漏掉大量区域。正确做法是把移动轨迹拆成密集采样点,对每个点所在马赛克单元格统一处理。
关键逻辑是:拿到触摸点 (x, y) 后,计算它所属的马赛克块左上角坐标:Math.floor(x / blockSize) * blockSize 和 Math.floor(y / blockSize) * blockSize
- 块大小建议设为
8–24,太小看不出效果,太大糊成一片 - 每次开始新涂抹前,必须调用
ctx.clearRect(0, 0, width, height)清空 canvas,否则旧像素残留干扰采样 - 采样时要限制边界:
startX = Math.max(0, Math.min(w - blockSize, ...)),避免越界读取 - 不要在
touchmove里频繁调用putImageData,会卡顿;建议累积多个点后批量重绘
uni.canvasToTempFilePath 导出失败的常见原因
导出为空白或仍是原图,大概率是时机或上下文问题。
-
uni.canvasToTempFilePath必须在ctx.draw()回调完成后再调用,不能靠 setTimeout 估时间 - App 端(尤其 iOS)对 canvas 渲染有延迟,建议加一层
setTimeout延迟 100ms,但优先用ctx.draw({ reserve: true }, () => { ... })的回调保证顺序 - 小程序平台要求 canvas 必须在 wxml 中声明
canvas-id,且 JS 中传入的 id 必须严格一致(区分大小写) - H5 端若用原生 canvas,导出要用
canvas.toDataURL(),而非uni.canvasToTempFilePath(该 API 在 H5 不生效)
跨平台兼容性最简落地路径
想在 App、小程序、H5 三端都跑通,别硬扛像素操作——uni.createCanvasContext 在 App 端根本不支持 getImageData,这是底层限制,不是写法问题。
真正可行的方案只有两个:
- **服务端处理**:前端只上传图片 + 马赛克区域坐标(x,y,width,height,blockSize),后端用 OpenCV/PIL 处理并返回带马赛克的新图 URL
- **小程序专属方案**:微信/支付宝基础库 ≥2.18.0 可用
canvas.toDataURL,配合 Worker 解析像素,但 H5 和 App 端无法复用
如果你的应用主要面向小程序,可走 canvas.toDataURL + Worker 路线;若含 App 端,服务端方案才是唯一稳定选择。本地像素操作只适合纯 H5 场景。


















