svg 单通道色彩从分布 feComponentTransfer 与 feFunc 相关函数
介绍
<feComponentTransfer>
滤镜为组件置换滤镜,它允许你配合 <feFuncR>
、<feFuncG>
、<feFuncB>
、<feFuncA>
这些色彩通道组件对 RGBA 四个颜色通道进行色彩的重新分布。 比如把红色整体加深,又或者去蓝光等等。
也许你会觉得这些功能 <feColorMatrix>
色彩矩阵滤镜就可以做,但是组件置换滤镜对单通道的支持更强,你可以使用区间、离散点、线性或者指数方式来重修某一色彩。
组件置换滤镜需要搭配 <feFuncR>
、<feFuncG>
、<feFuncB>
、<feFuncA>
四个滤镜组件使用,这四个组件各自对应了红色、绿色、蓝色和透明度(alpha)四个颜色通道。它们的一般使用如下:
<filter id="ComponentTransfer-1">
<feComponentTransfer>
<feFuncR type="" />
<feFuncG type="" />
<feFuncB type="" />
<feFuncA type="" />
</feComponentTransfer>
</filter>
<feComponentTransfer>
并没有任何特殊属性,当然你也可以使用 in
、class
或 style
等属性来修改它的输入源或者样式。
每个色彩通道组件上都有一个 type
属性用来标记从新分布色彩的方式。色彩通道组件的 type
属性可以为 "identity | table | discrete | linear | gamma"
。
设 C’ 为修改后的色彩,C 为原始色彩,在计算时,使用的是 非预乘 数据计算。
默认情况下,每种颜色的非预乘分布函数如下:
其数据图为:
这个图就是原本的颜色分布方式,可以看到默认情况下的颜色是线性的。
feFunc 色彩通道的 type 属性
四种颜色通道组件都有一个 type
属性,用来设置各种颜色按照什么方式从新分布。根据 type
值的不同,新的分布可能是线性、离散或指数等形式的。
所有类型分布颜色的计算方式可以参看这里。
identity 标识
当 type="identity"
时滤镜不会对颜色进行任何修改,也就是说原函数并不会改变。比如下面这个滤镜,所有通道仅做标识,所以使用该滤镜不会对原图造成任何影响。
<filter id="ComponentTransfer-identity">
<feComponentTransfer>
<feFuncR type="identity" />
<feFuncG type="identity" />
<feFuncB type="identity" />
<feFuncA type="identity" />
</feComponentTransfer>
</filter>
table 表格分布
type="table"
是通道使用表格分布。表格分布可以拼接多个不同的线性分布。表格分布需要额外设置 tableValues
属性,这个属性接收 n 个数据用于创建 n - 1 个区间平分颜色范围,并使用这些数据创建 n - 1 个新的区间与原本的区间一一映射,新颜色将会使用该分布重新分配颜色。
比如:
<feFuncR type="table" tableValues="0 0.8 0.9 1" />
上面 tableValues
设置了 4 (n)个值,这 4 个值会平分颜色到 3 (n - 1)个范围中:
0 ~ 0.33
0.33 ~ 0.66
0.66 ~ 1
接着会创建 3 (n - 1)个分布范围与上面的范围产生映射:
0 ~ 0.8
0.8 ~ 0.9
0.9 ~ 1
假如原颜色为 0.5,它属于 0.33 ~ 0.66 这个区间的中点,那么计算后它就会被从新映射到 0.8 ~ 0.9 这个区间的中点,得到 0.85。
使用二次坐标系可以直观的看到新的颜色分布:
因为原本颜色分布时一次函数,所以平分的范围恰好对应平分的横坐标范围。
从图中可以看到,tableValues
提供的数据实际上是图中的点,它们并不一定是要递增的,你可以设置 [0,1] 内的任何点,并以任何方式组合,不过与之带来的图也会更加复杂。除此之外,这些点是组合的图是连续的,并没有间断,也就是说,表格分布并不会造成跳跃式的颜色断层。
可以看到新的颜色分布加重了该通道的颜色,相比 Color(C) 函数,这个函数的颜色会更重一些。
观看以下示例:
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="400" height="200" viewBox="0 0 10 10">
<defs>
<linearGradient id="Red-Trans" x1="0" x2="1">
<stop stop-color="#000000" stop-opacity="1" offset="0" />
<stop stop-color="#ff0000" stop-opacity="1" offset="100%" />
</linearGradient>
<filter id="ComponentTransfer-table-2">
<feComponentTransfer>
<feFuncR type="table" tableValues="0 0.8 0.9 1 " />
<feFuncG type="identity" />
<feFuncB type="identity" />
<feFuncA type="identity" />
</feComponentTransfer>
</filter>
</defs>
<rect x="0" y="0" width="100%" height="5" fill="url(#Red-Trans)" />
<rect x="0" y="5" width="100%" height="5" fill="url(#Red-Trans)" filter="url(#ComponentTransfer-table-2)" />
</svg>
上面代码中应用了前面提到的表格分布,数值也没有改变,为了更加清楚的观看效果,使用了渐变效果,效果如下:
可以明显的看到,使用表格分布的矩形渐变得更快,颜色更加鲜艳。
把这个图和该组件的函数图对应上,就会感觉一目了然。这些颜色是连续的,并不会产生间断,正好对应了函数图中的效果。
根据 tableValues
提供数据不同,可以生成的分布效果也多样,比如使用 tableValue="1 0"
,此时颜色 [0, 1] 将被映射到 [1 ,0] 中,让颜色反向。此时新分布将变成:
有兴趣可以试一试。不过这样的线性分布最好使用后面的 type="linear"
,它能更直观的生产新的线性分布。
discrete 离散分布
type="discrete"
时通道采用离散分布。离散分布也接收 tableValues
属性提供的 n 个数据,并把颜色平分为 n 个范围,同时映射到这 n 个数据对应的点上。
比如:
<feFuncR type="discrete" tableValues="0.5 0.8 0.5 0.3" />
上面 tableValues
设置了 4 个值,这 4 个值会平分颜色到 4 个范围中:
0 ~ 0.25
0.25 ~ 0.5
0.5 ~ 0.75
0.75 ~ 1
这些范围的颜色会依次映射到值 0.5、0.8、0.5、0.3,最后得到的函数图大概如下:
使用这样的分布,表示该通道将把颜色分布到四个确切的值上。也就是说,红色现在就只剩下 4 个色值,比如 0.2 在 0 ~ 0.25 之间,那么转化后的颜色将为 0.5。
虽然 W3C 上的计算方式的确如此,但是我在实际开发效果并不如预期,比如:
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="400" height="200" viewBox="0 0 20 10">
<defs>
<linearGradient id="Red-Trans" x1="0" x2="1">
<stop stop-color="#000" stop-opacity="1" offset="0" />
<stop stop-color="#f00" stop-opacity="1" offset="100%" />
</linearGradient>
<filter id="ComponentTransfer-discrete-1">
<feComponentTransfer in="SourceGraphic">
<feFuncR type="discrete" tableValues="0.5 0.8 0.5 0.3" />
<feFuncG type="identity" />
<feFuncB type="identity" />
<feFuncA type="identity" />
</feComponentTransfer>
</filter>
</defs>
<rect x="0" y="0" width="100%" height="5" fill="url(#Red-Trans)" />
<rect x="0" y="5" width="100%" height="5" fill="url(#Red-Trans)" filter="url(#ComponentTransfer-discrete-1)" />
</svg>
上面代码也是对红色渐变使用了单通道滤镜,对红色通道进行 0.5 0.8 0.5 0.3
的离散分布,按道理说,使用该滤镜后,渐变红色将会被平分为 4 等分,并且颜色会按照离散值进行重新绘制,但是实际效果如下:
也许你会很疑惑,为什么和函数图的效果完全不一样,我们期待的应该是应该是四个平均的红色块,然后它们的红色依次是 0.5 * 255、0.8 * 255、0.5 * 255 和 0.3 * 255,但是上图不论是大小还是色彩,都存在巨大的偏差(使用吸管工具测试后色彩也对不上)。
这并不是计算错误,因为我们输入的色彩值是 sRGB,然而滤镜是根据 linear RGB 来计算,所以最后的结果出现了偏差。
物理意义上的颜色是线性的,也就是 linear RGB。也许是人日行动物,接受的光源较多,原本的 linear RGB 色彩让人感觉亮度太低了,为了迎合人心里感光方式,计算机开发商统一使用 sRGB 色值输出到屏幕,于是整体的色彩更加亮一些了。
一个色彩为 127,那么映射到 [0 ,1] 区间它的值约为 0.5(127 / 255),我们输入的是 sRGB,转化为 linear RGB 为:
所以 sRGB 的 0.5 转化到 linear RGB 中才 0.084,这个值属于 0 ~ 0.25,应该使用第一个离散值进行渲染,因为这种转化,导致了渐变区域并没有被平分成 4 等分。关于计算公式,可以到本节最后的参考链接中寻找。
这还没有结束,第一个离散值设置的是 0.5,这又是 linear RGB 中的值,为了渲染到显示器上,计算机又会把这个颜色转化为 sRGB,也就是反向进行之前的步骤,计算如下:
转化后的值为 0.735,再转化为 16 进制颜色是 bb
,如果使用吸管测试第一个部分的颜色,会发现颜色就是 #bb0000
,和计算结果相吻合。
实际上这些计算并不用去操心,因为我们输入的离散值和颜色都是 sRGB,只是在 <linearGradient>
这样的线性渐变上可以看出明显的不平均处理。而在生产环境中,滤镜一般用来处理一个整体,大多时候是图片,使用 sRGB 来思维可以更好调整色彩。
linear 线性分布
type="linear"
时通道采用线性分布。线性分布非常简单,仅用一个一次函数来重新分布色彩。一次函数仅需要提供 slope
斜率和 intercept
偏移值两个属性即可。
比如:
<feFuncR type="linear" slope="-0.5" intercept="0.5" />
上面代码把红色重新分布到了下面曲线上:
大概这么条线:
根据曲线可以明显看到,红色是反向的。
示例:
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="400" height="200" viewBox="0 0 20 10">
<defs>
<linearGradient id="Red-Trans" x1="0" x2="1">
<stop stop-color="#000" stop-opacity="1" offset="0" />
<stop stop-color="#f00" stop-opacity="1" offset="100%" />
</linearGradient>
<filter id="ComponentTransfer-linear-1">
<feComponentTransfer in="SourceGraphic">
<feFuncR type="linear" slope="-0.5" intercept="0.5" />
<feFuncG type="identity" />
<feFuncB type="identity" />
<feFuncA type="identity" />
</feComponentTransfer>
</filter>
</defs>
<rect x="0" y="0" width="100%" height="5" fill="url(#Red-Trans)" />
<rect x="0" y="5" width="100%" height="5" fill="url(#Red-Trans)" filter="url(#ComponentTransfer-linear-1)" />
</svg>
上面依旧使用了渐变效果,并在第二个渐变区域使用了反向的红色滤镜,导致红色的渲染翻转了,效果如下:
同上一节一样,所有 <feComponentTransfer>
滤镜渲染时都有 sRGB 到 linear RGB 颜色的转变,所以红色被保留的部分比较多,最大值实际上是 linear RGB 的 0.5,也就是 sRGB 的 0.735,上一节介绍过。
gamma 伽马分布
type="gamma"
时通道采用伽马分布。伽马分布类似线性分布,使用指数函数进行从分布,它提供 amplitude
幅度、exponent
指数和 offset
偏移三个属性来设置指数函数。
公式如下:
其实就是个指数函数嘛,根据指数函数的相关性质,exponent
为 1 时,函数呈线性,小于 1 积分面积变大,颜色会更饱和些,反之大于 1 颜色会被压缩,会暗淡些,但是这并不会影响最大最小值。
当 offset
为 0 ,amplitude
为 1 时,函数图大致如下:
其中:
-
蓝色为
exponent = 1
的情况; -
红色为
exponent < 1
的情况; -
灰色为
exponent > 1
的情况;
明暗效果就可以用伽马分布模拟,比如:
<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>
<g id="Image">
<image
x="0"
y="0"
width="10"
height="10"
xlink:href="/imgs/test1.jpg"
/>
</g>
<filter id="ComponentTransfer-linear-1">
<feComponentTransfer>
<feFuncR type="gamma" exponent="4" />
<feFuncG type="gamma" exponent="4" />
<feFuncB type="gamma" exponent="4" />
<feFuncA type="identity" />
</feComponentTransfer>
</filter>
</defs>
<use href="#Image" transform="translate(0,0)" filter="url(#ComponentTransfer-linear-1)" />
</svg>
上面代码把三个颜色通道都设置成了相同的 gamma 分布,让图片显得更暗淡了些,效果如下:
小结
这些分布中,超过分布的范围会使用分布 0 或 1 进行阈值处理。<feComponentTransfer>
滤镜对单色的处理能力非常强,大部分单色需求都可以使用该滤镜处理。除此之外,对 RGB 三个颜色进行同样的分布处理,即可跳转亮度、对比度、色彩平衡或阈值处理。
相比 <feColorMatrix>
来说,<feComponentTransfer>
更注重单色处理,<feColorMatrix>
更注重各种颜色之间的关系。
吐槽:我比较郁闷为什么没有贝塞尔曲线分布,如果有的话色彩控制将会更强。