常用滤镜算法以及WebGL实现

© Young 2017-08-22 10:41
Welcome to My GitHub

前言

如果你想仔细了解文中WebGL实现的滤镜算法,首先你得具备一些原生WebGL知识,但是如果你只是想大概了解滤镜算法的作用和规律,不具备原生WebGL知识也没关系,建议跳过具体实现,聚焦文字说明、函数、图形、矩阵等;另外下述部分关于滤镜的WebGL实现都是参考于WebGLImageFilter,因此你也可以认为这篇文章有部分内容是对WebGLImageFilter这个类库源代码的简单解析。

引子

几天前设计跟我说来实现稍微不一样的阴影,如下:

简单来说就是某个元素的阴影需要和其内容相关,这就可能导致阴影颜色是渐变的,而且渐变还没有明显规律,显然使用CSS3的box-shadow暂时是没办法实现的了。

稍微思考了一下,其实可以采用重叠方式来模拟,首先复制该元素,然后使用滤镜过滤,最后把过滤之后的元素和原始元素重叠在一起,原始元素在上,过滤元素在下,从而实现和内容相关阴影。

由于CSS3、SVG、Canvas等都直接支持滤镜,因此实现方式也有很多种;

一、CSS3实现

例子一
例子二

二、Canvas实现

例子三
例子四

需要注意的Canvas的filter属性兼容性很不好,因此下边的工具都是使用CSS3 filter来实现的。

另外因为阴影会降低页面的渲染效率,而且页面中的元素不一定是固定不变的,因此静态方式不能完全满足实际需要,所以我封装了一个简单的工具类库colorful-shadow用于动态给某个元素增加内容阴影,

Github上有源代码,在这个项目里边也有简单的测试用例

可以调用init方法来实现自定义的重叠内容阴影;
也可以直接调用bottomContentShadow来实现底部内容投影阴影;
或者调用bottomGradientShadow来实现底部渐变阴影。

另外由于是涉及重叠元素的动态定位,这就要求原始元素必须具有定位属性了,比如:relative、absolute、fixed等。

滤镜不光频繁出现在各种Web技术中,而且在各种图像处理软件中也有很多类似的概念;

由于滤镜实在是太普遍了,作为一个有追求的程序员(出于装逼的需要),仅仅知道怎么用,或者就算知道一些特殊的用法,显然也是远远不够的!

那接下来就聊聊滤镜算法以及实现吧……

blur高斯模糊

详细请看阮一峰 高斯模糊的算法,通俗易懂。

高斯模糊 WebGL实现

另外需要的注意当前实现相比于CSS blur、Canvas blur等通用的高斯模糊滤镜存在两个问题,第一不能处理纯色的情况;第二当参数设置过大时会出现聚合的情况。

主要算法如图:

图中区域A很明显是高斯函数权值,区域B则是模糊范围,但是有个地方需要注意一下,上文中高斯模糊的范围是当前像素点的四周,但是区域B只是对角线区域,为什么会这样呢?

从上述代码可以看到执行高斯模糊算法片元着色器时是分两次执行的,先设置blur.x为0处理纵轴,然后设置blur.y为0处理横轴,初一看起来这样做也只是处理了纵轴和横轴附近的点和处理四周的点依然有差距,其实不然因为是分开处理,最后处理横轴是基于上一次处理纵轴之后的数据,和一次性处理所有四周的效果是一样的。

维基百科上是这么描述的:

高斯模糊也可以在二维图像上对两个独立的一维空间分别进行计算,这叫作线性可分;这也就是说,使用二维矩阵变换得到的效果也可以通过在水平方向进行一维高斯矩阵变换加上竖直方向的一维高斯矩阵变换得到。

contrast对比度

contrast对比度 WebGL实现

主要算法如图:

需要注意一下上述算法中alpha通道没有变化且alpha通道也不会影响其它色值;

变换矩阵如图:

从片元着色器算法和变换矩阵可知contrast对比度算法中变换后色值和变换前色值的函数关系如下:

由此可知随着contrast值的不断增大,大于128的色值会越来越快速的增加到255,小于128的色值会越来越快速的减少到0,简单来说这种算法会让图像中的0-255的中间色值越来越少

稍微了解blur高斯模糊和contrast对比度后,就能理解在下边的这个例子中blur和contrast结合的效果了。



图中1位置即使在blur滤镜的基础上加了contrast滤镜之后依然没有变化是因为contrast滤镜算法中alpha值不会变化也不会影响其它色值,但是2区域确出现了红色,则是因为blackrgb(0,0,0)yellowrgb(255,255,0),那么中间的混合颜色区域肯定存在一部分R大于128G小于128的区域,这段区域在经过contrast对比度算法处理后R大于128的会快速增加为255G小于128的则会快速减少为0,最终呈现为rgb(255,0,0)为红色,contrast值越大红色和黄色的过渡颜色越少。

brightness亮度

brightness亮度 WebGL实现

由于brightness亮度算法也不涉及alpha通道的变化,因此可以和contrast对比度算法共用相同的片元着色器。

变换矩阵如图:

从片元着色器算法和变换矩阵可知brightness亮度算法中变换后色值和变换前色值的函数关系如下:

由此可知随着brightness值的不断增大,所有色值都会越来越快速的增加到255,简单来说这种算法会让小于255的色值越来越少

另外有个地方需要注意下,刚开始我以为brightness值超级大的时候,图像所有色值都会变成255,那图像最终会变成一片空白,然而实际却不是这样,不管brightness值设置成多大,图像中依然有些地方有颜色。

其实仔细看那个函数图就会发现,不管斜率如何增大,色值变化直线始终都会过原点,因此brightness值超级大时,图像中依然存在非空白颜色的地方就是那些一开始色值中就有某些通道为0的地方

其实仔细看上边那个brightness亮度滤镜算法存在一个问题的,也就是这个算法只能增加亮度不能降低亮度,而CSS原生的filter设置brightness值为小数时会降低亮度,主要原因是该算法会在设置的brightness值之后再加上1,导致函数斜率始终大于1。
另外增加亮度就是让所有色值向255(WebGL中是1)靠拢,那么也就意味着亮度滤镜算法并不是固定的,你可以实现其它多种算法。

grayscale灰度

grayscale灰度 WebGL实现

灰度照片只有256种颜色,一般的处理方法是将图像颜色值的RGB三个通道设为一样,这样图像的显示效果就会是灰色;灰度处理有很多中方法,常用的是加权平均值法,即新的颜色值R=G=B=(R * Wr+G*Wg+B*Wb)

一般由于人眼对不同颜色的敏感度不一样,所以三种颜色值的权重不一样,一般来说绿色最高,红色其次,蓝色最低,最合理的取值分别为Wr = 30%,Wg = 59%,Wb = 11%。

HSL、HSV

上边简单介绍了四种滤镜算法,我们大概知道了这些算法是怎么改变RGBA色值,但是我们并不清楚为什么要这么变化。比如brightness亮度滤镜为什么要让小于255的色值越来越少呢?grayscale灰度为什么要让RGB三个通道变为一样呢?

从RGBA颜色表示方法我们很难找到原因,因为RGBA颜色表示方法并不直观和人类感觉颜色的逻辑不太符合,因此诞生了HSL和HSV两种更直观的颜色表示方法。

HSL简单来说就是什么颜色饱和度如何亮度如何
HSV简单来说就是什么颜色深浅如何明暗如何

更多详情请去维基百科查询HSL和HSV色彩空间

之所以要提及这两种颜色表示方法是因为滤镜更多是人们从自身的颜色视角出发对图像进行一些处理的算法,单纯看RGBA的变化会对这种变化感觉比较困惑。

invert反转

invert反转 WebGL实现

invert反转同样没有涉及alpha通道的变化,因此还是和上边的几种滤镜共用相同的片元着色器。

变换矩阵如图:

在我理解来说invert反转滤镜属于色相变化滤镜,hue-rotate色相旋转滤镜(后续会介绍)也属于色相变化滤镜,只不过区别在于变化方式不一样,hue-rotate色相旋转滤镜应该是所有色相绕成一个圆然后旋转变化而invert反转滤镜则是直接翻转变化,从变换矩阵也可以看出。

可以看到从grayscale灰度滤镜开始我就没有再画函数图,并不是因为变懒了,而是因为以前画图是期望从图中找到这种滤镜算法在RGBA中的变化规律从而更好理解的该滤镜算法,但实际上从RGBA的变化规律理解反而会更迷惑;因此上边介绍了两种新的颜色表示方法,正确理解这些滤镜算法的路径应该是把RGBA色值的变化规律转换成HSL或者HSV的变化规律,转换规律可以在维基百科中查询到HSL和HSV色彩空间

hue-rotate色相旋转

色相旋转就是如上图所示颜色按照圆环中的规律旋转,变换矩阵如下图所示;

如果说grayscale灰度滤镜里边的常量30%、59%、11%还能勉强予以强行理解并接受的话,那这个变换矩阵和里边的常量就目前来说有点不知所云了,既然不能理解已知结果,那么我们可以从条件重新推导,实现自己的色相旋转变换矩阵。

任意RGB色彩可以表示在三维空间,那么所谓的色相旋转就是这个RGB点绕着图中RGB三点构成的平面的中垂线旋转,而3D空间中绕任意过原点轴旋转矩阵如下:

/**
 * A表示旋转角度;
 * N = [x,y,z]表示旋转轴方向上的单位向量;
 * M表示绕N旋转A的矩阵。
 */
 M = [x*x*(1-cosA)+cosA , x*y*(1-cosA)+x*sinA , x*z*(1-cosA)-y*sinA,
      x*y*(1-cosA)-z*sinA , y*y*(1-cosA)+cosA , y*z*(1-cosA)+x*sinA,
      x*z*(1-cosA)+y*sinA , y*z*(1-cosA)-x*sinA , z*z*(1-cosA)+cosA]

推导过程请看WebGL THreeJS学习总结四3D空间绕任意过原点轴旋转矩阵推导部分。

代入N = [1/Math.sqrt(3),1/Math.sqrt(3),1/Math.sqrt(3)],最终变换矩阵如下:

//表示旋转角度
M = [
        1.0/3*(1-cosR)+cosR, 1.0/3*(1-cosR)+1.0/Math.sqrt(3)*sinR, 1.0/3*(1-cosR)-sinR, 0, 0,
        1.0/3*(1-cosR)-1.0/Math.sqrt(3)*sinR, 1.0/3*(1-cosR)+cosR, 1.0/3*(1-cosR)+1.0/Math.sqrt(3)*sinR, 0, 0,
        1.0/3*(1-cosR)+1.0/Math.sqrt(3)*sinR, 1.0/3*(1-cosR)-1.0/Math.sqrt(3)*sinR, 1.0/3*(1-cosR)+cosR, 0, 0,
        0, 0, 0, 1, 0
    ];

hue-rotate色相旋转 WebGL实现

简单测试了一下自己推导的色相旋转矩阵的效果,貌似没什么问题,但我并不能百分百确定其正确与否。

未完待续

发表评论

电子邮件地址不会被公开。 必填项已用*标注