【Fanvas技术解密】HTML5 canvas实现脏区重绘
先说明一下,fanvas是笔者在企鹅公司开发的,即将开源的flash转canvas工具。
脏区重绘(dirty rectangle)并不是一门新鲜的技术了,这在最早2D游戏诞生的时候就已经存在。
复杂的术语或概念就不多说,简单说,脏区重绘就是每一帧绘制图形界面的时候,只重新绘制有变化的区域,而不是全屏刷新。很明显,这肯定能带来性能的提升。
举个例子,看下边两个图:

假设这里是动画的连续2帧,那么从第一帧到第二帧,其实变化的只有蝴蝶的区域。那么所谓的脏区就是两个图片的红色框之和,要把上一帧的蝴蝶擦掉,然后把新区域的蝴蝶位置也擦掉,接着才能绘制新的背景和蝴蝶。这相比整屏重绘,重绘的面积小了几十倍,由于canvas 2d使用的是CPU处理,那么相应地,CPU处理的像素个数就少了很多倍,顺理成章,动画的效率就会提高。
看起来非常简单,大概来说,只需要2步:
1、找出这一帧变化的矩形区域;
2、利用canvas的api实现脏区重绘。
但是,问题来了,怎么计算变化区域呢?canvas又是否提供了现成的接口呢?我们拿上述蝴蝶的例子,逐步来看看。
首先,我们看关于脏区的计算。
如果动画非常简单,没有使用“显示列表”,所有图案都是一层绘制的,那么“也许”绘制者,也就是开发者了,可能会知道蝴蝶的位置,然后手工指定重绘的区域。呃。。。等等,好像有点什么问题,不可能每次都手工指定重绘的区域!!!
再看看Fanvas里的情况,Fanvas采用了显示列表,把图案拆分为多个元件,元件和元件之间以“显示列表”的形式组织起来,这参考了Flash的技术。这里,蝴蝶被封装为一个Shape,蝴蝶在画面飞舞,抽象为Shape在父元件中移动、旋转。最初,在Shape中绘制蝴蝶的时候,可能占据的矩形区域是(x:0,y:0,width:100,height:50),这里参考的是Shape内部的坐标系(还没放到舞台上)。然后,蝴蝶被添加到舞台上时,需要位移和旋转,例如做了(x:400,y:100)的位移,和旋转了60度。这时候如何计算新的矩形呢?

这个过程其实就是局部坐标系映射到全局坐标系的问题,涉及到矩阵计算,可以参考我之前写的文章,这里就不多说了。http://km.oa.com/articles/show/238103。另外,提一下,这里其实还有一个难点,初始绘制时(x:0,y:0,width:100,height:50),这个矩形是如何计算得到的呢?如果绘制的是一个图片,那当然好计算;如果是一系列的矢量线条,这个就略麻烦了,不过这个不在这里讨论了,因为Fanvas是Flash导出Canvas动画,在导出的时候Flash自带了这个矩阵信息。
上述的计算都在一个前提情况下:我们已知蝴蝶是唯一一个变化的元件,但在实际动画过程中,如何自动识别变化的内容呢?
要从动画的原理说起,动画过程无非分为4种操作:
1. 新建一个元件(例如蝴蝶),添加到舞台上;
2. 移动、旋转、放缩原有的元件;
3. 删除已有的元件;
4. 修改元件的遮罩关系,这点有点特殊,如果对flash动画不熟悉的同学可能不大理解,不过不重要,我们知道有这回事就可以了,不影响文章的继续阅读。
那么,在Fanvas中,我们就需要对上述4种情况分别处理。
1. 新建:只有1个脏矩形,就是这个元件本身;
2. 移动/旋转/放缩:元件上一帧的矩形区域是脏区,新一帧的矩形区域也是脏区;
3. 删除:跟新建情况一样;
4. 遮罩变化:跟2一样。
理清楚这些细节之后,如何实现就比较好办了,无非就是每一帧绘制前把脏区列表情况,然后计算出所有脏区矩形,再开始绘制。
接着,我们再来看第二步,canvas如何具体操作,是否有脏区重绘接口?
其实,canvas并没有真正的脏区重绘接口,不过有一个clip,这个一般用于实现遮罩,不过也可以取巧的用来实现脏区重绘。经笔者测试,简单使用clip虽然性能优化不是太明显,但还是有20%的提升的。再复杂一些,当然大家可以自行根据脏区列表,重写每个元件的绘制方法,自行实现脏区重绘,不过笔者估计啊,js写这么多逻辑,最终还是吃力不讨好。
我们来看看代码:
for (var i = ; i < dirtyRectList.length; i++) {
var rect = dirtyRectList[i];
ctx.clearRect(rect.x, rect.y, rect.width, rect.height);
}
ctx.beginPath();
for (var i = ; i < dirtyRectList.length; i++) {
var rect = dirtyRectList[i];
ctx.rect(rect.x, rect.y, rect.width, rect.height);
}
ctx.clip();
相信变量名已经很明显的暴露了自己的用途,大家应该明白,实现脏区重绘非常简单,只需要在全部绘制前加那么一段clip,搞掂。
不过啊,这里可有一个很大的坑,估计有同学也知道。

正如上图所示,会出现一些1px白线或者没清干净的bug,尤其是舞台本身有拉伸的情况下,这种bug更明显。经过笔者多次摸索,大概搞清楚了,主要就是脏区要算仔细(如果舞台有拉伸,很容易算出来有1、2px差别),画面要等比例拉伸,另外就是清除和重绘时,大方点,给1px的放宽。
最后变成:
for (var i = 0; i < dirtyRectList.length; i++) {
var rect = dirtyRectList[i];
ctx.clearRect(rect.x-1, rect.y-1, rect.width+2, rect.height+2);
}
ctx.beginPath();
for (var i = 0; i < dirtyRectList.length; i++) {
var rect = dirtyRectList[i];
ctx.rect(rect.x-1, rect.y-1, rect.width+2, rect.height+2); }
ctx.clip();
至此,Fanvas脏区重绘的秘密就彻底曝光了。。。
最后来看看实际的效果(第一张是没有使用脏区重绘,第二张使用脏区重绘):



当然,这并不是每个动画都有效果,因为一些动画本来就大范围变化,所以Fanvas针对这些情况也做了兼容处理,如果发现脏区太多或者面积太大,就继续使用原来的全屏重绘。
【Fanvas技术解密】HTML5 canvas实现脏区重绘的更多相关文章
- 【HTML5 Canvas】计算元件/显示对象经过Matrix变换后在上级/舞台上的bounds(边界矩形rect)
如上图所示,这样的一个简单矩形,边界矩形是(x:-28, y:-35, width:152, height:128),这是在这个元件/显示对象自己的坐标空间的范围. 那么把这个放到父元件(舞台)中,再 ...
- 通过分析 WPF 的渲染脏区优化渲染性能
原文:通过分析 WPF 的渲染脏区优化渲染性能 本文介绍通过发现渲染脏区来提高渲染性能. 本文内容 脏区 Dirty Region WPF 性能套件 脏区监视 优化脏区重绘 脏区 Dirty Regi ...
- html5 Canvas和SVG的区别是什么(总结)
html5 Canvas和SVG的区别是什么(总结) 一.总结 一句话总结:都是2D做图,svg是矢量图,canvas是位图.Canvas 是逐像素进行渲染的,适合游戏. 1.svg的全称是什么? S ...
- html5 canvas的教程
原文地址:http://www.cnblogs.com/tim-li/archive/2012/08/06/2580252.html 原作很强悍 导航 前言 基本知识 绘制矩形 清除矩形区域 圆弧 路 ...
- HTML5 canvas画图
HTML5 canvas画图 HTML5 <canvas> 标签用于绘制图像(通过脚本,通常是 JavaScript).不过,<canvas> 元素本身并没有绘制能力(它仅仅是 ...
- html5 canvas 详细使用教程 转
分类: html5(9) 原文地址:http://www.cnblogs.com/tim-li/archive/2012/08/06/2580252.html 原作很强悍 导航 前言 基本知识 绘 ...
- html5和html的区别是什么(精问)
html5和html的区别是什么(精问) 一.总结 一句话总结:html5:简洁(文档生命,链接引入) 语义化(语义化标签) API(canvas,地理位置等) 一些标签(input新类型) 二. ...
- HTML5 程序设计 - 使用HTML5 Canvas API
请你跟着本篇示例代码实现每个示例,30分钟后,你会高喊:“HTML5 Canvas?!在哥面前,那都不是事儿!” 呵呵.不要被滚动条吓到,很多都是代码和图片.我没有分开写,不过上面给大家提供了目录,方 ...
- 赠书:HTML5 Canvas 2d 编程必读的两本经典
赠书:HTML5 Canvas 2d 编程必读的两本经典 这两年多一直在和HTML5 Canvas 打交道,也带领团队开发了世界首款基于HTML5 Canvas 的演示文档工具---AxeSlide( ...
随机推荐
- 64位Windows操作系统中的注冊表
x64系统上有x64.x86两种注冊表,记录下. 64 位Windows系统中的注冊表分为 32 位注冊表项和 64 位注冊表项.很多 32 位注冊表项与其对应的 64 位注冊表项同名. 在64位版本 ...
- [转]wget 下载整个网站,或者特定目录
FROM : http://www.cnblogs.com/lidp/archive/2010/03/02/1696447.html 需要下载某个目录下面的所有文件.命令如下 wget -c -r - ...
- [转]Linux常用命令大全
From : http://www.php100.com/html/webkaifa/Linux/2009/1106/3485.html 系统信息 arch 显示机器的处理器架构(1) uname - ...
- protobuf示例
Google protobuf 是一个高性能的序列化结构化数据存储格式的接口描述语言,具有多语言支持,协议数据小,方便传输,高性能等特点.通过将结构化数据序列化(串行化)成二进制数组,并将二进制数组反 ...
- HashMap 与 ConcurrentHashMap
1. HashMap 1) 并发问题 HashMap的并发问题源于多线程访问HashMap时, 如果存在修改Map的结构的操作(增删, 不包括修改), 则有可能会发生并发问题, 表现就是get()操作 ...
- maven清理.lastUpdated文件maven清理下载失败的jar,方便重新下载
因网络或其他的原因,maven下载jar等文件失败后,会在目录中存在 *.jar.lastUpdated ,如:xmlpull-1.1.3.1.jar.lastUpdated,此时,代码编译时会一直 ...
- 【UOJ Round #5】
构造+贪心/数论 为什么只有两个标题呢……因为第二题我不会…… 怎样提高智商 构造题……然而一开始半天我都yy不出来…… 后来我想:这题应该不会特别麻烦,而且既然样例只给了1,可能再给大一点就让人发现 ...
- MySQL C API的一个让我头疼的问题,获得一行记录中包括NULL
遇到过几次错误,通过gdb来查看错误对战,发现错误居然是atoi调用出错,除非atoi(NULL) 才会报这种错误.说明 row[0]==NULL. (gdb) bt #0 0x00007f82c66 ...
- 在PC上像普通winform程序调试WINCE程序
在PC上像普通winform程序调试WINCE程序 步骤: 1. 在VS2008中到 工具→选项→设备工具→设备,选择对应的平台,另存为新的名称,如CEDesktopRun,关闭VS2008.(如果不 ...
- IntelliJ IDEA windows与mac下常用快捷键
摘自: http://www.th7.cn/Program/java/201604/817219.shtml 1.找文件找代码找引用 shif双击在项目的所有目录查找 ctrl+f(mac下:comm ...