【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( ...
随机推荐
- USB OTG简单介绍、与普通USB线的差别
USB有三类接口A类接口 -----------最常见的扁平接口,四芯 VCC GND D+ D- B类接口 ...
- 《图解CSS3:核心技术与案例实战》
<图解CSS3:核心技术与案例实战> 基本信息 作者: 大漠 丛书名: Web开发技术丛书 出版社:机械工业出版社 ISBN:9787111469209 上架时间:2014-7-2 出版日 ...
- [转]Linux常用命令大全
From : http://www.php100.com/html/webkaifa/Linux/2009/1106/3485.html 系统信息 arch 显示机器的处理器架构(1) uname - ...
- 代理服务器polipo;socks5代理转http代理
安装: brew install polipo 使用: To have launchd start polipo now and restart at login: brew services sta ...
- go语言之进阶篇runtime包中 Gosched Goexit GOMAXPROCS的使用
一.runtime包 1.Gosched的使用 runtime.Gosched() 用于让出CPU时间片,让出当前goroutine的执行权限,调度器安排其他等待的任务运行,并在下次某个时候从该位置恢 ...
- [leetcode]Path Sum @ Python
原题地址:https://oj.leetcode.com/problems/path-sum/ 题意: Given a binary tree and a sum, determine if the ...
- 2018.08.28 ali 梯度下降法实现最小二乘
- 要理解梯度下降和牛顿迭代法的区别 #include<stdio.h> // 1. 线性多维函数原型是 y = f(x1,x2,x3) = a * x1 + b * x2 + c * x ...
- Eclipse系列:如何设置Eclipse关联JDK源码和文档
一.设置Eclipse关联JDK源码 1.打开Eclipse-->Windows-->Preferences 2. 在弹出的Preferences对话框中,Java--> ...
- Android推送分析
cpu多核利用能够实现Android推送的吞吐量. 讲明白这点,我们需要了解Android推送的基本原理了.如果实现C(客户端)与server(客户端)实时通讯了.这里有两种思路了: 1.一种是定时 ...
- Double-Array Trie 原理解析
http://ansjsun.iteye.com/blog/702255 Trie树是搜索树的一种,它在本质上是一个确定的有限状态自动机,每个结点代表一个状态,根据输入变量的不同,进行状态转 ...