o'Reill的SVG精髓(第二版)学习笔记——第十一章

Posted 风雨飘飘飘啊飘

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了o'Reill的SVG精髓(第二版)学习笔记——第十一章相关的知识,希望对你有一定的参考价值。

第十一章:滤镜

11.1滤镜的工作原理

当SVG阅读器程序处理一个图形对象时,它会将对象呈现在位图输出设备上;在某一时刻,阅读器程序会把对象的描述信息转换为一组对应的像素,然后呈现在输出设备上。例如我们用SVG的<filter>元素指定一组操作(也称作基元,primitive),在对象的旁边显示一个模糊的投影,然后把这个滤镜附加给一个对象:

   <fliter id="drop-shadow">
        <!-- 这是滤镜操作 -->
    </fliter>
    <g id="spring-flower" style="filter:url(#drop-shadow);">
    <!-- 这里绘制花朵 -->
    </g>

由于花朵在显示样式中用了滤镜,所以SVG不会将花朵直接渲染为最终图形,相反,SVG会渲染花朵的像素到临时位图中。由滤镜指定的操作会被应用到该临时区域,其结果会被渲染为最终图形。

11.2创建投影效果

11.2.1建立滤镜的边界

<filter>元素有一些属性用来描述该滤镜的裁剪区域。我们按照滤镜对象边界框的百分比指定x、y、width和height(这也是默认方式)。任何在边界外部的输出都不会显示。如果想要为多个对象应用到同一个滤镜,可能要完全忽略这些属性,并用默认值x等于-10%,y等于-10%,width等于120%,height等于120%。这就为滤镜提供了额外的空间——这样构造的投影,产生的输出就会比输入大。

这些属性是按照滤镜对象的边界框来计算的,这是比较特殊的地方,即filterUnits的默认值是objectBoundingBox。如果想要按照用户单位指定边界,设置这个属性的值为userSpaceOnUse即可。

还可以用primitiveUnits属性为用于滤镜基元中的单位指定单位。默认值为userSpaceOnUse,但是如果设置为objectBoundingBox,就可以按照图形尺寸的百分比来表示单位。

11.2.2投影<feGaussianBlur>

起始和结束<filter>标记之间就是执行我们想要的操作的滤镜的基元。每个基元有一个或多个输入,但只有一个输出。一个输入可以是原始图形(被指定为SourceGraphic)、图形的阿尔法(不透明度)通道(被指定为SourceAlpha),或者是前一个滤镜基元的输出。

第一次尝试为花朵生成一个投影,使用的是<feGaussianBlur>滤镜基元。我们指定SourceAlpha为它的输入源(用in属性),用stdDeviation属性指定它的模糊度,这个数值越大,模糊度越大。如果给stdDeviation值提供两个由空格分隔的数字,第一个数字被作为x方向的模糊度,而第二个被作为y方向的模糊度。

  <fliter id="drop-shadow">
        <feGaussianBlur in="SourceAlpha" stdDeviation="2" />
    </fliter>
    <g id="spring-flower" style="filter:url(#drop-shadow);">
    <!-- 这里绘制花朵 -->
    </g>

滤镜返回的是一个模糊的阿尔法通道,而不是原始图形。通过将花朵放入文档的<defs>中可以得到我们想要的效果。

更好的解决方案是添加更多的滤镜基元,让所有的工作可以在渲染期间一次完成。

 

11.2.3存储。链接以及合并滤镜结果。

  <fliter id="drop-shadow">
        <feGaussianBlur in="SourceAlpha" stdDeviation="2" result="blur" />
        <feOffset in="blur" dx="4" result="offsetBlur" />
        <feMerge>
            <feMergeNode in="offsetBlur" / >
            <feMerfeNode in="SourceGraphic" />
        </feMerge>
    </fliter>

result属性指定当前基元的结果稍后可以通过blur名引用,这不同于XML id,给定的名称是一个局部名称,它只在包含该基元的<fliter>中有效。

<feOffset>基元接受它的输入,在这里就是Gaussian blur的返回结果blur,它的偏移由dx和dy的值指定,然后将结果位图存储在offsetBlur名字下面

<feMerge>基于包裹一个<feMergeNode>元素列表,其中每个元素都指定一个输入,这些输入按照它们出现的顺序一个堆叠在另一个上面。在这里我们希望offsetBlur在原始SourceGraphic下面。

引用滤镜:

  <g id="spring-flower" style="filter:url(#drop-shadow);">
    <!-- 这里绘制花朵 -->
    </g>

11.3 创建发光式投影。

11.3.1<feColorMatrix>元素

<feColorMatrix>元素允许我们以一种非常通用的方式改变颜色值。用于创建蓝绿色发光式投影的基元序列如下示例:

  <svg width="500px" height="500px" viewBox="0 0 500 500">
    <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"/>
        <feGaussianBlur stdDeviation="2.5" result="coloredBlur" />
        <feMerge>
            <feMergeNode in="coloredBlur" />
            <feMergeNode in="SourceGraphic" />
        </feMerge>
    </filter>
    <text x="120" y="50" style="filter:url(#glow);fill:#003333;font-size: 18;">
        Spring<tspan x="120" y="70">Flower</tspan>
    </text>
    </svg>

<feColorMatrix>是一个通用的基元,允许我们修改任意像素点的颜色或者阿尔法值。当type属性等于matrix的时候,我们必须设置value为20个数字来描述变换信息。这20个数字按照4行5列编写最好理解。每一行代表一个代数方程,定义了如何计算输出的R、G、B、A值(按行的顺序)。每行中的数字分别乘以输入像素的R、G、B、A的值和常量1(按照列的顺序),然后加在一起得到输出值。要设置一个变换,将所有不透明区域绘制为相同的颜色,可以忽略输入颜色和常量。只要设置阿尔法列的值即可。

这里red、green和blue的值通常是0到1之间的十进制数。

上述例子中,<feColorMatrix>的结果是青色的色源。该滤镜的其他部分在它的基础上使用了一个高斯模糊;青色模糊结果被存储下来,以便将来用coloredBlur引用它。

11.3.2<feColorMatrix>元素详解

其type属性还有其他三个值,每个内置的颜色矩阵都完成一个特定的视觉任务,并且都有自己的指定values的方式

①hueRotate(色相旋转)

value是一个单一的数字,描述颜色的色相值应该被旋转多少度。

在线版本示例:http://oreillymedia.github.io/svg-essentials-examples/ch11/hue_rotate.html

②saturate(饱和度)

value属性指定一个0到1之间的数字。数字越小,颜色“褪色”越多。值为0,则将图形转换为黑白图。这个滤镜只可以用来降低图像的饱和度(洗白图像),不能增加饱和度(将正常图片变成彩色图像。)

http://oreillymedia.github.io/svg-essentials-examples/ch11/saturate.html

③luminanceToAlpha(用亮度决定阿尔法值)

这个滤镜根据颜色的亮度建立阿尔法通道,这个亮度是颜色固有的“亮度”。

 

11.4 <feImage>滤镜

<feImage>元素允许我们使用任意的JPG、PNG、SVG文件,或者带有id属性的SVG元素作为滤镜的输入源。

<feImage xlink:href="sky.jpg" result="sky" x="0" y="0" width="100%" height="100%" preserveAspectRatio="none" />

图像默认被拉伸以适应定义在<filter>元素上的滤镜区域(默认情况下滤镜没有尺寸,因此默认滤镜区域是对象边界框超出10%的范围)。在<feImage>元素上可以设置明确的宽度、高度和x/y偏移。默认情况下,这些都使用userSpaceOnUse单位;然而,所有的百分比值都是相对于滤镜区域进行计算的。可以在<filter>元素上使用primitiveUnits属性来切换到objectBoundingBox的单位,但是这会影响滤镜中的所有元素。

11.5 <feComponentTransfer>滤镜

<feComponentTransfer>提供了一种更方便、更灵活的方式来单独操作每个颜色分量。它还允许我们对每个颜色分量作出不同的调整,因此我们既可以让蓝天更亮,也可以通过增加绿色和红色级别(多于蓝色级别),让它没有那么强烈。

可以通过在<feComponentTransfer>内配置<feFuncR>、<feFuncG>、<feFuncB>和<feFuncA>元素,调整红、绿、蓝色和阿尔法的级别。每个子元素都可以单独制定一个type属性,说明应如何修改该通道。

为了模拟亮度控制的效果,我们要指定linear函数,它会把当前颜色分量值C放到公式slope*C+intercept中。intercept为结果提供了一个“基准值”,slope是一个简单的比例因子。

在线演示:http://oreillymedia.github.io/svg-essentials-examples/ch11/linear_transfer.html

     <feComponentTransfer in="sky" result="sky">
            <feFuncB type="linear" slope="3" intercept="0" />
            <feFuncR type="linear" slope="1.5" intercept="0.2" />
            <feFuncG type="linear" slope="1.5" intercept="0.2" />
        </feComponentTransfer>

简单的线性调整会为一个颜色分量中的所有值加上和乘以一个相同的量。但是gamma函数不是这样的,它把当前颜色值C放入了公式amplitude*C的exponent次方+offset中,offset为结果提供了一个“基准值”,amplitude是一个简单的比例因子,exponent让结果与原始值的对应关系是一条曲线而不是直线。由于颜色值始终是0到1之间的数字,所以,指数越大,修改后的值越小。

 

使用<feComponentTransfer>的伽马校正

http://oreillymedia.github.io/svg-essentials-examples/ch11/gamma_transfer.html

      <feImage xlink:href="sky.jpg" result="sky" />
        <feComponentTransfer in="sky" result="sky">
            <feFuncB type="gamma" amplitude="1" exponent="0.2" offset="0" />
            <feFuncR type="gamma" amplitude="1" exponent="0.707" offset="0" />
            <feFuncG type="gamma" amplitude="1" exponent="0.707" offset="0" />
        </feComponentTransfer>

线性和伽马函数都可以生成大于1.0的颜色值。在每个滤镜基元之后,SCG处理程序都会将值固定在一个有效的范围内。因此,所有大于1.0的值都会被减小为1.0,任何小于0的值都会被调整为0.

<feComponentTransfer>的type属性还有其他选项。我们可以任意混合和匹配这些选项:可以针对红色值使用伽马校正,而用线性函数来亮化绿色值。

①identity:一个什么都不做的函数。它允许我们明确规定颜色通道应该不受影响(如果不给通道提供一个<feFuncX>元素的话,这是默认行为)

②table:允许我们将颜色划分为一系列相等的间隔,每个间隔中的值都相应地扩大,类似这样:最小的四分之一颜色范围的值加倍,下一个四分之一都塞入一个十分之一的范围,保持第三个四分之一的范围不变,然后将最后一个四分之一的值塞入剩下的15%的颜色范围内:

原始值范围修改后的值范围
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:允许我们将颜色值划分为一系列相等的间隔,然后将每个都映射到一个离散的颜色值。

原始值范围修改后的值范围
0.00-0.25 0.125
0.25-0.50 0.375
0.50-0.75 0.625
0.75-1.00 0.875

我们可以在tableValues属性中列出离散值,指定绿色通道的映射,值之间用逗号或者空格分隔:

<feFuncG type="discrete" tableValues="0.125 0.375 0.625 0.875" />

在tableValues属性中分割输入通道为几个部分就需要几个入口。例外情况:如果想要重新映射所有的输入值给单个输出值,必须将该入口放入tableValues中两次,因此,要设置蓝色通道的输入值为0.5:

<feFuncB type="discrete" tableValues="0.5 0.5" />

如果想要反转通道的颜色值范围(即将从小到大的递增改变为从大到小的递增),使用这种方式:

 

定义颜色空间:

SVG使用一种特殊的方式表示颜色,而这样的值并不是一条0到1的直线。这种表示法被称作标砖RGB,也叫sRGB色彩空间。

默认情况下,滤镜算法会使用线性RGB空间的值计算所有插值颜色。因此如果我们给一个填充了渐变的对象应用滤镜,得到的结果可能根本就不是我们所期望的。为了得到正确的结果,我们必须通过给<fliter>元素添加color-interpolation-filters="sRGB"属性,让滤镜按照sRGB色彩空间来计算。作为另一种选择,我们还可以选择不修改滤镜,而是给<gradient>元素应用color-interpolation="linearRGB”,以让它使用的色彩空间与滤镜的默认色彩空间相同。

 

11.6<feComposite>滤镜

更常用的<feComposite>元素接受两个输入源,分别指定在in和in2属性中,它的operator属性用于设置如何合并这两个输入源。

下例中,加什么已经给前一个滤镜基元输出指定了result="A"和result=“B”

<feComposite operator="over" in="A" in2="B" />

生成的结果是A层叠在B上面,正如<feMergeNode>做的那样。事实上<feMergeNode>仅仅是指定over操作的<feComposite>元素的一种便利的快捷方式(<feMergeNode>也允许我们一次层叠两个以上的图形)

<feComposite operator="in" in="A" in2="B" />

结果是A的一部分重叠在B的不透明区域。类似于蒙版效果,但是这个蒙版仅仅基于B的阿尔法通道,而不是它时颜色亮度。不要混淆这个属性值的名字和in属性。

<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" />

结果包含位于B的外面的A的部分和位于A的外面的B的部分。

<feComposite in="A" in2="B" operator="arithmetic"... />

灵活性最大,我们要提供4个系数:k1、k2、k3、k4。每个像素的每个通道的结果按照如下方式计算:

k1*A*B+k2*A+k3*B+k4

这里的A和B是来自输入图形像素的颜色分量。

算数操作符在处理“溶解”效果时很有用。如果想要一个由图像A的a%和图像B的b%生成的结果图像,设置k1和k4为0,k2为a/100以及k3为b/100即可。例如要创建一个30%A和70%B的混合效果:

<feComposite in="A" in2="B" reslut="combined" k1="0" k2="0" k3="0.70" k4="0"/>

 

11.7 <feBlend>滤镜

滤镜还提供了另一种合并图像的方式。<feBlend>元素需要两个输入源,分别指定在in和in2属性中,还需要一个mode属性用于设置如何混合输入源。

可能的值由:normal、multiply、screen、lighten和darken。给定一个不透明的输入源:

<feBlend in="A" in2="B" mode="m"/>,下面描述了每种模式的结果像素的颜色

①normal:只有B,和<feComposite>中的over运算符一样。

②multiply:对于每个颜色通道,将A的值和B的值相乘。由于颜色值在0~1之间,所以相乘会让他们更小。这会加深颜色,对于暗色或者非常强烈的颜色,效果最强烈;如果某个颜色是白色则没有效果。

③screen:把每个通道的颜色值加在一起,然后减去它们的乘积,明亮的颜色或者浅色往往会比暗色占优势,但是相似亮度的颜色会被合并。这个效果类似于有两台不同的幻灯机,每个图像用一个幻灯机,然后照在同一个屏幕上—— 一台幻灯机的强者光会压过。

④darken:提取A和B的每个通道的最小值。颜色较暗。

⑤lighten:提取A和B的每个通道的最大值。颜色较亮。

如果输入源不是不透明的,那么所有的模式(除了screen因素)在计算时都会计算透明度。

最后,一旦颜色值计算完成,结果的透明度就由公式1-(1-opacity of A)*(1-opacity of B)决定。使用这个公式时,两个不透明项仍然保持不透明,而两个不透明度为50%的图形会合并为一个,不透明度变成75%;

 

11.8<feFlood>和<feTile>滤镜

<feFlood>和<feTile>元素都是很实用的,它们很想<feOffset>,允许我们在一系列滤镜基元内执行某些常见的操作,而不是主图形中创建额外的SVG元素。

<feFlood>提供了一个纯色区域用于组合或者合并。我们只需要提供flood-color和flood-opacity,然后滤镜会完全其他工作。

<feTile>会提取输入信息作为图案,然后横向和纵向平铺填充滤镜指定的区域。图案的尺寸由输入给<feTile>的尺寸决定。

 

11.9 光照效果。

外部光照使物体产生颜色称为漫反射;从某个面反射亮点的效果被称作镜面反射。

要得到这些,我们必须指定下列信息:

想要的反射类型(<feDiffuseLighting>漫反射或者<feSpecularLighting>镜面反射)

想要照亮的对象

使用的灯光颜色

想要的光源类型(<fePointLight>点光源、<feDistantLight>远光或者<feSportLight>聚光灯)以及它的位置。

<!-- 带有一个点光源的漫反射照明 -->
    <svg width="500" height="500" viewBox="0 0 500 500">
        <defs>
            <path id="curve" d="M 0 0 Q 5 20 10 10 T 20 20" style="stroke:black;fill:none;" /><!-- 定义用作图案的曲线 -->
            <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" /><!-- 用curve图像平铺滤镜区域,这回变成凹凸贴图 -->
                <feTile in="tile" result="tile"/>
                <feDiffuseLighting in="tile" lighting-color="#ffffcc" surfaceScale="1" diffuseConstant="0.5" result="diffuseOutput">
                    <!-- 这个平铺图区域会输入到<feDiffuseLighting>元素中,还给它加了一个浅黄色的光照,正如lighting-color属性中所指定的 -->
                    <!-- surfaceScale属性代表阿尔法值为1时表面的高度 -->
                    <!-- diffuseConstant是一个用于确定像素最终RGB值的乘积因子。它的值必须大于或等于0,默认值为1,要让lighting-color更明亮,这个值就应该更小 -->
                    <!-- result这个滤镜的结果被命名为diffuseOutput -->
                    <fePointLight x="0" y="50" z="50" />
                    <!-- 这个例子用一个点光源,意味着光源的光辉辐射到所有方向。而我们希望它照亮在滤镜区域的左上角,并且在屏幕的50单位前,设置光源离对象越远,对象被照亮得月均匀。 -->
                </feDiffuseLighting>
                <feComposite in="diffuseOutput" in2="SourceGraphic" operator="in" result="diffuseOutput"/><!-- 用<feComposite>的in属性裁剪滤镜的输出到源图形(圆)的边界。 -->
                <feBlend in="diffuseOutput" in2="SourceGraphic" mode="screen" /><feBlend><!-- 设置为screen模式,它会尝试让源图形变亮 -->
            </filter>
        </defs>
        <circle id="green-light" cx="50" cy="50" r="50" style="fill:#060;filter:url(#diff-light)"/><!-- 在想要的对象上启用滤镜 -->
    </svg>

11.9.2 镜面发射照明

镜面照明就是提供亮点而不是照明

<!-- 远光镜面反射照明 -->
    <svg width="500px" height="500px" viewBox="0 0 500 500">
        <defs>
            <path id="curve" d="M 0 0 Q 5 20 10 10 T 20 20" style="stroke:black;fill:none;" /><!-- 定义曲线 -->
            <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" /><!-- 用curve图像平铺滤镜区域,这回变成凹凸贴图 -->
                <feTile in="tile" result="tile" />
                <feSpecularLighting in="tile" lighting-color="#ffffcc" surfaceScale="1" specularConstant="1" specularExponent="4" result="specularOutput">
                    <!-- 开始定义<feSpecularLighting>滤镜,指定lighting-color为淡黄色 -->
                    <!-- surfaceScale属性代表阿尔法值为1时表面的高度(更通用的说法是,它是计算阿尔法值时的乘积因子) -->
                    <!-- specularConstant是一个用于确定像素最终RGB值的乘积因子,它的值必须大于或等于0,默认值为1。要让lighting-color更明亮,这个值就应该更小。这个数字的效果也可通过specularExponent属性来缓和 -->
                    <!-- specularExponent是用来确定像素最终RGB值的另一个因子。这个属性的值必须是0到128之间的数字,默认值是1。数字越大,结果越明亮。 -->
                    <!-- result这个滤镜的结果被命名为specularOutput -->
                    <feDistantLight elevation="25" azimuth="0" />
                    <!-- 这个例子用了一个远光源,离图像足够远,因而光照射到图片所有部分的角度都一样。所以这里不是指定光源的位置,而是指定光线来源的角度。
                    elevation和azimuth属性允许我们从三个维度指定角度。elevation提供了屏幕平面上光的角度:elevation=“0”表示光线平行于整个图像,而elevation=“90”表示光线直射下来。azimuth属性在平面内指定了角度,当elevation为0时,azimuth=“0”指定光线从图像右侧来(更普遍的说法是x轴正值结束处);azimuth=“90”表示从底部(y轴正值结束处)来,azimuth=“180”表示从左侧来,azimuth=“270”表示从顶部来 -->
                </feSpecularLighting>
                <feComposite in="specularOutput" in2="SourceGraphic" operator="in" result="specularOutput" />
                <!-- <feComposite>的in属性裁剪滤镜的输出到源图形(圆)的边界。 -->
                <feComposite in="specularOutput" in2="SourceGraphic" operator="arithmetic" k1="0" k2="1" k3="1" k4="0"/>
                <!-- 用<feComposite>的arithmetic运算符将光照和源图像叠加 -->
            </filter>
        </defs>

        <circle id="green-light" cx="50" cy="50" r="50" style="fill:#060;filter:url(#spec-light)"/><!-- 启用滤镜 -->
    </svg>

 

三个维度创建光照效果,有一个优秀的教程:http://www.webreference.com/3d/lesson12

11.10访问背景

除了SourceGraphic和SourceAlpha滤镜输入之外,当我们调用一个滤镜时,滤镜对象还可以访问已经渲染到画布上的图片的某一部分。这部分被称为BackgroundImage(不是BackgroundGraphic)和BackgroundAlpha。为了访问这些输入信息,滤镜对象必须位于emable-background属性值为new的容器元素之内。

<!-- 访问背景 -->
    <svg width="500" height="500" viewBox="0 0 500 500">
        <defs>
            <filter id="blur-background"><!-- 类似用于投影的模糊滤镜,只是输入信息是BackgroundImage而不是SourceAlpha -->
                <feGaussianBlur in="BackgroundImage" stdDeviation="10" result="blur" />
                <feComposite in="blur" in2="SourceGraphic" operator="in" />
                <feOffset dx="4" dy="4" result="offsetBlur" />
            </filter>
        </defs>
        <g enable-background="new"><!-- 由于<g>是一个容器元素,在这里放置emable-background是一个完美的选择,它所有的子元素都可以利用背景图像和阿尔法信息。 -->
            <rect x="0" y="0" width="60" height="60" style="fill:lightblue;stroke: blue;stroke-width:10;" /><!-- 把矩形绘制到画布上,然后进入背景缓冲区里。 -->
            <circle o'Reill的SVG精髓(第二版)学习笔记——第八章

o'Reill的SVG精髓(第二版)学习笔记——第十一章

o'Reill的SVG精髓(第二版)学习笔记——第四章

SVG 动画精髓

SVG 动画精髓

《TensorFlow实战Google深度学习框架(第二版)》学习笔记及书评