上次的隨筆介紹了如何用中點畫圓的算法提高Canvas繪圖性能,感覺大家還是比較感興趣的。
本節借助HTML5 canvas 強大的像素處理能力,重點給大家介紹計算機圖形中-光柵學Bresenham算法;並實現兩點畫直線的程序。
光柵圖形學(2)Bresenham算法畫直線
Bresenham算法是計算機圖形學典型的直線光柵化算法,其歷史可以追溯到上個世界,由 Jack E. Bresenham 1962年在IBM工作時提出。算法的歷史這里就不做更多的介紹了,有興趣的同學可以看這個wikipedia的鏈接,里面有很詳細的介紹,我們現在就關注其實現的理論。
1.Bresenham 算法的實現原理
用通俗的方法解釋Bresenham 算法:由於計算機屏幕的特殊性,像素不可能有小數的顯示,比如:我們無法顯示0.5像素在屏幕上。這樣我們可以假設一條線段的斜率為k,當這條直線在X方向增加(或減少)1個像素的同時;其Y方向只可能增加(或減少)1或是0,它取決於實際直線與最近光柵網格點的距離,這個距離的最大誤差為0.5。有了感性的認識,下面我們在數學的世界中計算這個Y方向的改變量。
Bresenham 算法的數學實施:(為了能理解下面的內容可能需要一些基本的數學概念,如果已經忘了也沒有關系我會盡可能簡單的說明)
1。假設空間中有兩點P1和P2,則我們可以求得斜率k,假設0<k<1,這樣我們只需要考慮x方向每次遞增1,y的方向每次是遞增1或是0。
2。設: 直線方程為:y = kx + b。
直線當前點為(xi, y) —— 此點是未經過離散后的點。
直線當前光柵點為(xi, yi) —— 此點是經過離散后的點。
則: 下一個直線的點應為(xi+1, k(xi + 1) + b),
由於光柵點只可能位於yi,或yi+1的位置,我們用變量 d_upper 表示真實點的坐標與(yi + 1)離散點坐標的距離:d_upper = (yi + 1) - (k(xi + 1) + b);用d_lower 表示真實點的坐標與(yi)離散度坐標的距離:d_lower = (k(xi + 1) + b) - yi。
這樣一來我們就可以明確該如何選擇點:如果d_upper > d_lower,則表示真實點距yi的位置更近我就選擇yi為下一離散點的y坐標;反之選擇yi + 1。
所以我們程序中需要求: d_lower - d_upper = 2k(xi + 1) - 2yi + 2b -1(化簡后的結果,如果大家不相信可以自己算算哈)。(1)
設:p1,p2之間x方向的距離為 dx,y方向的距離為dy,則斜率k = dy / dx;
我們將所求的的算式(1)乘以系數dx,命名為變量 Pi(第i步的決策參數) = dx(d_lower - d_upper) = 2*dy*xi - 2*dx*yi + c 。(2)(c=2*dy + dx(2b - 1),c為一常數在程序計算時會省去)。
通過算式(2)我們可以得出Pi+1(第i + 1時的決策參數) = 2 * dy * Xi+1 - 2*dx * Yi+1 + c (3)
將算式(3)與算式(2)做差並化簡后得 :
(4)(呼呼,終於推倒出我們要的算式了,不知道大家看懂推導過程了沒,其實都很簡單就是步驟多了些)
可以通過算式(2)得出p0 = 2 dy - dx。
之后我們就遞歸的調用計算決策參數的算法就可以啦,通過互換x、y的位置和坐標的位置可將其算法擴展到任意象限。
2.Bresenham的程序實現
// 使用 Bresenham 算法畫任意斜率的直線(包括起始點,不包括終止點) function Line_Bresenham(x1, y1, x2, y2, color) { var x = x1; var y = y1; var dx = Math.abs(x2 - x1); var dy = Math.abs(y2 - y1); var s1 = x2 > x1 ? 1 : -1; var s2 = y2 > y1 ? 1 : -1; var interchange = false; // 默認不互換 dx、dy if (dy > dx) // 當斜率大於 1 時,dx、dy 互換 { var temp = dx; dx = dy; dy = temp; interchange = true; } var p = 2 * dy - dx; for(var i = 0; i < dx; i++) { putpixel(x, y, color); if (p >= 0) { if (!interchange) // 當斜率 < 1 時,選取上下象素點 y += s2; else // 當斜率 > 1 時,選取左右象素點 x += s1; p = p + 2 * dy - 2 * dx; } if (!interchange) x += s1; // 當斜率 < 1 時,選取 x 為步長 else y += s2; // 當斜率 > 1 時,選取 y 為步長 p += 2 * dy; } }
這段代碼考慮到任意斜率的情況,大家可以調調看看對不同斜率的直線是如何做處理的。
下面這段簡單的demo是對這兩次隨筆的一個綜合應用(當然還很不完善,大家領會意思就好):
下次隨筆預告:1.多邊形繪制。
2.填充色。