兼容IE浏览器的图片局部高斯模糊实现

这篇文章发布于 2021年09月30日,星期四,23:10,归类于 Canvas相关。 阅读 13557 次, 今日 2 次 5 条评论

 

局部模糊占位图

一、需求描述

需求是这样的,需要实现下图所示的模糊效果。

局部模糊效果示意

两个难点,一是局部模糊,二是模糊的边缘是羽化的,而不是突然截断的。

这个需求该如何实现呢?

二、我的实现思路

我的实现思路分为3步:

  1. 绘制两层 Canvas,下层 Canvas 是完整的图像,而上层 Canvas 近绘制需要模糊的区域;
  2. 模糊上层 Canvas,示意图如下:
    模糊分为两层
  3. 两层 Canvas 合二为一。

其中,实现思路1和3都可以使用 Canvas 的 drawImage() 方法实现,这个比较入门,有挑战的是第2步,如何模糊一个 Canvas 图像?

如果是在 CSS 中,可以使用 CSS filter 滤镜属性一步到位,要是在 Canvas 中,也可以使用 filter 滤镜属性就好了?

恭喜你!愿望成真了,Canvas 还真就新支持了一个 filter 属性,可以实现在 Canvas 画布中的各种滤镜效果,不仅是模糊,反相、饱和度、色调旋转、灰度等 10 个滤镜函数全部都支持。

三、canvas filter滤镜函数

Canvas filter 滤镜支持的滤镜函数有下表这些:

滤镜函数 释义
url(#someId) 使用ID是someId的SVG滤镜
blur(5px) 模糊
brightness(2.4) 亮度
contrast(200%) 对比度
drop-shadow(4px 4px 8px blue) 投影
grayscale(50%) 灰度
hue-rotate(90deg) 色调变化
invert(75%) 反相
opacity(25%) 透明度
saturate(230%) 饱和度
sepia(60%) 褐色

各个滤镜效果可以见此文档页面

在本例中,我们需要的滤镜函数就是 blur() 模糊函数,使用语法如下:

context.filter = 'blur(5px)';
// 此时绘制的图像就是模糊的
context.drawImage(...)

是不是相当简单?你以为需求就这么轻松实现了吗?

非也,filter 属性虽然好用,但是兼容性情况却不容乐观,尤其 Safari 浏览器并不支持,这就好比丝线打结——难解了。

此时,只能出动原始的高斯模糊算法,对图像进行处理了。

当然,我们无需自己编写,可以去 GitHub 上找找开源的处理。

我在13年的时候有篇文章“使用CSS将图片转换成模糊(毛玻璃)效果”中提到了一个名为 StackBlur 的项目,https://github.com/flozz/StackBlur

打开一看,项目还在,Star数目也不少,看起来还挺靠谱,然而一番实践下来,发现不适合这里的需求。

StackBlur Star数目

四、StackBlur 的问题

可能是为了保证高性能,StackBlur 的模糊算法看起来是非对称的,如果是边缘犀利的高斯模糊,StackBlur 绝对没有任何问题,表现非常良好,但是,如果是这里的四周透明的高斯模糊,就会出现白边或者黑线,以及最终形状不规则的问题,如下图所示:

StackBlur 的问题示意

我还专门做了个简易demo展现此问题:StackBlur 白边黑线问题 demo

也给相关 Issues 补充了关于此问题的复现描述,不过作者一直没有回复,估计这个问题不太好解决。

Issues反馈截图

可是需求不等人,那就寻找比较正统的高斯模糊算法。

五、找到新的解决工具

找啊找,找啊找,找到了一个其貌不扬的项目:https://github.com/finscn/SimpleBlur

这个项目及其简陋,就一个 JS 文件加几行 ReadMe,Star 数量只有 1 个,而且是 8 年前的项目,也就是发布后动都没动过。

项目简陋示意

不禁让人怀疑,这个项目靠谱么?

靠不靠谱还是要通过实践才知道。

我就整个个简易 demo 跑了下,我去,效果贼好,就是性能差到要砸电脑,有多差呢,一张 600 * 800 像素的图。模糊半径是 40,结果在 IE Edge(就是 IE11 浏览器)下,居然要 20s。

运行时间示意

是不是找到 Star 数量如此低的原因了,这种性能,显然是不能用在生产环境的。

但是,实在是架不住最终的效果就是自己想要的,于是我就打开源码,打算帮忙优化下性能,结果发现作者还提供了另外一种算法实现,基于矩阵的,就在代码中,我整个人懵了,但很快就搞懂了。

作者示意的代码中有个参数为 gaussian,如下代码:

var blur = new Blur({
    radius: 20,
    gaussian : true,
});
blur.init();

使用的值是 true,性能问题就出在这个 true 上,如果作者示意的代码是 false,我估计 Star 数目会多不少,因为,如果 gaussian 参数的值是 false,使用的是另外那一种的性能高很多的模糊算法。

再来试试运行效果:

new Blur({
    radius: 40,
    gaussian: false
})

此时,同样的图像,同样的模糊半径,在 IE 11 浏览器下,只需要 1 秒就完成了,速度提升了 20 多倍:

另外一种算法效率

Chrome 浏览器下仅需要 170 毫秒左右:

Chrome下运行事件

这个性能完全可以在项目中使用。

所以,这里的 Blur.js 不是不能用,而是不能按照 ReadMe 提供的用法用。

六、整合、最终效果与开源

搞定了 IE 浏览器和 Safari 浏览器下的模糊实现,剩下的就是整合了。

支持 filter 滤镜的使用原生的滤镜,不支持的使用 Blur.js 进行模糊处理。

如果判断浏览器的 canvas 是否支持 filter 属性呢?

千万不要基于浏览器判断,因为某些 Android 设备以及某些 APP 使用的内核理论上应该支持,实际上并没有支持。

我使用判断方法如下(context 是任意 canvas 的上下文):

var isSupportFilter = (typeof context.filter === 'string');

假设需要绘制的图形元素是 img,则整合后的执行代码如下:

// 执行模糊
if (typeof context.filter == 'string') {
    // 支持canvas滤镜
    context.filter = 'blur(10px)';
    // 绘制局部图像
    context.drawImage(img, arrParamsDraw);
} else {
    // 不支持Canvas滤镜
    // 使用第三方方法
    var blurFixed = new Blur({
        radius: numRadius,
        gaussian : false
    });
    blurFixed.init();
    // 先绘制局部图像
    context.drawImage(img, 0, 0);
    // 再执行模糊
    var imgBlured = blurFixed.blurRGBA(canvas, null, true);
}

这样,就能实现 IE9 及其以上浏览器的全部兼容了。

代码封装与 gitee 开源

为了方便大家使用,我把相关的处理全部用一个方法封装起来了,并且开源到了 gitee 上,地址是:https://gitee.com/zhangxinxu/local-blur

如果你觉得项目还不错,欢迎 Star 该项目。

封装好的 JS 就是下面的 localBlur.js

开源JS文件示意

封装的方法名就是 localBlur,具体使用如下:

  1. 引入 localBlur.js
    <script src="./localBlur.js"></script>
  2. 调用 localBlur 方法
    localBlur(source, radius, bounding, output);
    // 或者
    localBlur(src, radius, bounding, callback);

其中:

source
表示原始图像资源,可以是 <img> 图像元素,也可以是 <canvas> 画布元素。
src
表示原始图像的 URL 地址,注意跨域的问题。
radius
数值,必需,表示模糊半径。
bounding
数组,可选,表示模糊的局部区域范围,由 4 个值组成,[x, y, width, height], 分别表示区域的左上角坐标和宽高大小,例如 [20, 20, 240, 120],表示图像上横坐标 20px,纵坐标 20px为左上角,宽度是 240px,高度是 120px 的矩形区域发生模糊。
output
显示最终模糊效果的 <canvas> 画布元素。
callback
显示最终模糊效果的回调函数,支持一个参数,表示包含模糊效果的 <canvas> 画布元素。

最终案例效果

封装的方法有了,那么效果如何呢?

项目中的 index.html 就是演示页面,不过 gitee 的 pages 功能没开发,大家也可以狠狠地点击这里体验效果:兼容Safari/IE的局部高斯模糊demo

如果手机访问的,不方便体验,也可以观看下面的视频感受下实现效果(不动点击播放):

核心实现其实就这么一句:

localBlur(source, 10, [76,40,360,240], output);

其他:目标图像尺寸越小,性能约好,模糊半径越小,性能也约好,这一点在 IE 浏览器上更为明显。

欢迎关注我的gitee

欢迎关注我的 gitee 账户,https://gitee.com/zhangxinxu/ 以后写小玩具全部都会扔在上面,因为速度快,目前已经有10个小玩具了。

关注我

七、结束之 CSS 也可以实现

如果不需要兼容 IE 浏览器,或者不需要最终合成一张图片,最简单的方法还是使用 CSS 实现,具体不表,可参见“使用CSS将图片转换成模糊(毛玻璃)效果”这篇文章。

前两天,编辑跟我说《CSS新世界》重印了,说实话,出乎了我的意料,没想到这么快就重印了,毕竟首印足足有4000册,在这里,特别感谢大家的支持,目前微博还有转发抽奖,如果不嫌麻烦的,可以转发下,说不定可以获得《CSS新世界》的签字版。

重印截图

还有1个小时就是国庆节了,算是马不停蹄在9月份的末尾赶出来了这篇文章,如果您觉得文章内容还不错,欢迎转发,欢迎分享。

在这里,祝所有的前端同行国庆节快乐,一切顺顺利利!

(本篇完)

分享到:


发表评论(目前5 条评论)

  1. hippcampus说道:

    这些方法不错,但是无法处理半透明图片的模糊问题,还有在透明区域中的羽化问题,有没有类似ps模糊工具这样的算法,可以让边缘不断地羽化

  2. 冷曜说道:

    我这可能是个假 IE,大佬的 demo 不但没效果,调模糊大小的条都没了 – –

  3. 码农说道:

    IE9、10都没效果

  4. Morotar说道:

    IE… 呜呼呼

  5. mmm说道:

    未曾试过裁剪,定位叠到一块,再套模糊滤镜?哦对,IE…