canvas换图时候会闪烁_基于Canvas实现的高斯模糊(上)「JS篇」

b69d7342e8682497f135aff334137555.png

作者:iNahoo

转发链接:https://mp.weixin.qq.com/s/5TxPjznpEBku_ybSMBdnfw

目录

基于Canvas实现的高斯模糊(上)「JS篇」本篇

基于Canvas实现的高斯模糊(下)「JS篇」

小编建议小伙们从第一篇开始,按照顺序来看,更清晰明了。

前言

自从扁平化流行起来之后,高斯模糊效果渐渐变成了视觉很喜欢用的一种表现形式,我们的视觉小姐姐也特别喜欢。为了满足她,踩了无穷无尽的坑之后,最后只能掏出Canvas来了。

没有什么视觉需求是Canvas解决不了的,如果有,再盖一层Canvas

解决痛点

  1. CSS模糊 和 大面积 transform 混用时,会导致的性能问题 ( 卡 )
  2. CSS(svg)模糊 在图片边界的表现不够优秀,比如白边
  3. iOS下高像素的高斯模糊会出现 奇怪的现象 ( 某些古董手机,突然颜色大变 )
  4. 一套解决方案,不再需要 svg+多种css兼容 判环境应用

理论原理

理论

模糊的效果相信大家都不陌生,实际上就是一种加权平均算法。而 高斯模糊( Gaussian Blur ) 就是以高斯分布作为权重的平均算法。高斯分布长下面这个样子。

5fda53ede356c22a96e2120c0a253bcd.png

图片有x,y两个维度,所以在平均的时候应该使用二维高斯分布

7f5ee6de03e9d145048679d30849ab2f.png

基本算法

  1. 输入 图片Img , 模糊半径radius
  2. 按 radius 计算出 高斯矩阵 gaussMatrix 避免重复计算
  3. 遍历每一个像素
  4. 提取当前像素[x,y]{r,g,b,a}
  5. 求范围 [ x ± radius , y ± radius ] 内地 {r,g,b,a} 各自在 gaussMatrix 内的加权均值
  6. 输出

边界处理

观察系统的高斯模糊效果,边界总是半透明的。为了强调这一效果,我们在原图的背后增加一块等大小的红色背景。

40d70fd9e997dafdf9fdd01dfac0cad8.png

可以很清楚的看到边界的虚边,我推测浏览器在实现时,在边界处增加了 alpha=0 的点来补齐计算。对此,我的解决方案是:仅计算存在的点的权重

算法实现

const gaussBlur = function (imgData, radius) {    radius *= 3; //不知为什么,我的模糊半径是 css中 filter:bulr 值的三倍时效果才一致。莫非是当时的测试机是@3x?    //Copy图片内容    const pixes = new Uint8ClampedArray(imgData.data);    const width = imgData.width;    const height = imgData.height;    let gaussSum = 0,        x, y,        r, g, b, a, i;    //模糊半径取整    radius = Math.floor(radius);    //sigma越小中心点权重越高, sigma越大越接近平均模糊    const sigma = radius / 3;    //两个分布无相关性, 为了各方向上权重分布一致    const Ror = 0;    const L = radius * 2 + 1;  //矩阵宽度    const Ror2 = Ror * Ror;    const s2 = sigma * sigma;    const c1 = 1 / (  2 * Math.PI * s2 * Math.sqrt(1 - Ror * Ror));    const c2 = -1 / (2 * (1 - Ror2));    //定义高斯矩阵 , 存储在一维数组中    const gaussMatrix = [];    //根据 xy 计算 index    gaussMatrix.getIndex = (x, y)=> {        return (x + radius) + (y + radius) * L;    }    //根据 xy 获取权重    gaussMatrix.getWeight = (x, y)=> {        return gaussMatrix[gaussMatrix.getIndex(x, y)];    }    //根据 index 获取 x 偏移    gaussMatrix.getX = (index)=> {        return index % L - radius;    }    //根据 index 获取 y 偏移    gaussMatrix.getY = (index)=> {        return Math.floor(index / L) - radius;    }    //覆写forEach , 方便遍历    gaussMatrix.forEach = (f)=> {        gaussMatrix.map((w, i)=> {            f(w, gaussMatrix.getX(i), gaussMatrix.getY(i))        })    }    //生成高斯矩阵    for (y = -radius; y <= radius; y++) {        for (x = -radius; x <= radius; x++) {            let i = gaussMatrix.getIndex(x, y);            g = c1 * Math.exp(c2 * (x * x + 2 * Ror * x * y + y * y) / s2);            gaussMatrix[i] = g;        }    }    //快捷获取像素点数据    const getPixel = (x, y)=> {        if (x = width || y = height) {            return null;        }        let p = (x + y * width) * 4;        return pixes.subarray(p, p + 4);    }    //遍历图像上的每个点    i = 0;    for (y = 0; y  {                let p = getPixel(x + dx, y + dy);                if (!p)return;                //求加权和                r += p[0] * w;                g += p[1] * w;                b += p[2] * w;                a += p[3] * w;                gaussSum += w;            });            //写回imgData            imgData.data.set([r, g, b, a].map(v=>v / gaussSum), i);            //遍历下一个点            i += 4;        }    }    return imgData;};

写完了实现的我,迫不及待的试了试

7bb17c46c77b863772bb0a8a31e67070.png

[ 效果拔群! 无与伦比! 掌声呢?!!! ]

一般来说写到这里,就算功成名就了,不过我瞥了一眼控制台…

5a60a71682ae3082c10d7fb0f4bccceb.png

足足算了21秒,这可是我心爱的 MacPro,我要报警了!

优化算法

目前的算法,复杂度大约是 wh * (2r)^2

之后我去搜了搜 大神代码,发现他们是先进行一轮X轴方向模糊,再进行一轮Y轴方向模糊,复杂度只有 2wh * 2r , 一下少了好多运算量。

我们也来试试。

88358c758a46c8229275e7c733ca2534.png
891a68eabfb98b4a29ad2e37ef3228f1.png

[ 效果立竿见影 ]

以我的数学水平,并不能证明两者是等效的,但是从视觉上来看是一致的,为什么可以这样优化,期望大神赐教。

使用优化

即使已经这样进行了优化,依然会有 500ms 的计算时长,对于移动端来说通常是不可接受的。

从算法上可以看出来,运算量由三个方面来决定:图片宽w、高h,模糊半径r。这样就能对我们的几个常见使用场景进行优化

1. 大尺寸图片

例如一张900x600的图片,需要输出一张300x200@2x

可以将图片先缩放到300x200再计算模糊

2. 大半径模糊

例如一张900x600的图片,需要模糊半径150,需要输出一张300x200@2x的图

这样的图可以说是细节全失,通常视觉只Care成图的大概色彩范围,我们可以用一些粗暴的方法。

  1. 等比例计算,把图片变成 6x4 r=1 ,
  2. 计算模糊,输出 6x4 的图片
  3. 使用css拉伸到 300x200

实现

说白了优化手段就是一招缩小射线,我们抽象一个参数: 缩小倍率 shrink对于一张图片的模糊过程可能会有超过一帧的计算量,所以我们把它变成异步的,方便未来迁移到 WebAssembly 或者 Worker 中。

/** * @public * 暴露的异步模糊方法 * --------------------- * @param URL       图片地址,需要跨域支持 * @param r         模糊半径 {Int} * @param shrink    缩小比率 {Number} * @return {Promise} */export const blur = (URL, r, shrink = 1)=> {    return new Promise((resolve, reject)=> {        const IMG = new Image();        IMG.crossOrigin = '*'; //需要图片跨域支持        IMG.onload = function () {            const Canvas = document.createElement('CANVAS'); //大量使用可考虑只创建一次            let w = IMG.width, h = IMG.height;            //缩小比例不为1时 , 重新计算宽高比            if (shrink !== 1) {                w = Math.ceil(w / shrink);                h = Math.ceil(h / shrink);                r = Math.ceil(r / shrink);            }            //因为懒, 就全Try了, 实际上只 Try跨域错误 即可            try {                //设置Canvas宽高,获取上下文                Canvas.width = w;                Canvas.height = h;                let ctx = Canvas.getContext('2d');                ctx.drawImage(IMG, 0, 0, w, h);                //提取图片信息                let d = ctx.getImageData(0, 0, w, h);                //进行高斯模糊                let gd = gaussBlur(d, r, 0);                //绘制模糊图像                ctx.putImageData(gd, 0, 0);                resolve(Canvas.toDataURL());            } catch (e) {                reject(e);            }        };        IMG.src = URL;    })};

以一张 640x426 的图片,输出{ 300x200,r=10 }为例:对比

  1. 原尺寸模糊
  2. 缩小到1/10进行模糊
487ef8ddfec08a187856cd9baf016af8.png
a283ee81443eceb8eaec2a9e68341259.png

首先要明确的是,在缩小情况下两种算法并不等价。小图放大的模糊效果取决于浏览器本身的算法实现。最终视觉上效果差别不显著,完全可以使用。

本篇未完结,请见下一篇

推荐JavaScript学习相关文章

《由浅入深,66条JavaScript面试知识点(一)》

《由浅入深,66条JavaScript面试知识点(二)》

《由浅入深,66条JavaScript面试知识点(三)》

《由浅入深,66条JavaScript面试知识点(四)》

《由浅入深,66条JavaScript面试知识点(五)》

《由浅入深,66条JavaScript面试知识点(六)》

《由浅入深,66条JavaScript面试知识点(七)》

《为什么 setTimeout 有最小时延 4ms ?》

《如何处理 Node.js 中出现的未捕获异常?》

《Angular v10.0.0 正式发布,不再支持 IE9/10》

《基于 Docker 的 SSR 持续开发集成环境实践》

《细聊图解webpack 指南手册》

《一文带你彻底搞懂 NPM 知识点「进阶篇」》

《细聊webpack性能优化面面观》

《JS实现各种日期操作方法汇总》

《「实践」细聊前端性能优化总结》

《「实践」浏览器中的画中画(Picture-in-Picture)模式及其 API》

《「多图」一文带你彻底搞懂 Web Workers (上)》

《「多图」一文带你彻底搞懂 Web Workers (中)》

《深入细聊前端下载总结「干货」》

《细品西瓜播放器功能分析(上)「实践」》

《细品西瓜播放器功能分析(下)「实践」》

《细聊50道JavaScript基础面试题「附答案」》

《webpack4主流程源码解说以及动手实现一个简单的webpack(上)》

《webpack4主流程源码解说以及动手实现一个简单的webpack(下)》

《细聊前端架构师的视野》

《细聊应用场景再谈防抖和节流「进阶篇」》

《前端埋点统一接入方案实践》

《细聊微内核架构在前端的应用「干货」》

《一种高性能的Tree组件实现方案「干货」》

《进击的JAMStack》

《前后端全部用 JS 开发是什么体验(Hybrid + Egg.js经验分享)上》

《前后端全部用 JS 开发是什么体验(Hybrid + Egg.js经验分享)中》

《前后端全部用 JS 开发是什么体验(Hybrid + Egg.js经验分享)下》

《一文带你搞懂 babel-plugin-import 插件(上)「源码解析」》

《一文带你搞懂 babel-plugin-import 插件(下)「源码解析」》

《JavaScript常用API合集汇总「值得收藏」》

《推荐10个常用的图片处理小帮手(上)「值得收藏」》

《推荐10个常用的图片处理小帮手(下)「值得收藏」》

《JavaScript 中ES6代理的实际用例》

《12 个实用的前端开发技巧总结》

《一文带你搞懂搭建企业级的 npm 私有仓库》

《教你如何使用内联框架元素 IFrames 的沙箱属性提高安全性?》

《细说前端开发UI公共组件的新认识「实践」》

《细说DOM API中append和appendChild的三个不同点》

《细品淘系大佬讲前端新人如何上王者「干货」》

《一文带你彻底解决背景跟随弹窗滚动问题「干货」》

《推荐常用的5款代码比较工具「值得收藏」》

《Node.js实现将文字与图片合成技巧》

《爱奇艺云剪辑Web端的技术实现》

《我再也不敢说我会写前端 Button组件「实践」》

《NodeX Component - 滴滴集团 Node.js 生态组件体系「实践」》

《Node Buffers 完整指南》

《推荐18个webpack精美插件「干货」》

《前端开发需要了解常用7种JavaScript设计模式》

《浅谈浏览器架构、单线程js、事件循环、消息队列、宏任务和微任务》

《了不起的 Webpack HMR 学习指南(上)「含源码讲解」》

《了不起的 Webpack HMR 学习指南(下)「含源码讲解」》

《10个打开了我新世界大门的 WebAPI(上)「实践」》

《10个打开了我新世界大门的 WebAPI(中)「实践」》

《10个打开了我新世界大门的 WebAPI(下)「实践」》

《「图文」ESLint 在中大型团队的应用实践》

《Deno是代码的浏览器,你认同吗?》

《前端存储除了 localStorage 还有啥?》

《Javascript 多线程编程​的前世今生》

《微前端方案 qiankun(实践及总结)》

《「图文」V8 垃圾回收原来这么简单?》

《Webpack 5模块联邦引发微前端的革命?》

《基于 Web 端的人脸识别身份验证「实践」》

《「前端进阶」高性能渲染十万条数据(时间分片)》

《「前端进阶」高性能渲染十万条数据(虚拟列表)》

《图解 Promise 实现原理(一):基础实现》

《图解 Promise 实现原理(二):Promise 链式调用》

《图解 Promise 实现原理(三):Promise 原型方法实现》

《图解 Promise 实现原理(四):Promise 静态方法实现》

《实践教你从零构建前端 Lint 工作流「干货」》

《高性能多级多选级联组件开发「JS篇」》

《深入浅出讲解Node.js CLI 工具最佳实战》

《延迟加载图像以提高Web网站性能的五种方法「实践」》

《比较 JavaScript 对象的四种方式「实践」》

《使用Service Worker让你的 Web 应用如虎添翼(上)「干货」》

《使用Service Worker让你的 Web 应用如虎添翼(中)「干货」》

《使用Service Worker让你的 Web 应用如虎添翼(下)「干货」》

《前端如何一次性处理10万条数据「进阶篇」》

《推荐三款正则可视化工具「JS篇」》

《如何让用户选择是否离开当前页面?「JS篇」》

《JavaScript开发人员更喜欢Deno的五大原因》

《仅用18行JavaScript实现一个倒数计时器》

《图文细说JavaScript 的运行机制》

《一个轻量级 JavaScript 全文搜索库,轻松实现站内离线搜索》

《推荐Web程序员常用的15个源代码编辑器》

《10个实用的JS技巧「值得收藏」》

《细品269个JavaScript小函数,让你少加班熬夜(一)「值得收藏」》

《细品269个JavaScript小函数,让你少加班熬夜(二)「值得收藏」》

《细品269个JavaScript小函数,让你少加班熬夜(三)「值得收藏」》

《细品269个JavaScript小函数,让你少加班熬夜(四)「值得收藏」》

《细品269个JavaScript小函数,让你少加班熬夜(五)「值得收藏」》

《细品269个JavaScript小函数,让你少加班熬夜(六)「值得收藏」》

《深入JavaScript教你内存泄漏如何防范》

《手把手教你7个有趣的JavaScript 项目-上「附源码」》

《手把手教你7个有趣的JavaScript 项目-下「附源码」》

《JavaScript 使用 mediaDevices API 访问摄像头自拍》

《手把手教你前端代码如何做错误上报「JS篇」》

《一文让你彻底搞懂移动前端和Web 前端区别在哪里》

《63个JavaScript 正则大礼包「值得收藏」》

《提高你的 JavaScript 技能10 个问答题》

《JavaScript图表库的5个首选》

《一文彻底搞懂JavaScript 中Object.freeze与Object.seal的用法》

《可视化的 JS:动态图演示 - 事件循环 Event Loop的过程》

《教你如何用动态规划和贪心算法实现前端瀑布流布局「实践」》

《可视化的 js:动态图演示 Promises & Async/Await 的过程》

《原生JS封装拖动验证滑块你会吗?「实践」》

《如何实现高性能的在线 PDF 预览》

《细说使用字体库加密数据-仿58同城》

《Node.js要完了吗?》

《Pug 3.0.0正式发布,不再支持 Node.js 6/8》

《纯JS手写轮播图(代码逻辑清晰,通俗易懂)》

《JavaScript 20 年 中文版之创立标准》

《值得收藏的前端常用60余种工具方法「JS篇」》

《箭头函数和常规函数之间的 5 个区别》

《通过发布/订阅的设计模式搞懂 Node.js 核心模块 Events》

《「前端篇」不再为正则烦恼》

《「速围」Node.js V14.3.0 发布支持顶级 Await 和 REPL 增强功能》

《深入细品浏览器原理「流程图」》

《JavaScript 已进入第三个时代,未来将何去何从?》

《前端上传前预览文件 image、text、json、video、audio「实践」》

《深入细品 EventLoop 和浏览器渲染、帧动画、空闲回调的关系》

《推荐13个有用的JavaScript数组技巧「值得收藏」》

《前端必备基础知识:window.location 详解》

《不要再依赖CommonJS了》

《犀牛书作者:最该忘记的JavaScript特性》

《36个工作中常用的JavaScript函数片段「值得收藏」》

《Node + H5 实现大文件分片上传、断点续传》

《一文了解文件上传全过程(1.8w字深度解析)「前端进阶必备」》

《【实践总结】关于小程序挣脱枷锁实现批量上传》

《手把手教你前端的各种文件上传攻略和大文件断点续传》

《字节跳动面试官:请你实现一个大文件上传和断点续传》

《谈谈前端关于文件上传下载那些事【实践】》

《手把手教你如何编写一个前端图片压缩、方向纠正、预览、上传插件》

《最全的 JavaScript 模块化方案和工具》

《「前端进阶」JS中的内存管理》

《JavaScript正则深入以及10个非常有意思的正则实战》

《前端面试者经常忽视的一道JavaScript 面试题》

《一行JS代码实现一个简单的模板字符串替换「实践」》

《JS代码是如何被压缩的「前端高级进阶」》

《前端开发规范:命名规范、html规范、css规范、js规范》

《【规范篇】前端团队代码规范最佳实践》

《100个原生JavaScript代码片段知识点详细汇总【实践】》

《关于前端174道 JavaScript知识点汇总(一)》

《关于前端174道 JavaScript知识点汇总(二)》

《关于前端174道 JavaScript知识点汇总(三)》

《几个非常有意思的javascript知识点总结【实践】》

《都2020年了,你还不会JavaScript 装饰器?》

《JavaScript实现图片合成下载》

《70个JavaScript知识点详细总结(上)【实践】》

《70个JavaScript知识点详细总结(下)【实践】》

《开源了一个 JavaScript 版敏感词过滤库》

《送你 43 道 JavaScript 面试题》

《3个很棒的小众JavaScript库,你值得拥有》

《手把手教你深入巩固JavaScript知识体系【思维导图】》

《推荐7个很棒的JavaScript产品步骤引导库》

《Echa哥教你彻底弄懂 JavaScript 执行机制》

《一个合格的中级前端工程师需要掌握的 28 个 JavaScript 技巧》

《深入解析高频项目中运用到的知识点汇总【JS篇】》

《JavaScript 工具函数大全【新】》

《从JavaScript中看设计模式(总结)》

《身份证号码的正则表达式及验证详解(JavaScript,Regex)》

《浏览器中实现JavaScript计时器的4种创新方式》

《Three.js 动效方案》

《手把手教你常用的59个JS类方法》

《127个常用的JS代码片段,每段代码花30秒就能看懂-【上】》

《深入浅出讲解 js 深拷贝 vs 浅拷贝》

《手把手教你JS开发H5游戏【消灭星星】》

《深入浅出讲解JS中this/apply/call/bind巧妙用法【实践】》

《手把手教你全方位解读JS中this真正含义【实践】》

《书到用时方恨少,一大波JS开发工具函数来了》

《干货满满!如何优雅简洁地实现时钟翻牌器(支持JS/Vue/React)》

《手把手教你JS 异步编程六种方案【实践】》

《让你减少加班的15条高效JS技巧知识点汇总【实践】》

《手把手教你JS开发H5游戏【黄金矿工】》

《手把手教你JS实现监控浏览器上下左右滚动》

《JS 经典实例知识点整理汇总【实践】》

《2.6万字JS干货分享,带你领略前端魅力【基础篇】》

《2.6万字JS干货分享,带你领略前端魅力【实践篇】》

《简单几步让你的 JS 写得更漂亮》

《恭喜你获得治疗JS this的详细药方》

《谈谈前端关于文件上传下载那些事【实践】》

《面试中教你绕过关于 JavaScript 作用域的 5 个坑》

《Jquery插件(常用的插件库)》

《【JS】如何防止重复发送ajax请求》

《JavaScript+Canvas实现自定义画板》

《Continuation 在 JS 中的应用「前端篇」》

作者:iNahoo

转发链接:https://mp.weixin.qq.com/s/5TxPjznpEBku_ybSMBdnfw

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值