第 11 章 滤镜
滤镜的工作原理
当 SVG 阅读器程序处理一个图形对象时,它会将对象呈现在位图输出设备上,在某一时刻,阅读器程序会把对象的描述信息转换为一组对应的像素,然后呈现在输出设备上
如用 <filter> 元素指定一组操作(也称为基元,primitive),在对象的旁边显示一个模糊的投影,然后把这个滤镜附加给一个对象
<filter id="drop-shadow">
<!-- 这是滤镜操作 -->
</filter>
<g id="spring-flower" style="filter: url(#drop-shadow);">
<!-- 这里绘制花朵 -->
</g>
由于花朵在显示样式中用了滤镜,所以 SVG 不会将花朵直接渲染为最终图形,SVG 会渲染花朵的像素到临时位图中,由滤镜指定的操作会被应用到该临时区域,其结果会被渲染为最终图形
默认情况下临时位图的尺寸取决于渲染图形的显示屏的分辨率和尺寸,这表明即使所有的 SVG 代码是相同的,某些滤镜效果也可能在不同大小的显示屏中有不同的外观。规范中定义了一些属性来控制滤镜效果的有效分辨率(规范地址),但是它们在 SVG 阅读器中的实现并不一致,因此不做讨论
创建投影效果
建立滤镜的边界
<filter> 元素有一些属性用来描述该滤镜的裁剪区域,任何在边界外部的输出都不会显示,这些属性如下:
x:默认值 -10%y:默认值 -10%width:默认值 120%height:默认值 120%
如果想要为多个对象应用同一个滤镜,则可能要完全忽略这些属性,并用默认值为滤镜提供额外的空间
filterUnits设置滤镜对象边界框的百分比计算方式objectBoundingBox默认值userSpaceonUse
primitiveUnits为用于滤镜基元中的单元指定单位objectBoundingBoxuserSpaceonUse默认值
投影 <feGaussianBlur>
起始和结束 <filter> 标记之间就算执行要操作的滤镜基元。每个基元有一个或多个输入,但只有一个输出,输出可以为
- 原始图形(被指定为 SourceGraphic)
- 图形的阿尔法通道(被指定为 SourceAlpha)
- 前一个滤镜基元的输出
当只对图形的形状感兴趣而不管其颜色时,阿尔法源是有用的,它会避免阿尔法和颜色相互作用
第一次尝试输出投影,可能不是我们所想的
<!-- @format -->
<svg>
<defs>
<filter id="drop-shadow">
<feGaussianBlur in="SourceAlpha" stdDeviation="2"></feGaussianBlur>
</filter>
</defs>
<g id="flower" filter="url(#drop-shadow)">
<image xlink:href="./flower.svg"></image>
</g>
</svg>
in:指定输入源为SourceAlphastdDeviation:指定模糊度,如果值为由空格分隔的两个数字,则分别作为x和y方向的模糊度
存储、链接、合并滤镜结果
改进后的滤镜
<!-- @format -->
<svg>
<defs>
<filter id="drop-shadow">
<feGaussianBlur
in="SourceAlpha"
stdDeviation="2"
result="blur"
></feGaussianBlur>
<feOffset in="blur" dx="4" dy="4" result="offsetBlur"></feOffset>
<feMerge>
<feMergeNode in="offsetBlur"></feMergeNode>
<feMergeNode in="SourceGraphic"></feMergeNode>
</feMerge>
</filter>
</defs>
<g id="flower" filter="url(#drop-shadow)">
<image xlink:href="./flower.svg"></image>
</g>
</svg>
result属性指定当前基元的结果稍后可以通过blur名引用<feOffset>基元接受它的输入,在这里就是feGaussianBlur基元的返回结果blur,它的偏移由dx和dy指定,然后将结果位图存储在offsetBlur名字下面<feMerge>基元包裹一个<feMergeNode>元素列表,其中每个元素都指定一个输入,这些输入按照它们出现的顺序一个个堆叠。此处希望offsetBlur在原始SourceGraphic下面
创建发光式投影
<feColorMatrix> 元素
<feColorMatrix> 元素允许以一种非常通用的方式改变颜色值。用于创建蓝绿色发光式投影的基元序列示例如下
<!-- @format -->
<svg>
<defs>
<filter id="glow">
<feColorMatrix
type="matrix"
values="0 0 0 0 0
0 0 0 0.9 0
0 0 0 0.9 0
0 0 0 1 0"
></feColorMatrix>
<feGaussianBlur
stdDeviation="2.5"
result="coloredBlur"
></feGaussianBlur>
<feMerge>
<feMergeNode in="coloredBlur"></feMergeNode>
<feMergeNode in="SourceGraphic"></feMergeNode>
</feMerge>
</filter>
</defs>
<text x="120" y="50" style="filter: url(#glow); fill: #000; font-size: 18">
spring
<tspan x="120" y="70">flower</tspan>
</text>
</svg>
<feColorMatrix> 是一个通用的基元,允许修改任意像素点的颜色或阿尔法值,当 type 属性等于 matrix 的时候,必须设置 value 为 20 个数字来描述变换信息。这 20 个数字按照 4 行 5 列编写时最好理解,每行代表一个代数方程,定义了如何计算输出的 RGBA 值(按行的顺序)。每行中的数字分别乘以输入像素的 RGBA 的值和常量 1(按照列的顺序),然后加在一起得到输出值
要设置一个变换,将所有不透明区域绘制为相同的颜色,可以忽略输入颜色和厂里,只要设置阿尔法列的值即可,这个矩阵模型看起来如下所示
values="
0 0 0 red 0
0 0 0 green 0
0 0 0 blue 0
0 0 0 1 0"
在上面的例子中,并没有一个用作 <feColorMatrix> 基元输入的 in 属性,默认使用 SourceGraphic,同时也没有 result 属性,这表明这个颜色矩阵操作的输出只用于下一个滤镜基元的隐性输入,如果使用这种快捷方式,则下一个滤镜基元一定不能有 in 属性
<feColorMatrix> 详解
type 属性还有其他 3 个值,每个“内置”的颜色矩阵都能完成一个特定的 视觉任务,并且都有自己的指定 values 的方式
hueRotate:色相旋转,values的值是一个单一的数字。描述颜色的色相值应该被旋转多少度saturate:饱和度,values的值是一个 0-1 之间的数字。数字越小,颜色越淡。只能用来降低图像的饱和度,不能增加luminanceToAlpha:用亮度决定阿尔法值,values属性被忽略。这个亮度是颜色固有的“亮度”。这个滤镜会丢弃原始的颜色,结果是具有不同透明度的纯黑色,颜色越浅,赋予滤镜对象的透明度越低
<feImage> 滤镜
<feImage> 元素允许使用任意的 JPG、PNG、SVG 文件,或者带有 id 属性的 SVG 元素作为滤镜的输入源
<!-- @format -->
<svg>
<defs>
<filter id="sky-shadow" filterUnits="objectBoundingBox">
<feImage
xlink:href="./t.jpeg"
result="sky"
x="0"
y="0"
width="100%"
height="100%"
preserveAspectRatio="none"
></feImage>
<feGaussianBlur
in="SourceAlpha"
stdDeviation="2"
result="blur"
></feGaussianBlur>
<feOffset in="blur" dx="4" dy="4" result="offsetBlur"></feOffset>
<feMerge>
<feMergeNode in="sky"></feMergeNode>
<feMergeNode in="offsetBlur"></feMergeNode>
<feMergeNode in="SourceGraphic"></feMergeNode>
</feMerge>
</filter>
</defs>
<g id="flower" style="filter: url(#sky-shadow)">
<image xlink:href="./flower.svg"></image>
</g>
</svg>
<feComponentTransfer> 滤镜
<feComponentTransfer> 提供了一种更方便、更灵活的方式来单独操作每个颜色的分量,允许对每个颜色分类做出不同的调整
通过在 <feComponentTransfer> 子级内配置 <feFuncR>、<feFuncG>、<feFuncB>、<feFuncA> 元素,调整各自的级别,每个子元素都可以单独指定一个 type 属性,说明如何修改该通道
<!-- @format -->
<svg>
<defs>
<filter id="brightness-shadow" filterUnits="objectBoundingBox">
<feImage xlink:href="./t.jpeg" result="sky"></feImage>
<feComponentTransfer in="sky" result="sky">
<feFuncB type="linear" slope="3" intercept="0"></feFuncB>
<feFuncR type="linear" slope="1.5" intercept="0.2"></feFuncR>
<feFuncG type="linear" slope="1.5" intercept="0.2"></feFuncG>
</feComponentTransfer>
<feGaussianBlur
in="SourceAlpha"
stdDeviation="2"
result="blur"
></feGaussianBlur>
<feOffset in="blur" dx="4" dy="4" result="offsetBlur"></feOffset>
<feMerge>
<feMergeNode in="sky"></feMergeNode>
<feMergeNode in="offsetBlur"></feMergeNode>
<feMergeNode in="SourceGraphic"></feMergeNode>
</feMerge>
</filter>
</defs>
<g id="flower" style="filter: url(#brightness-shadow)">
<image xlink:href="./flower.svg"></image>
</g>
</svg>
type 属性的选项
linear:线性调整颜色分量gamma:以伽马函数调整颜色分量identity:默认值,不进行调整table:将颜色值划分为一系列相等的间隔,每个间隔中的值都相应地扩大。以 4 个间隔为例原始值范围 修改后的值范围 0.00-0.25 0.00-0.50 0.25-0.50 0.50-0.60 0.50-0.75 0.60-0.85 0.75-1.00 0.85-1.00 通过在
tableValues属性中列出重映射范围的端点,指定某个通道的映射:<feFuncG type="table" tableValues="0.0 0.5 0.6 0.85 1.0" />discrete:将颜色值划分未一系列相等的间隔,将每个间隔都映射到一个离散的颜色值。以 4 个间隔为例原始值范围 修改后的值 0.00-0.25 0.125 0.25-0.50 0.378 0.50-0.75 0.625 0.75-1.00 0.875 在
tableValues中指定某个通道的映射:<feFuncG type="table" tableValues="0.125 0.375 0.625 0.875" />如果想要重新映射所有的输入值给单个输出值,必须将该入口放入tableValues中两次:<feFuncG type="table" tableValues="0.5 0.5" />如果想要反转通道的颜色值范围,可使用这种方式:<feFuncG type="table" tableValues="maximum minimun" />
<feComposite> 滤镜
除了可以使用 <feMerge> 将多个滤镜层叠起来,<feComposite> 也可以
<feComposite> 元素接受两个输入源,分别指定在 in 和 in2 属性中,它的 operator 属性用于设置如何合并这两个输入源。各个值的效果解释如下
<feComposite operator="over" in="A" in2="B" />:A 层叠在 B 上面<feComposite operator="in" in="A" in2="B" />:A 的一部分重叠在 B 的不透明区域,类似于蒙版效果,但是这个蒙版仅仅基于 B 的阿尔法通道<feComposite operator="out" in="A" in2="B" />:A 的一部分位于 B 的不透明区域的外部(半透明区域有反转蒙版的效果)<feComposite operator="atop" in="A" in2="B" />:A 的一部分位于 B 里面,B 的一部分在 A 外面(取两者相交的部分)<feComposite operator="xor" in="A" in2="B" />:与上一个相反,取两者不相交的部分<feComposite operator="arithmetic" in="A" in2="B" />:灵活性最大,需要提供 4 个系数:k1、k2、k3、k4,每个像素的每个通道的结果按照此方式计算:k1 * A * B + k2 * A + k3 * B + k4(A、B为来自输入图形像素的颜色分量)
<!-- @format -->
<svg>
<defs>
<filter id="sky-in" filterUnits="objectBoundingBox">
<feImage
xlink:href="./t.jpeg"
result="sky"
x="0"
y="0"
width="100%"
height="100%"
preserveAspectRatio="none"
></feImage>
<feComposite
in="sky"
in2="SourceGraphic"
operator="in"
></feComposite>
</filter>
<filter id="sky-out" filterUnits="objectBoundingBox">
<feImage
xlink:href="./t.jpeg"
result="sky"
x="0"
y="0"
width="100%"
height="100%"
preserveAspectRatio="none"
></feImage>
<feComposite
in="sky"
in2="SourceGraphic"
operator="out"
></feComposite>
</filter>
<g id="flower">
<image xlink:href="./flower.svg"></image>
</g>
</defs>
<use
xlink:href="#flower"
transform="translate(10,10)"
style="filter: url(#sky-in)"
></use>
<use
xlink:href="#flower"
transform="translate(170,10)"
style="filter: url(#sky-out)"
></use>
</svg>
<feBlend> 滤镜
<feBlend> 元素接受两个输入源,分别指定在 in 和 in2 属性中,还需要一个 mode 属性用于设置如何混合输入源
以 <feBlend mode="**" in="A" in2="B" /> 为例,mode 取值效果如下
normal:只有B,和<feComposite>中的over一样multiply:加深颜色。对于每个颜色通道,将A的值和B的值相乘,由于颜色值在 0-1 之间,所以相乘会让它们更小,因此颜色更深。对于暗色或非常强烈的颜色效果更明显,如果某个颜色是白色则没有效果screen:减淡颜色。把每个通道的颜色值加在一起,然后减去它们的乘积。明亮的颜色或者浅色比暗色占优势,相似亮度的颜色会被合并darken:提取A和B的每个通道的最小值,颜色较暗lighten:提取A和B的每个通道的最大值,颜色较亮
每个红绿蓝值的计算都是独立完成的,如果输入源是透明的,则所有的模式在计算时都会计算透明度(除了
screen) 一旦颜色值计算完成,结果的透明度就由公式1 - (1 - opacityA) * (1 0 opacityB)决定。因此两个不透明项仍然保持不透明,两个透明度为 50% 的图形会被合并为一个,不透明度变为 75%
<feFlood> 和 <feTile> 滤镜
它们两个很像 <feOffset>,允许我们在一系列滤镜基元内执行某些常见的操作,而不是在主图形中创建额外的 SVG 元素
<feFlood>:提供一个纯色区域用于组合或合并,只需要提供flood-color和flood-opacity,然后滤镜会完成其他工作<feTile>:提取输入信息作为图案,然后横向和纵向平铺填充滤镜指定的区域,图案的尺寸由输入给<feTile>的尺寸决定
光照效果
要产生光照效果,需要满足以下条件
- 反射类型:漫反射
<feDiffuseLighting>、镜面反射<feSpecularLighting> - 需要照亮的对象
- 光的颜色
- 光源类型:
<fePointLight>:点光源,x、y、z:点光源位置
<feDistantLight>:远光elevation:屏幕平面上的光照角度azimuth:平面内的光照角度
<feSpotLight>:聚光灯x、y、z:聚光灯的位置。默认值为 0pointsAtX、pointsAtY、pointsAtZ:聚光灯指向的点。默认值为 0specularExponent:光源焦点的值。默认值为 1limitingConeAngle:约束光线投射的范围。默认值为无限蔓延。这是聚光灯于锥形之间的范围,如果想要蔓延整个锥 30 度角,指定角度为 15 即可
漫反射和镜面反射都使用它们照亮对象的阿尔法通道作为凹凸贴图(bump map),较高的阿尔法值被假定为“凸”在对象表面上
漫反射照明
<!-- @format -->
<svg>
<defs>
<path
id="curve"
d="M 0 0 Q 5 20 10 10 T 20 20"
style="stroke: black; fill: none"
></path>
<filter
id="diff-light"
color-interpolation-filters="sRGB"
x="0"
y="0"
width="100%"
height="100%"
>
<feImage
xlink:href="#curve"
result="tile"
width="20"
height="20"
></feImage>
<feTile in="tile" result="tile"></feTile>
<feDiffuseLighting
in="tile"
lighting-color="#ffffcc"
surfaceScale="1"
diffuseConstant="0.5"
result="diffuseOutput"
>
<fePointLight x="0" y="50" z="50"></fePointLight>
</feDiffuseLighting>
<feComposite
in="diffuseOutput"
in2="SourceGraphic"
operator="in"
result="diffuseOutput"
></feComposite>
<feBlend
in="diffuseOutput"
in2="SourceGraphic"
mode="screen"
></feBlend>
</filter>
</defs>
<circle
id="green-light"
cx="50"
cy="50"
r="50"
style="fill: #060; filter: url(#diff-light)"
></circle>
</svg>
<path>:定义用作图案的曲线<filter>:用color-interpolation-filters设置颜色插值方法<feImage>:用curve图形平铺滤镜区域,这会变成凹凸贴图<feDiffuseLighting>:定义漫反射滤镜,将tile作为输入lighting-color:指定浅黄色的光照surfaceScale:阿尔法值为 1 时的表明高度(更通用的说,它是计算阿尔法值时的乘积因子)diffuseConstant:用于确定像素最终 RGB 值的乘积因子。默认值为 1,必须大于等于 0,要变得更亮,该值应该更小
<fePointLight>:指定光源类型为点光源及其位置<feComposite>:裁剪滤镜的输出到源图形的边界<feBlend>:设置为screen模式,让源图形变量<circle>:在该对象上启用滤镜
上面代码中的滤镜的输入信息是一个包含 4 个颜色分量的图形,但只有阿尔法通道被使用了。当插入一个
<feColorMatrix type="luminanceToAlpha">并用它的输出作为滤镜的输入时,luminanceToAlpha会把黑色区域(0 亮度)转换为完全透明的(0 阿尔法),从阿尔法级别看,黑色和透明之间毫无差异
镜面反射照明
<!-- @format -->
<svg>
<defs>
<path
id="curve"
d="M 0 0 Q 5 20 10 10 T 20 20"
style="stroke: black; fill: none"
></path>
<filter
id="spec-light"
color-interpolation-filters="sRGB"
x="0"
y="0"
width="100%"
height="100%"
>
<feImage
xlink:href="#curve"
result="tile"
width="20"
height="20"
></feImage>
<feTile in="tile" result="tile"></feTile>
<feSpecularLighting
in="tile"
lighting-color="#ffffcc"
surfaceScale="1"
specularConstant="1"
specularExponent="4"
result="specularOutput"
>
<feDistantLight elevation="25" azimuth="0"></feDistantLight>
</feSpecularLighting>
<feComposite
in="specularOutput"
in2="SourceGraphic"
operator="in"
result="specularOutput"
></feComposite>
<feComposite
in="specularOutput"
in2="SourceGraphic"
operator="arithmetic"
k1="0"
k2="1"
k3="1"
k4="0"
/>
</filter>
</defs>
<circle
id="green-light"
cx="50"
cy="50"
r="50"
style="fill: #060; filter: url(#spec-light)"
></circle>
</svg>
<feSpecularLighting>:定义镜面反射滤镜specularConstant:确定最终像素 RGB 值的一个乘积因子,默认值为 0,必须大于等于 0,要变得更亮,该值应该更小specularExponent:确定最终像素 RGB 值的一个乘积因子,默认值为 1,必须为 0-128 之间的数字,数字越大越亮
<feDistantLight>:定义一个远光源elevation:指定屏幕平面上光的角度,0 表示光线平行于整个图形,90 表示光线直射下来azimuth:在平面内指定角度,当elevation为 0 时,azimuth为 0 表示光线从图像右侧来(x轴正值结束处),为 90 表示从底部来(y轴正值结束处),180 表示左侧来,270 表示顶部来
访问背景
书中提到一个 enable-background 属性,但该属性并没有浏览器支持,且在 MDN 上已标记为弃用,因此不记录
<feMorphology> 元素
<feMorphology> 元素允许对图形进行“瘦身”或者“加厚”。指定 operator 的值为如下即可
erode:瘦身dilate:加厚
radius 属性指定瘦身或者加厚的程度,取值为数值
<!-- @format -->
<svg width="100%">
<defs>
<g id="flower" stroke-width="2">
<image xlink:href="./flower.svg"></image>
</g>
<filter id="erode">
<feMorphology operator="erode" radius="1"></feMorphology>
</filter>
<filter id="dilate">
<feMorphology operator="dilate" radius="1"></feMorphology>
</filter>
</defs>
<use xlink:href="#flower"></use>
<use xlink:href="#flower" transform="translate(150, 0)" style="filter:url(#erode);"></use>
<use xlink:href="#flower" transform="translate(300, 0)" style="filter:url(#dilate);"></use>
</svg>
<feConvolveMatrix> 元素
<feConvolveMatrix> 元素可以按照它邻近的像素计算像素的新值。可以生成如模糊、锐化、浮雕、斜切等效果。它的原理是合并像素和它邻近的像素,生成结果像素值
某个像素 P 和它邻近的 8 个像素如下(该滤镜的常见情况)
A B C
D P E
F G H
在 kernelMatrix 属性中指定 9 个因数即可,这些数字代表每个像素乘以多少。这些结果会被累加,总数很可能大于 1,因此为了均匀强度,结果还要除以因数总和。架设指定了如下 9 个数字
<feConvolveMatrix
kernelMatrix="
0 1 2
3 4 5
6 7 8"
></feConvolveMatrix>
像素 P 的新值如下公式计算得到
P = ((0 * A) + (1 * 8) + (2 * C) + (3 * D) + (4 * P) + (5 * E) + (6 * F) + (7 * G) + (8 * H)) / (0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8)
如果所有矩阵值总和为 0,则不会执行除法
还可以指定一个 bias 属性,通过为每个像素添加指定的偏移值来改变滤镜的输出范围。bias 会在除法后面计算,但在最终调整结果到 0-1 范围之前
<!-- @format -->
<svg>
<defs>
<filter id="emboss">
<feConvolveMatrix
preserveAlpha="true"
kernelMatrix="1 0 0 0 0 0 0 0 -1"
bias="0.5"
></feConvolveMatrix>
</filter>
<g id="flower">
<image xlink:href="./flower.svg"></image>
</g>
</defs>
<use xlink:href="#flower" style="filter: url(#emboss)"></use>
</svg>
可以通过 order 属性指定矩阵规格,如果指定 order="4",则 kernelMatrix 属性中就需要 16 个数字。order="3 2" 可以指定 3 列 2 行的矩阵。矩阵越大,生成结果所需要的计算就越多
edgeMode 属性决定图形边缘的像素如何确定
duplicate:默认值。复制所需方向上的边缘值生成邻近值wrap:绕到相反的一侧找到邻近值。如顶部像素的邻近值在底部,如果图片被用作重复平铺,该效果很有用none:为所有缺失的邻近值提供一个透明的黑色像素(RGBA 都为 0)
<feDisplacementMap> 元素
该元素使用第二个输入的颜色值决定第一个输入中移动像素的距离
xChannelSelector:指定哪个颜色通道来影响像素的x坐标。取值为 R、G、B、AyChannelSelector:指定哪个颜色通道来影响像素的y坐标scale:指定缩放因子,如果不指定,则该滤镜无效
下面示例无效,不知为何
<!-- @format -->
<svg width="100%">
<defs>
<linearGradient id="gradient">
<stop offset="0" style="stop-color: #ff0000" />
<stop offset="0.5" style="stop-color: #00ff00" />
<stop offset="1" style="stop-color: #000000" />
</linearGradient>
<rect
id="rectangle"
x="0"
y="0"
width="100"
height="200"
style="fill: url(#gradient)"
/>
<filter id="displace">
<feImage xlink:href="#rectangle" result="grad" />
<feDisplacementMap
scale="10"
xChannelSelector="R"
yChannelSelector="G"
in="SourceGraphic"
in2="grad"
/>
</filter>
<g id="flower">
<image xlink:href="./flower.svg"></image>
</g>
</defs>
<use xlink:href="#flower" style="filter: url(#displace)" />
</svg>
还可以对两个输入使用同一图形,这表明图形的位移由它自己的着色控制
<!-- @format -->
<svg>
<defs>
<filter id="self-displace">
<feDisplacementMap
scale="10"
xChannelSelector="R"
yChannelSelector="G"
in="SourceGraphic"
in2="SourceGraphic"
/>
</filter>
<g id="flower">
<image xlink:href="./flower.svg"></image>
</g>
</defs>
<use xlink:href="#flower" style="filter: url(#self-displace)" />
</svg>
<feTurbulence> 元素
该元素可生成大理石、云彩等人工纹理效果。其原理是使用由 Ken Perlin 开发的方程,该方程被称为 Perlin noise。需要指定以下属性
type:turbulence和fractalNoise之一,后者显示更平滑baseFrequency:大于 0 且小于 1 的数值,值越大,结果颜色变化越快numOctaves:默认值为 1。噪音函数使用的数值,生成最终结果时应该加上该数值。数值越大,纹理粒度越细seed:默认值为 0。滤镜使用的随机数生成器的种子