构建Canvas矢量图形渲染器(五) —— 添加图片,几何元素的外接矩形,初步性能优化,性能测试。

    本系列目录大家有需要的就看看哈。

    本节主要解决的有三个问题,求任意几何图形的外接矩形。这个外接矩形会在性能优化时候用到,当然不仅仅只在这一方面使用。最后对初步优化过的渲染器进行压力测试

    还是先上demo,之前的demo在firefox下滚动缩放漏做了。现在更新的可以再ie9,chrome,firefox中使用(当然需要支持canvas)。

    1.第一个demo表示了几何图形的外接矩形。大家发现点和图片没有外接矩形,因为他们的范围表示成一个点,均为中心点。

    为什么图片也没有范围呢?因为异步的图片加载使我们无法在一开始就得知图片的宽高。

 

 

    2.第二个demo 为性能测试demo,点击测试按钮会随机添加500个点和500个五角星,多次点击多次添加(大家悠着些啊,别把浏览器点崩了。。)。

    可以发现在我们放大2倍左右后,性能还是比较不错的,因为这里做了个小优化,通过上面我们取得的外接矩形和当前视图的范围做相交比较,只有和当前视图范围相交的几何图形才会被绘制。

 

     
    恩恩,大家测试完后对canvas的矢量绘图是更有信心了?还是觉得用canvas做矢量图形渲染本身就是一BUG?希望大家写写自己的看法,当然这个只是初步的性能优化有待提高的地方还是很多的。

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请点击下载



    

posted @ 2012-04-22 18:05  豆豆狗  阅读(4006)  评论(5编辑  收藏  举报