svg 卷积矩阵 feConvolveMatrix
简介
<feConvolveMatrix>
为卷积矩阵滤镜,该滤镜可以让一个像素的色值与其周围的像素点进行组合,通过卷积实现一些成像操作。比如模糊、边缘检测、锐化、压花和斜角等。
卷积矩阵基于一个 n * m 的卷积核来计算给定像素点与其相邻像素点的组合方式。每个像素点的卷积公式如下:
COLORX,Y = ( SUMI=0 to [orderY - 1] { SUMJ=0 to [orderX - 1] { SOURCEX-targetX+J, Y-targetY+i * kernelMatrixorderX-j-1, orderY-I-1 } } ) / divisor + bias * ALPHAX,Y
上面公式中,orderX
、orderY
、targetX
、targetY
、kernelMatrix
、divisor
、bias
都是通过属性的方式传入到卷积矩阵滤镜之中,稍后会介绍。
先来看下默认情况。假如一张不透明的图仅有 3 * 3 个像素点,它的其中一个颜色通道的值如下:
A B C
D E F
G H I
然后我们给出一个卷积矩阵如下:
1 2 3
4 5 6
7 8 9
那么 E 点该颜色的值为:
(9A + 8B + 7C + 6D + 5E + 4D + 3C + 2B + 1A) / (1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9)
卷积矩阵在计算时需要旋转 180 度,所以卷积矩阵的数值是反向与像素点周围的值进行相乘的。
上面的演示的就是中心点 E 于该颜色通道计算后的值,它是比较复杂的。
不过也可以看出,通过卷积矩阵,使得中心点 E 和周围的点产生了关联。
属性
为了灵活使用卷积矩阵滤镜,该滤镜几乎提供了全部参与计算的属性。
order
order
属性用来确定计算公式中 orderX
与 orderY
的值,这个值是固定的,它代表当前矩阵的列数与行数。W3C 并没有为其定义默认值,但是在实际使用中请填清楚,因为一个 4 * 5 与一个 5 * 4 的矩阵数据量是一样的,如果不填清可能会造成不同预期的计算。
它的语法为:
order="X[,Y]"
当省略 Y
值时,会使用 X
代替 Y
。
示例:
<feConvolveMatrix
order="3,3"
kernelMatrix="
1 0 0
0 0 0
0 0 -1"
></feConvolveMatrix>
像上面就定义了一个 3 * 3 的卷积矩阵滤镜,并且通过 order
指明了卷积矩阵的行列数。
一般来说,卷积矩阵的大小 3 * 3 是最常用的,因为过大的矩阵会造成高额的计算成本。
kernelMatrix
kernelMatrix
属性用来定义卷积矩阵的数据,可以使用空白符或者逗号分割各个值,列表的的数目必须满足 order
属性对应的条目。
示例:
<feConvolveMatrix
order="2,2"
kernelMatrix="
1 0
0 -1"
></feConvolveMatrix>
也许你会有些疑惑,如果卷积矩阵是根据像素点计算的,那么上面是一个 2 * 2 的矩阵,它如何进行像素计算。
卷积矩阵的计算会根据矩阵数据,重新在原图中建立坐标系,从而形参新的像素点。
divisor
divisor
为计算公式中的除数。在卷积矩阵计算后,产生的数字会除以这个数。它作为矩阵值总和的除数一般用来调整整体颜色强度,以便产生明亮或夜间效果。除数指定为零是错误的。默认情况下,除数是矩阵数据的总和,如果总和为 0,则除数设置为 1。
因此在简介中,默认情况下,我们没有指定 divisor
的值,但是最后的结果却要除以 1 ~ 9 的总和。又比如以下代码:
<feConvolveMatrix
order="3,3"
kernelMatrix="
1 0 0
0 0 0
0 0 -1"
></feConvolveMatrix>
这段代码我们同样没有指定 divisor
值,但是它的值为 1,因为 kernelMatrix
的总和为零,根据定义总和为零时除数设置为 1,所以上面代码中的 divisor
值为 1。
当然也可以主动指定,比如:
<feConvolveMatrix
order="3,3"
kernelMatrix="
1 0 0
0 0 0
0 0 -1"
divisor="2"
></feConvolveMatrix>
此时除数将不再使用总和计算,而是直接使用 2
作为除数。
bias
bias
属性为卷积矩阵滤镜的偏差值。在图形通过卷积矩阵计算并除以 divisor
后,会加上 bias
。如果不透明度参与计算,那么 bias
的值乘以不透明度。(卷积矩阵滤镜计算时,颜色通道使用的也是预乘值,范围 [0, 1],所以这个值一般也比较小)。
示例:
<feConvolveMatrix
order="3,3"
kernelMatrix="
0 0 0
0 1 0
0 0 0"
bias="0.2"
></feConvolveMatrix>
看的出来上面的卷积矩阵就是原图,但是所有颜色通道的颜色都提升了 0.2,造成整体颜色偏向白色。
- 假如,某一不透明像素点的红色通道为 0.8
- 再加上 0.2 就为 1
- 最终这个颜色值会变得非常接近白色。
在简介中的公式可以看到,该值是要乘以当前像素点的不透明度的、这个计算方式可以通过 preserveAlpha 属性来修改。
targetX
targetX
用来确定卷积矩阵相对提供计算的像素点的行号偏移量。矩阵的左上角列号行号都为 0,默认情况下,targetX=floor(orderX / 2)
,设置该值时必须满足: 0 <= targetX < orderX
。
综上,默认情况下,某一像素才会相对于矩阵中心进行计算。
设下面是一堆像素点:
A B C D E
F G H I J
K L M N O
面对一个 3 * 3 的卷积矩阵,默认情况下、每个点都是使用它们相邻的像素点进行计算。比如参与 G 点卷积计算的点就是:
A B C
F G H
K L M
当设置为 targetX="0"
时,每个点参与卷积矩阵计算的像素点就发生了偏移,比如此时参与 G 点卷积计算的点就为:
B C D
G H I
L M N
这个属性实用性并不强,只要图片足够大,那么此属性带来的效果就像把最终结果偏移几个像素点一样。
targetY
targetX
用来确定卷积矩阵相对提供计算的像素点的列号偏移量。矩阵的左上角列号行号都为 0,默认情况下,targetX=floor(orderX / 2)
,设置该值时必须满足: 0 <= targetX < orderX
。
参阅 targetX。
edgeMode
edgeMode
为边缘模式属性。它用来确定图像的边缘如何进行扩展计算。
这个属性的可用值如下:
edgeMode = "duplicate | wrap | none"
设一个图像素点如下:
a b ... c d
e f ... g h
...
i j ... k l
m n ... o p
当 edgeMode="duplicate"
、边缘需要扩展 2 个的像素时,图像的边缘扩展如下:
a a a b ... c d d d
a a a b ... c d d d
a a a b ... c d d d
e e e f ... g h h h
...
i i i j ... k l l l
m m m n ... o p p p
m m m n ... o p p p
m m m n ... o p p p
图案边缘会使用自身边缘的值进行扩展,从而实现卷积矩阵的计算,这个值是一个默认值。
当 edgeMode="wrap"
、边缘需要扩展 2 个的像素时,图像的边缘扩展如下:
k l i j ... k l i j
o p m n ... o p m n
c d a b ... c d a b
g h e f ... g h e f
...
k l i j ... k l i j
o p m n ... o p m n
c d a b ... c d a b
g h e f ... g h e f
图案每行每列会被认为是循环的,然后使用首末进行填充。
当 edgeMode="none"
时,不会使用边缘填充,不存在的像素点每个颜色通道都会使用 0 代替。
论点
此节内容在讨论中,如果你有兴趣,欢迎在 Issues 中发起讨论。
关于 edgeMode
属性,我自己测试了很多次,一直不是很能理解,比如经典的压花 3 * 3 卷积矩阵:
1 0 0
0 0 0
0 0 -1
这个矩阵的效果很不错,大概是这样的:
如果 edgeMode
默认情况下是 duplicate
重复值,那么对于纯色区域且不透明度的区域来说,得到的结果应该都是透明的黑色。
比如 rgba(255,255,0,0)
这个颜色,它的边缘使用 duplicate
来重复填充,得到的结果应该只是扩大了一圈而已。
因为压花卷积矩阵值总和为 0,所以除数为 1,那么任意像素点的 R、G、B 通道都将是:
阿尔法不透明度通道为 1,计算后为:
也就是完全透明,所以压花效果的边缘如果使用 edgeMode="duplicate"
模式应该是什么都看不到才对。
而且很意外的是,当我使用 edgeMode="none"
模式时,上面的结果是一样的,对于 edgeMode="none"
模式下压花的边缘特效是符合计算结果的,只是其他两个模式的渲染效果不和预期。
上述情况仅在边缘出现。
kernelUnitLength
这个属性用于建立生成新的坐标系参与该滤镜计算,我没有找到相关示例,这个属性我在 Chrome 92+ 版本上测试也没得到实质性的效果。详见 W3C。
preserveAlpha
preserveAlpha
属性用来设置是否保留 alpha 通道不参与计算,默认值为 false
,即不透明度参与计算,你也可以设置为 true
,此时透明度将不参与卷积计算。
当该值设置为 false
时,alpha 通道值计算如下:
ALPHAX,Y = ( SUMI=0 to [orderY - 1] { SUMJ=0 to [orderX - 1] { SOURCEX-targetX+J, Y-targetY+i * kernelMatrixorderX-j-1, orderY-I-1 } } ) / divisor + bias
当该值为 true
时,不透明度将不参与计算。此时各个颜色将不进行预乘而直接进行计算,然后在最终结果中重新预乘。此时每个像素点的不透明度将不产生变化,计算公式为:
用例
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" viewBox="0 0 10 10">
<defs>
<filter id="Relief-1">
<feConvolveMatrix
order="3,3"
kernelMatrix="
2 2 1
2 0 -2
1 -2 -2"
></feConvolveMatrix>
</filter>
</defs>
<image
x="1"
y="1"
width="8"
height="8"
xlink:href="/imgs/test1.jpg"
filter="url(#Relief-1)"
/>
</svg>
上述代码效果如下:
其他
Victor Powell 上有一些卷积矩阵的样例,不过他的样例好像并没有参照 W3C 标准进行计算,但是效果差别并不大。