跳到主要内容

Pixelate 马赛克

把图像分成方格,每格取平均色——马赛克、打码、像素风。

node.cache();
node.filters([Konva.Filters.Pixelate]);
node.pixelSize(10); // 默认 8

pixelSize 的单位是节点坐标​

这一点在 Konva 10.6.0 里发生过变化,值得说清楚。

滤镜拿到的 imageData 是缓存画布的像素, 而缓存画布的分辨率由 cache({ pixelRatio }) 决定,默认等于设备像素比。 如果 pixelSize 直接当作缓存像素数用,那么在 2x 屏上, 同样的 pixelSize(10) 只覆盖节点坐标里的 5px—— 同一份代码在不同设备上马赛克块的大小不一样。

10.6.0 修正了这个问题。现在 pixelSize、blurRadius 以及 CSS 滤镜里的长度 统一按节点坐标解释,与缓存的 pixelRatio 无关。 源码注释里写得很明确: "Blur radii, pixelation sizes and CSS lengths use node coordinates, independent of cache pixelRatio."

实际意义:

  • pixelSize(10) 在任何屏幕上都是节点坐标里 10×10 的块。
  • 节点被 scale(2) 放大时,马赛克块跟着变大(因为节点坐标被放大了), 不会变得更细。这通常正是你想要的——打码不应该因为用户放大就失效。
  • 如果你在写自定义滤镜并且有类似的长度参数, 需要自己用第二个参数 pixelRatio 做换算,Konva 不会替你做。

常见问题​

打码用多大的 pixelSize 才安全?​

看被打码内容的尺寸,不是看图片尺寸。经验法则是块大小至少是 待隐藏特征高度的三分之一——人脸打码要让单个块覆盖眼睛的大部分。

需要警惕的是:马赛克是可逆的。块的平均色保留了原始信息, 如果攻击者知道候选集(比如车牌只有有限的字符组合), 可以穷举比对还原。真正敏感的内容不要用马赛克,用纯色矩形盖住。

边缘的块为什么不完整?​

图像宽高通常不是 pixelSize 的整数倍,最右和最下会剩下不足一格的部分。 Konva 会按剩余区域实际的像素数取平均,所以边缘的块更窄但颜色正确。

如果这个视觉上的不整齐有影响,把节点尺寸调整成 pixelSize 的整数倍。

为什么半透明图片打码后边缘发暗?​

这是 Konva 10.4.0 修复过的一个问题: Pixelate 在计算块内平均色时,如果块里同时有不透明和全透明的像素, 早期版本会把透明像素的 RGB(通常是 0)也算进平均值, 导致圆形、文字、带 alpha 的图片边缘的块偏暗。

10.4.0 起已修正。如果你还看到这个现象,先确认 Konva 版本—— 本站统一使用 konva@10。

能做出矩形而不是正方形的块吗?​

不能,pixelSize 是单个值,块永远是正方形。

需要非方形的块(比如模拟扫描线或 LED 屏)只能自己写自定义滤镜, 分别控制横向和纵向的步长。

性能提示​

Pixelate 比逐像素滤镜贵:它要对每个块做两趟—— 先遍历块内所有像素求平均,再遍历一遍写回。 总的访问次数是像素数的两倍,而且写回时的内存访问不连续。

不过它有一个其他滤镜没有的特性:成本与 pixelSize 无关。 块大或块小,总的像素访问次数都一样。所以不要指望调大 pixelSize 来提速。

真正有效的几条:

降低缓存分辨率。 这是马赛克滤镜最该用的优化,而且几乎没有代价—— 输出本来就是方块,cache({ pixelRatio: 1 }) 相比 2x 省掉四分之三的工作量, 而视觉结果几乎不可分辨(块的大小以节点坐标计,不随分辨率变)。

打码区域单独成节点。 不要给整张图加 Pixelate 再用裁剪露出一块。 正确做法是把需要打码的区域做成一个独立的 Konva.Image(用 crop 截取原图的对应部分), 只对它缓存加滤镜。面积可能只有原来的百分之一。

静态打码就导出成图片。 如果打码区域不会变, toImage() 一次把结果存成位图,之后当普通图片用, 彻底不再跑滤镜。