CSS滤镜效果

前言

CSS3新增了filter属性用来专门设置元素的滤镜效果,不过目前只支持两种类型:

  • 内置的滤镜函数:一些很常用的滤镜效果。
  • url():该方法可以引用一个svg滤镜元素来进行滤镜效果的设置。

CSS滤镜函数一览

滤镜函数 作用 参数
blur() 高斯模糊 模糊范围;<length>类型数据,默认为0
不能为百分比!负值会被忽略;
brightness() 亮度调整 亮度值;小数或百分比
0时全黑,为1时图像不变
可以大于1,大于1时比原图像更亮
contrast() 对比度调整 对比度值;值的设置同亮度一样
drop-shadow() 阴影效果 参数设置同box-shadow()类似
grayscale() 灰度调整 灰度(线性)因子;值在0 - 1之间
1为完全的灰度图像,0则不变
hue-rotate() 色相旋转 色相旋转角度;单位为degturn
invert() 颜色反转效果 反转(线性)因子;值在0 - 1之间
1为完全反转,0则不变
opacity() 不透明度调整 opacity效果一样
saturate() 饱和度调整 饱和度值;指的设置同亮度一样
sepia() 褐色调整 褐色因子;设置同invert
1为深褐色,0则不变

滤镜详解

blur

blur()方法的核心算法就是高斯滤波器(滤波器是计算机视觉/图像处理中的一个概念),即利用高斯函数(二维正态分布函数)算出以当前像素点为中心点的矩形区域内所有点的权重,然后将权重归一化处理,权重和各像素点颜色的乘积之和作为当前像素点的新颜色值;因此这种处理效果看起来就是模糊的图像;

G(x, y)=12πσ2ex2+y22σ2G(x,\ y) = \large{\frac{1}{2\pi\sigma^2}e^{-\frac{x^2+y^2}{2\sigma^2}}}

上面就是二维正态分布函数,σ\sigma为标准差,对应的就是blur(length)方法中的长度值,可以理解为滤波器的半径,当长度为nn时,得到的高斯权重矩阵大小就为(2n+1, 2n+1)(2n + 1,\ 2n + 1);而x, yx,\ y则代表以当前像素点为原点,各个像素点的坐标;以blur(1px)为例,此时σ=1\sigma = 1r=1r = 1

img

上面就是某个像素点和其周围相邻一个像素点之间的坐标值了;将坐标值(x, y)(x,\ y)和标准差代入公式即可得到每个像素点对应的权重值:

img

显然,权重值相加不等于1,因此需要做归一化处理:

img

细节问题一:模糊半径

W3C文档可知,它仅仅规范了高斯滤波器中标准差的来源,而没有说清楚模糊半径(即滤波器半径)怎么取;

The passed parameter defines the value of the standard deviation to the Gaussian function. The parameter is specified a CSS length, but does not accept percentage values. [1]

因此,blur()方法规范在这一点上是不严谨的;但是MDN上面对于blur()方法中有个描述值得推敲:

The blur() fucntion applies a Gaussian blur to the input image. The value of radius defines the value of the standard deviation to the Gaussian function, or how many pixels on the screen blend into each other, so a larger value will create more blur. [2]

也就是说这个长度也可以用来描述模糊半径,由于这个长度可以是浮点数,但由于滤波器半径是整数,可以大胆推测:滤波器半径(模糊半径)是长度的向上取整值

细节问题二:模糊效果超出部分

应用模糊滤镜效果时,可以看到模糊效果会向外扩张一定的区域,这个多出的区域大概就是传入blur方法的长度:

1
2
3
4
5
6
7
8
9
10
.test {
--radius: 10px;
width: 200px;
height: 200px;
padding: var(--radius);
border: 1px solid red;
}
.blur {
filter: blur(var(--radius));
}
1
2
3
<div class="test">
<img class="blur" src="https://placekitten.com/200/200">
</div>

img

不知模糊滤镜效果为何会超出原本的区域,看起来像是处理边界部分时进行相应宽度的填充导致的,不知道这算不算是副作用?毕竟一般的模糊滤镜处理效果是不会超出图像原本区域的,但是W3C规范也并没有提及边界处理情况,所以有点迷;

当我们并不想要显示模糊滤镜超出的部分怎么办?传统的办法可以套一层父元素,然后利用overflow: hidden进行隐藏;但是,其实有更简单的办法,那就是利用clip-path直接将超出部分裁剪掉:

1
2
3
4
.blur {
filter: blur(var(--radius));
clip-path: inset(0 0 0 0);
}

img

brightness

亮度滤镜的原理很简单,就把亮度值与原像素值相乘,得到的像素值超出1则为1;其实这个效果很容易用混合模式中的正片叠底进行模拟(当然,亮度超出1时就没法模拟了);

1
2
3
4
<div class="brightness">
<img class="brightness-item" src="https://placekitten.com/200/200">
<div class="brightness-blend"></div>
</div>
1
2
3
4
5
6
7
8
9
10
.brightness-item {
filter: brightness(0.5);
}
.brightness-blend {
display: inline-block;
width: 200px;
height: 200px;
background-blend-mode: multiply;
background: rgba(0, 0, 0, 0.5) url("https://placekitten.com/200/200");
}

img

contrast

对比度滤镜计算公式如下:

Cresult=(0.5, 0.5, 0.5)(1a)+aCoriginC_{result} = (0.5,\ 0.5,\ 0.5)*(1 - a) + a * C_{origin}

这里,aa指的就是对比度系数,即传入contrast()方法中的参数;因此,可以看出当对比度系数为1时,得到图像就是原图像,当对比度系数为0时,得到是纯灰度图像;当对比度系数超出1,得到的效果就是黑的越黑,白的更白,即对比度上升;

img

drop-shadow

A drop shadow is effectively a blurred, offset version of the input image’s alpha mask drawn in a particular color, composited below the image.[3]

W3C的定义可知,投影滤镜就是复制了图像的透明通道加上了投影颜色组成了新的图层,且在该图层上应用了模糊滤镜效果,该图层位于原图层的下方;

1
2
3
<div class="drop">
<img class="drop-item" src="https://interactive-examples.mdn.mozilla.net/media/examples/firefox-logo.svg">
</div>
1
2
3
4
5
.drop-item {
width: 200px;
height: 200px;
filter: drop-shadow(300px 0 0 red);
}

img

使用一张具有透明通道的图片,加上投影滤镜可以看到新的图层确实复制了原有的透明通道;

grayscale

灰度滤镜原理就很简单,就是将原像素颜色与其灰度值按比例进行混合:

Cresult=Cgraya+Corigin(1a)C_{result} = C_{gray} * a + C_{origin} * (1- a)

hue-rotate

色相旋转,顾名思义就是将HSL中的H分量进行旋转;也就是先将颜色转为HSL颜色,再将H分量旋转指定量,然后再将颜色转回RGB颜色;只要理解了HSL色彩模型,就很容易这个色相旋转的本质了;

invert

反转滤镜的原理和灰度滤镜原理是类似的,就是将原像素颜色与其互补色按比例进行混合:

Cresult=Ca+Corigin(1a)C_{result} = C_{互补} * a + C_{origin} * (1- a)

当然,如果是invert(1)时(即完全的互补色),可以利用差值滤色模式来等价模拟:

1
2
3
4
<div class="invert">
<img class="invert-item" src="https://placekitten.com/200/200">
<div class="invert-other"></div>
</div>
1
2
3
4
5
6
7
8
9
10
.invert-item {
filter: invert(1);
}
.invert-other {
display: inline-block;
width: 200px;
height: 200px;
background-blend-mode: difference;
background: white url("https://placekitten.com/200/200");
}

img

saturate

饱和度滤镜原理同对比度类似,只不过需要先得出原像素颜色的明度值(即HSL颜色中的L分量),然后将明度值与原像素颜色按比例进行混合:

L=0.3Cr+0.59Cg+0.11CbCresult=(L,L,L)(1s)+Corigins\begin{aligned} L &= 0.3 * C_r + 0.59 * C_g + 0.11 * C_b \\ C_{result} &= (L, L, L) * (1 - s) + C_{origin} * s \end{aligned}

ss就是滤镜中设置的饱和度值,可以看出,当s=0s=0时,得到的就是一副灰度图像;

img

sepia

褐色滤镜本质上还是一个混合滤镜,只不过获取一个像素颜色对应的褐色需要多一点计算;不过由于没有在W3C上发现有描述相关的算法,所以在网上找了褐色滤镜的算法[4]

Csepia=(r, g, b)[0.3930.3490.2720.7690.6860.5340.1890.1680.131]Cresult=Cspeiaa+Corigin(1a)\begin{aligned} C_{sepia} &= (r,\ g,\ b) \cdot \begin{bmatrix} 0.393 & 0.349 & 0.272 \\ 0.769 & 0.686 & 0.534 \\ 0.189 & 0.168 & 0.131 \end{bmatrix} \\[5ex] C_{result} &= C_{speia} * a + C_{origin} * (1 - a) \end{aligned}

相关文档


  1. https://www.w3.org/TR/filter-effects-1/#funcdef-filter-blur ↩︎

  2. filter - CSS: Cascading Style Sheets | MDN ↩︎

  3. https://www.w3.org/TR/filter-effects-1/#funcdef-filter-drop-shadow ↩︎

  4. image processing - How is a sepia tone created? - Stack Overflow ↩︎