这篇文章发布于 2021年09月30日,星期四,23:10,归类于 Canvas相关。 阅读 16203 次, 今日 6 次 5 条评论
by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=10131 鑫空间-鑫生活
本文欢迎分享与聚合,全文转载就不必了,尊重版权,圈子就这么大,若急用可以联系授权。
一、需求描述
需求是这样的,需要实现下图所示的模糊效果。
两个难点,一是局部模糊,二是模糊的边缘是羽化的,而不是突然截断的。
这个需求该如何实现呢?
二、我的实现思路
我的实现思路分为3步:
- 绘制两层 Canvas,下层 Canvas 是完整的图像,而上层 Canvas 近绘制需要模糊的区域;
- 模糊上层 Canvas,示意图如下:
- 两层 Canvas 合二为一。
其中,实现思路1和3都可以使用 Canvas 的 drawImage()
方法实现,这个比较入门,有挑战的是第2步,如何模糊一个 Canvas 图像?
如果是在 CSS 中,可以使用 CSS filter
滤镜属性一步到位,要是在 Canvas 中,也可以使用 filter
滤镜属性就好了?
恭喜你!愿望成真了,Canvas 还真就新支持了一个 filter 属性,可以实现在 Canvas 画布中的各种滤镜效果,不仅是模糊,反相、饱和度、色调旋转、灰度等 10 个滤镜函数全部都支持。
//zxx: 如果你看到这段文字,说明你现在访问是不是原文站点,更好的阅读体验在这里:https://www.zhangxinxu.com/wordpress/?p=10131(作者张鑫旭)
三、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 的问题
可能是为了保证高性能,StackBlur 的模糊算法看起来是非对称的,如果是边缘犀利的高斯模糊,StackBlur 绝对没有任何问题,表现非常良好,但是,如果是这里的四周透明的高斯模糊,就会出现白边或者黑线,以及最终形状不规则的问题,如下图所示:
我还专门做了个简易demo展现此问题:StackBlur 白边黑线问题 demo
也给相关 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 毫秒左右:
这个性能完全可以在项目中使用。
所以,这里的 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。
封装的方法名就是 localBlur,具体使用如下:
- 引入 localBlur.js
<script src="./localBlur.js"></script>
- 调用 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月份的末尾赶出来了这篇文章,如果您觉得文章内容还不错,欢迎转发,欢迎分享。
在这里,祝所有的前端同行国庆节快乐,一切顺顺利利!
本文为原创文章,欢迎分享,勿全文转载,如果实在喜欢,可收藏,永不过期,且会及时更新知识点及修正错误,阅读体验也更好。
本文地址:https://www.zhangxinxu.com/wordpress/?p=10131
(本篇完)
- 使用CSS将图片转换成模糊(毛玻璃)效果 (1.000)
- 图片动态局部毛玻璃模糊效果的实现 (0.949)
- CSS backdrop-filter简介与苹果iOS毛玻璃效果 (0.949)
- 借助SVG滤镜实现CSS无法实现的阴影和模糊效果 (0.553)
- canvas实现iPhoneX炫彩壁纸屏保外加pixi.js流体动效 (0.447)
- 深入理解SVG feDisplacementMap滤镜及实际应用 (0.395)
- 今天学习SVG滤镜feGaussianBlur和feDropShadow (0.395)
- reflection.js-实现图片投影倒影效果js插件 (0.051)
- jQuery-两款不同原理的圆角插件讲解 (0.051)
- 图片旋转效果的一些研究、jQuery插件及实例 (0.051)
- SVG <foreignObject>简介与截图等应用 (RANDOM - 0.051)
这些方法不错,但是无法处理半透明图片的模糊问题,还有在透明区域中的羽化问题,有没有类似ps模糊工具这样的算法,可以让边缘不断地羽化
我这可能是个假 IE,大佬的 demo 不但没效果,调模糊大小的条都没了 – –
IE9、10都没效果
IE… 呜呼呼
未曾试过裁剪,定位叠到一块,再套模糊滤镜?哦对,IE…