构建Canvas矢量图形渲染器(五) —— 添加图片,几何元素的外接矩形,初步性能优化,性能测试。
本系列目录,大家有需要的就看看哈。
本节主要解决的有三个问题,求任意几何图形的外接矩形。这个外接矩形会在性能优化时候用到,当然不仅仅只在这一方面使用。最后对初步优化过的渲染器进行压力测试。
还是先上demo,之前的demo在firefox下滚动缩放漏做了。现在更新的可以再ie9,chrome,firefox中使用(当然需要支持canvas)。
1.第一个demo表示了几何图形的外接矩形。大家发现点和图片没有外接矩形,因为他们的范围表示成一个点,均为中心点。
为什么图片也没有范围呢?因为异步的图片加载使我们无法在一开始就得知图片的宽高。
2.第二个demo 为性能测试demo,点击测试按钮会随机添加500个点和500个五角星,多次点击多次添加(大家悠着些啊,别把浏览器点崩了。。)。
可以发现在我们放大2倍左右后,性能还是比较不错的,因为这里做了个小优化,通过上面我们取得的外接矩形和当前视图的范围做相交比较,只有和当前视图范围相交的几何图形才会被绘制。
1.下面就开始对我们上面提到的外接矩形做一个详细的描述
在渲染器中规定所有继承自geometry基类的几何类型必须要有一个计算其范围的方法(getBounds),当然我们的几何图形不同,计算范围的方法也就不同。
1.下面我们先看最简单的圆的范围计算方法。
Circle.prototype.getBounds = function () { if(!this.bounds) { this.bounds = new CanvasSketch.Bounds(this.x - this.radius, this.y - this.radius, this.x + this.radius, this.y + this.radius); return this.bounds; } else { return this.bounds; } }
大家一看代码就明白了,这不是初中、小学数学么。。
2.Path计算范围的方法。
在现在所有的几何图形,除了point(点)、circle(圆)、img(图像)是直接继承自geometry基类的其余的均继承自line。这样只要我们得到line计算范围的方法就可以解决所有的问题了。
Line.prototype.getBounds = function () { if(!this.bounds) { var p0 = this.points[0]; this.bounds = new CanvasSketch.Bounds(p0.x, p0.y, p0.x, p0.y); for(var i = 1, len = this.points.length; i< len; i++) { var point = this.points[i]; var bounds = new CanvasSketch.Bounds(point.x, point.y, point.x, point.y); this.bounds.extend(bounds); } } return this.bounds; }
line计算范围的方法其实也不难,我们遍历所有的点,把每个点都当做成一个范围。通过bounds.extend方法将所有的范围组合到一起便是我们需要的外接矩形。
extend方法如下:
CanvasSketch.Bounds.prototype.extend = function (bounds) { if(this.left > bounds.left) { this.left = bounds.left; } if(this.bottom > bounds.bottom) { this.bottom = bounds.bottom; } if(this.right < bounds.right) { this.right = bounds.right; } if(this.top < bounds.top) { this.top = bounds.top; } }
这个方法中我们就是比较大小,把适合的位置信息赋值给我们要扩展范围的bounds。
通过上面的两个方法就让我们获得了所有几何图形的外接矩形,下面我们讨论如何用外接矩形优化性能。
2.渲染器初步优化。
本次的优化其实非常简单,也是所有图形库必须的优化:绘制能看见的图形,其余的不进行考虑。在图形学中称这个过程为“二维裁剪”,有兴趣的同学可以baidu一下。
有了范围后,我们需要一个判断两个范围相交的函数:
CanvasSketch.Bounds.prototype.intersect = function (bounds) { var inBottom = ( ((bounds.bottom >= this.bottom) && (bounds.bottom <= this.top)) || ((this.bottom >= bounds.bottom) && (this.bottom <= bounds.top)) ); var inTop = ( ((bounds.top >= this.bottom) && (bounds.top <= this.top)) || ((this.top > bounds.bottom) && (this.top < bounds.top)) ); var inLeft = ( ((bounds.left >= this.left) && (bounds.left <= this.right)) || ((this.left >= bounds.left) && (this.left <= bounds.right)) ); var inRight = ( ((bounds.right >= this.left) && (bounds.right <= this.right)) || ((this.right >= bounds.left) && (this.right <= bounds.right)) ); intersects = ((inBottom || inTop) && (inLeft || inRight)); return intersects; }
通过这个函数我们判断出两个范围是否相交,下面正式的优化开始:
Canvas.prototype.redraw = function(){ this.context.clearRect(0, 0, this.layer.size.w, this.layer.size.h); var geometry; if(!this.lock){ for(var id in this.geometrys){ geometry = this.geometrys[id][0]; var bounds = geometry.getBounds(); if(this.layer.bounds.intersect(bounds)) { style = this.geometrys[id][1]; this.draw(geometry, style, geometry.id); } } } }
我们在绘制每一个图形之前,判断其范围是否和当前视图范围有相交,如果相交才绘制,不相交什么也不做了。
这样我们基本的优化就做完了,计划以后有更高级的优化,现在还在斟酌方案。
3.支持图片添加
1.有了这么一个框架我们添加新的图形元素变的非常简单,图像类也是如此。
//CLASS: 显示图像类。 function Img(point, image) { Geometry.apply(this, arguments); this.point = point; if(typeof image == Image) { this.useUrl = false; this.image = image; }else { this.useUrl = true; this.image = image; } } Img.prototype = new Geometry(); Img.prototype.geoType = "Img"; Img.prototype.getBounds = function () { return new CanvasSketch.Bounds(this.point.x, this.point.y, this.point.x, this.point.y); }
我们这里支持直接传入image对象(前提是你必须保证他已经请求完成)。我们还可以传入图像的url,程序内部帮大家请求图片;在请求完成后图片将被设置为Img对象的一个资源,以后再使用的时候就不用再次请求了。
2.渲染添加对应的解析图像的方法。
//针对图片的绘制方法。 Canvas.prototype.drawImage = function(geometry, style, id){ var canvas = this; if(!geometry.useUrl) { var img = geometry.image; imageLoad(); }else { var img = new Image(); img.onload = imageLoad; img.loadErro = imageErro; img.src = geometry.image; } function imageLoad() { canvas.setCanvasStyle("fill", style); var fixedSize = style.fixedSize; var pt = canvas.getLocalXY(geometry.point); var width = style.width || img.width; var height = style.width || img.height; if(fixedSize) { var offsetX = width / 2; var offsetY = height / 2; canvas.context.drawImage(img, pt.x - offsetX, pt.y - offsetY, width, height); }else { var res = canvas.layer.getRes(); var offsetX = width / 2 / res; var offsetY = height / 2 / res; canvas.context.drawImage(img, pt.x - offsetX, pt.y - offsetY, width / res, height / res); } if(geometry.useUrl) { geometry.useUrl = false; geometry.image = img; } canvas.setCanvasStyle("reset"); } function imageErro() { } }
这里我们使用了一个闭包的小技巧,我们用局部变量canvas保存了对this的引用(在图片加载完成后this已经指向图像本身了)。
添加了图像加载完成事件,其中我们首先将图像绘制到canvas画布上(这里我们添加了一个style属性:fixedSize;如果fixedSize设置为true,我们就不对图像进行缩放了)。
之后将图像作为一个资源保存起来。
这样我们的图像类也就制作完成了。
呼呼,一天写了两篇随笔。下次的随笔给大家带来些高级的应用:二三维一体化——通过我们设计好的渲染器实现几何图形的二维三维的互换(PS.由于还没有怎么实现、可能编码周期有点长大、敬请关注~)
尝试一下:下载这次的源码,修改里面的demo开开能否有新的发现(bug?呵呵~)
下随笔次预告:1. 矢量图形二三维一体化。
本次随笔的所有源码+demo,请点击下载。