前言 前文带大家看过3D LUT滤镜 如何实现影片级别的滤镜效果,这种滤镜最为强大,可以实现任何滤镜效果。
通过photoshop导出处理文件,即可通过程序实现对应的滤镜效果,而且由于是颜色查找,即颜色A,通过LUT文件,直接映射成颜色B,所以处理过程只是像素点的颜色映射,不存在计算,速度非常快。
然后也带大家看过如何操纵图片的像素 ,通过canvas获取页面上图片的像素,然后对像素点直接进行计算,再输出新的颜色,就能得到处理后的图片。
现在,带大家看看第二种方式的滤镜,到底可以实现哪些效果。由于这种方式是对像素点进行运算,而这些算法有限,所以能实现的效果也是有限的。
初始化页面 首先,我们初始化一个简单的页面,提供一个上传图片按钮,一个canvas显示图片,并用css控制下图片显示的大小,还有一个点击使用滤镜的按钮。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <!DOCTYPE html > <html > <head > <title > 基础滤镜</title > <style type ="text/css" > canvas { width : 300px ; } </style > </head > <body > <input type ="file" id ="fileInput" name ="选择图片" /> <div class ="wrap-image" > <canvas id ="imageUpload" > </canvas > </div > <div onclick ="useFilter()" > 点击使用滤镜</div > <script type ="text/javascript" > </script > </body > </html >
封装图片处理 应用滤镜,我们会重复使用getImageData、putImageData和遍历像素点的逻辑,这些我们都可以封装起来。
下面我们创建一个CanvasImage类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class CanvasImage { constructor (img, context ) { this .image = img; this .context = context; } getData ( ) { return this .context .getImageData (0 , 0 , this .image .width , this .image .height ); } setData (data ) { this .context .putImageData (data, 0 , 0 ); } transform ( ) { } }
然后我们在上传图片时创建一个CanvasImage的实例,接下来我们看看上传图片。
上传图片 用户点击选择图片按钮后,选择了一张图片。 我们通过new Image()创建一个img,并将src设置为用户选择的本地图片的临时地址,在图片加载完成后,给页面中的canvas设置宽高,然后drawImage把图片显示到canvas上,最后new CanvasImage(img, context)来创建一个CanvasImage的实例filter。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 let filterconst fileInput = document .getElementById ('fileInput' );fileInput.addEventListener ('change' , (e ) => { const imgElement = document .getElementById ('imageUpload' ); const img = new Image (); img.src = URL .createObjectURL (e.target .files [0 ]); img.onload = function ( ) { imgElement.width = img.width imgElement.height = img.height const context = imgElement.getContext ("2d" ); context.drawImage (img, 0 , 0 ); filter = new CanvasImage (img, context); } }, false );
封装滤镜处理函数 现在我们来完善上面空着的transform:
1 2 3 4 5 6 7 8 9 10 11 12 transform (fn, factor ) { const imageData = this .getData () const { data } = imageData for (let i = 0 ; i < data.length ; i += 4 ) { const [r, g, b, a] = fn.call (this , data[i], data[i + 1 ], data[i + 2 ], data[i + 3 ], factor, i); data[i] = r; data[i + 1 ] = g; data[i + 2 ] = b; data[i + 3 ] = a; } this .setData (imageData) }
transform接受2个参数,fn是滤镜算法函数,这个函数输入像素相关数据,输出r,g,b,a。factor代表滤镜算法需要的控制因子。
这样我们就只需要关注滤镜算法函数fn怎么写,然后执行filter.transform(fn, factor)就可以对图片做处理,并把处理后的图片数据更新到页面。
接下来我们看看几个简单的滤镜算法。
滤镜算法 灰度 比如我们先实现一个灰度算法,取r、g、b的加权平均值:
1 2 3 4 function Greyscale (r, g, b ) { const avg = 0.3 * r + 0.59 * g + 0.11 * b; return [avg, avg, avg, 255 ]; }
然后在点击使用滤镜绑定的函数useFilter中可以这样写:
1 2 3 function useFilter ( ) { filter.transform (Greyscale ) }
怀旧 1 2 3 4 function Sepia (r, g, b ) { const avg = 0.3 * r + 0.59 * g + 0.11 * b; return [avg + 100 , avg + 50 , avg, 255 ]; }
负片 1 2 3 function invert (r, g, b ) { return [255 - r, 255 - g, 255 - b, 255 ]; }
噪点 1 2 3 4 function noise (r, g, b, a, factor ) { var rand = (0.5 - Math .random ()) * factor; return [r + rand, g + rand, b + rand, 255 ]; }
调用时需传入噪点控制因子,比如传45:
1 2 3 function useFilter ( ) { filter.transform (noise, 45 ) }
下图是原图、噪点传45和100的对比:
亮度 1 2 3 function brightness (r, g, b, a, factor ) { return [r + factor, g + factor, b + factor, 255 ]; }
调用时,控制因子factor是亮度。 下图是原图、亮度传45和100的对比:
饱和度 1 2 3 4 5 6 7 8 function saturation (r, g, b, a, factor ) { const max = Math .max (r, g, b); r += max !== r ? (max - r) * (-factor) : 0 ; g += max !== g ? (max - g) * (-factor) : 0 ; b += max !== b ? (max - b) * (-factor) : 0 ; return [r, g, b, 255 ]; }
下图是饱和度传-0.5,原图,饱和度传0.5的对比:
对比度 1 2 3 4 5 6 7 8 9 function contrast (r, g, b, a, factor ) { const contrast = Math .floor (factor * 255 ); const contrastF = 259 * (contrast + 255 ) / (255 * (259 - contrast)); r = contrastF * (r - 128 ) + 128 ; g = contrastF * (g - 128 ) + 128 ; b = contrastF * (b - 128 ) + 128 ; return [r, g, b, 255 ]; }
下图是对比度传-0.2,原图,对比度传0.2的对比:
色温 1 2 3 4 5 6 7 function temperature (r, g, b, a, factor ) { const temperature = Math .round (factor * 255 ) r = Math .min (Math .max (r + temperature, 0 ), 255 ) b = Math .min (Math .max (b - temperature, 0 ), 255 ) return [r, g, b, 255 ]; }
下图是色温传-0.2,原图,色温传0.2的对比:
总结 上面写了这些示例,现在轮到你发挥想象力了,只需要在滤镜函数中编写你的颜色处理程序:
1 2 3 4 function noise (r, g, b, a, factor ) { return [r, g, b, a]; }
比如,调换r、g、b的顺序,或者把b都变成255……然后实现别的滤镜效果,比如色调、模糊等等,而且这些基础滤镜效果还可以叠加,从而创建出更多的效果哦。