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() 一次把结果存成位图,之后当普通图片用,
彻底不再跑滤镜。