本系列目錄,大家有需要的就看看哈。
本節主要解決的有三個問題,求任意幾何圖形的外接矩形。這個外接矩形會在性能優化時候用到,當然不僅僅只在這一方面使用。最后對初步優化過的渲染器進行壓力測試。
還是先上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,請點擊下載。